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}