devalang_wasm/engine/plugin/
runner.rs

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