MIDI Master Clock and Transport

Started by garcho, May 30, 2020, 04:51:53 PM

Previous topic - Next topic

garcho

Howdy digifolks! I hope it isn't too awful of me posting this here, considering it's got nothing to do with guitar pedals. But since this corner of the forum is a little less busy, I figured maybe I'm not mucking things up too bad? I could just put it in the OT board if it's obnoxious. I'm always super impressed with the people who frequent this forum, so I'm curious if any gurus here have some thoughts on this (which I posted on the Teensy forum):

I want to make a MIDI master clock to sync a number of devices to one tempo and control their individual start/stop functions.

I only have 2 arms to start or stop more than 2 devices, and I don't want to have one sequencer start or stop the entire MIDI chain. So what I envision is to dial in a BPM with an encoder, displayed on an OLED screen, and have start/stop buttons for 8 different MIDI DIN outputs. Because I'm only interested in sending System Messages, using one TX and sending multichannel data won't work. Everything is DIN, no USB. Therefore, I believe, I'll need to use multiple serial TX, like on the Teensy 4.1, which has 8. Perhaps I'm wrong, I'm a newb! So the big questions are:

How to sync multiple serial outputs? Is it possible to run 8 serial outputs that will produce 8 MIDI clock signals that are basically in sync with each other? MIDI is 31250 while Teensy 4.1 is 600MHz, so serial sync might not matter? Is that a correct assumption? So, if I have something like an interrupt that includes sending MIDI clock messages to all serial outputs once every 1/24 of a beat, I can forget about each TX being microscopically off from each other? Are there any other concerns I haven't thought of? A million?

How to sync button presses? Let's say there are 8 start/stop buttons, one for each TX. And let's say, for instance, I want a drum machine and sequencer to start or stop exactly together, so I try to simultaneously press the buttons that correspond to their respective TX outputs and the System Messages are sent at the same time. But time for MIDI is measured in 1/31250ths, right? So how to make sure it is the exact same time and not say, a 1/32 or 1/64 note off? Is there a way I can make sure start and stop messages only go out on say, 1/4 notes?
One half-baked idea I had: Let's say once every 24 clock pulses, 8 EEPROM addresses are read. And in the loop, there are 8 buttons being read. When button1 is pressed, a "1" is written to EEPROM(1). At the next conclusion of the 24 pulse cycle, when EEPROM is read, if there is a "1" in address 1, then send a start/stop message along TX1. I guess you'd have to keep track of start/stop state so next time the button is pressed, the opposite message is sent. Then rewrite EEPROM(1) as a "0", so no messages are sent until the next time button1 is pressed.
To be able to change the duration variable to be 1/8, 1/4, 1/2, 1/1, etc., would be ideal. Kind of like the LED blinking, I was thinking if Teensy was counting MIDI clock pulses, you could % by 12 for 1/8 notes, or 24 for 1/4, 48 for 1/2, or 96 for 1/1. Then you could for instance, set the start/stop to go out on whole notes, leisurely hit buttons 3, 4, and 7 within a whole note's duration, and next time the 96 pulse cycle concludes, the stop/start messages go out on TX 3, 4, and 7. That would be great for performing.
Does that make sense? Would something like that work? I'm completely inexperienced with using EEPROM. What's a better way of doing it? I haven't tried this out at all yet. Is there a way to count MIDI clock pulses? Like, each time the MIDI clock interrupt is called, part of the function is to ++ a counter? Then modulo the int and every time it == 0 send out the call to read EEPROM?

Here is where I'm at, code posted below. It's based on an Arduino Uno MIDI master clock code by Eunjae Im. It's just two outputs for now, to make it easier to read. Right now it basically works well, but I really want TX1 and TX2 (and 3-8, obviously) to sync up to the 1/4 (or 1/8, 1/2, 1/1) note flawlessly.

Thanks for your time, and for reading this long post. I have some electronics experience in the analog realm but my only coding experience is via Arduino and it's limited. Let me know what else I can do to make any help I might receive easier to give. Cheers y'all!


