1use crate::{
4 block::{Block, DEFAULT_MODULATOR_INPUT_COUNT, DEFAULT_MODULATOR_OUTPUT_COUNT},
5 context::DspContext,
6 parameter::{ModulationOutput, Parameter},
7 sample::Sample,
8};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum EnvelopeStage {
15 Idle,
16 Attack,
17 Decay,
18 Sustain,
19 Release,
20}
21
22pub struct EnvelopeBlock<S: Sample> {
24 pub attack: Parameter<S>,
26 pub decay: Parameter<S>,
28 pub sustain: Parameter<S>,
30 pub release: Parameter<S>,
32
33 stage: EnvelopeStage,
34 level: f64,
35 stage_time: f64,
36 release_level: f64,
37}
38
39impl<S: Sample> EnvelopeBlock<S> {
40 const MODULATION_OUTPUTS: &'static [ModulationOutput] = &[ModulationOutput {
41 name: "Envelope",
42 min_value: 0.0,
43 max_value: 1.0,
44 }];
45
46 const MIN_TIME: f64 = 0.001;
48 const MAX_TIME: f64 = 10.0;
50 const ENVELOPE_FLOOR: f64 = 1e-6;
52
53 pub fn new(attack: f64, decay: f64, sustain: f64, release: f64) -> Self {
56 Self {
57 attack: Parameter::Constant(S::from_f64(attack)),
58 decay: Parameter::Constant(S::from_f64(decay)),
59 sustain: Parameter::Constant(S::from_f64(sustain)),
60 release: Parameter::Constant(S::from_f64(release)),
61 stage: EnvelopeStage::Idle,
62 level: 0.0,
63 stage_time: 0.0,
64 release_level: 0.0,
65 }
66 }
67
68 pub fn note_on(&mut self) {
70 self.stage = EnvelopeStage::Attack;
71 self.stage_time = 0.0;
72 }
73
74 pub fn note_off(&mut self) {
76 if self.stage != EnvelopeStage::Idle {
77 self.release_level = self.level;
78 self.stage = EnvelopeStage::Release;
79 self.stage_time = 0.0;
80 }
81 }
82
83 pub fn reset(&mut self) {
85 self.stage = EnvelopeStage::Idle;
86 self.level = 0.0;
87 self.stage_time = 0.0;
88 self.release_level = 0.0;
89 }
90
91 #[inline]
92 fn clamp_time(time: f64) -> f64 {
93 time.clamp(Self::MIN_TIME, Self::MAX_TIME)
94 }
95}
96
97impl<S: Sample> Block<S> for EnvelopeBlock<S> {
98 fn process(&mut self, _inputs: &[&[S]], outputs: &mut [&mut [S]], modulation_values: &[S], context: &DspContext) {
99 let attack_time = Self::clamp_time(self.attack.get_value(modulation_values).to_f64());
100 let decay_time = Self::clamp_time(self.decay.get_value(modulation_values).to_f64());
101 let sustain_level = self.sustain.get_value(modulation_values).to_f64().clamp(0.0, 1.0);
102 let release_time = Self::clamp_time(self.release.get_value(modulation_values).to_f64());
103
104 let time_per_sample = 1.0 / context.sample_rate;
105
106 for sample_index in 0..context.buffer_size {
107 match self.stage {
108 EnvelopeStage::Idle => {
109 self.level = 0.0;
110 }
111 EnvelopeStage::Attack => {
112 self.level = self.stage_time / attack_time;
113 if self.level >= 1.0 {
114 self.level = 1.0;
115 self.stage = EnvelopeStage::Decay;
116 self.stage_time = 0.0;
117 }
118 }
119 EnvelopeStage::Decay => {
120 let decay_progress = self.stage_time / decay_time;
121 self.level = 1.0 - (1.0 - sustain_level) * decay_progress;
122 if self.level <= sustain_level {
123 self.level = sustain_level;
124 self.stage = EnvelopeStage::Sustain;
125 self.stage_time = 0.0;
126 }
127 }
128 EnvelopeStage::Sustain => {
129 self.level = sustain_level;
130 }
131 EnvelopeStage::Release => {
132 let release_progress = self.stage_time / release_time;
133 self.level = self.release_level * (1.0 - release_progress);
134 if self.level <= Self::ENVELOPE_FLOOR {
136 self.level = 0.0;
137 self.stage = EnvelopeStage::Idle;
138 self.stage_time = 0.0;
139 }
140 }
141 }
142
143 outputs[0][sample_index] = S::from_f64(self.level);
144
145 if self.stage != EnvelopeStage::Idle && self.stage != EnvelopeStage::Sustain {
146 self.stage_time += time_per_sample;
147 }
148 }
149 }
150
151 #[inline]
152 fn input_count(&self) -> usize {
153 DEFAULT_MODULATOR_INPUT_COUNT
154 }
155
156 #[inline]
157 fn output_count(&self) -> usize {
158 DEFAULT_MODULATOR_OUTPUT_COUNT
159 }
160
161 #[inline]
162 fn modulation_outputs(&self) -> &[ModulationOutput] {
163 Self::MODULATION_OUTPUTS
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170 use crate::channel::ChannelLayout;
171
172 fn test_context(buffer_size: usize, sample_rate: f64) -> DspContext {
173 DspContext {
174 sample_rate,
175 num_channels: 1,
176 buffer_size,
177 current_sample: 0,
178 channel_layout: ChannelLayout::Mono,
179 }
180 }
181
182 fn process_envelope<S: Sample>(env: &mut EnvelopeBlock<S>, context: &DspContext) -> Vec<S> {
183 let inputs: [&[S]; 0] = [];
184 let mut output = vec![S::ZERO; context.buffer_size];
185 let mut outputs: [&mut [S]; 1] = [&mut output];
186 env.process(&inputs, &mut outputs, &[], context);
187 output
188 }
189
190 #[test]
191 fn test_envelope_input_output_counts_f32() {
192 let env = EnvelopeBlock::<f32>::new(0.01, 0.1, 0.5, 0.2);
193 assert_eq!(env.input_count(), DEFAULT_MODULATOR_INPUT_COUNT);
194 assert_eq!(env.output_count(), DEFAULT_MODULATOR_OUTPUT_COUNT);
195 }
196
197 #[test]
198 fn test_envelope_input_output_counts_f64() {
199 let env = EnvelopeBlock::<f64>::new(0.01, 0.1, 0.5, 0.2);
200 assert_eq!(env.input_count(), DEFAULT_MODULATOR_INPUT_COUNT);
201 assert_eq!(env.output_count(), DEFAULT_MODULATOR_OUTPUT_COUNT);
202 }
203
204 #[test]
205 fn test_envelope_modulation_output_f32() {
206 let env = EnvelopeBlock::<f32>::new(0.01, 0.1, 0.5, 0.2);
207 let outputs = env.modulation_outputs();
208 assert_eq!(outputs.len(), 1);
209 assert_eq!(outputs[0].name, "Envelope");
210 assert!((outputs[0].min_value - 0.0).abs() < 1e-10);
211 assert!((outputs[0].max_value - 1.0).abs() < 1e-10);
212 }
213
214 #[test]
215 fn test_envelope_idle_produces_zero_f32() {
216 let mut env = EnvelopeBlock::<f32>::new(0.01, 0.1, 0.5, 0.2);
217 let context = test_context(512, 44100.0);
218 let output = process_envelope(&mut env, &context);
219
220 for (i, &sample) in output.iter().enumerate() {
221 assert!(
222 sample.abs() < 1e-6,
223 "Idle envelope should produce zero: output[{}]={}",
224 i,
225 sample
226 );
227 }
228 }
229
230 #[test]
231 fn test_envelope_idle_produces_zero_f64() {
232 let mut env = EnvelopeBlock::<f64>::new(0.01, 0.1, 0.5, 0.2);
233 let context = test_context(512, 44100.0);
234 let output = process_envelope(&mut env, &context);
235
236 for (i, &sample) in output.iter().enumerate() {
237 assert!(
238 sample.abs() < 1e-12,
239 "Idle envelope should produce zero: output[{}]={}",
240 i,
241 sample
242 );
243 }
244 }
245
246 #[test]
247 fn test_envelope_output_range_f32() {
248 let mut env = EnvelopeBlock::<f32>::new(0.01, 0.05, 0.7, 0.1);
249 let context = test_context(512, 44100.0);
250
251 env.note_on();
252
253 for _ in 0..100 {
254 let output = process_envelope(&mut env, &context);
255 for &sample in &output {
256 assert!(
257 sample >= 0.0 && sample <= 1.0,
258 "Envelope should be in [0, 1] range: {}",
259 sample
260 );
261 }
262 }
263 }
264
265 #[test]
266 fn test_envelope_output_range_f64() {
267 let mut env = EnvelopeBlock::<f64>::new(0.01, 0.05, 0.7, 0.1);
268 let context = test_context(512, 44100.0);
269
270 env.note_on();
271
272 for _ in 0..100 {
273 let output = process_envelope(&mut env, &context);
274 for &sample in &output {
275 assert!(
276 sample >= 0.0 && sample <= 1.0,
277 "Envelope should be in [0, 1] range: {}",
278 sample
279 );
280 }
281 }
282 }
283
284 #[test]
285 fn test_envelope_attack_rises_f32() {
286 let attack_time = 0.1;
287 let mut env = EnvelopeBlock::<f32>::new(attack_time, 0.1, 0.5, 0.1);
288 let sample_rate = 44100.0;
289 let context = test_context(512, sample_rate);
290
291 env.note_on();
292
293 let output = process_envelope(&mut env, &context);
294
295 assert!(output[0] < output[output.len() - 1], "Attack should rise over time");
296 assert!(output[0] < 0.5, "Attack should start low");
297 }
298
299 #[test]
300 fn test_envelope_attack_rises_f64() {
301 let attack_time = 0.1;
302 let mut env = EnvelopeBlock::<f64>::new(attack_time, 0.1, 0.5, 0.1);
303 let sample_rate = 44100.0;
304 let context = test_context(512, sample_rate);
305
306 env.note_on();
307
308 let output = process_envelope(&mut env, &context);
309
310 assert!(output[0] < output[output.len() - 1], "Attack should rise over time");
311 assert!(output[0] < 0.5, "Attack should start low");
312 }
313
314 #[test]
315 fn test_envelope_reaches_peak_f32() {
316 let attack_time = 0.01;
317 let mut env = EnvelopeBlock::<f32>::new(attack_time, 0.5, 0.5, 0.5);
318 let sample_rate = 44100.0;
319 let context = test_context(512, sample_rate);
320
321 env.note_on();
322
323 for _ in 0..10 {
324 let output = process_envelope(&mut env, &context);
325 let max = output.iter().fold(0.0f32, |acc, &x| acc.max(x));
326 if (max - 1.0).abs() < 0.05 {
327 return;
328 }
329 }
330 panic!("Envelope should reach peak of 1.0 during attack");
331 }
332
333 #[test]
334 fn test_envelope_reaches_peak_f64() {
335 let attack_time = 0.01;
336 let mut env = EnvelopeBlock::<f64>::new(attack_time, 0.5, 0.5, 0.5);
337 let sample_rate = 44100.0;
338 let context = test_context(512, sample_rate);
339
340 env.note_on();
341
342 for _ in 0..10 {
343 let output = process_envelope(&mut env, &context);
344 let max = output.iter().fold(0.0f64, |acc, &x| acc.max(x));
345 if (max - 1.0).abs() < 0.05 {
346 return;
347 }
348 }
349 panic!("Envelope should reach peak of 1.0 during attack");
350 }
351
352 #[test]
353 fn test_envelope_sustain_level_f32() {
354 let sustain = 0.6;
355 let mut env = EnvelopeBlock::<f32>::new(0.001, 0.001, sustain, 0.5);
356 let sample_rate = 44100.0;
357 let context = test_context(512, sample_rate);
358
359 env.note_on();
360
361 for _ in 0..20 {
362 let _ = process_envelope(&mut env, &context);
363 }
364
365 let output = process_envelope(&mut env, &context);
366 let avg: f32 = output.iter().sum::<f32>() / output.len() as f32;
367
368 assert!(
369 (avg - sustain as f32).abs() < 0.05,
370 "Sustain should hold at sustain level: expected={}, got={}",
371 sustain,
372 avg
373 );
374 }
375
376 #[test]
377 fn test_envelope_sustain_level_f64() {
378 let sustain = 0.6;
379 let mut env = EnvelopeBlock::<f64>::new(0.001, 0.001, sustain, 0.5);
380 let sample_rate = 44100.0;
381 let context = test_context(512, sample_rate);
382
383 env.note_on();
384
385 for _ in 0..20 {
386 let _ = process_envelope(&mut env, &context);
387 }
388
389 let output = process_envelope(&mut env, &context);
390 let avg: f64 = output.iter().sum::<f64>() / output.len() as f64;
391
392 assert!(
393 (avg - sustain).abs() < 0.05,
394 "Sustain should hold at sustain level: expected={}, got={}",
395 sustain,
396 avg
397 );
398 }
399
400 #[test]
401 fn test_envelope_release_falls_f32() {
402 let sustain = 0.7;
403 let mut env = EnvelopeBlock::<f32>::new(0.001, 0.001, sustain, 0.1);
404 let sample_rate = 44100.0;
405 let context = test_context(512, sample_rate);
406
407 env.note_on();
408
409 for _ in 0..10 {
410 let _ = process_envelope(&mut env, &context);
411 }
412
413 env.note_off();
414
415 let output1 = process_envelope(&mut env, &context);
416 let output2 = process_envelope(&mut env, &context);
417
418 let first_avg: f32 = output1.iter().sum::<f32>() / output1.len() as f32;
419 let second_avg: f32 = output2.iter().sum::<f32>() / output2.len() as f32;
420
421 assert!(
422 first_avg > second_avg,
423 "Release should fall over time: first={}, second={}",
424 first_avg,
425 second_avg
426 );
427 }
428
429 #[test]
430 fn test_envelope_release_falls_f64() {
431 let sustain = 0.7_f64;
432 let mut env = EnvelopeBlock::<f64>::new(0.001, 0.001, sustain, 0.1);
433 let sample_rate = 44100.0;
434 let context = test_context(512, sample_rate);
435
436 env.note_on();
437
438 for _ in 0..10 {
439 let _ = process_envelope(&mut env, &context);
440 }
441
442 env.note_off();
443
444 let output1 = process_envelope(&mut env, &context);
445 let output2 = process_envelope(&mut env, &context);
446
447 let first_avg: f64 = output1.iter().sum::<f64>() / output1.len() as f64;
448 let second_avg: f64 = output2.iter().sum::<f64>() / output2.len() as f64;
449
450 assert!(
451 first_avg > second_avg,
452 "Release should fall over time: first={}, second={}",
453 first_avg,
454 second_avg
455 );
456 }
457
458 #[test]
459 fn test_envelope_release_returns_to_zero_f32() {
460 let mut env = EnvelopeBlock::<f32>::new(0.001, 0.001, 0.5, 0.01);
461 let sample_rate = 44100.0;
462 let context = test_context(512, sample_rate);
463
464 env.note_on();
465
466 for _ in 0..5 {
467 let _ = process_envelope(&mut env, &context);
468 }
469
470 env.note_off();
471
472 for _ in 0..20 {
473 let output = process_envelope(&mut env, &context);
474 let last = output[output.len() - 1];
475 if last.abs() < 1e-5 {
476 return;
477 }
478 }
479 panic!("Release should return to zero");
480 }
481
482 #[test]
483 fn test_envelope_release_returns_to_zero_f64() {
484 let mut env = EnvelopeBlock::<f64>::new(0.001, 0.001, 0.5, 0.01);
485 let sample_rate = 44100.0;
486 let context = test_context(512, sample_rate);
487
488 env.note_on();
489
490 for _ in 0..5 {
491 let _ = process_envelope(&mut env, &context);
492 }
493
494 env.note_off();
495
496 for _ in 0..20 {
497 let output = process_envelope(&mut env, &context);
498 let last = output[output.len() - 1];
499 if last.abs() < 1e-5 {
500 return;
501 }
502 }
503 panic!("Release should return to zero");
504 }
505
506 #[test]
507 fn test_envelope_reset_f32() {
508 let mut env = EnvelopeBlock::<f32>::new(0.1, 0.1, 0.5, 0.1);
509 let context = test_context(512, 44100.0);
510
511 env.note_on();
512 let _ = process_envelope(&mut env, &context);
513
514 env.reset();
515
516 let output = process_envelope(&mut env, &context);
517 for &sample in &output {
518 assert!(sample.abs() < 1e-6, "Reset should return to zero");
519 }
520 }
521
522 #[test]
523 fn test_envelope_reset_f64() {
524 let mut env = EnvelopeBlock::<f64>::new(0.1, 0.1, 0.5, 0.1);
525 let context = test_context(512, 44100.0);
526
527 env.note_on();
528 let _ = process_envelope(&mut env, &context);
529
530 env.reset();
531
532 let output = process_envelope(&mut env, &context);
533 for &sample in &output {
534 assert!(sample.abs() < 1e-12, "Reset should return to zero");
535 }
536 }
537
538 #[test]
539 fn test_envelope_note_off_from_idle_f32() {
540 let mut env = EnvelopeBlock::<f32>::new(0.1, 0.1, 0.5, 0.1);
541 let context = test_context(512, 44100.0);
542
543 env.note_off();
544
545 let output = process_envelope(&mut env, &context);
546 for &sample in &output {
547 assert!(sample.abs() < 1e-6, "note_off from idle should stay at zero");
548 }
549 }
550
551 #[test]
552 fn test_envelope_retrigger_f32() {
553 let mut env = EnvelopeBlock::<f32>::new(0.001, 0.001, 0.5, 0.1);
554 let context = test_context(512, 44100.0);
555
556 env.note_on();
557 for _ in 0..10 {
558 let _ = process_envelope(&mut env, &context);
559 }
560
561 env.note_on();
562
563 let output = process_envelope(&mut env, &context);
564 assert!(
565 output.iter().any(|&x| x < 0.5),
566 "Retrigger should restart attack from beginning"
567 );
568 }
569
570 #[test]
571 fn test_envelope_time_clamping_f32() {
572 let mut env = EnvelopeBlock::<f32>::new(0.0001, 0.0001, 0.5, 0.0001);
573 let context = test_context(512, 44100.0);
574
575 env.note_on();
576
577 for _ in 0..100 {
578 let output = process_envelope(&mut env, &context);
579 for &sample in &output {
580 assert!(
581 sample >= 0.0 && sample <= 1.0,
582 "Very short times should still work: {}",
583 sample
584 );
585 }
586 }
587 }
588}