Skip to main content

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 = -((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            // remove all events in queue where event.time >= cancel_time
863            // i.e. keep all events where event.time < cancel_time
864            self.event_timeline
865                .retain(|queued| queued.time < event.time);
866            return; // cancel_values events are not inserted in queue
867        }
868
869        if event.event_type == AudioParamEventType::CancelAndHoldAtTime {
870            // 1. Let ๐ธ1 be the event (if any) at time ๐‘ก1 where ๐‘ก1 is the
871            // largest number satisfying ๐‘ก1 โ‰ค ๐‘ก๐‘.
872            // 2. Let ๐ธ2 be the event (if any) at time ๐‘ก2 where ๐‘ก2 is the
873            // smallest number satisfying ๐‘ก๐‘ < ๐‘ก2.
874            let mut e1: Option<&mut AudioParamEvent> = None;
875            let mut e2: Option<&mut AudioParamEvent> = None;
876            let mut t1 = f64::MIN;
877            let mut t2 = f64::MAX;
878            // we need a sorted timeline here to find siblings
879            self.event_timeline.sort();
880
881            for queued in self.event_timeline.iter_mut() {
882                // closest before cancel time: if several events at same time,
883                // we want the last one
884                if queued.time >= t1 && queued.time <= event.time {
885                    t1 = queued.time;
886                    e1 = Some(queued);
887                    // closest after cancel time: if several events at same time,
888                    // we want the first one
889                } else if queued.time < t2 && queued.time > event.time {
890                    t2 = queued.time;
891                    e2 = Some(queued);
892                }
893            }
894
895            // If ๐ธ2 exists:
896            if let Some(matched) = e2 {
897                // If ๐ธ2 is a linear or exponential ramp,
898                // Effectively rewrite ๐ธ2 to be the same kind of ramp ending
899                // at time ๐‘ก๐‘ with an end value that would be the value of the
900                // original ramp at time ๐‘ก๐‘.
901                // @note - this is done during the actual computation of the
902                //  ramp using the cancel_time
903                if matched.event_type == AudioParamEventType::LinearRampToValueAtTime
904                    || matched.event_type == AudioParamEventType::ExponentialRampToValueAtTime
905                {
906                    matched.cancel_time = Some(event.time);
907                }
908            } else if let Some(matched) = e1 {
909                if matched.event_type == AudioParamEventType::SetTargetAtTime {
910                    // Implicitly insert a setValueAtTime event at time ๐‘ก๐‘ with
911                    // the value that the setTarget would
912                    // @note - same strategy as for ramps
913                    matched.cancel_time = Some(event.time);
914                } else if matched.event_type == AudioParamEventType::SetValueCurveAtTime {
915                    // If ๐ธ1 is a setValueCurve with a start time of ๐‘ก3 and a duration of ๐‘‘
916                    // If ๐‘ก๐‘ <= ๐‘ก3 + ๐‘‘ :
917                    // Effectively replace this event with a setValueCurve event
918                    // with a start time of ๐‘ก3 and a new duration of ๐‘ก๐‘โˆ’๐‘ก3. However,
919                    // this is not a true replacement; this automation MUST take
920                    // care to produce the same output as the original, and not
921                    // one computed using a different duration. (That would cause
922                    // sampling of the value curve in a slightly different way,
923                    // producing different results.)
924                    let start_time = matched.time;
925                    let duration = matched.duration.unwrap();
926
927                    if event.time <= start_time + duration {
928                        matched.cancel_time = Some(event.time);
929                    }
930                }
931            }
932
933            // [spec] Remove all events with time greater than ๐‘ก๐‘.
934            self.event_timeline.retain(|queued| {
935                let mut time = queued.time;
936                // if the event has a `cancel_time` we use it instead of `time`
937                if let Some(cancel_time) = queued.cancel_time {
938                    time = cancel_time;
939                }
940
941                time <= event.time
942            });
943            return; // cancel_and_hold events are not inserted timeline
944        }
945
946        // handle SetValueCurveAtTime
947        // @note - These rules argue in favor of having events inserted in
948        // the control thread, let's panic for now
949        //
950        // [spec] If setValueCurveAtTime() is called for time ๐‘‡ and duration ๐ท
951        // and there are any events having a time strictly greater than ๐‘‡, but
952        // strictly less than ๐‘‡+๐ท, then a NotSupportedError exception MUST be thrown.
953        // In other words, itโ€™s not ok to schedule a value curve during a time period
954        // containing other events, but itโ€™s ok to schedule a value curve exactly
955        // at the time of another event.
956        if event.event_type == AudioParamEventType::SetValueCurveAtTime {
957            // check if we don't try to insert at the time of another event
958            let start_time = event.time;
959            let end_time = start_time + event.duration.unwrap();
960
961            for queued in self.event_timeline.iter() {
962                assert!(
963                    queued.time <= start_time || queued.time >= end_time,
964                    "NotSupportedError - scheduling SetValueCurveAtTime ({:?}) at time of another automation event ({:?})",
965                    event, queued,
966                );
967            }
968        }
969
970        // [spec] Similarly a NotSupportedError exception MUST be thrown if any
971        // automation method is called at a time which is contained in [๐‘‡,๐‘‡+๐ท), ๐‘‡
972        // being the time of the curve and ๐ท its duration.
973        // @note - Cancel methods are not automation methods
974        if event.event_type == AudioParamEventType::SetValueAtTime
975            || event.event_type == AudioParamEventType::SetValue
976            || event.event_type == AudioParamEventType::LinearRampToValueAtTime
977            || event.event_type == AudioParamEventType::ExponentialRampToValueAtTime
978            || event.event_type == AudioParamEventType::SetTargetAtTime
979        {
980            for queued in self.event_timeline.iter() {
981                if queued.event_type == AudioParamEventType::SetValueCurveAtTime {
982                    let start_time = queued.time;
983                    let end_time = start_time + queued.duration.unwrap();
984
985                    assert!(
986                        event.time <= start_time || event.time >= end_time,
987                        "NotSupportedError - scheduling automation event ({:?}) during SetValueCurveAtTime ({:?})",
988                        event, queued,
989                    );
990                }
991            }
992        }
993
994        // handle SetValue - param intrinsic value must be updated from event value
995        if event.event_type == AudioParamEventType::SetValue {
996            self.intrinsic_value = event.value;
997        }
998
999        // If no event in the timeline and event_type is `LinearRampToValueAtTime`
1000        // or `ExponentialRampToValue` at time, we must insert a `SetValueAtTime`
1001        // with intrinsic value and calling time.
1002        // cf. https://www.w3.org/TR/webaudio/#dom-audioparam-linearramptovalueattime
1003        // cf. https://www.w3.org/TR/webaudio/#dom-audioparam-exponentialramptovalueattime
1004        if self.event_timeline.is_empty()
1005            && self.last_event.is_none()
1006            && (event.event_type == AudioParamEventType::LinearRampToValueAtTime
1007                || event.event_type == AudioParamEventType::ExponentialRampToValueAtTime)
1008        {
1009            let set_value_event = AudioParamEvent {
1010                event_type: AudioParamEventType::SetValue,
1011                value: self.intrinsic_value,
1012                // make sure the event is applied before any other event, time
1013                // will be replaced by the block timestamp during event processing
1014                time: 0.,
1015                time_constant: None,
1016                cancel_time: None,
1017                duration: None,
1018                values: None,
1019            };
1020
1021            self.event_timeline.push(set_value_event);
1022        }
1023
1024        // for SetTarget, this behavior is not per se specified, but it allows
1025        // to make sure we have a stable start_value available without having
1026        // to store it elsewhere.
1027        if self.event_timeline.is_empty()
1028            && event.event_type == AudioParamEventType::SetTargetAtTime
1029        {
1030            let set_value_event = AudioParamEvent {
1031                event_type: AudioParamEventType::SetValue,
1032                value: self.intrinsic_value,
1033                // make sure the event is applied before any other event, time
1034                // will be replaced by the block timestamp during event processing
1035                time: 0.,
1036                time_constant: None,
1037                cancel_time: None,
1038                duration: None,
1039                values: None,
1040            };
1041
1042            self.event_timeline.push(set_value_event);
1043        }
1044
1045        self.event_timeline.push(event);
1046        self.event_timeline.sort();
1047    }
1048
1049    // https://www.w3.org/TR/webaudio/%23dom-audioparam-linearramptovalueattime#dom-audioparam-setvalueattime
1050    fn compute_set_value_automation(&mut self, infos: &BlockInfos) -> bool {
1051        let event = self.event_timeline.peek().unwrap();
1052        let mut time = event.time;
1053
1054        // `set_value` and implicitly inserted events are inserted with `time = 0.`,
1055        // so that we ensure they are processed first. Replacing their `time` with
1056        // `block_time` allows to conform to the spec:
1057        // cf. https://www.w3.org/TR/webaudio/#dom-audioparam-value
1058        // cf. https://www.w3.org/TR/webaudio/#dom-audioparam-linearramptovalueattime
1059        // cf. https://www.w3.org/TR/webaudio/#dom-audioparam-exponentialramptovalueattime
1060        if time == 0. {
1061            time = infos.block_time;
1062        }
1063
1064        // fill buffer with current intrinsic value until `event.time`
1065        if infos.is_a_rate {
1066            let end_index = ((time - infos.block_time).max(0.) / infos.dt).round() as usize;
1067            let end_index_clipped = end_index.min(infos.count);
1068
1069            for _ in self.buffer.len()..end_index_clipped {
1070                self.buffer.push(self.intrinsic_value);
1071            }
1072        }
1073
1074        // event time is not reached within this block, we are done
1075        if time > infos.next_block_time {
1076            return true;
1077        }
1078
1079        // else update `intrisic_value` and `last_event` and continue the loop
1080        self.intrinsic_value = event.value;
1081
1082        // no computation has been done on `time`
1083        #[allow(clippy::float_cmp)]
1084        if time != event.time {
1085            let mut event = self.event_timeline.pop().unwrap();
1086            event.time = time;
1087            self.last_event = Some(event);
1088        } else {
1089            self.last_event = self.event_timeline.pop();
1090        }
1091
1092        false
1093    }
1094
1095    // cf. https://www.w3.org/TR/webaudio/#dom-audioparam-linearramptovalueattime
1096    // ๐‘ฃ(๐‘ก) = ๐‘‰0 + (๐‘‰1โˆ’๐‘‰0) * ((๐‘กโˆ’๐‘‡0) / (๐‘‡1โˆ’๐‘‡0))
1097    fn compute_linear_ramp_automation(&mut self, infos: &BlockInfos) -> bool {
1098        let event = self.event_timeline.peek().unwrap();
1099        let last_event = self.last_event.as_ref().unwrap();
1100
1101        let start_time = last_event.time;
1102        let mut end_time = event.time;
1103        // compute duration before clapping `end_time` to`cancel_time`, to keep
1104        // declared slope of the ramp consistent
1105        let duration = end_time - start_time;
1106        if let Some(cancel_time) = event.cancel_time {
1107            end_time = cancel_time;
1108        }
1109
1110        let start_value = last_event.value;
1111        let end_value = event.value;
1112        let diff = end_value - start_value;
1113
1114        if infos.is_a_rate {
1115            let start_index = self.buffer.len();
1116            // TODO use ceil() or round() when `end_time` is between two samples?
1117            // <https://github.com/orottier/web-audio-api-rs/pull/460>
1118            let end_index = ((end_time - infos.block_time).max(0.) / infos.dt).round() as usize;
1119            let end_index_clipped = end_index.min(infos.count);
1120
1121            // compute "real" value according to `t` then clamp it
1122            // cf. Example 7 https://www.w3.org/TR/webaudio/#computation-of-value
1123            if end_index_clipped > start_index {
1124                let mut time = (start_index as f64).mul_add(infos.dt, infos.block_time);
1125
1126                let mut value = 0.;
1127                for _ in start_index..end_index_clipped {
1128                    value =
1129                        compute_linear_ramp_sample(start_time, duration, start_value, diff, time);
1130                    self.buffer.push(value);
1131                    time += infos.dt;
1132                }
1133                self.intrinsic_value = value;
1134            }
1135        }
1136
1137        // Event will continue in next tick:
1138        // compute value for `next_block_time` so that `param.value()`
1139        // stays coherent, also allows to properly fill k-rate
1140        // within next block too
1141        if end_time >= infos.next_block_time {
1142            let value = compute_linear_ramp_sample(
1143                start_time,
1144                duration,
1145                start_value,
1146                diff,
1147                infos.next_block_time,
1148            );
1149            self.intrinsic_value = value;
1150
1151            return true;
1152        }
1153
1154        // Event cancelled during this block
1155        if event.cancel_time.is_some() {
1156            let value =
1157                compute_linear_ramp_sample(start_time, duration, start_value, diff, end_time);
1158
1159            self.intrinsic_value = value;
1160
1161            let mut last_event = self.event_timeline.pop().unwrap();
1162            last_event.time = end_time;
1163            last_event.value = value;
1164            self.last_event = Some(last_event);
1165        // Event ended during this block
1166        } else {
1167            self.intrinsic_value = end_value;
1168            self.last_event = self.event_timeline.pop();
1169        }
1170
1171        false
1172    }
1173
1174    // cf. https://www.w3.org/TR/webaudio/#dom-audioparam-exponentialramptovalueattime
1175    // v(t) = v1 * (v2/v1)^((t-t1) / (t2-t1))
1176    fn compute_exponential_ramp_automation(&mut self, infos: &BlockInfos) -> bool {
1177        let event = self.event_timeline.peek().unwrap();
1178        let last_event = self.last_event.as_ref().unwrap();
1179
1180        let start_time = last_event.time;
1181        let mut end_time = event.time;
1182        // compute duration before clapping `end_time` to `cancel_time`, to keep
1183        // declared slope of the ramp consistent
1184        let duration = end_time - start_time;
1185        if let Some(cancel_time) = event.cancel_time {
1186            end_time = cancel_time;
1187        }
1188
1189        let start_value = last_event.value;
1190        let end_value = event.value;
1191        let ratio = end_value / start_value;
1192
1193        // Handle edge cases:
1194        // > If ๐‘‰0 and ๐‘‰1 have opposite signs or if ๐‘‰0 is zero,
1195        // > then ๐‘ฃ(๐‘ก)=๐‘‰0 for ๐‘‡0โ‰ค๐‘ก<๐‘‡1.
1196        // as:
1197        // > If there are no more events after this ExponentialRampToValue
1198        // > event then for ๐‘กโ‰ฅ๐‘‡1, ๐‘ฃ(๐‘ก)=๐‘‰1.
1199        // this should thus behave as a SetValue
1200        if start_value == 0. || start_value * end_value < 0. {
1201            let event = AudioParamEvent {
1202                event_type: AudioParamEventType::SetValueAtTime,
1203                time: end_time,
1204                value: end_value,
1205                time_constant: None,
1206                cancel_time: None,
1207                duration: None,
1208                values: None,
1209            };
1210
1211            self.event_timeline.replace_peek(event);
1212            return false;
1213        }
1214
1215        if infos.is_a_rate {
1216            let start_index = self.buffer.len();
1217            // TODO use ceil() or round() when `end_time` is between two samples?
1218            // <https://github.com/orottier/web-audio-api-rs/pull/460>
1219            let end_index = ((end_time - infos.block_time).max(0.) / infos.dt).round() as usize;
1220            let end_index_clipped = end_index.min(infos.count);
1221
1222            if end_index_clipped > start_index {
1223                let mut time = (start_index as f64).mul_add(infos.dt, infos.block_time);
1224
1225                let mut value = 0.;
1226                for _ in start_index..end_index_clipped {
1227                    value = compute_exponential_ramp_sample(
1228                        start_time,
1229                        duration,
1230                        start_value,
1231                        ratio,
1232                        time,
1233                    );
1234
1235                    self.buffer.push(value);
1236
1237                    time += infos.dt;
1238                }
1239                self.intrinsic_value = value;
1240            }
1241        }
1242
1243        // Event will continue in next tick:
1244        // compute value for `next_block_time` so that `param.value()`
1245        // stays coherent, also allows to properly fill k-rate
1246        // within next block too
1247        if end_time >= infos.next_block_time {
1248            let value = compute_exponential_ramp_sample(
1249                start_time,
1250                duration,
1251                start_value,
1252                ratio,
1253                infos.next_block_time,
1254            );
1255            self.intrinsic_value = value;
1256
1257            return true;
1258        }
1259
1260        // Event cancelled during this block
1261        if event.cancel_time.is_some() {
1262            let value =
1263                compute_exponential_ramp_sample(start_time, duration, start_value, ratio, end_time);
1264
1265            self.intrinsic_value = value;
1266
1267            let mut last_event = self.event_timeline.pop().unwrap();
1268            last_event.time = end_time;
1269            last_event.value = value;
1270            self.last_event = Some(last_event);
1271
1272        // Event ended during this block
1273        } else {
1274            self.intrinsic_value = end_value;
1275            self.last_event = self.event_timeline.pop();
1276        }
1277
1278        false
1279    }
1280
1281    // https://webaudio.github.io/web-audio-api/#dom-audioparam-settargetattime
1282    // ๐‘ฃ(๐‘ก) = ๐‘‰1 + (๐‘‰0 โˆ’ ๐‘‰1) * ๐‘’^โˆ’((๐‘กโˆ’๐‘‡0) / ๐œ)
1283    // Note that as SetTarget never resolves on an end value, a `SetTarget` event
1284    // is inserted in the timeline when the value is close enough to the target.
1285    // cf. `SNAP_TO_TARGET`
1286    fn compute_set_target_automation(&mut self, infos: &BlockInfos) -> bool {
1287        let event = self.event_timeline.peek().unwrap();
1288        let mut end_time = infos.next_block_time;
1289        let mut ended = false;
1290
1291        // handle next event stop SetTarget if any
1292        let some_next_event = self.event_timeline.next();
1293
1294        if let Some(next_event) = some_next_event {
1295            match next_event.event_type {
1296                AudioParamEventType::LinearRampToValueAtTime
1297                | AudioParamEventType::ExponentialRampToValueAtTime => {
1298                    // [spec] If the preceding event is a SetTarget
1299                    // event, ๐‘‡0 and ๐‘‰0 are chosen from the current
1300                    // time and value of SetTarget automation. That
1301                    // is, if the SetTarget event has not started,
1302                    // ๐‘‡0 is the start time of the event, and ๐‘‰0
1303                    // is the value just before the SetTarget event
1304                    // starts. In this case, the LinearRampToValue
1305                    // event effectively replaces the SetTarget event.
1306                    // If the SetTarget event has already started,
1307                    // ๐‘‡0 is the current context time, and ๐‘‰0 is
1308                    // the current SetTarget automation value at time ๐‘‡0.
1309                    // In both cases, the automation curve is continuous.
1310                    end_time = infos.block_time;
1311                    ended = true;
1312                }
1313                _ => {
1314                    // For all other events, the SetTarget
1315                    // event ends at the time of the next event.
1316                    if next_event.time < infos.next_block_time {
1317                        end_time = next_event.time;
1318                        ended = true;
1319                    }
1320                }
1321            }
1322        }
1323
1324        // handle CancelAndHoldAtTime
1325        if let Some(cancel_time) = event.cancel_time {
1326            if cancel_time < infos.next_block_time {
1327                end_time = cancel_time;
1328                ended = true;
1329            }
1330        }
1331
1332        let start_time = event.time;
1333        // if SetTarget is the first event registered, we implicitly
1334        // insert a SetValue event just before just as for Ramps.
1335        // Therefore we are sure last_event exists
1336        let start_value = self.last_event.as_ref().unwrap().value;
1337        let end_value = event.value;
1338        let diff = start_value - end_value;
1339        let time_constant = event.time_constant.unwrap();
1340
1341        if infos.is_a_rate {
1342            let start_index = self.buffer.len();
1343            // TODO use ceil() or round() when `end_time` is between two samples?
1344            // <https://github.com/orottier/web-audio-api-rs/pull/460>
1345            let end_index = ((end_time - infos.block_time).max(0.) / infos.dt).round() as usize;
1346            let end_index_clipped = end_index.min(infos.count);
1347
1348            if end_index_clipped > start_index {
1349                let mut time = (start_index as f64).mul_add(infos.dt, infos.block_time);
1350
1351                let mut value = 0.;
1352                for _ in start_index..end_index_clipped {
1353                    // check if we have reached start_time
1354                    value = if time - start_time < 0. {
1355                        self.intrinsic_value
1356                    } else {
1357                        compute_set_target_sample(start_time, time_constant, end_value, diff, time)
1358                    };
1359
1360                    self.buffer.push(value);
1361                    time += infos.dt;
1362                }
1363                self.intrinsic_value = value;
1364            }
1365        }
1366
1367        if !ended {
1368            // compute value for `next_block_time` so that `param.value()`
1369            // stays coherent (see. comment in `AudioParam`)
1370            // allows to properly fill k-rate within next block too
1371            let value = compute_set_target_sample(
1372                start_time,
1373                time_constant,
1374                end_value,
1375                diff,
1376                infos.next_block_time,
1377            );
1378
1379            let diff = (end_value - value).abs();
1380
1381            // abort event if diff is below SNAP_TO_TARGET
1382            if diff < SNAP_TO_TARGET {
1383                self.intrinsic_value = end_value;
1384
1385                // if end_value is zero, the buffer might contain
1386                // subnormals, we need to check that and flush to zero
1387                if end_value == 0. {
1388                    for v in self.buffer.iter_mut() {
1389                        if v.is_subnormal() {
1390                            *v = 0.;
1391                        }
1392                    }
1393                }
1394
1395                let event = AudioParamEvent {
1396                    event_type: AudioParamEventType::SetValueAtTime,
1397                    time: infos.next_block_time,
1398                    value: end_value,
1399                    time_constant: None,
1400                    cancel_time: None,
1401                    duration: None,
1402                    values: None,
1403                };
1404
1405                self.event_timeline.replace_peek(event);
1406            } else {
1407                self.intrinsic_value = value;
1408            }
1409
1410            return true;
1411        }
1412
1413        // setTarget has no "real" end value, compute according
1414        // to next event start time
1415        let value = compute_set_target_sample(start_time, time_constant, end_value, diff, end_time);
1416
1417        self.intrinsic_value = value;
1418        // end_value and end_time must be stored for use
1419        // as start time by next event
1420        let mut event = self.event_timeline.pop().unwrap();
1421        event.time = end_time;
1422        event.value = value;
1423        self.last_event = Some(event);
1424
1425        false
1426    }
1427
1428    fn compute_set_value_curve_automation(&mut self, infos: &BlockInfos) -> bool {
1429        let event = self.event_timeline.peek().unwrap();
1430        let start_time = event.time;
1431        let duration = event.duration.unwrap();
1432        let values = event.values.as_ref().unwrap();
1433        let mut end_time = start_time + duration;
1434
1435        // we must check for the cancel event after we have
1436        // the "real" duration computed to not change the
1437        // slope of the ramp
1438        if let Some(cancel_time) = event.cancel_time {
1439            end_time = cancel_time;
1440        }
1441
1442        if infos.is_a_rate {
1443            let start_index = self.buffer.len();
1444            // TODO use ceil() or round() when `end_time` is between two samples?
1445            // <https://github.com/orottier/web-audio-api-rs/pull/460>
1446            let end_index = ((end_time - infos.block_time).max(0.) / infos.dt).round() as usize;
1447            let end_index_clipped = end_index.min(infos.count);
1448
1449            if end_index_clipped > start_index {
1450                let mut time = (start_index as f64).mul_add(infos.dt, infos.block_time);
1451
1452                let mut value = 0.;
1453                for _ in start_index..end_index_clipped {
1454                    // check if we have reached start_time
1455                    value = if time < start_time {
1456                        self.intrinsic_value
1457                    } else {
1458                        compute_set_value_curve_sample(start_time, duration, values, time)
1459                    };
1460
1461                    self.buffer.push(value);
1462
1463                    time += infos.dt;
1464                }
1465                self.intrinsic_value = value;
1466            }
1467        }
1468
1469        // event will continue in next tick
1470        if end_time >= infos.next_block_time {
1471            // compute value for `next_block_time` so that `param.value()`
1472            // stays coherent (see. comment in `AudioParam`)
1473            // allows to properly fill k-rate within next block too
1474            let value =
1475                compute_set_value_curve_sample(start_time, duration, values, infos.next_block_time);
1476            self.intrinsic_value = value;
1477
1478            return true;
1479        }
1480
1481        // event has been cancelled
1482        if event.cancel_time.is_some() {
1483            let value = compute_set_value_curve_sample(start_time, duration, values, end_time);
1484
1485            self.intrinsic_value = value;
1486
1487            let mut last_event = self.event_timeline.pop().unwrap();
1488            last_event.time = end_time;
1489            last_event.value = value;
1490            self.last_event = Some(last_event);
1491        // event has ended
1492        } else {
1493            let value = values[values.len() - 1];
1494
1495            let mut last_event = self.event_timeline.pop().unwrap();
1496            last_event.time = end_time;
1497            last_event.value = value;
1498
1499            self.intrinsic_value = value;
1500            self.last_event = Some(last_event);
1501        }
1502
1503        false
1504    }
1505
1506    fn compute_buffer(&mut self, block_time: f64, dt: f64, count: usize) {
1507        // Set [[current value]] to the value of paramIntrinsicValue at the
1508        // beginning of this render quantum.
1509        let clamped = self.intrinsic_value.clamp(self.min_value, self.max_value);
1510        self.current_value.store(clamped, Ordering::Release);
1511
1512        // clear the buffer for this block
1513        self.buffer.clear();
1514
1515        let is_a_rate = self.automation_rate.is_a_rate();
1516        let next_block_time = dt.mul_add(count as f64, block_time);
1517
1518        // Check if we can safely return a buffer of length 1 even for a-rate params.
1519        // Several cases allow us to do so:
1520        // - The timeline is empty
1521        // - The timeline is not empty: in such case if `event.time >= next_block_time`
1522        //   AND `event_type` is not `LinearRampToValueAtTime` or `ExponentialRampToValueAtTime`
1523        //   this is safe, i.e.:
1524        //   + For linear and exponential ramps `event.time` is the end time while their
1525        //   start time is `last_event.time`, therefore if `peek()` is of these
1526        //   types, we are in the middle of the ramp.
1527        //   + For all other event, `event.time` is their start time.
1528        //   (@note - `SetTargetAtTime` events also uses `last_event` but only for
1529        //   its value, not for its timing information, so no problem there)
1530        let is_constant_block = match self.event_timeline.peek() {
1531            None => true,
1532            Some(event) => {
1533                if event.event_type != AudioParamEventType::LinearRampToValueAtTime
1534                    && event.event_type != AudioParamEventType::ExponentialRampToValueAtTime
1535                {
1536                    event.time >= next_block_time
1537                } else {
1538                    false
1539                }
1540            }
1541        };
1542
1543        if !is_a_rate || is_constant_block {
1544            self.buffer.push(self.intrinsic_value);
1545            // nothing to compute in timeline, for both k-rate and a-rate
1546            if is_constant_block {
1547                return;
1548            }
1549        }
1550
1551        // pack block infos for automation methods
1552        let block_infos = BlockInfos {
1553            block_time,
1554            dt,
1555            count,
1556            is_a_rate,
1557            next_block_time,
1558        };
1559
1560        loop {
1561            let next_event_type = self.event_timeline.peek().map(|e| e.event_type);
1562
1563            let exit_loop = match next_event_type {
1564                None => {
1565                    if is_a_rate {
1566                        // we use `count` rather then `buffer.remaining_capacity`
1567                        // to correctly handle unit tests where `count` is lower than
1568                        // RENDER_QUANTUM_SIZE
1569                        for _ in self.buffer.len()..count {
1570                            self.buffer.push(self.intrinsic_value);
1571                        }
1572                    }
1573                    true
1574                }
1575                Some(AudioParamEventType::SetValue) | Some(AudioParamEventType::SetValueAtTime) => {
1576                    self.compute_set_value_automation(&block_infos)
1577                }
1578                Some(AudioParamEventType::LinearRampToValueAtTime) => {
1579                    self.compute_linear_ramp_automation(&block_infos)
1580                }
1581                Some(AudioParamEventType::ExponentialRampToValueAtTime) => {
1582                    self.compute_exponential_ramp_automation(&block_infos)
1583                }
1584                Some(AudioParamEventType::SetTargetAtTime) => {
1585                    self.compute_set_target_automation(&block_infos)
1586                }
1587                Some(AudioParamEventType::SetValueCurveAtTime) => {
1588                    self.compute_set_value_curve_automation(&block_infos)
1589                }
1590                _ => panic!(
1591                    "AudioParamEvent {:?} should not appear in AudioParamEventTimeline",
1592                    next_event_type.unwrap()
1593                ),
1594            };
1595
1596            if exit_loop {
1597                break;
1598            }
1599        }
1600    }
1601}
1602
1603pub(crate) fn audio_param_pair(
1604    descriptor: AudioParamDescriptor,
1605    registration: AudioContextRegistration,
1606) -> (AudioParam, AudioParamProcessor) {
1607    let AudioParamDescriptor {
1608        automation_rate,
1609        default_value,
1610        max_value,
1611        min_value,
1612        ..
1613    } = descriptor;
1614
1615    assert_is_finite(default_value);
1616    assert_is_finite(min_value);
1617    assert_is_finite(max_value);
1618    assert!(
1619        min_value <= default_value,
1620        "InvalidStateError - AudioParam minValue should be <= defaultValue"
1621    );
1622    assert!(
1623        default_value <= max_value,
1624        "InvalidStateError - AudioParam defaultValue should be <= maxValue"
1625    );
1626
1627    let current_value = Arc::new(AtomicF32::new(default_value));
1628
1629    let param = AudioParam {
1630        registration: registration.into(),
1631        raw_parts: AudioParamInner {
1632            default_value,
1633            max_value,
1634            min_value,
1635            automation_rate_constrained: false,
1636            automation_rate: Arc::new(Mutex::new(automation_rate)),
1637            current_value: Arc::clone(&current_value),
1638        },
1639    };
1640
1641    let processor = AudioParamProcessor {
1642        intrinsic_value: default_value,
1643        current_value,
1644        default_value,
1645        min_value,
1646        max_value,
1647        automation_rate,
1648        event_timeline: AudioParamEventTimeline::new(),
1649        last_event: None,
1650        buffer: ArrayVec::new(),
1651    };
1652
1653    (param, processor)
1654}
1655
1656#[cfg(test)]
1657mod tests {
1658    use float_eq::assert_float_eq;
1659
1660    use crate::context::{BaseAudioContext, OfflineAudioContext};
1661    use crate::render::Alloc;
1662
1663    use super::*;
1664
1665    #[test]
1666    #[should_panic]
1667    fn test_assert_strictly_positive_fail() {
1668        assert_strictly_positive(0.);
1669    }
1670
1671    #[test]
1672    fn test_assert_strictly_positive() {
1673        assert_strictly_positive(0.1);
1674    }
1675
1676    #[test]
1677    #[should_panic]
1678    fn test_assert_not_zero_fail() {
1679        assert_not_zero(0.);
1680    }
1681
1682    #[test]
1683    fn test_assert_not_zero() {
1684        assert_not_zero(-0.1);
1685        assert_not_zero(0.1);
1686    }
1687
1688    #[test]
1689    #[should_panic]
1690    fn test_assert_sequence_length_fail() {
1691        assert_sequence_length(&[0.; 1]);
1692    }
1693
1694    #[test]
1695    fn test_assert_sequence_length() {
1696        assert_sequence_length(&[0.; 2]);
1697    }
1698
1699    #[test]
1700    fn test_default_and_accessors() {
1701        let context = OfflineAudioContext::new(1, 1, 48000.);
1702
1703        let opts = AudioParamDescriptor {
1704            name: String::new(),
1705            automation_rate: AutomationRate::A,
1706            default_value: 0.,
1707            min_value: -10.,
1708            max_value: 10.,
1709        };
1710        let (param, _render) = audio_param_pair(opts, context.mock_registration());
1711
1712        assert_eq!(param.automation_rate(), AutomationRate::A);
1713        assert_float_eq!(param.default_value(), 0., abs_all <= 0.);
1714        assert_float_eq!(param.min_value(), -10., abs_all <= 0.);
1715        assert_float_eq!(param.max_value(), 10., abs_all <= 0.);
1716        assert_float_eq!(param.value(), 0., abs_all <= 0.);
1717    }
1718
1719    #[test]
1720    fn test_automation_rate_synchronicity_on_control_thread() {
1721        let context = OfflineAudioContext::new(1, 1, 48000.);
1722
1723        let opts = AudioParamDescriptor {
1724            name: String::new(),
1725            automation_rate: AutomationRate::A,
1726            default_value: 0.,
1727            min_value: 0.,
1728            max_value: 1.,
1729        };
1730        let (param, _render) = audio_param_pair(opts, context.mock_registration());
1731
1732        param.set_automation_rate(AutomationRate::K);
1733        assert_eq!(param.automation_rate(), AutomationRate::K);
1734    }
1735
1736    #[test]
1737    fn test_audioparam_clones_in_sync() {
1738        let context = OfflineAudioContext::new(1, 1, 48000.);
1739
1740        let opts = AudioParamDescriptor {
1741            name: String::new(),
1742            automation_rate: AutomationRate::A,
1743            default_value: 0.,
1744            min_value: -10.,
1745            max_value: 10.,
1746        };
1747        let (param1, mut render) = audio_param_pair(opts, context.mock_registration());
1748        let param2 = param1.clone();
1749
1750        // changing automation rate on param1 should reflect in param2
1751        param1.set_automation_rate(AutomationRate::K);
1752        assert_eq!(param2.automation_rate(), AutomationRate::K);
1753
1754        // setting value on param1 should reflect in param2
1755        render.handle_incoming_event(param1.set_value_raw(2.));
1756        assert_float_eq!(param1.value(), 2., abs_all <= 0.);
1757        assert_float_eq!(param2.value(), 2., abs_all <= 0.);
1758
1759        // setting value on param2 should reflect in param1
1760        render.handle_incoming_event(param2.set_value_raw(3.));
1761        assert_float_eq!(param1.value(), 3., abs_all <= 0.);
1762        assert_float_eq!(param2.value(), 3., abs_all <= 0.);
1763    }
1764
1765    #[test]
1766    fn test_set_value() {
1767        {
1768            let context = OfflineAudioContext::new(1, 1, 48000.);
1769
1770            let opts = AudioParamDescriptor {
1771                name: String::new(),
1772                automation_rate: AutomationRate::A,
1773                default_value: 0.,
1774                min_value: -10.,
1775                max_value: 10.,
1776            };
1777            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
1778
1779            render.handle_incoming_event(param.set_value_raw(2.));
1780
1781            assert_float_eq!(param.value(), 2., abs_all <= 0.);
1782
1783            let vs = render.compute_intrinsic_values(0., 1., 10);
1784
1785            assert_float_eq!(param.value(), 2., abs_all <= 0.);
1786            assert_float_eq!(vs, &[2.; 10][..], abs_all <= 0.);
1787        }
1788
1789        // make sure param.value() is properly clamped
1790        {
1791            let context = OfflineAudioContext::new(1, 1, 48000.);
1792
1793            let opts = AudioParamDescriptor {
1794                name: String::new(),
1795                automation_rate: AutomationRate::A,
1796                default_value: 0.,
1797                min_value: 0.,
1798                max_value: 1.,
1799            };
1800            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
1801
1802            render.handle_incoming_event(param.set_value_raw(2.));
1803
1804            assert_float_eq!(param.value(), 1., abs_all <= 0.);
1805
1806            let vs = render.compute_intrinsic_values(0., 1., 10);
1807
1808            // value should clamped while intrinsic value should not
1809            assert_float_eq!(param.value(), 1., abs_all <= 0.);
1810            assert_float_eq!(vs, &[2.; 10][..], abs_all <= 0.);
1811        }
1812    }
1813
1814    #[test]
1815    fn test_steps_a_rate() {
1816        let context = OfflineAudioContext::new(1, 1, 48000.);
1817
1818        {
1819            let opts = AudioParamDescriptor {
1820                name: String::new(),
1821                automation_rate: AutomationRate::A,
1822                default_value: 0.,
1823                min_value: -10.,
1824                max_value: 10.,
1825            };
1826            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
1827
1828            render.handle_incoming_event(param.set_value_at_time_raw(5., 2.0));
1829            render.handle_incoming_event(param.set_value_at_time_raw(12., 8.0)); // should clamp
1830            render.handle_incoming_event(param.set_value_at_time_raw(8., 10.0)); // should not occur 1st run
1831
1832            let vs = render.compute_intrinsic_values(0., 1., 10);
1833            assert_float_eq!(
1834                vs,
1835                &[0., 0., 5., 5., 5., 5., 5., 5., 12., 12.][..],
1836                abs_all <= 0.
1837            );
1838
1839            // no event left in timeline, i.e. length is 1
1840            let vs = render.compute_intrinsic_values(10., 1., 10);
1841            assert_float_eq!(vs, &[8.; 1][..], abs_all <= 0.);
1842        }
1843
1844        {
1845            // events spread on several blocks
1846            let opts = AudioParamDescriptor {
1847                name: String::new(),
1848                automation_rate: AutomationRate::A,
1849                default_value: 0.,
1850                min_value: -10.,
1851                max_value: 10.,
1852            };
1853            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
1854
1855            render.handle_incoming_event(param.set_value_at_time_raw(5., 2.0));
1856            render.handle_incoming_event(param.set_value_at_time_raw(8., 12.0));
1857
1858            let vs = render.compute_intrinsic_values(0., 1., 10);
1859            assert_float_eq!(
1860                vs,
1861                &[0., 0., 5., 5., 5., 5., 5., 5., 5., 5.][..],
1862                abs_all <= 0.
1863            );
1864
1865            let vs = render.compute_intrinsic_values(10., 1., 10);
1866            assert_float_eq!(
1867                vs,
1868                &[5., 5., 8., 8., 8., 8., 8., 8., 8., 8.][..],
1869                abs_all <= 0.
1870            );
1871        }
1872    }
1873
1874    #[test]
1875    fn test_steps_k_rate() {
1876        let context = OfflineAudioContext::new(1, 1, 48000.);
1877        let opts = AudioParamDescriptor {
1878            name: String::new(),
1879            automation_rate: AutomationRate::K,
1880            default_value: 0.,
1881            min_value: -10.,
1882            max_value: 10.,
1883        };
1884        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
1885
1886        render.handle_incoming_event(param.set_value_at_time_raw(5., 2.0));
1887        render.handle_incoming_event(param.set_value_at_time_raw(12., 8.0)); // should not appear in results
1888        render.handle_incoming_event(param.set_value_at_time_raw(8., 10.0)); // should not occur 1st run
1889        render.handle_incoming_event(param.set_value_at_time_raw(3., 14.0)); // should appear in 3rd run
1890
1891        let vs = render.compute_intrinsic_values(0., 1., 10);
1892        assert_float_eq!(vs, &[0.; 1][..], abs_all <= 0.);
1893
1894        let vs = render.compute_intrinsic_values(10., 1., 10);
1895        assert_float_eq!(vs, &[8.; 1][..], abs_all <= 0.);
1896
1897        let vs = render.compute_intrinsic_values(20., 1., 10);
1898        assert_float_eq!(vs, &[3.; 1][..], abs_all <= 0.);
1899    }
1900
1901    #[test]
1902    fn test_linear_ramp_arate() {
1903        let context = OfflineAudioContext::new(1, 1, 48000.);
1904
1905        let opts = AudioParamDescriptor {
1906            name: String::new(),
1907            automation_rate: AutomationRate::A,
1908            default_value: 0.,
1909            min_value: -10.,
1910            max_value: 10.,
1911        };
1912        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
1913
1914        // set to 5 at t = 2
1915        render.handle_incoming_event(param.set_value_at_time_raw(5., 2.0));
1916        // ramp to 8 from t = 2 to t = 5
1917        render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(8.0, 5.0));
1918        // ramp to 0 from t = 5 to t = 13
1919        render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(0., 13.0));
1920
1921        let vs = render.compute_intrinsic_values(0., 1., 10);
1922        assert_float_eq!(
1923            vs,
1924            &[0., 0., 5., 6., 7., 8., 7., 6., 5., 4.][..],
1925            abs_all <= 0.
1926        );
1927    }
1928
1929    #[test]
1930    fn test_linear_ramp_arate_end_of_block() {
1931        let context = OfflineAudioContext::new(1, 1, 48000.);
1932
1933        let opts = AudioParamDescriptor {
1934            name: String::new(),
1935            automation_rate: AutomationRate::A,
1936            default_value: 0.,
1937            min_value: -10.,
1938            max_value: 10.,
1939        };
1940        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
1941
1942        // set to 0 at t = 0
1943        render.handle_incoming_event(param.set_value_at_time_raw(0., 0.));
1944        // ramp to 9 from t = 0 to t = 9
1945        render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(9.0, 9.0));
1946
1947        let vs = render.compute_intrinsic_values(0., 1., 10);
1948        assert_float_eq!(
1949            vs,
1950            &[0., 1., 2., 3., 4., 5., 6., 7., 8., 9.][..],
1951            abs_all <= 0.
1952        );
1953    }
1954
1955    #[test]
1956    // @note - with real params, a set_value event is always used to provide
1957    // init value to the param. Therefore this test is purely theoretical and
1958    // in real world the param would not behave like that, which is wrong
1959    // @todo - open an issue to review how init value is passed (or how last_event is set)
1960    fn test_linear_ramp_arate_implicit_set_value() {
1961        let context = OfflineAudioContext::new(1, 1, 48000.);
1962
1963        let opts = AudioParamDescriptor {
1964            name: String::new(),
1965            automation_rate: AutomationRate::A,
1966            default_value: 0.,
1967            min_value: -10.,
1968            max_value: 10.,
1969        };
1970        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
1971
1972        // mimic a ramp inserted after start
1973        // i.e. setTimeout(() => param.linearRampToValueAtTime(10, now + 10)), 10 * 1000);
1974
1975        // no event in timeline here, i.e. length is 1
1976        let vs = render.compute_intrinsic_values(0., 1., 10);
1977        assert_float_eq!(vs, &[0.; 1][..], abs_all <= 0.);
1978
1979        // implicitly insert a SetValue event at time 10
1980        render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(10.0, 20.0));
1981
1982        let vs = render.compute_intrinsic_values(10., 1., 10);
1983        assert_float_eq!(
1984            vs,
1985            &[0., 1., 2., 3., 4., 5., 6., 7., 8., 9.][..],
1986            abs_all <= 0.
1987        );
1988
1989        // ramp finishes on first value of this block, i.e. length is 10
1990        let vs = render.compute_intrinsic_values(20., 1., 10);
1991        assert_float_eq!(vs, &[10.; 10][..], abs_all <= 0.);
1992    }
1993
1994    #[test]
1995    fn test_linear_ramp_arate_multiple_blocks() {
1996        // regression test for issue #9
1997        let context = OfflineAudioContext::new(1, 1, 48000.);
1998
1999        let opts = AudioParamDescriptor {
2000            name: String::new(),
2001            automation_rate: AutomationRate::A,
2002            default_value: 0.,
2003            min_value: -20.,
2004            max_value: 20.,
2005        };
2006        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2007
2008        // ramp to 20 from t = 0 to t = 20
2009        render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(20.0, 20.0));
2010
2011        // first quantum t = 0..10
2012        let vs = render.compute_intrinsic_values(0., 1., 10);
2013        assert_float_eq!(
2014            vs,
2015            &[0., 1., 2., 3., 4., 5., 6., 7., 8., 9.][..],
2016            abs_all <= 0.
2017        );
2018        assert_float_eq!(param.value(), 0., abs <= 0.);
2019
2020        // next quantum t = 10..20
2021        let vs = render.compute_intrinsic_values(10., 1., 10);
2022        assert_float_eq!(
2023            vs,
2024            &[10., 11., 12., 13., 14., 15., 16., 17., 18., 19.][..],
2025            abs_all <= 0.
2026        );
2027        assert_float_eq!(param.value(), 10., abs <= 0.);
2028
2029        // ramp finished t = 20..30
2030        let vs = render.compute_intrinsic_values(20., 1., 10);
2031        assert_float_eq!(vs, &[20.0; 10][..], abs_all <= 0.);
2032        assert_float_eq!(param.value(), 20., abs <= 0.);
2033    }
2034
2035    #[test]
2036    fn test_linear_ramp_krate_multiple_blocks() {
2037        // regression test for issue #9
2038        let context = OfflineAudioContext::new(1, 1, 48000.);
2039
2040        {
2041            let opts = AudioParamDescriptor {
2042                name: String::new(),
2043                automation_rate: AutomationRate::K,
2044                default_value: 0.,
2045                min_value: -20.,
2046                max_value: 20.,
2047            };
2048            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2049
2050            // ramp to 20 from t = 0 to t = 20
2051            render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(20.0, 20.0));
2052            // first quantum t = 0..10
2053            let vs = render.compute_intrinsic_values(0., 1., 10);
2054            assert_float_eq!(vs, &[0.; 1][..], abs_all <= 0.);
2055            assert_float_eq!(param.value(), 0., abs <= 0.);
2056            // next quantum t = 10..20
2057            let vs = render.compute_intrinsic_values(10., 1., 10);
2058            assert_float_eq!(vs, &[10.; 1][..], abs_all <= 0.);
2059            assert_float_eq!(param.value(), 10., abs <= 0.);
2060            // ramp finished t = 20..30
2061            let vs = render.compute_intrinsic_values(20., 1., 10);
2062            assert_float_eq!(vs, &[20.0; 1][..], abs_all <= 0.);
2063            assert_float_eq!(param.value(), 20., abs <= 0.);
2064        }
2065
2066        {
2067            // finish in the middle of a block
2068            let opts = AudioParamDescriptor {
2069                name: String::new(),
2070                automation_rate: AutomationRate::K,
2071                default_value: 0.,
2072                min_value: -20.,
2073                max_value: 20.,
2074            };
2075            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2076
2077            // ramp to 20 from t = 0 to t = 20
2078            render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(15.0, 15.0));
2079            // first quantum t = 0..10
2080            let vs = render.compute_intrinsic_values(0., 1., 10);
2081            assert_float_eq!(vs, &[0.; 1][..], abs_all <= 0.);
2082            assert_float_eq!(param.value(), 0., abs <= 0.);
2083            // next quantum t = 10..20
2084            let vs = render.compute_intrinsic_values(10., 1., 10);
2085            assert_float_eq!(vs, &[10.; 1][..], abs_all <= 0.);
2086            assert_float_eq!(param.value(), 10., abs <= 0.);
2087            // ramp finished t = 20..30
2088            let vs = render.compute_intrinsic_values(20., 1., 10);
2089            assert_float_eq!(vs, &[15.0; 1][..], abs_all <= 0.);
2090            assert_float_eq!(param.value(), 15., abs <= 0.);
2091        }
2092    }
2093
2094    #[test]
2095    fn test_linear_ramp_start_time() {
2096        let context = OfflineAudioContext::new(1, 1, 48000.);
2097
2098        let opts = AudioParamDescriptor {
2099            name: String::new(),
2100            automation_rate: AutomationRate::A,
2101            default_value: 0.,
2102            min_value: -10.,
2103            max_value: 10.,
2104        };
2105        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2106
2107        render.handle_incoming_event(param.set_value_at_time_raw(1., 0.));
2108        render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(-1., 10.));
2109        let vs = render.compute_intrinsic_values(0., 1., 10);
2110        assert_float_eq!(
2111            vs,
2112            &[1., 0.8, 0.6, 0.4, 0.2, 0., -0.2, -0.4, -0.6, -0.8][..],
2113            abs_all <= 1e-7
2114        );
2115
2116        let vs = render.compute_intrinsic_values(10., 1., 10);
2117        assert_float_eq!(vs, &[-1.; 10][..], abs_all <= 0.);
2118
2119        // start time should be end time of last event, i.e. 10.
2120        render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(1., 30.));
2121
2122        let vs = render.compute_intrinsic_values(20., 1., 10);
2123        assert_float_eq!(
2124            vs,
2125            &[0., 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9][..],
2126            abs_all <= 1e-7
2127        );
2128    }
2129
2130    #[test]
2131    fn test_exponential_ramp_a_rate() {
2132        let context = OfflineAudioContext::new(1, 1, 48000.);
2133
2134        let opts = AudioParamDescriptor {
2135            name: String::new(),
2136            automation_rate: AutomationRate::A,
2137            default_value: 0.,
2138            min_value: 0.,
2139            max_value: 1.,
2140        };
2141        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2142
2143        // set to 0.0001 at t=0 (0. is a special case)
2144        render.handle_incoming_event(param.set_value_at_time_raw(0.0001, 0.));
2145        // ramp to 1 from t = 0 to t = 10
2146        render.handle_incoming_event(param.exponential_ramp_to_value_at_time_raw(1.0, 10.));
2147
2148        // compute resulting buffer:
2149        // v(t) = v1*(v2/v1)^((t-t1)/(t2-t1))
2150        let mut res = Vec::<f32>::with_capacity(10);
2151        let start: f32 = 0.0001;
2152        let end: f32 = 1.;
2153
2154        for t in 0..10 {
2155            let value = start * (end / start).powf(t as f32 / 10.);
2156            res.push(value);
2157        }
2158
2159        let vs = render.compute_intrinsic_values(0., 1., 10);
2160        assert_float_eq!(vs, &res[..], abs_all <= 0.);
2161
2162        let vs = render.compute_intrinsic_values(10., 1., 10);
2163        assert_float_eq!(vs, &[1.0; 10][..], abs_all <= 0.);
2164    }
2165
2166    #[test]
2167    fn test_exponential_ramp_a_rate_multiple_blocks() {
2168        let context = OfflineAudioContext::new(1, 1, 48000.);
2169
2170        let opts = AudioParamDescriptor {
2171            name: String::new(),
2172            automation_rate: AutomationRate::A,
2173            default_value: 0.,
2174            min_value: 0.,
2175            max_value: 1.,
2176        };
2177        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2178
2179        let start: f32 = 0.0001; // use 0.0001 as 0. is a special case
2180        let end: f32 = 1.;
2181        render.handle_incoming_event(param.set_value_at_time_raw(start, 3.));
2182        // ramp to 1 from t = 3. to t = 13.
2183        render.handle_incoming_event(param.exponential_ramp_to_value_at_time_raw(end, 13.));
2184
2185        // compute resulting buffer:
2186        let mut res = vec![0.; 3];
2187        // set_value is implicit here as this is the first value of the computed ramp
2188        // exponential ramp (v(t) = v1*(v2/v1)^((t-t1)/(t2-t1)))
2189        for t in 0..10 {
2190            let value = start * (end / start).powf(t as f32 / 10.);
2191            res.push(value);
2192        }
2193        // fill remaining with target value
2194        res.append(&mut vec![1.; 7]);
2195
2196        let vs = render.compute_intrinsic_values(0., 1., 10);
2197        assert_float_eq!(vs, &res[0..10], abs_all <= 0.);
2198        assert_float_eq!(param.value(), res[0], abs <= 0.);
2199
2200        let vs = render.compute_intrinsic_values(10., 1., 10);
2201        assert_float_eq!(vs, &res[10..20], abs_all <= 0.);
2202        assert_float_eq!(param.value(), res[10], abs <= 0.);
2203    }
2204
2205    #[test]
2206    fn test_exponential_ramp_a_rate_zero_and_opposite_target() {
2207        let context = OfflineAudioContext::new(1, 1, 48000.);
2208
2209        {
2210            // zero target
2211            let opts = AudioParamDescriptor {
2212                name: String::new(),
2213                automation_rate: AutomationRate::A,
2214                default_value: 0.,
2215                min_value: 0.,
2216                max_value: 1.,
2217            };
2218            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2219
2220            // set v=0. at t=0 (0. is a special case)
2221            render.handle_incoming_event(param.set_value_at_time_raw(0., 0.));
2222            // ramp to 1 from t=0 to t=5 -> should behave as a set target at t=5
2223            render.handle_incoming_event(param.exponential_ramp_to_value_at_time_raw(1.0, 5.));
2224
2225            let vs = render.compute_intrinsic_values(0., 1., 10);
2226            assert_float_eq!(
2227                vs,
2228                &[0., 0., 0., 0., 0., 1., 1., 1., 1., 1.][..],
2229                abs_all <= 0.
2230            );
2231        }
2232
2233        {
2234            // opposite signs
2235            let opts = AudioParamDescriptor {
2236                name: String::new(),
2237                automation_rate: AutomationRate::A,
2238                default_value: 0.,
2239                min_value: -1.,
2240                max_value: 1.,
2241            };
2242            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2243
2244            // set v=-1. at t=0
2245            render.handle_incoming_event(param.set_value_at_time_raw(-1., 0.));
2246            // ramp to 1 from t=0 to t=5 -> should behave as a set target at t=5
2247            render.handle_incoming_event(param.exponential_ramp_to_value_at_time_raw(1.0, 5.));
2248
2249            let vs = render.compute_intrinsic_values(0., 1., 10);
2250            assert_float_eq!(
2251                vs,
2252                &[-1., -1., -1., -1., -1., 1., 1., 1., 1., 1.][..],
2253                abs_all <= 0.
2254            );
2255        }
2256    }
2257
2258    #[test]
2259    #[should_panic]
2260    fn test_exponential_ramp_to_zero() {
2261        let context = OfflineAudioContext::new(1, 1, 48000.);
2262
2263        let opts = AudioParamDescriptor {
2264            name: String::new(),
2265            automation_rate: AutomationRate::A,
2266            default_value: 1.,
2267            min_value: 0.,
2268            max_value: 1.,
2269        };
2270        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2271        render.handle_incoming_event(param.exponential_ramp_to_value_at_time_raw(0.0, 10.));
2272    }
2273
2274    #[test]
2275    fn test_exponential_ramp_k_rate_multiple_blocks() {
2276        let context = OfflineAudioContext::new(1, 1, 48000.);
2277
2278        let opts = AudioParamDescriptor {
2279            name: String::new(),
2280            automation_rate: AutomationRate::K,
2281            default_value: 0.,
2282            min_value: 0.,
2283            max_value: 1.,
2284        };
2285        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2286
2287        let start: f32 = 0.0001; // use 0.0001 as 0. is a special case
2288        let end: f32 = 1.;
2289        render.handle_incoming_event(param.set_value_at_time_raw(start, 3.));
2290        // ramp to 1 from t = 3. to t = 13.
2291        render.handle_incoming_event(param.exponential_ramp_to_value_at_time_raw(end, 13.));
2292
2293        // compute resulting buffer:
2294        let mut res = vec![0.; 3];
2295        // set_value is implicit here as this is the first value of the computed ramp
2296        // exponential ramp (v(t) = v1*(v2/v1)^((t-t1)/(t2-t1)))
2297        for t in 0..10 {
2298            let value = start * (end / start).powf(t as f32 / 10.);
2299            res.push(value);
2300        }
2301        // fill remaining with target value
2302        res.append(&mut vec![1.; 7]);
2303
2304        // recreate k-rate blocks from computed values
2305        let vs = render.compute_intrinsic_values(0., 1., 10);
2306        assert_float_eq!(vs, &[res[0]; 1][..], abs_all <= 0.);
2307
2308        let vs = render.compute_intrinsic_values(10., 1., 10);
2309        assert_float_eq!(vs, &[res[10]; 1][..], abs_all <= 0.);
2310
2311        let vs = render.compute_intrinsic_values(20., 1., 10);
2312        assert_float_eq!(vs, &[1.; 1][..], abs_all <= 0.);
2313    }
2314
2315    #[test]
2316    fn test_exponential_ramp_k_rate_zero_and_opposite_target() {
2317        let context = OfflineAudioContext::new(1, 1, 48000.);
2318
2319        {
2320            // zero target
2321            let opts = AudioParamDescriptor {
2322                name: String::new(),
2323                automation_rate: AutomationRate::K,
2324                default_value: 0.,
2325                min_value: 0.,
2326                max_value: 1.,
2327            };
2328            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2329
2330            // ramp to 1 from t=0 to t=5 -> should behave as a set target at t=5
2331            render.handle_incoming_event(param.exponential_ramp_to_value_at_time_raw(1.0, 5.));
2332
2333            let vs = render.compute_intrinsic_values(0., 1., 10);
2334            assert_float_eq!(vs, &[0.; 1][..], abs_all <= 0.);
2335
2336            let vs = render.compute_intrinsic_values(10., 1., 10);
2337            assert_float_eq!(vs, &[1.; 1][..], abs_all <= 0.);
2338        }
2339
2340        {
2341            // opposite signs
2342            let opts = AudioParamDescriptor {
2343                name: String::new(),
2344                automation_rate: AutomationRate::K,
2345                default_value: -1.,
2346                min_value: -1.,
2347                max_value: 1.,
2348            };
2349            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2350
2351            // ramp to 1 from t=0 to t=5 -> should behave as a set target at t=5
2352            render.handle_incoming_event(param.exponential_ramp_to_value_at_time_raw(1.0, 5.));
2353
2354            let vs = render.compute_intrinsic_values(0., 1., 10);
2355            assert_float_eq!(vs, &[-1.; 1][..], abs_all <= 0.);
2356
2357            let vs = render.compute_intrinsic_values(10., 1., 10);
2358            assert_float_eq!(vs, &[1.; 1][..], abs_all <= 0.);
2359        }
2360    }
2361
2362    #[test]
2363    fn test_exponential_ramp_start_time() {
2364        let context = OfflineAudioContext::new(1, 1, 48000.);
2365
2366        let opts = AudioParamDescriptor {
2367            name: String::new(),
2368            automation_rate: AutomationRate::A,
2369            default_value: 0.,
2370            min_value: -10.,
2371            max_value: 10.,
2372        };
2373        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2374
2375        render.handle_incoming_event(param.set_value_at_time_raw(0., 0.));
2376        render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(1., 10.));
2377
2378        let vs = render.compute_intrinsic_values(0., 1., 10);
2379        assert_float_eq!(
2380            vs,
2381            &[0., 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9][..],
2382            abs_all <= 1e-7
2383        );
2384
2385        let vs = render.compute_intrinsic_values(10., 1., 10);
2386        assert_float_eq!(vs, &[1.; 10][..], abs_all <= 0.);
2387
2388        // start time should be end time of last event, i.e. 10.
2389        render.handle_incoming_event(param.exponential_ramp_to_value_at_time_raw(0.0001, 30.));
2390        let vs = render.compute_intrinsic_values(20., 1., 10);
2391        // compute expected on 20 samples, the 10 last ones should be in vs
2392        let start: f32 = 1.;
2393        let end: f32 = 0.0001;
2394        let mut res = [0.; 20];
2395        for (t, v) in res.iter_mut().enumerate() {
2396            *v = start * (end / start).powf(t as f32 / 20.);
2397        }
2398
2399        assert_float_eq!(vs, &res[10..], abs_all <= 1e-7);
2400    }
2401
2402    #[test]
2403    fn test_set_target_at_time_a_rate() {
2404        let context = OfflineAudioContext::new(1, 1, 48000.);
2405
2406        {
2407            let opts = AudioParamDescriptor {
2408                name: String::new(),
2409                automation_rate: AutomationRate::A,
2410                default_value: 0.,
2411                min_value: 0.,
2412                max_value: 1.,
2413            };
2414            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2415            // ๐‘ฃ(๐‘ก) = ๐‘‰1 + (๐‘‰0 โˆ’ ๐‘‰1) * ๐‘’^โˆ’((๐‘กโˆ’๐‘‡0) / ๐œ)
2416            let v0: f32 = 0.;
2417            let v1: f32 = 1.;
2418            let t0: f64 = 0.;
2419            let time_constant: f64 = 1.;
2420
2421            render.handle_incoming_event(param.set_value_at_time_raw(v0, t0));
2422            render.handle_incoming_event(param.set_target_at_time_raw(v1, t0, time_constant));
2423            let vs = render.compute_intrinsic_values(0., 1., 10);
2424
2425            let mut res = Vec::<f32>::with_capacity(10);
2426            for t in 0..10 {
2427                let val = v1 + (v0 - v1) * (-((t as f64 - t0) / time_constant)).exp() as f32;
2428                res.push(val);
2429            }
2430
2431            assert_float_eq!(vs, &res[..], abs_all <= 0.);
2432        }
2433
2434        {
2435            // implicit SetValue if SetTarget is first event
2436            let opts = AudioParamDescriptor {
2437                name: String::new(),
2438                automation_rate: AutomationRate::A,
2439                default_value: 0.,
2440                min_value: 0.,
2441                max_value: 1.,
2442            };
2443            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2444            // ๐‘ฃ(๐‘ก) = ๐‘‰1 + (๐‘‰0 โˆ’ ๐‘‰1) * ๐‘’^โˆ’((๐‘กโˆ’๐‘‡0) / ๐œ)
2445            let v0: f32 = 0.; // will be implicitly set in param (see default_value)
2446            let v1: f32 = 1.;
2447            let t0: f64 = 0.;
2448            let time_constant: f64 = 1.;
2449
2450            render.handle_incoming_event(param.set_target_at_time_raw(v1, t0, time_constant));
2451            let vs = render.compute_intrinsic_values(0., 1., 10);
2452
2453            let mut res = Vec::<f32>::with_capacity(10);
2454            for t in 0..10 {
2455                let val = v1 + (v0 - v1) * (-((t as f64 - t0) / time_constant)).exp() as f32;
2456                res.push(val);
2457            }
2458
2459            assert_float_eq!(vs, &res[..], abs_all <= 0.);
2460        }
2461
2462        {
2463            // start later in block with arbitrary values
2464            let opts = AudioParamDescriptor {
2465                name: String::new(),
2466                automation_rate: AutomationRate::A,
2467                default_value: 0.,
2468                min_value: 0.,
2469                max_value: 100.,
2470            };
2471            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2472            // ๐‘ฃ(๐‘ก) = ๐‘‰1 + (๐‘‰0 โˆ’ ๐‘‰1) * ๐‘’^โˆ’((๐‘กโˆ’๐‘‡0) / ๐œ)
2473            let v0: f32 = 1.;
2474            let v1: f32 = 42.;
2475            let t0: f64 = 1.;
2476            let time_constant: f64 = 2.1;
2477
2478            render.handle_incoming_event(param.set_value_at_time_raw(v0, t0));
2479            render.handle_incoming_event(param.set_target_at_time_raw(v1, t0, time_constant));
2480
2481            let mut res = Vec::<f32>::with_capacity(10);
2482            for t in 0..10 {
2483                let val = v1 + (v0 - v1) * (-((t as f64 - t0) / time_constant)).exp() as f32;
2484                res.push(val);
2485            }
2486            // start_time is 1.
2487            res[0] = 0.;
2488
2489            let vs = render.compute_intrinsic_values(0., 1., 10);
2490            assert_float_eq!(vs, &res[..], abs_all <= 0.);
2491        }
2492
2493        {
2494            // handle time_constant == 0.
2495            let opts = AudioParamDescriptor {
2496                name: String::new(),
2497                automation_rate: AutomationRate::A,
2498                default_value: 0.,
2499                min_value: 0.,
2500                max_value: 100.,
2501            };
2502            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2503            render.handle_incoming_event(param.set_target_at_time_raw(1., 1., 0.));
2504
2505            let mut res = [1.; 10];
2506            res[0] = 0.; // start_time is 1.
2507
2508            let vs = render.compute_intrinsic_values(0., 1., 10);
2509            assert_float_eq!(vs, &res[..], abs_all <= 0.);
2510        }
2511    }
2512
2513    #[test]
2514    fn test_set_target_at_time_a_rate_multiple_blocks() {
2515        let context = OfflineAudioContext::new(1, 1, 48000.);
2516
2517        {
2518            let opts = AudioParamDescriptor {
2519                name: String::new(),
2520                automation_rate: AutomationRate::A,
2521                default_value: 0.,
2522                min_value: 0.,
2523                max_value: 2.,
2524            };
2525            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2526            // ๐‘ฃ(๐‘ก) = ๐‘‰1 + (๐‘‰0 โˆ’ ๐‘‰1) * ๐‘’^โˆ’((๐‘กโˆ’๐‘‡0) / ๐œ)
2527            let v0: f32 = 0.;
2528            let v1: f32 = 2.;
2529            let t0: f64 = 0.;
2530            let time_constant: f64 = 1.;
2531            // ramp to 1 from t=0 to t=5 -> should behave as a set target at t=5
2532            render.handle_incoming_event(param.set_value_at_time_raw(v0, t0));
2533            render.handle_incoming_event(param.set_target_at_time_raw(v1, t0, time_constant));
2534
2535            let mut res = Vec::<f32>::with_capacity(20);
2536            for t in 0..20 {
2537                let val = v1 + (v0 - v1) * (-((t as f64 - t0) / time_constant)).exp() as f32;
2538                res.push(val);
2539            }
2540
2541            let vs = render.compute_intrinsic_values(0., 1., 10);
2542            assert_float_eq!(vs, &res[0..10], abs_all <= 0.);
2543
2544            let vs = render.compute_intrinsic_values(10., 1., 10);
2545            assert_float_eq!(vs, &res[10..20], abs_all <= 0.);
2546        }
2547    }
2548
2549    #[test]
2550    fn test_set_target_at_time_a_rate_followed_by_set_value() {
2551        let context = OfflineAudioContext::new(1, 1, 48000.);
2552
2553        {
2554            let opts = AudioParamDescriptor {
2555                name: String::new(),
2556                automation_rate: AutomationRate::A,
2557                default_value: 0.,
2558                min_value: 0.,
2559                max_value: 2.,
2560            };
2561            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2562            // ๐‘ฃ(๐‘ก) = ๐‘‰1 + (๐‘‰0 โˆ’ ๐‘‰1) * ๐‘’^โˆ’((๐‘กโˆ’๐‘‡0) / ๐œ)
2563            let v0: f32 = 0.;
2564            let v1: f32 = 2.;
2565            let t0: f64 = 0.;
2566            let time_constant: f64 = 1.;
2567
2568            render.handle_incoming_event(param.set_value_at_time_raw(v0, t0));
2569            render.handle_incoming_event(param.set_target_at_time_raw(v1, t0, time_constant));
2570            render.handle_incoming_event(param.set_value_at_time_raw(0.5, 15.));
2571
2572            let mut res = Vec::<f32>::with_capacity(20);
2573
2574            for t in 0..15 {
2575                let val = v1 + (v0 - v1) * (-((t as f64 - t0) / time_constant)).exp() as f32;
2576                res.push(val);
2577            }
2578
2579            res.resize(20, 0.5);
2580
2581            let vs = render.compute_intrinsic_values(0., 1., 10);
2582            assert_float_eq!(vs, &res[0..10], abs_all <= 0.);
2583
2584            let vs = render.compute_intrinsic_values(10., 1., 10);
2585            assert_float_eq!(vs, &res[10..20], abs_all <= 0.);
2586        }
2587    }
2588
2589    #[test]
2590    fn test_set_target_at_time_ends_at_threshold() {
2591        let context = OfflineAudioContext::new(1, 1, 48000.);
2592        let opts = AudioParamDescriptor {
2593            name: String::new(),
2594            automation_rate: AutomationRate::A,
2595            default_value: 0.,
2596            min_value: 0.,
2597            max_value: 2.,
2598        };
2599        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2600
2601        render.handle_incoming_event(param.set_value_at_time_raw(1., 0.));
2602        render.handle_incoming_event(param.set_target_at_time_raw(0., 1., 0.2));
2603
2604        let vs = render.compute_intrinsic_values(0., 1., 128);
2605        for v in vs.iter() {
2606            assert!(!v.is_subnormal());
2607        }
2608
2609        // check peek() has been replaced with set_value event
2610        let peek = render.event_timeline.peek();
2611        assert_eq!(
2612            peek.unwrap().event_type,
2613            AudioParamEventType::SetValueAtTime
2614        );
2615
2616        // this buffer should be filled with target values
2617        let vs = render.compute_intrinsic_values(10., 1., 128);
2618        assert_float_eq!(vs[..], [0.; 128], abs_all <= 0.);
2619    }
2620
2621    #[test]
2622    fn test_set_target_at_time_waits_for_start_time() {
2623        let context = OfflineAudioContext::new(1, 1, 48000.);
2624        let opts = AudioParamDescriptor {
2625            name: String::new(),
2626            automation_rate: AutomationRate::A,
2627            default_value: 0.,
2628            min_value: 0.,
2629            max_value: 2.,
2630        };
2631        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2632
2633        render.handle_incoming_event(param.set_value_at_time_raw(1., 0.));
2634        render.handle_incoming_event(param.set_target_at_time_raw(0., 5., 1.));
2635
2636        let vs = render.compute_intrinsic_values(0., 1., 10);
2637        assert_float_eq!(vs[0], 1., abs <= 0.);
2638        assert_float_eq!(vs[1], 1., abs <= 0.);
2639        assert_float_eq!(vs[2], 1., abs <= 0.);
2640        assert_float_eq!(vs[3], 1., abs <= 0.);
2641        assert_float_eq!(vs[4], 1., abs <= 0.);
2642        assert_float_eq!(vs[5], 1., abs <= 0.);
2643    }
2644
2645    #[test]
2646    fn test_set_target_at_time_a_rate_followed_by_ramp() {
2647        let context = OfflineAudioContext::new(1, 1, 48000.);
2648        {
2649            let opts = AudioParamDescriptor {
2650                name: String::new(),
2651                automation_rate: AutomationRate::A,
2652                default_value: 0.,
2653                min_value: 0.,
2654                max_value: 10.,
2655            };
2656            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2657            // ๐‘ฃ(๐‘ก) = ๐‘‰1 + (๐‘‰0 โˆ’ ๐‘‰1) * ๐‘’^โˆ’((๐‘กโˆ’๐‘‡0) / ๐œ)
2658            let v0: f32 = 0.;
2659            let v1: f32 = 2.;
2660            let t0: f64 = 0.;
2661            let time_constant: f64 = 10.;
2662
2663            render.handle_incoming_event(param.set_value_at_time_raw(v0, t0));
2664            render.handle_incoming_event(param.set_target_at_time_raw(v1, t0, time_constant));
2665
2666            let mut res = Vec::<f32>::with_capacity(20);
2667
2668            for t in 0..11 {
2669                // we compute the 10th elements as it will be the start value of the ramp
2670                let val = v1 + (v0 - v1) * (-((t as f64 - t0) / time_constant)).exp() as f32;
2671                res.push(val);
2672            }
2673
2674            let vs = render.compute_intrinsic_values(0., 1., 10);
2675            assert_float_eq!(vs, &res[0..10], abs_all <= 0.);
2676
2677            // ramp
2678            let v0 = res.pop().unwrap(); // v0 is defined by the SetTarget
2679            let v1 = 10.;
2680            let t0 = 10.;
2681            let t1 = 20.;
2682
2683            render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(v1, t1));
2684
2685            for t in 10..20 {
2686                let time = t as f64;
2687                let value = v0 + (v1 - v0) * (time - t0) as f32 / (t1 - t0) as f32;
2688                res.push(value);
2689            }
2690
2691            let vs = render.compute_intrinsic_values(10., 1., 10);
2692            assert_float_eq!(vs, &res[10..20], abs_all <= 1.0e-6);
2693            // ramp ended
2694            let vs = render.compute_intrinsic_values(20., 1., 10);
2695            assert_float_eq!(vs, &[v1; 10][..], abs_all <= 0.);
2696        }
2697    }
2698
2699    #[test]
2700    fn test_set_target_at_time_k_rate_multiple_blocks() {
2701        let context = OfflineAudioContext::new(1, 1, 48000.);
2702
2703        {
2704            let opts = AudioParamDescriptor {
2705                name: String::new(),
2706                automation_rate: AutomationRate::K,
2707                default_value: 0.,
2708                min_value: 0.,
2709                max_value: 2.,
2710            };
2711            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2712            // ๐‘ฃ(๐‘ก) = ๐‘‰1 + (๐‘‰0 โˆ’ ๐‘‰1) * ๐‘’^โˆ’((๐‘กโˆ’๐‘‡0) / ๐œ)
2713            let v0: f32 = 0.;
2714            let v1: f32 = 2.;
2715            let t0: f64 = 0.;
2716            let time_constant: f64 = 1.;
2717            // ramp to 1 from t=0 to t=5 -> should behave as a set target at t=5
2718            render.handle_incoming_event(param.set_value_at_time_raw(v0, t0));
2719            render.handle_incoming_event(param.set_target_at_time_raw(v1, t0, time_constant));
2720
2721            let mut res = Vec::<f32>::with_capacity(20);
2722            for t in 0..20 {
2723                let val = v1 + (v0 - v1) * (-((t as f64 - t0) / time_constant)).exp() as f32;
2724                res.push(val);
2725            }
2726
2727            let vs = render.compute_intrinsic_values(0., 1., 10);
2728            assert_float_eq!(vs, &[res[0]; 1][..], abs_all <= 0.);
2729
2730            let vs = render.compute_intrinsic_values(10., 1., 10);
2731            assert_float_eq!(vs, &[res[10]; 1][..], abs_all <= 0.);
2732        }
2733    }
2734
2735    #[test]
2736    // regression test for bcebfe6
2737    fn test_set_target_at_time_snap_to_value() {
2738        let context = OfflineAudioContext::new(1, 1, 48000.);
2739        let opts = AudioParamDescriptor {
2740            name: String::new(),
2741            automation_rate: AutomationRate::A,
2742            default_value: 0.,
2743            min_value: 0.,
2744            max_value: 1.,
2745        };
2746        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2747        let v0: f32 = 1.;
2748        let v1: f32 = 0.;
2749        let t0: f64 = 0.;
2750        let time_constant: f64 = 1.;
2751
2752        render.handle_incoming_event(param.set_value_at_time_raw(v0, t0));
2753        render.handle_incoming_event(param.set_target_at_time_raw(v1, t0, time_constant));
2754
2755        let mut res = [0.; 30];
2756        // ๐‘ฃ(๐‘ก) = ๐‘‰1 + (๐‘‰0 โˆ’ ๐‘‰1) * ๐‘’^โˆ’((๐‘กโˆ’๐‘‡0) / ๐œ)
2757        res.iter_mut().enumerate().for_each(|(t, r)| {
2758            *r = v1 + (v0 - v1) * (-((t as f64 - t0) / time_constant)).exp() as f32;
2759        });
2760
2761        let vs = render.compute_intrinsic_values(0., 1., 10);
2762        assert_float_eq!(vs, &res[..10], abs_all <= 0.);
2763
2764        let vs = render.compute_intrinsic_values(10., 1., 10);
2765        assert_float_eq!(vs, &res[10..20], abs_all <= 0.);
2766
2767        // the distance between the target value and the value just after this block
2768        // is smaller than SNAP_TO_TARGET (i.e. 1e-10)
2769        let vs = render.compute_intrinsic_values(20., 1., 10);
2770        assert_float_eq!(vs, &res[20..30], abs_all <= 0.);
2771
2772        // then this block should be [0.; 10]
2773        let vs = render.compute_intrinsic_values(30., 1., 10);
2774        assert_float_eq!(vs, &[0.; 10][..], abs_all <= 0.);
2775    }
2776
2777    #[test]
2778    fn test_cancel_scheduled_values() {
2779        let context = OfflineAudioContext::new(1, 1, 48000.);
2780
2781        let opts = AudioParamDescriptor {
2782            name: String::new(),
2783            automation_rate: AutomationRate::A,
2784            default_value: 0.,
2785            min_value: 0.,
2786            max_value: 10.,
2787        };
2788        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2789        for t in 0..10 {
2790            render.handle_incoming_event(param.set_value_at_time_raw(t as f32, t as f64));
2791        }
2792
2793        render.handle_incoming_event(param.cancel_scheduled_values_raw(5.));
2794
2795        let vs = render.compute_intrinsic_values(0., 1., 10);
2796        assert_float_eq!(
2797            vs,
2798            &[0., 1., 2., 3., 4., 4., 4., 4., 4., 4.][..],
2799            abs_all <= 0.
2800        );
2801    }
2802
2803    #[test]
2804    fn test_cancel_scheduled_values_ramp() {
2805        let context = OfflineAudioContext::new(1, 1, 48000.);
2806
2807        {
2808            let opts = AudioParamDescriptor {
2809                name: String::new(),
2810                automation_rate: AutomationRate::A,
2811                default_value: 0.,
2812                min_value: 0.,
2813                max_value: 10.,
2814            };
2815            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2816
2817            render.handle_incoming_event(param.set_value_at_time_raw(0., 0.));
2818            render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(10., 10.));
2819            // cancels the ramp, the set value event is kept in timeline
2820            render.handle_incoming_event(param.cancel_scheduled_values_raw(10.));
2821
2822            let vs = render.compute_intrinsic_values(0., 1., 10);
2823            assert_float_eq!(vs, &[0.; 10][..], abs_all <= 0.);
2824        }
2825
2826        // ramp already started, go back to previous value
2827        {
2828            let opts = AudioParamDescriptor {
2829                name: String::new(),
2830                automation_rate: AutomationRate::A,
2831                default_value: 0.,
2832                min_value: 0.,
2833                max_value: 20.,
2834            };
2835            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2836
2837            render.handle_incoming_event(param.set_value_at_time_raw(0., 0.));
2838            render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(20., 20.));
2839
2840            let vs = render.compute_intrinsic_values(0., 1., 10);
2841            assert_float_eq!(
2842                vs,
2843                &[0., 1., 2., 3., 4., 5., 6., 7., 8., 9.][..],
2844                abs_all <= 0.
2845            );
2846
2847            // the SetValue event has been consumed in first tick and the ramp
2848            // is removed from timeline, no event left in timeline (length is 1)
2849            render.handle_incoming_event(param.cancel_scheduled_values_raw(10.));
2850
2851            let vs = render.compute_intrinsic_values(10., 1., 10);
2852            assert_float_eq!(vs, &[0.; 1][..], abs_all <= 0.);
2853        }
2854
2855        // make sure we can't go into a situation where next_event is a ramp
2856        // and last_event is not defined
2857        // @see - note in CancelScheduledValues insertion in timeline
2858        {
2859            let opts = AudioParamDescriptor {
2860                name: String::new(),
2861                automation_rate: AutomationRate::A,
2862                default_value: 0.,
2863                min_value: 0.,
2864                max_value: 10.,
2865            };
2866            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2867
2868            // the SetValue from param inserted by the Ramp is left in timeline
2869            // i.e. length is 10
2870            render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(10., 10.));
2871            render.handle_incoming_event(param.cancel_scheduled_values_raw(10.)); // cancels the ramp
2872
2873            let vs = render.compute_intrinsic_values(0., 1., 10);
2874            assert_float_eq!(vs, &[0.; 10][..], abs_all <= 0.);
2875        }
2876
2877        {
2878            let opts = AudioParamDescriptor {
2879                name: String::new(),
2880                automation_rate: AutomationRate::A,
2881                default_value: 0.,
2882                min_value: 0.,
2883                max_value: 20.,
2884            };
2885            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2886
2887            render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(20., 20.));
2888
2889            let vs = render.compute_intrinsic_values(0., 1., 10);
2890            assert_float_eq!(
2891                vs,
2892                &[0., 1., 2., 3., 4., 5., 6., 7., 8., 9.][..],
2893                abs_all <= 0.
2894            );
2895
2896            // ramp is removed from timeline, no event left
2897            render.handle_incoming_event(param.cancel_scheduled_values_raw(10.));
2898
2899            let vs = render.compute_intrinsic_values(10., 1., 10);
2900            assert_float_eq!(vs, &[0.; 1][..], abs_all <= 0.);
2901        }
2902    }
2903
2904    #[test]
2905    fn test_cancel_and_hold() {
2906        let context = OfflineAudioContext::new(1, 1, 48000.);
2907        {
2908            let opts = AudioParamDescriptor {
2909                name: String::new(),
2910                automation_rate: AutomationRate::A,
2911                default_value: 0.,
2912                min_value: 0.,
2913                max_value: 10.,
2914            };
2915            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2916
2917            render.handle_incoming_event(param.set_value_at_time_raw(1., 1.));
2918            render.handle_incoming_event(param.set_value_at_time_raw(2., 2.));
2919            render.handle_incoming_event(param.set_value_at_time_raw(3., 3.));
2920            render.handle_incoming_event(param.set_value_at_time_raw(4., 4.));
2921            render.handle_incoming_event(param.cancel_and_hold_at_time_raw(2.5));
2922
2923            let vs = render.compute_intrinsic_values(0., 1., 10);
2924            assert_float_eq!(
2925                vs,
2926                &[0., 1., 2., 2., 2., 2., 2., 2., 2., 2.][0..10],
2927                abs_all <= 0.
2928            );
2929        }
2930    }
2931
2932    #[test]
2933    fn test_cancel_and_hold_during_set_target() {
2934        let context = OfflineAudioContext::new(1, 1, 48000.);
2935
2936        {
2937            let opts = AudioParamDescriptor {
2938                name: String::new(),
2939                automation_rate: AutomationRate::A,
2940                default_value: 0.,
2941                min_value: 0.,
2942                max_value: 2.,
2943            };
2944            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2945            // ๐‘ฃ(๐‘ก) = ๐‘‰1 + (๐‘‰0 โˆ’ ๐‘‰1) * ๐‘’^โˆ’((๐‘กโˆ’๐‘‡0) / ๐œ)
2946            let v0: f32 = 0.;
2947            let v1: f32 = 2.;
2948            let t0: f64 = 0.;
2949            let time_constant: f64 = 1.;
2950
2951            render.handle_incoming_event(param.set_value_at_time_raw(v0, t0));
2952            render.handle_incoming_event(param.set_target_at_time_raw(v1, t0, time_constant));
2953            render.handle_incoming_event(param.cancel_and_hold_at_time_raw(15.));
2954
2955            let mut res = Vec::<f32>::with_capacity(20);
2956
2957            // compute index 15 to have hold_value
2958            for t in 0..16 {
2959                let val = v1 + (v0 - v1) * (-((t as f64 - t0) / time_constant)).exp() as f32;
2960                res.push(val);
2961            }
2962
2963            let hold_value = res.pop().unwrap();
2964            res.resize(20, hold_value);
2965
2966            let vs = render.compute_intrinsic_values(0., 1., 10);
2967            assert_float_eq!(vs, &res[0..10], abs_all <= 0.);
2968
2969            let vs = render.compute_intrinsic_values(10., 1., 10);
2970            assert_float_eq!(vs, &res[10..20], abs_all <= 0.);
2971        }
2972    }
2973
2974    #[test]
2975    fn test_cancel_and_hold_during_linear_ramp() {
2976        let context = OfflineAudioContext::new(1, 1, 48000.);
2977
2978        {
2979            let opts = AudioParamDescriptor {
2980                name: String::new(),
2981                automation_rate: AutomationRate::A,
2982                default_value: 0.,
2983                min_value: 0.,
2984                max_value: 10.,
2985            };
2986            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
2987
2988            render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(10., 10.));
2989            render.handle_incoming_event(param.cancel_and_hold_at_time_raw(5.));
2990
2991            let vs = render.compute_intrinsic_values(0., 1., 10);
2992            assert_float_eq!(
2993                vs,
2994                &[0., 1., 2., 3., 4., 5., 5., 5., 5., 5.][0..10],
2995                abs_all <= 0.
2996            );
2997        }
2998
2999        {
3000            // cancel between two samples
3001            let opts = AudioParamDescriptor {
3002                name: String::new(),
3003                automation_rate: AutomationRate::A,
3004                default_value: 0.,
3005                min_value: 0.,
3006                max_value: 10.,
3007            };
3008            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
3009
3010            render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(10., 10.));
3011            render.handle_incoming_event(param.cancel_and_hold_at_time_raw(4.5));
3012
3013            let vs = render.compute_intrinsic_values(0., 1., 10);
3014            assert_float_eq!(
3015                vs,
3016                &[0., 1., 2., 3., 4., 4.5, 4.5, 4.5, 4.5, 4.5][0..10],
3017                abs_all <= 0.
3018            );
3019        }
3020    }
3021
3022    #[test]
3023    fn test_cancel_and_hold_during_exponential_ramp() {
3024        let context = OfflineAudioContext::new(1, 1, 48000.);
3025
3026        {
3027            let opts = AudioParamDescriptor {
3028                name: String::new(),
3029                automation_rate: AutomationRate::A,
3030                default_value: 0.,
3031                min_value: 0.,
3032                max_value: 10.,
3033            };
3034            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
3035
3036            // set to 0.0001 at t=0 (0. is a special case)
3037            render.handle_incoming_event(param.set_value_at_time_raw(0.0001, 0.));
3038            render.handle_incoming_event(param.exponential_ramp_to_value_at_time_raw(1.0, 10.));
3039            render.handle_incoming_event(param.cancel_and_hold_at_time_raw(5.));
3040
3041            // compute resulting buffer:
3042            // v(t) = v1*(v2/v1)^((t-t1)/(t2-t1))
3043            let mut res = Vec::<f32>::with_capacity(10);
3044            let start: f32 = 0.0001;
3045            let end: f32 = 1.;
3046
3047            for t in 0..6 {
3048                let value = start * (end / start).powf(t as f32 / 10.);
3049                res.push(value);
3050            }
3051
3052            let hold_value = res.pop().unwrap();
3053            res.resize(10, hold_value);
3054
3055            let vs = render.compute_intrinsic_values(0., 1., 10);
3056            assert_float_eq!(vs, &res[..], abs_all <= 0.);
3057        }
3058
3059        {
3060            // cancel between 2 samples
3061            let opts = AudioParamDescriptor {
3062                name: String::new(),
3063                automation_rate: AutomationRate::A,
3064                default_value: 0.,
3065                min_value: 0.,
3066                max_value: 10.,
3067            };
3068            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
3069
3070            // set to 0.0001 at t=0 (0. is a special case)
3071            render.handle_incoming_event(param.set_value_at_time_raw(0.0001, 0.));
3072            render.handle_incoming_event(param.exponential_ramp_to_value_at_time_raw(1.0, 10.));
3073            render.handle_incoming_event(param.cancel_and_hold_at_time_raw(4.5));
3074
3075            // compute resulting buffer:
3076            // v(t) = v1*(v2/v1)^((t-t1)/(t2-t1))
3077            let mut res = Vec::<f32>::with_capacity(10);
3078            let start: f32 = 0.0001;
3079            let end: f32 = 1.;
3080
3081            for t in 0..5 {
3082                let value = start * (end / start).powf(t as f32 / 10.);
3083                res.push(value);
3084            }
3085
3086            let hold_value = start * (end / start).powf(4.5 / 10.);
3087            res.resize(10, hold_value);
3088
3089            let vs = render.compute_intrinsic_values(0., 1., 10);
3090            assert_float_eq!(vs, &res[..], abs_all <= 0.);
3091        }
3092    }
3093
3094    #[test]
3095    fn test_cancel_and_hold_during_set_value_curve() {
3096        let context = OfflineAudioContext::new(1, 1, 48000.);
3097
3098        {
3099            let opts = AudioParamDescriptor {
3100                name: String::new(),
3101                automation_rate: AutomationRate::A,
3102                default_value: 0.,
3103                min_value: 0.,
3104                max_value: 2.,
3105            };
3106            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
3107
3108            let curve = [0., 0.5, 1., 0.5, 0.];
3109            render.handle_incoming_event(param.set_value_curve_at_time_raw(&curve[..], 0., 10.));
3110            render.handle_incoming_event(param.cancel_and_hold_at_time_raw(5.));
3111
3112            let vs = render.compute_intrinsic_values(0., 1., 10);
3113            assert_float_eq!(
3114                vs,
3115                &[0., 0.2, 0.4, 0.6, 0.8, 1., 1., 1., 1., 1.][..],
3116                abs_all <= 1e-7
3117            );
3118        }
3119
3120        {
3121            // sub-sample
3122            let opts = AudioParamDescriptor {
3123                name: String::new(),
3124                automation_rate: AutomationRate::A,
3125                default_value: 0.,
3126                min_value: 0.,
3127                max_value: 2.,
3128            };
3129            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
3130
3131            let curve = [0., 0.5, 1., 0.5, 0.];
3132            render.handle_incoming_event(param.set_value_curve_at_time_raw(&curve[..], 0., 10.));
3133            render.handle_incoming_event(param.cancel_and_hold_at_time_raw(4.5));
3134
3135            let vs = render.compute_intrinsic_values(0., 1., 10);
3136            assert_float_eq!(
3137                vs,
3138                &[0., 0.2, 0.4, 0.6, 0.8, 0.9, 0.9, 0.9, 0.9, 0.9][..],
3139                abs_all <= 1e-7
3140            );
3141        }
3142    }
3143
3144    #[test]
3145    fn test_set_value_curve_at_time_a_rate() {
3146        let context = OfflineAudioContext::new(1, 1, 48000.);
3147
3148        let opts = AudioParamDescriptor {
3149            name: String::new(),
3150            automation_rate: AutomationRate::A,
3151            default_value: 0.,
3152            min_value: 0.,
3153            max_value: 10.,
3154        };
3155        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
3156
3157        // set to 0.0001 at t=0 (0. is a special case)
3158        let curve = [0., 0.5, 1., 0.5, 0.];
3159        render.handle_incoming_event(param.set_value_curve_at_time_raw(&curve[..], 0., 10.));
3160
3161        let vs = render.compute_intrinsic_values(0., 1., 10);
3162        assert_float_eq!(
3163            vs,
3164            &[0., 0.2, 0.4, 0.6, 0.8, 1., 0.8, 0.6, 0.4, 0.2][..],
3165            abs_all <= 1e-7
3166        );
3167
3168        let vs = render.compute_intrinsic_values(10., 1., 10);
3169        assert_float_eq!(vs, &[0.; 10][..], abs_all <= 0.);
3170    }
3171
3172    #[test]
3173    fn test_set_value_curve_at_time_a_rate_multiple_frames() {
3174        let context = OfflineAudioContext::new(1, 1, 48000.);
3175
3176        let opts = AudioParamDescriptor {
3177            name: String::new(),
3178            automation_rate: AutomationRate::A,
3179            default_value: 0.,
3180            min_value: 0.,
3181            max_value: 10.,
3182        };
3183        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
3184
3185        // set to 0.0001 at t=0 (0. is a special case)
3186        let curve = [0., 0.5, 1., 0.5, 0.];
3187        render.handle_incoming_event(param.set_value_curve_at_time_raw(&curve[..], 0., 20.));
3188
3189        let vs = render.compute_intrinsic_values(0., 1., 10);
3190        assert_float_eq!(
3191            vs,
3192            &[0., 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9][..],
3193            abs_all <= 1e-7
3194        );
3195
3196        let vs = render.compute_intrinsic_values(10., 1., 10);
3197        assert_float_eq!(
3198            vs,
3199            &[1., 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1][..],
3200            abs_all <= 1e-7
3201        );
3202
3203        let vs = render.compute_intrinsic_values(20., 1., 10);
3204        assert_float_eq!(vs, &[0.; 10][..], abs_all <= 0.);
3205    }
3206
3207    #[test]
3208    #[should_panic]
3209    fn test_set_value_curve_at_time_insert_while_another_event() {
3210        let context = OfflineAudioContext::new(1, 1, 48000.);
3211
3212        let opts = AudioParamDescriptor {
3213            name: String::new(),
3214            automation_rate: AutomationRate::A,
3215            default_value: 1.,
3216            min_value: 0.,
3217            max_value: 1.,
3218        };
3219        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
3220
3221        render.handle_incoming_event(param.set_value_at_time_raw(0.0, 5.));
3222
3223        let curve = [0., 0.5, 1., 0.5, 0.];
3224        render.handle_incoming_event(param.set_value_curve_at_time_raw(&curve[..], 0., 10.));
3225        // this is necessary as the panic is triggered in the audio thread
3226        // @note - argues in favor of maintaining the queue in control thread
3227        let _vs = render.compute_intrinsic_values(0., 1., 10);
3228    }
3229
3230    #[test]
3231    #[should_panic]
3232    fn test_set_value_curve_at_time_insert_another_event_inside() {
3233        let context = OfflineAudioContext::new(1, 1, 48000.);
3234
3235        let opts = AudioParamDescriptor {
3236            name: String::new(),
3237            automation_rate: AutomationRate::A,
3238            default_value: 1.,
3239            min_value: 0.,
3240            max_value: 1.,
3241        };
3242        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
3243
3244        let curve = [0., 0.5, 1., 0.5, 0.];
3245        render.handle_incoming_event(param.set_value_curve_at_time_raw(&curve[..], 0., 10.));
3246        render.handle_incoming_event(param.set_value_at_time_raw(0.0, 5.));
3247        // this is necessary as the panic is triggered in the audio thread
3248        // @note - argues in favor of maintaining the queue in control thread
3249        let _vs = render.compute_intrinsic_values(0., 1., 10);
3250    }
3251
3252    #[test]
3253    fn test_set_value_curve_waits_for_start_time() {
3254        let context = OfflineAudioContext::new(1, 1, 48000.);
3255
3256        let opts = AudioParamDescriptor {
3257            name: String::new(),
3258            automation_rate: AutomationRate::A,
3259            default_value: 0.,
3260            min_value: 0.,
3261            max_value: 10.,
3262        };
3263        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
3264
3265        // set to 0.0001 at t=0 (0. is a special case)
3266        let curve = [0., 0.5, 1., 0.5, 0.];
3267        render.handle_incoming_event(param.set_value_curve_at_time_raw(&curve[..], 5., 10.));
3268
3269        let vs = render.compute_intrinsic_values(0., 1., 10);
3270        assert_float_eq!(
3271            vs,
3272            &[0., 0., 0., 0., 0., 0., 0.2, 0.4, 0.6, 0.8][..],
3273            abs_all <= 0.
3274        );
3275    }
3276
3277    #[test]
3278    fn test_update_automation_rate_to_k() {
3279        let context = OfflineAudioContext::new(1, 1, 48000.);
3280
3281        let opts = AudioParamDescriptor {
3282            name: String::new(),
3283            automation_rate: AutomationRate::A,
3284            default_value: 0.,
3285            min_value: -10.,
3286            max_value: 10.,
3287        };
3288        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
3289
3290        render.onmessage(&mut AutomationRate::K);
3291        render.handle_incoming_event(param.set_value_at_time_raw(2., 0.000001));
3292
3293        let vs = render.compute_intrinsic_values(0., 1., 10);
3294        assert_float_eq!(vs, &[0.; 1][..], abs_all <= 0.);
3295    }
3296
3297    #[test]
3298    fn test_update_automation_rate_to_a() {
3299        let context = OfflineAudioContext::new(1, 1, 48000.);
3300
3301        let opts = AudioParamDescriptor {
3302            name: String::new(),
3303            automation_rate: AutomationRate::K,
3304            default_value: 0.,
3305            min_value: -10.,
3306            max_value: 10.,
3307        };
3308        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
3309
3310        render.onmessage(&mut AutomationRate::A);
3311        render.handle_incoming_event(param.set_value_at_time_raw(2., 0.000001));
3312
3313        let vs = render.compute_intrinsic_values(0., 1., 10);
3314        assert_float_eq!(vs, &[2.; 10][..], abs_all <= 0.);
3315    }
3316
3317    #[test]
3318    fn test_varying_param_size() {
3319        // event registered online during rendering
3320        {
3321            let context = OfflineAudioContext::new(1, 1, 48000.);
3322
3323            let opts = AudioParamDescriptor {
3324                name: String::new(),
3325                automation_rate: AutomationRate::A,
3326                default_value: 0.,
3327                min_value: 0.,
3328                max_value: 10.,
3329            };
3330            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
3331
3332            render.handle_incoming_event(param.set_value_at_time_raw(0., 0.));
3333            render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(9., 9.));
3334
3335            // first block should be length 10 (128 in real world)
3336            let vs = render.compute_intrinsic_values(0., 1., 10);
3337            let expected = [0., 1., 2., 3., 4., 5., 6., 7., 8., 9.];
3338            assert_float_eq!(vs, &expected[..], abs_all <= 0.);
3339
3340            // second block should have length 1
3341            let vs = render.compute_intrinsic_values(10., 1., 10);
3342            let expected = [9.; 1];
3343            assert_float_eq!(vs, &expected[..], abs_all <= 0.);
3344
3345            // insert event in third block, should have length 10
3346            render.handle_incoming_event(param.set_value_at_time_raw(1., 25.));
3347
3348            let vs = render.compute_intrinsic_values(20., 1., 10);
3349            let expected = [9., 9., 9., 9., 9., 1., 1., 1., 1., 1.];
3350            assert_float_eq!(vs, &expected[..], abs_all <= 0.);
3351
3352            // fourth block should have length 1
3353            let vs = render.compute_intrinsic_values(30., 1., 10);
3354            let expected = [1.; 1];
3355            assert_float_eq!(vs, &expected[..], abs_all <= 0.);
3356        }
3357
3358        // event registered before rendering
3359        {
3360            let context = OfflineAudioContext::new(1, 1, 48000.);
3361
3362            let opts = AudioParamDescriptor {
3363                name: String::new(),
3364                automation_rate: AutomationRate::A,
3365                default_value: 0.,
3366                min_value: 0.,
3367                max_value: 10.,
3368            };
3369            let (param, mut render) = audio_param_pair(opts, context.mock_registration());
3370
3371            render.handle_incoming_event(param.set_value_at_time_raw(0., 0.));
3372            render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(9., 9.));
3373            render.handle_incoming_event(param.set_value_at_time_raw(1., 25.));
3374
3375            // first block should be length 10 (128 in real world)
3376            let vs = render.compute_intrinsic_values(0., 1., 10);
3377            let expected = [0., 1., 2., 3., 4., 5., 6., 7., 8., 9.];
3378            assert_float_eq!(vs, &expected[..], abs_all <= 0.);
3379
3380            // second block should have length 1
3381            let vs = render.compute_intrinsic_values(10., 1., 10);
3382            let expected = [9.; 1];
3383            assert_float_eq!(vs, &expected[..], abs_all <= 0.);
3384
3385            // set value event in third block, length should be 10
3386            let vs = render.compute_intrinsic_values(20., 1., 10);
3387            let expected = [9., 9., 9., 9., 9., 1., 1., 1., 1., 1.];
3388            assert_float_eq!(vs, &expected[..], abs_all <= 0.);
3389
3390            // fourth block should have length 1
3391            let vs = render.compute_intrinsic_values(30., 1., 10);
3392            let expected = [1.; 1];
3393            assert_float_eq!(vs, &expected[..], abs_all <= 0.);
3394        }
3395    }
3396
3397    #[test]
3398    fn test_varying_param_size_modulated() {
3399        let alloc = Alloc::with_capacity(1);
3400
3401        // buffer length is 1 and input is silence (no modulation)
3402        {
3403            let context = OfflineAudioContext::new(1, 1, 48000.);
3404
3405            let opts = AudioParamDescriptor {
3406                name: String::new(),
3407                automation_rate: AutomationRate::A,
3408                default_value: 0.,
3409                min_value: 0.,
3410                max_value: 10.,
3411            };
3412            let (_param, mut render) = audio_param_pair(opts, context.mock_registration());
3413
3414            // no event in timeline, buffer length is 1
3415            let vs = render.compute_intrinsic_values(0., 1., 128);
3416            assert_float_eq!(vs, &[0.; 1][..], abs_all <= 0.);
3417
3418            // mix to output step, input is silence
3419            let signal = alloc.silence();
3420            let input = AudioRenderQuantum::from(signal);
3421
3422            let signal = alloc.silence();
3423            let mut output = AudioRenderQuantum::from(signal);
3424
3425            render.mix_to_output(&input, &mut output);
3426
3427            assert!(output.single_valued());
3428            assert_float_eq!(output.channel_data(0)[0], 0., abs <= 0.);
3429        }
3430
3431        // buffer length is 1 and input is non silent
3432        {
3433            let context = OfflineAudioContext::new(1, 1, 48000.);
3434
3435            let opts = AudioParamDescriptor {
3436                name: String::new(),
3437                automation_rate: AutomationRate::A,
3438                default_value: 0.,
3439                min_value: 0.,
3440                max_value: 10.,
3441            };
3442            let (_param, mut render) = audio_param_pair(opts, context.mock_registration());
3443
3444            // no event in timeline, buffer length is 1
3445            let vs = render.compute_intrinsic_values(0., 1., 128);
3446            assert_float_eq!(vs, &[0.; 1][..], abs_all <= 0.);
3447
3448            // mix to output step, input is not silence
3449            let signal = alloc.silence();
3450            let mut input = AudioRenderQuantum::from(signal);
3451            input.channel_data_mut(0)[0] = 1.;
3452
3453            let signal = alloc.silence();
3454            let mut output = AudioRenderQuantum::from(signal);
3455
3456            render.mix_to_output(&input, &mut output);
3457
3458            let mut expected = [0.; 128];
3459            expected[0] = 1.;
3460
3461            assert!(!output.single_valued());
3462            assert_float_eq!(output.channel_data(0)[..], &expected[..], abs_all <= 0.);
3463        }
3464    }
3465
3466    #[test]
3467    fn test_k_rate_makes_input_single_valued() {
3468        let alloc = Alloc::with_capacity(1);
3469        let context = OfflineAudioContext::new(1, 1, 48000.);
3470
3471        let opts = AudioParamDescriptor {
3472            name: String::new(),
3473            automation_rate: AutomationRate::K,
3474            default_value: 0.,
3475            min_value: 0.,
3476            max_value: 10.,
3477        };
3478        let (_param, mut render) = audio_param_pair(opts, context.mock_registration());
3479
3480        // no event in timeline, buffer length is 1
3481        let vs = render.compute_intrinsic_values(0., 1., 128);
3482        assert_float_eq!(vs, &[0.; 1][..], abs_all <= 0.);
3483
3484        // mix to output step, input is not silence
3485        let signal = alloc.silence();
3486        let mut input = AudioRenderQuantum::from(signal);
3487        input.channel_data_mut(0)[0] = 1.;
3488        input.channel_data_mut(0)[1] = 2.;
3489        input.channel_data_mut(0)[2] = 3.;
3490
3491        let signal = alloc.silence();
3492        let mut output = AudioRenderQuantum::from(signal);
3493
3494        render.mix_to_output(&input, &mut output);
3495
3496        // expect only 1, not the other values
3497        assert!(output.single_valued());
3498        assert_float_eq!(output.channel_data(0)[0], 1., abs <= 0.);
3499    }
3500
3501    #[test]
3502    fn test_full_render_chain() {
3503        let alloc = Alloc::with_capacity(1);
3504        // prevent regression between the different processing stage
3505        let context = OfflineAudioContext::new(1, 1, 48000.);
3506
3507        let min = 2.;
3508        let max = 42.;
3509        let default = 2.;
3510
3511        let opts = AudioParamDescriptor {
3512            name: String::new(),
3513            automation_rate: AutomationRate::A,
3514            default_value: default,
3515            min_value: min,
3516            max_value: max,
3517        };
3518        let (param, mut render) = audio_param_pair(opts, context.mock_registration());
3519
3520        render.handle_incoming_event(param.set_value_raw(128.));
3521        render.handle_incoming_event(param.linear_ramp_to_value_at_time_raw(0., 128.));
3522
3523        let intrinsic_values = render.compute_intrinsic_values(0., 1., 128);
3524        let mut expected = [0.; 128];
3525        for (i, v) in expected.iter_mut().enumerate() {
3526            *v = 128. - i as f32;
3527        }
3528        assert_float_eq!(intrinsic_values, &expected[..], abs_all <= 0.);
3529
3530        let signal = alloc.silence();
3531        let mut input = AudioRenderQuantum::from(signal);
3532        input.channel_data_mut(0)[0] = f32::NAN;
3533        let signal = alloc.silence();
3534        let mut output = AudioRenderQuantum::from(signal);
3535
3536        render.mix_to_output(&input, &mut output);
3537
3538        // clamp expected
3539        expected.iter_mut().for_each(|v| *v = v.clamp(min, max));
3540        // fix NAN at position 0
3541        expected[0] = 2.;
3542
3543        assert_float_eq!(output.channel_data(0)[..], &expected[..], abs_all <= 0.);
3544    }
3545}