devalang_core/core/plugin/
runner.rs

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