IO Variants: 2
Variant: 1
frac32.bipolar Frequency modulation
frac32.bipolar Pulse width modulation
frac32.bipolar Phase modulation
bool32.rising External 24 PPQ clock
bool32.pulse Clock output
bool32.pulse 24 PPQ clock output
frac32 Output
frac32.s.map.lfopitch Internal frequency
frac32.u.map.ratio Pulse width
frac32.s.map Amplitude
int32 clockdiv
int32 1-5 = tri, sine, saw+, saw-, square
bool32.tgl Obey MIDI transport messages
bool32.tgl Unipolar output
combo clocksource
combo device
bool32 clock
//#define USE_CLOCK_SMOOTHING
enum {
CLOCK_SOURCE_internal = 0,
CLOCK_SOURCE_external = 1,
CLOCK_SOURCE_midi = 2
} CLOCK_SOURCE;
uint32_t start_sequence;
uint32_t sync_count;
uint32_t ktimer; // 3kHz timer
uint32_t phase;
uint32_t old_phase;
uint32_t old_24ppq;
uint32_t midi_clock_24;
uint32_t midi_last_ktime; // MIDI/24PPQ clock capture
uint32_t clock_disp_timer;
#ifdef USE_CLOCK_SMOOTHING
float midi_last_period;
#endif
int32_t midi_clock_freq;
int32_t midi_clock_div;
int32_t midi_clock_run;
int32_t midi_clock_count;
int32_t midi_transport;
__attribute__((always_inline)) __STATIC_INLINE int32_t ___ABS(int32_t op1) {
int32_t result;
__ASM volatile("movs %0, %1\n"
"it mi\n"
"rsbmi %0, %0, #0"
: "=r"(result)
: "r"(op1));
return (result);
};
__attribute__((always_inline)) __STATIC_INLINE int
midi_device_test(midi_device_t dev, uint8_t port, int selected_dev,
uint8_t selected_port) {
if ((selected_dev == MIDI_DEVICE_OMNI) ||
(selected_dev == dev && selected_port == port))
return 1;
return 0;
}
void on_sync_clock(void) {
if (midi_clock_run == 1) {
midi_clock_count++;
// Find noof sample periods since last time;
float period = (ktimer - midi_last_ktime) * 16 * midi_clock_div;
#ifdef USE_CLOCK_SMOOTHING
// Leaky integrator to avoid spiky clock on tempo changes
period = period + 0.5f * (midi_last_period - period);
midi_last_period = period;
#endif
if (period < 1.0f) {
period = 1.0f;
}
// Using slightly lower frequency (63.99 vs 64.0) so we avoid leading edge
// spikes from late phase resets.
float f_clock = SAMPLERATE / period;
double freq = 63.99 * (double)(1 << 30) * f_clock / (SAMPLERATE * 1.0);
midi_clock_freq = (uint32_t)freq;
if (sync_count < 2) {
sync_count++;
}
if (midi_clock_count >= midi_clock_div) {
phase = 0;
midi_clock_count = 0;
}
midi_clock_24 = 1;
midi_last_ktime = ktimer;
}
}
start_sequence = 0;
sync_count = 0;
ktimer = 0;
phase = 0;
old_phase = 0;
old_24ppq = 0;
midi_last_ktime = 0;
#ifdef USE_CLOCK_SMOOTHING
midi_last_period = 0.0f;
#endif
midi_clock_24 = 0;
midi_clock_freq = 0;
midi_clock_div = 12;
midi_clock_run = 1;
midi_clock_count = 0;
midi_transport = 0;
int32_t freq;
int32_t pulsewidth;
uint32_t phase2;
// Reset output pulses
disp_clock = 0;
outlet_clock = 0;
outlet_24ppq = 0;
// Need some variable copies to resolve scope...
midi_clock_div = param_clockdiv;
midi_transport = param_miditransport;
// Prevent MIDI sync from starting without a MIDI start message
if (start_sequence == 0) {
if (param_miditransport == 1) {
midi_clock_run = 0;
}
start_sequence++;
}
// 24 PPQ sync input
if (attr_clocksource == CLOCK_SOURCE_external) {
// Must keep the clock sync process running for external clock
// unless we obey the MIDI transport messages.
if (param_miditransport == 0) {
midi_clock_run = 1;
}
if (inlet_24ppq && !old_24ppq) {
on_sync_clock();
}
}
old_24ppq = inlet_24ppq;
if (attr_clocksource == CLOCK_SOURCE_internal ||
midi_clock_run == 1 && sync_count >= 2) {
if (attr_clocksource == CLOCK_SOURCE_internal) {
MTOFEXTENDED(param_freq + inlet_freq, freq);
phase += freq >> 2;
if (phase < 0x80000000 || freq > 0x04000000) {
disp_clock = 1;
}
} else {
// Reclocking the internal LFO
phase += midi_clock_freq;
// Maintain 24 PPQ clock pulse
if (midi_clock_24) {
outlet_24ppq = 1;
midi_clock_24 = 0;
}
// Maintain clock pulse display
if (phase < 0x80000000 || midi_clock_freq > 0x00C00000) {
disp_clock = 1;
}
}
if (phase < old_phase) {
outlet_clock = 1;
}
old_phase = phase;
phase2 = phase + (inlet_phase << 4);
switch (param_wave) {
default:
case 1:
// Triangle
outlet_out = (phase2 >> 4) - (1 << 27);
outlet_out = (1 << 27) - ___ABS(outlet_out);
break;
case 2:
// Sine
SINE2TINTERP(phase2, outlet_out)
outlet_out = (outlet_out >> 5) + (1 << 26);
break;
case 3:
// Saw rising
outlet_out = (phase2 >> 5);
break;
case 4:
// Saw falling
outlet_out = (1 << 27) - (phase2 >> 5);
break;
case 5:
// Square
pulsewidth = param_pw + inlet_pwm;
if (pulsewidth > (1 << 27)) {
pulsewidth = (1 << 27);
} else if (pulsewidth < 0) {
pulsewidth = 0;
}
if ((phase2 >> 5) > pulsewidth) {
outlet_out = 0;
} else {
outlet_out = (1 << 27) - 1;
}
break;
}
if (!param_unipolar) {
outlet_out = (outlet_out << 1) - (1 << 27);
}
outlet_out = __SSAT(___SMMUL(param_amp << 3, outlet_out << 2), 28);
} else {
outlet_out = 0;
}
ktimer++;
if (attr_clocksource == CLOCK_SOURCE_midi &&
midi_device_test(dev, port, attr_device) == 1) {
if (status == MIDI_TIMING_CLOCK) {
on_sync_clock();
} else if (midi_transport) {
if (status == MIDI_START) {
midi_clock_run = 1;
midi_clock_count = midi_clock_div; // Start immediately
// midi_clock_count = midi_clock_div-1; // Start on next clock
} else if (status == MIDI_STOP) {
midi_clock_run = 0;
} else if (status == MIDI_CONTINUE) {
midi_clock_run = 1;
}
}
}
Variant: 2
bool32.rising External 24 PPQ clock
frac32.bipolar Frequency modulation
frac32.bipolar Pulse width modulation
frac32.bipolar Phase modulation
frac32.bipolar PWM output
frac32.bipolar Saw output
frac32.bipolar Triangle output
frac32.bipolar Sine output
bool32.pulse Clock output
bool32.pulse 24 PPQ clock output
frac32.s.map.lfopitch Internal frequency
frac32.u.map.ratio Pulse width
int32 clockdiv
bool32.tgl Obey MIDI transport messages
combo clocksource
combo device
bool32 clock
#define USE_CLOCK_SMOOTHING
enum {
CLOCK_SOURCE_internal = 0,
CLOCK_SOURCE_external = 1,
CLOCK_SOURCE_midi = 2
} CLOCK_SOURCE;
uint32_t start_sequence;
uint32_t sync_count;
uint32_t ktimer; // 3kHz timer
uint32_t phase;
uint32_t old_phase;
uint32_t old_24ppq;
uint32_t midi_clock_24;
uint32_t midi_last_ktime; // MIDI/24PPQ clock capture
#ifdef USE_CLOCK_SMOOTHING
float midi_last_period;
#endif
int32_t midi_clock_freq;
int32_t midi_clock_div;
int32_t midi_clock_run;
int32_t midi_clock_count;
int32_t midi_transport;
__attribute__((always_inline)) __STATIC_INLINE int32_t ___ABS(int32_t op1) {
int32_t result;
__ASM volatile("movs %0, %1\n"
"it mi\n"
"rsbmi %0, %0, #0"
: "=r"(result)
: "r"(op1));
return (result);
};
__attribute__((always_inline)) __STATIC_INLINE int
midi_device_test(midi_device_t dev, uint8_t port, int selected_dev,
uint8_t selected_port) {
if ((selected_dev == MIDI_DEVICE_OMNI) ||
(selected_dev == dev && selected_port == port))
return 1;
return 0;
}
void on_sync_clock(void) {
if (midi_clock_run == 1) {
midi_clock_count++;
// Find noof sample periods since last time;
float period = (ktimer - midi_last_ktime) * 16 * midi_clock_div;
#ifdef USE_CLOCK_SMOOTHING
// Leaky integrator to avoid spiky clock on tempo changes
period = period + 0.5f * (midi_last_period - period);
midi_last_period = period;
#endif
if (period < 1.0f) {
period = 1.0f;
}
// Using slightly lower frequency (63.99 vs 64.0) so we avoid leading edge
// spikes from late phase resets. 26.11.2016 18:01 - may get slight glitch
// on very high frequencies - can use f_clock to scale freq
float f_clock = SAMPLERATE / period;
double freq = 63.99f * (double)(1 << 30) * f_clock / (SAMPLERATE * 1.0f);
midi_clock_freq = (uint32_t)freq;
if (sync_count < 2) {
sync_count++;
}
if (midi_clock_count >= midi_clock_div) {
phase = 0;
midi_clock_count = 0;
}
midi_clock_24 = 1;
midi_last_ktime = ktimer;
}
}
start_sequence = 0;
sync_count = 0;
ktimer = 0;
phase = 0;
old_phase = 0;
old_24ppq = 0;
midi_last_ktime = 0;
#ifdef USE_CLOCK_SMOOTHING
midi_last_period = 0.0f;
#endif
midi_clock_24 = 0;
midi_clock_freq = 0;
midi_clock_div = 12;
midi_clock_run = 1;
midi_clock_count = 0;
midi_transport = 0;
int32_t freq;
int32_t pulsewidth;
uint32_t phase2;
// Reset output pulses
disp_clock = 0;
outlet_clock = 0;
outlet_24ppq = 0;
// Need some variable copies to resolve scope...
midi_clock_div = param_clockdiv;
midi_transport = param_miditransport;
// Prevent MIDI sync from starting without a MIDI start message
if (start_sequence == 0) {
if (param_miditransport == 1) {
midi_clock_run = 0;
}
start_sequence++;
}
// 24 PPQ sync input
if (attr_clocksource == CLOCK_SOURCE_external) {
// Must keep the clock sync process running for external clock
// unless we obey the MIDI transport messages.
if (param_miditransport == 0) {
midi_clock_run = 1;
}
if (inlet_24ppq && !old_24ppq) {
on_sync_clock();
}
}
old_24ppq = inlet_24ppq;
if (attr_clocksource == CLOCK_SOURCE_internal ||
midi_clock_run == 1 && sync_count >= 2) {
if (attr_clocksource == CLOCK_SOURCE_internal) {
MTOFEXTENDED(param_freq + inlet_freq, freq);
phase += freq >> 2;
// Maintain clock pulse display
if (phase < 0x80000000 || freq > 0x04000000) {
disp_clock = 1;
}
} else {
// Reclocking the internal LFO
phase += midi_clock_freq;
// Maintain 24 PPQ clock pulse
if (midi_clock_24) {
outlet_24ppq = 1;
midi_clock_24 = 0;
}
// Maintain clock pulse display
if (phase < 0x80000000 || midi_clock_freq > 0x00C00000) {
disp_clock = 1;
}
}
if (phase < old_phase) {
outlet_clock = 1;
}
old_phase = phase;
phase2 = phase + (inlet_phase << 4);
pulsewidth = param_pw + inlet_pwm;
if (pulsewidth > (1 << 27)) {
pulsewidth = (1 << 27);
} else if (pulsewidth < 0) {
pulsewidth = 0;
}
if ((phase2 >> 5) > pulsewidth) {
outlet_pwm = -(1 << 27);
} else {
outlet_pwm = (1 << 27) - 1;
}
outlet_saw = (phase2 >> 4) - (1 << 27);
outlet_tri = (1 << 27) - ___ABS(outlet_saw << 1);
int32_t sine;
SINE2TINTERP(phase2, sine)
outlet_sine = (sine >> 4);
} else {
outlet_pwm = 0;
outlet_saw = 0;
outlet_tri = 0;
outlet_sine = 0;
}
ktimer++;
if (attr_clocksource == CLOCK_SOURCE_midi &&
midi_device_test(dev, port, attr_device) == 1) {
if (status == MIDI_TIMING_CLOCK) {
on_sync_clock();
} else if (midi_transport) {
if (status == MIDI_START) {
midi_clock_run = 1;
midi_clock_count = midi_clock_div; // Start immediately
} else if (status == MIDI_STOP) {
midi_clock_run = 0;
} else if (status == MIDI_CONTINUE) {
midi_clock_run = 1;
}
}
}