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
118 .get_mut(&hash)
119 .ok_or_else(|| "WASM instance cache lookup failed after insertion".to_string())?;
120
121 let memory = entry
122 .1
123 .get_memory(&mut entry.0, "memory")
124 .ok_or_else(|| "WASM memory export not found".to_string())?;
125
126 let func_name = if let Some(name) = synth_name {
128 if entry.1.get_func(&mut entry.0, name).is_some() {
130 name
131 } else if entry.1.get_func(&mut entry.0, "render_note").is_some() {
132 "render_note"
133 } else {
134 return Err(format!("Plugin export '{}' not found", name));
135 }
136 } else {
137 "render_note"
138 };
139
140 let func = entry
141 .1
142 .get_typed_func::<(i32, i32, f32, f32, i32, i32, i32), ()>(&mut entry.0, func_name)
143 .map_err(|e| {
144 format!(
145 "Function '{}' not found or wrong signature: {}",
146 func_name, e
147 )
148 })?;
149
150 if let Ok(reset_func) = entry.1.get_typed_func::<f32, ()>(&mut entry.0, "setReset") {
152 let _ = reset_func.call(&mut entry.0, 1.0);
153 }
154
155 let setter_map: HashMap<&str, &str> = [
157 ("waveform", "setWaveform"),
158 ("cutoff", "setCutoff"),
159 ("resonance", "setResonance"),
160 ("env_mod", "setEnvMod"),
161 ("decay", "setDecay"),
162 ("accent", "setAccent"),
163 ("drive", "setDrive"),
164 ("tone", "setTone"),
165 ("slide", "setSlide"),
166 ("glide", "setGlide"),
167 ]
168 .iter()
169 .cloned()
170 .collect();
171
172 let synth_id_str = instance_key.unwrap_or("default").to_string();
174
175 let params_to_apply = {
176 let mut last_params = self.last_params.borrow_mut();
177 let current_params = last_params
178 .entry(synth_id_str.clone())
179 .or_insert_with(HashMap::new);
180
181 if let Some(opts) = options {
183 for (key, value) in opts.iter() {
184 current_params.insert(key.clone(), *value);
185 }
186 opts.clone()
187 } else {
188 current_params.clone()
189 }
190 };
191
192 for (key, value) in params_to_apply.iter() {
194 if let Some(setter_name) = setter_map.get(key.as_str()) {
195 if let Ok(setter) = entry.1.get_typed_func::<f32, ()>(&mut entry.0, setter_name) {
196 let _ = setter.call(&mut entry.0, *value);
197 }
198 }
199 }
200
201 let byte_len = std::mem::size_of_val(buffer);
203 let ptr = Self::alloc_temp(&mut entry.0, &entry.1, &memory, byte_len)? as i32;
204
205 let mem_slice = memory
207 .data_mut(&mut entry.0)
208 .get_mut(ptr as usize..(ptr as usize) + byte_len)
209 .ok_or_else(|| "Failed to get memory slice".to_string())?;
210 let src_bytes =
211 unsafe { std::slice::from_raw_parts(buffer.as_ptr() as *const u8, byte_len) };
212 mem_slice.copy_from_slice(src_bytes);
213
214 func.call(
216 &mut entry.0,
217 (
218 ptr,
219 buffer.len() as i32,
220 freq,
221 amp,
222 duration_ms,
223 sample_rate,
224 channels,
225 ),
226 )
227 .map_err(|e| format!("Error calling '{}': {}", func_name, e))?;
228
229 let mem_slice_after = memory
231 .data(&entry.0)
232 .get(ptr as usize..(ptr as usize) + byte_len)
233 .ok_or_else(|| "Failed to get memory slice after".to_string())?;
234 let dst_bytes =
235 unsafe { std::slice::from_raw_parts_mut(buffer.as_mut_ptr() as *mut u8, byte_len) };
236 dst_bytes.copy_from_slice(mem_slice_after);
237
238 let _non_zero = buffer.iter().any(|&x| x.abs() > 0.0001);
240 let _max_val = buffer.iter().map(|&x| x.abs()).fold(0.0f32, f32::max);
241 Ok(())
244 }
245
246 fn alloc_temp(
248 store: &mut Store<()>,
249 _instance: &Instance,
250 memory: &wasmtime::Memory,
251 size: usize,
252 ) -> Result<i32, String> {
253 let pages = memory.size(&*store);
255 let current_size = pages * 65536; let ptr = current_size as i32;
257
258 let needed_pages = ((size + 65535) / 65536) as u64;
260 if needed_pages > 0 {
261 memory
262 .grow(store, needed_pages)
263 .map_err(|e| format!("Failed to grow memory: {}", e))?;
264 }
265
266 Ok(ptr)
267 }
268}