web_codecs/audio/
data.rs

1use std::ops::{Deref, DerefMut};
2use std::time::Duration;
3
4use crate::{Error, Result, Timestamp};
5
6pub use web_sys::AudioSampleFormat as AudioDataFormat;
7
8/// A wrapper around [web_sys::AudioData] that closes on Drop.
9// It's an option so `leak` can return the inner AudioData if needed.
10#[derive(Debug)]
11pub struct AudioData(Option<web_sys::AudioData>);
12
13impl AudioData {
14	/// A helper to construct AudioData in a more type-safe way.
15	/// This currently only supports F32.
16	pub fn new<'a>(
17		channels: impl ExactSizeIterator<Item = &'a [f32]>,
18		sample_rate: u32,
19		timestamp: Timestamp,
20	) -> Result<Self> {
21		let mut channels = channels.enumerate();
22		let channel_count = channels.size_hint().0;
23		let (_, channel) = channels.next().ok_or(Error::NoChannels)?;
24
25		let frame_count = channel.len();
26		let total_samples = channel_count * frame_count;
27
28		// Annoyingly, we need to create a contiguous buffer for the data.
29		let data = js_sys::Float32Array::new_with_length(total_samples as _);
30
31		// Copy the first channel using a Float32Array as a view into the buffer.
32		let slice = js_sys::Float32Array::new_with_byte_offset_and_length(&data.buffer(), 0, frame_count as _);
33		slice.copy_from(channel);
34
35		for (i, channel) in channels {
36			// Copy the other channels using a Float32Array as a view into the buffer.
37			let slice = js_sys::Float32Array::new_with_byte_offset_and_length(
38				&data.buffer(),
39				(i * frame_count) as u32,
40				frame_count as _,
41			);
42			slice.copy_from(channel);
43		}
44
45		let init = web_sys::AudioDataInit::new(
46			&data,
47			AudioDataFormat::F32Planar,
48			channel_count as _,
49			frame_count as _,
50			sample_rate as _,
51			timestamp.as_micros() as _,
52		);
53
54		// Manually add `transfer` to the init options.
55		// TODO Update web_sys to support this natively.
56		// I'm not even sure if this works.
57		let transfer = js_sys::Array::new();
58		transfer.push(&data.buffer());
59		js_sys::Reflect::set(&init, &js_sys::JsString::from("transfer"), &transfer)?;
60
61		let audio_data = web_sys::AudioData::new(&init)?;
62		Ok(Self(Some(audio_data)))
63	}
64
65	pub fn timestamp(&self) -> Timestamp {
66		Timestamp::from_micros(self.0.as_ref().unwrap().timestamp() as _)
67	}
68
69	pub fn duration(&self) -> Duration {
70		Duration::from_micros(self.0.as_ref().unwrap().duration() as _)
71	}
72
73	pub fn sample_rate(&self) -> u32 {
74		self.0.as_ref().unwrap().sample_rate() as u32
75	}
76
77	pub fn append_to<T: AudioAppend>(&self, dst: &mut T, channel: usize, options: AudioCopyOptions) -> Result<()> {
78		dst.append_to(self, channel, options)
79	}
80
81	pub fn copy_to<T: AudioCopy>(&self, dst: &mut T, channel: usize, options: AudioCopyOptions) -> Result<()> {
82		dst.copy_to(self, channel, options)
83	}
84
85	pub fn leak(mut self) -> web_sys::AudioData {
86		self.0.take().unwrap()
87	}
88}
89
90impl Clone for AudioData {
91	fn clone(&self) -> Self {
92		Self(self.0.clone())
93	}
94}
95
96impl Deref for AudioData {
97	type Target = web_sys::AudioData;
98
99	fn deref(&self) -> &Self::Target {
100		self.0.as_ref().unwrap()
101	}
102}
103
104impl DerefMut for AudioData {
105	fn deref_mut(&mut self) -> &mut Self::Target {
106		self.0.as_mut().unwrap()
107	}
108}
109
110// Make sure we close the frame on drop.
111impl Drop for AudioData {
112	fn drop(&mut self) {
113		if let Some(audio_data) = self.0.take() {
114			audio_data.close();
115		}
116	}
117}
118
119impl From<web_sys::AudioData> for AudioData {
120	fn from(this: web_sys::AudioData) -> Self {
121		Self(Some(this))
122	}
123}
124
125pub trait AudioCopy {
126	fn copy_to(&mut self, data: &AudioData, channel: usize, options: AudioCopyOptions) -> Result<()>;
127}
128
129impl AudioCopy for [u8] {
130	fn copy_to(&mut self, data: &AudioData, channel: usize, options: AudioCopyOptions) -> Result<()> {
131		let options = options.into_web_sys(channel);
132		// NOTE: The format is unuset so it will default to the AudioData format.
133		// This means you couldn't export as U8Planar for whatever that's worth...
134		data.0.as_ref().unwrap().copy_to_with_u8_slice(self, &options)?;
135		Ok(())
136	}
137}
138
139impl AudioCopy for [f32] {
140	fn copy_to(&mut self, data: &AudioData, channel: usize, options: AudioCopyOptions) -> Result<()> {
141		let options = options.into_web_sys(channel);
142		options.set_format(AudioDataFormat::F32Planar);
143
144		// Cast from a f32 to a u8 slice.
145		let bytes = bytemuck::cast_slice_mut(self);
146		data.0.as_ref().unwrap().copy_to_with_u8_slice(bytes, &options)?;
147		Ok(())
148	}
149}
150
151impl AudioCopy for js_sys::Uint8Array {
152	fn copy_to(&mut self, data: &AudioData, channel: usize, options: AudioCopyOptions) -> Result<()> {
153		let options = options.into_web_sys(channel);
154		data.0.as_ref().unwrap().copy_to_with_u8_array(self, &options)?;
155		Ok(())
156	}
157}
158
159impl AudioCopy for js_sys::Float32Array {
160	fn copy_to(&mut self, data: &AudioData, channel: usize, options: AudioCopyOptions) -> Result<()> {
161		let options = options.into_web_sys(channel);
162		data.0.as_ref().unwrap().copy_to_with_buffer_source(self, &options)?;
163		Ok(())
164	}
165}
166
167pub trait AudioAppend {
168	fn append_to(&mut self, data: &AudioData, channel: usize, options: AudioCopyOptions) -> Result<()>;
169}
170
171impl AudioAppend for Vec<f32> {
172	fn append_to(&mut self, data: &AudioData, channel: usize, options: AudioCopyOptions) -> Result<()> {
173		// TODO do unsafe stuff to avoid zeroing the buffer.
174		let grow = options.count.unwrap_or(data.number_of_frames() as _) - options.offset;
175		let offset = self.len();
176		self.resize(offset + grow, 0.0);
177
178		let options = options.into_web_sys(channel);
179		let bytes = bytemuck::cast_slice_mut(&mut self[offset..]);
180		data.0.as_ref().unwrap().copy_to_with_u8_slice(bytes, &options)?;
181
182		Ok(())
183	}
184}
185
186#[derive(Debug, Default)]
187pub struct AudioCopyOptions {
188	pub offset: usize,        // defaults to 0
189	pub count: Option<usize>, // defaults to remainder
190}
191
192impl AudioCopyOptions {
193	fn into_web_sys(self, channel: usize) -> web_sys::AudioDataCopyToOptions {
194		let options = web_sys::AudioDataCopyToOptions::new(channel as _);
195		options.set_frame_offset(self.offset as _);
196		if let Some(count) = self.count {
197			options.set_frame_count(count as _);
198		}
199		options
200	}
201}