The only Teensy based Dev Environment I used was the Arduino IDE. It’s very basic if you’re used to using pretty much any other major IDE. It does the job, but it’s really not ideally suited to large projects. It does something weird when compiling where is merges all your source into a single file (I’m not clear exactly how it does this), which can yield some fairly obscure compile errors due to missing dependencies when working with multiple files. I did experiment with trying to get Eclipse working with Teensy, but ended up giving up without actually getting it working (I don’t remember what the issue I was having was now).
The lack of easy debugging on Teensy made it tricky to code a module of this size. There were several times when I almost gave up trying to debug where certain unwanted click sounds were coming from. Capturing the output audio and zooming into the glitch in Audacity is not a coherent approach to debugging it turns out.
The majority of the complexity of the code is the effect itself, which is really just about handling audio data, and not platform specific Teensy code. I decided I could be much more productive if I could write this code on my Mac in Xcode with a proper debugger. I wrote a wrapper using the JUCE library which would allow me to do this, called TeensyJUCE. JUCE is a multi-platform API which takes some of the pain out of developing audio plug-ins. It has a similar interface to the Teensy Audio library, in that there is a single function which gets called to process an audio block. All I had to do was reprocess this audio block and pass it to the Teensy effect interface (which had been slightly modified to compile in Xcode). That way I could write my code in JUCE but the effect side would still compile on the Teensy. I then just had to copy and paste the effect module back into my Arduino project, knowing the DSP code was working. This approach definitely saved me some time and frustration, and as an added bonus I then had an plug-in version of my effect to use in my DAW.
As you will know from reading Part 1, the interface for the module (or the potentiometers at least) are read using a separate PIC chip, and transmitted to the Teensy via I2C. This uses the Arduino Wire interface, which makes the whole process very straightforward. I’ve tried to encapsulate various types of interface objects in Interface.h, to use in future projects.
I’ve actually started experimenting with the possibility of removing the PIC chip and doing all of the interface reading on the Teensy using the second ADC. I didn’t think this was possible when I started, but have since discovered the ADC library. Will report back with news on how this goes. For now though, to make the code work with the schematic in GitHub, make sure you have I2C_INTERFACE defined.
The effect itself is really just an extension of a standard delay line. The delay line is implemented as a circular/ring buffer. To make the most of the Teensy 3.6 256Kb of RAM, and because we are only using the built-in DAC rather than a higher resolution audio codec, all of the samples are stored at 12-bit (even though the Teensy audio library’s native width for samples is 16-bit).
A standard delay effect is often implemented as a delay line, with a write head, which writes into the delay line, and a read head, which reads from the delay line. The separation between these heads governs the delay time. In the Glitch Delay these read heads are actually loops, rather than following behind the write head at a set offset, they instead loop sections of the delay line. There are 3 looping heads, playing the audio at different speeds (one at normal speed, one at double speed – one octave up, one at half speed – one octave down).
When reading audio in this way, you need to be mindful of zero crossing. If you jump around in the audio buffer you will get zero crossing, which will lead to pops and clicks in the output audio. This is resolved in the Glitch Delay by using crossfading. Every time one of the loops is about to restart, it cross fades between the end of the loop and the start. This means reading 2 samples and blending between them. For the same reason, the loops must also avoid being ‘run-over’ by the write head. If the write head were to write over a section of looping audio, you would have both ‘old’ and ‘new’ audio in the same loop, which again would cause a pop. To solve this the loops ‘jump’ out of the way when the write head approaches, this jump must also be cross faded. If you look at the video below you can see the loops and the write head moving around, the orange bar is the write head, the grey bars are the read heads (3 looping, one playing backwards).