garcho
Member
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!
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!
Code:
#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);
}