devalang_wasm/engine/plugin/
runner.rs1use 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: RefCell<HashMap<u64, (Store<()>, Instance)>>,
11 last_params: RefCell<HashMap<String, HashMap<String, f32>>>,
13}
14
15#[cfg(feature = "cli")]
16impl Default for WasmPluginRunner {
17 fn default() -> Self {
18 Self::new()
19 }
20}
21
22#[cfg(feature = "cli")]
23impl WasmPluginRunner {
24 pub fn new() -> Self {
25 let engine = Engine::default();
26 Self {
27 engine,
28 cache: RefCell::new(HashMap::new()),
29 last_params: RefCell::new(HashMap::new()),
30 }
31 }
32
33 pub fn render_note_in_place(
39 &self,
40 wasm_bytes: &[u8],
41 buffer: &mut [f32],
42 instance_key: Option<&str>, synth_name: Option<&str>, freq: f32,
45 amp: f32,
46 duration_ms: i32,
47 sample_rate: i32,
48 channels: i32,
49 options: Option<&HashMap<String, f32>>,
50 ) -> Result<(), String> {
51 use std::collections::hash_map::DefaultHasher;
53 use std::hash::{Hash, Hasher};
54 let mut hasher = DefaultHasher::new();
55 wasm_bytes.hash(&mut hasher);
56 instance_key.hash(&mut hasher); let hash = hasher.finish();
58
59 let mut cache = self.cache.borrow_mut();
61
62 if !cache.contains_key(&hash) {
64 let module = Module::new(&self.engine, wasm_bytes)
66 .map_err(|e| format!("Failed to compile wasm: {e}"))?;
67
68 let mut store = Store::new(&self.engine, ());
69 let mut linker = Linker::new(&self.engine);
70
71 linker
73 .func_wrap(
74 "__wbindgen_placeholder__",
75 "__wbindgen_describe",
76 |_: i32| {},
77 )
78 .map_err(|e| format!("Failed to define wbindgen import: {e}"))?;
79 linker
80 .func_wrap(
81 "__wbindgen_placeholder__",
82 "__wbindgen_object_clone_ref",
83 |_: i32| -> i32 { 0 },
84 )
85 .map_err(|e| format!("Failed to define wbindgen import: {e}"))?;
86 linker
87 .func_wrap(
88 "__wbindgen_placeholder__",
89 "__wbindgen_object_drop_ref",
90 |_: i32| {},
91 )
92 .map_err(|e| format!("Failed to define wbindgen import: {e}"))?;
93 linker
94 .func_wrap(
95 "__wbindgen_placeholder__",
96 "__wbindgen_string_new",
97 |_: i32, _: i32| -> i32 { 0 },
98 )
99 .map_err(|e| format!("Failed to define wbindgen import: {e}"))?;
100 linker
101 .func_wrap(
102 "__wbindgen_placeholder__",
103 "__wbindgen_throw",
104 |_: i32, _: i32| {},
105 )
106 .map_err(|e| format!("Failed to define wbindgen import: {e}"))?;
107
108 let instance = linker
109 .instantiate(&mut store, &module)
110 .map_err(|e| format!("Failed to instantiate wasm: {e}"))?;
111
112 cache.insert(hash, (store, instance));
113 } else {
114 }
116
117 let entry = cache.get_mut(&hash).unwrap();
119
120 let memory = entry
121 .1
122 .get_memory(&mut entry.0, "memory")
123 .ok_or_else(|| "WASM memory export not found".to_string())?;
124
125 let func_name = if let Some(name) = synth_name {
127 if entry.1.get_func(&mut entry.0, name).is_some() {
129 name
130 } else if entry.1.get_func(&mut entry.0, "render_note").is_some() {
131 "render_note"
132 } else {
133 return Err(format!("Plugin export '{}' not found", name));
134 }
135 } else {
136 "render_note"
137 };
138
139 let func = entry
140 .1
141 .get_typed_func::<(i32, i32, f32, f32, i32, i32, i32), ()>(&mut entry.0, func_name)
142 .map_err(|e| {
143 format!(
144 "Function '{}' not found or wrong signature: {}",
145 func_name, e
146 )
147 })?;
148
149 if let Ok(reset_func) = entry.1.get_typed_func::<f32, ()>(&mut entry.0, "setReset") {
151 let _ = reset_func.call(&mut entry.0, 1.0);
152 }
153
154 let setter_map: HashMap<&str, &str> = [
156 ("waveform", "setWaveform"),
157 ("cutoff", "setCutoff"),
158 ("resonance", "setResonance"),
159 ("env_mod", "setEnvMod"),
160 ("decay", "setDecay"),
161 ("accent", "setAccent"),
162 ("drive", "setDrive"),
163 ("tone", "setTone"),
164 ("slide", "setSlide"),
165 ("glide", "setGlide"),
166 ]
167 .iter()
168 .cloned()
169 .collect();
170
171 let synth_id_str = instance_key.unwrap_or("default").to_string();
173
174 let params_to_apply = {
175 let mut last_params = self.last_params.borrow_mut();
176 let current_params = last_params
177 .entry(synth_id_str.clone())
178 .or_insert_with(HashMap::new);
179
180 if let Some(opts) = options {
182 for (key, value) in opts.iter() {
183 current_params.insert(key.clone(), *value);
184 }
185 opts.clone()
186 } else {
187 current_params.clone()
188 }
189 };
190
191 for (key, value) in params_to_apply.iter() {
193 if let Some(setter_name) = setter_map.get(key.as_str()) {
194 if let Ok(setter) = entry.1.get_typed_func::<f32, ()>(&mut entry.0, setter_name) {
195 let _ = setter.call(&mut entry.0, *value);
196 }
197 }
198 }
199
200 let byte_len = std::mem::size_of_val(buffer);
202 let ptr = Self::alloc_temp(&mut entry.0, &entry.1, &memory, byte_len)? as i32;
203
204 let mem_slice = memory
206 .data_mut(&mut entry.0)
207 .get_mut(ptr as usize..(ptr as usize) + byte_len)
208 .ok_or_else(|| "Failed to get memory slice".to_string())?;
209 let src_bytes =
210 unsafe { std::slice::from_raw_parts(buffer.as_ptr() as *const u8, byte_len) };
211 mem_slice.copy_from_slice(src_bytes);
212
213 func.call(
215 &mut entry.0,
216 (
217 ptr,
218 buffer.len() as i32,
219 freq,
220 amp,
221 duration_ms,
222 sample_rate,
223 channels,
224 ),
225 )
226 .map_err(|e| format!("Error calling '{}': {}", func_name, e))?;
227
228 let mem_slice_after = memory
230 .data(&entry.0)
231 .get(ptr as usize..(ptr as usize) + byte_len)
232 .ok_or_else(|| "Failed to get memory slice after".to_string())?;
233 let dst_bytes =
234 unsafe { std::slice::from_raw_parts_mut(buffer.as_mut_ptr() as *mut u8, byte_len) };
235 dst_bytes.copy_from_slice(mem_slice_after);
236
237 let _non_zero = buffer.iter().any(|&x| x.abs() > 0.0001);
239 let _max_val = buffer.iter().map(|&x| x.abs()).fold(0.0f32, f32::max);
240 Ok(())
243 }
244
245 fn alloc_temp(
247 store: &mut Store<()>,
248 _instance: &Instance,
249 memory: &wasmtime::Memory,
250 size: usize,
251 ) -> Result<i32, String> {
252 let pages = memory.size(&*store);
254 let current_size = pages * 65536; let ptr = current_size as i32;
256
257 let needed_pages = ((size + 65535) / 65536) as u64;
259 if needed_pages > 0 {
260 memory
261 .grow(store, needed_pages)
262 .map_err(|e| format!("Failed to grow memory: {}", e))?;
263 }
264
265 Ok(ptr)
266 }
267}