# AWK Music

February 12, 2011: Finally got around to cross posting this.
February 23, 2011: Aplay clarification.

Original Editable
version 1 & 2 of 3

Originally posted on reddit

## "I've enjoyed this sound so much that I just left the script running for the last 3 hours." -- eleven357

Listen to a sample MP3

Copy and paste into your terminal and run. It can keep playing pseudo random tones forever, though it might loop after a few decades.

``````awk 'function wl() {\
rate=64000; \
return (rate/160)*(0.87055^(int(rand()*10)))}; \
BEGIN {\
srand(); \
wla=wl(); \
while(1) {\
wlb=wla; \
wla=wl(); \
if (wla==wlb) \
wla*=2; \
d=(rand()*10+5)*rate/4; \
a=b=0; c=128; \
ca=40/wla; cb=20/wlb; \
de=rate/10; di=0; \
for (i=0;i<d;i++) {\
a++; b++; di++; c+=ca+cb; \
if (a>wla) \
{a=0; ca*=-1}; \
if (b>wlb) \
{b=0; cb*=-1}; \
if (di>de) \
{di=0; ca*=0.9; cb*=0.9}; \
printf("%c",c)}; \
c=int(c); \
while(c!=128) {\
c<128?c++:c--; \
printf("%c",c)};};}' > /dev/dsp
``````

Some notes on running it: It should chime about once a second. If the speed is messed up, vary the `rate` variable on the second line. No clue what it depends on, but values seem to range between 4000 and 256000. Forcing a bitrate with `pv` might help. If it spits out an error about `/dev/dsp` try `| aplay` instead, edit the last line. Replace `> /dev/dsp` with `| aplay` and try it again. If it stutters (though you'll need to be on a low end pentium 2), use `mawk` instead of `gawk`. It'll run about four times faster. You can also get rid of all the trailing slashes to make a scary looking one-liner.

xsa (2011-02-23-11-37-56-459)

I'm not a programmer but really want to listen to your program. I'm getting the /dev/dsp error. You say to try aplay instead. Don't know what that means. Where do I insert aplay into the code? Thanks so much!

One day I sat down to learn Awk, a language with a reputation for terse but powerful one liners. It was a pleasant surprise to find an entire "conventional" language underneath with loops, branching and math operations common to all imperative languages. Kernighan was a primary author, so it is no surprise it has a very C like syntax. Unlike C it is completely untyped. There are positional variables (`\$1`) similar to what is found in shells. Regex can be used anywhere. Everything fits together perfectly for maximum convenience while processing text.

But this is sound, not text. So what is going on here? Sound is just a stream of numbers. Text is also a stream of numbers. If you've ever tried making white noise with `cat /dev/urandom > /dev/dsp` this is just an extension of the idea. In fact, the very first version of this script was

`cat /dev/urandom | fold -b1 | awk '{for (i=0;i<100;i++) printf(\$1);}' > /dev/dsp`

which has the effect of holding each random value for 100 cycles, lowering the pitch of the noise. While Awk was made for processing text, it has a general purpose language backing it up. To illustrate a few of these features, here is the oldest version of the script:

``````awk 'BEGIN {srand(); \
while(1) { \
wl=400*(0.87055^(int(rand()*10)+1)); \
d=(rand()*80000+8000)/wl; \
for (i=0;i<d;i++) { \
for (j=0;j<wl;j++) \
{printf("a")}; \
for (j=0;j<wl;j++) \
{printf("z")}; };};};' \
> /dev/dsp
``````

This makes sound, but it pretty crude. It plays random square waves for random durations. The `BEGIN{}` stanza gives a chunk of code for Awk to execute before processing standard input. Sadly I don't have the previous iteration of the script, which used `yes | awk '{...}'` instead of `BEGIN`. The third line, `wl=400*(0.87055^(int(rand()*10)+1))`, takes a bit of explaining. A musical scale is exponential in nature, and this function generates the wavelengths of notes on a musical scale. There is one magic number, 0.87055. This is not a random number or even something found by trial and error. It is the fifth root of 0.5. One half is important because every octave the wavelength doubles or halves. Using the fifth root gives five intervals for each octave, a pentatonic scale. Why five notes? It is really easy to make something that sounds good in the pentatonic scale and really hard for a pair of notes to be dissonant. With a wavelength and a duration for the note, a pair of for loops pumps out a square wave. A and Z make up the low and high values.

The more complicated version grew out of this. The added features are triangle waves, two note chords, amplitude decay, and anti-pop. The last item needs a little explaining. Towards the end of the script there is a short loop, `while(c!=128)`, which returns the interrupted triangle wave to center. Without it there will be a sharp popping sound between note transitions. An explanation of the scoping rules regarding the `rate` variable is left as an exercise for the reader.

Since the output is a single byte, this is real 8 bit music. But this particular sound is a bit more mellow than other chip tunes around. It is a wide open area for more experimentation.