#!/usr/bin/env ruby # bess1.rb -- some examples from clm/rt.lisp and clm/bess5.cl # Copyright (C) 2002--2009 Michael Scholz # Author: Michael Scholz # Created: Sun Sep 15 19:11:12 CEST 2002 # Changed: Tue Sep 29 02:05:49 CEST 2009 # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # Commentary: # Requires sndlib.so and libxm.so! # # This file provides simple mono real time output to DAC. Tempo, # frequency, amplitude, and FM index can be controlled via sliders. # The music algorithms are taken from clm/rt.lisp and clm/bess5.cl. # # Bess.new.start -- starts a Motif widget with two DAC tests. # # Bess.new.start(:srate, $clm_srate # 22050 # :bufsize, $clm_rt_bufsize # 128 # :sample_type, $clm_sample_type # Mus_lshort # :which, :agn # :agn or :vct_test # :play, false) # Code: def warn(*args) str = format(*args) << ($! ? ": #{$!}" : "") << "\n" str << (($@ and $DEBUG) ? "\n[#{$@.join("\n")}]" : "") $stdout.print str $! = nil end def die(*args) warn(*args) exit 1 end def rbm_require(lib) puts "loading #{lib.inspect}" if $VERBOSE require lib.to_s rescue ScriptError die "\aScriptError" end rbm_require "sndlib" $output = nil # holds fd from mus_audio_open_output() $clm_srate = 22050 $clm_sample_type = Mus_lshort $clm_rt_bufsize = 128 module Bess_utils def rbm_random(n) mus_random(n).abs end def get_args(args, key, val) if(key == :help and (args == key or args.member?(key) or args.assoc(key))) val = true elsif(args.member?(key)) x = args[args.index(key) + 1] val = ((x == nil) ? val : x) elsif(args.assoc(key)) val = (args.assoc(key)[1] rescue val) end val end def seconds2samples(sec) sr = (mus_srate() rescue $clm_srate) (sec * sr).round end def envelope_interp(*args) x = args[0] env = args[1] base = args[2] if (not env) or env.empty? 0.0 elsif x <= env[0] or env[2..-1].empty? env[1] elsif env[2] > x if env[1] == env[3] or (base and base == 0.0) env[1] elsif (not base) or base == 1.0 env[1] + (x - env[0]) * ((env[3] - env[1]) / (env[2] - env[0])) else env[1] + ((env[3] - env[1]) / (base - 1.0)) * ((base ** ((x - env[0]) / (env[2] - env[0]))) - 1.0) end else envelope_interp(x, env[2..-1]) end end include Math # simple violin, see snd/fm.html def make_rt_violin(dur = 1.0, freq = 440.0, amp = 0.3, *args) fm_index = get_args(args, :fm_index, 1.0) amp_env = get_args(args, :amp_env, [0, 0, 25, 1, 75, 1, 100, 0]) frq_scl = hz2radians(freq) maxdev = frq_scl * fm_index index1 = maxdev * (5.0 / log(freq)) index2 = maxdev * 3.0 * ((8.5 - log(freq)) / (3.0 + freq / 1000.0)) index3 = maxdev * (4.0 / sqrt(freq)) carrier = make_oscil(:frequency, freq) fmosc1 = make_oscil(:frequency, freq) fmosc2 = make_oscil(:frequency, freq * 3.0) fmosc3 = make_oscil(:frequency, freq * 4.0) ampf = make_env(:envelope, amp_env, :scaler, amp, :duration, dur) indf1 = make_env(:envelope, [0, 1, 25, 0.4, 75, 0.6, 100, 0], :scaler, index1, :duration, dur) indf2 = make_env(:envelope, [0, 1, 25, 0.4, 75, 0.6, 100, 0], :scaler, index2, :duration, dur) indf3 = make_env(:envelope, [0, 1, 25, 0.4, 75, 0.6, 100, 0], :scaler, index3, :duration, dur) pervib = make_triangle_wave(:frequency, 0.5, :amplitude, 0.0025 * frq_scl) ranvib = make_rand_interp(:frequency, 16.0, :amplitude, 0.005 * frq_scl) lambda do | | vib = triangle_wave(pervib) + rand_interp(ranvib) env(ampf) * oscil(carrier, vib + env(indf1) * oscil(fmosc1, vib) + env(indf2) * oscil(fmosc2, 3.0 * vib) + env(indf3) * oscil(fmosc3, 4.0 * vib)) end end end # class Agn is a simplified translation of clm/bess5.cl and # clm/clm-example.lisp. class Agn include Bess_utils def initialize @tempo = 0.25 @amp = 1.0 @freq = 1.0 @index = 1.0 @play = false @lim = 256 @time = 60 @octs = Array.new(@lim + 1) do |i| (4 + 2 * rbell(rbm_random(1.0))).floor end @rhys = Array.new(@lim + 1) do |i| (4 + 6 * rbm_random(1.0)).floor end @amps = Array.new(@lim + 1) do |i| (1 + 8 * rbell(rbm_random(1.0))).floor end @pits = Array.new(@lim + 1) do |i| [0, 0, 2, 4, 11, 11, 5, 6, 7, 9, 2, 0, 0].at((12 * rbm_random(1.0)).floor) end @begs = Array.new(@lim + 1) do |i| if rbm_random(1.0) < 0.9 (4 + 2 * rbm_random(1.0)).floor else (6 * rbm_random(4.0)).floor end end end # called by XtAppAddWorkProc def rt_send2dac(func) if @play mus_audio_write($output, vct2sound_data(vct_map!(make_vct($clm_rt_bufsize), func.call), make_sound_data(1, $clm_rt_bufsize), 0), $clm_rt_bufsize) false else mus_audio_close($output) $output = nil true end end # see clm/rt.lisp def make_vct_test(*args) srate = get_args(args, :srate, $clm_srate) bufsize = get_args(args, :bufsize, $clm_rt_bufsize) sample_type = get_args(args, :sample_type, $clm_sample_type) $clm_srate = set_mus_srate(srate).to_i $clm_rt_bufsize = bufsize $output = mus_audio_open_output(Mus_audio_default, srate, 1, sample_type, bufsize * 2) mode = [0, 12, 2, 4, 14, 4, 5, 5, 0, 7, 7, 11, 11] pits = Array.new(@lim + 1) do rbm_random(12.0).floor end begs = Array.new(@lim + 1) do 1 + rbm_random(3.0).floor end cellbeg, cellsiz, cellctr = 0, 6, 0 func = nil len = dur = 0 lambda do | | if len > 1 len -= 1 else dur = @tempo * begs[cellctr + 1] cellctr += 1 if cellctr > (cellsiz + cellbeg) cellbeg += 1 if rbm_random(1.0) > 0.5 cellsiz += 1 if rbm_random(1.0) > 0.5 cellctr = cellbeg end func = make_rt_violin(dur, @freq * 16.351 * 16 * 2 ** (mode[pits[cellctr]] / 12.0), @amp * 0.3, :fm_index, @index) len = (seconds2samples(dur) / bufsize).ceil end func end end def tune(x) [1.0, 256.0 / 243, 9.0 / 8, 32.0 / 27, 81.0 / 64, 4.0 / 3, 1024.0 / 729, 3.0 / 2, 128.0 / 81, 27.0 / 16, 16.0 / 9, 243.0 / 128, 2.0].at(x % 12) * 2 ** x.divmod(12).first end def rbell(x) envelope_interp(x * 100, [0, 0, 10, 0.25, 90, 1.0, 100, 1.0]) end # see clm/bess5.cl def make_agn(*args) srate = get_args(args, :srate, $clm_srate) bufsize = get_args(args, :bufsize, $clm_rt_bufsize) sample_type = get_args(args, :sample_type, $clm_sample_type) $clm_srate = set_mus_srate(srate).to_i $clm_rt_bufsize = bufsize $output = mus_audio_open_output(Mus_audio_default, srate, 1, sample_type, bufsize * 2) die("can't open DAC (%s)", $output.inspect) if $output < 0 wins = [[0, 0, 40, 0.1, 60, 0.2, 75, 0.4, 82, 1, 90, 1, 100, 0], [0, 0, 60, 0.1, 80, 0.2, 90, 0.4, 95, 1, 100, 0], [0, 0, 10, 1, 16, 0, 32, 0.1, 50, 1, 56, 0, 60, 0, 90, 0.3, 100, 0], [0, 0, 30, 1, 56, 0, 60, 0, 90, 0.3, 100, 0], [0, 0, 50, 1, 80, 0.3, 100, 0], [0, 0, 40, 0.1, 60, 0.2, 75, 0.4, 82, 1, 90, 1, 100, 0], [0, 0, 40, 0.1, 60, 0.2, 75, 0.4, 82, 1, 90, 1, 100, 0], [0, 0, 10, 1, 32, 0.1, 50, 1, 90, 0.3, 100, 0], [0, 0, 60, 0.1, 80, 0.3, 95, 1, 100, 0], [0, 0, 80, 0.1, 90, 1, 100, 0]] cellbeg, cellsiz, cellctr, whichway = 0, 4, 0, 1 nextbeg = beg = 0.0 func = nil len = dur = 0 lambda do | | if len > 1 len -= 1 else beg += nextbeg nextbeg += [0.025, @tempo * (0.95 + rbm_random(0.1)) * @begs[cellctr]].max dur = [0.025, @tempo * (0.85 + rbm_random(0.1)) * @rhys[cellctr]].max freq = @freq * 16.351 * tune(@pits[cellctr]) * 2 ** @octs[cellctr] dur += dur if freq < 100 ampl = @amp * 10 * [0.003, @amps[cellctr] * 0.01].max ind = @index * rbm_random(1.0) * 3.0 cellctr += 1 if cellctr > (cellsiz + cellbeg) cellbeg += 1 if rbm_random(1.0) > 0.5 cellsiz += whichway end if cellsiz > 10 and rbm_random(1.0) > 0.99 whichway = -2 if cellsiz > 6 and rbm_random(1.0) > 0.999 whichway = -1 if cellsiz < 4 whichway = 1 end end end nextbeg += rbm_random(1.0) cellctr = cellbeg end func = make_rt_violin(dur, freq, ampl, :fm_index, ind, :amp_env, wins[(10 * (beg - beg.floor)).floor]) len = (seconds2samples(dur) / bufsize).ceil end func end end end class Bess < Agn rbm_require "libxm" def initialize super @sliderback = "lightsteelblue" @background = "lightsteelblue1" @which = @proc = nil @shell_app = @form = nil @tl = @ts = @fl = @fs = @al = @as = @il = @is = nil 1.upto(15) do |i| trap(i) do |sig| puts "\nSignal #{sig} received. Process #{$$} canceled." RXtRemoveWorkProc(@proc) if @proc exit 0 end end end def get_color(color) col = RXColor() dpy = RXtDisplay(@shell_app[0]) cmap = RDefaultColormap(dpy, RDefaultScreen(dpy)) warn("Can't allocate #{color.inspect}!") if RXAllocNamedColor(dpy, cmap, color, col, col).zero? Rpixel(col) end def set_label(wid, *args) RXtVaSetValues(wid, [RXmNlabelString, RXmStringCreate(format(*args), RXmFONTLIST_DEFAULT_TAG)]) end def make_label(wid, name) RXtCreateManagedWidget(name, RxmLabelWidgetClass, @form, [RXmNleftAttachment, RXmATTACH_FORM, RXmNbottomAttachment, RXmATTACH_NONE, RXmNtopAttachment, RXmATTACH_WIDGET, RXmNtopWidget, wid, RXmNrightAttachment, RXmATTACH_NONE, RXmNalignment, RXmALIGNMENT_END, RXmNrecomputeSize, false, RXmNbackground, get_color(@background)]) end def make_scale_label(wid) RXtCreateManagedWidget("label", RxmLabelWidgetClass, @form, [RXmNleftAttachment, RXmATTACH_WIDGET, RXmNleftWidget, wid, RXmNbottomAttachment, RXmATTACH_NONE, RXmNtopAttachment, RXmATTACH_OPPOSITE_WIDGET, RXmNtopWidget, wid, RXmNrightAttachment, RXmATTACH_NONE, RXmNbackground, get_color(@background)]) end def make_scale(wid) RXtCreateManagedWidget("scale", RxmScaleWidgetClass, @form, [RXmNleftAttachment, RXmATTACH_WIDGET, RXmNleftWidget, wid, RXmNbottomAttachment, RXmATTACH_NONE, RXmNtopAttachment, RXmATTACH_OPPOSITE_WIDGET, RXmNtopWidget, wid, RXmNrightAttachment, RXmATTACH_FORM, RXmNshowValue, false, RXmNorientation, RXmHORIZONTAL, RXmNheight, 20, RXmNbackground, get_color(@sliderback)]) end # return label and scale widget def make_scales(wid, name, val, callback) label = make_scale_label(make_label(wid, name)) scale = make_scale(label) set_label(label, val.kind_of?(Integer) ? "%4d" : "%4.3f", val) RXtAddCallback(scale, RXmNdragCallback, callback, label) RXtAddCallback(scale, RXmNvalueChangedCallback, callback ,label) [label, scale] end def do_play(*args) if @play case @which when :agn func = make_agn(*args) when :vct_test func = make_vct_test(*args) else func = make_agn(*args) end @proc = RXtAppAddWorkProc(@shell_app[1], lambda do |c| rt_send2dac(func) end) else RXtRemoveWorkProc(@proc) if @proc end end def set_defaults(parent) @tempo = 0.25 @amp = 1.0 @freq = 1.0 @index = 1.0 low_tempo = 0.05 high_tempo = 0.5 low_freq = 0.1 high_freq = 4.0 high_index = 2.0 set_label(@tl, "%4.3f", @tempo) RXmScaleSetValue(@ts, (100 * (@tempo - low_tempo) / (high_tempo - low_tempo)).round) set_label(@fl, "%4.3f", @freq) RXmScaleSetValue(@fs, (100 * (@freq - low_freq) / (high_freq - low_freq)).round) set_label(@al, "%4.3f", @amp) RXmScaleSetValue(@as, (100 * @amp).round) set_label(@il, "%4.3f", @index) RXmScaleSetValue(@is, (100 * (@index / high_index)).round) end def start(*args) @play = get_args(args, :play, false) @which = get_args(args, :which, :agn) # rest args are going to make_vct_test() or make_agn() cargs = [$0] + $* @shell_app = RXtVaOpenApplication("FM", cargs.length, cargs, RapplicationShellWidgetClass, [RXmNallowShellResize, true, RXmNtitle, "FM forever!"]) RXtAddEventHandler(@shell_app[0], 0, true, lambda do |w, c, i, f| R_XEditResCheckMessages(w, c, i, f) end) @form = RXtCreateManagedWidget("form", RxmFormWidgetClass, @shell_app[0], [RXmNresizePolicy, RXmRESIZE_GROW, RXmNbackground, get_color(@background)]) play = RXtCreateManagedWidget("play", RxmToggleButtonWidgetClass, @form, [RXmNtopAttachment, RXmATTACH_FORM, RXmNleftAttachment, RXmATTACH_FORM, RXmNrightAttachment, RXmATTACH_NONE, RXmNbottomAttachment, RXmATTACH_NONE, RXmNbackground, get_color(@background)]) radio = RXmCreateRadioBox(@form, "radio", [RXmNorientation, RXmHORIZONTAL, RXmNtopAttachment, RXmATTACH_FORM, RXmNleftAttachment, RXmATTACH_WIDGET, RXmNleftWidget, play, RXmNrightAttachment, RXmATTACH_NONE, RXmNbottomAttachment, RXmATTACH_NONE, RXmNbackground, get_color(@background)]) p_agn = RXtCreateManagedWidget("agn", RxmToggleButtonWidgetClass, radio, [RXmNtopAttachment, RXmATTACH_FORM, RXmNleftAttachment, RXmATTACH_FORM, RXmNrightAttachment, RXmATTACH_NONE, RXmNbottomAttachment, RXmATTACH_NONE, RXmNbackground, get_color(@background)]) p_test = RXtCreateManagedWidget("test", RxmToggleButtonWidgetClass, radio, [RXmNtopAttachment, RXmATTACH_FORM, RXmNleftAttachment, RXmATTACH_WIDGET, RXmNleftWidget, p_agn, RXmNrightAttachment, RXmATTACH_NONE, RXmNbottomAttachment, RXmATTACH_NONE, RXmNbackground, get_color(@background)]) quit = RXtCreateManagedWidget(" quit ", RxmPushButtonWidgetClass, @form, [RXmNtopAttachment, RXmATTACH_FORM, RXmNleftAttachment, RXmATTACH_WIDGET, RXmNleftWidget, radio, RXmNrightAttachment, RXmATTACH_FORM, RXmNbottomAttachment, RXmATTACH_NONE, RXmNbackground, get_color(@background)]) sep = RXtCreateManagedWidget("sep", RxmSeparatorWidgetClass, @form, [RXmNleftAttachment, RXmATTACH_FORM, RXmNbottomAttachment, RXmATTACH_NONE, RXmNtopAttachment, RXmATTACH_WIDGET, RXmNtopWidget, radio, RXmNrightAttachment, RXmATTACH_FORM, RXmNheight, 4, RXmNorientation, RXmHORIZONTAL]) RXmToggleButtonSetState(play, @play, true) RXtAddCallback(play, RXmNvalueChangedCallback, lambda do |w, c, i| @play = Rset(i) set_defaults(sep) if @play do_play(*args) end) RXmToggleButtonSetState(p_agn, @which == :agn, true) RXtAddCallback(p_agn, RXmNvalueChangedCallback, lambda do |w, c, i| @which = c if Rset(i) @play = false RXmToggleButtonSetState(play, @play, true) end, :agn) RXmToggleButtonSetState(p_test, @which == :vct_test, true) RXtAddCallback(p_test, RXmNvalueChangedCallback, lambda do |w, c, i| @which = c if Rset(i) @play = false RXmToggleButtonSetState(play, @play, true) end, :vct_test) RXtAddCallback(quit, RXmNactivateCallback, lambda do |w, c, i| RXtRemoveWorkProc(@proc) if @proc exit 0 end) low_tempo = 0.05 high_tempo = 0.5 low_freq = 0.1 high_freq = 4.0 high_index = 2.0 @tl, @ts = make_scales(sep, " tempo:", @tempo, lambda do |w, c, i| @tempo = low_tempo + Rvalue(i) * (high_tempo - low_tempo) * 0.01 set_label(c, "%4.3f", @tempo) end) RXmScaleSetValue(@ts, (100 * (@tempo - low_tempo) / (high_tempo - low_tempo)).round) @fl, @fs = make_scales(@ts, " freq:", @freq, lambda do |w, c, i| @freq = low_freq + Rvalue(i) * ((high_freq - low_freq) * 0.01) set_label(c, "%4.3f", @freq) end) RXmScaleSetValue(@fs, (100 * (@freq - low_freq) / (high_freq - low_freq)).round) @al, @as = make_scales(@fs, " amp:", @amp, lambda do |w, c, i| @amp = Rvalue(i) * 0.01 set_label(c, "%4.3f", @amp) end) RXmScaleSetValue(@as, (100 * @amp).round) @il, @is = make_scales(@as, " index:", @index, lambda do |w, c, i| @index = Rvalue(i) * high_index * 0.01 set_label(c, "%4.3f", @index) end) RXmScaleSetValue(@is, (100 * (@index / high_index)).round) do_play(*args) RXtManageChild(radio) RXtRealizeWidget(@shell_app[0]) RXtAppMainLoop(@shell_app[1]) end end begin # Bess.new.start(:srate, $clm_srate, # :bufsize, $clm_rt_bufsize, # :sample_type, $clm_sample_type, # :which, :agn, # :play, false) Bess.new.start end # bess1.rb ends here