rabid.audio

Documenting my work at the intersection of technology and music.

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.

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