//! Voltage-controlled oscillator (VCO). //! //! Waveforms: Sine, Saw, Square, Triangle, Pulse (variable width). //! Uses phase accumulation; bandlimited variants (BLEP/BLAMP) to follow. use crate::{AudioProcessor, CVProcessor, config::SampleRate}; use crate::descriptor::{ComponentDescriptor, Direction, JackDescriptor, ParamDescriptor, SignalKind}; #[derive(Clone, Copy, Debug, PartialEq)] pub enum Waveform { Sine, Saw, Square, Triangle, Pulse(f32), // pulse width 0.0–1.0 } pub struct Vco { pub waveform: Waveform, pub freq_hz: f32, phase: f32, sample_rate: SampleRate, } impl Vco { pub fn new(sample_rate: SampleRate, freq_hz: f32, waveform: Waveform) -> Self { Self { waveform, freq_hz, phase: 0.0, sample_rate } } pub const DESCRIPTOR: ComponentDescriptor = ComponentDescriptor { kind: "vco", label: "VCO", jacks: &[ JackDescriptor { id: "audio_out", label: "Out", direction: Direction::Output, signal: SignalKind::Audio }, ], params: &[ ParamDescriptor { id: "freq_hz", label: "Freq", min: 20.0, max: 20_000.0, default: 440.0, unit: "Hz", labels: &[] }, ParamDescriptor { id: "waveform", label: "Wave", min: 0.0, max: 4.0, default: 1.0, unit: "", labels: &["Sine", "Saw", "Sqr", "Tri", "Pls"] }, ], description: "\ ## VCO — Voltage-Controlled Oscillator Generates a periodic audio-rate waveform at the set frequency. **Waveforms:** Sine · Saw · Square · Triangle · Pulse **Freq** sets the base pitch. Connect an LFO or keyboard CV to `cv_freq_hz` for 1 V/oct pitch modulation (0 V = 440 Hz, +1 V = 880 Hz). **Typical chain:** VCO Out → Filter In → VCA In → Output In", }; #[inline] fn next_sample(&mut self) -> f32 { let p = self.phase; let sample = match self.waveform { Waveform::Sine => libm::sinf(p * core::f32::consts::TAU), Waveform::Saw => 2.0 * p - 1.0, Waveform::Square => if p < 0.5 { 1.0 } else { -1.0 }, Waveform::Triangle => 4.0 * (p - libm::floorf(p + 0.5)).abs() - 1.0, Waveform::Pulse(w) => if p < w { 1.0 } else { -1.0 }, }; let next = p + self.freq_hz * self.sample_rate.period(); self.phase = next - libm::floorf(next); sample } } impl AudioProcessor for Vco { fn process(&mut self, out: &mut [f32; B]) { for s in out.iter_mut() { *s = self.next_sample(); } } fn reset(&mut self) { self.phase = 0.0; } } impl CVProcessor for Vco { /// CV is 1 V/oct: 0 V = 440 Hz, +1 V = 880 Hz, −1 V = 220 Hz. fn set_cv(&mut self, cv: f32) { self.freq_hz = 440.0 * libm::powf(2.0, cv); } }