Last active
February 3, 2024 17:42
-
-
Save cowboy/d2d23392d634de0ba62eb2d60aecfa42 to your computer and use it in GitHub Desktop.
JRS syncatron
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// =========================================================================== | |
// 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