Sunday, June 15, 2014

Porting the MusicBox app to GA144


The pre-cursor to the GreenArrays  GA144 was the SeaForth S40C18. The SeaForth had 40 nodes whereas the GA144 has 144 but they have the same instruction set. Chuck Moore parted ways with Intellasys, the company which produced the SeaForth, a couple of years ago and the acrimonious lawsuit which resulted was settled last year.

One of the demo apps which was included in the VentureForth compiler kit was a musicbox app. It uses a synthesised plucked string algorithm to generate random but quite pleasant 'plucked string' music.

I've always liked the app so I decided for my own education to port it to the GA144 using ArrayForth.

The first thing I discovered was how great the divergence there has been between VentureForth, the version of Forth used on the SeaForth chip and ArrayForth, as used on the GA144. In addition ArrayForth is a closed universe. It's almost impossible to import any program files into aF. They have to be hand-typed, whereas vF is ANSI text based and I could use standard text editors such as Vim to manipulate the program code. However the need to understand each instruction meant hand-typing was a relatively small hurdle. The bigger task was to understand what the instruction meant in vF and replace it with the equivalent aF instruction. While the instruction set is one-to-one compatible, the compiler directives in vF are completely different and in some cases there is no equivalent. This caused me a few headaches.

The other handicap I faced is my lack of knowledge of Forth. In the end this wasn't a big problem because the code for MusicBox is designed for Forth chips like the SeaForth and GA144 and doesn't rely on a lot of standard Forth familiarity.

Overview of code

The code uses the best feature of the GA144, namely the ability to offload work onto adjacent nodes while continuing with another task. The "central" node, called the composer, decides which note to play next. It then relays this choice to one of six "plucked string" synthesis nodes which generate code using the Karplus-Strong algorithm. The resulting streams of note-generation code are fed to a moving average filter node which feeds the result to a 'pre-dac' node which converts the PCM music code to PWM code which is in turn fed to a node which controls one of the digital-analog converters (dac). The output of the dac is fed to headphones or a speaker.

Eighteen nodes are used. The composer node uses "random" input from an ADC to select the next note to play from a list of harmonically related notes. There are no "discordant" notes.

The output of composer is sent to a router node which keeps track of which nodes are busy synthesising notes and channels the next note to the next free node.

There are six "pluck" nodes. The KS algorithm uses a one-sample delay and so there are six "delay" nodes, one for each "pluck" node. Similarly the MA filter node requires a one-sample delay node.

The output of the MA filter node is passed to a "pre-dac" node which calculates the three parameters needed to drive the dac node. The calculations are time-expensive and are thus off-loaded to a separate node rather than attempting to run them in the 'dac' node.

Thus 1 composer, 1 router, 6 pluck, 1 filter, 7 delay, 1 predac, 1 dac = 18 nodes. Composer node has to be a node with analog input and obviously the dac node must have analog output. This constrains where in the GA144 the nodes can be. Thus I chose the following path:

717 (composer), 617 (router), 616, 615, 614, 613, 612, 611 (pluck), 610 (filter), 609 (predac), 709 (dac). Delay nodes are: 710, 711, 712, 713, 714, 715 and 716.


bc (bit count, by Michael Montvelishsky) The note to be played is chosen by counting bits in the number on top of the stack. That bitcount is used to index the table of frequencies at the beginning of the code, and that frequency is passed on to the router node, to be given to one of the voice nodes.

note Use note number to lookup frequency then send it on to the router. 400 is a constant used to determine the length of a rest, and therefore the tempo. Decrease constant to play faster.

play The actual note to play is derived by counting the bits in the number on top of the stack. If the new note, determined by counting the bits in 'new' is the same as the old then play a rest i.e. be quiet. Otherwise give the< new note number to 'note'. The note played is also left on the top of the stack to be compared with the next note.

piece Read a random number from the adc counter at 'data', "and" it with 511 to keep it reasonable (dac has only 512 levels). Store number in A register to be used as an increment to find the next note in the "piece".

compose Play 127 notes, beginning with 0 and incrementing the "note" number by the amount stored in the A register by piece. The actual note is determined by counting the bits in the number fed to play.
1204 list 
musicbox - plucked string synthesis
713 node 0 org
lookup table of frequency data
27400 , 27400 , 24500 , 21800 ,
19400 18300 , 16300 , 14500 ,
13700 , 12200 , 10900 , 9700 ,
9100 , 8100 , 7200 , 6800 ,
6100 , 5400 ,
bc 12 bitcount dup dup or - for
dup push zif drop pop - ;
then pop and next
 17 a push a! . 18 @ !b pop a!
 400 ;
 1b dup for
dup for unext dup or -
1f drop ;
 20 bc over over or if
drop dup push note
         drop pop ;
then rest drop drop ;
 28 random adc
       data a! @ 1ff and a! ;
compose 2c a dup dup or 
notes 127 for
2f over play 30 push
        a . + pop
next drop drop ;
start 33 e000 !b down b! rest
begin piece compose end 3a


ring The address following ring is a variable that holds the next address to
be executed as a coroutine in the list that follows the variable. When
used in voice and force, the effect is to cycle through the
numbers in the "tables", returning the next number each time voice or
force is executed.

+note  sends the note-on message on to the mixer chain, along with a
voice number and force number, telling the chain which node should
process this note and how loud it should be.

The main loop of the router first checks io register to see if the composer is
requesting attention. If so then a note is received from the composer
and passed on to the appropriate voice node. In either case a "play"
message is sent on to the next node in the mixer chain, to keep the
note samples going.

1216 list 
router 613 node 0 org
ring pop b! @b push ex pop !b ; 
voice ring
5 ,
r1 0 ex 1 ex 2 ex 3 ex 4 ex 5
   ex r1 ;
force ring
14 ,
f1 120 ex 100 ex 80 ex
    70 ex 60  ex 50 ex f1 ;
+note 4 voice @p ! ! ' a relay '
! @p ! . ' @p a! @p . '
' w lit ! ! @p ! @p a!
! force ! ;
start 2c up a! io down
over b! @b 2* 2* 2* 2* -if
33 over a push a! @ pop
         a! +note
then @p ! dup . ' @p play '
or !
end 3a


Pluck uses the Karplus-Strong algorithm,
A better explanation is at - Start with a buffer full of random numbers which is equivalent to an energetic string pluck, read through the buffer using the values as sample values, average each value with the previous value and write it back to the buffer as well as forwarding the sample to the dac player. Over time the averaging is equivalent to a low-pass filter and will remove the higher frequencies until eventually the waveform will be flat i.e. the string has stopped vibrating.

1214 list 
pluck - karplus-strong string synthesis
0 org
pluck dup push . + 2/ pop a -if
drop 2/ 2/ ;
then push zif
 05 over push push drop pop pop ;
then pop a! drop drop @p drop @p
 dup !p ; 3ffff , rnd 0b -if
2* 2cd81 or @p
then 2* dup .. drop !rnd 1ff and dup ;
 12 @p !b @b . ' !+ @ !b .. ' 1ff and ex
@p !b !b . ' @p .. ' ex
@p !b @b .. ' @ !b .. '
2/ 2/ 2/ 2/ 2/ 2/ 2/ 2/ 2/ 1ff and ex
8 for 2* unext
@p .. ' @p . + . ' !b !b ex rwrw ;
play 26 @p drop !p
 27 dup @p + ;
0 ,
pop drop push -if
1ffff and over b! pop ex pluck ex push
then push over b! pop @p . +
w 33 1ffff ,
pop mix @p !b !b mxplay 36 ' @p play ' 37

A full listing is on GitHub:

No comments: