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
Very nice sounds indeed.
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
, edit the last line. Replace > /dev/dsp
with | aplay -r 64000
and try it again. If you are on OSX, use | sox -t raw -r 64k -c 1 -e unsigned -b 8 - -d
after installing Sox. 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.
k
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.
Kernighan didn't write C
Yeah, be he co-wrote the book on it.
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
If you're using PulseAudio, you can replace the "> /dev/dsp" with "| pacat"
It works a little better if you have a pulseaudio server running.
In deb-based distros, pacat is part of the pulseaudio-utils package.
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
[Note: The following phrase originally linked to a TED talk by Bobby McFerrin] really easy to make something that sounds good [Note: End of linked text.]
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.
I found interesting equations to create digital sound and your algorithm to generate pentatonic scale notes and make them sound beautiful should be used to improve those new kind of equations. I will try to mix them: http://js.postbit.com/digital-computer-music-with-bitwise-operators.html
Hi. Thank you for inspiration. After reading your post I have written several scripts on sed --- a more esoteric language than awk --- with the similar purpose and with similar means.
(I have little idea of the concept of "music" though, so it was twice as challenging for me.)
Well, it's not so nice sounds but, anyway, have a look: - https://github.com/Circiter/music-in-sed -- an attempt to implement a kind of "piano" synthesizer (it takes a sequence of notes and plays them), - https://github.com/Circiter/cw-in-sed -- a utility to play Morse-code of a text message, - https://github.com/Circiter/r2d2-in-sed -- r2d2 "voice" synthesizer.