Building & Coding a drum sequencer: Midi, Timer, Microcontroller…

I try to build a drum sequencer. :slight_smile: It’s my first hardware/microcontroller project.

Could anyone please help me understanding the code of the clock of midipal?

What I kind of understand:

  1. Update() in clock.cc/clock.h saves calculated values in a intervals array. The values are calculated of a tick/step lenght by the bpm and the selected groove. Groves/swing can alter a tick duration.
  2. Tick() goes through the saved values.

But when, where and how often does tick() get called? I just can’t find it out :confused: Could anyone please give me a hint?

Here

I use the built-in 16-bit timer of the ATMega to periodically raise an interrupt. The frequency of the interrupt is given by the formula: f_cpu / 64 / counter_range ; so for example, for 120 BPM (= 48 Hz), the counter range is 6510.

When the interrupt is raised, I update a tick / beat counter to know exactly where I’m at in the groove pattern. This allows me to figure out the interval between ticks that needs to be reloaded in the timer counter.

Thanks :slight_smile: Okay, I didn’t knew about the built-in timer. I’m new to all the hardware programming.

I initialized the 16 bit timer on my arduino and set it to 6510.

Now ISR (TIMER1_COMPA_vect) gets called quite often. 64 times each beat count for 120bpm?

I don’t get the formula. 16000000 (16 mhz) / 64 / counterrange? What is counter range?

Sorry :confused:

counterrange is the value you write to the OCR1A register of the timer. I was giving you the formula to get the frequency from the value of the register. Obviously, you have to do it the other way round - from the BPM, find out the value of the register.

1 quarter note = 24 MIDI clock ticks.

120bpm = 120 beats per minute = 120 quarter notes per 60 seconds = 2 quarter notes per second = 48 MIDI clock ticks per second.

Hm, sorry I still don’t get it. It’s late…

E.g. 60 bpm has 24 midi clock ticks per second. Now I want to “repeat” the timer every 24 times per second to run it every midi tick, right?
Would this mean: OCR1A = 16000000 / 64 / 24?
No, this can’t be right :confused:

1 beat has 24 MIDI clock ticks (0xf8 events) in it, this is MIDI standard. This count does not change with BPM change, it’s a constant. What changes is the frequency 0xf8 events (aka MIDI clock ticks) are coming in. Beat is the same as quarter note. So:

60 BPM = 60 beats per minute = 1 beat per second
since each beat has 24 clock ticks in it, you’ll have 24 ticks per second, which means at 60BPM ticks are coming at 24Hz.

Yes, thats the part I understood. But what should I do with the 24?

What value do I need to set for OCR1A

Here 's a good article on AVR timer programming.

Okay thanks. Just a general question so that I can know after what I’m looking: The goal is to run the timer each midi tick, right?

The goal is to run the timer each MIDI tick.

x = a / y <=> y = a / x.

So:

frequency = f_cpu / 64 / counter_range

<=>

counter_range = f_cpu / 64 / frequency

Your frequency is 24 Hz (24 ticks per second because BPM = 60)

So you write 16000000 / 64 / 24 = 10416 in your counter register. Why do you think this can’t be right???

Note that the 64 in the above equation depends on how the timer has been initialized (mode and prescaler).

Yes, I found my mistake… I wrote the whole timer and timer count (OCR1A) initialization in the main loop instead of setup…
I saw the high repeat time and tried to let a led blink each 24 calls. I compared the led blinking with the midi clock of ableton and the led blinked obviously much faster… this confused me so much.
Anyway, I finally got it!
Thanks so much :slight_smile:

