Files
analogue_synth/crates/synth-core/src/oscillator.rs
Matt Spencer 496b6bdc71 Initial commit of a toy analogue audio generator
Key components:
- no_std core so it can be used bare metal on embedded systems
- default implementation for RPi 2350, with midi input and I2s output using PIO
- Visualiser for the web to play with on a dev system
2026-03-23 15:06:31 +00:00

63 lines
1.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 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};
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Waveform {
Sine,
Saw,
Square,
Triangle,
Pulse(f32), // pulse width 0.01.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 }
}
#[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<const B: usize> AudioProcessor<B> 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<const B: usize> CVProcessor<B> 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);
}
}