Auduino Granular Synthesis – Part 2

After having an initial look at the Auduino project, I was very interested in finding out more about how it worked, but the Internet seems somewhat mute on the subject and I’ve not found any information from the original author.  If you know of such a link, be sure to let me know (at the very least, I’ll be able to see if my analysis is right or not!).

In the mean time, for fear of possibly wasting my time, here we go with another one of my “notes to self” on what I’ve found out from working through the code.

Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments or gear!

This analysis is a little complex, so if you just want to play with the thing, then take a look at part 1 instead and ignore this part!

If you are new to Arduino, see the Getting Started pages.

Parts list

  • Arduino Uno
  • 5x 5k to 10k potentiometers (the original project used 4.7k pots)
  • 8 ohm speaker or old headphone speaker or alternative amplification
  • Breadboard and jumper wires

The Circuit

This is assuming the circuit as described in Auduino Granular Synthesis.  The video above demonstrates the use of the potentiometers.  Here is what is happening.

There is quite a lot of noise from the circuit right now – but then this is pretty lo-fi, especially on a breadboard.  This is using an output filter circuit but I’m still not convinced it is in any way optimal, but that is not the point of this post…

At the start of the video, the “sync” or grain repetition (frequency) potentiometer (the one on its own, connected to A4) is turned up from zero to a low value.  The LED on the Arduino toggles every time the “sync” pulse occurs signifying the start of the playing of the “grain”.  I’ve set it so that we can actually see each “grain” as it happens for the time being.

The other four potentiometers are arranged as follows (from top to bottom):

  • A0 = Grain 1 pitch
  • A1 = Grain 2 decay
  • A2 = Grain 1 decay
  • A3 = Grain 2 pitch

I don’t know why they are interleaved, I guess it makes “playing” it easier?  This means that grain 1 is controlled by the top and third pots, and grain 2 is controlled by the second and last, with pitch for both being the outermost pots, and the decay controlled by the inner two.

It doesn’t really say which way round to wire your pots.  I like to wire them so that fully clockwise gives 5V and fully anticlockwise gives 0V (so what you’d expect with a volume control).  They start in the video set to 0.  This does lead to “reversing” the expected behaviour of the pots though with the standard code (more later).

So, now that the “grain” is essentially being played as a repeated “single shot” we can fiddle with the grain pots and really hear what is going on… Here’s what is happening next in the video.

  1. I turn down Grain 1 pitch and you can hear the pitch drop – I settle on something nice an audible.
  2. All frequency pots are configured in the code so that “high value = low frequency” and “low value = high frequency” – so in turning from 0 to 5V they go from high to low frequency.
  3. With the decay on 0V there is basically no decay happening at all, so we pretty much get a continuous tone.  You can hear a “blip” when the grain resets (when the LED toggles) but that is all.
  4. As the decay (for grain 1) is turned up you can hear the playing of the grain gets cut shorter until you can hardly hear it at all (it is too short).  There is quite a harsh cut-off though.  At one point I wondered if I was using logarithmic pots rather than linear ones, but no they are labelled as linear… this is one area where I’d like more control over the decay so I’ll probably do some tweaking of the code later.
  5. Once you can hear the individual (short) grains I turn up the sync frequency.  At some point you stop hearing the individual grains and instead start hearing a continuous complex tone.  It is a lot more apparent as I turn the frequency back down and the continuous tone separates out into individual grains again.  I’m guessing this will be at around 20 Hz (human hearing range is something approximating 20Hz to 20kHz, at least when you’re young it is!).
  6. Then I bring in grain 2 – again it starts as a continuous tone (now modulating with grain 1), so I adjust the pitch to get a nice chorus/beating/phasing type effect going on…
  7. Then adjust the decay so it is shorter than grain 1, so the chorus/beating/phasing only happens at the start of the trigger.
  8. Then once again I dial up the sync frequency and we can hear the now more complex grain speeding up to become a continuous tone, and then back down into individual grains once more.

In reality, the best sounds are to be found at the more complex interplays of the two grains and the sync frequency, but I kept this deliberately simple to highlight the different operations of the potentiometers.

So how does the code achieve all this?  Read on to find out.

The Code

The Auduino code can be found here: https://code.google.com/archive/p/tinkerit/downloads.

As described before, you need the auduino_v5.pde file which needs putting in your Arduino src directory.  I created an Auduino folder and renamed the file to auduino.ino, but you could just open the “pde” file directly by the Arduino environment if you wish (Arduino files used to be called .pde files before they were .ino files).

I’m using the “smooth frequency mapping” function of the code – recall there are other modes that will provide discrete pitches if required, but they aren’t really covered here.

The basic idea behind granular synthesis is explained really well in this Sparkfun tutorial here.  The basic idea is to have short samples of a waveform, the “grain” (in this case a triangle wave), that is repeatedly played back according to a frequency set by a control somehow (in this case the “smooth” potentiometer).  When the grain is played back really quickly the repetition itself sets the frequency of the resultant tone.

