1use std::cell::RefCell;
14use std::rc::Rc;
15
16use moont::{CM32L, Frame, Synth, smf};
17use wasm_bindgen::prelude::*;
18use web_sys::{
19 AudioContext, AudioContextOptions, AudioProcessingEvent,
20 ScriptProcessorNode,
21};
22
23const BUFFER_SIZE: u32 = 1024;
24const SAMPLE_RATE: f32 = 32000.0;
25
26struct Inner {
27 synth: CM32L,
28 smf_events: Vec<smf::Event>,
29 smf_index: usize,
30}
31
32impl Inner {
33 fn feed_smf(&mut self, deadline: u32) {
34 while self.smf_index < self.smf_events.len()
35 && self.smf_events[self.smf_index].time() <= deadline
36 {
37 match &self.smf_events[self.smf_index] {
38 smf::Event::Msg { time, msg } => {
39 self.synth.play_msg_at(*msg, *time);
40 }
41 smf::Event::Sysex { time, data } => {
42 self.synth.play_sysex_at(data, *time);
43 }
44 }
45 self.smf_index += 1;
46 }
47 }
48}
49
50#[wasm_bindgen]
52pub struct CM32LWeb {
53 inner: Rc<RefCell<Inner>>,
54 _ctx: AudioContext,
55 _processor: ScriptProcessorNode,
56 _closure: Closure<dyn FnMut(AudioProcessingEvent)>,
57}
58
59fn setup(synth: CM32L) -> Result<CM32LWeb, JsValue> {
60 let inner = Rc::new(RefCell::new(Inner {
61 synth,
62 smf_events: Vec::new(),
63 smf_index: 0,
64 }));
65
66 let opts = AudioContextOptions::new();
67 opts.set_sample_rate(SAMPLE_RATE);
68 let ctx = AudioContext::new_with_context_options(&opts)?;
69
70 let processor = ctx.create_script_processor_with_buffer_size_and_number_of_input_channels_and_number_of_output_channels(
71 BUFFER_SIZE, 0, 2,
72 )?;
73
74 let inner_ref = inner.clone();
75 let closure = Closure::wrap(Box::new(move |event: AudioProcessingEvent| {
76 let buf = event.output_buffer().unwrap();
77 let len = buf.length() as usize;
78 let mut inner = inner_ref.borrow_mut();
79 let current = inner.synth.current_time();
80 inner.feed_smf(current + len as u32);
81 let mut frames = vec![Frame(0, 0); len];
82 inner.synth.render(&mut frames);
83 drop(inner);
84
85 let mut left = vec![0.0f32; len];
86 let mut right = vec![0.0f32; len];
87 for (i, f) in frames.iter().enumerate() {
88 left[i] = f.0 as f32 / 32768.0;
89 right[i] = f.1 as f32 / 32768.0;
90 }
91 buf.copy_to_channel(&left, 0).unwrap();
92 buf.copy_to_channel(&right, 1).unwrap();
93 }) as Box<dyn FnMut(AudioProcessingEvent)>);
94
95 processor.set_onaudioprocess(Some(closure.as_ref().unchecked_ref()));
96 processor.connect_with_audio_node(&ctx.destination())?;
97
98 Ok(CM32LWeb {
99 inner,
100 _ctx: ctx,
101 _processor: processor,
102 _closure: closure,
103 })
104}
105
106#[wasm_bindgen]
107impl CM32LWeb {
108 #[cfg(feature = "bundle-rom")]
112 #[wasm_bindgen(constructor)]
113 pub fn new() -> Result<CM32LWeb, JsValue> {
114 let synth = CM32L::new(moont::Rom::bundled());
115 setup(synth)
116 }
117
118 pub fn from_rom(
120 control_rom: &[u8],
121 pcm_rom: &[u8],
122 ) -> Result<CM32LWeb, JsValue> {
123 let rom = moont::Rom::new(control_rom, pcm_rom)
124 .map_err(|e| JsValue::from_str(&format!("{e:?}")))?;
125 let synth = CM32L::new(rom);
126 setup(synth)
127 }
128
129 pub fn play_midi(&self, data: &[u8]) -> bool {
133 if data.is_empty() || data.len() > 3 {
134 return false;
135 }
136 let mut msg: u32 = 0;
137 for (i, &b) in data.iter().enumerate() {
138 msg |= (b as u32) << (i * 8);
139 }
140 self.inner.borrow_mut().synth.play_msg(msg)
141 }
142
143 pub fn play_sysex(&self, data: &[u8]) -> bool {
147 self.inner.borrow_mut().synth.play_sysex(data)
148 }
149
150 pub fn load_smf(&self, data: &[u8]) -> Result<f64, JsValue> {
156 let events =
157 smf::parse(data).map_err(|e| JsValue::from_str(&format!("{e}")))?;
158 let last = events.last().map(|e| e.time()).unwrap_or(0);
159 let total = last + 2 * SAMPLE_RATE as u32;
160 let mut inner = self.inner.borrow_mut();
161 inner.smf_events = events;
162 inner.smf_index = 0;
163 Ok(total as f64 / SAMPLE_RATE as f64)
164 }
165
166 pub fn current_time(&self) -> f64 {
168 self.inner.borrow().synth.current_time() as f64 / SAMPLE_RATE as f64
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 #[test]
175 fn test_midi_packing() {
176 let data: &[u8] = &[0x90, 0x3C, 0x7F];
177 let mut msg: u32 = 0;
178 for (i, &b) in data.iter().enumerate() {
179 msg |= (b as u32) << (i * 8);
180 }
181 assert_eq!(msg, 0x007F3C90);
182 }
183}