type Point = (Sample, Sample);
use crate::trig::is_trigger;
use crate::{self as knyst, Trig};
use crate::{
controller::KnystCommands,
gen::{GenState, StopAction},
Sample, SampleRate,
};
use knyst_macro::impl_gen;
pub struct Envelope {
pub start_value: Sample,
pub points: Vec<Point>,
pub curves: Vec<Curve>,
pub sustain: SustainMode,
pub stop_action: StopAction,
}
impl Envelope {
pub fn to_gen(&self) -> EnvelopeGen {
let mut e = EnvelopeGen::new(
self.start_value,
self.points.clone(),
self.sustain,
self.stop_action,
)
.curves(self.curves.clone());
e.start();
e
}
}
impl Default for Envelope {
fn default() -> Self {
Self {
start_value: 0.0,
points: vec![(1.0, 0.5), (0., 0.5)],
curves: vec![Curve::Linear],
sustain: SustainMode::NoSustain,
stop_action: StopAction::Continue,
}
}
}
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy)]
pub enum SustainMode {
NoSustain,
SustainAtPoint(usize),
Loop { start: usize, end: usize },
}
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy)]
pub enum Curve {
Linear,
Exponential(Sample),
}
impl Curve {
#[inline]
pub fn transform(&self, a: Sample) -> Sample {
match self {
Curve::Linear => a,
Curve::Exponential(exponent) => {
fastapprox::fast::pow(a as f32, *exponent as f32) as Sample
}
}
}
}
#[derive(Debug, Clone)]
pub struct EnvelopeGen {
#[allow(missing_docs)]
pub start_value: Sample,
points_secs: Vec<Point>,
points: Vec<Point>,
curves: Vec<Curve>,
source_value: Sample,
target_value: Sample,
source_target_diff: Sample,
fade_out_duration: Sample,
segment_duration: Sample,
current_curve: Curve,
current_timestep: f64,
duration_passed: f64,
next_index: usize,
#[allow(missing_docs)]
pub playing: bool,
sample_rate: Sample,
sustain: SustainMode,
stop_action: StopAction,
waiting_for_release: bool,
}
impl EnvelopeGen {
pub fn adsr(
attack_time: Sample,
decay_time: Sample,
sustain_level: Sample,
release_time: Sample,
) -> Self {
let points = vec![
(1.0, attack_time),
(sustain_level, decay_time),
(0.0, release_time),
];
Self::new(
0.0,
points,
SustainMode::SustainAtPoint(1),
StopAction::Continue,
)
}
pub fn sustain(mut self, sustain: SustainMode) -> Self {
self.sustain = sustain;
self
}
pub fn stop_action(mut self, stop_action: StopAction) -> Self {
self.stop_action = stop_action;
self
}
pub fn set_points(&mut self, points: Vec<Point>) {
self.points_secs = points.clone();
let points = points
.into_iter()
.map(|mut p| {
p.1 *= self.sample_rate;
p
})
.collect();
self.points = points;
if self.curves.len() != self.points.len() {
self.curves.resize(self.points.len(), Curve::Linear);
}
}
pub fn curves(mut self, curves: Vec<Curve>) -> Self {
if curves.len() == self.points.len() {
self.curves = curves;
} else {
let default_curve = Curve::Linear;
self.curves = curves
.into_iter()
.chain(std::iter::repeat(default_curve))
.take(self.points.len())
.collect();
}
self
}
pub fn set_curve(&mut self, curve: Curve, index: usize) {
self.curves[index] = curve;
}
#[allow(missing_docs)]
pub fn get_point(&mut self, index: usize) -> Point {
self.points[index]
}
pub fn playing(&self) -> bool {
self.playing
}
pub fn update_from_envelope(&mut self, other: &EnvelopeGen) {
self.start_value = other.start_value;
for (i, p) in other.points.iter().enumerate() {
self.points[i] = *p;
if i == self.next_index - 1 {
if self.playing {
self.target_value = self.points[i].0;
self.source_target_diff = self.target_value - self.source_value;
self.segment_duration = self.points[i].1;
} else {
self.source_value = self.points[i].0;
}
}
}
}
pub fn start(&mut self) {
self.playing = true;
self.waiting_for_release = false;
self.source_value = self.start_value;
self.target_value = self.points[0].0;
self.source_target_diff = self.target_value - self.source_value;
self.segment_duration = self.points[0].1;
self.current_curve = self.curves[0];
self.current_timestep = (self.segment_duration as f64).recip();
self.duration_passed = 0.;
self.next_index = 1;
}
pub fn restart_from_current(&mut self) {
if self.playing {
self.source_value = self.current_value();
}
self.playing = true;
self.waiting_for_release = false;
self.target_value = self.points[0].0;
self.source_target_diff = self.target_value - self.source_value;
self.segment_duration = self.points[0].1;
self.current_curve = self.curves[0];
self.current_timestep = (self.segment_duration as f64).recip();
self.duration_passed = 0.;
self.next_index = 1;
}
pub fn release(&mut self) {
match self.sustain {
SustainMode::NoSustain => self.fade_out(),
SustainMode::SustainAtPoint(sustain_point) => {
if self.waiting_for_release {
self.next_segment();
self.waiting_for_release = false;
}
self.jump_to_segment(sustain_point + 1);
}
SustainMode::Loop { start: _, end } => {
self.jump_to_segment(end + 1);
}
}
}
pub fn fade_out(&mut self) {
self.source_value = self.current_value();
self.next_index = self.points.len();
self.target_value = 0.0;
self.source_target_diff = self.target_value - self.source_value;
self.duration_passed = 0.0;
self.segment_duration = self.fade_out_duration * self.sample_rate;
self.current_timestep = (self.segment_duration as f64).recip();
self.current_curve = Curve::Linear;
}
pub fn set_value(&mut self, value: Sample, index: usize) {
self.points[index].0 = value;
if index == self.next_index - 1 {
if self.playing && !self.waiting_for_release {
self.target_value = value;
self.source_target_diff = self.target_value - self.source_value;
} else {
self.source_value = value;
self.target_value = value;
self.source_target_diff = self.target_value - self.source_value;
}
}
}
pub fn set_duration(&mut self, duration: Sample, index: usize) {
self.points[index].1 = duration * self.sample_rate;
}
fn next_segment(&mut self) {
loop {
self.source_value = self.target_value;
if !match self.sustain {
SustainMode::NoSustain => false,
SustainMode::SustainAtPoint(sustain_index) => {
if self.next_index == sustain_index + 1 {
self.waiting_for_release = true;
self.source_target_diff = self.target_value - self.source_value;
true
} else {
false
}
}
SustainMode::Loop { start, end } => {
if self.next_index == end + 1 {
self.jump_to_segment(start);
true
} else {
false
}
}
} {
if self.next_index < self.points.len() {
self.target_value = self.points[self.next_index].0;
self.source_target_diff = self.target_value - self.source_value;
self.segment_duration = self.points[self.next_index].1;
self.current_timestep = (self.segment_duration as f64).recip();
self.current_curve = self.curves[self.next_index];
self.duration_passed = 0.0;
self.next_index += 1;
} else {
self.playing = false;
break;
}
}
if self.segment_duration > 0.0 || !self.playing {
break;
}
}
}
fn jump_to_segment(&mut self, destination_index: usize) {
self.source_value = self.current_value();
if destination_index < self.points.len() {
self.target_value = self.points[destination_index].0;
self.segment_duration = self.points[destination_index].1;
self.current_timestep = (self.segment_duration as f64).recip();
self.current_curve = self.curves[destination_index];
self.duration_passed = 0.;
self.next_index = destination_index + 1;
} else {
self.playing = false;
}
self.source_target_diff = self.target_value - self.source_value;
}
#[inline(always)]
fn current_value(&mut self) -> Sample {
let t = self.current_curve.transform(self.duration_passed as Sample);
self.source_value + (t * self.source_target_diff)
}
#[inline(always)]
pub fn next_sample(&mut self) -> Sample {
if self.playing && !self.waiting_for_release {
let value = self.current_value();
self.duration_passed += self.current_timestep;
if self.duration_passed >= 1.0 {
self.next_segment();
}
value
} else {
self.source_value }
}
pub fn iter_mut(&mut self) -> EnvelopeIterator {
EnvelopeIterator { envelope: self }
}
}
#[impl_gen]
impl EnvelopeGen {
pub fn new(
start_value: Sample,
points: Vec<Point>,
sustain: SustainMode,
stop_action: StopAction,
) -> Self {
let mut points = points;
let sample_rate = 41000.;
let points_secs = points.clone();
for point in &mut points {
point.1 *= sample_rate;
}
let target_value = points[0].0;
let segment_duration = points[0].1;
let curves = vec![Curve::Linear; points.len()];
let mut s = Self {
points_secs,
points,
curves,
start_value,
source_value: start_value,
target_value,
source_target_diff: target_value - start_value,
current_curve: Curve::Linear,
current_timestep: 0.0,
fade_out_duration: 0.5,
segment_duration,
duration_passed: 0.,
stop_action,
next_index: 1,
playing: true,
sample_rate,
sustain,
waiting_for_release: false,
};
s.start();
s
}
pub fn process(
&mut self,
release: &[Trig],
restart: &[Trig],
amplitude: &mut [Sample],
) -> GenState {
let release_trigger_in = release;
let restart_trigger_in = restart;
let mut stop_sample = None;
for (((i, out), &release_trig), &restart_trig) in amplitude
.iter_mut()
.enumerate()
.zip(release_trigger_in.iter())
.zip(restart_trigger_in.iter())
{
if is_trigger(release_trig) {
self.release();
}
if is_trigger(restart_trig) {
self.restart_from_current();
}
*out = self.next_sample();
if !self.playing() && stop_sample.is_none() {
stop_sample = Some(i)
}
}
if self.playing {
GenState::Continue
} else {
self.stop_action.to_gen_state(stop_sample.unwrap())
}
}
fn init(&mut self, sample_rate: SampleRate) {
if self.sample_rate != *sample_rate {
self.points = self
.points_secs
.iter()
.copied()
.map(|(x, y)| (x, y * *sample_rate))
.collect();
}
self.sample_rate = *sample_rate;
}
}
pub struct EnvelopeIterator<'a> {
envelope: &'a mut EnvelopeGen,
}
impl<'a> Iterator for EnvelopeIterator<'a> {
type Item = Sample;
fn next(&mut self) -> Option<Self::Item> {
if self.envelope.playing() {
Some(self.envelope.next_sample())
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_envelope() {
let sample_rate = 44100.;
let mut env = EnvelopeGen::new(
0.0,
vec![(1.0, 1.0), (0.75, 0.5), (0.1, 3.0)],
SustainMode::NoSustain,
StopAction::Continue,
);
env.init(SampleRate(sample_rate));
env.start();
assert_eq!(env.next_sample(), 0.);
assert!(env.next_sample() > 0.);
for _i in 0..(sample_rate * 0.5 - 2.0) as i32 {
env.next_sample();
}
assert_eq!(
env.next_sample(),
0.5,
"Envelope value was expected to be 0.5 halfway between 0.0 and 1.0. {:?}",
env
);
for _i in 0..(sample_rate * 0.5 - 1.0) as i32 {
env.next_sample();
}
assert_eq!(env.next_sample(), 1.0);
for _i in 0..(sample_rate * 0.5 - 1.0) as i32 {
env.next_sample();
}
assert_eq!(
env.next_sample(),
0.75,
"Envelope value was expected to be 0.75 right at the second point. {:?}",
env
);
for _i in 0..(sample_rate * 3.01) as i32 {
env.next_sample();
}
assert_eq!(
env.next_sample(),
0.1,
"Envelope did not keep its last value after finishing the envelope {:?}",
env
);
assert!(
!env.playing,
"Envelope is not supposed to be playing after it is done {:?}",
env
);
}
}