devalang_core/core/plugin/runner/
non_wasm.rs

1use devalang_utils::logger::{LogLevel, Logger};
2use std::collections::HashMap;
3
4use wasmtime::{Engine, Instance, Linker, Module, Store, TypedFunc};
5
6type RenderFunc = TypedFunc<(i32, i32, f32, f32, i32, i32, i32), ()>;
7
8pub struct WasmPluginRunner {
9    engine: Engine,
10}
11
12impl Default for WasmPluginRunner {
13    fn default() -> Self {
14        Self::new()
15    }
16}
17
18impl WasmPluginRunner {
19    pub fn new() -> Self {
20        let engine = Engine::default();
21        Self { engine }
22    }
23
24    pub fn process_in_place(&self, wasm_bytes: &[u8], buffer: &mut [f32]) -> Result<(), String> {
25        let module = Module::new(&self.engine, wasm_bytes)
26            .map_err(|e| format!("Failed to compile wasm: {e}"))?;
27
28        let mut store = Store::new(&self.engine, ());
29        let linker = Linker::new(&self.engine);
30
31        let instance = linker
32            .instantiate(&mut store, &module)
33            .map_err(|e| format!("Failed to instantiate wasm: {e}"))?;
34
35        let memory = instance
36            .get_memory(&mut store, "memory")
37            .ok_or_else(|| "WASM memory export not found".to_string())?;
38
39        let func = instance
40            .get_typed_func::<(i32, i32), ()>(&mut store, "process")
41            .map_err(|_| "Exported function `process(i32,i32)` not found".to_string())?;
42
43        let byte_len = std::mem::size_of_val(buffer) as i32;
44        let ptr = Self::alloc_temp(&mut store, &instance, &memory, byte_len as usize)? as i32;
45        let mem_slice = memory
46            .data_mut(&mut store)
47            .get_mut(ptr as usize..(ptr as usize) + (byte_len as usize))
48            .ok_or_else(|| "Failed to get memory slice".to_string())?;
49
50        let src_bytes =
51            unsafe { std::slice::from_raw_parts(buffer.as_ptr() as *const u8, byte_len as usize) };
52        mem_slice.copy_from_slice(src_bytes);
53
54        func.call(&mut store, (ptr, buffer.len() as i32))
55            .map_err(|e| format!("Error calling `process`: {e}"))?;
56
57        let mem_slice_after = memory
58            .data(&store)
59            .get(ptr as usize..(ptr as usize) + (byte_len as usize))
60            .ok_or_else(|| "Failed to get memory slice after".to_string())?;
61        let dst_bytes = unsafe {
62            std::slice::from_raw_parts_mut(buffer.as_mut_ptr() as *mut u8, byte_len as usize)
63        };
64        dst_bytes.copy_from_slice(mem_slice_after);
65
66        Ok(())
67    }
68
69    pub fn render_note_in_place(
70        &self,
71        wasm_bytes: &[u8],
72        buffer: &mut [f32],
73        synth_name: Option<&str>,
74        freq: f32,
75        amp: f32,
76        duration_ms: i32,
77        sample_rate: i32,
78        channels: i32,
79    ) -> Result<(), String> {
80        let module = Module::new(&self.engine, wasm_bytes)
81            .map_err(|e| format!("Failed to compile wasm: {e}"))?;
82
83        let mut store = Store::new(&self.engine, ());
84        let linker = Linker::new(&self.engine);
85
86        let instance = linker
87            .instantiate(&mut store, &module)
88            .map_err(|e| format!("Failed to instantiate wasm: {e}"))?;
89
90        let memory = instance
91            .get_memory(&mut store, "memory")
92            .ok_or_else(|| "WASM memory export not found".to_string())?;
93
94        // Try specific + generic entry points in order of preference.
95        // 1) render_note_<name>
96        // 2) render_note
97        // 3) synth_<name>
98        // 4) synth
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            if let Ok(f) = instance.get_typed_func::<(i32, i32, f32, f32, i32, i32, i32), ()>(
110                &mut store,
111                "render_note",
112            ) {
113                func_opt = Some(f);
114            }
115        }
116        if func_opt.is_none() {
117            if let Some(name) = synth_name {
118                let specific = format!("synth_{}", name);
119                if let Ok(f) = instance.get_typed_func::<(i32, i32, f32, f32, i32, i32, i32), ()>(
120                    &mut store, &specific,
121                ) {
122                    func_opt = Some(f);
123                }
124            }
125        }
126        if func_opt.is_none() {
127            if let Ok(f) = instance
128                .get_typed_func::<(i32, i32, f32, f32, i32, i32, i32), ()>(&mut store, "synth")
129            {
130                func_opt = Some(f);
131            }
132        }
133
134        let func = func_opt.ok_or_else(|| {
135            "Exported function not found: tried render_note[_<name>] and synth[_<name>]".to_string()
136        })?;
137
138        // Copy host buffer into wasm memory
139        let byte_len = std::mem::size_of_val(buffer) as i32;
140        let ptr = Self::alloc_temp(&mut store, &instance, &memory, byte_len as usize)? as i32;
141        let mem_slice = memory
142            .data_mut(&mut store)
143            .get_mut(ptr as usize..(ptr as usize) + (byte_len as usize))
144            .ok_or_else(|| "Failed to get memory slice".to_string())?;
145        let src_bytes =
146            unsafe { std::slice::from_raw_parts(buffer.as_ptr() as *const u8, byte_len as usize) };
147        mem_slice.copy_from_slice(src_bytes);
148
149        // Call render
150        func.call(
151            &mut store,
152            (
153                ptr,
154                buffer.len() as i32,
155                freq,
156                amp,
157                duration_ms,
158                sample_rate,
159                channels,
160            ),
161        )
162        .map_err(|e| format!("Error calling `render_note`: {e}"))?;
163
164        // Copy back
165        let mem_slice_after = memory
166            .data(&store)
167            .get(ptr as usize..(ptr as usize) + (byte_len as usize))
168            .ok_or_else(|| "Failed to get memory slice after".to_string())?;
169        let dst_bytes = unsafe {
170            std::slice::from_raw_parts_mut(buffer.as_mut_ptr() as *mut u8, byte_len as usize)
171        };
172        dst_bytes.copy_from_slice(mem_slice_after);
173
174        Ok(())
175    }
176
177    /// Same as render_note_in_place, but first tries to call exported setters `set_<param>(f32)`
178    /// for each provided param before rendering. Ignored if setter is missing.
179    pub fn render_note_with_params_in_place(
180        &self,
181        wasm_bytes: &[u8],
182        buffer: &mut [f32],
183        synth_name: Option<&str>,
184        freq: f32,
185        amp: f32,
186        duration_ms: i32,
187        sample_rate: i32,
188        channels: i32,
189        params_num: &HashMap<String, f32>,
190        params_str: Option<&HashMap<String, String>>,
191        exported_names: Option<&[String]>,
192    ) -> Result<(), String> {
193        let module = Module::new(&self.engine, wasm_bytes)
194            .map_err(|e| format!("Failed to compile wasm: {e}"))?;
195
196        let mut store = Store::new(&self.engine, ());
197        let linker = Linker::new(&self.engine);
198
199        let instance = linker
200            .instantiate(&mut store, &module)
201            .map_err(|e| format!("Failed to instantiate wasm: {e}"))?;
202
203        let memory = instance
204            .get_memory(&mut store, "memory")
205            .ok_or_else(|| "WASM memory export not found".to_string())?;
206
207        // Call numeric setters if present: set_<param>(f32)
208        let logger = Logger::new();
209        for (k, v) in params_num.iter() {
210            // Candidate patterns in order of preference
211            let candidates = [
212                format!("set_synth_{}", k),
213                format!("set_{}", k),
214                format!("set_note_{}", k),
215            ];
216
217            let mut any_called = false;
218
219            // If exported_names provided, try only declared exports first
220            if let Some(exports) = exported_names {
221                logger.log_message(
222                    LogLevel::Debug,
223                    &format!("Plugin exports provided ({} names)", exports.len()),
224                );
225                for c in &candidates {
226                    if exports.iter().any(|e| e == c) {
227                        match instance.get_typed_func::<f32, ()>(&mut store, c) {
228                            Ok(setter) => {
229                                let _ = setter.call(&mut store, *v);
230                                any_called = true;
231                                logger.log_message(
232                                    LogLevel::Debug,
233                                    &format!("Called setter '{}' with {}", c, v),
234                                );
235                            }
236                            Err(_) => {
237                                logger.log_message(
238                                    LogLevel::Debug,
239                                    &format!("Export '{}' declared but signature lookup failed", c),
240                                );
241                            }
242                        }
243                    }
244                }
245            }
246
247            // If nothing was called using the metadata, fall back to dynamic lookup
248            if !any_called {
249                for fname in &candidates {
250                    match instance.get_typed_func::<f32, ()>(&mut store, fname) {
251                        Ok(setter) => {
252                            let _ = setter.call(&mut store, *v);
253                            any_called = true;
254                            logger.log_message(
255                                LogLevel::Debug,
256                                &format!("Dynamically called setter '{}' with {}", fname, v),
257                            );
258                        }
259                        Err(_) => {
260                            // no-op, continue
261                        }
262                    }
263                }
264            }
265
266            if !any_called {
267                logger.log_message(
268                    LogLevel::Warning,
269                    &format!("No setter found/called for numeric param '{}'", k),
270                );
271            }
272        }
273
274        // Call string setters if present: support set_synth_<param>_str, set_<param>_str, set_note_<param>_str
275        if let Some(smap) = params_str {
276            for (k, v) in smap.iter() {
277                let candidates = [
278                    format!("set_synth_{}_str", k),
279                    format!("set_{}_str", k),
280                    format!("set_note_{}_str", k),
281                ];
282
283                let logger = Logger::new();
284                for (k, v) in smap.iter() {
285                    let candidates = [
286                        format!("set_synth_{}_str", k),
287                        format!("set_{}_str", k),
288                        format!("set_note_{}_str", k),
289                    ];
290
291                    let mut any_called = false;
292
293                    if let Some(exports) = exported_names {
294                        for c in &candidates {
295                            if exports.iter().any(|e| e == c) {
296                                match instance.get_typed_func::<(i32, i32), ()>(&mut store, c) {
297                                    Ok(setter) => {
298                                        let bytes = v.as_bytes();
299                                        let ptr = Self::alloc_temp(
300                                            &mut store,
301                                            &instance,
302                                            &memory,
303                                            bytes.len(),
304                                        )? as i32;
305                                        let mem_slice = memory
306                                            .data_mut(&mut store)
307                                            .get_mut(ptr as usize..(ptr as usize) + bytes.len())
308                                            .ok_or_else(|| {
309                                                "Failed to get memory slice for string".to_string()
310                                            })?;
311                                        mem_slice.copy_from_slice(bytes);
312                                        let _ = setter.call(&mut store, (ptr, bytes.len() as i32));
313                                        any_called = true;
314                                        logger.log_message(
315                                            LogLevel::Debug,
316                                            &format!("Called string setter '{}' with '{}'", c, v),
317                                        );
318                                    }
319                                    Err(_) => {
320                                        logger.log_message(
321                                            LogLevel::Debug,
322                                            &format!(
323                                                "Export '{}' declared but signature lookup failed",
324                                                c
325                                            ),
326                                        );
327                                    }
328                                }
329                            }
330                        }
331                    }
332
333                    if !any_called {
334                        for fname in &candidates {
335                            if let Ok(setter) =
336                                instance.get_typed_func::<(i32, i32), ()>(&mut store, fname)
337                            {
338                                let bytes = v.as_bytes();
339                                let ptr =
340                                    Self::alloc_temp(&mut store, &instance, &memory, bytes.len())?
341                                        as i32;
342                                let mem_slice = memory
343                                    .data_mut(&mut store)
344                                    .get_mut(ptr as usize..(ptr as usize) + bytes.len())
345                                    .ok_or_else(|| {
346                                        "Failed to get memory slice for string".to_string()
347                                    })?;
348                                mem_slice.copy_from_slice(bytes);
349                                let _ = setter.call(&mut store, (ptr, bytes.len() as i32));
350                                any_called = true;
351                                logger.log_message(
352                                    LogLevel::Debug,
353                                    &format!(
354                                        "Dynamically called string setter '{}' with '{}'",
355                                        fname, v
356                                    ),
357                                );
358                            }
359                        }
360                    }
361
362                    if !any_called {
363                        logger.log_message(
364                            LogLevel::Warning,
365                            &format!("No string setter found/called for param '{}'", k),
366                        );
367                    }
368                }
369            }
370        }
371
372        // Try specific + generic entry points in order of preference.
373        // 1) render_note_<name>
374        // 2) render_note
375        // 3) synth_<name>
376        // 4) synth
377        let mut func_opt: Option<RenderFunc> = None;
378        if let Some(name) = synth_name {
379            let specific = format!("render_note_{}", name);
380            if let Ok(f) = instance
381                .get_typed_func::<(i32, i32, f32, f32, i32, i32, i32), ()>(&mut store, &specific)
382            {
383                func_opt = Some(f);
384            }
385        }
386        if func_opt.is_none() {
387            if let Ok(f) = instance.get_typed_func::<(i32, i32, f32, f32, i32, i32, i32), ()>(
388                &mut store,
389                "render_note",
390            ) {
391                func_opt = Some(f);
392            }
393        }
394        if func_opt.is_none() {
395            if let Some(name) = synth_name {
396                let specific = format!("synth_{}", name);
397                if let Ok(f) = instance.get_typed_func::<(i32, i32, f32, f32, i32, i32, i32), ()>(
398                    &mut store, &specific,
399                ) {
400                    func_opt = Some(f);
401                }
402            }
403        }
404        if func_opt.is_none() {
405            if let Ok(f) = instance
406                .get_typed_func::<(i32, i32, f32, f32, i32, i32, i32), ()>(&mut store, "synth")
407            {
408                func_opt = Some(f);
409            }
410        }
411        let func = func_opt.ok_or_else(|| {
412            "Exported function not found: tried render_note[_<name>] and synth[_<name>]".to_string()
413        })?;
414
415        // Copy host buffer into wasm memory
416        let byte_len = std::mem::size_of_val(buffer) as i32;
417        let ptr = Self::alloc_temp(&mut store, &instance, &memory, byte_len as usize)? as i32;
418        let mem_slice = memory
419            .data_mut(&mut store)
420            .get_mut(ptr as usize..(ptr as usize) + (byte_len as usize))
421            .ok_or_else(|| "Failed to get memory slice".to_string())?;
422        let src_bytes =
423            unsafe { std::slice::from_raw_parts(buffer.as_ptr() as *const u8, byte_len as usize) };
424        mem_slice.copy_from_slice(src_bytes);
425
426        // Call render
427        func.call(
428            &mut store,
429            (
430                ptr,
431                buffer.len() as i32,
432                freq,
433                amp,
434                duration_ms,
435                sample_rate,
436                channels,
437            ),
438        )
439        .map_err(|e| format!("Error calling `render_note`: {e}"))?;
440
441        // Copy back
442        let mem_slice_after = memory
443            .data(&store)
444            .get(ptr as usize..(ptr as usize) + (byte_len as usize))
445            .ok_or_else(|| "Failed to get memory slice after".to_string())?;
446        let dst_bytes = unsafe {
447            std::slice::from_raw_parts_mut(buffer.as_mut_ptr() as *mut u8, byte_len as usize)
448        };
449        dst_bytes.copy_from_slice(mem_slice_after);
450
451        Ok(())
452    }
453
454    fn alloc_temp(
455        store: &mut Store<()>,
456        instance: &Instance,
457        memory: &wasmtime::Memory,
458        size: usize,
459    ) -> Result<usize, String> {
460        // Try to use an exported `__wbindgen_malloc` if present; otherwise, grow memory manually.
461        if let Ok(malloc) = instance.get_typed_func::<i32, i32>(&mut *store, "__wbindgen_malloc") {
462            let ptr = malloc
463                .call(&mut *store, size as i32)
464                .map_err(|e| format!("malloc failed: {e}"))? as usize;
465            return Ok(ptr);
466        }
467
468        // Fallback: grow memory and use end of memory as scratch space
469        let current_len = memory.data_size(&mut *store);
470        let need = size;
471        let pages_needed = (current_len + need).div_ceil(0x10000) as u64; // 64KiB pages
472        let current_pages = memory.size(&mut *store);
473        if pages_needed > current_pages {
474            let to_grow = pages_needed - current_pages;
475            memory
476                .grow(&mut *store, to_grow)
477                .map_err(|e| format!("memory.grow failed: {e}"))?;
478        }
479        Ok(current_len)
480    }
481}