I find the implicit loop around the “loop()” block in the arduino framework quite distracting. Years of training have taught me to notice while (1) { blocks and attach meaning to them… and poof they are gone!

Yep me too… I only programmed objective-c before, so using a main loop isn’t super familiar for me.

Hm, I realize a strange problem. If I set the bpm to a value smaller then 79 the timer gets crazy fast… Here is my code:


TCCR3A = TCCR3B = 0;
bitWrite(TCCR3B, CS11, 1);
bitWrite(TCCR3B, WGM12, 1);
timerSetFrequency();
bitWrite(TIMSK3, OCIE1A, 1);

#define frequency (((bpm)*(24))/60)
OCR3A = (F_CPU/ 8) / frequency - 1;

Does anyone know, why? I can’t find a mistake. The formula isn’t wrong or changing with a value smaller then 79…

A side question: If I want to have realtime midi recording for my device, should I just add a second 16bit timer with 96 „ticks“ per beat? Or do I need to consider anything else? I then would create a 4x96 sized array and add the midi note values to each array[tick]; Is this the correct way? :slight_smile:

And If I want to record several notes, let’s say a maximum of 16 notes, do I really need to create 16 4x96 sized sized arrays or do I overlook something? In objective-c I could have saved arrays in arrays.

I see two big mistakes with your code:

You are not careful at all in the way you evaluate your expressions.

Your expression looks like this:

(F_CPU/ 8) / (bpm * 24 / 60) - 1

So there will be a horrible loss of precision in the evaluation of the denominator. When the compiler sees integers, it evaluates everything as integers.

With BPM = 120, this is evaluated as:

(F_CPU/ 8) / (120 * 24 / 60) - 1 = (F_CPU / 8) / 48 - 1

With BPM = 121, or BPM = 122 this is evaluated as:

(F_CPU/ 8) / (121 * 24 / 60) - 1 = (F_CPU / 8) / 48 - 1

Because 120 * 24 / 60 = 121 * 24 / 60 = 122 * 24 / 60 using integer math.

So basically, you will get the same tempo when your clock is set to 120 BPM as when it is set to 121 BPM or 122 BPM. That’s not what I call a precise clock :slight_smile:

You have to re-arrange your expression so that it does not contain any operation incurring a loss of precision. For example :

(60 * F_CPU / 8) / (bpm * 24)

Forget all I have said if your bpm variable is defined as a float. But then you have made an even bigger mistake of using floats on a 8-bit embedded platform :slight_smile:

For a reason I can’t understand, you are using a prescaler of 8 rather than a prescaler of 32.

Why, oh why? Using a prescaler of 8 yields the following formula:

count = (60 * F_CPU / 8) / (bpm * 24)

rather than:

count = (60 * F_CPU / 32) / (bpm * 24)

So with tempo = 76:

count = 65789

It just does not fit in a 16-bit timer.

>should I just add a second 16bit timer with 96 „ticks“ per beat?

Well, the ATmega has only one 16-bit timer - (maybe schrab would be up to the task of directly hacking the die to add one?). This means that either you increase the speed of your internal clock by a factor of 4 to reach a 96 ppqn resolution ; or you just stick to the 24 ppqn resolution.

> I then would create a 4x96 sized array and add the midi note values to each array[tick]; Is this the correct way?

> do I really need to create 16 4x96 sized sized arrays or do I overlook something?

Yes, you’re overlooking that 16x4x96 = 6kb while the chip you’re running this on has 2kb of RAM. And unless your user records breakcore, most of this will be zeros. What a waste! Dense data structures like that are okay when things are heavily quantized (say 4 ppqn like in the MIDIpal built-in sequencers), but are awful if you want high resolution - because of the waste.

The best option is to use a sorted array of (timestamp, event) pairs. The timestamp can be a 16 bit integer, allowing you to record over 170 bars at 96 ppqn ; and the event can be, for a basic note sequencer, a 8 bit integer - 7 lowest bit indicating the MIDI note and highest bit indicating whether the message is a note off or note on. With this, you can store up to 600 notes in the 2kb of the ATMega. There are tricks allowing you to lower the timestamp to 8-bit and compress things even more (in short: the representation in memory of your sequence is the bytecode of a program for a simple VM that will generate your sequence data) - but get something basic working first!

Omg! Thanks so much :slight_smile:
Thanks for all the clarification. I now set the timer to a prescaler of 32:
|bitClear(TCCR3B, CS22);|
|bitSet(TCCR3B, CS21);|
|bitSet(TCCR3B, CS20);|
|bitWrite(TCCR3B, WGM12, 1);|

I’m also now using the correct time frequency formula:
|OCR3A = (60 * F_CPU / 32) / (bpm * 24) /2;|

Strangely I need to add a /2, else it is half as fast. Everything else is working now. I’m to spoiled in writing code for a computer where I often don’t need to think about data values.


I’m using the ATmega2560 which has three additional 16bit timers :slight_smile: So I should be able to use another timer.
I also thought about timestamp + event combos, but I’m not sure how to save them. If I define an array, I need to predefine the size of it. In objective-c I could have used a mutable array, where you can add as many objects as you like without defining the size during initialization.
As timestamp value I could use the timer count. I only need to record maximal 2 bars, so 2x4x96.
Hm, I will take a look how I can realize the real time recording.

> So I should be able to use another timer.

But then your two timer interrupts would stomp on each other and you’ll get concurrency problems and/or jittery timing. I don’t understand the need for two clocks…

> In objective-c I could have used a mutable array

You can pre-allocate a fixed area of memory for your sequencer data, keep a variable to indicate how many entries in this area are actually occupied by meaningful data (vs free), write routines for adding/removing/inserting data and tada you have a variable size array!

That’s more or less what the Objective-C runtime is doing in the background anyway…

@pichenettes: thanks for answering in such detail to questions like this.
i learned something today :slight_smile:

Yep thanks again :slight_smile:
I hope it’s okay to ask another timer/midi related question.

I’m coding on a drum sequencer and it is working fine so far. However there is a small problem. As soon as the sequencer starts, I send a midi start message. I also send the midi clock ticks. Ableton or any other midi device receives all the messages and it works fine. - However my steps/midi notes are always playing a few ms to late (maybe a 1/32 step to late). This is of course a problem…:confused:

Could you maybe take a look in my code? I reduced it to a minimum. It works on its own. It’s really short and I would be so thankful :slight_smile:

I simply can’t find a coding error and maybe it is some strange timer/midi delay mistake I make.

(Btw: Is it anyway possible to post code in a small scrolling box? I couldn’t find anything about it in the textile reference)

//Free to use for everyone :slight_smile:
byte playing = 0;
byte stepPosition = 0;
byte midiTickCount = 0;
int bpm = 120;
byte patternLength = 15;
byte stepArray[16] = {1,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0};

const byte noteOn = 153; //Channel 10
const byte noteOff = 137; //Channel 10

void setup() {
Serial.begin(31250); //sequencer normally starts on a button press. Reduced the code…
startStopSequence();
}

void loop() {
}

void startStopSequence() {
playing = !playing;
if (playing) {
stepPosition = 0;
midiTickCount = 0;
timerStart();
Serial.write(0xFA);
}
else {
timerStop();
Serial.write(0xFC);
}
}

void timerStart()
{
TCCR3A = TCCR3B = 0;
bitClear(TCCR3B, CS22);
bitSet(TCCR3B, CS21);
bitSet(TCCR3B, CS20);
bitWrite(TCCR3B, WGM12, 1);
timerSetFrequency();
bitWrite(TIMSK3, OCIE1A, 1);
}

void timerSetFrequency()
{
// Calculates the Frequency for the Timer, used by the PPQ clock (Pulses Per Quarter Note)
OCR3A = (60 * F_CPU / 32) / (bpm * 24) /2;
}

void timerStop(void)
{
bitWrite(TIMSK3, OCIE3A, 0);
TCCR3A = TCCR3B = OCR3A = 0;
}

ISR (TIMER3_COMPA_vect) {
if (midiTickCount 0) {
MIDIStepNote();
if (stepPosition patternLength) stepPosition = 0;
else stepPosition += 1;
}
//MidiTickCount reset
midiTickCount = midiTickCount + 1;
if (midiTickCount == 6) midiTickCount = 0;
Serial.write(0xF8);
}

void MIDIStepNote() {
if (stepArray[stepPosition] 1) {
Serial.write(noteOn);
Serial.write(60);
Serial.write(70);
}
//Sending a note off for the step before via velocity 0
int stepBefore = stepPosition-1;
if (stepBefore -1) stepBefore = patternLength;
if (stepArray[stepBefore] != 0) {
Serial.write(noteOn);
Serial.write(60);
Serial.write(0);
}
}

1/ A small comment:

I suggest you using

int patternLength = 15;

And then replacing patternLength by (patternLength - 1) in your code.

This won’t change anything in the way the code runs on the arduino, but I found it very confusing that the variable named “patternLength” does not actually represent the length of the pattern.

2/ I would send the clock immediately before the notes (if any)

3/ Use a MIDI monitoring program rather than live to judge the accuracy of your code - live might do its own things with the timing.