Loop pedal clock output
For a song I was working on, I really wanted to sync up a recorded bass loop with a drum machine. Unfortunately, MIDI is a feature only available on the more expensive loop pedals, not my lowly Boss RC-3.
I googled for a solution and ran across a post on Reddit that referenced a defunct blog post. The Wayback Machine didn’t archive the images but from the text I was able to put together enough information to recreate it. The original user found two test pads on the PCB that dipped in voltage when the tempo LEDs blinked.
![](/images/looper-hack-scope.jpg)
I connected these and a 3.3V power line to an ATTiny85, using two analog inputs to read the LED blinks. A state machine waits through the startup process where the LEDs blink a few times, and through one complete bar to get the tempo information before outputting clock signals to a 3.5mm jack. I also 4x’ed the clock frequency to output 16th notes instead of quarter notes.
![](/images/looper-hack-pcb.jpg)
![](/images/looper-hack-demo.gif)
The source code for this is pretty simple. Feel free to use it as you see fit!
size_t initializationState = 0; // 0-3=waiting for first three startup beats, 4=waiting for real beat 1, 5=counting beats while waiting for next beat 1, 6=initialized
bool wasTriggering = false; // was A or B triggered on the last tick
uint32_t lastBeatAt = 0; // the micros at which the last beat started
uint32_t lastSubdivisionAt = 0; // the micros at which the last subdivision started
uint32_t subdivisionLength = 0; // the number of micros between subdivisions
uint8_t subdivision = 0; // which subdivision we're on
uint32_t pulseStartedAt = 0; // micros at which the previous pulse started
const uint16_t THRESHOLD = 550; // 550/1024*3.1 = 1.66V
const uint32_t PULSE_LENGTH = 20000;
const size_t LED_PIN = 1; // change to 13 for MEGA
const size_t SUBDIVISIONS = 4;
void setup() {
// Serial.begin(9600);
pinMode(LED_PIN, OUTPUT);
}
void loop() {
uint32_t now = micros();
uint16_t a = analogRead(A2); // 1 beat
uint16_t b = analogRead(A3); // off beats
bool aTriggered = a < THRESHOLD;
bool bTriggered = b < THRESHOLD;
bool nowTriggering = aTriggered || bTriggered;
if (!wasTriggering && nowTriggering) {
// a new beat detected
if (initializationState < 4 && aTriggered) {
// got startup beat
initializationState++;
} else if (initializationState == 4 && aTriggered) {
// first real beat
lastBeatAt = now;
initializationState = 5;
} else if (initializationState == 5 && bTriggered) {
// got off-beat
lastBeatAt = now;
} else if (initializationState == 6 || (initializationState == 5 && aTriggered)) {
// new beat, start pulsing
initializationState = 6; // initialized
subdivisionLength = (now - lastBeatAt) / SUBDIVISIONS;
lastBeatAt = now;
lastSubdivisionAt = now;
subdivision = 0;
pulseStartedAt = now; // trigger quarter-period pulse
}
}
if (initializationState == 6 && subdivision < SUBDIVISIONS && (now - lastSubdivisionAt) > subdivisionLength) {
pulseStartedAt = now; // trigger quarter-period pulse
lastSubdivisionAt = now;
subdivision++;
}
wasTriggering = nowTriggering;
bool pulse = false;
if (initializationState == 6) {
pulse = (now - pulseStartedAt) < PULSE_LENGTH;
digitalWrite(LED_PIN, pulse);
}
}