#include <Bounce2.h>
Bounce debouncer1 = Bounce();
Bounce debouncer2 = Bounce();

#include <Adafruit_SSD1306.h>
#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

#include <TimerOne.h>
#include <Encoder.h>

#define LED_PIN1 2
#define LED_PIN2 3

Encoder myEnc(22, 23);

const int button_start1 = 9;
const int button_start2 = 10;

volatile int blinkCount1 = -1;

#define BLINK_TIME 12

#define MIDI_START 0xFA
#define MIDI_STOP 0xFC
#define MIDI_TIMING_CLOCK 0xF8

#define CLOCKS_PER_BEAT 24
#define MINIMUM_BPM 20
#define MAXIMUM_BPM 300

volatile unsigned long intervalMicroSeconds;

int bpm;

bool playing1 = false;
bool playing2 = false;

bool display_update = false;

void setup(void) {

  debouncer1.attach(button_start1, INPUT_PULLUP);
  debouncer1.interval(5);
  debouncer2.attach(button_start2, INPUT_PULLUP);
  debouncer2.interval(5);
 
  Serial1.begin(31250);
  Serial2.begin(31250);
 
  bpm = 120;
   
  Timer1.initialize(intervalMicroSeconds);
  Timer1.setPeriod(60L * 1000 * 1000 / bpm / CLOCKS_PER_BEAT);
  Timer1.attachInterrupt(sendClockPulse); 

  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  display.setTextColor(WHITE);
  display.setTextSize(3);
  display.setCursor(35,10);
  display.print(bpm);
  display.display();
}

void bpm_display() {
  updateBpm(); 
  display.clearDisplay();
  display.setTextSize(3);
  display.setCursor(0,0); 
  display.setTextColor(WHITE, BLACK);
  display.print("     ");
  display.setCursor(35,10);
  display.print(bpm);
  display.display();
  display_update = false;
}

int oldPosition;

void loop(void) {

  debouncer1.update();
  if (debouncer1.fell()) {
    startOrStop1();
  }

  debouncer2.update();
  if (debouncer2.fell()) {
    startOrStop2();
  }

  byte i = 0;
  long newPosition = (myEnc.read()/4);
  if (newPosition != oldPosition) {   
    if (oldPosition < newPosition) {
      i = 2;
    } else if (oldPosition > newPosition) {
      i = 1;
    }
    oldPosition = newPosition;
  }

  if (i == 2) {
        bpm++;
        if (bpm > MAXIMUM_BPM) {
          bpm = MAXIMUM_BPM;
        }
        bpm_display();         
      } else if (i == 1) {
        bpm--;
        if (bpm < MINIMUM_BPM) {
          bpm = MINIMUM_BPM;
        }
        bpm_display();
      } 
}

void startOrStop1() {
  if (!playing1) {
    Serial1.write(MIDI_START);
  } else {
    digitalWrite(LED_PIN1, LOW);
    Serial1.write(MIDI_STOP);
  }
  playing1 = !playing1;
}

void startOrStop2() {                   
  if (!playing2) {
    Serial2.write(MIDI_START);
  } else {
    digitalWrite(LED_PIN2, LOW);
    Serial2.write(MIDI_STOP);
  }
  playing2 = !playing2;
}

void sendClockPulse() { 
  Serial1.write(MIDI_TIMING_CLOCK);
  Serial2.write(MIDI_TIMING_CLOCK);

  blinkCount1 = (blinkCount1 + 1) % CLOCKS_PER_BEAT;
 
  if (playing1) {
    if (blinkCount1 == 0) {
      digitalWrite(LED_PIN1, HIGH);     
    } else {
      if (blinkCount1 == BLINK_TIME) {
        digitalWrite(LED_PIN1, LOW); }
    }
  }
 
  if (playing2) {
    if (blinkCount1 == 0) {
      digitalWrite(LED_PIN2, HIGH);     
    } else {
      if (blinkCount1 == BLINK_TIME) {
        digitalWrite(LED_PIN2, LOW); }
    }
  } 
}   // end sendClockPulse

