Bitwise
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.
- 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.
- 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. - 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. - 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. - 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 )
Breaking this down into it’s pieces:
- a = (t * 15); – 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)
- c = a & b; – 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.
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.