Universal Synthesizer Panel – Helios One++

The next Arduino synthesizer I’ve been wanting to try out, so will get the Universal Synthesizer Panel treatment, is the Helios One Synthesizer by “Blog Hoskings” (Gary White), who has provided a very comprehensive build guide as well as details into how the code works.  Once again it is based on Mozzi and this is what I’ve ended up doing with it.

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 Helios One code supports the build as describe on the blog (naturally) but there are a few tweaks required if I am to run it on my Arduino Nano Universal Synth Panel.

The main issues is that the pots use a mix of hardware scaling, using additional resistors, and software scaling, using the Mozzi AutoMap function. My panel has no extra resistors, so I want to do all the scaling in software (and the risk of losing a little sensitivity from some of the pots).

The original Helios One still uses six potentiometers though, so I’ve configured them as follows:

  • Pot 1 (A0): Filter cut-off frequency
  • Pot 2 (A1): Filter resonance.
  • Pot 3 (A2): Envelope generator attack.
  • Pot 4 (A3): Envelope generator release.
  • Pot 5 (A6): LFO rate.
  • Pot 6 (A7): LFO amount.

The switches are used as follows:

  • Switch 1 (D2): Wave select – saw or square.
  • Switch 2 (D3): LFO enable.
  • Switch 3 (D4): (new) ADSR reset mode.

IMG_5264

Find it on GitHub here.

From Hardware to Software Analog Scaling

Adding resistors to potentiometer inputs creates a “hard stop” on either the lower or upper limit by including a fixed element of a potential divider into the circuit.  A resistor between the pot and GND will set the lower limit above 0V and a resistor between the pot and 5V will set the upper limit to something below 5V.

The following variations are used in the Helios One:

  • 220Ω to GND.
  • 220Ω to 5V (twice).
  • 100Ω to 5V.
  • 470Ω to GND.

And one is left with no additional resistors.  Taking 220Ω to 5V as an example, the circuit will be as follows

       220          0-10k
5V---\/\/\/\---+---\/\/\/\---GND
|
ALG

When the pot is at 0Ω to GND, then ALG will read 0V.  But when it is at 10kΩ we can use the voltage divider equation to work out the voltage at ALG:

V(ALG) = 5 * 10k / (200 + 10k) = 4.89V

We can do similar calculations for the other resistors and we end up with the values as shown in the following table (this includes the equivalent ALG port reading based on the full 0-5V scale being read as 0-1023).

HeliosSynthPots-calc

So it is quite possible to adjust the software scaling settings to take account of this and lose the additional resistors entirely.  What we lose though is a little sensitivity of the pots.   Taking the biggest difference here – the Env Attack pot – it has the full turning capacity (around 270° or) selecting from a range of 46 to 1023 or around 3.6 units per °.  Setting the whole voltage range back across the entire pot resets that to 0 to 1023 or around 3.8 units per ° so I’m not too fussed about the difference.

So how do we adjust the software scaling?  Each pot is using the Mozzi AutoMap feature, which is a special, “optimised-for-Mozzi” version of the Arduino’s own map() function.

Each pot has a range defined that will be applied to the voltage read on the analog input.  But we know that some pots will have a minimum or maximum value that isn’t 0 or 1023 already, so we can work out what those alternative values would read when put through the AutoMap function. Then we can use these new values to extend the AutoMap to cope with the fact that we are stretching the analog read range back to the full 0 to 1023.

Lets do an example.  Back to that 220Ω resistor to 5V which will give us a working range of 0 to 4.89V which we can see will result in ALG readings between 0 and 1001.  This is used for the LFO Rate pot, which has the AutoMap line as follows

AutoMap LFOratePotMap(0, 1023, 40, 750);

From this we can work out (or write a test program to calculate) the final actual possible range of values, which in this case will be the mapped equivalents of 0 to 1001, or 40 and 735.

This means we can remove the resistor, read a full 0 to 5V range as 0 to 1023, and then scale it to between 40 and 735 and (if I’ve done the maths right) the end result will largely be the same.

Here is a completed table for the AutoMap values to use.

HeliosSynthPots-map

There is one other quirk. Notice that the LFO amount values are backwards.  This is to account for the fact that in the original Helios One, that pot is wired “backwards” so that turning it clockwise will decrease the voltage, whereas for all the others, clockwise increases the voltage.  Telling AutoMap to map it the other way allows for this.  Reading it directly using (1023-mozziAnalogRead()) would be another way.

Digital Switches