void updateBpm() {
  long interval = 60L * 1000 * 1000 / bpm / CLOCKS_PER_BEAT; 
  Timer1.setPeriod(interval);
}

  • SUPPORTER
"...and weird on top!"

Digital Larry

I'll tell you what I remember from programming MIDI in microcontrollers a long time ago.  I was using something like a 12 MHz 8031.

You have a UART which is the parallel/serial convertor for each MIDI in/out.  When you write a byte into it, it sends it out serialized at the programmed baud rate, along with framing of 1 start and 1 stop bit.  So, If you were writing to 8 UARTS in a row, there's going to be some instructions to look up the value you want to write.  If you're going to write the same byte to all of them, then the delay between writing to 1 vs. the next one is probably just a few cycles and I'd guess way faster than even a single bit width at 31250.   So they will still unavoidably be offset from each other, but it's not going to be by very much.

And I'm sure with some clever programming you can queue button presses to send the commands all "at once" on the next boundary, 1/4 note, measure, whatever, and it's just a matter of how you blink an LED to tell yourself what's going on etc. It becomes a UI problem as it's certainly possible to do anything you want along those lines.
Digital Larry
Want to quickly design your own effects patches for the Spin FV-1 DSP chip?
https://github.com/HolyCityAudio/SpinCAD-Designer

Rob Strand

I'm pretty sure there's standard methods out there to sync midi devices to a master.   I don't know the details.
Send:     . .- .-. - .... / - --- / --. --- .-. -
According to the water analogy of electricity, transistor leakage is caused by holes.

garcho

Everything works as far as sync to clock, start/stop, it's on the breadboard working fine. I'm just looking for a way to quantize the start/stop messages to the beat instead of the instant I hit the button. I've written some code that checks EEPROM for a button press within a certain time and calls a function at a later time; now just need figure out how to make that time the MIDI clock interrupt period*24 (for 1/4 note, 12 for 1/8, 48 for 1/2, etc).
  • SUPPORTER
"...and weird on top!"

Rob Strand

#4
I'll explain one method in simple terms without getting caught up with lingo, like event handlers etc. - Since I'm not familiar with interrupt and event handling features your system provides.

Instead of actually sending the START/STOP *when* you press the button, you set a flag like queuestart1 = true.   You do the same for button two.   The flag being true means that the start operation is put into a queue.

You then set-up a timer which is quantized at the period of quantization you want.   You need to set-up some sort of function (event handler) which gets called when the timer trips.  In that function you check each of the queuestart flags and if they are set you send out the start message and reset the flag.   The process of resetting the flag means the queued request to start has been completed and it will not send any more starts unless the button is pressed.  If there is more than one flag set it will get sent out on the same timer event.   Same idea for stop.

Forgive me if I haven't understood your problem exactly.   Hopefully you get the general idea that requests are queued and the messages are sent out synchronised to the local timer.    From that you should be able to massage the details into what you want.
Send:     . .- .-. - .... / - --- / --. --- .-. -
According to the water analogy of electricity, transistor leakage is caused by holes.

garcho

^ great! That makes a lot of sense, though I have to digest it further.

Thanks for the direction, and your time! I might be back to poke you for more questions after doing some homework :)
  • SUPPORTER
"...and weird on top!"

Digital Larry

Quote from: garcho on May 31, 2020, 01:05:10 AM
Everything works as far as sync to clock, start/stop, it's on the breadboard working fine. I'm just looking for a way to quantize the start/stop messages to the beat instead of the instant I hit the button. I've written some code that checks EEPROM for a button press within a certain time and calls a function at a later time; now just need figure out how to make that time the MIDI clock interrupt period*24 (for 1/4 note, 12 for 1/8, 48 for 1/2, etc).
As I recall, if MIDI clock is running, it's just running.  There's no beat/tick info encoded in it. So to count beats you'd need to reset a counter when "start" was received or sent.  So, increment a counter with every MIDI clock received and reset that to zero when MIDI start is received or sent.

