mimium_web/
lib.rs

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