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
This commit is contained in:
2026-03-23 15:06:31 +00:00
commit 496b6bdc71
34 changed files with 3662 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
//! ADSR envelope generator.
use crate::{AudioProcessor, config::SampleRate};
#[derive(Clone, Copy, Debug, PartialEq)]
enum Stage { Idle, Attack, Decay, Sustain, Release }
pub struct Adsr {
pub attack_s: f32,
pub decay_s: f32,
pub sustain: f32, // 0.01.0
pub release_s: f32,
sample_rate: SampleRate,
stage: Stage,
level: f32,
}
impl Adsr {
pub fn new(sample_rate: SampleRate) -> Self {
Self {
attack_s: 0.01, decay_s: 0.1, sustain: 0.7, release_s: 0.3,
sample_rate,
stage: Stage::Idle,
level: 0.0,
}
}
pub fn gate_on(&mut self) { self.stage = Stage::Attack; }
pub fn gate_off(&mut self) { self.stage = Stage::Release; }
pub fn is_idle(&self) -> bool { self.stage == Stage::Idle }
#[inline]
fn next_sample(&mut self) -> f32 {
let dt = self.sample_rate.period();
match self.stage {
Stage::Idle => {},
Stage::Attack => {
self.level += dt / self.attack_s.max(dt);
if self.level >= 1.0 { self.level = 1.0; self.stage = Stage::Decay; }
}
Stage::Decay => {
self.level -= dt / self.decay_s.max(dt) * (1.0 - self.sustain);
if self.level <= self.sustain { self.level = self.sustain; self.stage = Stage::Sustain; }
}
Stage::Sustain => { self.level = self.sustain; }
Stage::Release => {
self.level -= dt / self.release_s.max(dt) * self.level;
if self.level <= 0.0001 { self.level = 0.0; self.stage = Stage::Idle; }
}
}
self.level
}
}
impl<const B: usize> AudioProcessor<B> for Adsr {
fn process(&mut self, out: &mut [f32; B]) {
for s in out.iter_mut() {
*s = self.next_sample();
}
}
fn reset(&mut self) {
self.stage = Stage::Idle;
self.level = 0.0;
}
}