Skip to content

Instantly share code, notes, and snippets.

@cowboy
Last active February 3, 2024 17:42
Show Gist options
  • Save cowboy/d2d23392d634de0ba62eb2d60aecfa42 to your computer and use it in GitHub Desktop.
Save cowboy/d2d23392d634de0ba62eb2d60aecfa42 to your computer and use it in GitHub Desktop.
JRS syncatron
// To give your project a unique name, this code must be
// placed into a .c file (its own tab). It can not be in
// a .cpp file or your main sketch (the .ino file).
#include "usb_names.h"
// Edit these lines to create your own name. The length must
// match the number of characters in your custom name.
#define MIDI_NAME {'S','y','n','c','a','t','r','o','n'}
#define MIDI_NAME_LEN 9
// Do not change this part. This exact format is required by USB.
struct usb_string_descriptor_struct usb_string_product_name = {
2 + MIDI_NAME_LEN * 2,
3,
MIDI_NAME
};
// ===========================================================================
// Syncatron
// USB MIDI device -> Clock/Play/Stop/Continue -> DIN MIDI out - for Teensy LC
// ===========================================================================
// Settings:
// Tools > USB Type to "MIDI"
#include <MIDI.h> // access to serial (5 pin DIN) MIDI
#include <Bounce.h> // Teensy debounce library
MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, downstreamDinMIDI);
// =====================
// LED & SWITCH BASICS
// =====================
int LED_OTHER = 21;
int LED_HOST_ACTIVITY = 5;
int LED_RESTART_PENDING = 16;
int BUTTON_PIN = 3;
void blink(int count) {
int delay_ms = 100;
for (int i = 0; i < count; i++) {
digitalWrite(LED_OTHER, HIGH);
delay(delay_ms);
digitalWrite(LED_OTHER, LOW);
delay(delay_ms);
}
}
Bounce BUTTON = Bounce(BUTTON_PIN, 5);
bool restart_pending = false;
// =====================
// SETUP
// =====================
void setup() {
downstreamDinMIDI.begin(MIDI_CHANNEL_OMNI);
pinMode(LED_OTHER, OUTPUT);
pinMode(LED_HOST_ACTIVITY, OUTPUT);
pinMode(LED_RESTART_PENDING, OUTPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP);
blink(3);
}
// =====================
// CLOCK LOGIC
// =====================
// Assume 4/4 time
int BEATS_PER_MEASURE = 4;
int PPQN = 24;
int MAX_PULSES = BEATS_PER_MEASURE * PPQN;
bool clockRunning = false;
int clockCounter = 0;
void send_play() {
static uint8_t mmc_play[6] = {0xF0, 0x7F, 0x7F, 0x06, 0x02, 0xF7};
downstreamDinMIDI.sendSysEx(6, mmc_play);
}
void send_stop() {
static uint8_t mmc_stop[6] = {0xF0, 0x7F, 0x7F, 0x06, 0x01, 0xF7};
downstreamDinMIDI.sendSysEx(6, mmc_stop);
}
void updateClock() {
if (clockCounter % PPQN == 0) {
digitalWriteFast(LED_OTHER, HIGH);
} else if (clockCounter % (PPQN / 2) == 0) {
digitalWriteFast(LED_OTHER, LOW);
}
// int currentBeat = clockCounter / PPQN;
if (restart_pending && clockCounter == 0) {
downstreamDinMIDI.sendStart();
digitalWriteFast(LED_RESTART_PENDING, LOW);
restart_pending = false;
}
if (++clockCounter == MAX_PULSES) {
clockCounter = 0;
}
}
void startClock() {
clockCounter = 0;
clockRunning = true;
}
void stopClock() {
clockCounter = 0;
clockRunning = false;
digitalWriteFast(LED_OTHER, LOW);
}
// =====================
// LED MIDI ACTIVITY
// =====================
// A variable to know how long the LED has been turned on
elapsedMillis hostActivityLedTime;
// Different types of MIDI messages should display for different lengths of time
const int MIDI_MESSAGE_NONE = 0;
const int MIDI_MESSAGE_DEFAULT = 1;
const int MIDI_MESSAGE_CLOCK = 2;
const uint8_t ledTimeMap[3] = {
0, // MIDI_MESSAGE_NONE
15, // MIDI_MESSAGE_DEFAULT
1 // MIDI_MESSAGE_CLOCK
};
// Keep track of the current MIDI message type
int hostCurrentMessageType = MIDI_MESSAGE_NONE;
// =====================
// RUN LOOP
// =====================
void loop() {
bool hostActivity = false;
// Read messages the PC (upstream host) sends and forward them to devices
if (usbMIDI.read()) {
byte type = usbMIDI.getType();
byte data1 = usbMIDI.getData1();
byte data2 = usbMIDI.getData2();
byte channel = usbMIDI.getChannel();
const uint8_t *sys = usbMIDI.getSysExArray();
hostActivity = sendToDownstreamDevice(type, data1, data2, channel, sys);
}
// Flash LED on activity
if (hostActivity) {
digitalWriteFast(LED_HOST_ACTIVITY, HIGH);
hostActivityLedTime = 0;
}
if (hostCurrentMessageType != MIDI_MESSAGE_NONE && hostActivityLedTime > ledTimeMap[hostCurrentMessageType]) {
hostCurrentMessageType = MIDI_MESSAGE_NONE;
digitalWriteFast(LED_HOST_ACTIVITY, LOW);
}
BUTTON.update();
// button pressed
if (BUTTON.fallingEdge()) {
restart_pending = !restart_pending;
if (restart_pending) {
digitalWriteFast(LED_RESTART_PENDING, HIGH);
}
else {
digitalWriteFast(LED_RESTART_PENDING, LOW);
}
}
}
// =====================
// HANDLE MIDI MESSAGES
// =====================
// Send data from the upstream host (eg. computer) to the downstream MIDI device (eg. controller)
bool sendToDownstreamDevice(byte type, byte data1, byte data2, byte channel, const uint8_t *sysexarray) {
int prevMessageType = hostCurrentMessageType;
hostCurrentMessageType = MIDI_MESSAGE_DEFAULT;
if (type == usbMIDI.SystemExclusive) {
unsigned int SysExLength = data1 + data2 * 256;
downstreamDinMIDI.sendSysEx(SysExLength, sysexarray, true);
return true;
}
// Handle clock and MMC messages
if (type == usbMIDI.Clock) {
hostCurrentMessageType = prevMessageType == MIDI_MESSAGE_NONE ? MIDI_MESSAGE_CLOCK : prevMessageType;
updateClock();
} else if (type == usbMIDI.Start || type == usbMIDI.Continue) {
startClock();
} else if (type == usbMIDI.Stop) {
stopClock();
} else {
hostCurrentMessageType = prevMessageType;
return false;
}
midi::MidiType mtype = (midi::MidiType)type;
downstreamDinMIDI.send(mtype, data1, data2, channel);
return true;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment