use crate::engine::{Automaton, Range, Time};
use rill_core::traits::ParamValue;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum EnvelopeType {
ADSR,
AR,
ASR,
AHDSR,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum EnvelopeStage {
Attack,
Hold,
Decay,
Sustain,
Release,
Off,
}
impl EnvelopeStage {
pub fn name(&self) -> &'static str {
match self {
EnvelopeStage::Attack => "Attack",
EnvelopeStage::Hold => "Hold",
EnvelopeStage::Decay => "Decay",
EnvelopeStage::Sustain => "Sustain",
EnvelopeStage::Release => "Release",
EnvelopeStage::Off => "Off",
}
}
}
#[derive(Debug, Clone)]
pub struct EnvelopeAutomaton {
name: String,
env_type: EnvelopeType,
attack: f64,
hold: f64,
decay: f64,
sustain: f64,
release: f64,
range: Range,
curve: f64,
}
type EnvelopeInternal = (EnvelopeStage, f64, f64);
impl EnvelopeAutomaton {
pub fn adsr(name: &str, attack: f64, decay: f64, sustain: f64, release: f64) -> Self {
Self {
name: name.to_string(),
env_type: EnvelopeType::ADSR,
attack: attack.max(0.001),
hold: 0.0,
decay: decay.max(0.001),
sustain: sustain.clamp(0.0, 1.0),
release: release.max(0.001),
range: Range::unipolar(),
curve: 1.0,
}
}
pub fn ar(name: &str, attack: f64, release: f64) -> Self {
Self {
name: name.to_string(),
env_type: EnvelopeType::AR,
attack: attack.max(0.001),
hold: 0.0,
decay: 0.0,
sustain: 0.0,
release: release.max(0.001),
range: Range::unipolar(),
curve: 1.0,
}
}
pub fn asr(name: &str, attack: f64, sustain: f64, release: f64) -> Self {
Self {
name: name.to_string(),
env_type: EnvelopeType::ASR,
attack: attack.max(0.001),
hold: 0.0,
decay: 0.0,
sustain: sustain.clamp(0.0, 1.0),
release: release.max(0.001),
range: Range::unipolar(),
curve: 1.0,
}
}
pub fn ahdsr(
name: &str,
attack: f64,
hold: f64,
decay: f64,
sustain: f64,
release: f64,
) -> Self {
Self {
name: name.to_string(),
env_type: EnvelopeType::AHDSR,
attack: attack.max(0.001),
hold: hold.max(0.001),
decay: decay.max(0.001),
sustain: sustain.clamp(0.0, 1.0),
release: release.max(0.001),
range: Range::unipolar(),
curve: 1.0,
}
}
pub fn with_curve(mut self, curve: f64) -> Self {
self.curve = curve.max(0.1);
self
}
pub fn with_range(mut self, range: Range) -> Self {
self.range = range;
self
}
fn apply_curve(&self, t: f64) -> f64 {
if self.curve == 1.0 {
t
} else {
t.powf(self.curve)
}
}
fn stage_duration(&self, stage: EnvelopeStage) -> f64 {
match stage {
EnvelopeStage::Attack => self.attack,
EnvelopeStage::Hold => self.hold,
EnvelopeStage::Decay => self.decay,
EnvelopeStage::Release => self.release,
EnvelopeStage::Sustain | EnvelopeStage::Off => f64::INFINITY,
}
}
fn stage_target(&self, stage: EnvelopeStage) -> f64 {
match stage {
EnvelopeStage::Attack => 1.0,
EnvelopeStage::Hold => 1.0,
EnvelopeStage::Decay => self.sustain,
EnvelopeStage::Sustain => self.sustain,
EnvelopeStage::Release => 0.0,
EnvelopeStage::Off => 0.0,
}
}
fn next_stage(&self, current: EnvelopeStage) -> EnvelopeStage {
match (current, self.env_type) {
(EnvelopeStage::Attack, EnvelopeType::ADSR) => EnvelopeStage::Decay,
(EnvelopeStage::Attack, EnvelopeType::AR) => EnvelopeStage::Release,
(EnvelopeStage::Attack, EnvelopeType::ASR) => EnvelopeStage::Sustain,
(EnvelopeStage::Attack, EnvelopeType::AHDSR) => EnvelopeStage::Hold,
(EnvelopeStage::Hold, _) => EnvelopeStage::Decay,
(EnvelopeStage::Decay, _) => EnvelopeStage::Sustain,
(EnvelopeStage::Release, _) => EnvelopeStage::Off,
(EnvelopeStage::Sustain, _) => EnvelopeStage::Sustain,
(EnvelopeStage::Off, _) => EnvelopeStage::Off,
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Default)]
pub enum EnvelopeAction {
#[default]
None,
GateOn,
GateOff,
}
impl Automaton for EnvelopeAutomaton {
type Internal = EnvelopeInternal;
type Action = EnvelopeAction;
fn step(
&self,
internal: &mut Self::Internal,
current: &ParamValue,
time: Time,
action: &Self::Action,
) -> ParamValue {
let (stage, stage_start_time, stage_start_level) = *internal;
let current_level = current.as_f32().unwrap_or(0.0) as f64;
let (new_stage, new_start_time, new_start_level) = match action {
EnvelopeAction::GateOn => (EnvelopeStage::Attack, time, current_level),
EnvelopeAction::GateOff => (EnvelopeStage::Release, time, current_level),
EnvelopeAction::None => (stage, stage_start_time, stage_start_level),
};
let elapsed = time - new_start_time;
let duration = self.stage_duration(new_stage);
let target = self.stage_target(new_stage);
let (next_stage, next_start_time, next_start_level, level) = if elapsed >= duration {
let next = self.next_stage(new_stage);
let next_target = self.stage_target(next);
let next_dur = self.stage_duration(next);
if next_dur.is_infinite() {
(next, time, next_target, next_target)
} else {
(next, time, target, target)
}
} else {
let t = elapsed / duration;
let curved = self.apply_curve(t);
let lvl = new_start_level + (target - new_start_level) * curved;
(new_stage, new_start_time, new_start_level, lvl)
};
*internal = (next_stage, next_start_time, next_start_level);
let value = self.range.denormalize(level);
ParamValue::Float(value as f32)
}
fn initial_internal(&self) -> Self::Internal {
(EnvelopeStage::Off, 0.0, 0.0)
}
fn name(&self) -> &str {
&self.name
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_adsr_envelope() {
let env = EnvelopeAutomaton::adsr("ADSR", 0.1, 0.2, 0.7, 0.3);
let mut internal = env.initial_internal();
let current = ParamValue::Float(0.0);
assert_eq!(internal.0, EnvelopeStage::Off);
let value = env.step(&mut internal, ¤t, 0.0, &EnvelopeAction::GateOn);
assert_eq!(internal.0, EnvelopeStage::Attack);
let value = env.step(&mut internal, &value, 0.05, &EnvelopeAction::None);
let val = value.as_f32().unwrap();
assert!(val > 0.0);
assert!(val < 1.0);
let _value = env.step(&mut internal, &value, 0.5, &EnvelopeAction::GateOff);
assert_eq!(internal.0, EnvelopeStage::Release);
}
}