There are two switches that control the tone generation. In the original design, these switches have three terminals with the centre to the IO pin, top to 5V and bottom to GND.  But actually the 5V connection is superfluous as the switches are all configured for INPUT_PULLUP mode anyway, so I’m fine with my switches as they are with the panel.

I’ve added a configuration option for a third switch.   Mozzi supports two modes for triggering envelop generation: to reset or not to reset.

By default the envelope will not reset when noteOn is called.  This means that if the notes overlap the sound envelope will continue at the level it was already at, but just the pitch will change.  This is most useful for fast keyboard playing when you don’t really want the envelope retriggering all the time.

But one of the nice things about the Helios is being able to play around with long attack times, but this is lost if the envelope isn’t restarting, so I’ve used a third switch to enable or disable resetting of envelopes.

MIDI Handling

After some initial playing, I wanted slightly better handling of Note Off events, so added something that takes note of the MIDI note playing and only acts on the noteOff from the same note.  This stops overlapping notes prematurely stopping each other.

byte lastnote;
void HandleNoteOn(byte channel, byte note, byte velocity) {
   oscil1.setFreq(mtof(float(note)));
   envelope.noteOn();
   digitalWrite(LED,HIGH);
   lastnote = note;
}

void HandleNoteOff(byte channel, byte note, byte velocity) {
if (lastnote == note) {
envelope.noteOff();
digitalWrite(LED,LOW);
}
}

Also, whilst the MIDI library already takes care of this internally, I’ve always got into the habit of checking for a velocity=0 noteOn message and calling the noteOff function myself.  This just means that if I’m re-using code and using a different MIDI library, or writing my own MIDI handling, I don’t forget to do it.

At present the code listens on all MIDI channels and accepts any messages.  I’d probably include a channel filter at some point, even if just #defined in code.

I also remapped the LED definition to pin 8 to use the LED on the panel rather than the built-in LED.

Other Potential Optimisations

I’ll probably leave the code here, as the main aim is to “trial” a few different Arduino synths just to see what they can do, but if I was to use it further, there are a few things I’d probably change about the code:

  • All IO is read every time through the control loop.  I’d probably change it so that IO handling is spread over several scans which would give a “tighter” control loop for MIDI handling and less of a single impact on the audio handling (more here).
  • I’m not totally sure that the envelope.setTimes() function is being called correctly.  Setting both the decay time (after the initial attack) and sustain time to 60,000 (i.e. 60 seconds) sounds like a lot. I suspect the decay time ought to be a lot shorter, but would need to experiment.
  • The wavetables are set on every scan through the control loop. I’d probably update it to only update if the state of the switch changes.

If I was to use this in anger, there are a few other “maintenance” type issues I’d fix too.  The switch pins are defined at the top of the file, which is great, but then not used as the code hard-codes the pin numbers 2 and 3 in calls later on.  I would have come unstuck here as I was going to change pin numbers.

But I’ve not changed any of this as right now I wanted to keep my changes to the absolute minimum to get it working.

Closing Thoughts

This is a fun one to do, and one I’ve been wanting to have a go at for a while.  I’m not totally sure I’ve got my maths right as the filter resonance isn’t doing very much at present, so I’ll need to do some tweaking and have a proper play with it now.

It would be nice to make some use of the other three switches. Having more waveforms is the obvious thing that comes to mind, especially a sine and triangle.  Aside, I think I’ve got the waveforms wrong on the switch label.  Again.

Another option might be to allow it to turn the filter off, or swap it for some additional kind of modulation.  There are many possibilities.

Kevin

IMG_5262

 

2 thoughts on “Universal Synthesizer Panel – Helios One++

  1. Hi Kevin, I’m playing around with the Helios too.. adding two extra pots and a couple of switches, and merging some drone code into the Helios sketch so I can switch between using it as a synth and using it as a drone. Did you ever get the time to figure out how to tweak the resonance? It doesn’t seem to be doing much in my build either.

    Like

    1. No I didn’t go back to it! I should though. Perhaps once place to start might be the Mozzi LowPassFilter example. I’ve never really got to grips with how cut-off and resonance affect a LPF, so ought to do some reading on the theory first…

      I also notice that the two functions used in Helios for this are deprecated in Mozzi, and it ought to use the setCutoffFreqAndResonance() function now anyway, reflecting the fact they need to be set as a pair.

      Do let me know if you discover anything before I do! 🙂

      And feel free to share any links or videos you have of your synth/drone in action!

      Kevin

      Like

Leave a comment