devalang_wasm/engine/plugin/
runner.rs

1use std::cell::RefCell;
2use std::collections::HashMap;
3#[cfg(feature = "cli")]
4use wasmtime::{Engine, Instance, Linker, Module, Store};
5
6#[cfg(feature = "cli")]
7pub struct WasmPluginRunner {
8    engine: Engine,
9    // Cache instances by WASM hash to reuse state
10    cache: RefCell<HashMap<u64, (Store<()>, Instance)>>,
11}
12
13#[cfg(feature = "cli")]
14impl Default for WasmPluginRunner {
15    fn default() -> Self {
16        Self::new()
17    }
18}
19
20#[cfg(feature = "cli")]
21impl WasmPluginRunner {
22    pub fn new() -> Self {
23        let engine = Engine::default();
24        Self {
25            engine,
26            cache: RefCell::new(HashMap::new()),
27        }
28    }
29
30    /// Renders a note using a WASM plugin with optional parameter overrides
31    ///
32    /// Tries multiple function name patterns in order:
33    /// 1. Named export (if synth_name provided): e.g. "synth", "saw"
34    /// 2. Generic "render_note"
35    pub fn render_note_in_place(
36        &self,
37        wasm_bytes: &[u8],
38        buffer: &mut [f32],
39        instance_key: Option<&str>, // ← NEW: For cache hash ("acidScreamer" / "acidSquare")
40        synth_name: Option<&str>,   // ← Function to call ("synth")
41        freq: f32,
42        amp: f32,
43        duration_ms: i32,
44        sample_rate: i32,
45        channels: i32,
46        options: Option<&HashMap<String, f32>>,
47    ) -> Result<(), String> {
48        // Hash du WASM + instance_key pour avoir une instance par synth!
49        use std::collections::hash_map::DefaultHasher;
50        use std::hash::{Hash, Hasher};
51        let mut hasher = DefaultHasher::new();
52        wasm_bytes.hash(&mut hasher);
53        instance_key.hash(&mut hasher); // Use instance_key (synth_id) for caching!
54        let hash = hasher.finish();
55
56        // Mutably borrow the cache
57        let mut cache = self.cache.borrow_mut();
58
59        // Create instance if it doesn't exist
60        if !cache.contains_key(&hash) {
61            // Creating new instance for synth_id
62            let module = Module::new(&self.engine, wasm_bytes)
63                .map_err(|e| format!("Failed to compile wasm: {e}"))?;
64
65            let mut store = Store::new(&self.engine, ());
66            let mut linker = Linker::new(&self.engine);
67
68            // Add wasm-bindgen placeholder imports (for plugins compiled with wasm-bindgen)
69            linker
70                .func_wrap(
71                    "__wbindgen_placeholder__",
72                    "__wbindgen_describe",
73                    |_: i32| {},
74                )
75                .map_err(|e| format!("Failed to define wbindgen import: {e}"))?;
76            linker
77                .func_wrap(
78                    "__wbindgen_placeholder__",
79                    "__wbindgen_object_clone_ref",
80                    |_: i32| -> i32 { 0 },
81                )
82                .map_err(|e| format!("Failed to define wbindgen import: {e}"))?;
83            linker
84                .func_wrap(
85                    "__wbindgen_placeholder__",
86                    "__wbindgen_object_drop_ref",
87                    |_: i32| {},
88                )
89                .map_err(|e| format!("Failed to define wbindgen import: {e}"))?;
90            linker
91                .func_wrap(
92                    "__wbindgen_placeholder__",
93                    "__wbindgen_string_new",
94                    |_: i32, _: i32| -> i32 { 0 },
95                )
96                .map_err(|e| format!("Failed to define wbindgen import: {e}"))?;
97            linker
98                .func_wrap(
99                    "__wbindgen_placeholder__",
100                    "__wbindgen_throw",
101                    |_: i32, _: i32| {},
102                )
103                .map_err(|e| format!("Failed to define wbindgen import: {e}"))?;
104
105            let instance = linker
106                .instantiate(&mut store, &module)
107                .map_err(|e| format!("Failed to instantiate wasm: {e}"))?;
108
109            cache.insert(hash, (store, instance));
110        } else {
111            // Reusing cached instance for synth_id
112        }
113
114        // Retrieve cached instance
115        let entry = cache.get_mut(&hash).unwrap();
116
117        let memory = entry
118            .1
119            .get_memory(&mut entry.0, "memory")
120            .ok_or_else(|| "WASM memory export not found".to_string())?;
121
122        // Find the right function to call
123        let func_name = if let Some(name) = synth_name {
124            // Try the named export first
125            if entry.1.get_func(&mut entry.0, name).is_some() {
126                name
127            } else if entry.1.get_func(&mut entry.0, "render_note").is_some() {
128                "render_note"
129            } else {
130                return Err(format!("Plugin export '{}' not found", name));
131            }
132        } else {
133            "render_note"
134        };
135
136        let func = entry
137            .1
138            .get_typed_func::<(i32, i32, f32, f32, i32, i32, i32), ()>(&mut entry.0, func_name)
139            .map_err(|e| {
140                format!(
141                    "Function '{}' not found or wrong signature: {}",
142                    func_name, e
143                )
144            })?;
145
146        // Apply plugin options by calling setter functions if available
147        if let Some(opts) = options {
148            // Applying plugin options
149
150            // Map of parameter names to setter functions
151            let setter_map: HashMap<&str, &str> = [
152                ("waveform", "setWaveform"),
153                ("cutoff", "setCutoff"),
154                ("resonance", "setResonance"),
155                ("env_mod", "setEnvMod"),
156                ("decay", "setDecay"),
157                ("accent", "setAccent"),
158                ("drive", "setDrive"),
159                ("tone", "setTone"),
160                ("slide", "setSlide"),
161                ("glide", "setGlide"),
162            ]
163            .iter()
164            .cloned()
165            .collect();
166
167            for (key, value) in opts.iter() {
168                // Try to find matching setter
169                if let Some(setter_name) = setter_map.get(key.as_str()) {
170                    // Try to get the setter function
171                    if let Ok(setter) = entry.1.get_typed_func::<f32, ()>(&mut entry.0, setter_name)
172                    {
173                        // Call setter with the parameter value
174                        // Setting option
175                        let _ = setter.call(&mut entry.0, *value);
176                    } else {
177                        // Setter not found
178                    }
179                } else {
180                    // Unknown parameter
181                }
182            }
183        } else {
184            // No plugin options provided
185        }
186
187        // Allocate memory in WASM for the buffer
188        let byte_len = std::mem::size_of_val(buffer);
189        let ptr = Self::alloc_temp(&mut entry.0, &entry.1, &memory, byte_len)? as i32;
190
191        // Copy buffer into WASM memory
192        let mem_slice = memory
193            .data_mut(&mut entry.0)
194            .get_mut(ptr as usize..(ptr as usize) + byte_len)
195            .ok_or_else(|| "Failed to get memory slice".to_string())?;
196        let src_bytes =
197            unsafe { std::slice::from_raw_parts(buffer.as_ptr() as *const u8, byte_len) };
198        mem_slice.copy_from_slice(src_bytes);
199
200        // Call the plugin function
201        func.call(
202            &mut entry.0,
203            (
204                ptr,
205                buffer.len() as i32,
206                freq,
207                amp,
208                duration_ms,
209                sample_rate,
210                channels,
211            ),
212        )
213        .map_err(|e| format!("Error calling '{}': {}", func_name, e))?;
214
215        // Copy result back from WASM memory
216        let mem_slice_after = memory
217            .data(&entry.0)
218            .get(ptr as usize..(ptr as usize) + byte_len)
219            .ok_or_else(|| "Failed to get memory slice after".to_string())?;
220        let dst_bytes =
221            unsafe { std::slice::from_raw_parts_mut(buffer.as_mut_ptr() as *mut u8, byte_len) };
222        dst_bytes.copy_from_slice(mem_slice_after);
223
224        // Debug: Check if buffer has any non-zero values
225        let _non_zero = buffer.iter().any(|&x| x.abs() > 0.0001);
226        let _max_val = buffer.iter().map(|&x| x.abs()).fold(0.0f32, f32::max);
227        // Plugin result diagnostics
228
229        Ok(())
230    }
231
232    /// Helper to allocate temporary memory in WASM module
233    fn alloc_temp(
234        store: &mut Store<()>,
235        _instance: &Instance,
236        memory: &wasmtime::Memory,
237        size: usize,
238    ) -> Result<i32, String> {
239        // Simple bump allocator: just use end of current memory
240        let pages = memory.size(&*store);
241        let current_size = pages * 65536; // WASM page size
242        let ptr = current_size as i32;
243
244        // Grow memory if needed
245        let needed_pages = ((size + 65535) / 65536) as u64;
246        if needed_pages > 0 {
247            memory
248                .grow(store, needed_pages)
249                .map_err(|e| format!("Failed to grow memory: {}", e))?;
250        }
251
252        Ok(ptr)
253    }
254}