Then, if you want to have an expression to count beats, etc. then you need to do a "modulo" operation with however many MIDI clocks are contained within whichever boundary you are after.  e.g. starting from 0, 24 clocks per quarter note, when the counter modulo 24 is zero, there's your boundary.
Digital Larry
Want to quickly design your own effects patches for the Spin FV-1 DSP chip?
https://github.com/HolyCityAudio/SpinCAD-Designer

garcho

I made the button in the loop simply change state true/false and then put a counter in the timer interrupt that can be adjusted by 12/24/48/96, when the counter % == 0, the start/stop message gets sent. I had never heard of "flags" before, even though I understood the concept; that was the push I needed to figure this out. Like usual, I was making it more complicated than it needed to be! Thanks for the time and help gentlemen, it works perfectly now.
  • SUPPORTER
"...and weird on top!"

garcho

QuoteAs I recall, if MIDI clock is running, it's just running.  There's no beat/tick info encoded in it.

Not sure what you mean but MIDI serial is at 31250 and MIDI clock messages are sent every 1/24th of a beat, that's how tempo is handled for sequencers. Again, thanks for the time :)
  • SUPPORTER
"...and weird on top!"

potul

Instead of having multiple UARTS, can't you just have one and use some hex inverters to split it in multiple MIDI OUTs? That'w what I've always done whenever I need to send MIDI to multiple devices...


garcho

For sure you can do that, like a MIDI through box. But the problem is that start and stop messages are System Messages, meaning they go to every channel. If I only want say 3 out of 8 devices to start their sequencers, I'm out of luck. That's why I need separate MIDI serial for each output/device.
  • SUPPORTER
"...and weird on top!"

potul

Ok, I see what you need. Correct, the MIDI start/stop messages are omni, they don't go to a specific channel.

free electron

Or instead of 8 uarts use one and a bunch of 1bit buffers with an oe input pin (Output Enable), ie like this one:
http://www.ti.com/lit/ds/symlink/sn74lvc1g125.pdf?ts=1590997870637
1 such chip per midi output. A generic gpio pins can be used to switch the midi outputs on or off. Basically to block or pass the midi signal from one single uart. Once off, the output goes tri-state/hi-z, so to ensure the unused midi out stays in the idle state i'd add pull up resistors right after the buffers.
Another advantage is the outputs will be perfectly in sync, since there is only one source. With 8 uarts how can you be sure all of them will start to transmit simultaneously? Especially in such heavily abstracted from the hardware environment as Arduino.

garcho

#13
^ that was my first idea, basically. Buffer the one UART on an Uno for all MIDI outs, and have some kind of switching for each output that determined if the master start/stop command made it through or not. That was the part I couldn't figure out because I understand so little of it, I wasn't sure where to start, or even what words to use in a google search. Is there a way to only filter out a word, not the entire data signal? Anyway, as I was looking over an Arduino Mega board for something else, I thought "Huh, what if I just use another one of these UARTs?" Then the Teensy 4 came out and here I am, 8 UARTs deep.

QuoteOnce off, the output goes tri-state/hi-z, so to ensure the unused midi out stays in the idle state i'd add pull up resistors right after the buffers.
As you've probably noticed, I don't really know what I'm talking about, but when I read this I ask myself "how will the 24ppqn MIDI clock message go out if the buffer is off?"

I still need to do some actual testing but judging by ear, there is no issue with synchronicity; what time difference there is between UARTs doesn't seem to make any difference. The Teensy 4 runs at 600MHz, about 20,000 times faster than MIDI baud rate. There is only one interrupt for all UARTs, so I assume each MIDI clock message happens immediately after each other as it's written in the code, which means UART 8 is 1µs off from UART 1. Good enough for music! EDIT:I did the math wrong, UART 8 isn't 1µs off from UART 1, it's only 0.01µs off. Nano seconds are acceptable. But maybe I've done the math wrong again... (1s/600,000,000)*8