In order to achieve this in the Auduino code, there are several key stages, each of which will be described below.  Note this is only considering the code supporting the ATmega328 (as found in the Arduino Uno, Nano and others).  The code will automatically support the ATmega8 and ATmega1280, but I’m not considering those in this description.

There are two main threads of execution going on in the code:

  • A slower “control loop” that reads the potentiometers and updates the synthesis parameters accordingly.  This is set up in the main Arduino loop() function.
  • A fast, interrupt-driven “tick” that is responsible for calculating the next sample value to be played and sending it out to the PWM hardware.  This is set up in the SIGNAL(PWM_INTERRUPT) function, which for the ATmega328 becomes an alias for SIGNAL(TIMER2_OVF_vect).

PWM Timer Interrupt and Configuration

For a general introduction on the operation of PWM for Arduinos, look here.

  • One of the ATmega timers is used to provide a regular “tick” using an interrupt.  This ensures that no matter what the rest of the code is doing, the sample playback will always happen with a regular 31.25kHz frequency (that is 32uS per “tick”).
  • The ATmega328 (Arduino Uno/Nano/etc) is configured to use Timer 2, which is an 8-bit timer/counter with PWM functionality on output OC2B (pin 3 on an Uno).  All details can be found in section 21 (“TC2”) of the ATmega328 data sheet.
  • Timer 2 is configured as follows (this happens in the audioOn() function):
    • Phase correct PWM – WGM2[2:0] = b001
    • No prescaling – CS2[2:0] = b001 – so it is running at the main system clock speed, which is 16MHz for the Uno.
    • Compare mode – COM2B[1:0] = b10 – “Clear OC2B on compare match when up-counting. Set OC2B on compare match when down-counting.”
  • As this is an 8-bit timer, running at 16 MHz, it will be counting from 0 to 255 on each “tick”.  This means the resulting waveform has a frequency of 16,000,000 / 256 = 62.5kHz.
  • In “Phase Correct PWM” the counter counts from BOTTOM to TOP and then from TOP to BOTTOM, so as it is counting twice, the actual frequency of operation is half again i.e. 62.5kHz / 2 = 31.25kHz.
  • I’m not entirely sure it really matters for this application if phase-correct or fast PWM mode is used, other than the halving of the frequency for the former.  We aren’t using any other PWM signals for it to be “phase correct” with… (for more on Arduino PWM modes, see here).
  • The code hangs its “tick” function off the timer overflow interrupt – TIMER2_OVF_vect – by setting TOIE2 in TIMSK2; which in PWM mode is triggered every time the counter changes direction on reaching zero (see the explanation of the TOV2 bit in TIFR2 in the data sheet).
  • The compare value that sets the width of the PWM pulse is OCR2B.  This is written to at the end of the interrupt routine, once it has worked out the sample value to be output.

Most of the magic relating to setting up PWM and the timers happens in the #defines at the top of the file and the audioOn() function.

Triangle Wave Grain Generation

A key component of granular synthesis is defining the waveform to use as the “grain”. Many implementations might use a wavetable, but this code uses a 16-bit counter (the “grain accumulator”) and some bit manipulation to generate the waveform on the fly.

This is the relevant code from the interrupt routine:

// Convert phase into a triangle wave
value = (grainPhaseAcc >> 7) & 0xff;
if (grainPhaseAcc & 0x8000) value = ~value;

So what is going on here?  Well, grainPhaseAcc has already been incremented by adding grainPhaseInc earlier in the routine and grainPhaseInc is set from the potentiometers using the “mapPhaseInc” calculation I’ll discuss in a moment.  The larger the increase in grainPhaseAcc on each “tick”, the higher the frequency of the grain – as it gets to read through the sample more quickly if it is skipping some of the values each time.

But for now, lets assume grainPhaseAcc is updated simply by 1 each time.  This means that every “tick” (i.e. every 32uS) this is incremented by 1 from 0 through to 65535 (it’s a 16-bit value) and will wrap around when it gets to the top.  This will occur 65536 x 32uS later or in around two seconds at its longest!  That would be a very, very low frequency of course, but most will be much quicker than this (for 440Hz or “concert A” it would have to be wrapping around every 2mS or thereabouts).

Also on every “tick”, the above code runs to calculate the output value from grainPhaseAcc, first by shifting right by 7 bits.  This is the same as dividing by 128 so this creates a value in the range 0 to 511 (65536/128 – 1).  But the “& 0xFF” means it only takes the bottom 8-bits of this value.  The upshot of all this is that as grainPhaseAcc goes from 0 to 65535, “value” in the above code will go from 0 to 255 twice.

