servo_media_audio/
param.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use malloc_size_of_derive::MallocSizeOf;
6
7use crate::block::{Block, FRAMES_PER_BLOCK_USIZE, Tick};
8use crate::node::BlockInfo;
9
10#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, MallocSizeOf)]
11pub enum ParamType {
12    Frequency,
13    Detune,
14    Gain,
15    Q,
16    Pan,
17    PlaybackRate,
18    Position(ParamDir),
19    Forward(ParamDir),
20    Up(ParamDir),
21    Orientation(ParamDir),
22    Offset,
23}
24
25#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, MallocSizeOf)]
26pub enum ParamDir {
27    X,
28    Y,
29    Z,
30}
31
32/// An AudioParam.
33///
34/// <https://webaudio.github.io/web-audio-api/#AudioParam>
35pub struct Param {
36    val: f32,
37    kind: ParamRate,
38    events: Vec<AutomationEvent>,
39    current_event: usize,
40    event_start_time: Tick,
41    event_start_value: f32,
42    /// Cache of inputs from connect()ed nodes
43    blocks: Vec<Block>,
44    /// The value of all connect()ed inputs mixed together, for this frame
45    block_mix_val: f32,
46    /// If true, `blocks` has been summed together into a single block
47    summed: bool,
48    dirty: bool,
49}
50
51#[derive(Copy, Clone, Eq, PartialEq, Debug, MallocSizeOf)]
52pub enum ParamRate {
53    /// Value is held for entire block
54    KRate,
55    /// Value is updated each frame
56    ARate,
57}
58
59impl Param {
60    pub fn new(val: f32) -> Self {
61        Param {
62            val,
63            kind: ParamRate::ARate,
64            events: vec![],
65            current_event: 0,
66            event_start_time: Tick(0),
67            event_start_value: val,
68            blocks: Vec::new(),
69            block_mix_val: 0.,
70            summed: false,
71            dirty: false,
72        }
73    }
74
75    pub fn new_krate(val: f32) -> Self {
76        Param {
77            val,
78            kind: ParamRate::KRate,
79            events: vec![],
80            current_event: 0,
81            event_start_time: Tick(0),
82            event_start_value: val,
83            blocks: Vec::new(),
84            block_mix_val: 0.,
85            summed: false,
86            dirty: false,
87        }
88    }
89
90    /// Update the value of this param to the next
91    ///
92    /// Invariant: This should be called with monotonically increasing
93    /// ticks, and Tick(0) should never be skipped.
94    ///
95    /// Returns true if anything changed
96    pub fn update(&mut self, block: &BlockInfo, tick: Tick) -> bool {
97        let mut changed = self.dirty;
98        self.dirty = false;
99        if tick.0 == 0 {
100            self.summed = true;
101            if let Some(first) = self.blocks.pop() {
102                // first sum them together
103                // https://webaudio.github.io/web-audio-api/#dom-audionode-connect-destinationparam-output
104                let block = self
105                    .blocks
106                    .drain(..)
107                    .fold(first, |acc, block| acc.sum(block));
108                self.blocks.push(block);
109            }
110        } else if self.kind == ParamRate::KRate {
111            return changed;
112        }
113
114        // Even if the timeline does nothing, it's still possible
115        // that there were connected inputs, so we should not
116        // directly return `false` after this point, instead returning
117        // `changed`
118        changed |= if let Some(block) = self.blocks.first() {
119            // store to be summed with `val` later
120            self.block_mix_val = block.data_chan_frame(tick.0 as usize, 0);
121            true
122        } else {
123            false
124        };
125
126        if self.events.len() <= self.current_event {
127            return changed;
128        }
129
130        let current_tick = block.absolute_tick(tick);
131        let mut current_event = &self.events[self.current_event];
132
133        // move to next event if necessary
134        // XXXManishearth k-rate events may get skipped over completely by this
135        // method. Firefox currently doesn't support these, however, so we can
136        // handle those later
137        loop {
138            let mut move_next = false;
139            if let Some(done_time) = current_event.done_time() {
140                // If this event is done, move on
141                if done_time < current_tick {
142                    move_next = true;
143                }
144            } else if let Some(next) = self.events.get(self.current_event + 1) {
145                // this event has no done time and we must run it till the next one
146                // starts
147                if let Some(start_time) = next.start_time() {
148                    // if the next one is ready to start, move on
149                    if start_time <= current_tick {
150                        move_next = true;
151                    }
152                } else {
153                    // If we have a next event with no start time and
154                    // the current event has no done time, this *has* to be because
155                    // the current event is SetTargetAtTime and the next is a Ramp
156                    // event. In this case we skip directly to the ramp assuming
157                    // the SetTarget is ready to start (or has started already)
158                    if current_event.time() <= current_tick {
159                        move_next = true;
160                    } else {
161                        // This is a SetTarget event before its start time, ignore
162                        return changed;
163                    }
164                }
165            }
166            if move_next {
167                self.current_event += 1;
168                self.event_start_value = self.val;
169                self.event_start_time = current_tick;
170                if let Some(next) = self.events.get(self.current_event + 1) {
171                    current_event = next;
172                    // may need to move multiple times
173                    continue;
174                } else {
175                    return changed;
176                }
177            }
178            break;
179        }
180
181        current_event.run(
182            &mut self.val,
183            current_tick,
184            self.event_start_time,
185            self.event_start_value,
186        )
187    }
188
189    pub fn value(&self) -> f32 {
190        // the data from connect()ed audionodes is first mixed
191        // together in update(), and then mixed with the actual param value
192        // https://webaudio.github.io/web-audio-api/#dom-audionode-connect-destinationparam-output
193        self.val + self.block_mix_val
194    }
195
196    pub fn set_rate(&mut self, rate: ParamRate) {
197        self.kind = rate;
198    }
199
200    pub(crate) fn insert_event(&mut self, event: AutomationEvent) {
201        if let AutomationEvent::SetValue(val) = event {
202            self.val = val;
203            self.event_start_value = val;
204            self.dirty = true;
205            return;
206        }
207
208        let time = event.time();
209
210        let result = self.events.binary_search_by(|e| e.time().cmp(&time));
211        // XXXManishearth this should handle overlapping events
212        let idx = match result {
213            Ok(idx) => idx,
214            Err(idx) => idx,
215        };
216
217        // XXXManishearth this isn't quite correct, this
218        // doesn't handle cases for when this lands inside a running
219        // event
220        if let Some(is_hold) = event.cancel_event() {
221            self.events.truncate(idx);
222            if !is_hold {
223                // If we cancelled the current event, reset
224                // the value to what it was before
225                if self.current_event >= self.events.len() {
226                    self.val = self.event_start_value;
227                }
228            }
229            // don't actually insert the event
230            return;
231        }
232        self.events.insert(idx, event);
233        // XXXManishearth handle inserting events with a time before that
234        // of the current one
235    }
236
237    pub(crate) fn add_block(&mut self, block: Block) {
238        debug_assert!(block.chan_count() == 1);
239        // summed only becomes true during a node's process() call,
240        // but add_block is called during graph traversal before processing,
241        // so if summed is true that means we've moved on to the next block
242        // and should clear our inputs
243        if self.summed {
244            self.blocks.clear();
245        }
246        self.blocks.push(block)
247    }
248
249    /// Flush an entire block of values into a buffer
250    ///
251    /// Only for use with AudioListener.
252    ///
253    /// Invariant: `block` must be a FRAMES_PER_BLOCK length array filled with silence
254    pub(crate) fn flush_to_block(&mut self, info: &BlockInfo, block: &mut [f32]) {
255        // common case
256        if self.current_event >= self.events.len() && self.blocks.is_empty() {
257            if self.val != 0. {
258                for block_tick in &mut block[0..FRAMES_PER_BLOCK_USIZE] {
259                    // ideally this can use some kind of vectorized memset()
260                    *block_tick = self.val;
261                }
262            }
263        // if the value is zero, our buffer is already zeroed
264        } else {
265            for block_tick in &mut block[0..FRAMES_PER_BLOCK_USIZE] {
266                self.update(info, Tick(*block_tick as u64));
267                *block_tick = self.val;
268            }
269        }
270    }
271}
272
273#[derive(Clone, Copy, Eq, PartialEq, Debug, MallocSizeOf)]
274pub enum RampKind {
275    Linear,
276    Exponential,
277}
278
279#[derive(Clone, PartialEq, Debug)]
280/// <https://webaudio.github.io/web-audio-api/#dfn-automation-event>
281pub(crate) enum AutomationEvent {
282    SetValue(f32),
283    SetValueAtTime(f32, Tick),
284    RampToValueAtTime(RampKind, f32, Tick),
285    SetTargetAtTime(f32, Tick, /* time constant, units of Tick */ f64),
286    SetValueCurveAtTime(
287        Vec<f32>,
288        /* start time */ Tick,
289        /* duration */ Tick,
290    ),
291    CancelAndHoldAtTime(Tick),
292    CancelScheduledValues(Tick),
293}
294
295#[derive(Clone, PartialEq, Debug, MallocSizeOf)]
296/// An AutomationEvent that uses times in s instead of Ticks
297pub enum UserAutomationEvent {
298    SetValue(f32),
299    SetValueAtTime(f32, /* time */ f64),
300    RampToValueAtTime(RampKind, f32, /* time */ f64),
301    SetTargetAtTime(f32, f64, /* time constant, units of s */ f64),
302    SetValueCurveAtTime(Vec<f32>, /* start time */ f64, /* duration */ f64),
303    CancelAndHoldAtTime(f64),
304    CancelScheduledValues(f64),
305}
306
307impl UserAutomationEvent {
308    pub(crate) fn convert_to_event(self, rate: f32) -> AutomationEvent {
309        match self {
310            UserAutomationEvent::SetValue(val) => AutomationEvent::SetValue(val),
311            UserAutomationEvent::SetValueAtTime(val, time) => {
312                AutomationEvent::SetValueAtTime(val, Tick::from_time(time, rate))
313            },
314            UserAutomationEvent::RampToValueAtTime(kind, val, time) => {
315                AutomationEvent::RampToValueAtTime(kind, val, Tick::from_time(time, rate))
316            },
317            UserAutomationEvent::SetValueCurveAtTime(values, start, duration) => {
318                AutomationEvent::SetValueCurveAtTime(
319                    values,
320                    Tick::from_time(start, rate),
321                    Tick::from_time(duration, rate),
322                )
323            },
324            UserAutomationEvent::SetTargetAtTime(val, start, tau) => {
325                AutomationEvent::SetTargetAtTime(
326                    val,
327                    Tick::from_time(start, rate),
328                    tau * rate as f64,
329                )
330            },
331            UserAutomationEvent::CancelScheduledValues(t) => {
332                AutomationEvent::CancelScheduledValues(Tick::from_time(t, rate))
333            },
334            UserAutomationEvent::CancelAndHoldAtTime(t) => {
335                AutomationEvent::CancelAndHoldAtTime(Tick::from_time(t, rate))
336            },
337        }
338    }
339}
340
341impl AutomationEvent {
342    /// The time of the event used for ordering
343    pub fn time(&self) -> Tick {
344        match *self {
345            AutomationEvent::SetValueAtTime(_, tick) => tick,
346            AutomationEvent::SetValueCurveAtTime(_, start, _) => start,
347            AutomationEvent::RampToValueAtTime(_, _, tick) => tick,
348            AutomationEvent::SetTargetAtTime(_, start, _) => start,
349            AutomationEvent::CancelAndHoldAtTime(t) => t,
350            AutomationEvent::CancelScheduledValues(tick) => tick,
351            AutomationEvent::SetValue(..) => {
352                unreachable!("SetValue should never appear in the timeline")
353            },
354        }
355    }
356
357    pub fn done_time(&self) -> Option<Tick> {
358        match *self {
359            AutomationEvent::SetValueAtTime(_, tick) => Some(tick),
360            AutomationEvent::RampToValueAtTime(_, _, tick) => Some(tick),
361            AutomationEvent::SetValueCurveAtTime(_, start, duration) => Some(start + duration),
362            AutomationEvent::SetTargetAtTime(..) => None,
363            AutomationEvent::CancelAndHoldAtTime(t) => Some(t),
364            AutomationEvent::CancelScheduledValues(..) | AutomationEvent::SetValue(..) => {
365                unreachable!("CancelScheduledValues/SetValue should never appear in the timeline")
366            },
367        }
368    }
369
370    pub fn start_time(&self) -> Option<Tick> {
371        match *self {
372            AutomationEvent::SetValueAtTime(_, tick) => Some(tick),
373            AutomationEvent::RampToValueAtTime(..) => None,
374            AutomationEvent::SetValueCurveAtTime(_, start, _) => Some(start),
375            AutomationEvent::SetTargetAtTime(_, start, _) => Some(start),
376            AutomationEvent::CancelAndHoldAtTime(t) => Some(t),
377            AutomationEvent::CancelScheduledValues(..) | AutomationEvent::SetValue(..) => {
378                unreachable!("CancelScheduledValues/SetValue should never appear in the timeline")
379            },
380        }
381    }
382
383    /// Returns Some if it's a cancel event
384    /// the boolean is if it's CancelAndHold
385    pub fn cancel_event(&self) -> Option<bool> {
386        match *self {
387            AutomationEvent::CancelAndHoldAtTime(..) => Some(true),
388            AutomationEvent::CancelScheduledValues(..) => Some(false),
389            _ => None,
390        }
391    }
392
393    /// Update a parameter based on this event
394    ///
395    /// Returns true if something changed
396    pub fn run(
397        &self,
398        value: &mut f32,
399        current_tick: Tick,
400        event_start_time: Tick,
401        event_start_value: f32,
402    ) -> bool {
403        if matches!(self.start_time(), Some(start_time) if start_time > current_tick) {
404            // The previous event finished and we advanced to this
405            // event, but it's not started yet. Return early
406            return false;
407        }
408
409        match *self {
410            AutomationEvent::SetValueAtTime(val, time) => {
411                if current_tick == time {
412                    *value = val;
413                    true
414                } else {
415                    false
416                }
417            },
418            AutomationEvent::RampToValueAtTime(kind, val, time) => {
419                let progress =
420                    (current_tick - event_start_time).0 as f32 / (time - event_start_time).0 as f32;
421                match kind {
422                    RampKind::Linear => {
423                        *value = event_start_value + (val - event_start_value) * progress;
424                    },
425                    RampKind::Exponential => {
426                        let ratio = val / event_start_value;
427                        if event_start_value == 0. || ratio < 0. {
428                            if time == current_tick {
429                                *value = val;
430                            } else {
431                                *value = event_start_value;
432                            }
433                        } else {
434                            *value = event_start_value * (ratio).powf(progress);
435                        }
436                    },
437                }
438                true
439            },
440            AutomationEvent::SetTargetAtTime(val, start, tau) => {
441                let exp = -((current_tick - start) / tau);
442                *value = val + (event_start_value - val) * exp.exp() as f32;
443                true
444            },
445            AutomationEvent::SetValueCurveAtTime(ref values, start, duration) => {
446                let progress = ((current_tick.0 as f32) - (start.0 as f32)) / (duration.0 as f32);
447                debug_assert!(progress >= 0.);
448                let n = values.len() as f32;
449                let k_float = (n - 1.) * progress;
450                let k = k_float.floor();
451                if (k + 1.) < n {
452                    let progress = k_float - k;
453                    *value =
454                        values[k as usize] * (1. - progress) + values[(k + 1.) as usize] * progress;
455                } else {
456                    *value = values[(n - 1.) as usize];
457                }
458                true
459            },
460            AutomationEvent::CancelAndHoldAtTime(..) => false,
461            AutomationEvent::CancelScheduledValues(..) | AutomationEvent::SetValue(..) => {
462                unreachable!("CancelScheduledValues/SetValue should never appear in the timeline")
463            },
464        }
465    }
466}