Skip to main content

audio_automation/
envelope.rs

1//! Automation envelope — time-indexed parameter automation with interpolation
2
3use alloc::collections::BTreeSet;
4use alloc::vec::Vec;
5
6use super::curve::CurveType;
7use serde::{Deserialize, Serialize};
8
9/// Single automation point
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct AutomationPoint {
12    /// Time position in beats (or seconds, depending on context)
13    pub time: f64,
14    /// If None, calculated from beat position and tempo map
15    pub sample_position: Option<u64>,
16    pub value: f32,
17    /// Curve to next point
18    pub curve: CurveType,
19}
20
21impl AutomationPoint {
22    pub const fn new(time: f64, value: f32) -> Self {
23        Self {
24            time,
25            sample_position: None,
26            value,
27            curve: CurveType::Linear,
28        }
29    }
30
31    pub const fn with_curve(time: f64, value: f32, curve: CurveType) -> Self {
32        Self {
33            time,
34            sample_position: None,
35            value,
36            curve,
37        }
38    }
39
40    pub const fn with_samples(
41        time: f64,
42        sample_position: u64,
43        value: f32,
44        curve: CurveType,
45    ) -> Self {
46        Self {
47            time,
48            sample_position: Some(sample_position),
49            value,
50            curve,
51        }
52    }
53
54    pub fn set_sample_position(&mut self, sample: u64) {
55        self.sample_position = Some(sample);
56    }
57}
58
59/// Compares two [`AutomationPoint`]s using epsilon-based floating-point comparison
60/// on `time` and `value` fields. The `curve` type is not considered.
61impl PartialEq for AutomationPoint {
62    fn eq(&self, other: &Self) -> bool {
63        (self.time - other.time).abs() < f64::EPSILON
64            && (self.value - other.value).abs() < f32::EPSILON
65    }
66}
67
68/// Automation envelope for a single parameter.
69///
70/// Generic over `T` (the automation target). Use `with_*` builder methods for construction,
71/// or mutating methods (returning `&mut Self`) for chaining on an existing envelope.
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct AutomationEnvelope<T> {
74    pub target: T,
75    /// Sorted by time
76    pub points: Vec<AutomationPoint>,
77    pub enabled: bool,
78    pub min_value: Option<f32>,
79    pub max_value: Option<f32>,
80    pub step_size: Option<f32>,
81}
82
83impl<T> AutomationEnvelope<T> {
84    pub fn new(target: T) -> Self {
85        Self {
86            target,
87            points: Vec::new(),
88            enabled: true,
89            min_value: None,
90            max_value: None,
91            step_size: None,
92        }
93    }
94
95    pub fn with_min(mut self, min: f32) -> Self {
96        self.min_value = Some(min);
97        self
98    }
99
100    pub fn with_max(mut self, max: f32) -> Self {
101        self.max_value = Some(max);
102        self
103    }
104
105    pub fn with_range(mut self, min: f32, max: f32) -> Self {
106        self.min_value = Some(min);
107        self.max_value = Some(max);
108        self
109    }
110
111    pub fn with_step(mut self, step: f32) -> Self {
112        self.step_size = Some(step);
113        self
114    }
115
116    /// Returns `Self` (owned) for building; `add_point` returns `&mut Self` for chaining.
117    pub fn with_point(mut self, point: AutomationPoint) -> Self {
118        self.add_point(point);
119        self
120    }
121
122    pub fn with_points(mut self, points: impl IntoIterator<Item = AutomationPoint>) -> Self {
123        for point in points {
124            self.add_point(point);
125        }
126        self
127    }
128
129    /// Inserts maintaining sorted order; replaces existing point at same time.
130    pub fn add_point(&mut self, point: AutomationPoint) -> &mut Self {
131        let pos = self
132            .points
133            .binary_search_by(|p| p.time.total_cmp(&point.time));
134
135        match pos {
136            Ok(idx) => {
137                self.points[idx] = point;
138            }
139            Err(idx) => {
140                self.points.insert(idx, point);
141            }
142        }
143        self
144    }
145
146    pub fn remove_point_at(&mut self, time: f64) -> &mut Self {
147        if let Some(pos) = self
148            .points
149            .iter()
150            .position(|p| (p.time - time).abs() < 0.001)
151        {
152            self.points.remove(pos);
153        }
154        self
155    }
156
157    pub fn remove_point(&mut self, index: usize) -> &mut Self {
158        if index < self.points.len() {
159            self.points.remove(index);
160        }
161        self
162    }
163
164    #[must_use]
165    pub fn get_point(&self, index: usize) -> Option<&AutomationPoint> {
166        self.points.get(index)
167    }
168
169    pub fn get_point_mut(&mut self, index: usize) -> Option<&mut AutomationPoint> {
170        self.points.get_mut(index)
171    }
172
173    pub fn clear(&mut self) -> &mut Self {
174        self.points.clear();
175        self
176    }
177
178    #[must_use]
179    pub fn len(&self) -> usize {
180        self.points.len()
181    }
182
183    #[must_use]
184    pub fn is_empty(&self) -> bool {
185        self.points.is_empty()
186    }
187
188    #[must_use]
189    #[inline]
190    pub fn get_value_at(&self, time: f64) -> Option<f32> {
191        if !self.enabled || self.points.is_empty() {
192            return None;
193        }
194
195        if self.points.len() == 1 {
196            return Some(self.apply_constraints(self.points[0].value));
197        }
198
199        if time <= self.points[0].time {
200            return Some(self.apply_constraints(self.points[0].value));
201        }
202
203        let last_idx = self.points.len() - 1;
204        if time >= self.points[last_idx].time {
205            return Some(self.apply_constraints(self.points[last_idx].value));
206        }
207
208        let (prev_idx, next_idx) = self.find_surrounding_indices(time)?;
209        let prev = &self.points[prev_idx];
210        let next = &self.points[next_idx];
211
212        let time_span = next.time - prev.time;
213        let t = if time_span > 0.0 {
214            ((time - prev.time) / time_span) as f32
215        } else {
216            0.0
217        };
218
219        let value = prev.curve.interpolate(prev.value, next.value, t);
220        Some(self.apply_constraints(value))
221    }
222
223    #[must_use]
224    /// Preferred for real-time audio processing — sample-accurate interpolation.
225    #[inline]
226    pub fn get_value_at_sample(&self, sample: u64) -> Option<f32> {
227        if !self.enabled || self.points.is_empty() {
228            return None;
229        }
230
231        if self.points.len() == 1 {
232            return Some(self.apply_constraints(self.points[0].value));
233        }
234
235        // Fallback: if no sample positions set, treat first point as sample 0
236        let first_sample = self.points[0].sample_position.unwrap_or(0);
237
238        if sample <= first_sample {
239            return Some(self.apply_constraints(self.points[0].value));
240        }
241
242        let last_idx = self.points.len() - 1;
243        // Fallback: estimate based on time (assume 48kHz)
244        let last_sample = self.points[last_idx]
245            .sample_position
246            .unwrap_or((self.points[last_idx].time * 48000.0) as u64);
247
248        if sample >= last_sample {
249            return Some(self.apply_constraints(self.points[last_idx].value));
250        }
251
252        let (prev_idx, next_idx) = self.find_surrounding_samples(sample)?;
253        let prev = &self.points[prev_idx];
254        let next = &self.points[next_idx];
255
256        let prev_sample = prev.sample_position.unwrap_or((prev.time * 48000.0) as u64);
257        let next_sample = next.sample_position.unwrap_or((next.time * 48000.0) as u64);
258
259        let sample_span = next_sample - prev_sample;
260        let t = if sample_span > 0 {
261            (sample - prev_sample) as f32 / sample_span as f32
262        } else {
263            0.0
264        };
265
266        let value = prev.curve.interpolate(prev.value, next.value, t);
267        Some(self.apply_constraints(value))
268    }
269
270    fn apply_constraints(&self, mut value: f32) -> f32 {
271        if let Some(min) = self.min_value {
272            value = value.max(min);
273        }
274        if let Some(max) = self.max_value {
275            value = value.min(max);
276        }
277
278        if let Some(step) = self.step_size {
279            if step > 0.0 {
280                value = libm::roundf(value / step) * step;
281            }
282        }
283
284        value
285    }
286
287    fn find_surrounding_indices(&self, time: f64) -> Option<(usize, usize)> {
288        let pos = self.points.binary_search_by(|p| p.time.total_cmp(&time));
289
290        match pos {
291            Ok(exact) => Some((exact, exact)),
292            Err(insert_pos) => {
293                if insert_pos == 0 || insert_pos >= self.points.len() {
294                    None
295                } else {
296                    Some((insert_pos - 1, insert_pos))
297                }
298            }
299        }
300    }
301
302    fn find_surrounding_samples(&self, sample: u64) -> Option<(usize, usize)> {
303        let pos = self.points.binary_search_by_key(&sample, |p| {
304            p.sample_position.unwrap_or((p.time * 48000.0) as u64)
305        });
306
307        match pos {
308            Ok(exact) => Some((exact, exact)),
309            Err(insert_pos) => {
310                if insert_pos == 0 || insert_pos >= self.points.len() {
311                    None
312                } else {
313                    Some((insert_pos - 1, insert_pos))
314                }
315            }
316        }
317    }
318
319    /// Usually not needed — `add_point` maintains sorted order.
320    pub fn sort_points(&mut self) -> &mut Self {
321        self.points.sort_by(|a, b| a.time.total_cmp(&b.time));
322        self
323    }
324
325    pub fn validate(&mut self) {
326        for i in 1..self.points.len() {
327            if self.points[i].time < self.points[i - 1].time {
328                self.sort_points();
329                break;
330            }
331        }
332
333        let mut seen_times = BTreeSet::new();
334        self.points.retain(|p| seen_times.insert(p.time.to_bits()));
335    }
336
337    #[must_use]
338    pub fn get_range_samples(&self, start_sample: u64, end_sample: u64) -> Option<(f32, f32)> {
339        if self.points.is_empty() {
340            return None;
341        }
342
343        let mut min = f32::INFINITY;
344        let mut max = f32::NEG_INFINITY;
345
346        let sample_step = ((end_sample - start_sample) / 100).max(1);
347        let mut current = start_sample;
348
349        while current <= end_sample {
350            if let Some(value) = self.get_value_at_sample(current) {
351                min = min.min(value);
352                max = max.max(value);
353            }
354            current += sample_step;
355        }
356
357        if min.is_finite() {
358            Some((min, max))
359        } else {
360            None
361        }
362    }
363
364    #[must_use]
365    pub fn get_range(&self, start_time: f64, end_time: f64) -> Option<(f32, f32)> {
366        if self.points.is_empty() {
367            return None;
368        }
369
370        let mut min = f32::INFINITY;
371        let mut max = f32::NEG_INFINITY;
372
373        let sample_count = 100;
374        let step = (end_time - start_time) / sample_count as f64;
375
376        for i in 0..=sample_count {
377            let time = start_time + step * i as f64;
378            if let Some(value) = self.get_value_at(time) {
379                min = min.min(value);
380                max = max.max(value);
381            }
382        }
383
384        for point in &self.points {
385            if point.time >= start_time && point.time <= end_time {
386                min = min.min(point.value);
387                max = max.max(point.value);
388            }
389        }
390
391        if min.is_finite() {
392            Some((min, max))
393        } else {
394            None
395        }
396    }
397
398    pub fn shift_points(&mut self, offset: f64) -> &mut Self {
399        for point in &mut self.points {
400            point.time += offset;
401        }
402        self
403    }
404
405    pub fn scale_time(&mut self, factor: f64) -> &mut Self {
406        if factor > 0.0 {
407            for point in &mut self.points {
408                point.time *= factor;
409            }
410        }
411        self
412    }
413
414    pub fn trim(&mut self, start_time: f64, end_time: f64) -> &mut Self {
415        self.points
416            .retain(|p| p.time >= start_time && p.time <= end_time);
417        self
418    }
419
420    pub fn reverse(&mut self) -> &mut Self {
421        if self.points.is_empty() {
422            return self;
423        }
424
425        let max_time = self.points.last().unwrap().time;
426
427        for point in &mut self.points {
428            point.time = max_time - point.time;
429        }
430
431        self.points.reverse();
432        self
433    }
434
435    pub fn invert_values(&mut self, min: f32, max: f32) -> &mut Self {
436        for point in &mut self.points {
437            point.value = max - (point.value - min);
438        }
439        self
440    }
441
442    pub fn quantize_time(&mut self, grid: f64) -> &mut Self {
443        if grid <= 0.0 {
444            return self;
445        }
446
447        for point in &mut self.points {
448            point.time = libm::round(point.time / grid) * grid;
449        }
450
451        self.validate();
452        self
453    }
454
455    /// Removes points whose omission would produce error ≤ `tolerance`.
456    pub fn simplify(&mut self, tolerance: f32) -> &mut Self {
457        if self.points.len() <= 2 {
458            return self;
459        }
460
461        let mut simplified = Vec::new();
462        simplified.push(self.points[0].clone());
463
464        for i in 1..self.points.len() - 1 {
465            let prev = &self.points[i - 1];
466            let curr = &self.points[i];
467            let next = &self.points[i + 1];
468
469            let time_span = next.time - prev.time;
470            let t = ((curr.time - prev.time) / time_span) as f32;
471            let interpolated = prev.curve.interpolate(prev.value, next.value, t);
472
473            if (curr.value - interpolated).abs() > tolerance {
474                simplified.push(curr.clone());
475            }
476        }
477
478        simplified.push(self.points.last().unwrap().clone());
479        self.points = simplified;
480        self
481    }
482
483    #[must_use]
484    pub fn to_buffer(&self, sample_rate: f64, duration: f64) -> Vec<f32> {
485        let num_samples = (duration * sample_rate) as usize;
486        (0..num_samples)
487            .map(|i| self.get_value_at(i as f64 / sample_rate).unwrap_or(0.0))
488            .collect()
489    }
490
491    #[must_use]
492    pub fn iter_samples(&self, sample_rate: f64, duration: f64) -> SampleIterator<'_, T> {
493        SampleIterator {
494            envelope: self,
495            sample_rate,
496            current_sample: 0,
497            total_samples: (duration * sample_rate) as usize,
498        }
499    }
500
501    #[must_use]
502    pub fn get_slope_at(&self, time: f64) -> Option<f32> {
503        if self.points.len() < 2 {
504            return Some(0.0);
505        }
506
507        let delta = 0.001;
508        let v1 = self.get_value_at(time - delta)?;
509        let v2 = self.get_value_at(time + delta)?;
510
511        Some((v2 - v1) / (2.0 * delta) as f32)
512    }
513
514    #[must_use]
515    pub fn find_peaks(&self) -> Vec<(f64, f32)> {
516        self.points
517            .windows(3)
518            .filter(|w| w[1].value > w[0].value && w[1].value > w[2].value)
519            .map(|w| (w[1].time, w[1].value))
520            .collect()
521    }
522
523    #[must_use]
524    pub fn find_valleys(&self) -> Vec<(f64, f32)> {
525        self.points
526            .windows(3)
527            .filter(|w| w[1].value < w[0].value && w[1].value < w[2].value)
528            .map(|w| (w[1].time, w[1].value))
529            .collect()
530    }
531}
532
533impl<T: Clone> AutomationEnvelope<T> {
534    /// 0.0 → 1.0 over `duration`.
535    pub fn fade_in(target: T, duration: f64, curve: CurveType) -> Self {
536        let mut env = Self::new(target);
537        env.add_point(AutomationPoint::new(0.0, 0.0));
538        env.add_point(AutomationPoint::with_curve(duration, 1.0, curve));
539        env
540    }
541
542    /// 1.0 → 0.0 over `duration`.
543    pub fn fade_out(target: T, duration: f64, curve: CurveType) -> Self {
544        let mut env = Self::new(target);
545        env.add_point(AutomationPoint::new(0.0, 1.0));
546        env.add_point(AutomationPoint::with_curve(duration, 0.0, curve));
547        env
548    }
549
550    /// Attack → sustain → release envelope.
551    pub fn pulse(target: T, fade_in: f64, sustain: f64, fade_out: f64, curve: CurveType) -> Self {
552        let mut env = Self::new(target);
553        env.add_point(AutomationPoint::new(0.0, 0.0));
554        env.add_point(AutomationPoint::with_curve(fade_in, 1.0, curve));
555        env.add_point(AutomationPoint::new(fade_in + sustain, 1.0));
556        env.add_point(AutomationPoint::with_curve(
557            fade_in + sustain + fade_out,
558            0.0,
559            curve,
560        ));
561        env
562    }
563
564    pub fn ramp(target: T, duration: f64, start: f32, end_value: f32, curve: CurveType) -> Self {
565        let mut envelope = Self::new(target);
566        envelope.add_point(AutomationPoint::new(0.0, start));
567        envelope.add_point(AutomationPoint::with_curve(duration, end_value, curve));
568        envelope
569    }
570
571    /// Sine-wave oscillation between `min` and `max` at `frequency` Hz.
572    pub fn lfo(target: T, frequency: f64, duration: f64, min: f32, max: f32) -> Self {
573        let period = 1.0 / frequency;
574        let num_cycles = libm::ceil(duration / period) as usize;
575
576        (0..=num_cycles * 4)
577            .map(|i| {
578                let t = i as f64 * period / 4.0;
579                let phase = (i % 4) as f32 / 4.0;
580                let value = min
581                    + (max - min) * (libm::sinf(phase * core::f32::consts::PI * 2.0) * 0.5 + 0.5);
582                (t, value)
583            })
584            .take_while(|&(t, _)| t <= duration)
585            .fold(Self::new(target), |mut env, (t, value)| {
586                env.add_point(AutomationPoint::new(t, value));
587                env
588            })
589    }
590}
591
592impl<T: Clone> AutomationEnvelope<T> {
593    /// Blend this envelope with another. `factor` 0.0 = this, 1.0 = other.
594    #[must_use]
595    pub fn blend(&self, other: &Self, factor: f32) -> Self {
596        let factor = factor.clamp(0.0, 1.0);
597        self.combine(other, |a, b| a * (1.0 - factor) + b * factor)
598    }
599
600    /// Concatenate `other` into this envelope, shifted by `offset`.
601    pub fn merge(&mut self, other: &Self, offset: f64) -> &mut Self {
602        for point in &other.points {
603            self.add_point(AutomationPoint {
604                time: point.time + offset,
605                ..point.clone()
606            });
607        }
608        self
609    }
610
611    /// Samples both envelopes at all time points and adds their values.
612    #[must_use]
613    pub fn add(&self, other: &Self) -> Self {
614        self.combine(other, |a, b| a + b)
615    }
616
617    /// Useful for amplitude modulation or applying gain curves.
618    #[must_use]
619    pub fn multiply(&self, other: &Self) -> Self {
620        self.combine(other, |a, b| a * b)
621    }
622
623    /// Useful for envelope followers or ducking effects.
624    #[must_use]
625    pub fn min(&self, other: &Self) -> Self {
626        self.combine(other, |a, b| a.min(b))
627    }
628
629    /// Useful for gating or ensuring minimum levels.
630    #[must_use]
631    pub fn max(&self, other: &Self) -> Self {
632        self.combine(other, |a, b| a.max(b))
633    }
634
635    #[must_use]
636    pub fn subtract(&self, other: &Self) -> Self {
637        self.combine(other, |a, b| a - b)
638    }
639
640    fn combine<F>(&self, other: &Self, op: F) -> Self
641    where
642        F: Fn(f32, f32) -> f32,
643    {
644        let times: BTreeSet<u64> = self
645            .points
646            .iter()
647            .chain(other.points.iter())
648            .map(|p| p.time.to_bits())
649            .collect();
650
651        let mut result = Self::new(self.target.clone());
652        for time in times.into_iter().map(f64::from_bits) {
653            let v1 = self.get_value_at(time).unwrap_or(0.0);
654            let v2 = other.get_value_at(time).unwrap_or(0.0);
655            result.add_point(AutomationPoint::new(time, op(v1, v2)));
656        }
657        result
658    }
659
660    /// Scales all values to fit between `new_min` and `new_max`.
661    pub fn normalize(&mut self, new_min: f32, new_max: f32) -> &mut Self {
662        let (current_min, current_max) = self
663            .points
664            .iter()
665            .fold((f32::INFINITY, f32::NEG_INFINITY), |(lo, hi), p| {
666                (lo.min(p.value), hi.max(p.value))
667            });
668
669        let range = current_max - current_min;
670        if range > 0.0 {
671            let new_range = new_max - new_min;
672            for point in &mut self.points {
673                point.value = new_min + (point.value - current_min) / range * new_range;
674            }
675        }
676
677        self
678    }
679
680    pub fn scale(&mut self, factor: f32) -> &mut Self {
681        for point in &mut self.points {
682            point.value *= factor;
683        }
684        self
685    }
686
687    pub fn offset(&mut self, amount: f32) -> &mut Self {
688        for point in &mut self.points {
689            point.value += amount;
690        }
691        self
692    }
693
694    pub fn clamp_values(&mut self, min: f32, max: f32) -> &mut Self {
695        for point in &mut self.points {
696            point.value = point.value.clamp(min, max);
697        }
698        self
699    }
700
701    pub fn apply_fade_in(&mut self, duration: f64, curve: CurveType) -> &mut Self {
702        if duration <= 0.0 {
703            return self;
704        }
705        if let Some(start_time) = self.points.first().map(|p| p.time) {
706            let end_time = start_time + duration;
707            for point in &mut self.points {
708                if point.time <= end_time {
709                    let t = ((point.time - start_time) / duration).clamp(0.0, 1.0) as f32;
710                    point.value *= curve.interpolate(0.0, 1.0, t);
711                }
712            }
713        }
714        self
715    }
716
717    pub fn apply_fade_out(&mut self, duration: f64, curve: CurveType) -> &mut Self {
718        if duration <= 0.0 {
719            return self;
720        }
721        if let Some(end_time) = self.points.last().map(|p| p.time) {
722            let start_time = end_time - duration;
723            for point in &mut self.points {
724                if point.time >= start_time {
725                    let t = ((point.time - start_time) / duration).clamp(0.0, 1.0) as f32;
726                    point.value *= curve.interpolate(1.0, 0.0, t);
727                }
728            }
729        }
730        self
731    }
732
733    pub fn apply_fades(
734        &mut self,
735        fade_in_duration: f64,
736        fade_out_duration: f64,
737        curve: CurveType,
738    ) -> &mut Self {
739        self.apply_fade_in(fade_in_duration, curve)
740            .apply_fade_out(fade_out_duration, curve)
741    }
742
743    pub fn apply_gate(&mut self, threshold: f32) -> &mut Self {
744        for point in &mut self.points {
745            point.value = if point.value < threshold {
746                0.0
747            } else {
748                point.value
749            };
750        }
751        self
752    }
753
754    /// Values above `threshold` are reduced by `ratio`.
755    pub fn apply_compression(&mut self, threshold: f32, ratio: f32) -> &mut Self {
756        for point in &mut self.points {
757            if point.value > threshold {
758                let excess = point.value - threshold;
759                point.value = threshold + excess / ratio;
760            }
761        }
762        self
763    }
764}
765
766/// Iterator over sampled envelope values
767pub struct SampleIterator<'a, T> {
768    envelope: &'a AutomationEnvelope<T>,
769    sample_rate: f64,
770    current_sample: usize,
771    total_samples: usize,
772}
773
774impl<'a, T> Iterator for SampleIterator<'a, T> {
775    type Item = f32;
776
777    fn next(&mut self) -> Option<Self::Item> {
778        if self.current_sample >= self.total_samples {
779            return None;
780        }
781
782        let time = self.current_sample as f64 / self.sample_rate;
783        self.current_sample += 1;
784
785        self.envelope.get_value_at(time)
786    }
787}
788
789impl<'a, T> ExactSizeIterator for SampleIterator<'a, T> {
790    fn len(&self) -> usize {
791        self.total_samples - self.current_sample
792    }
793}
794
795impl<T> core::ops::Index<usize> for AutomationEnvelope<T> {
796    type Output = AutomationPoint;
797    fn index(&self, index: usize) -> &Self::Output {
798        &self.points[index]
799    }
800}
801
802impl<T> core::ops::IndexMut<usize> for AutomationEnvelope<T> {
803    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
804        &mut self.points[index]
805    }
806}
807
808impl<'a, T> IntoIterator for &'a AutomationEnvelope<T> {
809    type Item = &'a AutomationPoint;
810    type IntoIter = core::slice::Iter<'a, AutomationPoint>;
811    fn into_iter(self) -> Self::IntoIter {
812        self.points.iter()
813    }
814}
815
816impl<'a, T> IntoIterator for &'a mut AutomationEnvelope<T> {
817    type Item = &'a mut AutomationPoint;
818    type IntoIter = core::slice::IterMut<'a, AutomationPoint>;
819    fn into_iter(self) -> Self::IntoIter {
820        self.points.iter_mut()
821    }
822}
823
824impl<T: Default> FromIterator<AutomationPoint> for AutomationEnvelope<T> {
825    fn from_iter<I: IntoIterator<Item = AutomationPoint>>(iter: I) -> Self {
826        let mut env = Self::new(T::default());
827        for point in iter {
828            env.add_point(point);
829        }
830        env
831    }
832}
833
834#[cfg(test)]
835mod tests {
836    use super::*;
837
838    // Simple target type for testing
839    #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
840    enum TestTarget {
841        Volume,
842        Pan,
843    }
844
845    #[test]
846    fn test_add_point_maintains_order() {
847        let mut env = AutomationEnvelope::new(TestTarget::Volume);
848        env.add_point(AutomationPoint::new(2.0, 0.5));
849        env.add_point(AutomationPoint::new(1.0, 0.3));
850        env.add_point(AutomationPoint::new(3.0, 0.7));
851
852        assert_eq!(env.points.len(), 3);
853        assert_eq!(env.points[0].time, 1.0);
854        assert_eq!(env.points[1].time, 2.0);
855        assert_eq!(env.points[2].time, 3.0);
856    }
857
858    #[test]
859    fn test_get_value_at() {
860        let mut env = AutomationEnvelope::new(TestTarget::Volume);
861        env.add_point(AutomationPoint::new(0.0, 0.0));
862        env.add_point(AutomationPoint::new(4.0, 1.0));
863
864        // At midpoint with linear curve
865        let mid_value = env.get_value_at(2.0).unwrap();
866        assert!((mid_value - 0.5).abs() < 0.01);
867
868        // Before first point
869        assert_eq!(env.get_value_at(-1.0).unwrap(), 0.0);
870
871        // After last point
872        assert_eq!(env.get_value_at(5.0).unwrap(), 1.0);
873    }
874
875    #[test]
876    fn test_remove_point() {
877        let mut env = AutomationEnvelope::new(TestTarget::Volume);
878        env.add_point(AutomationPoint::new(1.0, 0.1));
879        env.add_point(AutomationPoint::new(2.0, 0.2));
880        env.add_point(AutomationPoint::new(3.0, 0.3));
881
882        env.remove_point_at(2.0);
883        assert_eq!(env.points.len(), 2);
884        assert_eq!(env.points[1].time, 3.0);
885    }
886
887    #[test]
888    fn test_value_constraints() {
889        let mut env = AutomationEnvelope::new(TestTarget::Volume).with_range(0.0, 1.0);
890        env.add_point(AutomationPoint::new(0.0, -0.5));
891        env.add_point(AutomationPoint::new(4.0, 1.5));
892
893        // Values should be clamped
894        assert_eq!(env.get_value_at(0.0).unwrap(), 0.0);
895        assert_eq!(env.get_value_at(4.0).unwrap(), 1.0);
896    }
897
898    #[test]
899    fn test_step_quantization() {
900        let mut env = AutomationEnvelope::new(TestTarget::Volume).with_step(0.25);
901        env.add_point(AutomationPoint::new(0.0, 0.0));
902        env.add_point(AutomationPoint::new(4.0, 1.0));
903
904        let value = env.get_value_at(2.0).unwrap();
905        // Value should be quantized to nearest 0.25
906        assert_eq!(value, 0.5);
907    }
908
909    #[test]
910    fn test_shift_points() {
911        let mut env = AutomationEnvelope::new(TestTarget::Volume);
912        env.add_point(AutomationPoint::new(0.0, 0.0));
913        env.add_point(AutomationPoint::new(4.0, 1.0));
914
915        env.shift_points(2.0);
916        assert_eq!(env.points[0].time, 2.0);
917        assert_eq!(env.points[1].time, 6.0);
918    }
919
920    #[test]
921    fn test_scale_time() {
922        let mut env = AutomationEnvelope::new(TestTarget::Volume);
923        env.add_point(AutomationPoint::new(0.0, 0.0));
924        env.add_point(AutomationPoint::new(4.0, 1.0));
925
926        env.scale_time(2.0);
927        assert_eq!(env.points[0].time, 0.0);
928        assert_eq!(env.points[1].time, 8.0);
929    }
930
931    #[test]
932    fn test_reverse() {
933        let mut env = AutomationEnvelope::new(TestTarget::Volume);
934        env.add_point(AutomationPoint::new(0.0, 0.0));
935        env.add_point(AutomationPoint::new(2.0, 0.5));
936        env.add_point(AutomationPoint::new(4.0, 1.0));
937
938        env.reverse();
939        assert_eq!(env.points[0].time, 0.0);
940        assert_eq!(env.points[0].value, 1.0);
941        assert_eq!(env.points[2].time, 4.0);
942        assert_eq!(env.points[2].value, 0.0);
943    }
944
945    #[test]
946    fn test_invert_values() {
947        let mut env = AutomationEnvelope::new(TestTarget::Volume);
948        env.add_point(AutomationPoint::new(0.0, 0.0));
949        env.add_point(AutomationPoint::new(4.0, 1.0));
950
951        env.invert_values(0.0, 1.0);
952        assert_eq!(env.points[0].value, 1.0);
953        assert_eq!(env.points[1].value, 0.0);
954    }
955
956    #[test]
957    fn test_preset_fade_in() {
958        let env = AutomationEnvelope::fade_in(TestTarget::Volume, 4.0, CurveType::Linear);
959        assert_eq!(env.points.len(), 2);
960        assert_eq!(env.get_value_at(0.0).unwrap(), 0.0);
961        assert_eq!(env.get_value_at(4.0).unwrap(), 1.0);
962    }
963
964    #[test]
965    fn test_preset_pulse() {
966        let env = AutomationEnvelope::pulse(TestTarget::Volume, 1.0, 2.0, 1.0, CurveType::Linear);
967        assert_eq!(env.points.len(), 4);
968        assert_eq!(env.get_value_at(0.0).unwrap(), 0.0);
969        assert_eq!(env.get_value_at(1.0).unwrap(), 1.0);
970        assert_eq!(env.get_value_at(3.0).unwrap(), 1.0);
971        assert_eq!(env.get_value_at(4.0).unwrap(), 0.0);
972    }
973
974    #[test]
975    fn test_to_buffer() {
976        let mut env = AutomationEnvelope::new(TestTarget::Volume);
977        env.add_point(AutomationPoint::new(0.0, 0.0));
978        env.add_point(AutomationPoint::new(1.0, 1.0));
979
980        let buffer = env.to_buffer(10.0, 1.0);
981        assert_eq!(buffer.len(), 10);
982        assert_eq!(buffer[0], 0.0);
983        // At time 0.9 (sample 9), should be close to 1.0
984        assert!((buffer[9] - 0.9).abs() < 0.1);
985    }
986
987    #[test]
988    fn test_iter_samples() {
989        let mut env = AutomationEnvelope::new(TestTarget::Volume);
990        env.add_point(AutomationPoint::new(0.0, 0.0));
991        env.add_point(AutomationPoint::new(1.0, 1.0));
992
993        let samples: Vec<f32> = env.iter_samples(10.0, 1.0).collect();
994        assert_eq!(samples.len(), 10);
995        assert_eq!(samples[0], 0.0);
996        // At time 0.9 (sample 9), should be close to 1.0
997        assert!((samples[9] - 0.9).abs() < 0.1);
998    }
999
1000    #[test]
1001    fn test_find_peaks() {
1002        let mut env = AutomationEnvelope::new(TestTarget::Volume);
1003        env.add_point(AutomationPoint::new(0.0, 0.0));
1004        env.add_point(AutomationPoint::new(2.0, 1.0)); // peak
1005        env.add_point(AutomationPoint::new(4.0, 0.5));
1006
1007        let peaks = env.find_peaks();
1008        assert_eq!(peaks.len(), 1);
1009        assert_eq!(peaks[0].0, 2.0);
1010        assert_eq!(peaks[0].1, 1.0);
1011    }
1012
1013    #[test]
1014    fn test_blend() {
1015        let mut env1 = AutomationEnvelope::new(TestTarget::Volume);
1016        env1.add_point(AutomationPoint::new(0.0, 0.0));
1017        env1.add_point(AutomationPoint::new(4.0, 0.0));
1018
1019        let mut env2 = AutomationEnvelope::new(TestTarget::Volume);
1020        env2.add_point(AutomationPoint::new(0.0, 1.0));
1021        env2.add_point(AutomationPoint::new(4.0, 1.0));
1022
1023        let blended = env1.blend(&env2, 0.5);
1024        assert!((blended.get_value_at(2.0).unwrap() - 0.5).abs() < 0.01);
1025    }
1026
1027    // ==================== Mathematical Operations Tests ====================
1028
1029    #[test]
1030    fn test_add() {
1031        let mut env1 = AutomationEnvelope::new(TestTarget::Volume);
1032        env1.add_point(AutomationPoint::new(0.0, 0.5));
1033        env1.add_point(AutomationPoint::new(4.0, 0.5));
1034
1035        let mut env2 = AutomationEnvelope::new(TestTarget::Volume);
1036        env2.add_point(AutomationPoint::new(0.0, 0.3));
1037        env2.add_point(AutomationPoint::new(4.0, 0.3));
1038
1039        let result = env1.add(&env2);
1040        assert!((result.get_value_at(2.0).unwrap() - 0.8).abs() < 0.01);
1041    }
1042
1043    #[test]
1044    fn test_multiply() {
1045        let mut env1 = AutomationEnvelope::new(TestTarget::Volume);
1046        env1.add_point(AutomationPoint::new(0.0, 2.0));
1047        env1.add_point(AutomationPoint::new(4.0, 2.0));
1048
1049        let mut env2 = AutomationEnvelope::new(TestTarget::Volume);
1050        env2.add_point(AutomationPoint::new(0.0, 0.5));
1051        env2.add_point(AutomationPoint::new(4.0, 0.5));
1052
1053        let result = env1.multiply(&env2);
1054        assert!((result.get_value_at(2.0).unwrap() - 1.0).abs() < 0.01);
1055    }
1056
1057    #[test]
1058    fn test_min_max() {
1059        let mut env1 = AutomationEnvelope::new(TestTarget::Volume);
1060        env1.add_point(AutomationPoint::new(0.0, 0.3));
1061        env1.add_point(AutomationPoint::new(4.0, 0.3));
1062
1063        let mut env2 = AutomationEnvelope::new(TestTarget::Volume);
1064        env2.add_point(AutomationPoint::new(0.0, 0.7));
1065        env2.add_point(AutomationPoint::new(4.0, 0.7));
1066
1067        let min_result = env1.min(&env2);
1068        assert!((min_result.get_value_at(2.0).unwrap() - 0.3).abs() < 0.01);
1069
1070        let max_result = env1.max(&env2);
1071        assert!((max_result.get_value_at(2.0).unwrap() - 0.7).abs() < 0.01);
1072    }
1073
1074    // ==================== Normalization Tests ====================
1075
1076    #[test]
1077    fn test_normalize() {
1078        let mut env = AutomationEnvelope::new(TestTarget::Volume);
1079        env.add_point(AutomationPoint::new(0.0, 10.0));
1080        env.add_point(AutomationPoint::new(4.0, 20.0));
1081
1082        env.normalize(0.0, 1.0);
1083
1084        assert!((env.get_value_at(0.0).unwrap() - 0.0).abs() < 0.01);
1085        assert!((env.get_value_at(4.0).unwrap() - 1.0).abs() < 0.01);
1086    }
1087
1088    #[test]
1089    fn test_scale_offset() {
1090        let mut env = AutomationEnvelope::new(TestTarget::Volume);
1091        env.add_point(AutomationPoint::new(0.0, 1.0));
1092        env.add_point(AutomationPoint::new(4.0, 1.0));
1093
1094        env.scale(2.0).offset(0.5);
1095
1096        assert!((env.get_value_at(2.0).unwrap() - 2.5).abs() < 0.01);
1097    }
1098
1099    #[test]
1100    fn test_clamp_values() {
1101        let mut env = AutomationEnvelope::new(TestTarget::Volume);
1102        env.add_point(AutomationPoint::new(0.0, -1.0));
1103        env.add_point(AutomationPoint::new(2.0, 0.5));
1104        env.add_point(AutomationPoint::new(4.0, 2.0));
1105
1106        env.clamp_values(0.0, 1.0);
1107
1108        assert_eq!(env.points[0].value, 0.0);
1109        assert_eq!(env.points[1].value, 0.5);
1110        assert_eq!(env.points[2].value, 1.0);
1111    }
1112
1113    // ==================== Fade Tests ====================
1114
1115    #[test]
1116    fn test_apply_fade_in() {
1117        let mut env = AutomationEnvelope::new(TestTarget::Volume);
1118        env.add_point(AutomationPoint::new(0.0, 1.0));
1119        env.add_point(AutomationPoint::new(4.0, 1.0));
1120
1121        env.apply_fade_in(2.0, CurveType::Linear);
1122
1123        // At start should be 0 (1.0 * 0.0)
1124        assert!((env.get_value_at(0.0).unwrap() - 0.0).abs() < 0.01);
1125        // After fade should be full
1126        assert!((env.get_value_at(4.0).unwrap() - 1.0).abs() < 0.01);
1127    }
1128
1129    #[test]
1130    fn test_apply_fade_out() {
1131        let mut env = AutomationEnvelope::new(TestTarget::Volume);
1132        env.add_point(AutomationPoint::new(0.0, 1.0));
1133        env.add_point(AutomationPoint::new(4.0, 1.0));
1134
1135        env.apply_fade_out(2.0, CurveType::Linear);
1136
1137        // At start should be full
1138        assert!((env.get_value_at(0.0).unwrap() - 1.0).abs() < 0.01);
1139        // At end should be 0 (1.0 * 0.0)
1140        assert!((env.get_value_at(4.0).unwrap() - 0.0).abs() < 0.01);
1141    }
1142
1143    #[test]
1144    fn test_apply_fades_chaining() {
1145        let mut env = AutomationEnvelope::new(TestTarget::Volume);
1146        env.add_point(AutomationPoint::new(0.0, 1.0));
1147        env.add_point(AutomationPoint::new(2.0, 1.0));
1148        env.add_point(AutomationPoint::new(4.0, 1.0));
1149        env.add_point(AutomationPoint::new(6.0, 1.0));
1150        env.add_point(AutomationPoint::new(8.0, 1.0));
1151
1152        env.apply_fades(2.0, 2.0, CurveType::Linear);
1153
1154        // Should have fade in at start and fade out at end
1155        assert!((env.get_value_at(0.0).unwrap() - 0.0).abs() < 0.01);
1156        // Middle should be close to 1.0
1157        assert!((env.get_value_at(4.0).unwrap() - 1.0).abs() < 0.1);
1158        assert!((env.get_value_at(8.0).unwrap() - 0.0).abs() < 0.01);
1159    }
1160
1161    #[test]
1162    fn test_apply_gate() {
1163        let mut env = AutomationEnvelope::new(TestTarget::Volume);
1164        env.add_point(AutomationPoint::new(0.0, 0.1));
1165        env.add_point(AutomationPoint::new(2.0, 0.5));
1166        env.add_point(AutomationPoint::new(4.0, 0.2));
1167
1168        env.apply_gate(0.3);
1169
1170        assert_eq!(env.points[0].value, 0.0); // Below threshold
1171        assert_eq!(env.points[1].value, 0.5); // Above threshold
1172        assert_eq!(env.points[2].value, 0.0); // Below threshold
1173    }
1174
1175    #[test]
1176    fn test_apply_compression() {
1177        let mut env = AutomationEnvelope::new(TestTarget::Volume);
1178        env.add_point(AutomationPoint::new(0.0, 0.5));
1179        env.add_point(AutomationPoint::new(2.0, 1.0));
1180
1181        env.apply_compression(0.7, 2.0);
1182
1183        // First point below threshold - unchanged
1184        assert_eq!(env.points[0].value, 0.5);
1185        // Second point: 0.7 + (1.0 - 0.7) / 2.0 = 0.85
1186        assert!((env.points[1].value - 0.85).abs() < 0.01);
1187    }
1188}