servo_media_audio/
param.rs

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