Introduction: Beverly-Crusher: Bit Crushing. 1-bit Arduino Music.

I had been looking for a tool to convert audio down to 1-bit depth but gave up and wrote my own. Supports export for Arduino sketch.

Here I am offering an audio crushing program which also makes exporting to arduino sketch extremely easy so that one can experiment with 1-bit audio and manipulate sample rates rapidly. The github has source to the crusher which compiles under Linux and can allow you to preview your compressed audio through ALSA prior to generating the Arduino sketch. The array of samples that is generated can be used in other embedded projects without specifically being an arduino but you will need to modify the playback code accordingly.

https://github.com/electronoob/beverly-crusher


Step 1: How I Go About Crushing the Audio and Some Backstory.

Inspiration

Having been a fan of sites like instructables for a long time and on several occasions saw that there were projects geared towards generating sound or music from a microcontroller I became sure that someday I will get around to trying this cool stuff myself.

I have worked on audio projects before but this is first time I've gone out of my way to create the tools needed to make it reproducible easily.

One of my previous projects was to use a cheap DDS module from china, change it's frequency and then detect it using a SDR (Software defined radio) on LSB (Lower side band) and it played tetris music. Anyway. I digress lol.

Mostly these projects had in common that they required 8 output pins and resistors to form a DAC, which is pretty awesome and sounds quite nice.. There were however a couple of projects which dealt with 1-bit audio needing only 1 digital I/O pin for it to generate the sound as it is essentially a square wave. I fell in love with this idea due to how it sounds because when I produce music I tend to use a lot of distortion and it fills me with warm fuzzy feelings!

Here we decide what we hope to achieve, I hoped to achieve a downsampling of an audio recording from 24-bit to 1-bit.. I tried to find a tool to do this but struggled and ultimately gave up and started writing my own. Now I have to say that to simplify this process and since I needed to cut up the audio sample to get the part that I actually wanted to play, I used audacity to export a file with the following parameters:

  • unsigned 8 bit
  • raw (header-less)

Of course I also edited out the right hand audio channel before exporting because I was only interested in dealing with a mono audio sample.

Parsing the file

The cool thing about this exported file is that it is very easy to deal with as each byte of the file represents one entire sample of audio, as in.. how much energy or how loud that particular moment of sound is.

An 8-bit or 1-byte sample is really just a value of loudness between 0 and 255, giving you a possible range of 256 values.

Then my program reduces that down from 256 possible values down to 2. On or off.

The only caveat being that you have to make a decision, what constitutes being on and what is discarded by switching it off.

My decision is to pick a place which is roughly in the middle of the 256 values. Let's say for arguments sake that we choose 128 as the cut off point, if a sound sample isn't loud enough to reach at least 128 it is discarded and considered to be off and that is stored away as 0.

If however the sample has sufficient amplitude to peak above the 128 we say okay we consider that to be on enough so we set aside a 1 value.

Step 2: Copy Eight of Our 1bit Samples Into a Byte of Memory.

Bitshifting to populate our 8bit storage space.

Once the samples have been converted to a 1-bit resolution it's now simply a case of going through those converted samples and joining them together to make a string of 8 bits; aka 1 byte of storage.

This will require some knowledge on bitshifting, thankfully there are many resources which teach this simply, one of those being at the arduino website. http://arduino.cc/en/Reference/Bitshift

The basic idea is that we have to conserve space on our microcontroller, as each sound sample obviously takes up space, ...

We could have an array of integers to store our samples in but that would be a gross misuse of storage as we need just a mere fraction of that space to store our audio reproduction.

For more information on storage required for an int see arduino website. http://arduino.cc/en/Reference/int

I chose to specify an unsigned char which is an 8 bit value, or 1 byte of space, but again that is 8 times larger than what we need!

The solution

Get some space allocated in memory, let's say for this tutorial we requested 1 byte of storage space from memory, we proceed as follows.

  • We get our first 1 bit audio sample
  • fill the least significant bit of our 1 byte of storage with the value of our 1 bit audio sample
  • move all of the bits in our 1 byte of storage across to the left by 1 bit

That algorithm is repeated until we have filled up one byte of memory.

This is useful because depending on your microcontroller an Int can have anything from say 16 bits of memory even as much as 32 bits. So that would be 32 times more storage used up than we would have required. Nice saving right there!

Step 3: Making the Arduino Understand Our Musical Awesomeness

How I store the sample data on the microcontroller

You will no doubt recall from the previous step that we took our downsampled information and packed it into a neat little package of the size of 1 byte or 8 bits.

This saves space on the microcontroller as you know but you may be wondering how we store it and access this information for playback later on the arduino.

Enter avr/pgmspace.h:

#include <avr/pgmspace.h>

