• jon@schemawound.com
Supercollider
Bitwise

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) &amp; (t &gt;&gt; 5)) |
        ((t * 5)  &amp; (t &gt;&gt; [3, 4])) |
        ((t * 2)  &amp; (t &gt;&gt; 9)) |
        ((t * 8)  &amp; (t &gt;&gt; 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 &gt;&gt; 3);
	c = a &amp; 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.