The second line looks at the top bit of grainPhaseAcc and if set (that is, if grainPhaseAcc > 32767) will binary invert “value” using the “~” operator in C.  This has the result that the second 0 to 255 is reversed and will count down instead from 255 back to 0. If you want to know why this works, then you need to look up one’s complement, binary NOT operations and some of the weirdness you get when switching between signed and unsigned decimal values and binary… but that is a little out of scope for this (already quite long) article.

The combined effect of this is that every time grainPhaseAcc goes from 0 to 65535, “value” will go from 0 up to 255 and back down to 0 again.  You can see this in the graphs below (I put the values into a spreadsheet and ran the same calculations as the steps in the code to generate the different graphs).

Auduino Granular Synthesis 1

The final part of generating the triangular wave grain is to process the decay value.  This is done in the next line of code:

// Multiply by current grain amplitude to get sample
output = value * (grainAmp >> 8);

Where grainAmp starts at 0x7fff (i.e. 32767) and is decreased each time through the loop according to the code:

 // Make the grain amplitudes decay by a factor every sample (exponential decay)
grainAmp -= (grainAmp >> 8) * grainDecay;

where grainDecay is determined from the potentiometers in the main loop() function.

Phase Increment – Frequency Generation

There are three places where the frequency has to be calculated to create some of the effects:

  • The two “grain” frequencies.
  • The “sync” or grain repetition frequency.

Assuming smooth frequency operation, these all use the same function to translate an input value into a frequency – mapPhaseInc (the two non-smooth modes for the sync frequency have the option to use something else).

This function is quite interesting as it looks like this:

uint16_t mapPhaseInc(uint16_t input) {
return (antilogTable[input & 0x3f]) >> (input >> 6);
}

That is it.  This is a fascinating bit of code for me – I’d really like to know if this is a published algorithm somewhere… So what is going on?

“input” is a 16-bit value, so can in theory go from 0 to 65535, but as the input is the output from a potentiometer in reality it will be a 10-bit number in the range 0 to 1023 – we can see this as every call to mapPhaseInc() passes in the output of the analogRead() function directly.

“input” is used twice here:

  • input & 0x3F is a bitwise AND with binary b0000-0000-0011-1111 which will create a 6-bit value in the range 0 to 63, doing this 16 times across the range of inputs from 0 to 1023.  That is to say each of 0 to 63, 64 to 127, 128 to 191, 192 to 255, and so on, will all result in a number in the range 0 to 63 when “AND”ed with 0x3F.
  • input >> 6 is a bitwise right shift of six places, which is the same as “divide by 64”.  This creates a value in the range 0 to 15 from the input values of 0 to 1023.

The end result of the above is we have two indexes based on “input”: a lower index (0 to 63) based on the lowest 6 bits of input; and an upper index (0 to 15) based on the top 4 bits of input.

The lower-index is used to pick a value out of the antilog table, and the upper-index is used to shift the retrieved value to the right (i.e. divide it by a multiple of two).  To make that a little clearer, it could be re-written as follows:

uint16_t mapPhaseInc(uint16_t input) {
uint16_t index = input & 0x3F;
uint16_t divisor = input >> 6;
uint16_t value = antilogTable[index];
value = value >> divisor;
return value;
}

What is particularly interesting, at least to me, is that when combined with the specific values in the antilog table, this will generate a complete “reverse log” table of values for all numbers from 0 to 65536 from a look-up table containing just 64 entries!?  How can it do this?  From the almost fractal nature of the curve being returned – it is self-similar at different scales…

Look at the values in the antilog table:

uint16_t antilogTable[] = {
64830,64132,63441,62757,62081,61413,60751,60097,59449,58809,58176,57549,56929,56316,55709,55109,
54515,53928,53347,52773,52204,51642,51085,50535,49991,49452,48920,48393,47871,47356,46846,46341,
45842,45348,44859,44376,43898,43425,42958,42495,42037,41584,41136,40693,40255,39821,39392,38968,
38548,38133,37722,37316,36914,36516,36123,35734,35349,34968,34591,34219,33850,33486,33125,32768
};

Every single one of these values is in the range 32768 to 65536 – i.e. in the upper half of the 16-bit range and they are decreasing from the highest to the lowest.  The best way to see what is going on is to take some sample input values, and work out the corresponding bit-shift for that value and the equivalent bit-shifted table of values:

 Input   Bitshift    antilogTable resultant range of values
0-63 0 32768 - 64830
64-127 1 16284 - 32415
128-191 2 8192 - 16207
192-255 3 4096 - 8103
...
959-1023 15 1024 - 1

The values have been chosen so that as the input range increases, it switches to a new scale of values and each new range of values sits fractally within the “parent” range.  Shown graphically (again a spreadsheet helps loads here):

Auduino Granular Synthesis 3

The red lines show the first four different ranges.

