tap.io v0.3.0

Author:
License: GPL
Github: logsol/clock/tap.io/tap.io v0.3.0.axo

Inlets

bool32.rising reset

bool32 master

int32 set clock by samples of 1 bar (only on change)

int32 factor

Outlets

int32.positive 24ppqsmp

int32.positive barsamples

bool32 slave

bool32 start

int32 bpm

bool32.pulse 24ppq

Declaration
int isClockIncoming;

// measure
int16_t timer;
int lastInletReset;
int32_t lastInletSamples;
int32_t lastInletFactor;
const int averageSteps = 96;
int16_t averagePool[96];
int averagePoolWriteIndex;
float currentAverage;
int shouldTrigger24Ppq;
int midiStartTrigger;
int forceMaster;
int barsamples;

// osc
int32_t Phase;
int32_t oldPhase;

void calculateCurrentAverage(bool samplesUpdateNeeded, int inl_samples,
                             bool factorUpdateNeeded, int inl_factor) {
  inl_factor = inl_factor ? inl_factor : 16;
  if ((inl_samples > 0 && samplesUpdateNeeded) || factorUpdateNeeded) {
    currentAverage = (inl_samples / 24.0 / 4.0) * (inl_factor / 16.0);
    barsamples = inl_samples * (inl_factor / 16.0);
  }
}

void onMidi(int status) {
  if (!forceMaster) {
    if (status == MIDI_TIMING_CLOCK) {
      measureClock();
      shouldTrigger24Ppq = true;
      Phase = 0;
    }
    if (status == MIDI_STOP) {
      isClockIncoming = false;
    }
    if (status == MIDI_START) {
      midiStartTrigger = true;
    }
  }
}

// Measure samples since last call and calculate average
void measureClock() {
  isClockIncoming = true;
  averagePool[averagePoolWriteIndex] = timer;
  // LogTextMessage("timer %d", timer);
  timer = 0;
  averagePoolWriteIndex =
      (averagePoolWriteIndex >= averageSteps) ? 0 : averagePoolWriteIndex + 1;
  currentAverage = 0;

  for (int i = 0; i < averageSteps; i++) {
    currentAverage += averagePool[i];
  }
  barsamples = currentAverage * lastInletFactor;
  currentAverage /= averageSteps;
}

// Return current calculated BPM from currentAverage (samples)
float getBpm() {
  // LogTextMessage("average %f", currentAverage);
  return 48000.0 / currentAverage * 2.5;
}

/**
 * For (1431655.7653 = 1hz) see
 * http://community.axoloti.com/t/generate-square-wave-from-milliseconds-or-hz/391/2
 * x in samples  samples per second    convert to ms      24ppq     to get 16th
 * in ms 1000          / 48000               * 1000             * 24      / 4
 * = 125ms 1000 / 125 to get hz = 8
 */
bool get24ppq(bool reset) {
  // internal oscillator

  if (getStart(reset)) {
    Phase = 0;
  } else {
    double freq;
    freq = 1000.0 / (currentAverage / 48.0);
    Phase += int(1431655.7653 * freq);
  }

  int internalOscPulse = (oldPhase <= 0 && Phase > 0);
  oldPhase = Phase;
  bool result =
      !forceMaster && isClockIncoming ? shouldTrigger24Ppq : internalOscPulse;
  shouldTrigger24Ppq = false;
  return result;
}

bool getStart(bool inl_reset) { return midiStartTrigger || inl_reset; }
Init
averagePoolWriteIndex = 0;
currentAverage = 1000;
isClockIncoming = false;
shouldTrigger24Ppq = false;
midiStartTrigger = false;
forceMaster = false;

lastInletReset = false;
lastInletSamples = 0;
lastInletFactor = 0;

// set default exisiting average to 120 bpm
for (int i = 0; i < averageSteps; i++) {
  averagePool[i] = currentAverage;
}
barsamples = 48000 * 2; // 120bpm
Control Rate
bool reset = inlet_reset && !lastInletReset;
bool samplesUpdateNeeded = inlet_samples != lastInletSamples;
bool factorUpdateNeeded = inlet_factor != lastInletFactor;

forceMaster = inlet_master;
calculateCurrentAverage(samplesUpdateNeeded, inlet_samples, factorUpdateNeeded,
                        inlet_factor);

outlet_bpm = (int)(getBpm() + 0.5);
outlet_slave = isClockIncoming;
outlet_24ppq = get24ppq(reset);
outlet_start = getStart(reset);
outlet_24ppqsmp = (int)currentAverage;
outlet_barsamples = barsamples;

lastInletReset = inlet_reset;
lastInletSamples = inlet_samples;
lastInletFactor = inlet_factor;

midiStartTrigger = false;
Audio Rate
if (isClockIncoming) {
  timer++;
} else {
  timer = 0;
}
if (timer >= 12000) {
  isClockIncoming = false;
}
Midi Handler
onMidi(status);

Privacy

© 2024 Zrna Research