I think some form of what you're saying would be ideal, but until I figure out how to interject a word to only one output and not all, I don't see how to move forward.
  • SUPPORTER
"...and weird on top!"

free electron

Potential relevant project alert! :)
https://www.hackster.io/news/deftaudio-s-midi-breakout-boards-bring-easy-3x3-or-5x5-midi-support-to-the-teensy-3-2-teensy-4-0-1c78af04bd4d

I think i know what you mean, ie. all the devices get a sync signal, but only the "chosen one" gets start / stop etc. So, a hardware solution i mentioned isn't really a solution.
A midi merger is a way to go and i think the project above can do that - the Teensy4.1 version.
At some point i will be facing the same problem. I think i will use it as an excuse to exercise some Verilog and make a 8 hardware synced uart block for ultra minimal latency.

Btw, i don't think such a complex MCU as the one used in T4 has a single uart interrupt. Most likely it's the arduino that exposes it and does a lot more stuff behind the scenes. I'm pretty sure you can't expect a single clock interrupt latency between the writes to each TX buffer. Even a typical write function for TX would look like:
- jump to subroutine
- write the byte to the buffer
- update the pointers/check the buffer boundaries
at this point the UART most likely gets it's own INT (tx buffer not empty) to send the data, which also introduces latency. Not to mention all the stuff Arduino does in background, which can trigger interrupt with higher priority than the UART. Still the overall latency will be almost negligible for such a beast as T4, but as it often happens, the devil is in the details. 

garcho

QuoteA midi merger is a way to go

My understanding of a MIDI merger is a device that takes multiple MIDI inputs, and combines them into one output. I don't see how that's relevant to anything in this topic. Why would that help in filtering out transport messages to individual devices?

The maker of Teensy, Paul Stoffregen, was generous with his time and ran a few tests. If you're in a hurry, I can sum it up by saying he found that the widest gap in sync between the serial outs was about 30μs. I'm cool with that. I've played with and have recorded some amazing drummers, and 30µs doesn't mean anything to them either. Or any human, for that matter. If you have a digital audio recording setup with round-trip latency of 1ms, you're doing great. The sharpest of humans can "perceive" 10ms, maximum, most of us are more like 15, maximum. 30µs? Pshaw.


QuoteI'm pretty sure you can't expect a single clock interrupt latency between the writes to each TX buffer.

You're totally right, as you can see, I don't know much at all about this stuff!
  • SUPPORTER
"...and weird on top!"

free electron

By merger i meant something that combines two or more midi streams, wheter they are all coming from a midi inputs or a button press injecting a message into a stream.

You are totally right about be 30µs being just "meh" in the large scale. Paul as always did a great investigation job.
My idea of making hardware based, synced group of UARTs is just an excuse for myself to get back and play more with Verilog ;) I think with PSoC5LP's glue logic it can have up to 24UARTS, so it might be an option for even larger midi mixers.

garcho

Quoteit can have up to 24UARTS, so it might be an option for even larger midi mixers.

I haven't ever heard of PSoC 5LP, I'll check it out some more, looks cool, keep us updated!
  • SUPPORTER
"...and weird on top!"

free electron

Made by Cypress, Arm cortex m3. What makes it very versatile is instead of fixed digital peripherals it has a bunch of cpld glue logic cells. IDE is unfortunately Windows only, but really decent and works on a virtual win machine: PSoC Creator. It has quite a big library of peripherals or logic blocks which you can just drag and drop onto schematic, connect everything graphically and click build - it will generate all the api's and config everything.
I'd recommend getting this dev stick if you'd like to play with them:
CY8CKIT-059
Has a top of the line PSoC5LP MCU,  programmer and a full hardware debugger + usb<->serial as a small module, which you can later snap off and use for other projects.
available through mouser/digikey.