int32 set clock by samples of 1 bar (only on change)
int32 factor
bool32.rising reset
bool32.rising tap
bool32 master
int32 bpm
bool32.pulse 24ppq
int32.positive 24ppqsmp
int32.positive barsamples
bool32 slave
bool32 start
bool32 tapping
int isClockIncoming;
// measure
int16_t timer;
int32_t tapTimer;
int tapTimePoolSize = 4;
int32_t tapTimePool[4];
int tapTimePoolIndex;
int lastInletTap;
int lastInletReset;
const int waitSamplesForTap = 96000;
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,
int inl_factor) {
inl_factor = inl_factor ? inl_factor : 16;
if (inl_samples > 0 && samplesUpdateNeeded) {
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; }
bool tapTempo(bool inl_tap, uint32_t factor) {
if (inl_tap) {
// todo convert to rising pulse, because now uses 1 sample before button
// release time
if (tapTimer == waitSamplesForTap) {
for (int i = 0; i < tapTimePoolSize; i++) {
tapTimePool[i] = 0;
}
tapTimePoolIndex = 0;
} else {
tapTimePool[tapTimePoolIndex] = tapTimer;
tapTimePoolIndex++;
tapTimePoolIndex %= tapTimePoolSize;
double average = 0;
int counted = 0;
for (int i = 0; i < tapTimePoolSize; i++) {
if (tapTimePool[i] != 0) {
average += tapTimePool[i];
counted++;
}
}
if (counted > 0) {
average /= counted;
currentAverage = average / 24.0;
barsamples = average * 4;
}
}
if (tapTimePoolIndex == 0) {
Phase = 0;
midiStartTrigger = true;
}
tapTimer = 0;
}
return tapTimer < waitSamplesForTap;
}
timer = 0;
tapTimer = waitSamplesForTap;
averagePoolWriteIndex = 0;
currentAverage = 1000;
isClockIncoming = false;
shouldTrigger24Ppq = false;
midiStartTrigger = false;
forceMaster = false;
lastInletTap = 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
bool reset = inlet_reset && !lastInletReset;
bool tap = inlet_tap && !lastInletTap;
bool samplesUpdateNeeded = inlet_samples != lastInletSamples;
inlet_factor != lastInletFactor;
forceMaster = inlet_master;
calculateCurrentAverage(samplesUpdateNeeded, inlet_samples, inlet_factor);
outlet_bpm = (int)(getBpm() + 0.5);
outlet_slave = isClockIncoming;
outlet_24ppq = get24ppq(reset);
outlet_tapping = tapTempo(tap, inlet_factor);
outlet_start = getStart(reset);
outlet_24ppqsmp = (int)currentAverage;
outlet_barsamples = barsamples;
lastInletReset = inlet_reset;
lastInletTap = inlet_tap;
lastInletSamples = inlet_samples;
lastInletFactor = inlet_factor;
midiStartTrigger = false;
if (isClockIncoming) {
timer++;
} else {
timer = 0;
}
if (timer >= 12000) {
isClockIncoming = false;
}
if (tapTimer < waitSamplesForTap) {
tapTimer++;
}
onMidi(status);