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            // These are stub implementations that do nothing but allow the WASM to load
70            linker
71                .func_wrap(
72                    "__wbindgen_placeholder__",
73                    "__wbindgen_describe",
74                    |_: i32| {},
75                )
76                .map_err(|e| format!("Failed to define wbindgen import: {e}"))?;
77            linker
78                .func_wrap(
79                    "__wbindgen_placeholder__",
80                    "__wbindgen_object_clone_ref",
81                    |_: i32| -> i32 { 0 },
82                )
83                .map_err(|e| format!("Failed to define wbindgen import: {e}"))?;
84            linker
85                .func_wrap(
86                    "__wbindgen_placeholder__",
87                    "__wbindgen_object_drop_ref",
88                    |_: i32| {},
89                )
90                .map_err(|e| format!("Failed to define wbindgen import: {e}"))?;
91            linker
92                .func_wrap(
93                    "__wbindgen_placeholder__",
94                    "__wbindgen_string_new",
95                    |_: i32, _: i32| -> i32 { 0 },
96                )
97                .map_err(|e| format!("Failed to define wbindgen import: {e}"))?;
98            linker
99                .func_wrap(
100                    "__wbindgen_placeholder__",
101                    "__wbindgen_throw",
102                    |_: i32, _: i32| {},
103                )
104                .map_err(|e| format!("Failed to define wbindgen import: {e}"))?;
105
106            let instance = linker
107                .instantiate(&mut store, &module)
108                .map_err(|e| format!("Failed to instantiate wasm: {e}"))?;
109
110            cache.insert(hash, (store, instance));
111        } else {
112            // Reusing cached instance for synth_id
113        }
114
115        // Retrieve cached instance
116        let entry = cache.get_mut(&hash).unwrap();
117
118        let memory = entry
119            .1
120            .get_memory(&mut entry.0, "memory")
121            .ok_or_else(|| "WASM memory export not found".to_string())?;
122
123        // Find the right function to call
124        let func_name = if let Some(name) = synth_name {
125            // Try the named export first
126            if entry.1.get_func(&mut entry.0, name).is_some() {
127                name
128            } else if entry.1.get_func(&mut entry.0, "render_note").is_some() {
129                "render_note"
130            } else {
131                return Err(format!("Plugin export '{}' not found", name));
132            }
133        } else {
134            "render_note"
135        };
136
137        let func = entry
138            .1
139            .get_typed_func::<(i32, i32, f32, f32, i32, i32, i32), ()>(&mut entry.0, func_name)
140            .map_err(|e| {
141                format!(
142                    "Function '{}' not found or wrong signature: {}",
143                    func_name, e
144                )
145            })?;
146
147        // Apply plugin options by calling setter functions if available
148        if let Some(opts) = options {
149            // Applying plugin options
150
151            // Map of parameter names to setter functions
152            let setter_map: HashMap<&str, &str> = [
153                ("waveform", "setWaveform"),
154                ("cutoff", "setCutoff"),
155                ("resonance", "setResonance"),
156                ("env_mod", "setEnvMod"),
157                ("decay", "setDecay"),
158                ("accent", "setAccent"),
159                ("drive", "setDrive"),
160                ("tone", "setTone"),
161                ("slide", "setSlide"),
162                ("glide", "setGlide"),
163            ]
164            .iter()
165            .cloned()
166            .collect();
167
168            for (key, value) in opts.iter() {
169                // Try to find matching setter
170                if let Some(setter_name) = setter_map.get(key.as_str()) {
171                    // Try to get the setter function
172                    if let Ok(setter) = entry.1.get_typed_func::<f32, ()>(&mut entry.0, setter_name)
173                    {
174                        // Call setter with the parameter value
175                        // Setting option
176                        let _ = setter.call(&mut entry.0, *value);
177                    } else {
178                        // Setter not found
179                    }
180                } else {
181                    // Unknown parameter
182                }
183            }
184        } else {
185            // No plugin options provided
186        }
187
188        // Allocate memory in WASM for the buffer
189        let byte_len = std::mem::size_of_val(buffer);
190        let ptr = Self::alloc_temp(&mut entry.0, &entry.1, &memory, byte_len)? as i32;
191
192        // Calling plugin with parameters (log removed)
193
194        // Copy buffer into WASM memory
195        let mem_slice = memory
196            .data_mut(&mut entry.0)
197            .get_mut(ptr as usize..(ptr as usize) + byte_len)
198            .ok_or_else(|| "Failed to get memory slice".to_string())?;
199        let src_bytes =
200            unsafe { std::slice::from_raw_parts(buffer.as_ptr() as *const u8, byte_len) };
201        mem_slice.copy_from_slice(src_bytes);
202
203        // Call the plugin function
204        func.call(
205            &mut entry.0,
206            (
207                ptr,
208                buffer.len() as i32,
209                freq,
210                amp,
211                duration_ms,
212                sample_rate,
213                channels,
214            ),
215        )
216        .map_err(|e| format!("Error calling '{}': {}", func_name, e))?;
217
218        // Copy result back from WASM memory
219        let mem_slice_after = memory
220            .data(&entry.0)
221            .get(ptr as usize..(ptr as usize) + byte_len)
222            .ok_or_else(|| "Failed to get memory slice after".to_string())?;
223        let dst_bytes =
224            unsafe { std::slice::from_raw_parts_mut(buffer.as_mut_ptr() as *mut u8, byte_len) };
225        dst_bytes.copy_from_slice(mem_slice_after);
226
227        // Debug: Check if buffer has any non-zero values
228        let _non_zero = buffer.iter().any(|&x| x.abs() > 0.0001);
229        let _max_val = buffer.iter().map(|&x| x.abs()).fold(0.0f32, f32::max);
230        // Plugin result diagnostics
231
232        Ok(())
233    }
234
235    /// Helper to allocate temporary memory in WASM module
236    fn alloc_temp(
237        store: &mut Store<()>,
238        _instance: &Instance,
239        memory: &wasmtime::Memory,
240        size: usize,
241    ) -> Result<i32, String> {
242        // Simple bump allocator: just use end of current memory
243        let pages = memory.size(&*store);
244        let current_size = pages * 65536; // WASM page size
245        let ptr = current_size as i32;
246
247        // Grow memory if needed
248        let needed_pages = ((size + 65535) / 65536) as u64;
249        if needed_pages > 0 {
250            memory
251                .grow(store, needed_pages)
252                .map_err(|e| format!("Failed to grow memory: {}", e))?;
253        }
254
255        Ok(ptr)
256    }
257}