Sniff is a "Scratch-like" programming language that's designed to help Scratchers move gently from Scratch to more conventional languages. They can start writing programs, without having to learn a new language because Sniff is based on Scratch. They learn a little more about variables, compiling, syntax errors (!), and they can have fun controlling real hardware while they're doing it.

Tuesday, 19 August 2014

Arduino Music

When working on the Gamebuno drivers I was really impressed by their audio driver, which can play up to four part harmonies by directly driving one of the Arduino's output pins from interrupts. While not exactly orchestral, the results are pretty authentic 8-bit classic video game goodness. It works on a standard Uno - just connect a piezzo, or an audio amplifier to pin 3.

However while their backend is excellent, their API is pretty confusing, and undocumented. It involves entering midi notes into an Excel spreadsheet, and then copying and pasting the results of the calculation into C data structures... In porting the code to work with Sniff, I simplified their backend, removing some of the less essential features, and build an API that should make it easy for anyone to play simple tunes. If anyone remembers programming music in Ample on a BBC micro, then the notation should be a little familiar...

Lets start of with the basic declarations:

make player avrSound device
make tune string
make channel number
make noteLength number
make soundPlaying boolean

We start by making a player device - for the moment that's avrSound, but in future we might have midiSound, or piSound device. We have variables: tune, channel, and noteLength which control the playing of notes, and soundPlaying which we'll use to check if the audio is still playing.

when start
..tell player to "update"
..wait 0.05 secs

when start
.set noteLength to 8
.set tune to " cEGCgec"
.set channel to 1
.tell player to "playTune"

Sounds play automatically in the background, but we need to regularly call update to change the notes, or sound thats playing. Every 20th of a second is recommended, so the easiest way to do that is just to make a script which just runs forever calling "update".

Then to play a sound we set the default note length which is measured in update ticks, so in this case its 8x0.05 secs which is 0.4 of a second. 8 is a good choice, as if we need to play something with smaller notes it subdivides nicely (12 would also be good, as it would allow for triplets). You can find tune the tempo by changing the tick speed. We also have to specify the channel - in this case I'm using channel 1, but you can use 1,2 or 3 (if you need 4, then you can make a minor edit to the device source code, but it will use more resources).

We specify the notes by their name, and each lasts the specified note duration. Here we start on a C, and play a major arpeggio up to the top C. We then go back down again - using lower case to indicate these are falling notes, while we used upper case notes to choose a rising pitch. If you want sharps or flats, then place a + or - before the note. If you want to change pitch by more than an octave then use > or <.

Then just call playTune, and the tune will play in the background. If you want to know that the sound has finished, then you can:

..wait until not soundPlaying

Here's a more complex example:

..set tune to " .cEF|G///|.cEF|G///|.cEF|G/e/|c/E/|d///|.EEd|c///|E/G/|Gf//|..eF|G/e/|c/E/|c///|/"

A "/" means to increase the note length by one noteLength, so you can set noteLength to the shortest note, and specific the rest using /. A dot is a rest, and a vertical bar is a bar line: you don't need to include bar lines, as they don't actually have any effect on the sound but they're very handy to help you keep track of where you are in the piece! You can also include spaces to keep things lined up, but again they have no meaning.

make fancy boolean

when start
.set fancy to no
.repeat 2
..set noteLength to 8
..set tune to " .cEF|G///|.cEF|G///|.cEF|G/e/|c/E/|d///|.EEd|c///|E/G/|Gf//|..eF|G/e/|c/E/|c///|/"
..set channel to 1
..tell player to "playTune"
..change channel by 1
..set tune to "<....|C.g.|C.g.|C.g.|C.g.|C.g.|C.g.|g.D.|ggAB|C/g/|C/g/|f/C/|ffAB|C/g/|g/B/|C/..|."
..tell player to "playTune"
..change channel by 1
..if fancy
...set noteLength to 4
...set tune to ">........|..CbC.Cb|C.......|..CbC.Cb|C.......|..CbC.Cb|C.C/b/a/|g/////"
...tell player to "playTune"
..wait until not soundPlaying
..set fancy to yes

Here's an example that uses three channels, and includes a repeat. The first time round we place the tune and the bass line. At the end of the loop we wait for the first verse to finish, then go back around again play three parts.

Remember tunes are just strings, so you could write code to choose notes and add them to a tune, or even write out several shorter phrases, and join them together before playing them...

The avrSound device is included in the Beta 8 release.

No comments:

Post a Comment