1use std::cell::RefCell;
51use std::rc::Rc;
52
53use moont::{CM32L, Frame, Synth, smf};
54use wasm_bindgen::prelude::*;
55use web_sys::{
56 AudioContext, AudioContextOptions, AudioProcessingEvent,
57 ScriptProcessorNode,
58};
59
60const BUFFER_SIZE: u32 = 1024;
61const SAMPLE_RATE: f32 = moont::SAMPLE_RATE as f32;
62
63struct Inner {
64 synth: CM32L,
65 smf_events: Vec<smf::Event>,
66 smf_index: usize,
67 smf_start: u32,
68 smf_duration: u32,
69}
70
71impl Inner {
72 fn feed_smf(&mut self, deadline: u32) {
73 while self.smf_index < self.smf_events.len()
74 && self.smf_events[self.smf_index].time() <= deadline
75 {
76 match &self.smf_events[self.smf_index] {
77 smf::Event::Msg { time, msg } => {
78 self.synth.play_msg_at(*msg, *time);
79 }
80 smf::Event::Sysex { time, data } => {
81 self.synth.play_sysex_at(data, *time);
82 }
83 }
84 self.smf_index += 1;
85 }
86 }
87}
88
89#[wasm_bindgen]
98pub struct CM32LWeb {
99 inner: Rc<RefCell<Inner>>,
100 _ctx: AudioContext,
101 _processor: ScriptProcessorNode,
102 _closure: Closure<dyn FnMut(AudioProcessingEvent)>,
103}
104
105fn setup(synth: CM32L) -> Result<CM32LWeb, JsValue> {
106 let inner = Rc::new(RefCell::new(Inner {
107 synth,
108 smf_events: Vec::new(),
109 smf_index: 0,
110 smf_start: 0,
111 smf_duration: 0,
112 }));
113
114 let opts = AudioContextOptions::new();
115 opts.set_sample_rate(SAMPLE_RATE);
116 let ctx = AudioContext::new_with_context_options(&opts)?;
117
118 let processor = ctx.create_script_processor_with_buffer_size_and_number_of_input_channels_and_number_of_output_channels(
119 BUFFER_SIZE, 0, 2,
120 )?;
121
122 let inner_ref = inner.clone();
123 let closure = Closure::wrap(Box::new(move |event: AudioProcessingEvent| {
124 let buf = event.output_buffer().unwrap();
125 let len = buf.length() as usize;
126 let mut inner = inner_ref.borrow_mut();
127 let current = inner.synth.current_time();
128 inner.feed_smf(current + len as u32);
129 let mut frames = vec![Frame(0, 0); len];
130 inner.synth.render(&mut frames);
131 drop(inner);
132
133 let mut left = vec![0.0f32; len];
134 let mut right = vec![0.0f32; len];
135 for (i, f) in frames.iter().enumerate() {
136 left[i] = f.0 as f32 / 32768.0;
137 right[i] = f.1 as f32 / 32768.0;
138 }
139 buf.copy_to_channel(&left, 0).unwrap();
140 buf.copy_to_channel(&right, 1).unwrap();
141 }) as Box<dyn FnMut(AudioProcessingEvent)>);
142
143 processor.set_onaudioprocess(Some(closure.as_ref().unchecked_ref()));
144 processor.connect_with_audio_node(&ctx.destination())?;
145
146 Ok(CM32LWeb {
147 inner,
148 _ctx: ctx,
149 _processor: processor,
150 _closure: closure,
151 })
152}
153
154#[wasm_bindgen]
155impl CM32LWeb {
156 #[cfg(feature = "bundle-rom")]
161 #[wasm_bindgen(constructor)]
162 pub fn new() -> Result<CM32LWeb, JsValue> {
163 let synth = CM32L::new(moont::Rom::bundled());
164 setup(synth)
165 }
166
167 pub fn from_rom(
172 control_rom: &[u8],
173 pcm_rom: &[u8],
174 ) -> Result<CM32LWeb, JsValue> {
175 let rom = moont::Rom::new(control_rom, pcm_rom)
176 .map_err(|e| JsValue::from_str(&format!("{e:?}")))?;
177 let synth = CM32L::new(rom);
178 setup(synth)
179 }
180
181 pub fn play_midi(&self, data: &[u8]) -> bool {
185 if data.is_empty() || data.len() > 3 {
186 return false;
187 }
188 let mut msg: u32 = 0;
189 for (i, &b) in data.iter().enumerate() {
190 msg |= (b as u32) << (i * 8);
191 }
192 self.inner.borrow_mut().synth.play_msg(msg)
193 }
194
195 pub fn play_sysex(&self, data: &[u8]) -> bool {
199 self.inner.borrow_mut().synth.play_sysex(data)
200 }
201
202 pub fn load_smf(&self, data: &[u8]) -> Result<f64, JsValue> {
209 let events =
210 smf::parse(data).map_err(|e| JsValue::from_str(&format!("{e}")))?;
211 let last = events.last().map(|e| e.time()).unwrap_or(0);
212 let tail = 2 * SAMPLE_RATE as u32;
213 let mut inner = self.inner.borrow_mut();
214 let offset = inner.synth.current_time();
215 let shifted: Vec<smf::Event> = events
216 .into_iter()
217 .map(|e| match e {
218 smf::Event::Msg { time, msg } => smf::Event::Msg {
219 time: time + offset,
220 msg,
221 },
222 smf::Event::Sysex { time, data } => smf::Event::Sysex {
223 time: time + offset,
224 data,
225 },
226 })
227 .collect();
228 inner.smf_events = shifted;
229 inner.smf_index = 0;
230 inner.smf_start = offset;
231 inner.smf_duration = last + tail;
232 Ok(inner.smf_duration as f64 / SAMPLE_RATE as f64)
233 }
234
235 pub fn stop_smf(&self) {
237 let mut inner = self.inner.borrow_mut();
238 inner.smf_events.clear();
239 inner.smf_index = 0;
240 inner.smf_duration = 0;
241 }
242
243 pub fn smf_elapsed(&self) -> f64 {
245 let inner = self.inner.borrow();
246 let elapsed =
247 inner.synth.current_time().saturating_sub(inner.smf_start);
248 elapsed as f64 / SAMPLE_RATE as f64
249 }
250
251 pub fn current_time(&self) -> f64 {
253 self.inner.borrow().synth.current_time() as f64 / SAMPLE_RATE as f64
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 #[test]
260 fn test_midi_packing() {
261 let data: &[u8] = &[0x90, 0x3C, 0x7F];
262 let mut msg: u32 = 0;
263 for (i, &b) in data.iter().enumerate() {
264 msg |= (b as u32) << (i * 8);
265 }
266 assert_eq!(msg, 0x007F3C90);
267 }
268}