devalang_core/core/plugin/runner/
non_wasm.rs

1use std::collections::HashMap;
2
3use wasmtime::{Engine, Instance, Linker, Module, Store, TypedFunc};
4
5type RenderFunc = TypedFunc<(i32, i32, f32, f32, i32, i32, i32), ()>;
6
7pub struct WasmPluginRunner {
8    engine: Engine,
9}
10
11impl Default for WasmPluginRunner {
12    fn default() -> Self {
13        Self::new()
14    }
15}
16
17impl WasmPluginRunner {
18    pub fn new() -> Self {
19        let engine = Engine::default();
20        Self { engine }
21    }
22
23    pub fn process_in_place(&self, wasm_bytes: &[u8], buffer: &mut [f32]) -> Result<(), String> {
24        let module = Module::new(&self.engine, wasm_bytes)
25            .map_err(|e| format!("Failed to compile wasm: {e}"))?;
26
27        let mut store = Store::new(&self.engine, ());
28        let linker = Linker::new(&self.engine);
29
30        let instance = linker
31            .instantiate(&mut store, &module)
32            .map_err(|e| format!("Failed to instantiate wasm: {e}"))?;
33
34        let memory = instance
35            .get_memory(&mut store, "memory")
36            .ok_or_else(|| "WASM memory export not found".to_string())?;
37
38        let func = instance
39            .get_typed_func::<(i32, i32), ()>(&mut store, "process")
40            .map_err(|_| "Exported function `process(i32,i32)` not found".to_string())?;
41
42        let byte_len = std::mem::size_of_val(buffer) as i32;
43        let ptr = Self::alloc_temp(&mut store, &instance, &memory, byte_len as usize)? as i32;
44        let mem_slice = memory
45            .data_mut(&mut store)
46            .get_mut(ptr as usize..(ptr as usize) + (byte_len as usize))
47            .ok_or_else(|| "Failed to get memory slice".to_string())?;
48
49        let src_bytes =
50            unsafe { std::slice::from_raw_parts(buffer.as_ptr() as *const u8, byte_len as usize) };
51        mem_slice.copy_from_slice(src_bytes);
52
53        func.call(&mut store, (ptr, buffer.len() as i32))
54            .map_err(|e| format!("Error calling `process`: {e}"))?;
55
56        let mem_slice_after = memory
57            .data(&store)
58            .get(ptr as usize..(ptr as usize) + (byte_len as usize))
59            .ok_or_else(|| "Failed to get memory slice after".to_string())?;
60        let dst_bytes = unsafe {
61            std::slice::from_raw_parts_mut(buffer.as_mut_ptr() as *mut u8, byte_len as usize)
62        };
63        dst_bytes.copy_from_slice(mem_slice_after);
64
65        Ok(())
66    }
67
68    pub fn render_note_in_place(
69        &self,
70        wasm_bytes: &[u8],
71        buffer: &mut [f32],
72        synth_name: Option<&str>,
73        freq: f32,
74        amp: f32,
75        duration_ms: i32,
76        sample_rate: i32,
77        channels: i32,
78    ) -> Result<(), String> {
79        let module = Module::new(&self.engine, wasm_bytes)
80            .map_err(|e| format!("Failed to compile wasm: {e}"))?;
81
82        let mut store = Store::new(&self.engine, ());
83        let linker = Linker::new(&self.engine);
84
85        let instance = linker
86            .instantiate(&mut store, &module)
87            .map_err(|e| format!("Failed to instantiate wasm: {e}"))?;
88
89        let memory = instance
90            .get_memory(&mut store, "memory")
91            .ok_or_else(|| "WASM memory export not found".to_string())?;
92
93        // Try specific function first
94        let mut func_opt: Option<RenderFunc> = None;
95        if let Some(name) = synth_name {
96            let specific = format!("render_note_{}", name);
97            if let Ok(f) = instance
98                .get_typed_func::<(i32, i32, f32, f32, i32, i32, i32), ()>(&mut store, &specific)
99            {
100                func_opt = Some(f);
101            }
102        }
103        if func_opt.is_none() {
104            // fallback to generic name
105            if let Ok(f) = instance.get_typed_func::<(i32, i32, f32, f32, i32, i32, i32), ()>(
106                &mut store,
107                "render_note",
108            ) {
109                func_opt = Some(f);
110            }
111        }
112
113        let func =
114            func_opt.ok_or_else(|| "Exported function `render_note` not found".to_string())?;
115
116        // Copy host buffer into wasm memory
117        let byte_len = std::mem::size_of_val(buffer) as i32;
118        let ptr = Self::alloc_temp(&mut store, &instance, &memory, byte_len as usize)? as i32;
119        let mem_slice = memory
120            .data_mut(&mut store)
121            .get_mut(ptr as usize..(ptr as usize) + (byte_len as usize))
122            .ok_or_else(|| "Failed to get memory slice".to_string())?;
123        let src_bytes =
124            unsafe { std::slice::from_raw_parts(buffer.as_ptr() as *const u8, byte_len as usize) };
125        mem_slice.copy_from_slice(src_bytes);
126
127        // Call render
128        func.call(
129            &mut store,
130            (
131                ptr,
132                buffer.len() as i32,
133                freq,
134                amp,
135                duration_ms,
136                sample_rate,
137                channels,
138            ),
139        )
140        .map_err(|e| format!("Error calling `render_note`: {e}"))?;
141
142        // Copy back
143        let mem_slice_after = memory
144            .data(&store)
145            .get(ptr as usize..(ptr as usize) + (byte_len as usize))
146            .ok_or_else(|| "Failed to get memory slice after".to_string())?;
147        let dst_bytes = unsafe {
148            std::slice::from_raw_parts_mut(buffer.as_mut_ptr() as *mut u8, byte_len as usize)
149        };
150        dst_bytes.copy_from_slice(mem_slice_after);
151
152        Ok(())
153    }
154
155    /// Same as render_note_in_place, but first tries to call exported setters `set_<param>(f32)`
156    /// for each provided param before rendering. Ignored if setter is missing.
157    pub fn render_note_with_params_in_place(
158        &self,
159        wasm_bytes: &[u8],
160        buffer: &mut [f32],
161        synth_name: Option<&str>,
162        freq: f32,
163        amp: f32,
164        duration_ms: i32,
165        sample_rate: i32,
166        channels: i32,
167        params_num: &HashMap<String, f32>,
168        params_str: Option<&HashMap<String, String>>,
169    ) -> Result<(), String> {
170        let module = Module::new(&self.engine, wasm_bytes)
171            .map_err(|e| format!("Failed to compile wasm: {e}"))?;
172
173        let mut store = Store::new(&self.engine, ());
174        let linker = Linker::new(&self.engine);
175
176        let instance = linker
177            .instantiate(&mut store, &module)
178            .map_err(|e| format!("Failed to instantiate wasm: {e}"))?;
179
180        let memory = instance
181            .get_memory(&mut store, "memory")
182            .ok_or_else(|| "WASM memory export not found".to_string())?;
183
184        // Call numeric setters if present: set_<param>(f32)
185        for (k, v) in params_num.iter() {
186            let fname = format!("set_{}", k);
187            if let Ok(setter) = instance.get_typed_func::<f32, ()>(&mut store, &fname) {
188                let _ = setter.call(&mut store, *v);
189            }
190        }
191
192        // Call string setters if present: set_<param>_str(ptr: i32, len: i32)
193        if let Some(smap) = params_str {
194            for (k, v) in smap.iter() {
195                let fname = format!("set_{}_str", k);
196                if let Ok(setter) = instance.get_typed_func::<(i32, i32), ()>(&mut store, &fname) {
197                    // Allocate and copy UTF-8 bytes into wasm memory
198                    let bytes = v.as_bytes();
199                    let ptr = Self::alloc_temp(&mut store, &instance, &memory, bytes.len())? as i32;
200                    let mem_slice = memory
201                        .data_mut(&mut store)
202                        .get_mut(ptr as usize..(ptr as usize) + bytes.len())
203                        .ok_or_else(|| "Failed to get memory slice for string".to_string())?;
204                    mem_slice.copy_from_slice(bytes);
205                    let _ = setter.call(&mut store, (ptr, bytes.len() as i32));
206                }
207            }
208        }
209
210        // Try specific or generic render function
211        let mut func_opt: Option<RenderFunc> = None;
212        if let Some(name) = synth_name {
213            let specific = format!("render_note_{}", name);
214            if let Ok(f) = instance
215                .get_typed_func::<(i32, i32, f32, f32, i32, i32, i32), ()>(&mut store, &specific)
216            {
217                func_opt = Some(f);
218            }
219        }
220        if func_opt.is_none() {
221            if let Ok(f) = instance.get_typed_func::<(i32, i32, f32, f32, i32, i32, i32), ()>(
222                &mut store,
223                "render_note",
224            ) {
225                func_opt = Some(f);
226            }
227        }
228        let func =
229            func_opt.ok_or_else(|| "Exported function `render_note` not found".to_string())?;
230
231        // Copy host buffer into wasm memory
232        let byte_len = std::mem::size_of_val(buffer) as i32;
233        let ptr = Self::alloc_temp(&mut store, &instance, &memory, byte_len as usize)? as i32;
234        let mem_slice = memory
235            .data_mut(&mut store)
236            .get_mut(ptr as usize..(ptr as usize) + (byte_len as usize))
237            .ok_or_else(|| "Failed to get memory slice".to_string())?;
238        let src_bytes =
239            unsafe { std::slice::from_raw_parts(buffer.as_ptr() as *const u8, byte_len as usize) };
240        mem_slice.copy_from_slice(src_bytes);
241
242        // Call render
243        func.call(
244            &mut store,
245            (
246                ptr,
247                buffer.len() as i32,
248                freq,
249                amp,
250                duration_ms,
251                sample_rate,
252                channels,
253            ),
254        )
255        .map_err(|e| format!("Error calling `render_note`: {e}"))?;
256
257        // Copy back
258        let mem_slice_after = memory
259            .data(&store)
260            .get(ptr as usize..(ptr as usize) + (byte_len as usize))
261            .ok_or_else(|| "Failed to get memory slice after".to_string())?;
262        let dst_bytes = unsafe {
263            std::slice::from_raw_parts_mut(buffer.as_mut_ptr() as *mut u8, byte_len as usize)
264        };
265        dst_bytes.copy_from_slice(mem_slice_after);
266
267        Ok(())
268    }
269
270    fn alloc_temp(
271        store: &mut Store<()>,
272        instance: &Instance,
273        memory: &wasmtime::Memory,
274        size: usize,
275    ) -> Result<usize, String> {
276        // Try to use an exported `__wbindgen_malloc` if present; otherwise, grow memory manually.
277        if let Ok(malloc) = instance.get_typed_func::<i32, i32>(&mut *store, "__wbindgen_malloc") {
278            let ptr = malloc
279                .call(&mut *store, size as i32)
280                .map_err(|e| format!("malloc failed: {e}"))? as usize;
281            return Ok(ptr);
282        }
283
284        // Fallback: grow memory and use end of memory as scratch space
285        let current_len = memory.data_size(&mut *store);
286        let need = size;
287        let pages_needed = (current_len + need).div_ceil(0x10000) as u64; // 64KiB pages
288        let current_pages = memory.size(&mut *store);
289        if pages_needed > current_pages {
290            let to_grow = pages_needed - current_pages;
291            memory
292                .grow(&mut *store, to_grow)
293                .map_err(|e| format!("memory.grow failed: {e}"))?;
294        }
295        Ok(current_len)
296    }
297}