use nord_format::formats::nsmp::codec::FIELD_RATE;
use wasm_bindgen::JsValue;
use web_sys::{AudioBufferSourceNode, AudioContext, AudioScheduledSourceNode};
#[derive(Default)]
pub struct Sound {
context: Option<AudioContext>,
source: Option<AudioBufferSourceNode>,
}
impl Sound {
pub fn play(&mut self, samples: &[i16], channels: u16) -> Result<(), String> {
self.start(samples, channels)
.map_err(|e| format!("the browser refused to play this zone: {e:?}"))
}
fn start(&mut self, samples: &[i16], channels: u16) -> Result<(), JsValue> {
self.stop();
let channels = u32::from(channels).max(1);
let frames = u32::try_from(samples.len()).unwrap_or(u32::MAX) / channels;
if frames == 0 {
return Err(JsValue::from_str("this zone decoded to no frames"));
}
let context = match &self.context {
Some(context) => context,
None => self.context.insert(AudioContext::new()?),
};
let buffer = context.create_buffer(channels, frames, FIELD_RATE as f32)?;
for channel in 0..channels {
let plane: Vec<f32> = samples
.iter()
.skip(channel as usize)
.step_by(channels as usize)
.map(|s| f32::from(*s) / 32768.0)
.collect();
buffer.copy_to_channel(&plane, channel as i32)?;
}
let source = context.create_buffer_source()?;
source.set_buffer(Some(&buffer));
source.connect_with_audio_node(&context.destination())?;
source.start()?;
self.source = Some(source);
Ok(())
}
pub fn stop(&mut self) {
if let Some(source) = self.source.take() {
let _ = AudioScheduledSourceNode::stop(&source);
}
}
pub fn finished(&self) -> bool {
false
}
}