Skip to main content

servo_media_audio/delay_node/
delay_reader.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 num_traits::Zero;
6
7use crate::audio_node::{AudioNodeEngine, AudioNodeType, BlockInfo, ChannelInfo};
8use crate::block::{Block, Chunk, FRAMES_PER_BLOCK_USIZE, Tick};
9use crate::delay_node::{CachedUpmixedBlock, DelayBuffer, UpmixedBlock};
10use crate::param::{Param, ParamType};
11
12/// <https://webaudio.github.io/web-audio-api/#delayreader>
13/// > ...an object that has the same interface as an AudioNode,
14/// > and that can read the audio data from the internal buffer of the DelayNode.
15/// > It is connected to the same AudioNodes as the DelayNode it was created from.
16#[derive(AudioNodeCommon)]
17pub(crate) struct DelayReader {
18    channel_info: ChannelInfo,
19    // Tracks the delay time in terms of number of frames, relative to each frame in the input block.
20    // When reading from the buffer, we look for the stored block with the relevant frames
21    delay_frames: [f32; FRAMES_PER_BLOCK_USIZE],
22    // Ring buffer where we push to the front.
23    // Easier mental model since entries in the back are the oldest.
24    delay_line: DelayBuffer,
25    // Block that has been upmixed based on the channel count of the output.
26    upmixed_block: CachedUpmixedBlock,
27    // delay_time param passed on from the delay node. Delay time in seconds.
28    delay_time: Param,
29}
30
31fn find_block_with_index(delay_frame_index: usize) -> usize {
32    delay_frame_index / FRAMES_PER_BLOCK_USIZE
33}
34
35impl DelayReader {
36    pub(super) fn new(
37        buffer: DelayBuffer,
38        upmixed_block: CachedUpmixedBlock,
39        delay_time: Param,
40        channel_info: ChannelInfo,
41    ) -> Self {
42        DelayReader {
43            channel_info,
44            delay_frames: [0.; FRAMES_PER_BLOCK_USIZE],
45            delay_line: buffer,
46            upmixed_block,
47            delay_time,
48        }
49    }
50
51    /// <https://webaudio.github.io/web-audio-api/#dom-delaynode-delaytime>
52    fn update_parameters(&mut self, info: &BlockInfo, tick: Tick) -> bool {
53        let updated = self.delay_time.update(info, tick);
54        self.update_delay_frames(tick.0 as usize, self.delay_time.value() * info.sample_rate);
55        updated
56    }
57
58    /// 1.18.4
59    /// > When producing an output buffer, a DelayReader MUST yield exactly the audio that was written to the
60    /// > corresponding DelayWriter delayTime seconds ago.
61    ///
62    /// We are processing tick t, where t ranges from 0 to (FRAMES_PER_BLOCK - 1).
63    /// The DelayWriter is writing a frame into the delay line every tick.
64    /// Let delay_frame = delay_time * sample_rate, at tick t.
65    /// Now let's process tick t + 1. delay_frame must increase by 1 to account for the new tick.
66    /// There are FRAMES_PER_BLOCK - 1 - t ticks to process after tick t.
67    /// So after processing all ticks (FRAMES_PER_BLOCK - 1),
68    /// delay_frames[t] = delay_time * sample_rate + FRAMES_PER_BLOCK - 1 - t
69    fn update_delay_frames(&mut self, tick: usize, value: f32) {
70        self.delay_frames[tick] = value + FRAMES_PER_BLOCK_USIZE as f32 - 1. - tick as f32;
71    }
72
73    /// Calculates the output channel count
74    /// <https://webaudio.github.io/web-audio-api/#tail-time>
75    /// 4.3
76    /// > When an AudioNode has a non-zero tail-time,
77    /// > and an output channel count that depends on the input channels count,
78    /// > the AudioNode’s tail-time must be taken into account when the input channel count changes.
79    /// >
80    /// > When there is a decrease in input channel count,
81    /// > the change in output channel count MUST happen when the input that was received
82    /// > with greater channel count no longer affects the output.
83    /// >
84    /// > When there is an increase in input channel count, the behavior depends on the AudioNode type:
85    /// > * For a DelayNode or a DynamicsCompressorNode, the number of output channels MUST increase
86    /// >   when the input that was received with greater channel count begins to affect the output.
87    ///
88    /// Therefore, we know that output channel count will be the highest channel count of the
89    /// blocks read.
90    fn calc_output_channel_count(&self) -> u8 {
91        // If the delay line is empty, there's obviously nothing that was delayed, so there will be no output.
92        if self.delay_line.read().is_empty() {
93            return 0;
94        }
95        let (min_delay_frame, max_delay_frame) = self
96            .delay_frames
97            .iter()
98            .fold((f32::MAX, f32::MIN), |frames, delay_frame| {
99                (frames.0.min(*delay_frame), frames.1.max(*delay_frame))
100            });
101        // With the range of delay frames we can check which blocks we will be reading from.
102        let earlier_block = find_block_with_index(max_delay_frame.ceil() as usize);
103        let later_block = find_block_with_index(min_delay_frame.floor() as usize);
104        // Now search through the potential blocks for their channel counts
105        // By construction earlier blocks are in higher indices of the delay line
106        let mut channel_count = 0;
107        let delay_line = self.delay_line.read();
108        for block in later_block..=(later_block.max(earlier_block.min(delay_line.len() - 1))) {
109            channel_count = channel_count.max(
110                delay_line
111                    .get(block)
112                    .map(|block| {
113                        // Silent blocks don't affect the output.
114                        if !block.is_silence() {
115                            block.chan_count()
116                        } else {
117                            0
118                        }
119                    })
120                    .unwrap_or_default(),
121            );
122        }
123        channel_count
124    }
125
126    fn upmix_block(&self, index: usize, channel_count: u8, block: &Block) -> UpmixedBlock {
127        UpmixedBlock::new(
128            index,
129            channel_count,
130            self.channel_info.interpretation,
131            block,
132        )
133    }
134
135    /// Read frames from the delay line at the values indexed around the specified delays.
136    pub(super) fn read(&mut self) -> Chunk {
137        let channel_count = self.calc_output_channel_count();
138        // If channel count is 0, then no data is outputted.
139        // In this case we just return an output block with a single channel of 0s.
140        if channel_count.is_zero() {
141            return Chunk::explicit_silence();
142        }
143        let mut has_active_value = false;
144        // Initialize the output block
145        let mut output_block = Block::for_channels_explicit(channel_count);
146        {
147            let delay_line = self.delay_line.read();
148            for (tick, delay_frame) in self.delay_frames.into_iter().enumerate() {
149                let lower_frame_index = delay_frame.floor() as usize;
150                let higher_frame_index = delay_frame.ceil() as usize;
151                let lower_block_index = find_block_with_index(lower_frame_index);
152                let higher_block_index = find_block_with_index(higher_frame_index);
153                let mut linear_interpolation_factor = delay_frame.fract();
154                for (frame_index, block_index) in [
155                    (lower_frame_index, lower_block_index),
156                    (higher_frame_index, higher_block_index),
157                ]
158                .into_iter()
159                {
160                    if !linear_interpolation_factor.is_zero() {
161                        let Some(block) = delay_line.get(block_index) else {
162                            continue;
163                        };
164                        // Update the upmixed block if necessary.
165                        {
166                            let mut maybe_upmixed_block = self.upmixed_block.write();
167                            if let Some(upmixed_block) = maybe_upmixed_block.as_ref() {
168                                if upmixed_block.index() != block_index {
169                                    *maybe_upmixed_block =
170                                        Some(self.upmix_block(block_index, channel_count, block));
171                                }
172                            } else {
173                                *maybe_upmixed_block =
174                                    Some(self.upmix_block(block_index, channel_count, block));
175                            }
176                        }
177                        for channel in 0..channel_count as usize {
178                            // Get the position of the target frame within the block
179                            let position_for_block = frame_index % FRAMES_PER_BLOCK_USIZE;
180                            // Remember that block buffer data goes from oldest to newest
181                            let upmixed_value = self
182                                .upmixed_block
183                                .read()
184                                .as_ref()
185                                .map(|upmixed_block| {
186                                    upmixed_block.block().data_chan_frame(
187                                        FRAMES_PER_BLOCK_USIZE - 1 - position_for_block,
188                                        channel as u8,
189                                    )
190                                })
191                                .unwrap_or_default();
192                            // Flag if we are actively processing
193                            if upmixed_value.abs() >= f32::MIN && !has_active_value {
194                                has_active_value = true;
195                            }
196                            let output_channel = output_block.data_chan_mut(channel as u8);
197                            output_channel[tick] += linear_interpolation_factor * upmixed_value;
198                        }
199                    }
200                    linear_interpolation_factor = 1. - linear_interpolation_factor;
201                }
202            }
203        }
204        // 1.5.3
205        // > AudioNodes that are not actively processing output a single channel of silence.
206        Chunk::new(output_block)
207    }
208}
209
210impl AudioNodeEngine for DelayReader {
211    fn node_type(&self) -> AudioNodeType {
212        AudioNodeType::DelayReader
213    }
214
215    fn process(&mut self, _inputs: Chunk, info: &BlockInfo) -> Chunk {
216        // Reset the delay frames array
217        self.delay_frames = [0.; FRAMES_PER_BLOCK_USIZE];
218
219        // Update delay_frames
220        for i in 0..FRAMES_PER_BLOCK_USIZE {
221            self.update_parameters(info, Tick(i as u64));
222        }
223
224        // Read from the internal buffer
225        self.read()
226    }
227
228    fn get_param(&mut self, id: ParamType) -> &mut Param {
229        match id {
230            ParamType::DelayTime => &mut self.delay_time,
231            _ => panic!("Unknown param {:?} for DelayNode", id),
232        }
233    }
234}