This header file allows us to program our sample data directly into the flash memory on the Arduino, yay!

It's pretty easy to use just with a tiny bit of consideration on how we read the information back.

prog_uchar onebitraw[] PROGMEM = {    0XFF, 0XFF, 0XEF, 0XFF,..... };

I guess the 2 key points to make about that chunk of code above are that we use prog_uchar as the type of data we are storing, this is important for us to be able to read the data back out of memory when we play the sample. The other notable thing is that we use the keyword PROGMEM, this relies on the header file that I mentioned avr/pgmspace.h and this tells the compiler where to store this array of data.

prog_uchar tells the compiler that we are storing data of the type unsigned char.

A char is simply 1 byte, so it can store a value of 0 through to 255, 8 bits.

We specify unsigned because we are storing only positive numbers from 0 and above. This is critical because we aren't really storing numbers as you may recall we are actually storing 8 sound samples inside of this value, this ends up being converted to a number value and we can move it around as if it's a number but the reality is it's not quite what it seems but the compiler doesn't know or care about this configuration. If we were using a signed storage method we would be in a right mess.

If you fancy knowing more about signed, unsigned and two's complement then this wiki article should be an interesting read for you. http://en.wikipedia.org/wiki/Two's_complement

Pointer arithmetic is wayyy easier than it sounds

For the Arduino to read back our information from the PROGMEM section of memory we will be needing to use the function pgm_read_byte_near(); It is very easy to use and the only thing that complicates it is that it requires you to use pointer arithmetic to specify which byte of memory you want... Like so:

pgm_read_byte_near(onebitraw + which_one);

In that example I lay out above you will see 'onebitraw' which I will use to express the storage of our audio samples. Now you may be familiar with using array indices such as variable[index] and this is no different except we replace the [index] with +index instead.. Make sense? The reason is that we stored our audio data as a block of bytes, one after another, so we know that each one is simply one more along than the one previous to it.

See? Very simple!

Step 4: Demo of 1-Bit Music

Here I show you a little something I was working on tonight in order to play back some music, the music is sampled in the same way as shown before with my crusher program and then inside of the microcontroller it's possible to make those sounds play backwards or at different speeds.

Here is the sketch for this demo: https://github.com/electronoob/beverly-crusher/blo...

Step 5: Playback Manipulation

Here you can see three functions which all playback the sound samples, just in different ways...

  • playback();
    plays back a sample forwards.
  • playback_r();
    plays the sample backwards.
  • playback_s();
    plays the sample forward but at a reduced speed.

As you can see from the code it's very easy to play the sound in interesting ways, here is snippet of how I was able to sequence the patterns in the music video.

playback_r(onebitraw_1, BC_BYTE_COUNT_1);
playback_r(onebitraw_1, BC_BYTE_COUNT_1);
playback_r(onebitraw_1, BC_BYTE_COUNT_1);
playback_r(onebitraw_2, BC_BYTE_COUNT_2);
playback(onebitraw_1, BC_BYTE_COUNT_1);
playback(onebitraw_1, BC_BYTE_COUNT_1);
playback(onebitraw_1, BC_BYTE_COUNT_1);
playback(onebitraw_3, BC_BYTE_COUNT_3);
playback(onebitraw_1, BC_BYTE_COUNT_1);
playback(onebitraw_1, BC_BYTE_COUNT_1);
playback(onebitraw_1, BC_BYTE_COUNT_1);
playback_r(onebitraw_4, BC_BYTE_COUNT_4);

Very simple yet quite powerful in the flexibility of what you can create!

With a moment of inspiration I realised that I could play back chunks of each sample and stitch them together, keeping quantization and bringing in another aspect of the remixing idea.

int z;
for (z = 0; z < 4; z++){ playback(onebitraw_1, BC_BYTE_COUNT_1 /4); playback(onebitraw_2 + (BC_BYTE_COUNT_1 /4), BC_BYTE_COUNT_1 /4); playback_r(onebitraw_3 + (BC_BYTE_COUNT_1 /2), BC_BYTE_COUNT_1 /4); playback(onebitraw_2 + ((BC_BYTE_COUNT_1 /4) + (BC_BYTE_COUNT_1 /2)) , BC_BYTE_COUNT_1 /4); }

If you break down what I did it becomes very simple to understand.. Imagine these letters represented the 4 different beat patterns which I created in Reason before importing into my Arduino.

[AAAA]

[BBBB]

[CCCC]

[DDDD]

The for loop I used above sample breaks those patterns apart so now it looks more like:

[ABCD]

The duration stays the same, it all sounds in time and as a result of playing back a bit of each pattern sounds quite enjoyable!

Microcontroller Contest

Participated in the
Microcontroller Contest