Universal Synthesizer Panel – Auduino++

One of the reasons for creating my Universal Synthesizer Panel is to port over a range of different microcontroller based synthesizer engines to allow me to play with them without having to worry about building hardware.  In this post I return to my Auduino MIDI Granular Synthesis and add a few more features.

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

The Code

The original Auduino uses five potentiometers as described in my first post.  One of those is the base frequency control which I replaced in my last post with a MIDI input engine.  So in principle that leaves two pots available for use and the six switches.

This is how they’ve all ended up being used:

  • Pot 1 (A0): Grain 1 frequency.
  • Pot 2 (A1): Grain 1 decay.
  • Pot 3 (A2): Grain 2 frequency.
  • Pot 4 (A3): Grain 2 decay.
  • Pot 5 (A6): Sync frequency in non-MIDI mode; grain 2 volume in MIDI mode.
  • Pot 6 (A7): Decay factor (when enabled).

The switches are used as follows:

  • Switch 1 (D2): MIDI or SYNC (manual) mode.
  • Switch 2 (D3): Continuous or pentatonic scale selector in SYNC (manual) mode.
  • Switch 3 (D4): Enable adjustable decay factor.
  • Switch 4 (D5): Grain 1 waveform – square or triangle.
  • Switch 5 (D6): Grain 2 volume enable.
  • Switch 6 (D7): Grain 2 waveform – square or triangle.

IMG_5256

Main Logic Loop

The code to add the switches and potentiometers is relatively straightforward – it is largely a case of adding the appropriate conditional code dependent on the state of the switches.  The basic logic for the main loop is as follows:

Check for MIDI messages.
IF in SYNC manual mode THEN
IF in continuous SYNC mode THEN
set syncPhaseInc based on SYNC pot
ELSE
set syncPhaseInc from the pentatonic scale based on SYNC pot
ELSE
set syncPhaseInc based on the MIDI note currently playing
IF grain 2 volume switch is on THEN
set the grain 2 volume based on the SYNC pot

IF decay factor switch is on THEN
set the decay factor based on the decay factor pot
ELSE
assume a fixed decay factor (of 8)

grain 1 wave = grain 1 wave switch setting
grain 2 wave = grain 2 wave switch setting

set grainPhaseInc based on grain 1 freq pot
set grainDecay based on grain 1 decay pot (scaled by the decay factor)
set grain2PhaseInc based on grain 2 freq pot
set grain2Decay based on grain 2 decay pot (scaled by the decay factor)

Producing a Square Wave

You may recall from Auduino Granular Synthesis – Part 2 that the Auduino code has a neat way of producing a triangle wave based on the value of the 16-bit grain accumulator.  I’ve used a similar, but very much simpler, trick to produce a square wave.

value = 0;
if (grainPhaseAcc & 0x8000) {
value = 255;
}

Basically if the top bit is set, output HIGH (255) otherwise leave it LOW (0).  This means that the output will be HIGH when the accumulator is counting from 32768 to 65535 and LOW when counting from 0 to 32767.

Changing Output Pin

There is one complication with all this though.  My universal synth panel was designed to support Mozzi so expects the audio output on pin 9.  By default Auduino outputs to pin 3.  Changing pins means reconfiguring the Timer used to generate the PWM signal.

I gave a full breakdown of the timer configuration in my previous post.  The original code uses Timer 2, output OC2B.  It now needs to change to use Timer 1, output OC1A.

This requires changes to the function audioOn as follows:

  • In the Timer Counter Control Register 1 A and B:
    • WGM1[2:0] = 001 for PWM; Phase correct; 8-bit operation.
    • COM1A[1:0] = 10 to clear OC1A on compare match whilst counting up and set OC1A on compare match when counting down.
    • CS1[2:0] = 001 for “no pre-scalar”
  • In the Timer Interrupt Mask Register 1:
    • TOIE1 = Timer/Counter 1 Overflow Interrupt Enable

Using no pre-scaler means that the timer will run at the master clock speed, which is 16MHz for the ATMega328 in an Arduino.  As the counter is being used in “phase correct PWM” mode it will count both up and down, counting from 0 to 255 then back down to 0.  This means the period for the waveform is 16MHz/512 or 31.25kHz.

It is also worth noticing that Timer 1 is a 16-bit timer, whereas Timer 2 is an 8-bit timer, but I’m configuring Timer 1 in 8-bit PWM mode to match.

TCCR1A = _BV(COM1A1) | _BV(WGM10);
TCCR1B = _BV(CS10);
TIMSK1 = _BV(TOIE1);

There are just a couple of definitions at the top that now need to change to match:

#define PWM_PIN 9
#define PWM_VALUE OCR1A
#define PWM_INTERRUPT TIMER1_OVF_vect

It still uses the Timer overflow interrupt, but just now for Timer 1 rather than Timer 2.

Final Adjustments

For some reason most of the analog IO in the original is “reversed” – i.e. the mappings assume that a reading of 1023 is the start of the various lookup tables, and 0 is the end.  When wiring pots how I usually wire them (and how they are wired in the universal synth panel) this means that they are all working backwards – so you’d have pitch and volume decreasing as you turn clockwise for example.

Consequently in most places, I replaced analogRead() with (1023-analogRead()) to even it back out.

To obtain the grain decay factor, I wanted even powers of two, so I use the code:

 delayFactor = (1<<(analogRead(DELAY_FACTOR)>>7));

Which scales the 0-1023 analog value down to 0-7, which I then use as a “shift left” to give a value of 1,2,4,8,16,32, or 64 for the delay factor divisor.

When the grain 2 volume pot (SYNC) is active, again it is actually really specifying a divisor, so it is more of an attenuation than a volume control.

// In the control loop
if (readSwitch(SW_GR2VOL)) {
grain2Volume = (1023-analogRead(SYNC_CONTROL))>>4;
} else {
grain2Volume = 0; // Means it will be ignored
}

// Then in the PWM interrupt routine
if (grain2Volume) {
output += value * (grain2Amp >> 8) / grain2Volume;
} else {
output += value * (grain2Amp >> 8);
}

These are the major changes.  As there are now a fair few, and there is more of my own code in here now, you can find my updated version on GitHub here.

Closing Thoughts

This is now starting to get quite a fun piece of kit to play with, especially hooked up to my step sequencer as you can see in the video.  There are still a number of really interesting transition points where the different pots interact in curious ways.

One weird one, is that if the decay is “hard right” – i.e. so basically there is no decay, then when the SYNC frequency kicks off a note, if you then turn the frequency right down, you are left with a note “hanging”.  From the sounds of it I think this may be a continually repeating playing of the actual “grain” being processed, but I’m not sure.

There is also a curiosity with respect to the waveforms.  I’ve double checked my logic and believe the labelling of my switches is correct – so triangle is up, square is down – but whilst playing and listening, I do wonder if I’ve got that the wrong way round… but I didn’t have an oscilloscope handy to check.  So see how you go!

It would be nice to get some more sophisticated “grains” in there somehow but I expect that will need a pre-calculated wavetable now and there probably isn’t the memory for that now.

Kevin

IMG_5255

 

Leave a comment