web_audio_api/
param.rs

1//! AudioParam interface
2
3use std::any::Any;
4use std::slice::{Iter, IterMut};
5use std::sync::atomic::Ordering;
6use std::sync::{Arc, Mutex, OnceLock};
7
8use arrayvec::ArrayVec;
9
10use crate::context::AudioContextRegistration;
11use crate::node::{
12    AudioNode, AudioNodeOptions, ChannelConfig, ChannelCountMode, ChannelInterpretation,
13};
14use crate::render::{
15    AudioParamValues, AudioProcessor, AudioRenderQuantum, AudioWorkletGlobalScope,
16};
17use crate::{assert_valid_time_value, AtomicF32, RENDER_QUANTUM_SIZE};
18
19/// For SetTargetAtTime event, that theoretically cannot end, if the diff between
20/// the current value and the target is below this threshold, the value is set
21/// to target value and the event is considered ended.
22const SNAP_TO_TARGET: f32 = 1e-10;
23
24#[track_caller]
25fn assert_is_finite(value: f32) {
26    assert!(
27        value.is_finite(),
28        "TypeError - The provided value is non-finite."
29    );
30}
31
32#[track_caller]
33fn assert_strictly_positive(value: f64) {
34    assert!(
35        value.is_finite(),
36        "TypeError - The provided value is non-finite."
37    );
38    assert!(
39        value > 0.,
40        "RangeError - duration ({:?}) should be strictly positive",
41        value
42    );
43}
44
45#[track_caller]
46fn assert_not_zero(value: f32) {
47    assert_is_finite(value);
48    assert_ne!(
49        value, 0.,
50        "RangeError - value ({:?}) should not be equal to zero",
51        value
52    );
53}
54
55#[track_caller]
56fn assert_sequence_length(values: &[f32]) {
57    assert!(
58        values.len() >= 2,
59        "InvalidStateError - sequence length ({:?}) should not be less than 2",
60        values.len()
61    );
62}
63
64// ๐‘ฃ(๐‘ก) = ๐‘‰0 + (๐‘‰1โˆ’๐‘‰0) * ((๐‘กโˆ’๐‘‡0) / (๐‘‡1โˆ’๐‘‡0))
65#[inline(always)]
66fn compute_linear_ramp_sample(
67    start_time: f64,
68    duration: f64,
69    start_value: f32,
70    diff: f32, // end_value - start_value
71    time: f64,
72) -> f32 {
73    let phase = (time - start_time) / duration;
74    diff.mul_add(phase as f32, start_value)
75}
76
77// v(t) = v1 * (v2/v1)^((t-t1) / (t2-t1))
78#[inline(always)]
79fn compute_exponential_ramp_sample(
80    start_time: f64,
81    duration: f64,
82    start_value: f32,
83    ratio: f32, // end_value / start_value
84    time: f64,
85) -> f32 {
86    let phase = (time - start_time) / duration;
87    start_value * ratio.powf(phase as f32)
88}
89
90// ๐‘ฃ(๐‘ก) = ๐‘‰1 + (๐‘‰0 โˆ’ ๐‘‰1) * ๐‘’^โˆ’((๐‘กโˆ’๐‘‡0) / ๐œ)
91#[inline(always)]
92fn compute_set_target_sample(
93    start_time: f64,
94    time_constant: f64,
95    end_value: f32,
96    diff: f32, // start_value - end_value
97    time: f64,
98) -> f32 {
99    let exponent = -1. * ((time - start_time) / time_constant);
100    diff.mul_add(exponent.exp() as f32, end_value)
101}
102
103// ๐‘˜=โŒŠ๐‘โˆ’1 / ๐‘‡๐ท * (๐‘กโˆ’๐‘‡0)โŒ‹
104// Then ๐‘ฃ(๐‘ก) is computed by linearly interpolating between ๐‘‰[๐‘˜] and ๐‘‰[๐‘˜+1],
105#[inline(always)]
106fn compute_set_value_curve_sample(
107    start_time: f64,
108    duration: f64,
109    values: &[f32],
110    time: f64,
111) -> f32 {
112    if time - start_time >= duration {
113        return values[values.len() - 1];
114    }
115
116    let position = (values.len() - 1) as f64 * (time - start_time) / duration;
117    let k = position as usize;
118    let phase = (position - position.floor()) as f32;
119    (values[k + 1] - values[k]).mul_add(phase, values[k])
120}
121
122/// Precision of AudioParam value calculation per render quantum
123#[derive(Copy, Clone, PartialEq, Eq, Debug)]
124pub enum AutomationRate {
125    /// Audio Rate - sampled for each sample-frame of the block
126    A,
127    /// Control Rate - sampled at the time of the very first sample-frame,
128    /// then used for the entire block
129    K,
130}
131
132impl AutomationRate {
133    fn is_a_rate(self) -> bool {
134        match self {
135            AutomationRate::A => true,
136            AutomationRate::K => false,
137        }
138    }
139}
140
141/// Options for constructing an [`AudioParam`]
142#[derive(Clone, Debug)]
143pub struct AudioParamDescriptor {
144    pub name: String,
145    pub automation_rate: AutomationRate,
146    pub default_value: f32,
147    pub min_value: f32,
148    pub max_value: f32,
149}
150
151#[derive(PartialEq, Eq, Debug, Copy, Clone)]
152enum AudioParamEventType {
153    SetValue,
154    SetValueAtTime,
155    LinearRampToValueAtTime,
156    ExponentialRampToValueAtTime,
157    CancelScheduledValues,
158    SetTargetAtTime,
159    CancelAndHoldAtTime,
160    SetValueCurveAtTime,
161}
162
163#[derive(Debug)]
164pub(crate) struct AudioParamEvent {
165    event_type: AudioParamEventType,
166    value: f32,
167    time: f64,
168    time_constant: Option<f64>, // populated by `SetTargetAtTime` events
169    cancel_time: Option<f64>,   // populated by `CancelAndHoldAtTime` events
170    duration: Option<f64>,      // populated by `SetValueCurveAtTime` events
171    values: Option<Box<[f32]>>, // populated by `SetValueCurveAtTime` events
172}
173
174// Event queue that contains `AudioParamEvent`s, most of the time, events must be
175// ordered (using stable sort), some operation may break this ordering (e.g. `push`)
176// in which cases `sort` must be called explicitly.
177// In the current implementation of the param rendering, `sort` is called once after
178// all events have been inserted in the queue at each `tick` (with the exception
179// `CancelAndHoldAtTime` which needs a clean queue to find its neighbors, but this
180// occurs during the insertion of events)
181// After this point, the queue should be considered sorted and no operations that
182// breaks the ordering should be done.
183#[derive(Debug, Default)]
184struct AudioParamEventTimeline {
185    inner: Vec<AudioParamEvent>,
186    dirty: bool,
187}
188
189impl AudioParamEventTimeline {
190    fn new() -> Self {
191        Self {
192            inner: Vec::with_capacity(32),
193            dirty: false,
194        }
195    }
196
197    fn push(&mut self, item: AudioParamEvent) {
198        self.dirty = true;
199        self.inner.push(item);
200    }
201
202    // `pop` and `retain` preserve order so they don't make the queue dirty
203    fn pop(&mut self) -> Option<AudioParamEvent> {
204        if !self.inner.is_empty() {
205            Some(self.inner.remove(0))
206        } else {
207            None
208        }
209    }
210
211    fn retain<F>(&mut self, func: F)
212    where
213        F: Fn(&AudioParamEvent) -> bool,
214    {
215        self.inner.retain(func);
216    }
217
218    // Only used to handle special cases in `ExponentialRampToValueAtTime`:
219    // as the replaced item has the same time, order is preserved.
220    // If the method turned out to be used elsewhere, this could maybe
221    // become wrong, be careful here.
222    fn replace_peek(&mut self, item: AudioParamEvent) {
223        self.inner[0] = item;
224    }
225
226    fn is_empty(&self) -> bool {
227        self.inner.is_empty()
228    }
229
230    fn unsorted_peek(&self) -> Option<&AudioParamEvent> {
231        self.inner.first()
232    }
233
234    // panic if dirty, we are doing something wrong here
235    fn peek(&self) -> Option<&AudioParamEvent> {
236        assert!(
237            !self.dirty,
238            "`AudioParamEventTimeline`: Invalid `.peek()` call, the queue is dirty"
239        );
240        self.inner.first()
241    }
242
243    fn next(&self) -> Option<&AudioParamEvent> {
244        assert!(
245            !self.dirty,
246            "`AudioParamEventTimeline`: Invalid `.next()` call, the queue is dirty"
247        );
248        self.inner.get(1)
249    }
250
251    fn sort(&mut self) {
252        self.inner
253            .sort_by(|a, b| a.time.partial_cmp(&b.time).unwrap());
254        self.dirty = false;
255    }
256
257    fn iter(&mut self) -> Iter<'_, AudioParamEvent> {
258        self.inner.iter()
259    }
260
261    fn iter_mut(&mut self) -> IterMut<'_, AudioParamEvent> {
262        self.inner.iter_mut()
263    }
264}
265
266/// AudioParam controls an individual aspect of an AudioNode's functionality, such as volume.
267#[derive(Clone)] // `Clone` for the node bindings, see #378
268pub struct AudioParam {
269    registration: Arc<AudioContextRegistration>,
270    raw_parts: AudioParamInner,
271}
272
273impl std::fmt::Debug for AudioParam {
274    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
275        f.debug_struct("AudioParam")
276            .field("registration", &self.registration())
277            .field("automation_rate", &self.automation_rate())
278            .field(
279                "automation_rate_constrained",
280                &self.raw_parts.automation_rate_constrained,
281            )
282            .field("default_value", &self.default_value())
283            .field("min_value", &self.min_value())
284            .field("max_value", &self.max_value())
285            .finish()
286    }
287}
288
289// helper struct to attach / detach to context (for borrow reasons)
290#[derive(Debug, Clone)]
291pub(crate) struct AudioParamInner {
292    default_value: f32,                          // immutable
293    min_value: f32,                              // immutable
294    max_value: f32,                              // immutable
295    automation_rate_constrained: bool,           // effectively immutable
296    automation_rate: Arc<Mutex<AutomationRate>>, // shared with clones
297    current_value: Arc<AtomicF32>,               // shared with clones and with render thread
298}
299
300impl AudioNode for AudioParam {
301    fn registration(&self) -> &AudioContextRegistration {
302        &self.registration
303    }
304
305    fn channel_config(&self) -> &'static ChannelConfig {
306        static INSTANCE: OnceLock<ChannelConfig> = OnceLock::new();
307        INSTANCE.get_or_init(|| {
308            AudioNodeOptions {
309                channel_count: 1,
310                channel_count_mode: ChannelCountMode::Explicit,
311                channel_interpretation: ChannelInterpretation::Discrete,
312            }
313            .into()
314        })
315    }
316
317    fn number_of_inputs(&self) -> usize {
318        1
319    }
320
321    fn number_of_outputs(&self) -> usize {
322        1
323    }
324
325    fn set_channel_count(&self, _v: usize) {
326        panic!("NotSupportedError - AudioParam has channel count constraints");
327    }
328    fn set_channel_count_mode(&self, _v: ChannelCountMode) {
329        panic!("NotSupportedError - AudioParam has channel count mode constraints");
330    }
331    fn set_channel_interpretation(&self, _v: ChannelInterpretation) {
332        panic!("NotSupportedError - AudioParam has channel interpretation constraints");
333    }
334}
335
336impl AudioParam {
337    /// Current value of the automation rate of the AudioParam
338    #[allow(clippy::missing_panics_doc)]
339    pub fn automation_rate(&self) -> AutomationRate {
340        *self.raw_parts.automation_rate.lock().unwrap()
341    }
342
343    /// Update the current value of the automation rate of the AudioParam
344    ///
345    /// # Panics
346    ///
347    /// Some nodes have automation rate constraints and may panic when updating the value.
348    pub fn set_automation_rate(&self, value: AutomationRate) {
349        assert!(
350            !self.raw_parts.automation_rate_constrained || value == self.automation_rate(),
351            "InvalidStateError - automation rate cannot be changed for this param"
352        );
353
354        let mut guard = self.raw_parts.automation_rate.lock().unwrap();
355        *guard = value;
356        self.registration().post_message(value);
357        drop(guard); // drop guard after sending message to prevent out of order arrivals on
358                     // concurrent access
359    }
360
361    pub(crate) fn set_automation_rate_constrained(&mut self, value: bool) {
362        self.raw_parts.automation_rate_constrained = value;
363    }
364
365    pub fn default_value(&self) -> f32 {
366        self.raw_parts.default_value
367    }
368
369    pub fn min_value(&self) -> f32 {
370        self.raw_parts.min_value
371    }
372
373    pub fn max_value(&self) -> f32 {
374        self.raw_parts.max_value
375    }
376
377    /// Retrieve the current value of the `AudioParam`.
378    //
379    // @note: the choice here is to have this coherent with the first sample of
380    // the last rendered block, which means `intrinsic_value` must be calculated
381    // for next_block_time at each tick.
382    // @note - maybe check with spec editors that it is correct
383    //
384    // see. test_linear_ramp_arate_multiple_blocks
385    //      test_linear_ramp_krate_multiple_blocks
386    //      test_exponential_ramp_a_rate_multiple_blocks
387    //      test_exponential_ramp_k_rate_multiple_blocks
388    pub fn value(&self) -> f32 {
389        self.raw_parts.current_value.load(Ordering::Acquire)
390    }
391
392    /// Set the value of the `AudioParam`.
393    ///
394    /// Is equivalent to calling the `set_value_at_time` method with the current
395    /// AudioContext's currentTime
396    //
397    // @note: Setting this attribute has the effect of assigning the requested value
398    // to the [[current value]] slot, and calling the setValueAtTime() method
399    // with the current AudioContext's currentTime and [[current value]].
400    // Any exceptions that would be thrown by setValueAtTime() will also be
401    // thrown by setting this attribute.
402    // cf. https://www.w3.org/TR/webaudio/#dom-audioparam-value
403    pub fn set_value(&self, value: f32) -> &Self {
404        self.send_event(self.set_value_raw(value))
405    }
406
407    fn set_value_raw(&self, value: f32) -> AudioParamEvent {
408        assert_is_finite(value);
409        // current_value should always be clamped
410        let clamped = value.clamp(self.raw_parts.min_value, self.raw_parts.max_value);
411        self.raw_parts
412            .current_value
413            .store(clamped, Ordering::Release);
414
415        // this event is meant to update param intrinsic value before any calculation
416        // is done, will behave as SetValueAtTime with `time == block_timestamp`
417        AudioParamEvent {
418            event_type: AudioParamEventType::SetValue,
419            value,
420            time: 0.,
421            time_constant: None,
422            cancel_time: None,
423            duration: None,
424            values: None,
425        }
426    }
427
428    /// Schedules a parameter value change at the given time.
429    ///
430    /// # Panics
431    ///
432    /// Will panic if `start_time` is negative
433    pub fn set_value_at_time(&self, value: f32, start_time: f64) -> &Self {
434        self.send_event(self.set_value_at_time_raw(value, start_time))
435    }
436
437    fn set_value_at_time_raw(&self, value: f32, start_time: f64) -> AudioParamEvent {
438        assert_is_finite(value);
439        assert_valid_time_value(start_time);
440
441        AudioParamEvent {
442            event_type: AudioParamEventType::SetValueAtTime,
443            value,
444            time: start_time,
445            time_constant: None,
446            cancel_time: None,
447            duration: None,
448            values: None,
449        }
450    }
451
452    /// Schedules a linear continuous change in parameter value from the
453    /// previous scheduled parameter value to the given value.
454    ///
455    /// # Panics
456    ///
457    /// Will panic if `end_time` is negative
458    pub fn linear_ramp_to_value_at_time(&self, value: f32, end_time: f64) -> &Self {
459        self.send_event(self.linear_ramp_to_value_at_time_raw(value, end_time))
460    }
461
462    fn linear_ramp_to_value_at_time_raw(&self, value: f32, end_time: f64) -> AudioParamEvent {
463        assert_is_finite(value);
464        assert_valid_time_value(end_time);
465
466        AudioParamEvent {
467            event_type: AudioParamEventType::LinearRampToValueAtTime,
468            value,
469            time: end_time,
470            time_constant: None,
471            cancel_time: None,
472            duration: None,
473            values: None,
474        }
475    }
476
477    /// Schedules an exponential continuous change in parameter value from the
478    /// previous scheduled parameter value to the given value.
479    ///
480    /// # Panics
481    ///
482    /// Will panic if:
483    /// - `value` is zero
484    /// - `end_time` is negative
485    pub fn exponential_ramp_to_value_at_time(&self, value: f32, end_time: f64) -> &Self {
486        self.send_event(self.exponential_ramp_to_value_at_time_raw(value, end_time))
487    }
488
489    fn exponential_ramp_to_value_at_time_raw(&self, value: f32, end_time: f64) -> AudioParamEvent {
490        assert_not_zero(value);
491        assert_valid_time_value(end_time);
492
493        AudioParamEvent {
494            event_type: AudioParamEventType::ExponentialRampToValueAtTime,
495            value,
496            time: end_time,
497            time_constant: None,
498            cancel_time: None,
499            duration: None,
500            values: None,
501        }
502    }
503
504    /// Start exponentially approaching the target value at the given time with
505    /// a rate having the given time constant.
506    ///
507    /// # Panics
508    ///
509    /// Will panic if:
510    /// - `start_time` is negative
511    /// - `time_constant` is negative
512    pub fn set_target_at_time(&self, value: f32, start_time: f64, time_constant: f64) -> &Self {
513        self.send_event(self.set_target_at_time_raw(value, start_time, time_constant))
514    }
515
516    fn set_target_at_time_raw(
517        &self,
518        value: f32,
519        start_time: f64,
520        time_constant: f64,
521    ) -> AudioParamEvent {
522        assert_is_finite(value);
523        assert_valid_time_value(start_time);
524        assert_valid_time_value(time_constant);
525
526        // [spec] If timeConstant is zero, the output value jumps immediately to the final value.
527        if time_constant == 0. {
528            AudioParamEvent {
529                event_type: AudioParamEventType::SetValueAtTime,
530                value,
531                time: start_time,
532                time_constant: None,
533                cancel_time: None,
534                duration: None,
535                values: None,
536            }
537        } else {
538            AudioParamEvent {
539                event_type: AudioParamEventType::SetTargetAtTime,
540                value,
541                time: start_time,
542                time_constant: Some(time_constant),
543                cancel_time: None,
544                duration: None,
545                values: None,
546            }
547        }
548    }
549
550    /// Cancels all scheduled parameter changes with times greater than or equal
551    /// to `cancel_time`.
552    ///
553    /// # Panics
554    ///
555    /// Will panic if `cancel_time` is negative
556    pub fn cancel_scheduled_values(&self, cancel_time: f64) -> &Self {
557        self.send_event(self.cancel_scheduled_values_raw(cancel_time))
558    }
559
560    fn cancel_scheduled_values_raw(&self, cancel_time: f64) -> AudioParamEvent {
561        assert_valid_time_value(cancel_time);
562
563        AudioParamEvent {
564            event_type: AudioParamEventType::CancelScheduledValues,
565            value: 0., // no value
566            time: cancel_time,
567            time_constant: None,
568            cancel_time: None,
569            duration: None,
570            values: None,
571        }
572    }
573
574    /// Cancels all scheduled parameter changes with times greater than or equal
575    /// to `cancel_time` and the automation value that would have happened at
576    /// that time is then propagated for all future time.
577    ///
578    /// # Panics
579    ///
580    /// Will panic if `cancel_time` is negative
581    pub fn cancel_and_hold_at_time(&self, cancel_time: f64) -> &Self {
582        self.send_event(self.cancel_and_hold_at_time_raw(cancel_time))
583    }
584
585    fn cancel_and_hold_at_time_raw(&self, cancel_time: f64) -> AudioParamEvent {
586        assert_valid_time_value(cancel_time);
587
588        AudioParamEvent {
589            event_type: AudioParamEventType::CancelAndHoldAtTime,
590            value: 0., // value will be defined by cancel event
591            time: cancel_time,
592            time_constant: None,
593            cancel_time: None,
594            duration: None,
595            values: None,
596        }
597    }
598
599    /// Sets an array of arbitrary parameter values starting at the given time
600    /// for the given duration.
601    ///
602    /// # Panics
603    ///
604    /// Will panic if:
605    /// - `value` length is less than 2
606    /// - `start_time` is negative
607    /// - `duration` is negative or equal to zero
608    pub fn set_value_curve_at_time(&self, values: &[f32], start_time: f64, duration: f64) -> &Self {
609        self.send_event(self.set_value_curve_at_time_raw(values, start_time, duration))
610    }
611
612    fn set_value_curve_at_time_raw(
613        &self,
614        values: &[f32],
615        start_time: f64,
616        duration: f64,
617    ) -> AudioParamEvent {
618        assert_sequence_length(values);
619        assert_valid_time_value(start_time);
620        assert_strictly_positive(duration);
621
622        // When this method is called, an internal copy of the curve is
623        // created for automation purposes.
624        let copy = values.to_vec();
625        let boxed_copy = copy.into_boxed_slice();
626
627        AudioParamEvent {
628            event_type: AudioParamEventType::SetValueCurveAtTime,
629            value: 0., // value will be defined at the end of the event
630            time: start_time,
631            time_constant: None,
632            cancel_time: None,
633            duration: Some(duration),
634            values: Some(boxed_copy),
635        }
636    }
637
638    // helper function to detach from context (for borrow reasons)
639    pub(crate) fn into_raw_parts(self) -> AudioParamInner {
640        let Self {
641            registration: _,
642            raw_parts,
643        } = self;
644        raw_parts
645    }
646
647    // helper function to attach to context (for borrow reasons)
648    pub(crate) fn from_raw_parts(
649        registration: AudioContextRegistration,
650        raw_parts: AudioParamInner,
651    ) -> Self {
652        Self {
653            registration: registration.into(),
654            raw_parts,
655        }
656    }
657
658    fn send_event(&self, event: AudioParamEvent) -> &Self {
659        self.registration().post_message(event);
660        self
661    }
662}
663
664struct BlockInfos {
665    block_time: f64,
666    dt: f64,
667    count: usize,
668    is_a_rate: bool,
669    next_block_time: f64,
670}
671
672#[derive(Debug)]
673pub(crate) struct AudioParamProcessor {
674    default_value: f32, // immutable
675    min_value: f32,     // immutable
676    max_value: f32,     // immutable
677    intrinsic_value: f32,
678    automation_rate: AutomationRate,
679    current_value: Arc<AtomicF32>,
680    event_timeline: AudioParamEventTimeline,
681    last_event: Option<AudioParamEvent>,
682    buffer: ArrayVec<f32, RENDER_QUANTUM_SIZE>,
683}
684
685impl AudioProcessor for AudioParamProcessor {
686    fn process(
687        &mut self,
688        inputs: &[AudioRenderQuantum],
689        outputs: &mut [AudioRenderQuantum],
690        _params: AudioParamValues<'_>,
691        scope: &AudioWorkletGlobalScope,
692    ) -> bool {
693        let period = 1. / scope.sample_rate as f64;
694
695        let input = &inputs[0]; // single input mode
696        let output = &mut outputs[0];
697
698        self.compute_intrinsic_values(scope.current_time, period, RENDER_QUANTUM_SIZE);
699        self.mix_to_output(input, output);
700
701        true // has intrinsic value
702    }
703
704    fn onmessage(&mut self, msg: &mut dyn Any) {
705        if let Some(automation_rate) = msg.downcast_ref::<AutomationRate>() {
706            self.automation_rate = *automation_rate;
707            return;
708        }
709
710        if let Some(event) = msg.downcast_mut::<AudioParamEvent>() {
711            // Avoid deallocation of the event by replacing it with a tombstone.
712            let tombstone_event = AudioParamEvent {
713                event_type: AudioParamEventType::SetValue,
714                value: Default::default(),
715                time: Default::default(),
716                time_constant: None,
717                cancel_time: None,
718                duration: None,
719                values: None,
720            };
721            let event = std::mem::replace(event, tombstone_event);
722            self.handle_incoming_event(event);
723            return;
724        };
725
726        log::warn!("AudioParamProcessor: Dropping incoming message {msg:?}");
727    }
728}
729
730impl AudioParamProcessor {
731    // Warning: compute_intrinsic_values in called directly in the unit tests so
732    // everything important for the tests should be done here
733    // The returned value is only used in tests
734    fn compute_intrinsic_values(&mut self, block_time: f64, dt: f64, count: usize) -> &[f32] {
735        self.compute_buffer(block_time, dt, count);
736        self.buffer.as_slice()
737    }
738
739    fn mix_to_output(&mut self, input: &AudioRenderQuantum, output: &mut AudioRenderQuantum) {
740        #[cfg(test)]
741        assert!(self.buffer.len() == 1 || self.buffer.len() == RENDER_QUANTUM_SIZE);
742
743        // handle all k-rate and inactive a-rate processing
744        if self.buffer.len() == 1 || !self.automation_rate.is_a_rate() {
745            let mut value = self.buffer[0];
746
747            // we can return a 1-sized buffer if either
748            // - the input signal is zero, or
749            // - if this is k-rate processing
750            if input.is_silent() || !self.automation_rate.is_a_rate() {
751                output.set_single_valued(true);
752
753                value += input.channel_data(0)[0];
754
755                if value.is_nan() {
756                    value = self.default_value;
757                } else {
758                    // do not use `clamp` because it has extra branches for NaN or when max < min
759                    value = value.max(self.min_value).min(self.max_value);
760                }
761                output.channel_data_mut(0)[0] = value;
762            } else {
763                // a-rate processing and input non-zero
764                output.set_single_valued(false);
765                *output = input.clone();
766                output.channel_data_mut(0).iter_mut().for_each(|o| {
767                    *o += value;
768
769                    if o.is_nan() {
770                        *o = self.default_value;
771                    } else {
772                        // do not use `clamp` because it has extra branches for NaN or when max < min
773                        *o = o.max(self.min_value).min(self.max_value);
774                    }
775                });
776            }
777        } else {
778            // a-rate processing
779            *output = input.clone();
780            output.set_single_valued(false);
781
782            output
783                .channel_data_mut(0)
784                .iter_mut()
785                .zip(self.buffer.iter())
786                .for_each(|(o, p)| {
787                    *o += p;
788
789                    if o.is_nan() {
790                        *o = self.default_value;
791                    } else {
792                        // do not use `clamp` because it has extra branches for NaN or when max < min
793                        *o = o.max(self.min_value).min(self.max_value);
794                    }
795                });
796        }
797    }
798
799    fn handle_incoming_event(&mut self, event: AudioParamEvent) {
800        // cf. https://www.w3.org/TR/webaudio/#computation-of-value
801        // 1. paramIntrinsicValue will be calculated at each time, which is either the
802        // value set directly to the value attribute, or, if there are any automation
803        // events with times before or at this time, the value as calculated from
804        // these events. If automation events are removed from a given time range,
805        // then the paramIntrinsicValue value will remain unchanged and stay at its
806        // previous value until either the value attribute is directly set, or
807        // automation events are added for the time range.
808
809        // handle CancelScheduledValues events
810        // cf. https://www.w3.org/TR/webaudio/#dom-audioparam-cancelscheduledvalues
811        if event.event_type == AudioParamEventType::CancelScheduledValues {
812            // peek current event before inserting new events, and possibly sort
813            // the queue, we need that for checking that we are (or not) in the middle
814            // of a ramp when handling `CancelScheduledValues`
815            // @note - probably not robust enough in some edge cases where the
816            // event is not the first received at this tick (`SetValueCurveAtTime`
817            // and `CancelAndHold` need to sort the queue)
818            let some_current_event = self.event_timeline.unsorted_peek();
819
820            match some_current_event {
821                None => (),
822                Some(current_event) => {
823                    // @note - The spec is not particularly clear about what to do
824                    // with on-going SetTarget events, but both Chrome and Firefox
825                    // ignore the Cancel event if at same time nor if startTime is
826                    // equal to cancelTime, this can makes sens as the event time
827                    // (which is a `startTime` for `SetTarget`) is before the `cancelTime`
828                    // (maybe this should be clarified w/ spec editors)
829
830                    match current_event.event_type {
831                        AudioParamEventType::LinearRampToValueAtTime
832                        | AudioParamEventType::ExponentialRampToValueAtTime => {
833                            // we are in the middle of a ramp
834                            //
835                            // @note - Firefox and Chrome behave differently
836                            // on this: Firefox actually restore intrinsic_value
837                            // from the value at the beginning of the vent, while
838                            // Chrome just keeps the current intrinsic_value
839                            // The spec is not very clear there, but Firefox
840                            // seems to be more compliant:
841                            // "Any active automations whose automation event
842                            // time is less than cancelTime are also cancelled,
843                            // and such cancellations may cause discontinuities
844                            // because the original value (**from before such
845                            // automation**) is restored immediately."
846                            //
847                            // @note - last_event cannot be `None` here, because
848                            // linear or exponential ramps are always preceded
849                            // by another event (even a set_value_at_time
850                            // inserted implicitly), so if the ramp is the next
851                            // event that means that at least one event has
852                            // already been processed.
853                            if current_event.time >= event.time {
854                                let last_event = self.last_event.as_ref().unwrap();
855                                self.intrinsic_value = last_event.value;
856                            }
857                        }
858                        _ => (),
859                    }
860                }
861            }
862
863            // remove all events in queue where event.time >= cancel_time
864            // i.e. keep all events where event.time < cancel_time
865            self.event_timeline
866                .retain(|queued| queued.time < event.time);
867            return; // cancel_values events are not inserted in queue
868        }
869
870        if event.event_type == AudioParamEventType::CancelAndHoldAtTime {
871            // 1. Let ๐ธ1 be the event (if any) at time ๐‘ก1 where ๐‘ก1 is the
872            // largest number satisfying ๐‘ก1 โ‰ค ๐‘ก๐‘.
873            // 2. Let ๐ธ2 be the event (if any) at time ๐‘ก2 where ๐‘ก2 is the
874            // smallest number satisfying ๐‘ก๐‘ < ๐‘ก2.
875            let mut e1: Option<&mut AudioParamEvent> = None;
876            let mut e2: Option<&mut AudioParamEvent> = None;
877            let mut t1 = f64::MIN;
878            let mut t2 = f64::MAX;
879            // we need a sorted timeline here to find siblings
880            self.event_timeline.sort();
881
882            for queued in self.event_timeline.iter_mut() {
883                // closest before cancel time: if several events at same time,
884                // we want the last one
885                if queued.time >= t1 && queued.time <= event.time {
886                    t1 = queued.time;
887                    e1 = Some(queued);
888                    // closest after cancel time: if several events at same time,
889                    // we want the first one
890                } else if queued.time < t2 && queued.time > event.time {
891                    t2 = queued.time;
892                    e2 = Some(queued);
893                }
894            }
895
896            // If ๐ธ2 exists:
897            if let Some(matched) = e2 {
898                // If ๐ธ2 is a linear or exponential ramp,
899                // Effectively rewrite ๐ธ2 to be the same kind of ramp ending
900                // at time ๐‘ก๐‘ with an end value that would be the value of the
901                // original ramp at time ๐‘ก๐‘.
902                // @note - this is done during the actual computation of the
903                //  ramp using the cancel_time
904                if matched.event_type == AudioParamEventType::LinearRampToValueAtTime
905                    || matched.event_type == AudioParamEventType::ExponentialRampToValueAtTime
906                {
907                    matched.cancel_time = Some(event.time);
908                }
909            } else if let Some(matched) = e1 {
910                if matched.event_type == AudioParamEventType::SetTargetAtTime {
911                    // Implicitly insert a setValueAtTime event at time ๐‘ก๐‘ with
912                    // the value that the setTarget would
913                    // @note - same strategy as for ramps
914                    matched.cancel_time = Some(event.time);
915                } else if matched.event_type == AudioParamEventType::SetValueCurveAtTime {
916                    // If ๐ธ1 is a setValueCurve with a start time of ๐‘ก3 and a duration of ๐‘‘
917                    // If ๐‘ก๐‘ <= ๐‘ก3 + ๐‘‘ :
918                    // Effectively replace this event with a setValueCurve event
919                    // with a start time of ๐‘ก3 and a new duration of ๐‘ก๐‘โˆ’๐‘ก3. However,
920                    // this is not a true replacement; this automation MUST take
921                    // care to produce the same output as the original, and not
922                    // one computed using a different duration. (That would cause
923                    // sampling of the value curve in a slightly different way,
924                    // producing different results.)
925                    let start_time = matched.time;
926                    let duration = matched.duration.unwrap();
927
928                    if event.time <= start_time + duration {
929                        matched.cancel_time = Some(event.time);
930                    }
931                }
932            }
933
934            // [spec] Remove all events with time greater than ๐‘ก๐‘.
935            self.event_timeline.retain(|queued| {
936                let mut time = queued.time;
937                // if the event has a `cancel_time` we use it instead of `time`
938                if let Some(cancel_time) = queued.cancel_time {
939                    time = cancel_time;
940                }
941
942                time <= event.time
943            });
944            return; // cancel_and_hold events are not inserted timeline
945        }
946
947        // handle SetValueCurveAtTime
948        // @note - These rules argue in favor of having events inserted in
949        // the control thread, let's panic for now
950        //
951        // [spec] If setValueCurveAtTime() is called for time ๐‘‡ and duration ๐ท
952        // and there are any events having a time strictly greater than ๐‘‡, but
953        // strictly less than ๐‘‡+๐ท, then a NotSupportedError exception MUST be thrown.
954        // In other words, itโ€™s not ok to schedule a value curve during a time period
955        // containing other events, but itโ€™s ok to schedule a value curve exactly
956        // at the time of another event.
957        if event.event_type == AudioParamEventType::SetValueCurveAtTime {
958            // check if we don't try to insert at the time of another event
959            let start_time = event.time;
960            let end_time = start_time + event.duration.unwrap();
961
962            for queued in self.event_timeline.iter() {
963                assert!(
964                    queued.time <= start_time || queued.time >= end_time,
965                    "NotSupportedError - scheduling SetValueCurveAtTime ({:?}) at time of another automation event ({:?})",
966                    event, queued,
967                );
968            }
969        }
970
971        // [spec] Similarly a NotSupportedError exception MUST be thrown if any
972        // automation method is called at a time which is contained in [๐‘‡,๐‘‡+๐ท), ๐‘‡
973        // being the time of the curve and ๐ท its duration.
974        // @note - Cancel methods are not automation methods
975        if event.event_type == AudioParamEventType::SetValueAtTime
976            || event.event_type == AudioParamEventType::SetValue
977            || event.event_type == AudioParamEventType::LinearRampToValueAtTime
978            || event.event_type == AudioParamEventType::ExponentialRampToValueAtTime
979            || event.event_type == AudioParamEventType::SetTargetAtTime
980        {
981            for queued in self.event_timeline.iter() {
982                if queued.event_type == AudioParamEventType::SetValueCurveAtTime {
983                    let start_time = queued.time;
984                    let end_time = start_time + queued.duration.unwrap();
985
986                    assert!(
987                        event.time <= start_time || event.time >= end_time,
988                        "NotSupportedError - scheduling automation event ({:?}) during SetValueCurveAtTime ({:?})",
989                        event, queued,
990                    );
991                }
992            }
993        }
994
995        // handle SetValue - param intrinsic value must be updated from event value
996        if event.event_type == AudioParamEventType::SetValue {
997            self.intrinsic_value = event.value;
998        }
999
1000        // If no event in the timeline and event_type is `LinearRampToValueAtTime`
1001        // or `ExponentialRampToValue` at time, we must insert a `SetValueAtTime`
1002        // with intrinsic value and calling time.
1003        // cf. https://www.w3.org/TR/webaudio/#dom-audioparam-linearramptovalueattime
1004        // cf. https://www.w3.org/TR/webaudio/#dom-audioparam-exponentialramptovalueattime
1005        if self.event_timeline.is_empty()
1006            && self.last_event.is_none()
1007            && (event.event_type == AudioParamEventType::LinearRampToValueAtTime
1008                || event.event_type == AudioParamEventType::ExponentialRampToValueAtTime)
1009        {
1010            let set_value_event = AudioParamEvent {
1011                event_type: AudioParamEventType::SetValue,
1012                value: self.intrinsic_value,
1013                // make sure the event is applied before any other event, time
1014                // will be replaced by the block timestamp during event processing
1015                time: 0.,
1016                time_constant: None,
1017                cancel_time: None,
1018                duration: None,
1019                values: None,
1020            };
1021
1022            self.event_timeline.push(set_value_event);
1023        }
1024
1025        // for SetTarget, this behavior is not per se specified, but it allows
1026        // to make sure we have a stable start_value available without having
1027        // to store it elsewhere.
1028        if self.event_timeline.is_empty()
1029            && event.event_type == AudioParamEventType::SetTargetAtTime
1030        {
1031            let set_value_event = AudioParamEvent {
1032                event_type: AudioParamEventType::SetValue,
1033                value: self.intrinsic_value,
1034                // make sure the event is applied before any other event, time
1035                // will be replaced by the block timestamp during event processing
1036                time: 0.,
1037                time_constant: None,
1038                cancel_time: None,
1039                duration: None,
1040                values: None,
1041            };
1042
1043            self.event_timeline.push(set_value_event);
1044        }
1045
1046        self.event_timeline.push(event);
1047        self.event_timeline.sort();
1048    }
1049
1050    // https://www.w3.org/TR/webaudio/%23dom-audioparam-linearramptovalueattime#dom-audioparam-setvalueattime
1051    fn compute_set_value_automation(&mut self, infos: &BlockInfos) -> bool {
1052        let event = self.event_timeline.peek().unwrap();
1053        let mut time = event.time;
1054
1055        // `set_value` and implicitly inserted events are inserted with `time = 0.`,
1056        // so that we ensure they are processed first. Replacing their `time` with
1057        // `block_time` allows to conform to the spec:
1058        // cf. https://www.w3.org/TR/webaudio/#dom-audioparam-value
1059        // cf. https://www.w3.org/TR/webaudio/#dom-audioparam-linearramptovalueattime
1060        // cf. https://www.w3.org/TR/webaudio/#dom-audioparam-exponentialramptovalueattime
1061        if time == 0. {
1062            time = infos.block_time;
1063        }
1064
1065        // fill buffer with current intrinsic value until `event.time`
1066        if infos.is_a_rate {
1067            let end_index = ((time - infos.block_time).max(0.) / infos.dt).round() as usize;
1068            let end_index_clipped = end_index.min(infos.count);
1069
1070            for _ in self.buffer.len()..end_index_clipped {
1071                self.buffer.push(self.intrinsic_value);
1072            }
1073        }
1074
1075        // event time is not reached within this block, we are done
1076        if time > infos.next_block_time {
1077            return true;
1078        }
1079
1080        // else update `intrisic_value` and `last_event` and continue the loop
1081        self.intrinsic_value = event.value;
1082
1083        // no computation has been done on `time`
1084        #[allow(clippy::float_cmp)]
1085        if time != event.time {
1086            let mut event = self.event_timeline.pop().unwrap();
1087            event.time = time;
1088            self.last_event = Some(event);
1089        } else {
1090            self.last_event = self.event_timeline.pop();
1091        }
1092
1093        false
1094    }
1095
1096    // cf. https://www.w3.org/TR/webaudio/#dom-audioparam-linearramptovalueattime
1097    // ๐‘ฃ(๐‘ก) = ๐‘‰0 + (๐‘‰1โˆ’๐‘‰0) * ((๐‘กโˆ’๐‘‡0) / (๐‘‡1โˆ’๐‘‡0))
1098    fn compute_linear_ramp_automation(&mut self, infos: &BlockInfos) -> bool {
1099        let event = self.event_timeline.peek().unwrap();
1100        let last_event = self.last_event.as_ref().unwrap();
1101
1102        let start_time = last_event.time;
1103        let mut end_time = event.time;
1104        // compute duration before clapping `end_time` to`cancel_time`, to keep
1105        // declared slope of the ramp consistent
1106        let duration = end_time - start_time;
1107        if let Some(cancel_time) = event.cancel_time {
1108            end_time = cancel_time;
1109        }
1110
1111        let start_value = last_event.value;
1112        let end_value = event.value;
1113        let diff = end_value - start_value;
1114
1115        if infos.is_a_rate {
1116            let start_index = self.buffer.len();
1117            // TODO use ceil() or round() when `end_time` is between two samples?
1118            // <https://github.com/orottier/web-audio-api-rs/pull/460>
1119            let end_index = ((end_time - infos.block_time).max(0.) / infos.dt).round() as usize;
1120            let end_index_clipped = end_index.min(infos.count);
1121
1122            // compute "real" value according to `t` then clamp it
1123            // cf. Example 7 https://www.w3.org/TR/webaudio/#computation-of-value
1124            if end_index_clipped > start_index {
1125                let mut time = (start_index as f64).mul_add(infos.dt, infos.block_time);
1126
1127                let mut value = 0.;
1128                for _ in start_index..end_index_clipped {
1129                    value =
1130                        compute_linear_ramp_sample(start_time, duration, start_value, diff, time);
1131                    self.buffer.push(value);
1132                    time += infos.dt;
1133                }
1134                self.intrinsic_value = value;
1135            }
1136        }
1137
1138        // Event will continue in next tick:
1139        // compute value for `next_block_time` so that `param.value()`
1140        // stays coherent, also allows to properly fill k-rate
1141        // within next block too
1142        if end_time >= infos.next_block_time {
1143            let value = compute_linear_ramp_sample(
1144                start_time,
1145                duration,
1146                start_value,
1147                diff,
1148                infos.next_block_time,
1149            );
1150            self.intrinsic_value = value;
1151
1152            return true;
1153        }
1154
1155        // Event cancelled during this block
1156        if event.cancel_time.is_some() {
1157            let value =
1158                compute_linear_ramp_sample(start_time, duration, start_value, diff, end_time);
1159
1160            self.intrinsic_value = value;
1161
1162            let mut last_event = self.event_timeline.pop().unwrap();
1163            last_event.time = end_time;
1164            last_event.value = value;
1165            self.last_event = Some(last_event);
1166        // Event ended during this block
1167        } else {
1168            self.intrinsic_value = end_value;
1169            self.last_event = self.event_timeline.pop();
1170        }
1171
1172        false
1173    }
1174
1175    // cf. https://www.w3.org/TR/webaudio/#dom-audioparam-exponentialramptovalueattime
1176    // v(t) = v1 * (v2/v1)^((t-t1) / (t2-t1))
1177    fn compute_exponential_ramp_automation(&mut self, infos: &BlockInfos) -> bool {
1178        let event = self.event_timeline.peek().unwrap();
1179        let last_event = self.last_event.as_ref().unwrap();
1180
1181        let start_time = last_event.time;
1182        let mut end_time = event.time;
1183        // compute duration before clapping `end_time` to `cancel_time`, to keep
1184        // declared slope of the ramp consistent
1185        let duration = end_time - start_time;
1186        if let Some(cancel_time) = event.cancel_time {
1187            end_time = cancel_time;
1188        }
1189
1190        let start_value = last_event.value;
1191        let end_value = event.value;
1192        let ratio = end_value / start_value;
1193
1194        // Handle edge cases:
1195        // > If ๐‘‰0 and ๐‘‰1 have opposite signs or if ๐‘‰0 is zero,
1196        // > then ๐‘ฃ(๐‘ก)=๐‘‰0 for ๐‘‡0โ‰ค๐‘ก<๐‘‡1.
1197        // as:
1198        // > If there are no more events after this ExponentialRampToValue
1199        // > event then for ๐‘กโ‰ฅ๐‘‡1, ๐‘ฃ(๐‘ก)=๐‘‰1.
1200        // this should thus behave as a SetValue
1201        if start_value == 0. || start_value * end_value < 0. {
1202            let event = AudioParamEvent {
1203                event_type: AudioParamEventType::SetValueAtTime,
1204                time: end_time,
1205                value: end_value,
1206                time_constant: None,
1207                cancel_time: None,
1208                duration: None,
1209                values: None,
1210            };
1211
1212            self.event_timeline.replace_peek(event);
1213            return false;
1214        }
1215
1216        if infos.is_a_rate {
1217            let start_index = self.buffer.len();
1218            // TODO use ceil() or round() when `end_time` is between two samples?
1219            // <https://github.com/orottier/web-audio-api-rs/pull/460>
1220            let end_index = ((end_time - infos.block_time).max(0.) / infos.dt).round() as usize;
1221            let end_index_clipped = end_index.min(infos.count);
1222
1223            if end_index_clipped > start_index {
1224                let mut time = (start_index as f64).mul_add(infos.dt, infos.block_time);
1225
1226                let mut value = 0.;
1227                for _ in start_index..end_index_clipped {
1228                    value = compute_exponential_ramp_sample(
1229                        start_time,
1230                        duration,
1231                        start_value,
1232                        ratio,
1233                        time,
1234                    );
1235
1236                    self.buffer.push(value);
1237
1238                    time += infos.dt;
1239                }
1240                self.intrinsic_value = value;
1241            }
1242        }
1243
1244        // Event will continue in next tick:
1245        // compute value for `next_block_time` so that `param.value()`
1246        // stays coherent, also allows to properly fill k-rate
1247        // within next block too
1248        if end_time >= infos.next_block_time {
1249            let value = compute_exponential_ramp_sample(
1250                start_time,
1251                duration,
1252                start_value,
1253                ratio,
1254                infos.next_block_time,
1255            );
1256            self.intrinsic_value = value;
1257
1258            return true;
1259        }
1260
1261        // Event cancelled during this block
1262        if event.cancel_time.is_some() {
1263            let value =
1264                compute_exponential_ramp_sample(start_time, duration, start_value, ratio, end_time);
1265
1266            self.intrinsic_value = value;
1267
1268            let mut last_event = self.event_timeline.pop().unwrap();
1269            last_event.time = end_time;
1270            last_event.value = value;
1271            self.last_event = Some(last_event);
1272
1273        // Event ended during this block
1274        } else {
1275            self.intrinsic_value = end_value;
1276            self.last_event = self.event_timeline.pop();
1277        }
1278
1279        false
1280    }
1281
1282    // https://webaudio.github.io/web-audio-api/#dom-audioparam-settargetattime
1283    // ๐‘ฃ(๐‘ก) = ๐‘‰1 + (๐‘‰0 โˆ’ ๐‘‰1) * ๐‘’^โˆ’((๐‘กโˆ’๐‘‡0) / ๐œ)
1284    // Note that as SetTarget never resolves on an end value, a `SetTarget` event
1285    // is inserted in the timeline when the value is close enough to the target.
1286    // cf. `SNAP_TO_TARGET`
1287    fn compute_set_target_automation(&mut self, infos: &BlockInfos) -> bool {
1288        let event = self.event_timeline.peek().unwrap();
1289        let mut end_time = infos.next_block_time;
1290        let mut ended = false;
1291
1292        // handle next event stop SetTarget if any
1293        let some_next_event = self.event_timeline.next();
1294
1295        if let Some(next_event) = some_next_event {
1296            match next_event.event_type {
1297                AudioParamEventType::LinearRampToValueAtTime
1298                | AudioParamEventType::ExponentialRampToValueAtTime => {
1299                    // [spec] If the preceding event is a SetTarget
1300                    // event, ๐‘‡0 and ๐‘‰0 are chosen from the current
1301                    // time and value of SetTarget automation. That
1302                    // is, if the SetTarget event has not started,
1303                    // ๐‘‡0 is the start time of the event, and ๐‘‰0
1304                    // is the value just before the SetTarget event
1305                    // starts. In this case, the LinearRampToValue
1306                    // event effectively replaces the SetTarget event.
1307                    // If the SetTarget event has already started,
1308                    // ๐‘‡0 is the current context time, and ๐‘‰0 is
1309                    // the current SetTarget automation value at time ๐‘‡0.
1310                    // In both cases, the automation curve is continuous.
1311                    end_time = infos.block_time;
1312                    ended = true;
1313                }
1314                _ => {
1315                    // For all other events, the SetTarget
1316                    // event ends at the time of the next event.
1317                    if next_event.time < infos.next_block_time {
1318                        end_time = next_event.time;
1319                        ended = true;
1320                    }
1321                }
1322            }
1323        }
1324
1325        // handle CancelAndHoldAtTime
1326        if let Some(cancel_time) = event.cancel_time {
1327            if cancel_time < infos.next_block_time {
1328                end_time = cancel_time;
1329                ended = true;
1330            }
1331        }
1332
1333        let start_time = event.time;
1334        // if SetTarget is the first event registered, we implicitly
1335        // insert a SetValue event just before just as for Ramps.
1336        // Therefore we are sure last_event exists
1337        let start_value = self.last_event.as_ref().unwrap().value;
1338        let end_value = event.value;
1339        let diff = start_value - end_value;
1340        let time_constant = event.time_constant.unwrap();
1341
1342        if infos.is_a_rate {
1343            let start_index = self.buffer.len();
1344            // TODO use ceil() or round() when `end_time` is between two samples?
1345            // <https://github.com/orottier/web-audio-api-rs/pull/460>
1346            let end_index = ((end_time - infos.block_time).max(0.) / infos.dt).round() as usize;
1347            let end_index_clipped = end_index.min(infos.count);
1348
1349            if end_index_clipped > start_index {
1350                let mut time = (start_index as f64).mul_add(infos.dt, infos.block_time);
1351
1352                let mut value = 0.;
1353                for _ in start_index..end_index_clipped {
1354                    // check if we have reached start_time
1355                    value = if time - start_time < 0. {
1356                        self.intrinsic_value
1357                    } else {
1358                        compute_set_target_sample(start_time, time_constant, end_value, diff, time)
1359                    };
1360
1361                    self.buffer.push(value);
1362                    time += infos.dt;
1363                }
1364                self.intrinsic_value = value;
1365            }
1366        }
1367
1368        if !ended {
1369            // compute value for `next_block_time` so that `param.value()`
1370            // stays coherent (see. comment in `AudioParam`)
1371            // allows to properly fill k-rate within next block too
1372            let value = compute_set_target_sample(
1373                start_time,
1374                time_constant,
1375                end_value,
1376                diff,
1377                infos.next_block_time,
1378            );
1379
1380            let diff = (end_value - value).abs();
1381
1382            // abort event if diff is below SNAP_TO_TARGET
1383            if diff < SNAP_TO_TARGET {
1384                self.intrinsic_value = end_value;
1385
1386                // if end_value is zero, the buffer might contain
1387                // subnormals, we need to check that and flush to zero
1388                if end_value == 0. {
1389                    for v in self.buffer.iter_mut() {
1390                        if v.is_subnormal() {
1391                            *v = 0.;
1392                        }
1393                    }
1394                }
1395
1396                let event = AudioParamEvent {
1397                    event_type: AudioParamEventType::SetValueAtTime,
1398                    time: infos.next_block_time,
1399                    value: end_value,
1400                    time_constant: None,
1401                    cancel_time: None,
1402                    duration: None,
1403                    values: None,
1404                };
1405
1406                self.event_timeline.replace_peek(event);
1407            } else {
1408                self.intrinsic_value = value;
1409            }
1410
1411            return true;
1412        }
1413
1414        // setTarget has no "real" end value, compute according
1415        // to next event start time
1416        let value = compute_set_target_sample(start_time, time_constant, end_value, diff, end_time);
1417
1418        self.intrinsic_value = value;
1419        // end_value and end_time must be stored for use
1420        // as start time by next event
1421        let mut event = self.event_timeline.pop().unwrap();
1422        event.time = end_time;
1423        event.value = value;
1424        self.last_event = Some(event);
1425
1426        false
1427    }
1428
1429    fn compute_set_value_curve_automation(&mut self, infos: &BlockInfos) -> bool {
1430        let event = self.event_timeline.peek().unwrap();
1431        let start_time = event.time;
1432        let duration = event.duration.unwrap();
1433        let values = event.values.as_ref().unwrap();
1434        let mut end_time = start_time + duration;
1435
1436        // we must check for the cancel event after we have
1437        // the "real" duration computed to not change the
1438        // slope of the ramp
1439        if let Some(cancel_time) = event.cancel_time {
1440            end_time = cancel_time;
1441        }
1442
1443        if infos.is_a_rate {
1444            let start_index = self.buffer.len();
1445            // TODO use ceil() or round() when `end_time` is between two samples?
1446            // <https://github.com/orottier/web-audio-api-rs/pull/460>
1447            let end_index = ((end_time - infos.block_time).max(0.) / infos.dt).round() as usize;
1448            let end_index_clipped = end_index.min(infos.count);
1449
1450            if end_index_clipped > start_index {
1451                let mut time = (start_index as f64).mul_add(infos.dt, infos.block_time);
1452
1453                let mut value = 0.;
1454                for _ in start_index..end_index_clipped {
1455                    // check if we have reached start_time
1456                    value = if time < start_time {
1457                        self.intrinsic_value
1458                    } else {
1459                        compute_set_value_curve_sample(start_time, duration, values, time)
1460                    };
1461
1462                    self.buffer.push(value);
1463
1464                    time += infos.dt;
1465                }
1466                self.intrinsic_value = value;
1467            }
1468        }
1469
1470        // event will continue in next tick
1471        if end_time >= infos.next_block_time {
1472            // compute value for `next_block_time` so that `param.value()`
1473            // stays coherent (see. comment in `AudioParam`)
1474            // allows to properly fill k-rate within next block too
1475            let value =
1476                compute_set_value_curve_sample(start_time, duration, values, infos.next_block_time);
1477            self.intrinsic_value = value;
1478
1479            return true;
1480        }
1481
1482        // event has been cancelled
1483        if event.cancel_time.is_some() {
1484            let value = compute_set_value_curve_sample(start_time, duration, values, end_time);
1485
1486            self.intrinsic_value = value;
1487
1488            let mut last_event = self.event_timeline.pop().unwrap();
1489            last_event.time = end_time;
1490            last_event.value = value;
1491            self.last_event = Some(last_event);
1492        // event has ended
1493        } else {
1494            let value = values[values.len() - 1];
1495
1496            let mut last_event = self.event_timeline.pop().unwrap();
1497            last_event.time = end_time;
1498            last_event.value = value;
1499
1500            self.intrinsic_value = value;
1501            self.last_event = Some(last_event);
1502        }
1503
1504        false
1505    }
1506
1507    fn compute_buffer(&mut self, block_time: f64, dt: f64, count: usize) {
1508        // Set [[current value]] to the value of paramIntrinsicValue at the
1509        // beginning of this render quantum.
1510        let clamped = self.intrinsic_value.clamp(self.min_value, self.max_value);
1511        self.current_value.store(clamped, Ordering::Release);
1512
1513        // clear the buffer for this block
1514        self.buffer.clear();
1515
1516        let is_a_rate = self.automation_rate.is_a_rate();
1517        let next_block_time = dt.mul_add(count as f64, block_time);
1518
1519        // Check if we can safely return a buffer of length 1 even for a-rate params.
1520        // Several cases allow us to do so:
1521        // - The timeline is empty
1522        // - The timeline is not empty: in such case if `event.time >= next_block_time`
1523        //   AND `event_type` is not `LinearRampToValueAtTime` or `ExponentialRampToValueAtTime`
1524        //   this is safe, i.e.:
1525        //   + For linear and exponential ramps `event.time` is the end time while their
1526        //   start time is `last_event.time`, therefore if `peek()` is of these
1527        //   types, we are in the middle of the ramp.
1528        //   + For all other event, `event.time` is their start time.
1529        //   (@note - `SetTargetAtTime` events also uses `last_event` but only for
1530        //   its value, not for its timing information, so no problem there)
1531        let is_constant_block = match self.event_timeline.peek() {
1532            None => true,
1533            Some(event) => {
1534                if event.event_type != AudioParamEventType::LinearRampToValueAtTime
1535                    && event.event_type != AudioParamEventType::ExponentialRampToValueAtTime
1536                {
1537                    event.time >= next_block_time
1538                } else {
1539                    false
1540                }
1541            }
1542        };
1543
1544        if !is_a_rate || is_constant_block {
1545            self.buffer.push(self.intrinsic_value);
1546            // nothing to compute in timeline, for both k-rate and a-rate
1547            if is_constant_block {
1548                return;
1549            }
1550        }
1551
1552        // pack block infos for automation methods
1553        let block_infos = BlockInfos {
1554            block_time,
1555            dt,
1556            count,
1557            is_a_rate,
1558            next_block_time,
1559        };
1560
1561        loop {
1562            let next_event_type = self.event_timeline.peek().map(|e| e.event_type);
1563
1564            let exit_loop = match next_event_type {
1565                None => {
1566                    if is_a_rate {
1567                        // we use `count` rather then `buffer.remaining_capacity`
1568                        // to correctly handle unit tests where `count` is lower than
1569                        // RENDER_QUANTUM_SIZE
1570                        for _ in self.buffer.len()..count {
1571                            self.buffer.push(self.intrinsic_value);
1572                        }
1573                    }
1574                    true
1575                }
1576                Some(AudioParamEventType::SetValue) | Some(AudioParamEventType::SetValueAtTime) => {
1577                    self.compute_set_value_automation(&block_infos)
1578                }
1579                Some(AudioParamEventType::LinearRampToValueAtTime) => {
1580                    self.compute_linear_ramp_automation(&block_infos)
1581                }
1582                Some(AudioParamEventType::ExponentialRampToValueAtTime) => {
1583                    self.compute_exponential_ramp_automation(&block_infos)
1584                }
1585                Some(AudioParamEventType::SetTargetAtTime) => {
1586                    self.compute_set_target_automation(&block_infos)
1587                }
1588                Some(AudioParamEventType::SetValueCurveAtTime) => {
1589                    self.compute_set_value_curve_automation(&block_infos)
1590                }
1591                _ => panic!(
1592                    "AudioParamEvent {:?} should not appear in AudioParamEventTimeline",
1593                    next_event_type.unwrap()
1594                ),
1595            };
1596
1597            if exit_loop {
1598                break;
1599            }
1600        }
1601    }
1602}
1603
1604pub(crate) fn audio_param_pair(
1605    descriptor: AudioParamDescriptor,
1606    registration: AudioContextRegistration,
1607) -> (AudioParam, AudioParamProcessor) {
1608    let AudioParamDescriptor {
1609        automation_rate,
1610        default_value,
1611        max_value,
1612        min_value,
1613        ..
1614    } = descriptor;
1615
1616    assert_is_finite(default_value);
1617    assert_is_finite(min_value);
1618    assert_is_finite(max_value);
1619    assert!(
1620        min_value <= default_value,
1621        "InvalidStateError - AudioParam minValue should be <= defaultValue"
1622    );
1623    assert!(
1624        default_value <= max_value,
1625        "InvalidStateError - AudioParam defaultValue should be <= maxValue"
1626    );
1627
1628    let current_value = Arc::new(AtomicF32::new(default_value));
1629
1630    let param = AudioParam {
1631        registration: registration.into(),
1632        raw_parts: AudioParamInner {
1633            default_value,
1634            max_value,
1635            min_value,
1636            automation_rate_constrained: false,
1637            automation_rate: Arc::new(Mutex::new(automation_rate)),
1638            current_value: Arc::clone(&current_value),
1639        },
1640    };
1641
1642    let processor = AudioParamProcessor {
1643        intrinsic_value: default_value,
1644        current_value,
1645        default_value,
1646        min_value,
1647        max_value,
1648        automation_rate,
1649        event_timeline: AudioParamEventTimeline::new(),
1650        last_event: None,
1651        buffer: ArrayVec::new(),
1652    };
1653
1654    (param, processor)
1655}
1656
1657#[cfg(test)]
1658mod tests {
1659    use float_eq::assert_float_eq;
1660
1661    use crate::context::{BaseAudioContext, OfflineAudioContext};
1662    use crate::render::Alloc;
1663
1664    use super::*;
1665
1666    #[test]
1667    #[should_panic]
1668    fn test_assert_strictly_positive_fail() {
1669        assert_strictly_positive(0.);
1670    }
1671
1672    #[test]
1673    fn test_assert_strictly_positive() {
1674        assert_strictly_positive(0.1);
1675    }
1676
1677    #[test]
1678    #[should_panic]
1679    fn test_assert_not_zero_fail() {
1680        assert_not_zero(0.);
1681    }
1682
1683    #[test]
1684    fn test_assert_not_zero() {
1685        assert_not_zero(-0.1);
1686        assert_not_zero(0.1);
1687    }
1688
1689    #[test]
1690    #[should_panic]
1691    fn test_assert_sequence_length_fail() {
1692        assert_sequence_length(&[0.; 1]);
1693    }
1694
1695    #[test]
1696    fn test_assert_sequence_length() {
1697        assert_sequence_length(&[0.; 2]);
1698    }
1699
1700    #[test]
1701    fn test_default_and_accessors() {
1702        let context = OfflineAudioContext::new(1, 1, 48000.);
1703
1704        let opts = AudioParamDescriptor {
1705            name: String::new(),
1706            automation_rate: AutomationRate::A,
1707            default_value: 0.,
1708            min_value: -10.,
1709            max_value: 10.,
1710        };
1711        let (param, _render) = audio_param_pair(opts, context.mock_registration());
1712
1713        assert_eq!(param.automation_rate(), AutomationRate::A);
1714        assert_float_eq!(param.default_value(), 0., abs_all <= 0.);
1715        assert_float_eq!(param.min_value(), -10., abs_all <= 0.);
1716        assert_float_eq!(param.max_value(), 10., abs_all <= 0.);
1717        assert_float_eq!(param.value(), 0., abs_all <= 0.);
1718    }
1719
1720    #[test]
1721    fn test_automation_rate_synchronicity_on_control_thread() {
1722        let context = OfflineAudioContext::new(1, 1, 48000.);
1723
1724        let opts = AudioParamDescriptor {
1725            name: String::new(),
1726            automation_rate: AutomationRate::A,
1727            default_value: 0.,
1728            min_value: 0.,
1729            max_value: 1.,
1730        };
1731        let (param, _render) = audio_param_pair(opts, context.mock_registration());
1732
1733        param.set_automation_rate(AutomationRate::K);
1734        assert_eq!(param.automation_rate(), AutomationRate::K);
1735    }
1736
1737    #[test]
1738    fn test_audioparam_clones_in_sync() {
1739        let context = OfflineAudioContext::new(1, 1, 48000.);
1740
1741        let opts = AudioParamDescriptor {
1742            name: String::new(),
1743            automation_rate: AutomationRate::A,
1744            default_value: 0.,
1745            min_value: -10.,
1746            max_value: 10.,
1747        };
1748        let (param1, mut render) = audio_param_pair(opts, context.mock_registration());
1749        let param2 = param1.clone();
1750
1751        // changing automation rate on param1 should reflect in param2
1752        param1.set_automation_rate(AutomationRate::K);
1753        assert_eq!(param2.automation_rate(), AutomationRate::K);
1754
1755        // setting value on param1 should reflect in param2
1756        render.handle_incoming_event(param1.set_value_raw(2.));
1757        assert_float_eq!(param1.value(), 2., abs_all <= 0.);
1758        assert_float_eq!(param2.value(), 2., abs_all <= 0.);
1759
1760        // setting value on param2 should reflect in param1
1761        render.handle_incoming_event(param2.set_value_raw(3.));
1762        assert_float_eq!(param1.value(), 3., abs_all <= 0.);
1763        assert_float_eq!(param2.value(), 3., abs_all <= 0.);
1764    }
1765
1766    #[test]
1767    fn test_set_value() {
1768        {
1769            let context = OfflineAudioContext::new(1, 1, 48000.);
1770
1771            let opts = AudioParamDescriptor {
1772                name: String::new(),
1773                automation_rate: AutomationRate::A,
1774                default_value: 0.,
1775                min_value: -10.,
1776                max_value: 10.,
1777            };
1778            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
1779
1780            render.handle_incoming_event(param.set_value_raw(2.));
1781
1782            assert_float_eq!(param.value(), 2., abs_all <= 0.);
1783
1784            let vs = render.compute_intrinsic_values(0., 1., 10);
1785
1786            assert_float_eq!(param.value(), 2., abs_all <= 0.);
1787            assert_float_eq!(vs, &[2.; 10][..], abs_all <= 0.);
1788        }
1789
1790        // make sure param.value() is properly clamped
1791        {
1792            let context = OfflineAudioContext::new(1, 1, 48000.);
1793
1794            let opts = AudioParamDescriptor {
1795                name: String::new(),
1796                automation_rate: AutomationRate::A,
1797                default_value: 0.,
1798                min_value: 0.,
1799                max_value: 1.,
1800            };
1801            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
1802
1803            render.handle_incoming_event(param.set_value_raw(2.));
1804
1805            assert_float_eq!(param.value(), 1., abs_all <= 0.);
1806
1807            let vs = render.compute_intrinsic_values(0., 1., 10);
1808
1809            // value should clamped while intrinsic value should not
1810            assert_float_eq!(param.value(), 1., abs_all <= 0.);
1811            assert_float_eq!(vs, &[2.; 10][..], abs_all <= 0.);
1812        }
1813    }
1814
1815    #[test]
1816    fn test_steps_a_rate() {
1817        let context = OfflineAudioContext::new(1, 1, 48000.);
1818
1819        {
1820            let opts = AudioParamDescriptor {
1821                name: String::new(),
1822                automation_rate: AutomationRate::A,
1823                default_value: 0.,
1824                min_value: -10.,
1825                max_value: 10.,
1826            };
1827            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
1828
1829            render.handle_incoming_event(param.set_value_at_time_raw(5., 2.0));
1830            render.handle_incoming_event(param.set_value_at_time_raw(12., 8.0)); // should clamp
1831            render.handle_incoming_event(param.set_value_at_time_raw(8., 10.0)); // should not occur 1st run
1832
1833            let vs = render.compute_intrinsic_values(0., 1., 10);
1834            assert_float_eq!(
1835                vs,
1836                &[0., 0., 5., 5., 5., 5., 5., 5., 12., 12.][..],
1837                abs_all <= 0.
1838            );
1839
1840            // no event left in timeline, i.e. length is 1
1841            let vs = render.compute_intrinsic_values(10., 1., 10);
1842            assert_float_eq!(vs, &[8.; 1][..], abs_all <= 0.);
1843        }
1844
1845        {
1846            // events spread on several blocks
1847            let opts = AudioParamDescriptor {
1848                name: String::new(),
1849                automation_rate: AutomationRate::A,
1850                default_value: 0.,
1851                min_value: -10.,
1852                max_value: 10.,
1853            };
1854            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
1855
1856            render.handle_incoming_event(param.set_value_at_time_raw(5., 2.0));
1857            render.handle_incoming_event(param.set_value_at_time_raw(8., 12.0));
1858
1859            let vs = render.compute_intrinsic_values(0., 1., 10);
1860            assert_float_eq!(
1861                vs,
1862                &[0., 0., 5., 5., 5., 5., 5., 5., 5., 5.][..],
1863                abs_all <= 0.
1864            );
1865
1866            let vs = render.compute_intrinsic_values(10., 1., 10);
1867            assert_float_eq!(
1868                vs,
1869                &[5., 5., 8., 8., 8., 8., 8., 8., 8., 8.][..],
1870                abs_all <= 0.
1871            );
1872        }
1873    }
1874
1875    #[test]
1876    fn test_steps_k_rate() {
1877        let context = OfflineAudioContext::new(1, 1, 48000.);
1878        let opts = AudioParamDescriptor {
1879            name: String::new(),
1880            automation_rate: AutomationRate::K,
1881            default_value: 0.,
1882            min_value: -10.,
1883            max_value: 10.,
1884        };
1885        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
1886
1887        render.handle_incoming_event(param.set_value_at_time_raw(5., 2.0));
1888        render.handle_incoming_event(param.set_value_at_time_raw(12., 8.0)); // should not appear in results
1889        render.handle_incoming_event(param.set_value_at_time_raw(8., 10.0)); // should not occur 1st run
1890        render.handle_incoming_event(param.set_value_at_time_raw(3., 14.0)); // should appear in 3rd run
1891
1892        let vs = render.compute_intrinsic_values(0., 1., 10);
1893        assert_float_eq!(vs, &[0.; 1][..], abs_all <= 0.);
1894
1895        let vs = render.compute_intrinsic_values(10., 1., 10);
1896        assert_float_eq!(vs, &[8.; 1][..], abs_all <= 0.);
1897
1898        let vs = render.compute_intrinsic_values(20., 1., 10);
1899        assert_float_eq!(vs, &[3.; 1][..], abs_all <= 0.);
1900    }
1901
1902    #[test]
1903    fn test_linear_ramp_arate() {
1904        let context = OfflineAudioContext::new(1, 1, 48000.);
1905
1906        let opts = AudioParamDescriptor {
1907            name: String::new(),
1908            automation_rate: AutomationRate::A,
1909            default_value: 0.,
1910            min_value: -10.,
1911            max_value: 10.,
1912        };
1913        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
1914
1915        // set to 5 at t = 2
1916        render.handle_incoming_event(param.set_value_at_time_raw(5., 2.0));
1917        // ramp to 8 from t = 2 to t = 5
1918        render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(8.0, 5.0));
1919        // ramp to 0 from t = 5 to t = 13
1920        render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(0., 13.0));
1921
1922        let vs = render.compute_intrinsic_values(0., 1., 10);
1923        assert_float_eq!(
1924            vs,
1925            &[0., 0., 5., 6., 7., 8., 7., 6., 5., 4.][..],
1926            abs_all <= 0.
1927        );
1928    }
1929
1930    #[test]
1931    fn test_linear_ramp_arate_end_of_block() {
1932        let context = OfflineAudioContext::new(1, 1, 48000.);
1933
1934        let opts = AudioParamDescriptor {
1935            name: String::new(),
1936            automation_rate: AutomationRate::A,
1937            default_value: 0.,
1938            min_value: -10.,
1939            max_value: 10.,
1940        };
1941        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
1942
1943        // set to 0 at t = 0
1944        render.handle_incoming_event(param.set_value_at_time_raw(0., 0.));
1945        // ramp to 9 from t = 0 to t = 9
1946        render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(9.0, 9.0));
1947
1948        let vs = render.compute_intrinsic_values(0., 1., 10);
1949        assert_float_eq!(
1950            vs,
1951            &[0., 1., 2., 3., 4., 5., 6., 7., 8., 9.][..],
1952            abs_all <= 0.
1953        );
1954    }
1955
1956    #[test]
1957    // @note - with real params, a set_value event is always used to provide
1958    // init value to the param. Therefore this test is purely theoretical and
1959    // in real world the param would not behave like that, which is wrong
1960    // @todo - open an issue to review how init value is passed (or how last_event is set)
1961    fn test_linear_ramp_arate_implicit_set_value() {
1962        let context = OfflineAudioContext::new(1, 1, 48000.);
1963
1964        let opts = AudioParamDescriptor {
1965            name: String::new(),
1966            automation_rate: AutomationRate::A,
1967            default_value: 0.,
1968            min_value: -10.,
1969            max_value: 10.,
1970        };
1971        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
1972
1973        // mimic a ramp inserted after start
1974        // i.e. setTimeout(() => param.linearRampToValueAtTime(10, now + 10)), 10 * 1000);
1975
1976        // no event in timeline here, i.e. length is 1
1977        let vs = render.compute_intrinsic_values(0., 1., 10);
1978        assert_float_eq!(vs, &[0.; 1][..], abs_all <= 0.);
1979
1980        // implicitly insert a SetValue event at time 10
1981        render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(10.0, 20.0));
1982
1983        let vs = render.compute_intrinsic_values(10., 1., 10);
1984        assert_float_eq!(
1985            vs,
1986            &[0., 1., 2., 3., 4., 5., 6., 7., 8., 9.][..],
1987            abs_all <= 0.
1988        );
1989
1990        // ramp finishes on first value of this block, i.e. length is 10
1991        let vs = render.compute_intrinsic_values(20., 1., 10);
1992        assert_float_eq!(vs, &[10.; 10][..], abs_all <= 0.);
1993    }
1994
1995    #[test]
1996    fn test_linear_ramp_arate_multiple_blocks() {
1997        // regression test for issue #9
1998        let context = OfflineAudioContext::new(1, 1, 48000.);
1999
2000        let opts = AudioParamDescriptor {
2001            name: String::new(),
2002            automation_rate: AutomationRate::A,
2003            default_value: 0.,
2004            min_value: -20.,
2005            max_value: 20.,
2006        };
2007        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2008
2009        // ramp to 20 from t = 0 to t = 20
2010        render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(20.0, 20.0));
2011
2012        // first quantum t = 0..10
2013        let vs = render.compute_intrinsic_values(0., 1., 10);
2014        assert_float_eq!(
2015            vs,
2016            &[0., 1., 2., 3., 4., 5., 6., 7., 8., 9.][..],
2017            abs_all <= 0.
2018        );
2019        assert_float_eq!(param.value(), 0., abs <= 0.);
2020
2021        // next quantum t = 10..20
2022        let vs = render.compute_intrinsic_values(10., 1., 10);
2023        assert_float_eq!(
2024            vs,
2025            &[10., 11., 12., 13., 14., 15., 16., 17., 18., 19.][..],
2026            abs_all <= 0.
2027        );
2028        assert_float_eq!(param.value(), 10., abs <= 0.);
2029
2030        // ramp finished t = 20..30
2031        let vs = render.compute_intrinsic_values(20., 1., 10);
2032        assert_float_eq!(vs, &[20.0; 10][..], abs_all <= 0.);
2033        assert_float_eq!(param.value(), 20., abs <= 0.);
2034    }
2035
2036    #[test]
2037    fn test_linear_ramp_krate_multiple_blocks() {
2038        // regression test for issue #9
2039        let context = OfflineAudioContext::new(1, 1, 48000.);
2040
2041        {
2042            let opts = AudioParamDescriptor {
2043                name: String::new(),
2044                automation_rate: AutomationRate::K,
2045                default_value: 0.,
2046                min_value: -20.,
2047                max_value: 20.,
2048            };
2049            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2050
2051            // ramp to 20 from t = 0 to t = 20
2052            render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(20.0, 20.0));
2053            // first quantum t = 0..10
2054            let vs = render.compute_intrinsic_values(0., 1., 10);
2055            assert_float_eq!(vs, &[0.; 1][..], abs_all <= 0.);
2056            assert_float_eq!(param.value(), 0., abs <= 0.);
2057            // next quantum t = 10..20
2058            let vs = render.compute_intrinsic_values(10., 1., 10);
2059            assert_float_eq!(vs, &[10.; 1][..], abs_all <= 0.);
2060            assert_float_eq!(param.value(), 10., abs <= 0.);
2061            // ramp finished t = 20..30
2062            let vs = render.compute_intrinsic_values(20., 1., 10);
2063            assert_float_eq!(vs, &[20.0; 1][..], abs_all <= 0.);
2064            assert_float_eq!(param.value(), 20., abs <= 0.);
2065        }
2066
2067        {
2068            // finish in the middle of a block
2069            let opts = AudioParamDescriptor {
2070                name: String::new(),
2071                automation_rate: AutomationRate::K,
2072                default_value: 0.,
2073                min_value: -20.,
2074                max_value: 20.,
2075            };
2076            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2077
2078            // ramp to 20 from t = 0 to t = 20
2079            render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(15.0, 15.0));
2080            // first quantum t = 0..10
2081            let vs = render.compute_intrinsic_values(0., 1., 10);
2082            assert_float_eq!(vs, &[0.; 1][..], abs_all <= 0.);
2083            assert_float_eq!(param.value(), 0., abs <= 0.);
2084            // next quantum t = 10..20
2085            let vs = render.compute_intrinsic_values(10., 1., 10);
2086            assert_float_eq!(vs, &[10.; 1][..], abs_all <= 0.);
2087            assert_float_eq!(param.value(), 10., abs <= 0.);
2088            // ramp finished t = 20..30
2089            let vs = render.compute_intrinsic_values(20., 1., 10);
2090            assert_float_eq!(vs, &[15.0; 1][..], abs_all <= 0.);
2091            assert_float_eq!(param.value(), 15., abs <= 0.);
2092        }
2093    }
2094
2095    #[test]
2096    fn test_linear_ramp_start_time() {
2097        let context = OfflineAudioContext::new(1, 1, 48000.);
2098
2099        let opts = AudioParamDescriptor {
2100            name: String::new(),
2101            automation_rate: AutomationRate::A,
2102            default_value: 0.,
2103            min_value: -10.,
2104            max_value: 10.,
2105        };
2106        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2107
2108        render.handle_incoming_event(param.set_value_at_time_raw(1., 0.));
2109        render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(-1., 10.));
2110        let vs = render.compute_intrinsic_values(0., 1., 10);
2111        assert_float_eq!(
2112            vs,
2113            &[1., 0.8, 0.6, 0.4, 0.2, 0., -0.2, -0.4, -0.6, -0.8][..],
2114            abs_all <= 1e-7
2115        );
2116
2117        let vs = render.compute_intrinsic_values(10., 1., 10);
2118        assert_float_eq!(vs, &[-1.; 10][..], abs_all <= 0.);
2119
2120        // start time should be end time of last event, i.e. 10.
2121        render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(1., 30.));
2122
2123        let vs = render.compute_intrinsic_values(20., 1., 10);
2124        assert_float_eq!(
2125            vs,
2126            &[0., 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9][..],
2127            abs_all <= 1e-7
2128        );
2129    }
2130
2131    #[test]
2132    fn test_exponential_ramp_a_rate() {
2133        let context = OfflineAudioContext::new(1, 1, 48000.);
2134
2135        let opts = AudioParamDescriptor {
2136            name: String::new(),
2137            automation_rate: AutomationRate::A,
2138            default_value: 0.,
2139            min_value: 0.,
2140            max_value: 1.,
2141        };
2142        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2143
2144        // set to 0.0001 at t=0 (0. is a special case)
2145        render.handle_incoming_event(param.set_value_at_time_raw(0.0001, 0.));
2146        // ramp to 1 from t = 0 to t = 10
2147        render.handle_incoming_event(param.exponential_ramp_to_value_at_time_raw(1.0, 10.));
2148
2149        // compute resulting buffer:
2150        // v(t) = v1*(v2/v1)^((t-t1)/(t2-t1))
2151        let mut res = Vec::<f32>::with_capacity(10);
2152        let start: f32 = 0.0001;
2153        let end: f32 = 1.;
2154
2155        for t in 0..10 {
2156            let value = start * (end / start).powf(t as f32 / 10.);
2157            res.push(value);
2158        }
2159
2160        let vs = render.compute_intrinsic_values(0., 1., 10);
2161        assert_float_eq!(vs, &res[..], abs_all <= 0.);
2162
2163        let vs = render.compute_intrinsic_values(10., 1., 10);
2164        assert_float_eq!(vs, &[1.0; 10][..], abs_all <= 0.);
2165    }
2166
2167    #[test]
2168    fn test_exponential_ramp_a_rate_multiple_blocks() {
2169        let context = OfflineAudioContext::new(1, 1, 48000.);
2170
2171        let opts = AudioParamDescriptor {
2172            name: String::new(),
2173            automation_rate: AutomationRate::A,
2174            default_value: 0.,
2175            min_value: 0.,
2176            max_value: 1.,
2177        };
2178        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2179
2180        let start: f32 = 0.0001; // use 0.0001 as 0. is a special case
2181        let end: f32 = 1.;
2182        render.handle_incoming_event(param.set_value_at_time_raw(start, 3.));
2183        // ramp to 1 from t = 3. to t = 13.
2184        render.handle_incoming_event(param.exponential_ramp_to_value_at_time_raw(end, 13.));
2185
2186        // compute resulting buffer:
2187        let mut res = vec![0.; 3];
2188        // set_value is implicit here as this is the first value of the computed ramp
2189        // exponential ramp (v(t) = v1*(v2/v1)^((t-t1)/(t2-t1)))
2190        for t in 0..10 {
2191            let value = start * (end / start).powf(t as f32 / 10.);
2192            res.push(value);
2193        }
2194        // fill remaining with target value
2195        res.append(&mut vec![1.; 7]);
2196
2197        let vs = render.compute_intrinsic_values(0., 1., 10);
2198        assert_float_eq!(vs, &res[0..10], abs_all <= 0.);
2199        assert_float_eq!(param.value(), res[0], abs <= 0.);
2200
2201        let vs = render.compute_intrinsic_values(10., 1., 10);
2202        assert_float_eq!(vs, &res[10..20], abs_all <= 0.);
2203        assert_float_eq!(param.value(), res[10], abs <= 0.);
2204    }
2205
2206    #[test]
2207    fn test_exponential_ramp_a_rate_zero_and_opposite_target() {
2208        let context = OfflineAudioContext::new(1, 1, 48000.);
2209
2210        {
2211            // zero target
2212            let opts = AudioParamDescriptor {
2213                name: String::new(),
2214                automation_rate: AutomationRate::A,
2215                default_value: 0.,
2216                min_value: 0.,
2217                max_value: 1.,
2218            };
2219            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2220
2221            // set v=0. at t=0 (0. is a special case)
2222            render.handle_incoming_event(param.set_value_at_time_raw(0., 0.));
2223            // ramp to 1 from t=0 to t=5 -> should behave as a set target at t=5
2224            render.handle_incoming_event(param.exponential_ramp_to_value_at_time_raw(1.0, 5.));
2225
2226            let vs = render.compute_intrinsic_values(0., 1., 10);
2227            assert_float_eq!(
2228                vs,
2229                &[0., 0., 0., 0., 0., 1., 1., 1., 1., 1.][..],
2230                abs_all <= 0.
2231            );
2232        }
2233
2234        {
2235            // opposite signs
2236            let opts = AudioParamDescriptor {
2237                name: String::new(),
2238                automation_rate: AutomationRate::A,
2239                default_value: 0.,
2240                min_value: -1.,
2241                max_value: 1.,
2242            };
2243            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2244
2245            // set v=-1. at t=0
2246            render.handle_incoming_event(param.set_value_at_time_raw(-1., 0.));
2247            // ramp to 1 from t=0 to t=5 -> should behave as a set target at t=5
2248            render.handle_incoming_event(param.exponential_ramp_to_value_at_time_raw(1.0, 5.));
2249
2250            let vs = render.compute_intrinsic_values(0., 1., 10);
2251            assert_float_eq!(
2252                vs,
2253                &[-1., -1., -1., -1., -1., 1., 1., 1., 1., 1.][..],
2254                abs_all <= 0.
2255            );
2256        }
2257    }
2258
2259    #[test]
2260    #[should_panic]
2261    fn test_exponential_ramp_to_zero() {
2262        let context = OfflineAudioContext::new(1, 1, 48000.);
2263
2264        let opts = AudioParamDescriptor {
2265            name: String::new(),
2266            automation_rate: AutomationRate::A,
2267            default_value: 1.,
2268            min_value: 0.,
2269            max_value: 1.,
2270        };
2271        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2272        render.handle_incoming_event(param.exponential_ramp_to_value_at_time_raw(0.0, 10.));
2273    }
2274
2275    #[test]
2276    fn test_exponential_ramp_k_rate_multiple_blocks() {
2277        let context = OfflineAudioContext::new(1, 1, 48000.);
2278
2279        let opts = AudioParamDescriptor {
2280            name: String::new(),
2281            automation_rate: AutomationRate::K,
2282            default_value: 0.,
2283            min_value: 0.,
2284            max_value: 1.,
2285        };
2286        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2287
2288        let start: f32 = 0.0001; // use 0.0001 as 0. is a special case
2289        let end: f32 = 1.;
2290        render.handle_incoming_event(param.set_value_at_time_raw(start, 3.));
2291        // ramp to 1 from t = 3. to t = 13.
2292        render.handle_incoming_event(param.exponential_ramp_to_value_at_time_raw(end, 13.));
2293
2294        // compute resulting buffer:
2295        let mut res = vec![0.; 3];
2296        // set_value is implicit here as this is the first value of the computed ramp
2297        // exponential ramp (v(t) = v1*(v2/v1)^((t-t1)/(t2-t1)))
2298        for t in 0..10 {
2299            let value = start * (end / start).powf(t as f32 / 10.);
2300            res.push(value);
2301        }
2302        // fill remaining with target value
2303        res.append(&mut vec![1.; 7]);
2304
2305        // recreate k-rate blocks from computed values
2306        let vs = render.compute_intrinsic_values(0., 1., 10);
2307        assert_float_eq!(vs, &[res[0]; 1][..], abs_all <= 0.);
2308
2309        let vs = render.compute_intrinsic_values(10., 1., 10);
2310        assert_float_eq!(vs, &[res[10]; 1][..], abs_all <= 0.);
2311
2312        let vs = render.compute_intrinsic_values(20., 1., 10);
2313        assert_float_eq!(vs, &[1.; 1][..], abs_all <= 0.);
2314    }
2315
2316    #[test]
2317    fn test_exponential_ramp_k_rate_zero_and_opposite_target() {
2318        let context = OfflineAudioContext::new(1, 1, 48000.);
2319
2320        {
2321            // zero target
2322            let opts = AudioParamDescriptor {
2323                name: String::new(),
2324                automation_rate: AutomationRate::K,
2325                default_value: 0.,
2326                min_value: 0.,
2327                max_value: 1.,
2328            };
2329            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2330
2331            // ramp to 1 from t=0 to t=5 -> should behave as a set target at t=5
2332            render.handle_incoming_event(param.exponential_ramp_to_value_at_time_raw(1.0, 5.));
2333
2334            let vs = render.compute_intrinsic_values(0., 1., 10);
2335            assert_float_eq!(vs, &[0.; 1][..], abs_all <= 0.);
2336
2337            let vs = render.compute_intrinsic_values(10., 1., 10);
2338            assert_float_eq!(vs, &[1.; 1][..], abs_all <= 0.);
2339        }
2340
2341        {
2342            // opposite signs
2343            let opts = AudioParamDescriptor {
2344                name: String::new(),
2345                automation_rate: AutomationRate::K,
2346                default_value: -1.,
2347                min_value: -1.,
2348                max_value: 1.,
2349            };
2350            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2351
2352            // ramp to 1 from t=0 to t=5 -> should behave as a set target at t=5
2353            render.handle_incoming_event(param.exponential_ramp_to_value_at_time_raw(1.0, 5.));
2354
2355            let vs = render.compute_intrinsic_values(0., 1., 10);
2356            assert_float_eq!(vs, &[-1.; 1][..], abs_all <= 0.);
2357
2358            let vs = render.compute_intrinsic_values(10., 1., 10);
2359            assert_float_eq!(vs, &[1.; 1][..], abs_all <= 0.);
2360        }
2361    }
2362
2363    #[test]
2364    fn test_exponential_ramp_start_time() {
2365        let context = OfflineAudioContext::new(1, 1, 48000.);
2366
2367        let opts = AudioParamDescriptor {
2368            name: String::new(),
2369            automation_rate: AutomationRate::A,
2370            default_value: 0.,
2371            min_value: -10.,
2372            max_value: 10.,
2373        };
2374        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2375
2376        render.handle_incoming_event(param.set_value_at_time_raw(0., 0.));
2377        render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(1., 10.));
2378
2379        let vs = render.compute_intrinsic_values(0., 1., 10);
2380        assert_float_eq!(
2381            vs,
2382            &[0., 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9][..],
2383            abs_all <= 1e-7
2384        );
2385
2386        let vs = render.compute_intrinsic_values(10., 1., 10);
2387        assert_float_eq!(vs, &[1.; 10][..], abs_all <= 0.);
2388
2389        // start time should be end time of last event, i.e. 10.
2390        render.handle_incoming_event(param.exponential_ramp_to_value_at_time_raw(0.0001, 30.));
2391        let vs = render.compute_intrinsic_values(20., 1., 10);
2392        // compute expected on 20 samples, the 10 last ones should be in vs
2393        let start: f32 = 1.;
2394        let end: f32 = 0.0001;
2395        let mut res = [0.; 20];
2396        for (t, v) in res.iter_mut().enumerate() {
2397            *v = start * (end / start).powf(t as f32 / 20.);
2398        }
2399
2400        assert_float_eq!(vs, &res[10..], abs_all <= 1e-7);
2401    }
2402
2403    #[test]
2404    fn test_set_target_at_time_a_rate() {
2405        let context = OfflineAudioContext::new(1, 1, 48000.);
2406
2407        {
2408            let opts = AudioParamDescriptor {
2409                name: String::new(),
2410                automation_rate: AutomationRate::A,
2411                default_value: 0.,
2412                min_value: 0.,
2413                max_value: 1.,
2414            };
2415            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2416            // ๐‘ฃ(๐‘ก) = ๐‘‰1 + (๐‘‰0 โˆ’ ๐‘‰1) * ๐‘’^โˆ’((๐‘กโˆ’๐‘‡0) / ๐œ)
2417            let v0: f32 = 0.;
2418            let v1: f32 = 1.;
2419            let t0: f64 = 0.;
2420            let time_constant: f64 = 1.;
2421
2422            render.handle_incoming_event(param.set_value_at_time_raw(v0, t0));
2423            render.handle_incoming_event(param.set_target_at_time_raw(v1, t0, time_constant));
2424            let vs = render.compute_intrinsic_values(0., 1., 10);
2425
2426            let mut res = Vec::<f32>::with_capacity(10);
2427            for t in 0..10 {
2428                let val = v1 + (v0 - v1) * (-1. * ((t as f64 - t0) / time_constant)).exp() as f32;
2429                res.push(val);
2430            }
2431
2432            assert_float_eq!(vs, &res[..], abs_all <= 0.);
2433        }
2434
2435        {
2436            // implicit SetValue if SetTarget is first event
2437            let opts = AudioParamDescriptor {
2438                name: String::new(),
2439                automation_rate: AutomationRate::A,
2440                default_value: 0.,
2441                min_value: 0.,
2442                max_value: 1.,
2443            };
2444            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2445            // ๐‘ฃ(๐‘ก) = ๐‘‰1 + (๐‘‰0 โˆ’ ๐‘‰1) * ๐‘’^โˆ’((๐‘กโˆ’๐‘‡0) / ๐œ)
2446            let v0: f32 = 0.; // will be implicitly set in param (see default_value)
2447            let v1: f32 = 1.;
2448            let t0: f64 = 0.;
2449            let time_constant: f64 = 1.;
2450
2451            render.handle_incoming_event(param.set_target_at_time_raw(v1, t0, time_constant));
2452            let vs = render.compute_intrinsic_values(0., 1., 10);
2453
2454            let mut res = Vec::<f32>::with_capacity(10);
2455            for t in 0..10 {
2456                let val = v1 + (v0 - v1) * (-1. * ((t as f64 - t0) / time_constant)).exp() as f32;
2457                res.push(val);
2458            }
2459
2460            assert_float_eq!(vs, &res[..], abs_all <= 0.);
2461        }
2462
2463        {
2464            // start later in block with arbitrary values
2465            let opts = AudioParamDescriptor {
2466                name: String::new(),
2467                automation_rate: AutomationRate::A,
2468                default_value: 0.,
2469                min_value: 0.,
2470                max_value: 100.,
2471            };
2472            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2473            // ๐‘ฃ(๐‘ก) = ๐‘‰1 + (๐‘‰0 โˆ’ ๐‘‰1) * ๐‘’^โˆ’((๐‘กโˆ’๐‘‡0) / ๐œ)
2474            let v0: f32 = 1.;
2475            let v1: f32 = 42.;
2476            let t0: f64 = 1.;
2477            let time_constant: f64 = 2.1;
2478
2479            render.handle_incoming_event(param.set_value_at_time_raw(v0, t0));
2480            render.handle_incoming_event(param.set_target_at_time_raw(v1, t0, time_constant));
2481
2482            let mut res = Vec::<f32>::with_capacity(10);
2483            for t in 0..10 {
2484                let val = v1 + (v0 - v1) * (-1. * ((t as f64 - t0) / time_constant)).exp() as f32;
2485                res.push(val);
2486            }
2487            // start_time is 1.
2488            res[0] = 0.;
2489
2490            let vs = render.compute_intrinsic_values(0., 1., 10);
2491            assert_float_eq!(vs, &res[..], abs_all <= 0.);
2492        }
2493
2494        {
2495            // handle time_constant == 0.
2496            let opts = AudioParamDescriptor {
2497                name: String::new(),
2498                automation_rate: AutomationRate::A,
2499                default_value: 0.,
2500                min_value: 0.,
2501                max_value: 100.,
2502            };
2503            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2504            render.handle_incoming_event(param.set_target_at_time_raw(1., 1., 0.));
2505
2506            let mut res = [1.; 10];
2507            res[0] = 0.; // start_time is 1.
2508
2509            let vs = render.compute_intrinsic_values(0., 1., 10);
2510            assert_float_eq!(vs, &res[..], abs_all <= 0.);
2511        }
2512    }
2513
2514    #[test]
2515    fn test_set_target_at_time_a_rate_multiple_blocks() {
2516        let context = OfflineAudioContext::new(1, 1, 48000.);
2517
2518        {
2519            let opts = AudioParamDescriptor {
2520                name: String::new(),
2521                automation_rate: AutomationRate::A,
2522                default_value: 0.,
2523                min_value: 0.,
2524                max_value: 2.,
2525            };
2526            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2527            // ๐‘ฃ(๐‘ก) = ๐‘‰1 + (๐‘‰0 โˆ’ ๐‘‰1) * ๐‘’^โˆ’((๐‘กโˆ’๐‘‡0) / ๐œ)
2528            let v0: f32 = 0.;
2529            let v1: f32 = 2.;
2530            let t0: f64 = 0.;
2531            let time_constant: f64 = 1.;
2532            // ramp to 1 from t=0 to t=5 -> should behave as a set target at t=5
2533            render.handle_incoming_event(param.set_value_at_time_raw(v0, t0));
2534            render.handle_incoming_event(param.set_target_at_time_raw(v1, t0, time_constant));
2535
2536            let mut res = Vec::<f32>::with_capacity(20);
2537            for t in 0..20 {
2538                let val = v1 + (v0 - v1) * (-1. * ((t as f64 - t0) / time_constant)).exp() as f32;
2539                res.push(val);
2540            }
2541
2542            let vs = render.compute_intrinsic_values(0., 1., 10);
2543            assert_float_eq!(vs, &res[0..10], abs_all <= 0.);
2544
2545            let vs = render.compute_intrinsic_values(10., 1., 10);
2546            assert_float_eq!(vs, &res[10..20], abs_all <= 0.);
2547        }
2548    }
2549
2550    #[test]
2551    fn test_set_target_at_time_a_rate_followed_by_set_value() {
2552        let context = OfflineAudioContext::new(1, 1, 48000.);
2553
2554        {
2555            let opts = AudioParamDescriptor {
2556                name: String::new(),
2557                automation_rate: AutomationRate::A,
2558                default_value: 0.,
2559                min_value: 0.,
2560                max_value: 2.,
2561            };
2562            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2563            // ๐‘ฃ(๐‘ก) = ๐‘‰1 + (๐‘‰0 โˆ’ ๐‘‰1) * ๐‘’^โˆ’((๐‘กโˆ’๐‘‡0) / ๐œ)
2564            let v0: f32 = 0.;
2565            let v1: f32 = 2.;
2566            let t0: f64 = 0.;
2567            let time_constant: f64 = 1.;
2568
2569            render.handle_incoming_event(param.set_value_at_time_raw(v0, t0));
2570            render.handle_incoming_event(param.set_target_at_time_raw(v1, t0, time_constant));
2571            render.handle_incoming_event(param.set_value_at_time_raw(0.5, 15.));
2572
2573            let mut res = Vec::<f32>::with_capacity(20);
2574
2575            for t in 0..15 {
2576                let val = v1 + (v0 - v1) * (-1. * ((t as f64 - t0) / time_constant)).exp() as f32;
2577                res.push(val);
2578            }
2579
2580            res.resize(20, 0.5);
2581
2582            let vs = render.compute_intrinsic_values(0., 1., 10);
2583            assert_float_eq!(vs, &res[0..10], abs_all <= 0.);
2584
2585            let vs = render.compute_intrinsic_values(10., 1., 10);
2586            assert_float_eq!(vs, &res[10..20], abs_all <= 0.);
2587        }
2588    }
2589
2590    #[test]
2591    fn test_set_target_at_time_ends_at_threshold() {
2592        let context = OfflineAudioContext::new(1, 1, 48000.);
2593        let opts = AudioParamDescriptor {
2594            name: String::new(),
2595            automation_rate: AutomationRate::A,
2596            default_value: 0.,
2597            min_value: 0.,
2598            max_value: 2.,
2599        };
2600        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2601
2602        render.handle_incoming_event(param.set_value_at_time_raw(1., 0.));
2603        render.handle_incoming_event(param.set_target_at_time_raw(0., 1., 0.2));
2604
2605        let vs = render.compute_intrinsic_values(0., 1., 128);
2606        for v in vs.iter() {
2607            assert!(!v.is_subnormal());
2608        }
2609
2610        // check peek() has been replaced with set_value event
2611        let peek = render.event_timeline.peek();
2612        assert_eq!(
2613            peek.unwrap().event_type,
2614            AudioParamEventType::SetValueAtTime
2615        );
2616
2617        // this buffer should be filled with target values
2618        let vs = render.compute_intrinsic_values(10., 1., 128);
2619        assert_float_eq!(vs[..], [0.; 128], abs_all <= 0.);
2620    }
2621
2622    #[test]
2623    fn test_set_target_at_time_waits_for_start_time() {
2624        let context = OfflineAudioContext::new(1, 1, 48000.);
2625        let opts = AudioParamDescriptor {
2626            name: String::new(),
2627            automation_rate: AutomationRate::A,
2628            default_value: 0.,
2629            min_value: 0.,
2630            max_value: 2.,
2631        };
2632        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2633
2634        render.handle_incoming_event(param.set_value_at_time_raw(1., 0.));
2635        render.handle_incoming_event(param.set_target_at_time_raw(0., 5., 1.));
2636
2637        let vs = render.compute_intrinsic_values(0., 1., 10);
2638        assert_float_eq!(vs[0], 1., abs <= 0.);
2639        assert_float_eq!(vs[1], 1., abs <= 0.);
2640        assert_float_eq!(vs[2], 1., abs <= 0.);
2641        assert_float_eq!(vs[3], 1., abs <= 0.);
2642        assert_float_eq!(vs[4], 1., abs <= 0.);
2643        assert_float_eq!(vs[5], 1., abs <= 0.);
2644    }
2645
2646    #[test]
2647    fn test_set_target_at_time_a_rate_followed_by_ramp() {
2648        let context = OfflineAudioContext::new(1, 1, 48000.);
2649        {
2650            let opts = AudioParamDescriptor {
2651                name: String::new(),
2652                automation_rate: AutomationRate::A,
2653                default_value: 0.,
2654                min_value: 0.,
2655                max_value: 10.,
2656            };
2657            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2658            // ๐‘ฃ(๐‘ก) = ๐‘‰1 + (๐‘‰0 โˆ’ ๐‘‰1) * ๐‘’^โˆ’((๐‘กโˆ’๐‘‡0) / ๐œ)
2659            let v0: f32 = 0.;
2660            let v1: f32 = 2.;
2661            let t0: f64 = 0.;
2662            let time_constant: f64 = 10.;
2663
2664            render.handle_incoming_event(param.set_value_at_time_raw(v0, t0));
2665            render.handle_incoming_event(param.set_target_at_time_raw(v1, t0, time_constant));
2666
2667            let mut res = Vec::<f32>::with_capacity(20);
2668
2669            for t in 0..11 {
2670                // we compute the 10th elements as it will be the start value of the ramp
2671                let val = v1 + (v0 - v1) * (-1. * ((t as f64 - t0) / time_constant)).exp() as f32;
2672                res.push(val);
2673            }
2674
2675            let vs = render.compute_intrinsic_values(0., 1., 10);
2676            assert_float_eq!(vs, &res[0..10], abs_all <= 0.);
2677
2678            // ramp
2679            let v0 = res.pop().unwrap(); // v0 is defined by the SetTarget
2680            let v1 = 10.;
2681            let t0 = 10.;
2682            let t1 = 20.;
2683
2684            render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(v1, t1));
2685
2686            for t in 10..20 {
2687                let time = t as f64;
2688                let value = v0 + (v1 - v0) * (time - t0) as f32 / (t1 - t0) as f32;
2689                res.push(value);
2690            }
2691
2692            let vs = render.compute_intrinsic_values(10., 1., 10);
2693            assert_float_eq!(vs, &res[10..20], abs_all <= 1.0e-6);
2694            // ramp ended
2695            let vs = render.compute_intrinsic_values(20., 1., 10);
2696            assert_float_eq!(vs, &[v1; 10][..], abs_all <= 0.);
2697        }
2698    }
2699
2700    #[test]
2701    fn test_set_target_at_time_k_rate_multiple_blocks() {
2702        let context = OfflineAudioContext::new(1, 1, 48000.);
2703
2704        {
2705            let opts = AudioParamDescriptor {
2706                name: String::new(),
2707                automation_rate: AutomationRate::K,
2708                default_value: 0.,
2709                min_value: 0.,
2710                max_value: 2.,
2711            };
2712            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2713            // ๐‘ฃ(๐‘ก) = ๐‘‰1 + (๐‘‰0 โˆ’ ๐‘‰1) * ๐‘’^โˆ’((๐‘กโˆ’๐‘‡0) / ๐œ)
2714            let v0: f32 = 0.;
2715            let v1: f32 = 2.;
2716            let t0: f64 = 0.;
2717            let time_constant: f64 = 1.;
2718            // ramp to 1 from t=0 to t=5 -> should behave as a set target at t=5
2719            render.handle_incoming_event(param.set_value_at_time_raw(v0, t0));
2720            render.handle_incoming_event(param.set_target_at_time_raw(v1, t0, time_constant));
2721
2722            let mut res = Vec::<f32>::with_capacity(20);
2723            for t in 0..20 {
2724                let val = v1 + (v0 - v1) * (-1. * ((t as f64 - t0) / time_constant)).exp() as f32;
2725                res.push(val);
2726            }
2727
2728            let vs = render.compute_intrinsic_values(0., 1., 10);
2729            assert_float_eq!(vs, &[res[0]; 1][..], abs_all <= 0.);
2730
2731            let vs = render.compute_intrinsic_values(10., 1., 10);
2732            assert_float_eq!(vs, &[res[10]; 1][..], abs_all <= 0.);
2733        }
2734    }
2735
2736    #[test]
2737    // regression test for bcebfe6
2738    fn test_set_target_at_time_snap_to_value() {
2739        let context = OfflineAudioContext::new(1, 1, 48000.);
2740        let opts = AudioParamDescriptor {
2741            name: String::new(),
2742            automation_rate: AutomationRate::A,
2743            default_value: 0.,
2744            min_value: 0.,
2745            max_value: 1.,
2746        };
2747        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2748        let v0: f32 = 1.;
2749        let v1: f32 = 0.;
2750        let t0: f64 = 0.;
2751        let time_constant: f64 = 1.;
2752
2753        render.handle_incoming_event(param.set_value_at_time_raw(v0, t0));
2754        render.handle_incoming_event(param.set_target_at_time_raw(v1, t0, time_constant));
2755
2756        let mut res = [0.; 30];
2757        // ๐‘ฃ(๐‘ก) = ๐‘‰1 + (๐‘‰0 โˆ’ ๐‘‰1) * ๐‘’^โˆ’((๐‘กโˆ’๐‘‡0) / ๐œ)
2758        res.iter_mut().enumerate().for_each(|(t, r)| {
2759            *r = v1 + (v0 - v1) * (-1. * ((t as f64 - t0) / time_constant)).exp() as f32;
2760        });
2761
2762        let vs = render.compute_intrinsic_values(0., 1., 10);
2763        assert_float_eq!(vs, &res[..10], abs_all <= 0.);
2764
2765        let vs = render.compute_intrinsic_values(10., 1., 10);
2766        assert_float_eq!(vs, &res[10..20], abs_all <= 0.);
2767
2768        // the distance between the target value and the value just after this block
2769        // is smaller than SNAP_TO_TARGET (i.e. 1e-10)
2770        let vs = render.compute_intrinsic_values(20., 1., 10);
2771        assert_float_eq!(vs, &res[20..30], abs_all <= 0.);
2772
2773        // then this block should be [0.; 10]
2774        let vs = render.compute_intrinsic_values(30., 1., 10);
2775        assert_float_eq!(vs, &[0.; 10][..], abs_all <= 0.);
2776    }
2777
2778    #[test]
2779    fn test_cancel_scheduled_values() {
2780        let context = OfflineAudioContext::new(1, 1, 48000.);
2781
2782        let opts = AudioParamDescriptor {
2783            name: String::new(),
2784            automation_rate: AutomationRate::A,
2785            default_value: 0.,
2786            min_value: 0.,
2787            max_value: 10.,
2788        };
2789        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2790        for t in 0..10 {
2791            render.handle_incoming_event(param.set_value_at_time_raw(t as f32, t as f64));
2792        }
2793
2794        render.handle_incoming_event(param.cancel_scheduled_values_raw(5.));
2795
2796        let vs = render.compute_intrinsic_values(0., 1., 10);
2797        assert_float_eq!(
2798            vs,
2799            &[0., 1., 2., 3., 4., 4., 4., 4., 4., 4.][..],
2800            abs_all <= 0.
2801        );
2802    }
2803
2804    #[test]
2805    fn test_cancel_scheduled_values_ramp() {
2806        let context = OfflineAudioContext::new(1, 1, 48000.);
2807
2808        {
2809            let opts = AudioParamDescriptor {
2810                name: String::new(),
2811                automation_rate: AutomationRate::A,
2812                default_value: 0.,
2813                min_value: 0.,
2814                max_value: 10.,
2815            };
2816            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2817
2818            render.handle_incoming_event(param.set_value_at_time_raw(0., 0.));
2819            render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(10., 10.));
2820            // cancels the ramp, the set value event is kept in timeline
2821            render.handle_incoming_event(param.cancel_scheduled_values_raw(10.));
2822
2823            let vs = render.compute_intrinsic_values(0., 1., 10);
2824            assert_float_eq!(vs, &[0.; 10][..], abs_all <= 0.);
2825        }
2826
2827        // ramp already started, go back to previous value
2828        {
2829            let opts = AudioParamDescriptor {
2830                name: String::new(),
2831                automation_rate: AutomationRate::A,
2832                default_value: 0.,
2833                min_value: 0.,
2834                max_value: 20.,
2835            };
2836            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2837
2838            render.handle_incoming_event(param.set_value_at_time_raw(0., 0.));
2839            render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(20., 20.));
2840
2841            let vs = render.compute_intrinsic_values(0., 1., 10);
2842            assert_float_eq!(
2843                vs,
2844                &[0., 1., 2., 3., 4., 5., 6., 7., 8., 9.][..],
2845                abs_all <= 0.
2846            );
2847
2848            // the SetValue event has been consumed in first tick and the ramp
2849            // is removed from timeline, no event left in timeline (length is 1)
2850            render.handle_incoming_event(param.cancel_scheduled_values_raw(10.));
2851
2852            let vs = render.compute_intrinsic_values(10., 1., 10);
2853            assert_float_eq!(vs, &[0.; 1][..], abs_all <= 0.);
2854        }
2855
2856        // make sure we can't go into a situation where next_event is a ramp
2857        // and last_event is not defined
2858        // @see - note in CancelScheduledValues insertion in timeline
2859        {
2860            let opts = AudioParamDescriptor {
2861                name: String::new(),
2862                automation_rate: AutomationRate::A,
2863                default_value: 0.,
2864                min_value: 0.,
2865                max_value: 10.,
2866            };
2867            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2868
2869            // the SetValue from param inserted by the Ramp is left in timeline
2870            // i.e. length is 10
2871            render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(10., 10.));
2872            render.handle_incoming_event(param.cancel_scheduled_values_raw(10.)); // cancels the ramp
2873
2874            let vs = render.compute_intrinsic_values(0., 1., 10);
2875            assert_float_eq!(vs, &[0.; 10][..], abs_all <= 0.);
2876        }
2877
2878        {
2879            let opts = AudioParamDescriptor {
2880                name: String::new(),
2881                automation_rate: AutomationRate::A,
2882                default_value: 0.,
2883                min_value: 0.,
2884                max_value: 20.,
2885            };
2886            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2887
2888            render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(20., 20.));
2889
2890            let vs = render.compute_intrinsic_values(0., 1., 10);
2891            assert_float_eq!(
2892                vs,
2893                &[0., 1., 2., 3., 4., 5., 6., 7., 8., 9.][..],
2894                abs_all <= 0.
2895            );
2896
2897            // ramp is removed from timeline, no event left
2898            render.handle_incoming_event(param.cancel_scheduled_values_raw(10.));
2899
2900            let vs = render.compute_intrinsic_values(10., 1., 10);
2901            assert_float_eq!(vs, &[0.; 1][..], abs_all <= 0.);
2902        }
2903    }
2904
2905    #[test]
2906    fn test_cancel_and_hold() {
2907        let context = OfflineAudioContext::new(1, 1, 48000.);
2908        {
2909            let opts = AudioParamDescriptor {
2910                name: String::new(),
2911                automation_rate: AutomationRate::A,
2912                default_value: 0.,
2913                min_value: 0.,
2914                max_value: 10.,
2915            };
2916            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2917
2918            render.handle_incoming_event(param.set_value_at_time_raw(1., 1.));
2919            render.handle_incoming_event(param.set_value_at_time_raw(2., 2.));
2920            render.handle_incoming_event(param.set_value_at_time_raw(3., 3.));
2921            render.handle_incoming_event(param.set_value_at_time_raw(4., 4.));
2922            render.handle_incoming_event(param.cancel_and_hold_at_time_raw(2.5));
2923
2924            let vs = render.compute_intrinsic_values(0., 1., 10);
2925            assert_float_eq!(
2926                vs,
2927                &[0., 1., 2., 2., 2., 2., 2., 2., 2., 2.][0..10],
2928                abs_all <= 0.
2929            );
2930        }
2931    }
2932
2933    #[test]
2934    fn test_cancel_and_hold_during_set_target() {
2935        let context = OfflineAudioContext::new(1, 1, 48000.);
2936
2937        {
2938            let opts = AudioParamDescriptor {
2939                name: String::new(),
2940                automation_rate: AutomationRate::A,
2941                default_value: 0.,
2942                min_value: 0.,
2943                max_value: 2.,
2944            };
2945            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2946            // ๐‘ฃ(๐‘ก) = ๐‘‰1 + (๐‘‰0 โˆ’ ๐‘‰1) * ๐‘’^โˆ’((๐‘กโˆ’๐‘‡0) / ๐œ)
2947            let v0: f32 = 0.;
2948            let v1: f32 = 2.;
2949            let t0: f64 = 0.;
2950            let time_constant: f64 = 1.;
2951
2952            render.handle_incoming_event(param.set_value_at_time_raw(v0, t0));
2953            render.handle_incoming_event(param.set_target_at_time_raw(v1, t0, time_constant));
2954            render.handle_incoming_event(param.cancel_and_hold_at_time_raw(15.));
2955
2956            let mut res = Vec::<f32>::with_capacity(20);
2957
2958            // compute index 15 to have hold_value
2959            for t in 0..16 {
2960                let val = v1 + (v0 - v1) * (-1. * ((t as f64 - t0) / time_constant)).exp() as f32;
2961                res.push(val);
2962            }
2963
2964            let hold_value = res.pop().unwrap();
2965            res.resize(20, hold_value);
2966
2967            let vs = render.compute_intrinsic_values(0., 1., 10);
2968            assert_float_eq!(vs, &res[0..10], abs_all <= 0.);
2969
2970            let vs = render.compute_intrinsic_values(10., 1., 10);
2971            assert_float_eq!(vs, &res[10..20], abs_all <= 0.);
2972        }
2973    }
2974
2975    #[test]
2976    fn test_cancel_and_hold_during_linear_ramp() {
2977        let context = OfflineAudioContext::new(1, 1, 48000.);
2978
2979        {
2980            let opts = AudioParamDescriptor {
2981                name: String::new(),
2982                automation_rate: AutomationRate::A,
2983                default_value: 0.,
2984                min_value: 0.,
2985                max_value: 10.,
2986            };
2987            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2988
2989            render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(10., 10.));
2990            render.handle_incoming_event(param.cancel_and_hold_at_time_raw(5.));
2991
2992            let vs = render.compute_intrinsic_values(0., 1., 10);
2993            assert_float_eq!(
2994                vs,
2995                &[0., 1., 2., 3., 4., 5., 5., 5., 5., 5.][0..10],
2996                abs_all <= 0.
2997            );
2998        }
2999
3000        {
3001            // cancel between two samples
3002            let opts = AudioParamDescriptor {
3003                name: String::new(),
3004                automation_rate: AutomationRate::A,
3005                default_value: 0.,
3006                min_value: 0.,
3007                max_value: 10.,
3008            };
3009            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
3010
3011            render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(10., 10.));
3012            render.handle_incoming_event(param.cancel_and_hold_at_time_raw(4.5));
3013
3014            let vs = render.compute_intrinsic_values(0., 1., 10);
3015            assert_float_eq!(
3016                vs,
3017                &[0., 1., 2., 3., 4., 4.5, 4.5, 4.5, 4.5, 4.5][0..10],
3018                abs_all <= 0.
3019            );
3020        }
3021    }
3022
3023    #[test]
3024    fn test_cancel_and_hold_during_exponential_ramp() {
3025        let context = OfflineAudioContext::new(1, 1, 48000.);
3026
3027        {
3028            let opts = AudioParamDescriptor {
3029                name: String::new(),
3030                automation_rate: AutomationRate::A,
3031                default_value: 0.,
3032                min_value: 0.,
3033                max_value: 10.,
3034            };
3035            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
3036
3037            // set to 0.0001 at t=0 (0. is a special case)
3038            render.handle_incoming_event(param.set_value_at_time_raw(0.0001, 0.));
3039            render.handle_incoming_event(param.exponential_ramp_to_value_at_time_raw(1.0, 10.));
3040            render.handle_incoming_event(param.cancel_and_hold_at_time_raw(5.));
3041
3042            // compute resulting buffer:
3043            // v(t) = v1*(v2/v1)^((t-t1)/(t2-t1))
3044            let mut res = Vec::<f32>::with_capacity(10);
3045            let start: f32 = 0.0001;
3046            let end: f32 = 1.;
3047
3048            for t in 0..6 {
3049                let value = start * (end / start).powf(t as f32 / 10.);
3050                res.push(value);
3051            }
3052
3053            let hold_value = res.pop().unwrap();
3054            res.resize(10, hold_value);
3055
3056            let vs = render.compute_intrinsic_values(0., 1., 10);
3057            assert_float_eq!(vs, &res[..], abs_all <= 0.);
3058        }
3059
3060        {
3061            // cancel between 2 samples
3062            let opts = AudioParamDescriptor {
3063                name: String::new(),
3064                automation_rate: AutomationRate::A,
3065                default_value: 0.,
3066                min_value: 0.,
3067                max_value: 10.,
3068            };
3069            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
3070
3071            // set to 0.0001 at t=0 (0. is a special case)
3072            render.handle_incoming_event(param.set_value_at_time_raw(0.0001, 0.));
3073            render.handle_incoming_event(param.exponential_ramp_to_value_at_time_raw(1.0, 10.));
3074            render.handle_incoming_event(param.cancel_and_hold_at_time_raw(4.5));
3075
3076            // compute resulting buffer:
3077            // v(t) = v1*(v2/v1)^((t-t1)/(t2-t1))
3078            let mut res = Vec::<f32>::with_capacity(10);
3079            let start: f32 = 0.0001;
3080            let end: f32 = 1.;
3081
3082            for t in 0..5 {
3083                let value = start * (end / start).powf(t as f32 / 10.);
3084                res.push(value);
3085            }
3086
3087            let hold_value = start * (end / start).powf(4.5 / 10.);
3088            res.resize(10, hold_value);
3089
3090            let vs = render.compute_intrinsic_values(0., 1., 10);
3091            assert_float_eq!(vs, &res[..], abs_all <= 0.);
3092        }
3093    }
3094
3095    #[test]
3096    fn test_cancel_and_hold_during_set_value_curve() {
3097        let context = OfflineAudioContext::new(1, 1, 48000.);
3098
3099        {
3100            let opts = AudioParamDescriptor {
3101                name: String::new(),
3102                automation_rate: AutomationRate::A,
3103                default_value: 0.,
3104                min_value: 0.,
3105                max_value: 2.,
3106            };
3107            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
3108
3109            let curve = [0., 0.5, 1., 0.5, 0.];
3110            render.handle_incoming_event(param.set_value_curve_at_time_raw(&curve[..], 0., 10.));
3111            render.handle_incoming_event(param.cancel_and_hold_at_time_raw(5.));
3112
3113            let vs = render.compute_intrinsic_values(0., 1., 10);
3114            assert_float_eq!(
3115                vs,
3116                &[0., 0.2, 0.4, 0.6, 0.8, 1., 1., 1., 1., 1.][..],
3117                abs_all <= 1e-7
3118            );
3119        }
3120
3121        {
3122            // sub-sample
3123            let opts = AudioParamDescriptor {
3124                name: String::new(),
3125                automation_rate: AutomationRate::A,
3126                default_value: 0.,
3127                min_value: 0.,
3128                max_value: 2.,
3129            };
3130            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
3131
3132            let curve = [0., 0.5, 1., 0.5, 0.];
3133            render.handle_incoming_event(param.set_value_curve_at_time_raw(&curve[..], 0., 10.));
3134            render.handle_incoming_event(param.cancel_and_hold_at_time_raw(4.5));
3135
3136            let vs = render.compute_intrinsic_values(0., 1., 10);
3137            assert_float_eq!(
3138                vs,
3139                &[0., 0.2, 0.4, 0.6, 0.8, 0.9, 0.9, 0.9, 0.9, 0.9][..],
3140                abs_all <= 1e-7
3141            );
3142        }
3143    }
3144
3145    #[test]
3146    fn test_set_value_curve_at_time_a_rate() {
3147        let context = OfflineAudioContext::new(1, 1, 48000.);
3148
3149        let opts = AudioParamDescriptor {
3150            name: String::new(),
3151            automation_rate: AutomationRate::A,
3152            default_value: 0.,
3153            min_value: 0.,
3154            max_value: 10.,
3155        };
3156        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
3157
3158        // set to 0.0001 at t=0 (0. is a special case)
3159        let curve = [0., 0.5, 1., 0.5, 0.];
3160        render.handle_incoming_event(param.set_value_curve_at_time_raw(&curve[..], 0., 10.));
3161
3162        let vs = render.compute_intrinsic_values(0., 1., 10);
3163        assert_float_eq!(
3164            vs,
3165            &[0., 0.2, 0.4, 0.6, 0.8, 1., 0.8, 0.6, 0.4, 0.2][..],
3166            abs_all <= 1e-7
3167        );
3168
3169        let vs = render.compute_intrinsic_values(10., 1., 10);
3170        assert_float_eq!(vs, &[0.; 10][..], abs_all <= 0.);
3171    }
3172
3173    #[test]
3174    fn test_set_value_curve_at_time_a_rate_multiple_frames() {
3175        let context = OfflineAudioContext::new(1, 1, 48000.);
3176
3177        let opts = AudioParamDescriptor {
3178            name: String::new(),
3179            automation_rate: AutomationRate::A,
3180            default_value: 0.,
3181            min_value: 0.,
3182            max_value: 10.,
3183        };
3184        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
3185
3186        // set to 0.0001 at t=0 (0. is a special case)
3187        let curve = [0., 0.5, 1., 0.5, 0.];
3188        render.handle_incoming_event(param.set_value_curve_at_time_raw(&curve[..], 0., 20.));
3189
3190        let vs = render.compute_intrinsic_values(0., 1., 10);
3191        assert_float_eq!(
3192            vs,
3193            &[0., 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9][..],
3194            abs_all <= 1e-7
3195        );
3196
3197        let vs = render.compute_intrinsic_values(10., 1., 10);
3198        assert_float_eq!(
3199            vs,
3200            &[1., 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1][..],
3201            abs_all <= 1e-7
3202        );
3203
3204        let vs = render.compute_intrinsic_values(20., 1., 10);
3205        assert_float_eq!(vs, &[0.; 10][..], abs_all <= 0.);
3206    }
3207
3208    #[test]
3209    #[should_panic]
3210    fn test_set_value_curve_at_time_insert_while_another_event() {
3211        let context = OfflineAudioContext::new(1, 1, 48000.);
3212
3213        let opts = AudioParamDescriptor {
3214            name: String::new(),
3215            automation_rate: AutomationRate::A,
3216            default_value: 1.,
3217            min_value: 0.,
3218            max_value: 1.,
3219        };
3220        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
3221
3222        render.handle_incoming_event(param.set_value_at_time_raw(0.0, 5.));
3223
3224        let curve = [0., 0.5, 1., 0.5, 0.];
3225        render.handle_incoming_event(param.set_value_curve_at_time_raw(&curve[..], 0., 10.));
3226        // this is necessary as the panic is triggered in the audio thread
3227        // @note - argues in favor of maintaining the queue in control thread
3228        let _vs = render.compute_intrinsic_values(0., 1., 10);
3229    }
3230
3231    #[test]
3232    #[should_panic]
3233    fn test_set_value_curve_at_time_insert_another_event_inside() {
3234        let context = OfflineAudioContext::new(1, 1, 48000.);
3235
3236        let opts = AudioParamDescriptor {
3237            name: String::new(),
3238            automation_rate: AutomationRate::A,
3239            default_value: 1.,
3240            min_value: 0.,
3241            max_value: 1.,
3242        };
3243        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
3244
3245        let curve = [0., 0.5, 1., 0.5, 0.];
3246        render.handle_incoming_event(param.set_value_curve_at_time_raw(&curve[..], 0., 10.));
3247        render.handle_incoming_event(param.set_value_at_time_raw(0.0, 5.));
3248        // this is necessary as the panic is triggered in the audio thread
3249        // @note - argues in favor of maintaining the queue in control thread
3250        let _vs = render.compute_intrinsic_values(0., 1., 10);
3251    }
3252
3253    #[test]
3254    fn test_set_value_curve_waits_for_start_time() {
3255        let context = OfflineAudioContext::new(1, 1, 48000.);
3256
3257        let opts = AudioParamDescriptor {
3258            name: String::new(),
3259            automation_rate: AutomationRate::A,
3260            default_value: 0.,
3261            min_value: 0.,
3262            max_value: 10.,
3263        };
3264        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
3265
3266        // set to 0.0001 at t=0 (0. is a special case)
3267        let curve = [0., 0.5, 1., 0.5, 0.];
3268        render.handle_incoming_event(param.set_value_curve_at_time_raw(&curve[..], 5., 10.));
3269
3270        let vs = render.compute_intrinsic_values(0., 1., 10);
3271        assert_float_eq!(
3272            vs,
3273            &[0., 0., 0., 0., 0., 0., 0.2, 0.4, 0.6, 0.8][..],
3274            abs_all <= 0.
3275        );
3276    }
3277
3278    #[test]
3279    fn test_update_automation_rate_to_k() {
3280        let context = OfflineAudioContext::new(1, 1, 48000.);
3281
3282        let opts = AudioParamDescriptor {
3283            name: String::new(),
3284            automation_rate: AutomationRate::A,
3285            default_value: 0.,
3286            min_value: -10.,
3287            max_value: 10.,
3288        };
3289        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
3290
3291        render.onmessage(&mut AutomationRate::K);
3292        render.handle_incoming_event(param.set_value_at_time_raw(2., 0.000001));
3293
3294        let vs = render.compute_intrinsic_values(0., 1., 10);
3295        assert_float_eq!(vs, &[0.; 1][..], abs_all <= 0.);
3296    }
3297
3298    #[test]
3299    fn test_update_automation_rate_to_a() {
3300        let context = OfflineAudioContext::new(1, 1, 48000.);
3301
3302        let opts = AudioParamDescriptor {
3303            name: String::new(),
3304            automation_rate: AutomationRate::K,
3305            default_value: 0.,
3306            min_value: -10.,
3307            max_value: 10.,
3308        };
3309        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
3310
3311        render.onmessage(&mut AutomationRate::A);
3312        render.handle_incoming_event(param.set_value_at_time_raw(2., 0.000001));
3313
3314        let vs = render.compute_intrinsic_values(0., 1., 10);
3315        assert_float_eq!(vs, &[2.; 10][..], abs_all <= 0.);
3316    }
3317
3318    #[test]
3319    fn test_varying_param_size() {
3320        // event registered online during rendering
3321        {
3322            let context = OfflineAudioContext::new(1, 1, 48000.);
3323
3324            let opts = AudioParamDescriptor {
3325                name: String::new(),
3326                automation_rate: AutomationRate::A,
3327                default_value: 0.,
3328                min_value: 0.,
3329                max_value: 10.,
3330            };
3331            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
3332
3333            render.handle_incoming_event(param.set_value_at_time_raw(0., 0.));
3334            render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(9., 9.));
3335
3336            // first block should be length 10 (128 in real world)
3337            let vs = render.compute_intrinsic_values(0., 1., 10);
3338            let expected = [0., 1., 2., 3., 4., 5., 6., 7., 8., 9.];
3339            assert_float_eq!(vs, &expected[..], abs_all <= 0.);
3340
3341            // second block should have length 1
3342            let vs = render.compute_intrinsic_values(10., 1., 10);
3343            let expected = [9.; 1];
3344            assert_float_eq!(vs, &expected[..], abs_all <= 0.);
3345
3346            // insert event in third block, should have length 10
3347            render.handle_incoming_event(param.set_value_at_time_raw(1., 25.));
3348
3349            let vs = render.compute_intrinsic_values(20., 1., 10);
3350            let expected = [9., 9., 9., 9., 9., 1., 1., 1., 1., 1.];
3351            assert_float_eq!(vs, &expected[..], abs_all <= 0.);
3352
3353            // fourth block should have length 1
3354            let vs = render.compute_intrinsic_values(30., 1., 10);
3355            let expected = [1.; 1];
3356            assert_float_eq!(vs, &expected[..], abs_all <= 0.);
3357        }
3358
3359        // event registered before rendering
3360        {
3361            let context = OfflineAudioContext::new(1, 1, 48000.);
3362
3363            let opts = AudioParamDescriptor {
3364                name: String::new(),
3365                automation_rate: AutomationRate::A,
3366                default_value: 0.,
3367                min_value: 0.,
3368                max_value: 10.,
3369            };
3370            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
3371
3372            render.handle_incoming_event(param.set_value_at_time_raw(0., 0.));
3373            render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(9., 9.));
3374            render.handle_incoming_event(param.set_value_at_time_raw(1., 25.));
3375
3376            // first block should be length 10 (128 in real world)
3377            let vs = render.compute_intrinsic_values(0., 1., 10);
3378            let expected = [0., 1., 2., 3., 4., 5., 6., 7., 8., 9.];
3379            assert_float_eq!(vs, &expected[..], abs_all <= 0.);
3380
3381            // second block should have length 1
3382            let vs = render.compute_intrinsic_values(10., 1., 10);
3383            let expected = [9.; 1];
3384            assert_float_eq!(vs, &expected[..], abs_all <= 0.);
3385
3386            // set value event in third block, length should be 10
3387            let vs = render.compute_intrinsic_values(20., 1., 10);
3388            let expected = [9., 9., 9., 9., 9., 1., 1., 1., 1., 1.];
3389            assert_float_eq!(vs, &expected[..], abs_all <= 0.);
3390
3391            // fourth block should have length 1
3392            let vs = render.compute_intrinsic_values(30., 1., 10);
3393            let expected = [1.; 1];
3394            assert_float_eq!(vs, &expected[..], abs_all <= 0.);
3395        }
3396    }
3397
3398    #[test]
3399    fn test_varying_param_size_modulated() {
3400        let alloc = Alloc::with_capacity(1);
3401
3402        // buffer length is 1 and input is silence (no modulation)
3403        {
3404            let context = OfflineAudioContext::new(1, 1, 48000.);
3405
3406            let opts = AudioParamDescriptor {
3407                name: String::new(),
3408                automation_rate: AutomationRate::A,
3409                default_value: 0.,
3410                min_value: 0.,
3411                max_value: 10.,
3412            };
3413            let (_param, mut render) = audio_param_pair(opts, context.mock_registration());
3414
3415            // no event in timeline, buffer length is 1
3416            let vs = render.compute_intrinsic_values(0., 1., 128);
3417            assert_float_eq!(vs, &[0.; 1][..], abs_all <= 0.);
3418
3419            // mix to output step, input is silence
3420            let signal = alloc.silence();
3421            let input = AudioRenderQuantum::from(signal);
3422
3423            let signal = alloc.silence();
3424            let mut output = AudioRenderQuantum::from(signal);
3425
3426            render.mix_to_output(&input, &mut output);
3427
3428            assert!(output.single_valued());
3429            assert_float_eq!(output.channel_data(0)[0], 0., abs <= 0.);
3430        }
3431
3432        // buffer length is 1 and input is non silent
3433        {
3434            let context = OfflineAudioContext::new(1, 1, 48000.);
3435
3436            let opts = AudioParamDescriptor {
3437                name: String::new(),
3438                automation_rate: AutomationRate::A,
3439                default_value: 0.,
3440                min_value: 0.,
3441                max_value: 10.,
3442            };
3443            let (_param, mut render) = audio_param_pair(opts, context.mock_registration());
3444
3445            // no event in timeline, buffer length is 1
3446            let vs = render.compute_intrinsic_values(0., 1., 128);
3447            assert_float_eq!(vs, &[0.; 1][..], abs_all <= 0.);
3448
3449            // mix to output step, input is not silence
3450            let signal = alloc.silence();
3451            let mut input = AudioRenderQuantum::from(signal);
3452            input.channel_data_mut(0)[0] = 1.;
3453
3454            let signal = alloc.silence();
3455            let mut output = AudioRenderQuantum::from(signal);
3456
3457            render.mix_to_output(&input, &mut output);
3458
3459            let mut expected = [0.; 128];
3460            expected[0] = 1.;
3461
3462            assert!(!output.single_valued());
3463            assert_float_eq!(output.channel_data(0)[..], &expected[..], abs_all <= 0.);
3464        }
3465    }
3466
3467    #[test]
3468    fn test_k_rate_makes_input_single_valued() {
3469        let alloc = Alloc::with_capacity(1);
3470        let context = OfflineAudioContext::new(1, 1, 48000.);
3471
3472        let opts = AudioParamDescriptor {
3473            name: String::new(),
3474            automation_rate: AutomationRate::K,
3475            default_value: 0.,
3476            min_value: 0.,
3477            max_value: 10.,
3478        };
3479        let (_param, mut render) = audio_param_pair(opts, context.mock_registration());
3480
3481        // no event in timeline, buffer length is 1
3482        let vs = render.compute_intrinsic_values(0., 1., 128);
3483        assert_float_eq!(vs, &[0.; 1][..], abs_all <= 0.);
3484
3485        // mix to output step, input is not silence
3486        let signal = alloc.silence();
3487        let mut input = AudioRenderQuantum::from(signal);
3488        input.channel_data_mut(0)[0] = 1.;
3489        input.channel_data_mut(0)[1] = 2.;
3490        input.channel_data_mut(0)[2] = 3.;
3491
3492        let signal = alloc.silence();
3493        let mut output = AudioRenderQuantum::from(signal);
3494
3495        render.mix_to_output(&input, &mut output);
3496
3497        // expect only 1, not the other values
3498        assert!(output.single_valued());
3499        assert_float_eq!(output.channel_data(0)[0], 1., abs <= 0.);
3500    }
3501
3502    #[test]
3503    fn test_full_render_chain() {
3504        let alloc = Alloc::with_capacity(1);
3505        // prevent regression between the different processing stage
3506        let context = OfflineAudioContext::new(1, 1, 48000.);
3507
3508        let min = 2.;
3509        let max = 42.;
3510        let default = 2.;
3511
3512        let opts = AudioParamDescriptor {
3513            name: String::new(),
3514            automation_rate: AutomationRate::A,
3515            default_value: default,
3516            min_value: min,
3517            max_value: max,
3518        };
3519        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
3520
3521        render.handle_incoming_event(param.set_value_raw(128.));
3522        render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(0., 128.));
3523
3524        let intrinsic_values = render.compute_intrinsic_values(0., 1., 128);
3525        let mut expected = [0.; 128];
3526        for (i, v) in expected.iter_mut().enumerate() {
3527            *v = 128. - i as f32;
3528        }
3529        assert_float_eq!(intrinsic_values, &expected[..], abs_all <= 0.);
3530
3531        let signal = alloc.silence();
3532        let mut input = AudioRenderQuantum::from(signal);
3533        input.channel_data_mut(0)[0] = f32::NAN;
3534        let signal = alloc.silence();
3535        let mut output = AudioRenderQuantum::from(signal);
3536
3537        render.mix_to_output(&input, &mut output);
3538
3539        // clamp expected
3540        expected.iter_mut().for_each(|v| *v = v.clamp(min, max));
3541        // fix NAN at position 0
3542        expected[0] = 2.;
3543
3544        assert_float_eq!(output.channel_data(0)[..], &expected[..], abs_all <= 0.);
3545    }
3546}