Arduino Trigger sequencer WIP

Hey,
I am currently planning a small trigger sequencer for my drum modules. It should be something in between the robaux SWT16, the Grayscale Algorhythm and the TipTop Circadian Rhythms. I have a Neotrellis 4x4 LED button pad and now I am thinking of how to condense the Circadian Rhythms 8x8 interface into 4x4 only :wink: My idea is to only implement the vertical view or CR and have the lower 8 buttons (two rows) as the steps and upper 8 buttons as functions to select the pattern/group/loops. I will probably add two or three extra buttons without ledsā€¦
First I want to get the ā€œbackendā€ of the sequencer going and then implement the U when that works. I have this so far:

So, I set up a timer that advances the tick/step/clock and all the pattern data are in a 8x8x8 array. At each step the output is written to the arduino pins (currently connected to leds, later outputs). At the end of a pattern the code checks if more patterns are selected for the loop and if so, enters the next pattern, so you can chain together multiple patterns to get longer sequences (just like CR). This seems to work okay so far. My main questions (for now :wink: ):
How do I make these gates into short triggers? because for now, The timer is the same as a step and if two steps in a row are high, the output never goes off. (Itā€™s the same problem that some people had with their analog/discrete sequencers :wink: ) I should probably already think how this fits together with an external clockā€¦
Should I separate the tick from the step/clock? I have seen this in a few examples but I currently donā€™t see how this would help, but maybe it could if I only set the output high during a short tick and not the whole step (16th note)?
Any other stuff in the code that looks suspicious and should be considered now in the early stages? :wink: I tried to document my intentions and hope it can be understood.

1 Like

Super fun idea. I have some of those Neotrellis things and want to do something similar one day.

I was going to say look into PWM, but it looks like you only have 6 real PWM pins, so youā€™re a couple of pins short - so maybe youā€™d need to bit bang something like PWM.

Iā€™d do something like have your onClock run 8 times faster and update the states every 8th onClock.
Depending on the states, you could use the intermediate ticks to either set or reset the output. This way you could possibly define gate length - i.e. 1/8th, 1/4, 1/2, 3/4 or 1. Then, on the intermediate ticks you check whether you want to turn it on or off depending on the state and the gate length.

Something else I noticed:
You are doing modulo arithmetic for your tick counter - which, depending on how itā€™s implemented can be a little slow. But if your tick counter rolls over on a power of two you can use bit masks to do the same thing:

tick++;
tick = tick & 0x07;

Maybe of dubious value, but I think itā€™s kind of neat.

1 Like

If youā€™re just clocking internally, you can clock at say 2x the trigger frequency and on odd clocks turn on whatever needs to be on, on even clocks turn off everything.

If you go to an external clock, on the rising edge you can turn on whatever needs to be on, and turn everything off on the falling edge.

If you need shorter output pulses you could do that with a faster clock ā€” a higher multiple of the trigger frequency. Then again an analog pulse shortening circuit might be easierā€¦

1 Like

My clock module, copying from the quinine module, uses the shift register to set the pins with the clock pulse high, delay for the pulse, then set them all back to low. It does this for every clock pulse, checking between each one for what should be low and high based on clock/beat divider or if it is set to random. Other algorithms, such as euclidean sequencing or simply stepping through a matrix, could easily be added.

1 Like

Thanks for the ideas! Yes, I guess I can run the internal clock a bit faster (I am reading about MIDI clock and 24PPQN, currently I have 4PPQN aka 16th notes, if I got this right). But that makes syncing with an external clock more difficult (or I need to make two cases with external and internal clock activatedā€¦)

The trick with the tick counter is really neat! But I guess I can see if I can get it to work with the more readable mod first :wink:

I would like to avoid analog pulse shortening, because it makes it impossible to have a (future) mode to put out gates instead of triggers and it also does not solve the issue of two consecutive beats leading to a signal that is always 1 :slight_smile:

I checked the quinie code and you probably mean this, @BenRufenacht right?

//output
  outputClock(outputClockBits);
  delay(4);
  outputClock(B00000000);

I donā€™t like that the delay locks up everything and might also stop inputs from being registered, but maybe 4ms is so short that itā€™s not really a problemā€¦ And itā€™s the most simple implementation, which I always like :wink:

Probably I will go with the 24PPQN and set up an extra interrupt/function at 4PPQN to advance my sequence and then turn off the outputs in one of the 24PPQN calls after that.

