Creating wave formulas with BWise

by Theo Verelst

It is fun to use BWise to create formulas which are intended to be rendered as musical notes, in this case in line with Making mathematical waves I use the Maxima syntax for formulas but we could also use C or even Exp type of formulea, and because the block approach doesn't require much recompiling of the result or better put formula translation, this shouldn't be too hard.

In this case I approach the making of 3 notes forming achord in a straightforward way, the bocks on the bwise canvas are Maxima formula rendering blocks, their output pins contain Maxima mathematics which one could change into other languages, even in parallel by adding more than 1 blockfunction (I've made an example of that somewhere, this is written quickly) and using the propagation function which uses the right block functions. There are three tones made with sine shape and exponential decay, and the blocks are made by hand, like this:

newproc {set sine1.o "sin((${sine1.freq})*2*%pi*x)"} sine1 freq o
newproc {set sine2.o "sin((${sine2.freq})*2*%pi*x)"} sine2 freq o
newproc {set sine3.o "sin((${sine3.freq})*2*%pi*x)"} sine3 freq o
newproc {set exp1.o "(${exp1.i})*%e^(-3*x)"} exp1 i o
newproc {set exp2.o "(${exp2.i})*%e^(-3*2*x)"} exp2 i o
newproc {set exp3.o "(${exp3.i})*%e^(-3*6*x)"} exp3 i o
newproc {set add1.o "${add1.i1}+${add1.i2}+${add1.i3}"} add1 {i1 i2 i3} 
newproc {set div1.o "(${div1.i})/3"} div1 i o

Which after dragging in plae and conneting looks like this:

Image Bwise chord1bwise.png

(I added a monitor block, too), the canvas can be downloaded here [L1 ].

The result at the output of div1 can be gotten by propagating all data through the network, this can be done automatically by:

foreach i [net_funct div1] {net_funprop $i}

and then get the value by right-clicking for an inspector block on div1, or simply using the console:

(Math) 24 % puts ${div1.o}

Which is the required Maxima formula, which in turn can be fed to the formula rending cgi on my server, and then it is pretty printed:

Image TV Wiki chord1form.gif

Clearly the three components are visible; all an octave apart. Rendering the first 1/10 sec of the resulting graph is like:

Image TV Wiki chord1graph.gif

And finally the above mentioned Tcl server script also creates a 3 seconds sound file, which can be tried out here [L2 ]

Of course the blocks could be made to autogenerate from the canvas popup menu, and maybe given a wizard, and then of course and automatic code generator. The fun is the graph is freely changable and flows through the actual mathematical formula (Maxima is a algebraic manipulating package) and at least easy to read. It is also possible to optimize block results by calling maxima in blocks on the canvas, see Bwise blocks using exec maxima (I've made an improved verison for the server, I don't think this page was upgraded). Of course more work is possible in various directions.

Oh, I just remembered, one must set a frequency at the input of the sine generator blocks, in Herz. I used 440/2 as the base frequency, with parenthesis to make sure the composed formula is correct. One could use A musical keyboard for BWise and Midi connections to make various nots and figure out the frequencies, I didn't make that block as repeatably instantiatable bwise block yet...

Lets look a little closer to how this can work, without going in to the server functionality to render the formulas and in a general way, so we could also make expr formulas from bwise blocks, like in constructing mathematical formulas with Bwise blocks.

We can make a bwise canvas popup menu by first clearing out the standard blocks:

foreach i [info procs new*] {rename $i d$i}

And now add a new function to try out whether it appears on the popup menu:

proc newsine {} {

Indeed, right clicking on the canvas will show a 'Sine' meny entry. Now we should adapt one of the good working new* procs to create a block in place on the canvas and of course make a generator for unique (numbered) instance names, and create a block function generator preferably with some easy to use mechanism to create the correct instance pin-names from the formal arguments and function value(s) of the formula block, and a maxima code generator with the correct feeding through of the arguments in the maxima form function body/prototype.

Anyway, I made this into working prototype by making use of the Automatically generate Bwise blocks from procedures and defining some signal processing mathematical blocks:

proc exp { e } {
    return "%e^(-x*$e)"
proc mult { {a} {b {1}} } {
    return "($a*$b)"
proc plus { {a} {b} } {
    return "($a+$b)"
proc sine { {f} {m} } {
    return "sin($f*2*%pi*(x+$m))"
proc sine1 {f m} {
    return "sin($f*x+$m)"

By source-ing these (for the moment) and pressing 'refresh list' in the bwise function window (see: interactive command composer), one of these can be picked by double-clicking it in the function list, and then a block can be generated by pressing the 'Block' button.

The blocks of the needed kinds (with the above maxima function definitions as function (proc) call in them can be made in any number and connected up by bwise wires. The all left side branches must be rigth-clicked to get the popup schedule menu and then 'funprop'-ed, so that the last block output wil contain the combined formulas in correct maxima form of the desired signal graph.

No we want to render this formula to get a prettyprinted formula (if you have Latex) an example sound from this formula when time is represented in seconds by the x-variable,a nd I want to play the sound from a master/midi keyboard connected with a computer over a DA converter driven by the mianly Linux based 'Jack' soudn connection program, in real time, for which in this case a prgram is automatically created which when started can be connected over jack to a midi source and a audio output channel.

The various Tcl functions needed to make the desired program automatically are:

# \
exec tclsh "$0" "$@"
#   puts stdout "Content-type: text/html\n"

proc domaxima { {m} } {
     set t "display2d:false;\n$m;"
     # return [string range [exec maxima << $t | tail -2  ] 6 end-7]
     return [string range [exec maxima -q << $t ] 32 end-7]
     #return [exec maxima --real-quiet << $t ]

proc domaximafor { {m} } {
     set t "display2d:false;\nlinel:3000;\nfortran(subst((float(%e)),%e,subst((fl
     return [string range [exec maxima -q << $t ] 42 end-18]

proc domaximagraph { {m} } {
     set t "display2d:false;\nlinel:3000;\nplot2d ($m, \[x, 0, 0.1\], \[gnuplot_t
rm, \"gif size 1600,512\"\], \[gnuplot_out_file, \"/dev/shm/gnuplot.gif\"\]);\n

     return [exec maxima --very-quiet << $t ]

proc domaximaformula { {m} } {
     #set t "tex($m);"
     set t "display2d:false;\ntex($m);"
     return [string range [exec maxima -q --very-quiet << $t ] 6 end-6]

proc formake { {e} } {
    global f
    set t [subst -nocommand {
         subroutine sayhello(x,r)
             double precision x,r
             r = $e
    set f [open /dev/shm/sub.f w]
    puts $f [string trim $t \n]
    close $f
    set env(TMP) /dev/shm/
    set env(TEMP) /dev/shm/
    exec gcc -O3 -ffixed-line-length-none -c -o sub.o /dev/shm/sub.f
    exec gcc -O3 -o synthbw synth.c sub.o -lasound -ljack

#   exec gcc -ffixed-line-length-none -c -o /dev/shm/sub.o /dev/shm/sub.f
#   exec gcc -o /dev/shm/fm /dev/shm/sub.o mw.c wav.o -lm
    #exec gfortran -ffixed-line-length-none -c sub.f
    #exec gcc -o fm sub.o main.c -lm
#   return [exec /dev/shm/fm]

proc doformula { m } {
    set t [subst -nocommand {

    set f [open /dev/shm/formula.tex w]
    puts $f [string trim $t \n]
    puts $f $m
    puts $f "\\end{document}"
    close $f
    catch { exec latex -output-directory=/dev/shm /dev/shm/formula  > /dev/null  }
    catch { exec dvigif -T tight --gif /dev/shm/formula.dvi -o /dev/shm/formula.g
if 2>@ stdout > /dev/null }

proc makeprog {form} {
global makeprogout
#    cd /dev/shm/S

    set makeprogout ""
    append makeprogout "[clock format [clock seconds]]:\n" 

    append makeprogout "evaluating maxima expression..\n"
    flush stdout
#   append makeprogout "[set in [gets stdin]]<br>--><br>"
    append makeprogout  "[set r [domaxima $form]]\n"
    append makeprogout  "generating Fortran expression from it..\n"
    set s [domaximafor $r]
    append makeprogout  "generating wave..\n"
    append makeprogout  "[formake $s]\n"
    # append makeprogout "making mp3 from wave ..\n"
    # append makeprogout "[exec ffmpeg -i /dev/shm/math.wav -ab 256k -y /dev/shm/
ath.mp3 >& /dev/null ]\n"
    append makeprogout "making prettyprinted formula ..\n"
    doformula [domaximaformula $r]
    # catch {exec ppmquant 256 /dev/shm/sine.ppm | ppmtogif > /dev/shm/graph1.gif
2> /dev/null}

    append makeprogout end\n
# Makes the cgi-bin script stop the server process thinking there's a connection
#   exit

The above wav.c (actually wav.o) is needed, and this synth program: [L3 ], and wehen all is in plae (current dir) calling makeprog from the above code will generate synthbw which is an executable which willk connct over Jack and Alsa.

A bit more complicated example of a FM synthesizer bwise graph:

Image TV Wiki scrcamfm4.jpg

This is the formula from {makeprog.o} in neat form:

Image TV Wiki formulafm4.gif

And it sound like this [L4 ], you can try it for yourself when you have all parts installed by loading the bwise canvas file [L5 ] .

The synth program now uses doubles to communicate with the fortran routine coming from maxima, and so all computations are carried out in double (64 bit float) to compute the waves, which is very accurate. You need the right maxima version, or it will make constants without a D addition which may be compiled into single precision numbers by gcc.

The midi implementation has no envelope generator, it acts on the gate signal only. It does recognize note velocity, and the modulation wheel is assigned to overall volume, and it is polyphonic, call it for instance like this:

./synthbw 0 6 1.0 12 0.3 0.1 0.4 0.6 .2 0.3 0.3 0.4 0.5 0.2 0.1

call it without arguments to get an arg list (some args don't work in this version). I used fairly recent jack version on Fedora 8/64 to run the whole. Of course maxima and gcc are needed, as well as development fies/headers for Jack and Alsa.

TV 2008-09-30: I've added an additive synthesis example with added velocity sensitive fm modulation, for which the formula must contain a variable 'v' (a double from 0 to 1) with 10 sine blocks:

Image Bwise scradd10.jpg

To create the right multiply factors, I created blocks like above with the 'block' button, and changed the b-inputs like this:

for {set i 2} {$i <=10} {incr i} {set mult$i.b "((v^($i/3))/$i)"}

so that the amplitude of the 9 harmonics depends on how hard a key is struck, individually governed by a different exponential. There is one FM component, reused in the graph, but repeated in the tree-like formula representation, which modulates more with higher velocity.

The project files needed for recompile (when using bwise) and the executable for linux (FC8/64 with Jack/Alsa + dev-headers): [L6 ]

2009-02-01: To get an impression of the wave being generated, I've made this two-liner:

time {set r [domaxima float(makelist(subst(y/44100,x,subst((1/2),v,${mult11.out})),y,0,170))]}
eval ${scope0.bfunc_init} ;foreach i [split [string range $r 1 end-1] ,] { set [expr 300*$i] ; eval ${scope0.bfunc} }

To use from the console AFTER a scope0 canvas block has been created by e.g. pressing 'Scope' in to top button row of Bwise:

Image Bwise maxbwgraph1.png

I haven't taken the time (yet) to make nice blocks (with enumeration and proper pin variables) of this. The main idea is to have maxima compute 171 values from a functions, after having set the virtual synthesizer note velocity value to 0.5 (or rather: 1/2) and creating the time values for the first 171 samples.

Luckily, Maxima then can nicely return a list of values, and even can apply the 'float' command to all at once. The second line clearly puts the result in the scope block for viewing, which can be a longer list than one horizontal screen, but this is only for an impression.

The greatness of the approach, as opposed to the weakness: the 170 computations of a quite not small formula takes a second or two in maxima, is that the whole computation down to the last step takes place in formal algebraic domain with infinite number precision, fractions, special symbolic numbers and error free algebraic manipulation/simplifications applicable! So the rounding takes place on a perfect formula, after all manipulations have been done..

I´ve made this work with windows XP (pro) too, see Wave player for formulas from BWise and Maxima on Windows XP

2009-01-16: To make the above a bit neater but especially to create more complicated algorithms more easily, I make Creating arrays of wave formulas with BWise

TV 2010-09-02: I made a demo here (with some music examples): . The Latest BWise page contains the code for some of the latest extensions.

See Also
An application of these tcl/tk programs in music theory, on that page some more of my latest scripts.