Why this function?  I’m guessing it is related to the fact that pitches in music are not linear in frequency – i.e. to go up one octave the frequency is doubled – that is A4 is 440Hz (“concert A”), but the A above it is 880Hz and the A above that is 1.76kHz.  Going down, A3 is 220Hz, A2 is 110Hz and so on. Using a function like this means that a doubling of the input value results in halving the output value – the frequency potentiometers “go backwards” in this code.

Reading the Potentiometers

What was that about the potentiometers?  Ah yes, at various places in the code the value read from the potentiometers is often passed into a function to generate a frequency.  But when received the value is subtracted from 1023, which effectively “reverses” the pots.

In the function “mapMidi” for example, it has the following line:

 return (midiTable[(1023-input) >> 3]);

And similarly in “mapPentatonic”:

 uint8_t value = (1023-input) / (1024/53);

So if you wire your pots as I do, this means they are “reversed”.  So you have two options:

  • Either wire your pots with 5V on the “fully anti-clockwise” side and GND on the “fully clockwise” side; or
  • Change the calls to analogRead() in the code to be (1023 – analogRead()) instead; or
  • Just accept your pots work backwards!

Note: whilst you could remove the “1023-” in the two functions mentioned above, it isn’t so easy to adjust things when calculating the phase, so whilst redesigning the “sense” is possible, I think the above three options are the simplest!

Bringing it all together

Ok, so what is left?  Well other points to note are:

  1. The “sync” frequency is used to reset the grain parameters – in the interrupt routine, “if (syncPhaseAcc < syncPhaseInc)” results in both grainPhaseAcc/Inc being reset to start a new “grain” and the amplitudes reset to 32767 (0x7fff).  This is also where the LED is toggled, to give an idea of the grain repetition rate.
  2. A number of additional scaling factors appear in the code when setting the values from the pots in loop():
    • syncPhaseInc = mapPhaseInc() / 4
    • grain(2)PhaseInc = mapPhaseInc() / 2
    • grainDecay = analogRead() / 8
    • grain2Decay = analogRead() / 4
  3. The final sample value is an 8-bit value, so a number of things need to happen before the final writing out to the PWM register:
    • the two values from both grain calculations must be added together, both processed according to the amplitude from the appropriate grain decay.
    • the resulting output is shifted by 9 (so taking a 16-bit value, dividing it by 512 to results in a 7-bit value).
    • there is a check for “clipping” too, although after bit-shifting by 9, surely this is redundant?
  4. There are two other “sync” modes that will pick a preset frequency based on the sync pot rather than a continuous “smooth” range. These each have their own “map” function: mapMidi and mapPentatonic.

Closing Thoughts

Considering there are just 209 lines of code for this, there is an awful lot going on.  I take my hat off to the author – Peter Knight – this is some pretty neat code!  I’d be very interested in knowing how close the above analysis is, so I really mean it – if anyone knows of a detailed breakdown for the code or any links or references for the algorithms in use, I’d be very interested in hearing from you.

Regardless of all that, this was a really worthwhile exercise for me as I now feel like I can tweak things and still understand what is going on.  Some ideas for modifications I’m thinking about include:

  • Changing the delay settings to I can get more nuanced control without the abrupt cut-off that seems to be happening at the moment. I’ll probably make them the same too (I don’t know why one is /4 and one is /8 at present).
  • Add MIDI. I know Notes and Volts have done this already, but still…
  • I might experiment with a wavetable rather than the calculated triangle wave.
  • I might see how things sound if I add a third triangle wave to the original for a more complex grain.

Any I really need to do something about the output quality – I suspect soldering it all into a shield or enclosure would help quite a lot…

Kevin

3 thoughts on “Auduino Granular Synthesis – Part 2

  1. This is mad! Thanks so much for sharing your investigating here, it’s a really good write up. I will admit that the stuff after the triangle wave explanation began to get a bit over my head – I need to come back in a few months when i’ve learnt a bit more and re-read this.

    I’ve been learning about granular synthesis recently and I was starting to wonder how this even was granular synthesis without a sample. This has really cleared that up for me. It’s granular synthesis pretty much from scratch, no sample required, and I think that’s a really smart way to create waves in code. Peter Knight where are youuu haha – I hope he is well.

    If you don’t mind, i’ll link this in my write up and video as well so that people can come and see a better break down of the code. Because, as you’ve said, this information seems really hard to find.

    Again, really nicely written – don’t know how i’ve missed this blog before!

    Liked by 1 person

    1. Great! Glad you liked it 🙂 Yes, the phase increment bit is mad – all from that one line of code!

      > Again, really nicely written – don’t know how i’ve missed this blog before!

      Ah, well I’ve only been writing it since June, so no real surprises there! Feel free to pass the word and very happy to talk through anything else you come across (assuming I can work through it of course) or expand on anything else I’ve written already.

      Like

Leave a comment