Files
analogue_synth/crates/synth-core/src/envelope.rs
Matt Spencer c8ef3df460 Add audio nodes to the patch bay
Also make the patch bay responsive.
2026-03-26 08:47:47 +00:00

83 lines
2.9 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.
//! ADSR envelope generator.
use crate::{AudioProcessor, config::SampleRate};
use crate::descriptor::{ComponentDescriptor, Direction, JackDescriptor, ParamDescriptor, SignalKind};
#[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 const DESCRIPTOR: ComponentDescriptor = ComponentDescriptor {
kind: "adsr",
label: "ADSR",
jacks: &[
JackDescriptor { id: "gate_in", label: "Gate", direction: Direction::Input, signal: SignalKind::Gate },
JackDescriptor { id: "env_out", label: "Env", direction: Direction::Output, signal: SignalKind::Cv },
],
params: &[
ParamDescriptor { id: "attack_s", label: "Attack", min: 0.001, max: 4.0, default: 0.01, unit: "s" },
ParamDescriptor { id: "decay_s", label: "Decay", min: 0.001, max: 4.0, default: 0.1, unit: "s" },
ParamDescriptor { id: "sustain", label: "Sustain", min: 0.0, max: 1.0, default: 0.7, unit: "" },
ParamDescriptor { id: "release_s", label: "Release", min: 0.001, max: 8.0, default: 0.3, unit: "s" },
],
};
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;
}
}