I found this interesting clock library by midilab where they have an example where they put the current note on a stack (not really a stack in the programming sense) with its duration (in terms of 96PPQN ticks) and then on each 96PPQN tick they go through the stack and decrease the duration of each note by one, turning them off once they reach zero. They also implemented syncing the internal with an external clock, but I donā€™t yet understand whatā€™s really happening there, but it seems nice!
Maybe this is already over engineered, and I hope that this all still works together with the neotrellis on the poor tiny Arduino nano, but also seems most clean way to go, what do you think?

I took a quick look through that midilab thing, looks reasonable. I took note of it and will look into it more, maybe I can make use of it, but as is itā€™s got support for Arduino, Teensy and Seeed, but not the STM32 things or SAMD things or rp2040 things I actually have. But then I followed to their webpage and found lots of interesting projects there - reminded me of the midibox stuff at ucapps.de

Cheers

1 Like

You could have a switch to enable or disable it. But yes, itā€™s not a fix for consecutive beats. Itā€™s just a way to make the trigger length variable once you have a software solution for the consecutive beats.

1 Like

I donā€™t know if itā€™s better or worse butā€¦

In my clock module I wanted to allow arbitrary clock duty cycle (actually itā€™s from 5% to 95% in increments of 5%) and not to go resetting the timer whenever the clock frequency was changed. So I ended up with a design where thereā€™s an interrupt every 100 Āµs; thereā€™s a variable cycle_start_time that holds the time at the start of a clock pulse, ontime is the amount of time the clock pulse is on, and period is the time from one clock pulse to another. Then the interrupt handler is:

void timerStuff()
{
  // Called by interrupt handler once every 100 us
  // When we pass ontime ticks, call cycle_off
  // When we pass period ticks, call cycle_on
  
  if (running)
    {
      tickcount += 100; 
      if (clock_state and tickcount - cycle_start_time >= ontime) // should be rollover safe
        {
          cycle_off();
          clock_state = false;
        }
      if (tickcount - cycle_start_time >= period)
        {
          cycle_on();
          clock_state = true;
          cycle_start_time += period;
        }
    }
}

It seems to work accurately enough up to clock rates of 83 Hz (maximum 208 beats per minute and 24 clock pulses per beat) with the full range of duty cycle (down to 600 Āµs on or off time). (cycle_on() and cycle_off() have to be fast enough of course.)

Yes, that is the method I have implemented. I am not worried about taking any inputs between clock pulses, since my only input is clock pulses or start/stop.

If you wanted to keep it running during the pulse, and also leave the ability to vary the pulse/gate length, you could use millis:

if (millis() - pulseStart >= pulseLength)

My understanding is millis() (or micros()) canā€™t be used inside the interrupt handler.

This would be in the loop, not in the interrupt handler. I think you have to disable the interrupts for setting the counters.

1 Like

Ah, okay.

In my case, in-the-loop handling of the timing wasnā€™t fast enough to keep up with 208 BPM * 24 pulses per beat and 5% or 95% duty cycle, so I used interrupts. And then in-the-loop handling of the encoder was causing it to miss. So I have another interrupt handler for the encoder rising edge, just a single line to detect the direction and the rest goes in the loop. (It was behaving like my washing machine, whose encoder misses if you turn it too fast. Sheesh. Kenmore should hire me.) Push button and OLED handling are all in the loop.

Yeah, I think having to disable interrupts to set the timer was what led me to just use 100 Āµs interrupts and do it that way.

I had to change all of the UI code from what quinie used. The encoder was not usable for me. Since the two interrupt pins on the micro were used by the inputs, I did not have one for the encoder. I think I tried three different encoder libraries before I found one that worked well.

1 Like

Iā€™m not sure I completely understood your meaning, but Arduino does support the RP2040.

You could also have a look at my morse code thingy: Arduino-Morse-Gate-Generator/arduino_morse_gate_generator_firmware.ino at main Ā· TimMJN/Arduino-Morse-Gate-Generator Ā· GitHub

Here Iā€™ve used the timerOne lib to generate an internal clock, while also being able to use the same interrupt routine to sync to an external clock. You could combine this approach with the millis() approach to set the end of every pulse. Anyway timing is more critical on the start of the beat than on its end.

1 Like

One of my pet peves of the whole Arduino setup, thereā€™s the Arduino IDE, and the various Arduino hardware platforms and one must take extra care when talking about things like ā€œsupporting Arduinoā€ in internet forums because of this.

In any case what I was trying to say was, when looking at the code, I saw a lot of the timer/interrupt setup wrapped in things like:

#if defined(ARDUINO_ARCH_AVR)
#if defined(TEENSYDUINO)
#if defined(SEEED_XIAO_M0)

So, probably not difficult to run on other hardware devices, but not out of the box either.

Cheers

Ah, OK, I see now.
It could indeed be more complicated to port/debug.