use std::cell::RefCell;
use wasm_bindgen::{JsCast, JsValue};
use js_sys::{Array, Function, Reflect};
use web_sys::{AudioContext, GainNode, OscillatorNode, OscillatorType, PannerNode};
struct Tone {
osc: OscillatorNode,
lfo: OscillatorNode,
lfo_gain: GainNode,
amp: GainNode,
panner: PannerNode,
}
struct AudioState {
ctx: AudioContext,
tones: Vec<Option<Tone>>,
master: GainNode,
}
thread_local! {
static AUDIO: RefCell<Option<AudioState>> = RefCell::new(None);
}
fn js_call(obj: &JsValue, method: &str, args: &[f64]) {
if let Ok(func_val) = Reflect::get(obj, &JsValue::from_str(method)) {
if let Some(func) = func_val.dyn_ref::<Function>() {
let arr = Array::new();
for &a in args {
arr.push(&JsValue::from_f64(a));
}
let _ = func.apply(obj, &arr);
}
}
}
fn ensure_init() -> bool {
AUDIO.with(|a| {
if a.borrow().is_some() { return true; }
match AudioContext::new() {
Ok(ctx) => {
let master = match ctx.create_gain() {
Ok(g) => g,
Err(_) => return false,
};
master.gain().set_value(1.0);
master.connect_with_audio_node(&ctx.destination()).ok();
*a.borrow_mut() = Some(AudioState {
ctx,
tones: (0..16).map(|_| None).collect(),
master,
});
true
}
Err(e) => {
web_sys::console::warn_1(&e);
false
}
}
})
}
pub fn set_tone(
idx: usize,
x: f32, y: f32, z: f32,
_w: f32,
freq: f32, amp: f32,
lfo_rate: f32, lfo_depth: f32,
) {
if !ensure_init() { return; }
AUDIO.with(|a| {
let mut opt = a.borrow_mut();
if let Some(state) = opt.as_mut() {
if idx >= state.tones.len() { return; }
if state.tones[idx].is_none() {
let ctx = &state.ctx;
let result = (|| -> Option<Tone> {
let osc = ctx.create_oscillator().ok()?;
let lfo = ctx.create_oscillator().ok()?;
let lfo_gain = ctx.create_gain().ok()?;
let amp_node = ctx.create_gain().ok()?;
let panner = ctx.create_panner().ok()?;
osc.set_type(OscillatorType::Sine);
lfo.set_type(OscillatorType::Sine);
lfo.connect_with_audio_node(&lfo_gain).ok()?;
lfo_gain.connect_with_audio_param(&osc.frequency()).ok()?;
osc.connect_with_audio_node(&_node).ok()?;
amp_node.connect_with_audio_node(&panner).ok()?;
panner.connect_with_audio_node(&state.master).ok()?;
osc.start().ok()?;
lfo.start().ok()?;
Some(Tone { osc, lfo, lfo_gain, amp: amp_node, panner })
})();
state.tones[idx] = result;
}
if let Some(tone) = &state.tones[idx] {
tone.osc.frequency().set_value(freq);
tone.amp.gain().set_value(amp);
tone.lfo.frequency().set_value(lfo_rate);
tone.lfo_gain.gain().set_value(freq * lfo_depth);
js_call(
tone.panner.as_ref(),
"setPosition",
&[x as f64, y as f64, z as f64],
);
}
}
});
}
pub fn set_listener(cry: f32, sry: f32, crx: f32, srx: f32) {
AUDIO.with(|a| {
let opt = a.borrow();
if let Some(state) = opt.as_ref() {
let listener = state.ctx.listener();
let fx = sry;
let fy = -srx * cry;
let fz = -crx * cry;
js_call(
listener.as_ref(),
"setOrientation",
&[fx as f64, fy as f64, fz as f64, 0.0, 1.0, 0.0],
);
}
});
}
pub fn set_master_volume(vol: f32) {
AUDIO.with(|a| {
if let Some(state) = a.borrow().as_ref() {
state.master.gain().set_value(vol);
}
});
}
pub fn load_bgm(_path: &str, _vol: f32) {}
pub fn set_bgm_volume(_vol: f32) {}