synth_utils/adsr.rs
1//! # Attack, Decay, Sustain, Release generator
2//!
3//! ## Acronyms used:
4//!
5//! - `ADSR`: Attack Decay Sustain Release generator
6//! - `LUT`: Look Up Table
7//! - `DDS`: Direct Digital Synthesis
8//!
9//! ADSRs are a standard component of most analog synthesizers, used to
10//! dynamically modulate various parameters of synthesizers, most commonly
11//! loudness, timbre, or pitch.
12//!
13//! This ADSR simulates the RC curves typically found in analog ADSRs, where
14//! the attack curve is a truncated up-going convex RC curve, and the decay and
15//! release curves are down-going concave RC curves.
16//!
17//! This ADSR has four variable input parameters:
18//!
19//! - Attack time
20//! - Decay time
21//! - Sustain level
22//! - Release time
23//!
24//! This ADSR responds to two types of time based events:
25//!
26//! - Gate On events initiate an attack phase
27//! - Gate Off events initiate a release phase
28//!
29//! This ADSR has a single output:
30//!
31//! - The current sample of the ADSR waveform in the range [0.0, 1.0].
32//!
33//! A Phase-Accumulator and Look-Up-Table (LUT) approach is used.
34//! This is known as "Direct Digital Synthesis", or DDS.
35//!
36//! LUTs are used to store the Attack and Decay curves for the ADSRs. These
37//! curves simulate the typical resistor/capacitor time constant curves used in
38//! analog ADSRs.
39
40use crate::{lookup_tables, phase_accumulator::PhaseAccumulator, utils::*};
41
42#[derive(Debug, Clone, Copy)]
43/// An ADSR envelope generator is represented here
44pub struct Adsr {
45 attack_time: TimePeriod,
46 decay_time: TimePeriod,
47 sustain_level: SustainLevel,
48 release_time: TimePeriod,
49
50 phase_accumulator: PhaseAccumulator<TOT_NUM_ACCUM_BITS, NUM_LUT_INDEX_BITS>,
51
52 state: State,
53
54 value_when_gate_on_received: f32,
55 value_when_gate_off_received: f32,
56 value: f32,
57}
58
59impl Adsr {
60 /// `Adrs::new(sr)` is a new ADSR with sample rate `sr`
61 pub fn new(sample_rate_hz: f32) -> Self {
62 Self {
63 // set defaults for very fast times and 100% on sustain
64 attack_time: MIN_TIME_PERIOD_SEC.into(),
65 decay_time: MIN_TIME_PERIOD_SEC.into(),
66 sustain_level: 1.0_f32.into(),
67 release_time: MIN_TIME_PERIOD_SEC.into(),
68
69 phase_accumulator: PhaseAccumulator::new(sample_rate_hz),
70 state: State::AtRest,
71 value_when_gate_on_received: 0.0_f32,
72 value_when_gate_off_received: 0.0_f32,
73 value: 0.0f32,
74 }
75 }
76
77 /// `adsr.tick()` advances the ADSR by 1 tick, must be called at the sample rate
78 pub fn tick(&mut self) {
79 // only calculate frequency and tick the accumulator for tick-able states
80 if self.state == State::Attack || self.state == State::Decay || self.state == State::Release
81 {
82 let period_of_this_phase = match self.state {
83 State::Attack => self.attack_time.0,
84 State::Decay => self.decay_time.0,
85 State::Release => self.release_time.0,
86 // SUSTAIN and AT-REST have no period, these can never happen here. But don't use wildcards, we want the
87 // compiler to complain if anyone adds more stages to make more complex envelopes (hold time, whatever)
88 State::Sustain => MIN_TIME_PERIOD_SEC,
89 State::AtRest => MIN_TIME_PERIOD_SEC,
90 };
91
92 self.phase_accumulator.set_period(period_of_this_phase);
93
94 self.phase_accumulator.tick();
95
96 if self.phase_accumulator.rolled_over() {
97 self.phase_accumulator.reset();
98
99 self.state = match self.state {
100 State::Attack => State::Decay,
101 State::Decay => State::Sustain,
102 State::Release => State::AtRest,
103 // SUSTAIN and AT-REST can't happen here, but explicitly match all arms
104 State::Sustain => State::Sustain,
105 State::AtRest => State::AtRest,
106 };
107 }
108 }
109
110 // calculate the output no matter which state
111 self.value = self.calc_value();
112 }
113
114 /// `adsr.gate_on()` sends a gate-on message to the ADSR, triggering an ATTACK phase if it's not already in ATTACK
115 ///
116 /// Attack phases may be re-triggered by sending a new gate-on message during any phase.
117 pub fn gate_on(&mut self) {
118 match self.state {
119 State::AtRest | State::Decay | State::Sustain | State::Release => {
120 self.value_when_gate_on_received = self.value;
121 self.phase_accumulator.reset();
122 self.state = State::Attack;
123 }
124 State::Attack => (), // ignore the message, we're already in an attack phase
125 }
126 }
127
128 /// `adsr.gate_off()` sends a gate-off message to the ADSR, triggering a RELEASE phase unless it's already RELEASED
129 pub fn gate_off(&mut self) {
130 match self.state {
131 State::Attack | State::Decay | State::Sustain => {
132 self.value_when_gate_off_received = self.value;
133 self.phase_accumulator.reset();
134 self.state = State::Release;
135 }
136 State::Release | State::AtRest => (), // ignore the message, we're already in a release or at-rest phase
137 }
138 }
139
140 /// `adsr.value()` is the current value of the ADSR in `[0.0, 1.0]`
141 pub fn value(&self) -> f32 {
142 self.value
143 }
144
145 /// `adsr.set_input(i)` sets the given ADSR input
146 ///
147 /// # Examples
148 ///
149 /// ```
150 /// # use synth_utils::adsr;
151 /// # let mut adsr = adsr::Adsr::new(1_000.0_f32);
152 ///
153 /// // set attack time to 30 milliseconds
154 /// adsr.set_input(adsr::Input::Attack(0.03_f32.into()));
155 ///
156 /// // set decay time to 100 milliseconds
157 /// adsr.set_input(adsr::Input::Decay(0.1_f32.into()));
158 ///
159 /// // set sustain level to 3/4 way up
160 /// adsr.set_input(adsr::Input::Sustain(0.75_f32.into()));
161 ///
162 /// // set release time to 150 milliseconds
163 /// adsr.set_input(adsr::Input::Release(0.15_f32.into()));
164 /// ```
165 pub fn set_input(&mut self, input: Input) {
166 match input {
167 Input::Attack(a) => self.attack_time = a,
168 Input::Decay(d) => self.decay_time = d,
169 Input::Sustain(s) => self.sustain_level = s,
170 Input::Release(r) => self.release_time = r,
171 }
172 }
173
174 /// `adsr.calc_value()` is a private helper function to calculate the current ADSR value
175 fn calc_value(&self) -> f32 {
176 // The coefficient for the sample is between 0 and 1.0. This is used to
177 // "squish" the attack, decay, and release curves as needed.
178
179 // Example: the decay curve starts at full scale, and ramps down to the sustain
180 // level. The range of the decay curve from top to bottom is full-scale at the
181 // top to sustain level at the bottom. The decay curve must be compressed to
182 // fit in this reduced range. The coefficient variable helps accomplish this.
183 let coefficient: f32;
184
185 // The value of the current sample. This will come from the attack LUT if the
186 // current state is attack, from the decay LUT if the current state is decay
187 // or release, and from the sustain level input if the current state is
188 // sustain. If the current state is at-rest, the value of the sample will be zero
189 let sample: f32;
190
191 // The offset for the current sample. This is only non-zero when an attack
192 // phase begins while the ADSR is not at rest, or a decay phase begins while
193 // the sustain level is non-zero. Basically this is how much to "push up" the
194 // ADSR curve, so that it fits between the starting value for the curve segment
195 // and the target value for the curve segment.
196 let offset: f32;
197
198 let lut_idx = self.phase_accumulator.index();
199 // next idx is for interpolation, clamp at the end to avoid bad behavior, we don't want to wrap around here
200 let next_lut_idx = (lut_idx + 1).min(lookup_tables::ADSR_CURVE_LUT_SIZE - 1);
201
202 match self.state {
203 State::Attack => {
204 let y0 = lookup_tables::ADSR_ATTACK_TABLE[lut_idx];
205 let y1 = lookup_tables::ADSR_ATTACK_TABLE[next_lut_idx];
206 coefficient = 1.0_f32 - self.value_when_gate_on_received;
207 sample = linear_interp(y0, y1, self.phase_accumulator.fraction());
208 offset = self.value_when_gate_on_received;
209 }
210 State::Decay => {
211 let y0 = lookup_tables::ADSR_DECAY_TABLE[lut_idx];
212 let y1 = lookup_tables::ADSR_DECAY_TABLE[next_lut_idx];
213 coefficient = 1.0_f32 - self.sustain_level.0;
214 sample = linear_interp(y0, y1, self.phase_accumulator.fraction());
215 offset = self.sustain_level.0;
216 }
217 State::Sustain => {
218 coefficient = 1.0_f32;
219 sample = self.sustain_level.0;
220 offset = 0.0;
221 }
222 State::Release => {
223 let y0 = lookup_tables::ADSR_DECAY_TABLE[lut_idx];
224 let y1 = lookup_tables::ADSR_DECAY_TABLE[next_lut_idx];
225 coefficient = self.value_when_gate_off_received;
226 sample = linear_interp(y0, y1, self.phase_accumulator.fraction());
227 offset = 0.0;
228 }
229 State::AtRest => {
230 coefficient = 0.0_f32;
231 sample = 0.0_f32;
232 offset = 0.0;
233 }
234 };
235
236 coefficient * sample + offset
237 }
238}
239
240/// ADSR input types are represented here
241///
242/// A, D, and S are represented as positive-only time periods, S is represented as a number in `[0.0, 1.0]`
243#[derive(Debug, Clone, Copy, PartialEq)]
244pub enum Input {
245 Attack(TimePeriod),
246 Decay(TimePeriod),
247 Sustain(SustainLevel),
248 Release(TimePeriod),
249}
250
251/// A time period in seconds is represented here
252///
253/// Time periods are positive only numbers with min and max values in a pleasing range for users of the ADSR
254#[derive(Debug, Clone, Copy, PartialEq)]
255pub struct TimePeriod(f32);
256
257impl From<f32> for TimePeriod {
258 fn from(p: f32) -> Self {
259 Self(p.max(MIN_TIME_PERIOD_SEC).min(MAX_TIME_PERIOD_SEC))
260 }
261}
262
263impl From<TimePeriod> for f32 {
264 fn from(val: TimePeriod) -> Self {
265 val.0
266 }
267}
268
269/// A sustain level in the range `[0.0, 1.0]` is represented here
270#[derive(Debug, Clone, Copy, PartialEq)]
271pub struct SustainLevel(f32);
272
273impl From<f32> for SustainLevel {
274 fn from(val: f32) -> Self {
275 Self(val.max(0.0_f32).min(1.0_f32))
276 }
277}
278
279impl From<SustainLevel> for f32 {
280 fn from(val: SustainLevel) -> Self {
281 val.0
282 }
283}
284
285/// ADSR states are represented here
286///
287/// An ADSR is in exactly one of these states at any given time
288#[derive(Clone, Copy, Eq, PartialEq, Debug)]
289pub enum State {
290 AtRest,
291 Attack,
292 Decay,
293 Sustain,
294 Release,
295}
296
297/// The minimum time period for an ADSR state period
298pub const MIN_TIME_PERIOD_SEC: f32 = 0.001_f32;
299
300/// The maximum time period for an ADSR state period
301pub const MAX_TIME_PERIOD_SEC: f32 = 20.0_f32;
302
303/// The total number of bits to use for the phase accumulator
304///
305/// Must be in `[1..32]`
306const TOT_NUM_ACCUM_BITS: u32 = 24;
307
308/// The number of index bits, depends on the lookup tables used
309///
310/// Note that the lookup table size MUST be a power of 2
311const NUM_LUT_INDEX_BITS: u32 = ilog_2(lookup_tables::ADSR_CURVE_LUT_SIZE);
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316
317 #[test]
318 fn gate_on_starts_attack_phase_from_at_rest() {
319 let mut adsr = Adsr::new(1_000.0_f32);
320
321 //. it starts out at-rest
322 assert_eq!(adsr.state, State::AtRest);
323
324 adsr.gate_on();
325 assert_eq!(adsr.state, State::Attack);
326 }
327
328 #[test]
329 fn attack_transitions_to_decay_after_ticks() {
330 let mut adsr = Adsr::new(1_000.0_f32);
331
332 // 100 millisecond stages at 1kHz sample rate should complete after 101 ticks
333 adsr.set_input(Input::Attack(0.1.into()));
334
335 adsr.gate_on();
336 assert_eq!(adsr.state, State::Attack);
337
338 // almost done with attack phase
339 for _ in 0..100 {
340 adsr.tick();
341 }
342 assert_eq!(adsr.state, State::Attack);
343
344 // one more tick puts us into a decay phase
345 adsr.tick();
346 assert_eq!(adsr.state, State::Decay);
347 }
348
349 #[test]
350 fn transition_through_phases() {
351 let mut adsr = Adsr::new(1_000.0_f32);
352
353 // 100 millisecond stages at 1kHz sample rate should complete after 101 ticks
354 adsr.set_input(Input::Attack(0.1.into()));
355 adsr.set_input(Input::Decay(0.1.into()));
356 adsr.set_input(Input::Sustain(0.5.into()));
357 adsr.set_input(Input::Release(0.1.into()));
358
359 adsr.gate_on();
360
361 for _ in 0..101 {
362 adsr.tick();
363 }
364 assert_eq!(adsr.state, State::Decay);
365
366 for _ in 0..101 {
367 adsr.tick();
368 }
369 assert_eq!(adsr.state, State::Sustain);
370
371 // only a gate-off message initiates a release phase
372 adsr.gate_off();
373 assert_eq!(adsr.state, State::Release);
374
375 for _ in 0..101 {
376 adsr.tick();
377 }
378 assert_eq!(adsr.state, State::AtRest);
379 }
380
381 #[test]
382 fn attack_can_retrigger_in_any_phase() {
383 let mut adsr = Adsr::new(1_000.0_f32);
384
385 // 100 millisecond stages at 1kHz sample rate should complete after 101 ticks
386 adsr.set_input(Input::Attack(0.1.into()));
387 adsr.set_input(Input::Decay(0.1.into()));
388 adsr.set_input(Input::Sustain(0.5.into()));
389 adsr.set_input(Input::Release(0.1.into()));
390
391 adsr.gate_on();
392
393 for _ in 0..101 {
394 adsr.tick();
395 }
396 assert_eq!(adsr.state, State::Decay);
397
398 adsr.gate_on();
399 assert_eq!(adsr.state, State::Attack);
400
401 for _ in 0..202 {
402 adsr.tick();
403 }
404 assert_eq!(adsr.state, State::Sustain);
405
406 adsr.gate_on();
407 assert_eq!(adsr.state, State::Attack);
408
409 for _ in 0..202 {
410 adsr.tick();
411 }
412 adsr.gate_off();
413 assert_eq!(adsr.state, State::Release);
414
415 adsr.gate_on();
416 assert_eq!(adsr.state, State::Attack);
417 }
418
419 #[test]
420 fn release_can_start_from_any_phase_but_at_rest() {
421 let mut adsr = Adsr::new(1_000.0_f32);
422
423 // 100 millisecond stages at 1kHz sample rate should complete after 101 ticks
424 adsr.set_input(Input::Attack(0.1.into()));
425 adsr.set_input(Input::Decay(0.1.into()));
426 adsr.set_input(Input::Sustain(0.5.into()));
427 adsr.set_input(Input::Release(0.1.into()));
428
429 adsr.gate_on();
430
431 for _ in 0..50 {
432 adsr.tick();
433 }
434 assert_eq!(adsr.state, State::Attack);
435
436 adsr.gate_off();
437 assert_eq!(adsr.state, State::Release);
438
439 adsr.gate_on();
440 for _ in 0..101 {
441 adsr.tick();
442 }
443 assert_eq!(adsr.state, State::Decay);
444
445 adsr.gate_off();
446 assert_eq!(adsr.state, State::Release);
447
448 adsr.gate_on();
449 for _ in 0..202 {
450 adsr.tick();
451 }
452 assert_eq!(adsr.state, State::Sustain);
453
454 adsr.gate_off();
455 assert_eq!(adsr.state, State::Release);
456
457 for _ in 0..101 {
458 adsr.tick();
459 }
460 assert_eq!(adsr.state, State::AtRest);
461
462 // turning the gate off when we're already at rest doesn't start a new release phase
463 adsr.gate_off();
464 assert_eq!(adsr.state, State::AtRest);
465 }
466}