use std::cell::RefCell;
use std::rc::Rc;
use moont::{Frame, Synth, cm32l, smf};
use wasm_bindgen::prelude::*;
use web_sys::{
AudioContext, AudioContextOptions, AudioProcessingEvent,
ScriptProcessorNode,
};
const BUFFER_SIZE: u32 = 1024;
const SAMPLE_RATE: f32 = moont::SAMPLE_RATE as f32;
struct Inner {
synth: cm32l::Device,
smf_events: Vec<smf::Event>,
smf_index: usize,
smf_start: u32,
smf_duration: u32,
}
impl Inner {
fn feed_smf(&mut self, deadline: u32) {
while self.smf_index < self.smf_events.len()
&& self.smf_events[self.smf_index].time() <= deadline
{
match &self.smf_events[self.smf_index] {
smf::Event::Msg { time, msg } => {
self.synth.play_msg_at(*msg, *time);
}
smf::Event::Sysex { time, data } => {
self.synth.play_sysex_at(data, *time);
}
_ => {}
}
self.smf_index += 1;
}
}
}
#[wasm_bindgen]
pub struct Cm32lSynth {
inner: Rc<RefCell<Inner>>,
_ctx: AudioContext,
_processor: ScriptProcessorNode,
_closure: Closure<dyn FnMut(AudioProcessingEvent)>,
}
fn setup(synth: cm32l::Device) -> Result<Cm32lSynth, JsValue> {
let inner = Rc::new(RefCell::new(Inner {
synth,
smf_events: Vec::new(),
smf_index: 0,
smf_start: 0,
smf_duration: 0,
}));
let opts = AudioContextOptions::new();
opts.set_sample_rate(SAMPLE_RATE);
let ctx = AudioContext::new_with_context_options(&opts)?;
let processor = ctx.create_script_processor_with_buffer_size_and_number_of_input_channels_and_number_of_output_channels(
BUFFER_SIZE, 0, 2,
)?;
let inner_ref = inner.clone();
let closure = Closure::wrap(Box::new(move |event: AudioProcessingEvent| {
let buf = event.output_buffer().unwrap();
let len = buf.length() as usize;
let mut inner = inner_ref.borrow_mut();
let current = inner.synth.current_time();
inner.feed_smf(current + len as u32);
let mut frames = vec![Frame(0, 0); len];
inner.synth.render(&mut frames);
drop(inner);
let mut left = vec![0.0f32; len];
let mut right = vec![0.0f32; len];
for (i, f) in frames.iter().enumerate() {
left[i] = f.0 as f32 / 32768.0;
right[i] = f.1 as f32 / 32768.0;
}
buf.copy_to_channel(&left, 0).unwrap();
buf.copy_to_channel(&right, 1).unwrap();
}) as Box<dyn FnMut(AudioProcessingEvent)>);
processor.set_onaudioprocess(Some(closure.as_ref().unchecked_ref()));
processor.connect_with_audio_node(&ctx.destination())?;
Ok(Cm32lSynth {
inner,
_ctx: ctx,
_processor: processor,
_closure: closure,
})
}
#[wasm_bindgen]
impl Cm32lSynth {
#[cfg(feature = "bundle-rom")]
#[wasm_bindgen(constructor)]
pub fn new() -> Result<Cm32lSynth, JsValue> {
let synth = cm32l::Device::new(cm32l::Rom::bundled());
setup(synth)
}
pub fn from_rom(
control_rom: &[u8],
pcm_rom: &[u8],
) -> Result<Cm32lSynth, JsValue> {
let rom = cm32l::Rom::new(control_rom, pcm_rom)
.map_err(|e| JsValue::from_str(&format!("{e:?}")))?;
let synth = cm32l::Device::new(rom);
setup(synth)
}
pub fn play_midi(&self, data: &[u8]) -> bool {
if data.is_empty() || data.len() > 3 {
return false;
}
let mut msg: u32 = 0;
for (i, &b) in data.iter().enumerate() {
msg |= (b as u32) << (i * 8);
}
self.inner.borrow_mut().synth.play_msg(msg)
}
pub fn play_sysex(&self, data: &[u8]) -> bool {
self.inner.borrow_mut().synth.play_sysex(data)
}
pub fn load_smf(&self, data: &[u8]) -> Result<f64, JsValue> {
let events =
smf::parse(data).map_err(|e| JsValue::from_str(&format!("{e}")))?;
let last = events.last().map(|e| e.time()).unwrap_or(0);
let tail = 2 * SAMPLE_RATE as u32;
let mut inner = self.inner.borrow_mut();
let offset = inner.synth.current_time();
let shifted: Vec<smf::Event> = events
.into_iter()
.map(|e| match e {
smf::Event::Msg { time, msg } => smf::Event::Msg {
time: time + offset,
msg,
},
smf::Event::Sysex { time, data } => smf::Event::Sysex {
time: time + offset,
data,
},
_ => e,
})
.collect();
inner.smf_events = shifted;
inner.smf_index = 0;
inner.smf_start = offset;
inner.smf_duration = last + tail;
Ok(inner.smf_duration as f64 / SAMPLE_RATE as f64)
}
pub fn stop_smf(&self) {
let mut inner = self.inner.borrow_mut();
inner.smf_events.clear();
inner.smf_index = 0;
inner.smf_duration = 0;
}
pub fn smf_elapsed(&self) -> f64 {
let inner = self.inner.borrow();
let elapsed =
inner.synth.current_time().saturating_sub(inner.smf_start);
elapsed as f64 / SAMPLE_RATE as f64
}
pub fn current_time(&self) -> f64 {
self.inner.borrow().synth.current_time() as f64 / SAMPLE_RATE as f64
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_midi_packing() {
let data: &[u8] = &[0x90, 0x3C, 0x7F];
let mut msg: u32 = 0;
for (i, &b) in data.iter().enumerate() {
msg |= (b as u32) << (i * 8);
}
assert_eq!(msg, 0x007F3C90);
}
}