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.

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.

Wanted my loop pedal to sync with my drum machine but the RC-3 doesn't have MIDI so I hacked it to output a clock signal 1/ pic.twitter.com/OSuETAM5EN

— Julian (@okay_sure_cool) August 13, 2022

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);
  }
}