mimium_web/
lib.rs

1use std::path::PathBuf;
2use std::sync::mpsc;
3
4use mimium_audiodriver::backends::local_buffer::LocalBufferDriver;
5use mimium_audiodriver::driver::{Driver, RuntimeData};
6use mimium_lang::ExecContext;
7use mimium_lang::log;
8use mimium_lang::runtime::vm;
9use mimium_lang::utils::error::report;
10use wasm_bindgen::prelude::*;
11#[wasm_bindgen]
12#[derive(Default)]
13pub struct Config {
14    pub sample_rate: f64,
15    pub input_channels: u32,
16    pub output_channels: u32,
17    pub buffer_size: u32,
18}
19#[wasm_bindgen]
20impl Config {
21    #[wasm_bindgen]
22    pub fn new() -> Self {
23        Self::default()
24    }
25}
26
27type Output<'a> = &'a mut [f32];
28type Input<'a> = &'a [f32];
29
30type Processer = Box<dyn FnMut(Input, Output) -> u64>;
31
32#[wasm_bindgen]
33#[derive(Default)]
34pub struct Context {
35    processor: Option<Processer>,
36    swap_channel: Option<mpsc::Sender<vm::Program>>,
37    config: Config,
38}
39
40fn get_default_context() -> ExecContext {
41    let mut ctx = ExecContext::new([].into_iter(), None, Default::default());
42    ctx.add_system_plugin(mimium_scheduler::get_default_scheduler_plugin());
43    if let Some(midi_plug) = mimium_midi::MidiPlugin::try_new() {
44        ctx.add_system_plugin(midi_plug);
45    } else {
46        log::warn!("Midi is not supported on this platform.")
47    }
48    ctx
49}
50
51#[wasm_bindgen]
52impl Context {
53    #[wasm_bindgen(constructor)]
54    pub fn new(config: Config) -> Self {
55        std::panic::set_hook(Box::new(console_error_panic_hook::hook));
56        Context {
57            config,
58            ..Default::default()
59        }
60    }
61    #[wasm_bindgen]
62    pub fn compile(&mut self, src: String) {
63        let (sender, receiver) = mpsc::channel();
64        self.swap_channel = Some(sender);
65        let mut ctx = get_default_context();
66        let mut driver = LocalBufferDriver::new(self.config.buffer_size as usize);
67        ctx.add_plugin(driver.get_as_plugin());
68
69        if let Err(e) = ctx.prepare_machine(src.as_str()) {
70            report(&src, PathBuf::from("/"), &e);
71        }
72        ctx.run_main();
73        let runtimedata = {
74            let ctxmut: &mut ExecContext = &mut ctx;
75            RuntimeData::try_from(ctxmut).unwrap()
76        };
77        let iochannels = driver.init(
78            runtimedata,
79            Some(mimium_audiodriver::driver::SampleRate::from(
80                self.config.sample_rate as u32,
81            )),
82        );
83        let (ichannels, ochannels) = iochannels.map_or((0, 0), |io| (io.input, io.output));
84        self.config.input_channels = ichannels;
85        self.config.output_channels = ochannels;
86        let out_ch = self.config.output_channels;
87        let mut out_buf = vec![0.0; (out_ch * self.config.buffer_size) as usize];
88        self.processor = Some(Box::new(move |_input, output: Output| -> u64 {
89            if let (Some(vmdata), Ok(prog)) = (driver.vmdata.as_mut(), receiver.try_recv()) {
90                vmdata.vm = vmdata.vm.new_resume(prog);
91            };
92            driver.play();
93            driver
94                .get_generated_samples()
95                .iter()
96                .map(|f| *f as f32)
97                .enumerate()
98                .for_each(|(i, f)| {
99                    out_buf[i] = f;
100                });
101            output.copy_from_slice(&out_buf);
102            0
103        }));
104    }
105    #[wasm_bindgen]
106    pub fn recompile(&mut self,src:String){
107        let mut ctx = get_default_context();
108        let driver = LocalBufferDriver::new(self.config.buffer_size as usize);
109        ctx.add_plugin(driver.get_as_plugin());
110        match ctx.prepare_machine(src.as_str()) {
111            Err(e) => {
112                report(&src, PathBuf::from("/"), &e);
113            }
114            Ok(()) => {
115                ctx.run_main();
116                let prog = ctx.take_vm().unwrap().prog;                
117                self.config.input_channels = prog.iochannels.map_or(0, |io| io.input);
118                self.config.output_channels = prog.iochannels.map_or(0, |io| io.output);
119                self.swap_channel.as_mut().unwrap().send(prog).unwrap();
120            }
121            
122        }
123    }
124    #[wasm_bindgen]
125    pub fn get_input_channels(&self) -> u32 {
126        self.config.input_channels
127    }
128    #[wasm_bindgen]
129    pub fn get_output_channels(&self) -> u32 {
130        self.config.output_channels
131    }
132    /// .
133    ///
134    /// # Safety
135    /// Array size of input and output must be equal to `input_channels * buffer_size` and `output_channels * buffer_size` respectively.
136    /// .
137    #[wasm_bindgen]
138    pub fn process(&mut self, input: &[f32], output: &mut [f32]) -> u64 {
139        unsafe { self.processor.as_mut().unwrap_unchecked()(input, output) }
140    }
141}
142
143#[cfg(test)]
144mod test {
145    use super::*;
146    #[test]
147    fn test_iochannels() {
148        let mut ctx = Context::new(Config::default());
149        ctx.compile(
150            r#"fn dsp(input:float){
151        (0,input)
152        }"#
153            .to_string(),
154        );
155        assert_eq!(1, ctx.get_input_channels());
156        assert_eq!(2, ctx.get_output_channels());
157    }
158}