multi_wave_sync

LFO, midi/clock sync, selectable waveform
Author: Are Leistad
License: BSD
Github: drj/lfo/lfo_sync.axo

IO Variants: 2


Variant: 1

Inlets

frac32.bipolar Frequency modulation

frac32.bipolar Pulse width modulation

frac32.bipolar Phase modulation

bool32.rising External 24 PPQ clock

Outlets

bool32.pulse Clock output

bool32.pulse 24 PPQ clock output

frac32 Output

Parameters

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

Attributes

combo clocksource

combo device

Displays

bool32 clock

Declaration
//#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;
  }
}
Init
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;
Control Rate
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++;
Midi Handler
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

Inlets

bool32.rising External 24 PPQ clock

frac32.bipolar Frequency modulation

frac32.bipolar Pulse width modulation

frac32.bipolar Phase modulation

Outlets

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

Parameters

frac32.s.map.lfopitch Internal frequency

frac32.u.map.ratio Pulse width

int32 clockdiv

bool32.tgl Obey MIDI transport messages

Attributes

combo clocksource

combo device

Displays

bool32 clock

Declaration
#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;
  }
}
Init
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;
Control Rate
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++;
Midi Handler
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;
    }
  }
}

Privacy

© 2024 Zrna Research