Background:

While looking for Arduino projects to build I found the Algorithmic Noise Machine. I was instantly intrigued by the noises it generated using bitwise operations.

Looking further I realized that Supercollider added support for bitwise operators in v3.5. Unfortunately the only documentation was a single example (quoted below).

// 8-bit magic ( play { var t = PulseCount.ar(Impulse.ar(8e3)); HPF.ar( ( ((t * 15) & (t >> 5)) | ((t * 5) & (t >> [3, 4])) | ((t * 2) & (t >> 9)) | ((t * 8) & (t >> 11)) - 3 % 256 ) / 127-1 * 3 , 20 ).tanh } )

I decided to explore the concept a to understand how bitwise operations work on an audio signal. My examples are written in Supercollider but much of this could be applied to other audio languages.

Other Concepts:

Before we get into the bitwise operators I figured we should talk about three basic concepts used in the code above.

( { i = Impulse.ar(2e3); t = PulseCount.ar(i); [i, t, t % 8] }.plot() )

Impulse - Sends out single sample pulses at the frequency specified. This is often used to trigger other Ugens.

- Sends out single sample pulses at the frequency specified. This is often used to trigger other Ugens. PulseCount - Outputs a number that is incremented each time it receives a trigger. In the plot above notice the steady ascent from 0 to 20.

- Outputs a number that is incremented each time it receives a trigger. In the plot above notice the steady ascent from 0 to 20. Modulo % - The remainder from a division operation. Very useful to “wrap” a number into a certain range. Notice in the plot above how the numbers count from 0 to 7 and then repeat.

Bitwise Operations:

Bitwise operators are used to manipulate numbers directly at the bit level. A bit has only 2 possible values, 1 or 0 (True or False). If you need a refresher on binary numbers I recommend this tutorial.

AND & - Result is true only if both bits are true. ex. 9 & 3 = 1

00001001 = 9

00000011 = 3

00000001 = 1

Notice how the result only contains true bits that both numbers have in common.

- Result is true only if both bits are true. ex. 9 & 3 = 1 00001001 = 9 00000011 = 3 00000001 = 1 Notice how the result only contains true bits that both numbers have in common. OR | - Result is true if either bit is true. ex. 9 | 3 = 11

00001001 = 9

00000011 = 3

00001011 = 11

Notice how the result contains all the true bits from both numbers.

- Result is true if either bit is true. ex. 9 | 3 = 11 00001001 = 9 00000011 = 3 00001011 = 11 Notice how the result contains all the true bits from both numbers. Bitshift Right >> - Move all bits right the number of digits specified on the righthand side of the equation. ex. 100 >> 2 = 25

01100100 = 100

00011001 = 25

Notice that between the two numbers the pattern of digits is the same, they have simply shifted two places to the right.

- Move all bits right the number of digits specified on the righthand side of the equation. ex. 100 >> 2 = 25 01100100 = 100 00011001 = 25 Notice that between the two numbers the pattern of digits is the same, they have simply shifted two places to the right. Bitshift Left << - Move all bits left the number of digits specified on the righthand side of the equation. ex. 31 <<2 = 124

00011111 = 31

01111100= 124

Notice that between the two numbers the pattern of digits is the same, they have simply shifted two places to the left.

Re-examining The Example:

Now that you understand the basic operations you can look at the middle section of the code as a complicated formula to generate values. The results of this formula are run through a 20 Hz highpass filter to remove excessive bass and a tanh to shape the output. I have put this formula into a function and created a loop to output the values produced using the numbers 1 thru 100 as an input:

( f = {|t| ( ((t * 15) & (t >> 5)) | ((t * 5) & (t >> [3, 4])) | ((t * 2) & (t >> 9)) | ((t * 8) & (t >> 11)) - 3 % 256 ) / 127-1 * 3 }; (1..100).do({|i|f.(i).postln}); )

A Simpler Example:

The formula is still pretty complicated so I tried to make a simpler example to understand why it sounds the way it does:

( { var t = PulseCount.ar(Impulse.ar(8e3)); a = (t * 15); b = (t >> 3); c = a & b; c % 256; }.play )

a = (t * 15); - value that is incrementing by 15. (ex. 0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150)

- value that is incrementing by 15. (ex. 0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150) b = (t >> 3); - incrementing value with a bitshift right 3 places applied. (ex. 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1)

- incrementing value with a bitshift right 3 places applied. (ex. 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1) c = a & b; - apply a bitwise AND to both previous values. (ex. 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1)

- apply a bitwise AND to both previous values. (ex. 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1) c % 256; - this keeps the numbers from incrementing infinity and forms the basis of the repeating pattern.

Breaking this down into it’s pieces:

Why does it form a sawtooth shape?

The result of an AND operation will never be larger than the smaller of the inputs.

A bitshift right will always produce a smaller number.

Based on the previous two points variable b will always be much smaller than a and will control the rate at which the numbers increase.

The modulo operation will “wrap” the numbers around instead of letting them rise infinitely, thus performing a repeating pattern.

Closing Thoughts:

Bitwise operations are capable of complex and chaotic rhythms, a little bit of knowledge about them will help you to bend these patterns to your will.

Further Reading:

A comment suggested this paper as further reading on the topic.