///|
/// Supported oscillator waveforms for the Phase 1 source primitive.
pub(all) enum Waveform {
Sine
Saw
Square
Triangle
} derive(Eq)
///|
/// Stateful oscillator for the first reusable Phase 1 DSP primitive.
pub struct Oscillator {
priv mut phase : Double
}
///|
/// Create a new oscillator at phase zero.
#alias(new)
pub fn Oscillator::Oscillator() -> Oscillator {
{ phase: 0.0 }
}
///|
/// Reset the oscillator to the start of the waveform.
pub fn Oscillator::reset(self : Oscillator) -> Unit {
self.phase = 0.0
}
///|
/// Expose the current phase for testing and diagnostics.
pub fn Oscillator::phase(self : Oscillator) -> Double {
self.phase
}
///|
/// Generate one sample for the selected waveform and advance the oscillator
/// state.
pub fn Oscillator::tick_waveform(
self : Oscillator,
waveform~ : Waveform,
freq_hz~ : Double,
sample_rate~ : Double,
) -> Double {
if !is_finite(freq_hz) || !is_finite(sample_rate) || sample_rate <= 0.0 {
return 0.0
}
let out = sample_for_phase(waveform, self.phase)
self.phase = wrap_phase(self.phase + freq_hz / sample_rate)
out
}
///|
/// Generate one sine sample and advance the oscillator state.
pub fn Oscillator::tick(
self : Oscillator,
freq_hz~ : Double,
sample_rate~ : Double,
) -> Double {
self.tick_waveform(waveform=Waveform::Sine, freq_hz~, sample_rate~)
}
///|
/// Fill an output buffer with samples for the selected waveform.
pub fn Oscillator::process_waveform(
self : Oscillator,
context~ : DspContext,
output~ : AudioBuffer,
waveform~ : Waveform,
freq_hz~ : Double,
) -> Unit {
let sample_rate = context.sample_rate()
let sample_count = effective_sample_count(context, output)
if !is_finite(freq_hz) ||
!is_finite(sample_rate) ||
sample_rate <= 0.0 ||
sample_count <= 0 {
output.fill(0.0)
return
}
let phase_increment = freq_hz / sample_rate
if !is_finite(phase_increment) {
output.fill(0.0)
return
}
for index = 0; index < sample_count; index = index + 1 {
output.set(index, self.tick_step(waveform, phase_increment))
}
for index = sample_count; index < output.length(); index = index + 1 {
output.set(index, 0.0)
}
}
///|
/// Step the oscillator by `phase_increment` and return the current sample.
/// Callers must ensure `phase_increment` is finite.
fn Oscillator::tick_step(
self : Oscillator,
waveform : Waveform,
phase_increment : Double,
) -> Double {
let out = sample_for_phase(waveform, self.phase)
self.phase = wrap_phase(self.phase + phase_increment)
out
}
///|
/// Fill an output buffer with oscillator samples.
pub fn Oscillator::process(
self : Oscillator,
context~ : DspContext,
output~ : AudioBuffer,
freq_hz~ : Double,
) -> Unit {
self.process_waveform(context~, output~, waveform=Waveform::Sine, freq_hz~)
}
///|
fn sample_for_phase(waveform : Waveform, phase : Double) -> Double {
match waveform {
Sine => @math.sin(phase * 2.0 * @math.PI)
Saw => 2.0 * phase - 1.0
Square => if phase < 0.5 { 1.0 } else { -1.0 }
Triangle => if phase < 0.5 { 4.0 * phase - 1.0 } else { 3.0 - 4.0 * phase }
}
}
///|
fn wrap_phase(phase : Double) -> Double {
if phase >= 1.0 {
phase - @math.floor(phase)
} else if phase < 0.0 {
phase - @math.floor(phase)
} else {
phase
}
}