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

Original Finally got around to cross posting this. Aplay clarification. Sox and OSX are anagrams. 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.