Adding swing to my Yocto sequencer
I have been slowly writing my own sequencer firmware for the Yocto DIY drum machine, which is a clone of the TR-808. The Yocto came with firmware but I just like writing my own. I like knowing how a sequencer works and I'm interested in learning how to keep this simple.
On the other hand, I also want to add functions to the Yocto that the original 808 lacked, such as shuffle (or "swing time"). I have now tried two approaches to shuffle which both work: delayed clock pulses and using a higher-resolution clock. In this blog post I will share what I've learned.
Clocks
At the heart of the TR-808 sequencer is a voltage-controlled oscillator (a clock) that beats 24 times per quarter note (24 PPQN). The number 24 has the useful property that it can be divided by 8, giving 32nd note resolution, and by 6, giving 16th triplet resolution: these are the fastest notes the 808 can play.
In my Yocto firmware I also implemented a 24PPQN clock, using the "clear on timer compare" (CTC) mode of the 16-bit timer in the ATmega1284P microcontroller (MCU) the Yocto is based on. This was convenient because MIDI clock also uses 24 PPQN time resolution: in order to follow an external MIDI clock, my sequencer treats MIDI clock messages as if they were internal clock pulses.
Swing
"Swing time" or "shuffle feel" is a way of playing music where even subdivisions of the main tempo are played late.
| | | | | | | | straight time
| | | | | | | | swing time
If you're using 16th notes on a 24PPQN clock then it is possible to delay the even steps, but you don't have a lot of options. Delaying by 2 clock ticks gives "triplet feel" which is already a lot of swing, and beyond that it starts to feel erratic. So with 24 PPQN there are just two non-zero levels of swing: half (1 step delay) and full (2 steps). How can we make levels of swing in between?
Implementation strategies
The first thing that came to my mind was to increase the sequencer clock resolution. If we double the clock speed to 48PPQN, then triplet feel would correspond to a 4 tick delay: we get 4 levels to choose from. In the 1980s, 96PPQN was a common resolution (for example I know that the Alesis MMT-8 and the Yamaha RX5 use it). A more modern sequencer like the Sequentix Cirklon uses 192PPQN.
Knowing all this, a faster sequencer clock seemed like the right solution. But I couldn't wrap my head around how to do that while staying in sync to an external MIDI clock.
Strategy 1: time based delay
I told a friend about my project and how I was stuck, and he said why don't you just delay the even clock pulses? My musical goal was still to 16th note rhythms into the Yocto, so why would I have to create many extra in between steps.
I thought this was very interesting because it's not what most sequencers I know do: they let you configure swing as a percentage, which is consistent with having a higher-resolution internal clock. If you change the sequencer tempo the swing feel stays the same. That won't happen with a time delay but who says it has to?
If your tempo is 120BPM then the 16th notes are 125ms apart. To create triplet swing feel you would delay the even steps by 125ms/3=41.7ms. You can see this version of the firmware here. The key part is:
if ((!midiactive && TIFR1 & 1 << OCF1A) ||
(midiactive && midiclock)) {
/* code that runs after clock tick */
if (!(clock % SCALE))
schedule(TRIG, step & 1 ? shuffle << 6 : 0);
}
if (event(TRIG)) {
/* actually create sound */
I separated the code that handles clock ticks from the code that creates the final sound and put a delay between the two. On odd steps the delay is 0, on even steps it is whatever is in the shuffle variable, which ranges up to 261.12ms in steps of 1.024ms.
This worked great, with only the downside that changing the sequencer tempo changes the swing feel. I never change tempo in the middle of a song so this was not a serious problem for me.
Clock rollover and CTC mode
I ran into an interesting problem when I implemented clock delay. On microcontrollers such as the ATmega1284P, I measure time by sampling a counter running at a known speed, and taking the difference. Say I see a timer value of 1234 and a while later I see 5678, and I know that the timer counts at 62.5kHz. Then the elapsed time is (5678-1234)/62.5kHz = 71.1ms. Because of how unsigned integer arithmetic works, subtracting timestamps works correctly even if the timer has wrapped around after reaching 65535: 11-1=10, and 5-65531=10 as well. If the timer wraps around more than once I'd have a problem but I make sure my code checks often enough so that can't happen.
My tempo clock uses a 16-bit AVR timer in "CTC" mode which means that the MCU resets the timer when it hits a preset value of my choosing. Supposing a 62.5kHz timer, for a 24PPQN clock at 120BPM I would want the timer to reset when it reaches 1302.
The funny mistake I made was to reuse the CTC timer for general time measurement. Because the resets of the CTC timer don't happen at 65535, subtracting timestamps starts giving wrong results when the timestamps straddle a reset. Luckily the ATmega1284P has more than one 16-bit timer so I could solve this problem by setting up a second timer for general time measurement and leaving the CTC timer for tempo use only.
Strategy 2: faster clock
Although delayed clock pulses achieved the musical effect I wanted, it bugged me that all these 1980s sequencers can run MIDI-synced clocks faster than 24PPQN while I remained confused. I had to get this working. Running the internal clock at a higher rate, say 192PPQN, is easy. But what do I do when I want to follow an external MIDI clock?
My first idea was to run the internal clock, just like in stand-alone mode, but to continuously adjust its speed in some hand-wavy way to keep it in sync. This didn't work. First I tried making a PID controller but it kept oscillating and/or having a phase offset. For example:
Phase-locked loops (PLLs) are actually a common technique people use to derive a new clock signal with a different frequency from an existing one. But they work in the analog domain and I want to stay digital.
Instead of slowing down and speeding up a continuously ticking clock, maybe I needed to do something "dumber".
To sync a 192PPQN clock to a 24PPQN clock I need to create 8 times as many clock pulses. The pulses that fall on the 24PPQN clock are already taken care of, so I just need to count to 7 every time a new MIDI clock comes in. To know how fast to count I divide the time between the last two MIDI clocks in 8. This isn't complicated at all! The clock speed will not be very steady but it won't drift away from the external clock.
This code deals with MIDI clock messages:
if (msg.status == 0xf8) { /* clock */
uint16_t period24ppqn = TCNT3 - lastmidiclock;
lastmidiclock += period24ppqn;
OCR1A = tempo =
((fTimer1 / fTimer3) / CLOCKMUL) *
period24ppqn;
TCNT1 = 0;
doclock = 1;
clock += CLOCKMUL - 1 - (clock % CLOCKMUL);
}
TCNT3 is the timer I use to measure time between MIDI clock messages. OCR1A and TCNT1 control the CTC clock that does the tempo. CLOCKMUL is 8, the multiplier between the internal clock (192PPQN) and MIDI clock (24PPQN). If the MIDI clock message came in before we were able to count to 7, the last line "clock += ..." advances the internal clock to catch up.
This is the code that checks the tempo clock:
if (TIFR1 & 1 << OCF1A) {
TIFR1 |= 1 << OCF1A;
if (!midiactive || clock % CLOCKMUL < CLOCKMUL - 1)
doclock = 1;
}
If TIFR1 & 1 << OCF1A is true, the CTC tempo clock has ticked. We need to acknowledge this by writing to TIFR1 with |=. If external MIDI sync is not active we always act on the clock (doclock = 1). If it is, and we haven't counted past 7 yet, then we also act on the clock.
You can see the rest here.
Conclusion
It took me a long time to get there but I found two simple ways of adding swing to my 808-style sequencer: either a time delay on the even pulses or a faster internal clock. When following an external MIDI clock, my fast internal clock runs in short bursts in between the MIDI clock messages.