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 = moont::SAMPLE_RATE as f32;
25
26struct Inner {
27 synth: CM32L,
28 smf_events: Vec<smf::Event>,
29 smf_index: usize,
30 smf_start: u32,
31 smf_duration: u32,
32}
33
34impl Inner {
35 fn feed_smf(&mut self, deadline: u32) {
36 while self.smf_index < self.smf_events.len()
37 && self.smf_events[self.smf_index].time() <= deadline
38 {
39 match &self.smf_events[self.smf_index] {
40 smf::Event::Msg { time, msg } => {
41 self.synth.play_msg_at(*msg, *time);
42 }
43 smf::Event::Sysex { time, data } => {
44 self.synth.play_sysex_at(data, *time);
45 }
46 }
47 self.smf_index += 1;
48 }
49 }
50}
51
52#[wasm_bindgen]
54pub struct CM32LWeb {
55 inner: Rc<RefCell<Inner>>,
56 _ctx: AudioContext,
57 _processor: ScriptProcessorNode,
58 _closure: Closure<dyn FnMut(AudioProcessingEvent)>,
59}
60
61fn setup(synth: CM32L) -> Result<CM32LWeb, JsValue> {
62 let inner = Rc::new(RefCell::new(Inner {
63 synth,
64 smf_events: Vec::new(),
65 smf_index: 0,
66 smf_start: 0,
67 smf_duration: 0,
68 }));
69
70 let opts = AudioContextOptions::new();
71 opts.set_sample_rate(SAMPLE_RATE);
72 let ctx = AudioContext::new_with_context_options(&opts)?;
73
74 let processor = ctx.create_script_processor_with_buffer_size_and_number_of_input_channels_and_number_of_output_channels(
75 BUFFER_SIZE, 0, 2,
76 )?;
77
78 let inner_ref = inner.clone();
79 let closure = Closure::wrap(Box::new(move |event: AudioProcessingEvent| {
80 let buf = event.output_buffer().unwrap();
81 let len = buf.length() as usize;
82 let mut inner = inner_ref.borrow_mut();
83 let current = inner.synth.current_time();
84 inner.feed_smf(current + len as u32);
85 let mut frames = vec![Frame(0, 0); len];
86 inner.synth.render(&mut frames);
87 drop(inner);
88
89 let mut left = vec![0.0f32; len];
90 let mut right = vec![0.0f32; len];
91 for (i, f) in frames.iter().enumerate() {
92 left[i] = f.0 as f32 / 32768.0;
93 right[i] = f.1 as f32 / 32768.0;
94 }
95 buf.copy_to_channel(&left, 0).unwrap();
96 buf.copy_to_channel(&right, 1).unwrap();
97 }) as Box<dyn FnMut(AudioProcessingEvent)>);
98
99 processor.set_onaudioprocess(Some(closure.as_ref().unchecked_ref()));
100 processor.connect_with_audio_node(&ctx.destination())?;
101
102 Ok(CM32LWeb {
103 inner,
104 _ctx: ctx,
105 _processor: processor,
106 _closure: closure,
107 })
108}
109
110#[wasm_bindgen]
111impl CM32LWeb {
112 #[cfg(feature = "bundle-rom")]
116 #[wasm_bindgen(constructor)]
117 pub fn new() -> Result<CM32LWeb, JsValue> {
118 let synth = CM32L::new(moont::Rom::bundled());
119 setup(synth)
120 }
121
122 pub fn from_rom(
124 control_rom: &[u8],
125 pcm_rom: &[u8],
126 ) -> Result<CM32LWeb, JsValue> {
127 let rom = moont::Rom::new(control_rom, pcm_rom)
128 .map_err(|e| JsValue::from_str(&format!("{e:?}")))?;
129 let synth = CM32L::new(rom);
130 setup(synth)
131 }
132
133 pub fn play_midi(&self, data: &[u8]) -> bool {
137 if data.is_empty() || data.len() > 3 {
138 return false;
139 }
140 let mut msg: u32 = 0;
141 for (i, &b) in data.iter().enumerate() {
142 msg |= (b as u32) << (i * 8);
143 }
144 self.inner.borrow_mut().synth.play_msg(msg)
145 }
146
147 pub fn play_sysex(&self, data: &[u8]) -> bool {
151 self.inner.borrow_mut().synth.play_sysex(data)
152 }
153
154 pub fn load_smf(&self, data: &[u8]) -> Result<f64, JsValue> {
161 let events =
162 smf::parse(data).map_err(|e| JsValue::from_str(&format!("{e}")))?;
163 let last = events.last().map(|e| e.time()).unwrap_or(0);
164 let tail = 2 * SAMPLE_RATE as u32;
165 let mut inner = self.inner.borrow_mut();
166 let offset = inner.synth.current_time();
167 let shifted: Vec<smf::Event> = events
168 .into_iter()
169 .map(|e| match e {
170 smf::Event::Msg { time, msg } => smf::Event::Msg {
171 time: time + offset,
172 msg,
173 },
174 smf::Event::Sysex { time, data } => smf::Event::Sysex {
175 time: time + offset,
176 data,
177 },
178 })
179 .collect();
180 inner.smf_events = shifted;
181 inner.smf_index = 0;
182 inner.smf_start = offset;
183 inner.smf_duration = last + tail;
184 Ok(inner.smf_duration as f64 / SAMPLE_RATE as f64)
185 }
186
187 pub fn stop_smf(&self) {
189 let mut inner = self.inner.borrow_mut();
190 inner.smf_events.clear();
191 inner.smf_index = 0;
192 inner.smf_duration = 0;
193 }
194
195 pub fn smf_elapsed(&self) -> f64 {
197 let inner = self.inner.borrow();
198 let elapsed =
199 inner.synth.current_time().saturating_sub(inner.smf_start);
200 elapsed as f64 / SAMPLE_RATE as f64
201 }
202
203 pub fn current_time(&self) -> f64 {
205 self.inner.borrow().synth.current_time() as f64 / SAMPLE_RATE as f64
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 #[test]
212 fn test_midi_packing() {
213 let data: &[u8] = &[0x90, 0x3C, 0x7F];
214 let mut msg: u32 = 0;
215 for (i, &b) in data.iter().enumerate() {
216 msg |= (b as u32) << (i * 8);
217 }
218 assert_eq!(msg, 0x007F3C90);
219 }
220}