use super::{AudioDeviceCore};
use wasm_bindgen::prelude::*;
use wasm_bindgen::*;
use web_sys::*;
use std::cell::RefCell;
use std::collections::VecDeque;
use std::rc::Rc;
use std::sync::Arc;
use crate::collections::linked_list::*;
use crate::console::*;
use crate::*;
static mut AUDIO_THREADS: Option<LinkedList<Option<js_sys::Function>>> = None;
pub fn init_audio_threads() {
unsafe {
AUDIO_THREADS = Some(LinkedList::new());
}
}
fn get_audio_thread_list() -> &'static LinkedList<Option<js_sys::Function>> {
unsafe { AUDIO_THREADS.as_ref().unwrap() }
}
fn get_audio_thread_list_mut() -> &'static mut LinkedList<Option<js_sys::Function>> {
unsafe { AUDIO_THREADS.as_mut().unwrap() }
}
pub struct FlufflAudioContext {
pub ctx: AudioContext,
}
impl FlufflAudioContext {
pub fn new() -> Self {
let ctx = AudioContext::new().unwrap();
Self { ctx }
}
}
impl Drop for FlufflAudioContext {
fn drop(&mut self) {
let _ = self.ctx.close();
}
}
pub struct FlufflAudioDeviceContext<F, S> {
glue_callback: F,
state: Rc<RefCell<S>>,
thread_id: u32,
}
impl<F, S> Clone for FlufflAudioDeviceContext<F, S>
where
F: Copy,
{
fn clone(&self) -> Self {
Self {
glue_callback: self.glue_callback,
state: self.state.clone(),
thread_id: self.thread_id,
}
}
}
impl<F, S> FlufflAudioDeviceContext<F, S>
where
F: FnMut(&mut S, &mut [f32]) + Copy + 'static,
S: 'static,
{
pub fn new(
mut core: AudioDeviceCore<F, S>,
audio_context: Arc<RefCell<FlufflAudioContext>>,
) -> Self {
const BUFFER_TIME: f64 = 0.5;
let pump_list: Rc<RefCell<VecDeque<js_sys::Function>>> =
Rc::new(RefCell::new(VecDeque::new()));
let state = core.state.take().unwrap_or_else(|| {
panic!("Error: Failed to create GlueAudioDevice!\n .with_state(..) not initalized!\n")
});
let mut glue_callback = core.callback();
let state = Rc::new(RefCell::new(state));
let (sample_rate, channels, buffer_size) = core.desired_specs.get_specs();
let mut play_time = audio_context.borrow().ctx.current_time();
let get_state_ptr = |state: Rc<RefCell<S>>| (&mut *state.borrow_mut()) as *mut S;
let state_ptr = get_state_ptr(state.clone());
let mut sample_callback_buffer = Vec::new();
let mut sample_buffer_for_channel = Vec::new();
sample_buffer_for_channel.resize(buffer_size, 0f32);
get_audio_thread_list_mut().push_front(None);
let thread_id = get_audio_thread_list().get_front();
let process_raw_pcm = move || {
while play_time - audio_context.borrow().ctx.current_time() < BUFFER_TIME {
let web_audio_buffer = audio_context
.borrow()
.ctx
.create_buffer(channels as u32, buffer_size as u32, sample_rate as f32)
.unwrap();
sample_callback_buffer.resize(buffer_size * channels, 0f32);
let state_ref = unsafe { &mut *state_ptr };
glue_callback(state_ref, &mut sample_callback_buffer[..]);
for channel_index in 0..channels {
sample_buffer_for_channel.clear();
for k in 0..sample_callback_buffer.len() / channels {
let sample_index = k * channels + channel_index;
sample_buffer_for_channel.push(sample_callback_buffer[sample_index]);
}
web_audio_buffer
.copy_to_channel(&mut sample_buffer_for_channel[..], channel_index as i32)
.unwrap();
}
let web_audio_buffer_source_node =
audio_context.borrow().ctx.create_buffer_source().unwrap();
web_audio_buffer_source_node.set_buffer(Some(&web_audio_buffer));
let pump_list_ptr = pump_list.clone();
let continue_buffering = move || {
let pump_list = pump_list_ptr;
get_audio_thread_list()[thread_id].get_data().map(|data| {
data.as_ref().map(|thread: &js_sys::Function| {
let _ = thread.call0(&JsValue::null());
});
});
pump_list.borrow_mut().pop_front();
};
let cb = Closure::once_into_js(continue_buffering)
.dyn_into::<js_sys::Function>()
.unwrap();
pump_list.borrow_mut().push_back(cb);
web_audio_buffer_source_node.set_onended(pump_list.borrow().back());
web_audio_buffer_source_node
.start_with_when(play_time)
.unwrap();
play_time += buffer_size as f64 / sample_rate as f64;
let node: AudioNode = web_audio_buffer_source_node
.dyn_into::<AudioNode>()
.unwrap();
let _connect_result = node.connect_with_audio_node(
&audio_context.borrow().ctx.destination().dyn_into().unwrap(),
);
}
};
let process_raw_pcm_closure = Closure::wrap(Box::new(process_raw_pcm) as Box<dyn FnMut()>)
.into_js_value()
.dyn_into::<js_sys::Function>()
.unwrap();
get_audio_thread_list_mut()[thread_id]
.get_data_mut()
.map(|data| {
*data = Some(process_raw_pcm_closure);
});
Self {
state,
glue_callback,
thread_id,
}
}
pub fn modify_state<CBF>(&self, mut cb: CBF)
where
CBF: FnMut(Option<&mut S>),
{
if let Ok(mut state_ptr) = self.state.try_borrow_mut() {
let state_ref = &mut *state_ptr;
cb(Some(state_ref));
}
}
pub fn resume(&self) {
let thread_id = self.thread_id;
get_audio_thread_list_mut()[thread_id]
.get_data()
.map(|data| {
data.as_ref().map(|thread| thread.call0(&JsValue::null()));
});
}
pub fn pause(&self) {
panic!("not implemented");
}
}
impl<F, S> Drop for FlufflAudioDeviceContext<F, S> {
fn drop(&mut self) {
let thread_id = self.thread_id;
let result: Option<Option<js_sys::Function>> =
get_audio_thread_list_mut().remove(thread_id);
if let Some(Some(func)) = result {
console_log!("drop func\n");
std::mem::drop(func);
}
let state_count = Rc::strong_count(&self.state);
console_log!(
"CONTEXT[ id={} ] IS ABOUT TO BE DROPPED UH OH!, state_count = {}\n",
thread_id,
state_count
);
}
}