devalang_core/core/plugin/
runner.rs

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