Trinket USB MIDI Multi-Pot Mozzi Synthesis – Part 3

So, I’ve performed some optimisations for my polyphonic Trinket USB MIDI Multi-Pot Mozzi Synthesis and think this is probably where I’ll leave the code for the time being.  You can get an idea of what it can do in the video below.

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

These are the key tutorials for the main concepts used in this project:

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

Parts list

  • Adafruit Trinket M0 (or probably any other Adafruit M0 board)
  • 1x 10uF non-polar capacitor (optional)
  • 2x 1kΩ resistors (optional)
  • 4x 10kΩ potentiometers
  • 1x 220uF capacitor (optional)
  • 3.5mm jack socket (optional) and amplification
  • Breadboard and jumper wires

The Circuit

Nothing has changed electronics wise since Trinket USB MIDI Multi-Pot Mozzi Synthesis – Part 2.  All the updates have been in the code.  Read on 🙂

The Code

Now that I understand what Mozzi is doing a bit better, I thought I’d take another look at how the different parts of the code fit together.  There are three main optimisations I wanted to look at:

  • Is there a more efficient analogRead() for the SAMD21?
  • Can I move the MIDI handling out of the updateControl loop?
  • Is it possible to disable the Use1ByteParsing option in the MIDI library?

The driver for the first is simply that I know analogRead is doing quite a bit of waiting around for ADCs to be ready and I know mozzi includes an asynchronous mozziAnalogRead function that speeds things up – well, in reality allows code to keep running rather than waiting for analog to digital conversion to happen.

The driver for the second is some speculation that the blips and clips I hear are related to some immediate cutting off of notes as a result of MIDI note on and off.  On an oscilloscope I can see sine waves immediately dropping to zero mid-cycle and so on, so I’m wondering if the envelopes aren’t getting a change to do the “note off” properly due to the relative timings of the MIDI handling, control loop (where envelopes are updated), audio loop (where samples are calculated), and the audio output (where samples are “played”).

The third is simply that I believe handling MIDI messages in one go would be more efficient than repeatedly calling MIDI.read().

Improving Analog Read

I simply searched for “samd fast analog read” to see what happened, and luckily stumbled across the following almost straight away: https://www.avdweb.nl/arduino/adc-dac/fast-10-bit-adc.

This is a “wrapper” around analogRead for the Arduino Zero, which also uses a SAMD21 processor.  It claims an order of magnitude improvement in speed, so I thought that was worth a go.

Looking at the code, it stores the existing ADC control register values, sets some new options, then calls the existing analogRead() function, then restores the control registers again.

I’m not of the implications of the changes in ADC mode, but it seems to work fine for me and I am indeed seeing some speeding up of the analog reads.

To install, I saved the “raw” file from here into my Arduino sketch folder alongside the USH2 MIDI files and my own sketch.

MIDI Control Loop

Now that I know that both the updateControl and updateAudio functions are effectively just calls from within Mozzi’s audioHook function (see the detailed discussion in the last post), I could see no reason why my MIDI handling had to stay in the control loop, so I’ve moved it out to the main Arduino loop().

This means I can perform much more regular MIDI checks whilst being able to drop the frequency of the control loop back down to something a bit more sensible.  It also means that MIDI handling won’t stall if the audio buffer for Mozzi fills up. In short, it makes the two things independent.

My Arduino loop now looks like this.

void loop(){
midicount++;
if (midicount == (MIDI_RATE)/2) {
scanUsb();
} else if (midicount > MIDI_RATE) {
midicount = 0;
scanMidi();
}

audioHook();
}

I’ve set MIDI_RATE to 16 which means every 16 scans of the loop, some MIDI handling will go on.  I’ve also included the UsbH.Task() scanning here too, which will also run every 16 scans, but on different scans to the MIDI handling.

The CONTROL_RATE is now set to 256.

I’ve also taken the chance to update some of the potentiometer code to make sure they are handled in the correct order, especially on startup (I think there was a window where some might have been “uninitialised”).  It also seems that when the pots are turned right round to “zero”, I am still reading values in the 3-12 range, so I’ve also included a “cut off” for what I’m treating as zero.  Anything below 15 is now considered “zero” for the intensity and intensity rate pots.

Use1ByteParsing = false

Not really being a c++ person, I wasn’t at home with the template syntax used to configure settings for the MIDI library, but I think I’ve worked it out.  I believe that the following will instantiate the USB Host MIDI transport with the Use1ByteParsing option disabled.

struct MyMIDISettings : public MIDI_NAMESPACE::DefaultSettings {
static const bool Use1ByteParsing = false;
};
USBHost UsbH;
UHS2MIDI_NAMESPACE::uhs2MidiTransport usbHMIDI(&UsbH, 0);
MIDI_NAMESPACE::MidiInterface<UHS2MIDI_NAMESPACE::uhs2MidiTransport, MyMIDISettings> MIDI((UHS2MIDI_NAMESPACE::uhs2MidiTransport&)usbHMIDI);

This should mean that a single call to MIDI.read() is all that is required to process a complete MIDI message from the USB layers at a time.

I’m pretty pleased with these results. It still isn’t perfect – there is still the odd skipped MIDI message or the odd clipped note, but by and large this is now working, but you wouldn’t want to perform with it!

Find it on GitHub here.

Closing Thoughts

In terms of fiddling about with the code now, I think I’m going to leave this one here for a bit. I think I have something usable and think the next stage would be some accurate profiling of the code via a logic analyser on a board with more IO pins and I might in slower time dig into the various layers behind the MIDI.read call to see if I can spot anything odd going on in terms of buffering.

It is still tempting to see if I could build more oscillators in though… if I do, I’ll probably just update this post.

Kevin

Leave a comment