1#![cfg(not(target_os = "macos"))]
2
3use std::{
4 collections::{HashMap, HashSet},
5 ffi::{CStr, CString, c_char, c_void},
6 fmt,
7 path::{Path, PathBuf},
8 sync::atomic::{AtomicBool, Ordering},
9 sync::{Arc, Mutex},
10};
11
12use crate::audio::io::AudioIO;
13use crate::message::{Lv2ControlPortInfo, Lv2PluginState, Lv2StatePortValue, Lv2StateProperty};
14use crate::midi::io::MidiEvent;
15use crate::mutex::UnsafeMutex;
16use lilv::{World, instance::ActiveInstance, node::Node, plugin::Plugin};
17use lv2_raw::{
18 LV2_ATOM__DOUBLE, LV2_ATOM__FRAMETIME, LV2_ATOM__INT, LV2_ATOM__LONG, LV2_ATOM__OBJECT,
19 LV2_ATOM__SEQUENCE, LV2_URID__MAP, LV2_URID__UNMAP, LV2Atom, LV2AtomDouble, LV2AtomEvent,
20 LV2AtomLong, LV2AtomObjectBody, LV2AtomPropertyBody, LV2AtomSequence, LV2AtomSequenceBody,
21 LV2Feature, LV2Urid, LV2UridMap, LV2UridMapHandle, lv2_atom_pad_size,
22 lv2_atom_sequence_append_event, lv2_atom_sequence_begin, lv2_atom_sequence_is_end,
23 lv2_atom_sequence_next,
24};
25
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct Lv2PluginInfo {
28 pub uri: String,
29 pub name: String,
30 pub class_label: String,
31 pub bundle_uri: String,
32 pub required_features: Vec<String>,
33 pub audio_inputs: usize,
34 pub audio_outputs: usize,
35 pub midi_inputs: usize,
36 pub midi_outputs: usize,
37}
38
39pub struct Lv2Host {
40 world: World,
41 sample_rate: f64,
42 loaded_plugins: HashMap<String, LoadedPlugin>,
43}
44
45#[derive(Debug, Clone, Copy, Default)]
46pub struct Lv2TransportInfo {
47 pub transport_sample: usize,
48 pub playing: bool,
49 pub bpm: f64,
50 pub tsig_num: u32,
51 pub tsig_denom: u32,
52}
53
54type Lv2WorkerStatus = u32;
55const LV2_WORKER_SUCCESS: Lv2WorkerStatus = 0;
56const LV2_WORKER_ERR_UNKNOWN: Lv2WorkerStatus = 1;
57const LV2_WORKER__SCHEDULE: &str = "http://lv2plug.in/ns/ext/worker#schedule";
58const LV2_WORKER__INTERFACE: &str = "http://lv2plug.in/ns/ext/worker#interface";
59
60const LV2_MIDNAM__INTERFACE: &str = "http://ardour.org/lv2/midnam#interface";
62
63#[repr(C)]
64struct Lv2MidnamInterface {
65 midnam: Option<unsafe extern "C" fn(instance: Lv2Handle) -> *mut c_char>,
66 model: Option<unsafe extern "C" fn(instance: Lv2Handle) -> *mut c_char>,
67 free: Option<unsafe extern "C" fn(ptr: *mut c_char)>,
68}
69
70#[repr(C)]
71struct Lv2WorkerSchedule {
72 handle: *mut c_void,
73 schedule_work:
74 Option<unsafe extern "C" fn(handle: *mut c_void, size: u32, data: *const c_void) -> u32>,
75}
76
77type Lv2WorkerRespondFunc =
78 Option<unsafe extern "C" fn(handle: *mut c_void, size: u32, data: *const c_void) -> u32>;
79
80#[repr(C)]
81struct Lv2WorkerInterface {
82 work: Option<
83 unsafe extern "C" fn(
84 handle: *mut c_void,
85 respond: Lv2WorkerRespondFunc,
86 respond_handle: *mut c_void,
87 size: u32,
88 data: *const c_void,
89 ) -> u32,
90 >,
91 work_response:
92 Option<unsafe extern "C" fn(handle: *mut c_void, size: u32, data: *const c_void) -> u32>,
93 end_run: Option<unsafe extern "C" fn(handle: *mut c_void)>,
94}
95
96struct WorkerScheduleState {
97 jobs: UnsafeMutex<Vec<Vec<u8>>>,
98 responses: UnsafeMutex<Vec<Vec<u8>>>,
99}
100
101struct WorkerFeature {
102 _uri: CString,
103 _schedule: Box<Lv2WorkerSchedule>,
104 feature: LV2Feature,
105 state: Box<WorkerScheduleState>,
106}
107
108unsafe impl Send for WorkerFeature {}
109
110#[derive(Clone, Copy)]
111enum PortBinding {
112 AudioInput(usize),
113 AudioOutput(usize),
114 AtomInput(usize),
115 AtomOutput(usize),
116 Scalar(usize),
117}
118
119pub struct Lv2Processor {
120 uri: String,
121 plugin_name: String,
122 sample_rate: f64,
123 instance: Option<ActiveInstance>,
124 _world: World,
125 _urid_feature: UridMapFeature,
126 _state_path_feature: StatePathFeature,
127 _instantiate_features: InstantiateFeatureSet,
128 port_bindings: Vec<PortBinding>,
129 scalar_values: Arc<Mutex<Vec<f32>>>,
130 audio_inputs: Vec<Arc<AudioIO>>,
131 audio_outputs: Vec<Arc<AudioIO>>,
132 main_audio_inputs: usize,
133 main_audio_outputs: usize,
134 atom_inputs: Vec<AtomBuffer>,
135 atom_outputs: Vec<AtomBuffer>,
136 atom_sequence_urid: LV2Urid,
137 atom_object_urid: LV2Urid,
138 atom_long_urid: LV2Urid,
139 atom_double_urid: LV2Urid,
140 atom_frame_time_urid: LV2Urid,
141 midi_event_urid: LV2Urid,
142 time_position_urid: LV2Urid,
143 time_frame_urid: LV2Urid,
144 time_speed_urid: LV2Urid,
145 time_bpm_urid: LV2Urid,
146 time_bar_urid: LV2Urid,
147 time_bar_beat_urid: LV2Urid,
148 time_beats_per_bar_urid: LV2Urid,
149 time_beat_unit_urid: LV2Urid,
150 midi_inputs: usize,
151 midi_outputs: usize,
152 has_worker_interface: bool,
153 control_ports: Vec<ControlPortInfo>,
154 midnam_note_names: UnsafeMutex<HashMap<u8, String>>,
155 bypassed: AtomicBool,
156}
157
158struct LoadedPlugin {
159 instance: ActiveInstance,
160 _urid_feature: UridMapFeature,
161 _state_path_feature: StatePathFeature,
162 _instantiate_features: InstantiateFeatureSet,
163}
164
165#[derive(Default)]
166struct UridMapState {
167 next_urid: LV2Urid,
168 by_uri: HashMap<String, LV2Urid>,
169 by_urid: HashMap<LV2Urid, CString>,
170}
171
172struct UridMapFeature {
173 _map_uri: CString,
174 _unmap_uri: CString,
175 map_feature: LV2Feature,
176 unmap_feature: LV2Feature,
177 _map: Box<LV2UridMap>,
178 _unmap: Box<LV2UridUnmap>,
179 _state: Box<Mutex<UridMapState>>,
180}
181
182unsafe impl Send for UridMapFeature {}
183
184#[repr(C)]
185struct LV2UridUnmap {
186 handle: LV2UridMapHandle,
187 unmap: extern "C" fn(handle: LV2UridMapHandle, urid: LV2Urid) -> *const c_char,
188}
189
190struct InstantiateFeatureSet {
191 _feature_uris: Vec<CString>,
192 features: Vec<LV2Feature>,
193 _worker_feature: WorkerFeature,
194 _option_values: Vec<u32>,
195 _options: Vec<LV2OptionsOption>,
196 _flag_feature_data: Box<u8>,
197}
198
199unsafe impl Send for InstantiateFeatureSet {}
200
201#[repr(C)]
202#[derive(Clone, Copy)]
203struct LV2OptionsOption {
204 context: u32,
205 subject: u32,
206 key: u32,
207 size: u32,
208 type_: u32,
209 value: *const c_void,
210}
211
212#[derive(Debug, Clone)]
213struct ControlPortInfo {
214 index: u32,
215 name: String,
216 min: f32,
217 max: f32,
218}
219
220struct AtomBuffer {
221 words: Vec<u64>,
222}
223
224impl AtomBuffer {
225 fn new(len_bytes: usize) -> Self {
226 let words = len_bytes.div_ceil(std::mem::size_of::<u64>()).max(1);
227 Self {
228 words: vec![0; words],
229 }
230 }
231
232 fn bytes_mut(&mut self) -> &mut [u8] {
233 let full_len = self.words.len() * std::mem::size_of::<u64>();
234 let raw = self.words.as_mut_ptr().cast::<u8>();
235 unsafe { std::slice::from_raw_parts_mut(raw, full_len) }
236 }
237
238 fn ptr_mut(&mut self) -> *mut u8 {
239 self.words.as_mut_ptr().cast::<u8>()
240 }
241}
242
243#[derive(Debug)]
244struct RawStateProperty {
245 key: u32,
246 type_: u32,
247 flags: u32,
248 value: Vec<u8>,
249}
250
251struct StateSaveContext {
252 properties: Vec<RawStateProperty>,
253}
254
255struct StateRestoreContext {
256 properties: Vec<RawStateProperty>,
257 by_key: HashMap<u32, usize>,
258}
259
260type Lv2Handle = *mut c_void;
261type Lv2StateHandle = *mut c_void;
262type Lv2StateStatus = u32;
263type Lv2StateStoreFn = Option<
264 unsafe extern "C" fn(
265 handle: Lv2StateHandle,
266 key: u32,
267 value: *const c_void,
268 size: usize,
269 type_: u32,
270 flags: u32,
271 ) -> Lv2StateStatus,
272>;
273type Lv2StateRetrieveFn = Option<
274 unsafe extern "C" fn(
275 handle: Lv2StateHandle,
276 key: u32,
277 size: *mut usize,
278 type_: *mut u32,
279 flags: *mut u32,
280 ) -> *const c_void,
281>;
282const LV2_STATE_STATUS_SUCCESS: Lv2StateStatus = 0;
283const LV2_STATE_STATUS_ERR_NO_PROPERTY: Lv2StateStatus = 5;
284
285#[repr(C)]
286struct Lv2StateInterface {
287 save: Option<
288 unsafe extern "C" fn(
289 instance: Lv2Handle,
290 store: Lv2StateStoreFn,
291 handle: Lv2StateHandle,
292 flags: u32,
293 features: *const *const LV2Feature,
294 ) -> Lv2StateStatus,
295 >,
296 restore: Option<
297 unsafe extern "C" fn(
298 instance: Lv2Handle,
299 retrieve: Lv2StateRetrieveFn,
300 handle: Lv2StateHandle,
301 flags: u32,
302 features: *const *const LV2Feature,
303 ) -> Lv2StateStatus,
304 >,
305}
306
307#[repr(C)]
308struct Lv2StateMapPath {
309 handle: *mut c_void,
310 abstract_path: Option<extern "C" fn(*mut c_void, *const c_char) -> *mut c_char>,
311 absolute_path: Option<extern "C" fn(*mut c_void, *const c_char) -> *mut c_char>,
312}
313
314#[repr(C)]
315struct Lv2StateMakePath {
316 handle: *mut c_void,
317 path: Option<extern "C" fn(*mut c_void, *const c_char) -> *mut c_char>,
318}
319
320#[repr(C)]
321struct Lv2StateFreePath {
322 handle: *mut c_void,
323 free_path: Option<extern "C" fn(*mut c_void, *mut c_char)>,
324}
325
326#[derive(Default)]
327struct StatePathContext {
328 base_dir: PathBuf,
329 copy_counter: u64,
330}
331
332struct StatePathFeature {
333 _map_uri: CString,
334 _make_uri: CString,
335 _free_uri: CString,
336 _map: Box<Lv2StateMapPath>,
337 _make: Box<Lv2StateMakePath>,
338 _free: Box<Lv2StateFreePath>,
339 map_feature: LV2Feature,
340 make_feature: LV2Feature,
341 free_feature: LV2Feature,
342 _context: Box<Mutex<StatePathContext>>,
343}
344
345unsafe impl Send for StatePathFeature {}
346
347const LV2_STATE_INTERFACE_URI: &str = "http://lv2plug.in/ns/ext/state#interface";
348const LV2_STATE_MAP_PATH_URI: &str = "http://lv2plug.in/ns/ext/state#mapPath";
349const LV2_STATE_MAKE_PATH_URI: &str = "http://lv2plug.in/ns/ext/state#makePath";
350const LV2_STATE_FREE_PATH_URI: &str = "http://lv2plug.in/ns/ext/state#freePath";
351const LV2_OPTIONS__OPTIONS: &str = "http://lv2plug.in/ns/ext/options#options";
352const LV2_BUF_SIZE__BOUNDED_BLOCK_LENGTH: &str =
353 "http://lv2plug.in/ns/ext/buf-size#boundedBlockLength";
354const LV2_BUF_SIZE__MIN_BLOCK_LENGTH: &str = "http://lv2plug.in/ns/ext/buf-size#minBlockLength";
355const LV2_BUF_SIZE__MAX_BLOCK_LENGTH: &str = "http://lv2plug.in/ns/ext/buf-size#maxBlockLength";
356const LV2_BUF_SIZE__NOMINAL_BLOCK_LENGTH: &str =
357 "http://lv2plug.in/ns/ext/buf-size#nominalBlockLength";
358const LV2_URID__MAP_URI_TYPO_COMPAT: &str = "http://lv2plug.in/ns//ext/urid#map";
359
360const LV2_ATOM_BUFFER_BYTES: usize = 8192;
361
362impl fmt::Debug for Lv2Processor {
363 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
364 f.debug_struct("Lv2Processor")
365 .field("uri", &self.uri)
366 .field("audio_inputs", &self.audio_inputs.len())
367 .field("audio_outputs", &self.audio_outputs.len())
368 .field("main_audio_inputs", &self.main_audio_inputs)
369 .field("main_audio_outputs", &self.main_audio_outputs)
370 .finish()
371 }
372}
373
374impl Lv2Processor {
375 pub fn new(sample_rate: f64, buffer_size: usize, uri: &str) -> Result<Self, String> {
376 let world = World::new();
377 world.load_all();
378
379 let uri_node = world.new_uri(uri);
380 let plugin = world
381 .plugins()
382 .plugin(&uri_node)
383 .ok_or_else(|| format!("Plugin not found for URI: {uri}"))?;
384 if !plugin.verify() {
385 return Err(format!("Plugin failed verification: {uri}"));
386 }
387
388 let mut urid_feature = UridMapFeature::new()?;
389 let mut state_path_feature = StatePathFeature::new(default_state_base_dir());
390 let (instance, instantiate_features) = instantiate_plugin(
391 &plugin,
392 sample_rate,
393 uri,
394 &mut urid_feature,
395 &mut state_path_feature,
396 )?;
397 let active_instance = unsafe { instance.activate() };
398
399 let input_port = world.new_uri("http://lv2plug.in/ns/lv2core#InputPort");
400 let output_port = world.new_uri("http://lv2plug.in/ns/lv2core#OutputPort");
401 let audio_port = world.new_uri("http://lv2plug.in/ns/lv2core#AudioPort");
402 let control_port = world.new_uri("http://lv2plug.in/ns/lv2core#ControlPort");
403 let toggled_property = world.new_uri("http://lv2plug.in/ns/lv2core#toggled");
404 let integer_property = world.new_uri("http://lv2plug.in/ns/lv2core#integer");
405 let enumeration_property = world.new_uri("http://lv2plug.in/ns/lv2core#enumeration");
406 let port_group_predicate = world.new_uri("http://lv2plug.in/ns/ext/port-groups#group");
407 let main_input_group_predicate =
408 world.new_uri("http://lv2plug.in/ns/ext/port-groups#mainInput");
409 let main_output_group_predicate =
410 world.new_uri("http://lv2plug.in/ns/ext/port-groups#mainOutput");
411 let atom_port = world.new_uri("http://lv2plug.in/ns/ext/atom#AtomPort");
412 let event_port = world.new_uri("http://lv2plug.in/ns/ext/event#EventPort");
413
414 let ports_count = plugin.ports_count();
415 let mut port_bindings = vec![PortBinding::Scalar(0); ports_count];
416 let mut scalar_values = vec![0.0_f32; ports_count.max(1)];
417 let mut audio_inputs: Vec<Arc<AudioIO>> = vec![];
418 let mut audio_outputs: Vec<Arc<AudioIO>> = vec![];
419 let mut atom_inputs: Vec<AtomBuffer> = vec![];
420 let mut atom_outputs: Vec<AtomBuffer> = vec![];
421 let mut midi_inputs = 0_usize;
422 let mut midi_outputs = 0_usize;
423 let mut control_ports = vec![];
424 let has_worker_interface = plugin.has_extension_data(&world.new_uri(LV2_WORKER__INTERFACE));
425 let atom_sequence_urid = urid_feature.map_uri(LV2_ATOM__SEQUENCE);
426 let atom_object_urid = urid_feature.map_uri(LV2_ATOM__OBJECT);
427 let atom_long_urid = urid_feature.map_uri(LV2_ATOM__LONG);
428 let atom_double_urid = urid_feature.map_uri(LV2_ATOM__DOUBLE);
429 let atom_frame_time_urid = urid_feature.map_uri(LV2_ATOM__FRAMETIME);
430 let midi_event_urid = urid_feature.map_uri(lv2_raw::LV2_MIDI__MIDIEVENT);
431 let time_position_urid = urid_feature.map_uri(lv2_raw::LV2_TIME__POSITION);
432 let time_frame_urid = urid_feature.map_uri(lv2_raw::LV2_TIME__FRAME);
433 let time_speed_urid = urid_feature.map_uri(lv2_raw::LV2_TIME__SPEED);
434 let time_bpm_urid = urid_feature.map_uri(lv2_raw::LV2_TIME__BEATSPERMINUTE);
435 let time_bar_urid = urid_feature.map_uri(lv2_raw::LV2_TIME__BAR);
436 let time_bar_beat_urid = urid_feature.map_uri(lv2_raw::LV2_TIME__BARBEAT);
437 let time_beats_per_bar_urid = urid_feature.map_uri(lv2_raw::LV2_TIME__BEATSPERBAR);
438 let time_beat_unit_urid = urid_feature.map_uri(lv2_raw::LV2_TIME__BEATUNIT);
439 let declared_audio_inputs = plugin
440 .iter_ports()
441 .filter(|port| port.is_a(&audio_port) && port.is_a(&input_port))
442 .count();
443 let declared_audio_outputs = plugin
444 .iter_ports()
445 .filter(|port| port.is_a(&audio_port) && port.is_a(&output_port))
446 .count();
447 let main_audio_inputs = count_main_audio_ports(
448 &plugin,
449 &main_input_group_predicate,
450 &port_group_predicate,
451 &audio_port,
452 &input_port,
453 )
454 .unwrap_or(declared_audio_inputs);
455 let main_audio_outputs = count_main_audio_ports(
456 &plugin,
457 &main_output_group_predicate,
458 &port_group_predicate,
459 &audio_port,
460 &output_port,
461 )
462 .unwrap_or(declared_audio_outputs);
463
464 for port in plugin.iter_ports() {
465 let index = port.index();
466 let is_audio = port.is_a(&audio_port);
467 let is_control = port.is_a(&control_port);
468 let is_atom = port.is_a(&atom_port) || port.is_a(&event_port);
469 let is_input = port.is_a(&input_port);
470 let is_output = port.is_a(&output_port);
471
472 if is_audio && is_input {
473 let channel = audio_inputs.len();
474 audio_inputs.push(Arc::new(AudioIO::new(buffer_size)));
475 port_bindings[index] = PortBinding::AudioInput(channel);
476 } else if is_audio && is_output {
477 let channel = audio_outputs.len();
478 audio_outputs.push(Arc::new(AudioIO::new(buffer_size)));
479 port_bindings[index] = PortBinding::AudioOutput(channel);
480 } else if is_atom && is_input {
481 midi_inputs += 1;
482 let atom_idx = atom_inputs.len();
483 let mut buffer = AtomBuffer::new(LV2_ATOM_BUFFER_BYTES);
484 prepare_empty_atom_sequence(
485 buffer.bytes_mut(),
486 atom_sequence_urid,
487 atom_frame_time_urid,
488 );
489 atom_inputs.push(buffer);
490 port_bindings[index] = PortBinding::AtomInput(atom_idx);
491 } else if is_atom && is_output {
492 midi_outputs += 1;
493 let atom_idx = atom_outputs.len();
494 let mut buffer = AtomBuffer::new(LV2_ATOM_BUFFER_BYTES);
495 prepare_output_atom_sequence(
496 buffer.bytes_mut(),
497 atom_sequence_urid,
498 atom_frame_time_urid,
499 );
500 atom_outputs.push(buffer);
501 port_bindings[index] = PortBinding::AtomOutput(atom_idx);
502 } else {
503 let range = port.range();
504 let min = range
505 .minimum
506 .as_ref()
507 .and_then(lv2_node_to_f32)
508 .unwrap_or(0.0);
509 let max = range
510 .maximum
511 .as_ref()
512 .and_then(lv2_node_to_f32)
513 .unwrap_or(1.0);
514 let explicit_default = range.default.as_ref().and_then(lv2_node_to_f32);
515 let is_toggled = is_control && is_input && port.has_property(&toggled_property);
516 let is_discrete = is_control
517 && is_input
518 && (port.has_property(&integer_property)
519 || port.has_property(&enumeration_property));
520 let (fallback_default, fallback_reason) =
521 infer_missing_control_default(min, max, is_toggled, is_discrete);
522 let default_value = explicit_default.unwrap_or(fallback_default);
523 scalar_values[index] = default_value;
524 port_bindings[index] = PortBinding::Scalar(index);
525
526 if explicit_default.is_none()
527 && is_control
528 && is_input
529 && std::env::var_os("MAOLAN_TRACE_LV2_DEFAULTS").is_some()
530 {
531 let symbol = port
532 .symbol()
533 .and_then(|node| node.as_str().map(str::to_string))
534 .unwrap_or_else(|| format!("port_{index}"));
535 let _ = (uri, index, symbol, min, max, default_value, fallback_reason);
536 }
537
538 if is_control && is_input {
539 let mut min = min;
540 let mut max = max;
541 if !matches!(min.partial_cmp(&max), Some(std::cmp::Ordering::Less)) {
542 min = default_value - 1.0;
543 max = default_value + 1.0;
544 }
545 let fallback_name = format!("Port {}", index);
546 let name = port
547 .name()
548 .and_then(|node| node.as_str().map(str::to_string))
549 .or_else(|| {
550 port.symbol()
551 .and_then(|node| node.as_str().map(str::to_string))
552 })
553 .unwrap_or(fallback_name);
554 control_ports.push(ControlPortInfo {
555 index: index as u32,
556 name,
557 min,
558 max,
559 });
560 }
561 }
562 }
563
564 let mut processor = Self {
565 uri: uri.to_string(),
566 plugin_name: plugin
567 .name()
568 .as_str()
569 .map(str::to_string)
570 .unwrap_or_else(|| uri.to_string()),
571 sample_rate,
572 instance: Some(active_instance),
573 _world: world,
574 _urid_feature: urid_feature,
575 _state_path_feature: state_path_feature,
576 _instantiate_features: instantiate_features,
577 port_bindings,
578 scalar_values: Arc::new(Mutex::new(scalar_values)),
579 audio_inputs,
580 audio_outputs,
581 main_audio_inputs,
582 main_audio_outputs,
583 atom_inputs,
584 atom_outputs,
585 atom_sequence_urid,
586 atom_object_urid,
587 atom_long_urid,
588 atom_double_urid,
589 atom_frame_time_urid,
590 midi_event_urid,
591 time_position_urid,
592 time_frame_urid,
593 time_speed_urid,
594 time_bpm_urid,
595 time_bar_urid,
596 time_bar_beat_urid,
597 time_beats_per_bar_urid,
598 time_beat_unit_urid,
599 midi_inputs,
600 midi_outputs,
601 has_worker_interface,
602 control_ports,
603 midnam_note_names: UnsafeMutex::new(HashMap::new()),
604 bypassed: AtomicBool::new(false),
605 };
606 processor.validate_required_runtime_callbacks()?;
607 processor.connect_ports();
608 if std::env::var_os("MAOLAN_LV2_ENABLE_MIDNAM").is_some() {
609 processor.query_midnam();
610 }
611 Ok(processor)
612 }
613
614 pub fn uri(&self) -> &str {
615 &self.uri
616 }
617
618 pub fn name(&self) -> &str {
619 &self.plugin_name
620 }
621
622 pub fn audio_inputs(&self) -> &[Arc<AudioIO>] {
623 &self.audio_inputs
624 }
625
626 pub fn audio_outputs(&self) -> &[Arc<AudioIO>] {
627 &self.audio_outputs
628 }
629
630 pub fn audio_input_count(&self) -> usize {
631 self.audio_inputs.len()
632 }
633
634 pub fn audio_output_count(&self) -> usize {
635 self.audio_outputs.len()
636 }
637
638 pub fn main_audio_input_count(&self) -> usize {
639 self.main_audio_inputs
640 }
641
642 pub fn main_audio_output_count(&self) -> usize {
643 self.main_audio_outputs
644 }
645
646 pub fn midi_input_count(&self) -> usize {
647 self.midi_inputs
648 }
649
650 pub fn midi_output_count(&self) -> usize {
651 self.midi_outputs
652 }
653
654 pub fn setup_audio_ports(&self) {
655 for io in &self.audio_inputs {
656 io.setup();
657 }
658 for io in &self.audio_outputs {
659 io.setup();
660 }
661 }
662
663 pub fn process(&mut self, input_channels: &[Vec<f32>], frames: usize) -> Vec<Vec<f32>> {
664 if let Ok(mut values) = self.scalar_values.lock()
665 && values.is_empty()
666 {
667 values.push(0.0);
668 }
669
670 for (channel, io) in self.audio_inputs.iter_mut().enumerate() {
671 let buffer = io.buffer.lock();
672 buffer.fill(0.0);
673 if let Some(input) = input_channels.get(channel) {
674 let copy_len = input.len().min(frames);
675 buffer[..copy_len].copy_from_slice(&input[..copy_len]);
676 }
677 }
678 for io in &self.audio_outputs {
679 let buffer = io.buffer.lock();
680 buffer.fill(0.0);
681 }
682 for buffer in &mut self.atom_inputs {
683 prepare_empty_atom_sequence(
684 buffer.bytes_mut(),
685 self.atom_sequence_urid,
686 self.atom_frame_time_urid,
687 );
688 }
689 for buffer in &mut self.atom_outputs {
690 prepare_output_atom_sequence(
691 buffer.bytes_mut(),
692 self.atom_sequence_urid,
693 self.atom_frame_time_urid,
694 );
695 }
696
697 self.connect_ports();
698 if let Some(instance) = self.instance.as_mut() {
699 unsafe {
700 instance.run(frames);
701 }
702 }
703 self.audio_outputs
704 .iter()
705 .map(|io| io.buffer.lock().to_vec())
706 .collect()
707 }
708
709 pub fn set_bypassed(&self, bypassed: bool) {
710 self.bypassed.store(bypassed, Ordering::Relaxed);
711 }
712
713 pub fn is_bypassed(&self) -> bool {
714 self.bypassed.load(Ordering::Relaxed)
715 }
716
717 fn bypass_copy_inputs_to_outputs(&self) {
718 for (input, output) in self.audio_inputs.iter().zip(self.audio_outputs.iter()) {
719 let src = input.buffer.lock();
720 let dst = output.buffer.lock();
721 dst.fill(0.0);
722 for (d, s) in dst.iter_mut().zip(src.iter()) {
723 *d = *s;
724 }
725 *output.finished.lock() = true;
726 }
727 for output in self.audio_outputs.iter().skip(self.audio_inputs.len()) {
728 output.buffer.lock().fill(0.0);
729 *output.finished.lock() = true;
730 }
731 }
732
733 pub fn process_with_audio_io(
734 &mut self,
735 frames: usize,
736 midi_inputs: &[Vec<MidiEvent>],
737 transport: Lv2TransportInfo,
738 ) -> Vec<Vec<MidiEvent>> {
739 if let Ok(mut values) = self.scalar_values.lock()
740 && values.is_empty()
741 {
742 values.push(0.0);
743 }
744
745 for io in &self.audio_outputs {
746 let buffer = io.buffer.lock();
747 buffer.fill(0.0);
748 *io.finished.lock() = false;
749 }
750
751 if self.bypassed.load(Ordering::Relaxed) {
752 self.bypass_copy_inputs_to_outputs();
753 return vec![Vec::new(); self.midi_outputs];
754 }
755
756 for buffer in &mut self.atom_inputs {
757 prepare_empty_atom_sequence(
758 buffer.bytes_mut(),
759 self.atom_sequence_urid,
760 self.atom_frame_time_urid,
761 );
762 }
763 for buffer in &mut self.atom_outputs {
764 prepare_output_atom_sequence(
765 buffer.bytes_mut(),
766 self.atom_sequence_urid,
767 self.atom_frame_time_urid,
768 );
769 }
770 for port in 0..self.atom_inputs.len() {
771 self.write_transport_event(port, transport);
772 }
773 for (port, events) in midi_inputs.iter().enumerate() {
774 self.write_midi_input_events(port, events);
775 }
776
777 self.connect_ports();
778 if let Some(instance) = self.instance.as_mut() {
779 unsafe {
780 instance.run(frames);
781 }
782 }
783 self.run_worker_cycle();
784
785 for io in &self.audio_outputs {
786 *io.finished.lock() = true;
787 }
788 let mut midi_outputs = vec![];
789 for port in 0..self.midi_outputs {
790 midi_outputs.push(self.read_midi_output_events(port));
791 }
792 midi_outputs
793 }
794
795 fn run_worker_cycle(&mut self) {
796 if !self.has_worker_interface {
797 return;
798 }
799 let Some(worker_iface) = self.worker_interface() else {
800 return;
801 };
802 let (work_fn, work_response_fn, end_run_fn) = (
803 worker_iface.work,
804 worker_iface.work_response,
805 worker_iface.end_run,
806 );
807 let Some(work_fn) = work_fn else {
808 return;
809 };
810 let instance_handle = self.instance_handle();
811 if instance_handle.is_null() {
812 return;
813 }
814
815 let worker_state = &self._instantiate_features._worker_feature.state;
816 let mut jobs = std::mem::take(worker_state.jobs.lock());
817 for job in jobs.drain(..) {
818 if job.len() > (u32::MAX as usize) {
819 continue;
820 }
821 unsafe {
822 work_fn(
823 instance_handle,
824 Some(lv2_worker_respond_callback),
825 &**worker_state as *const WorkerScheduleState as *mut c_void,
826 job.len() as u32,
827 job.as_ptr().cast::<c_void>(),
828 );
829 }
830 }
831 *worker_state.jobs.lock() = jobs;
832
833 if let Some(work_response_fn) = work_response_fn {
834 let mut responses = std::mem::take(worker_state.responses.lock());
835 for response in responses.drain(..) {
836 if response.len() > (u32::MAX as usize) {
837 continue;
838 }
839 unsafe {
840 work_response_fn(
841 instance_handle,
842 response.len() as u32,
843 response.as_ptr().cast::<c_void>(),
844 );
845 }
846 }
847 *worker_state.responses.lock() = responses;
848 }
849
850 if let Some(end_run_fn) = end_run_fn {
851 unsafe {
852 end_run_fn(instance_handle);
853 }
854 }
855 }
856
857 fn connect_ports(&mut self) {
858 for (port_index, binding) in self.port_bindings.iter().enumerate() {
859 match binding {
860 PortBinding::AudioInput(channel) => {
861 let ptr = self.audio_inputs[*channel].buffer.lock().as_mut_ptr();
862 if let Some(instance) = self.instance.as_mut() {
863 unsafe {
864 instance.instance_mut().connect_port_mut(port_index, ptr);
865 }
866 }
867 }
868 PortBinding::AudioOutput(channel) => {
869 let ptr = self.audio_outputs[*channel].buffer.lock().as_mut_ptr();
870 if let Some(instance) = self.instance.as_mut() {
871 unsafe {
872 instance.instance_mut().connect_port_mut(port_index, ptr);
873 }
874 }
875 }
876 PortBinding::AtomInput(atom_index) => {
877 let ptr = self.atom_inputs[*atom_index].ptr_mut();
878 if let Some(instance) = self.instance.as_mut() {
879 unsafe {
880 instance.instance_mut().connect_port_mut(port_index, ptr);
881 }
882 }
883 }
884 PortBinding::AtomOutput(atom_index) => {
885 let ptr = self.atom_outputs[*atom_index].ptr_mut();
886 if let Some(instance) = self.instance.as_mut() {
887 unsafe {
888 instance.instance_mut().connect_port_mut(port_index, ptr);
889 }
890 }
891 }
892 PortBinding::Scalar(index) => {
893 if let Ok(mut values) = self.scalar_values.lock()
894 && *index < values.len()
895 {
896 let ptr = (&mut values[*index]) as *mut f32;
897 if let Some(instance) = self.instance.as_mut() {
898 unsafe {
899 instance.instance_mut().connect_port_mut(port_index, ptr);
900 }
901 }
902 }
903 }
904 }
905 }
906 }
907
908 fn validate_required_runtime_callbacks(&self) -> Result<(), String> {
909 let Some(instance) = self.instance.as_ref() else {
910 return Err(format!("LV2 plugin '{}' has no active instance", self.uri));
911 };
912 let Some(descriptor) = instance.instance().descriptor() else {
913 return Err(format!("LV2 plugin '{}' has no descriptor", self.uri));
914 };
915 let connect_ptr = (descriptor.connect_port as *const ()) as usize;
916 if connect_ptr == 0 {
917 return Err(format!(
918 "LV2 plugin '{}' has null connect_port callback",
919 self.uri
920 ));
921 }
922 let run_ptr = (descriptor.run as *const ()) as usize;
923 if run_ptr == 0 {
924 return Err(format!("LV2 plugin '{}' has null run callback", self.uri));
925 }
926 Ok(())
927 }
928
929 pub fn snapshot_state(&self) -> Lv2PluginState {
930 let mut state = Lv2PluginState {
931 port_values: self.control_port_values(),
932 properties: vec![],
933 };
934 let Some(interface) = self.state_interface() else {
935 return state;
936 };
937 let Some(save_fn) = interface.save else {
938 return state;
939 };
940
941 let mut ctx = StateSaveContext { properties: vec![] };
942 let features = self.state_feature_ptrs();
943 let status = unsafe {
944 save_fn(
945 self.instance_handle(),
946 Some(lv2_state_store_callback),
947 (&mut ctx as *mut StateSaveContext).cast::<c_void>(),
948 0,
949 features.as_ptr(),
950 )
951 };
952 if status != LV2_STATE_STATUS_SUCCESS {
953 return state;
954 }
955
956 state.properties = ctx
957 .properties
958 .into_iter()
959 .filter_map(|p| {
960 let key_uri = self._urid_feature.unmap_urid(p.key)?;
961 let type_uri = self._urid_feature.unmap_urid(p.type_)?;
962 Some(Lv2StateProperty {
963 key_uri,
964 type_uri,
965 flags: p.flags,
966 value: p.value,
967 })
968 })
969 .collect();
970 state
971 }
972
973 pub fn snapshot_port_state(&self) -> Lv2PluginState {
974 Lv2PluginState {
975 port_values: self.control_port_values(),
976 properties: vec![],
977 }
978 }
979
980 pub fn restore_state(&mut self, state: &Lv2PluginState) -> Result<(), String> {
981 self.set_control_port_values(&state.port_values);
982 if state.properties.is_empty() {
983 return Ok(());
984 }
985 let Some(interface) = self.state_interface() else {
986 return Ok(());
987 };
988 let Some(restore_fn) = interface.restore else {
989 return Ok(());
990 };
991
992 let mut properties: Vec<RawStateProperty> = vec![];
993 let mut by_key: HashMap<u32, usize> = HashMap::new();
994 for prop in &state.properties {
995 let key = self._urid_feature.map_uri(prop.key_uri.as_bytes());
996 let type_ = self._urid_feature.map_uri(prop.type_uri.as_bytes());
997 if key == 0 || type_ == 0 {
998 continue;
999 }
1000 let idx = properties.len();
1001 properties.push(RawStateProperty {
1002 key,
1003 type_,
1004 flags: prop.flags,
1005 value: prop.value.clone(),
1006 });
1007 by_key.insert(key, idx);
1008 }
1009 let mut ctx = StateRestoreContext { properties, by_key };
1010 let features = self.state_feature_ptrs();
1011
1012 let status = unsafe {
1013 restore_fn(
1014 self.instance_handle(),
1015 Some(lv2_state_retrieve_callback),
1016 (&mut ctx as *mut StateRestoreContext).cast::<c_void>(),
1017 0,
1018 features.as_ptr(),
1019 )
1020 };
1021 if status == LV2_STATE_STATUS_SUCCESS {
1022 Ok(())
1023 } else {
1024 Err(format!(
1025 "LV2 state restore failed for '{}': status {}",
1026 self.uri, status
1027 ))
1028 }
1029 }
1030
1031 fn state_interface(&self) -> Option<&Lv2StateInterface> {
1032 let instance = self.instance.as_ref()?;
1033 if !self.has_extension_data_callback(instance.instance()) {
1034 return None;
1035 }
1036 let ptr = unsafe {
1037 instance
1038 .instance()
1039 .extension_data::<Lv2StateInterface>(LV2_STATE_INTERFACE_URI)?
1040 };
1041 Some(unsafe { ptr.as_ref() })
1042 }
1043
1044 fn worker_interface(&self) -> Option<&Lv2WorkerInterface> {
1045 let instance = self.instance.as_ref()?;
1046 if !self.has_extension_data_callback(instance.instance()) {
1047 return None;
1048 }
1049 let ptr = unsafe {
1050 instance
1051 .instance()
1052 .extension_data::<Lv2WorkerInterface>(LV2_WORKER__INTERFACE)?
1053 };
1054 Some(unsafe { ptr.as_ref() })
1055 }
1056
1057 fn has_extension_data_callback(&self, instance: &lilv::instance::Instance) -> bool {
1058 let Some(descriptor) = instance.descriptor() else {
1059 return false;
1060 };
1061 (descriptor.extension_data as *const ()) as usize != 0
1062 }
1063
1064 fn instance_handle(&self) -> Lv2Handle {
1065 self.instance
1066 .as_ref()
1067 .map(|i| i.instance().handle() as Lv2Handle)
1068 .unwrap_or(std::ptr::null_mut())
1069 }
1070
1071 pub fn instance_access_handle(&self) -> Option<usize> {
1072 let handle = self.instance_handle();
1073 if handle.is_null() {
1074 None
1075 } else {
1076 Some(handle as usize)
1077 }
1078 }
1079
1080 fn state_feature_ptrs(&self) -> [*const LV2Feature; 6] {
1081 let sp = self._state_path_feature.feature_ptrs();
1082 [
1083 self._urid_feature.map_feature() as *const LV2Feature,
1084 self._urid_feature.unmap_feature() as *const LV2Feature,
1085 sp[0],
1086 sp[1],
1087 sp[2],
1088 std::ptr::null(),
1089 ]
1090 }
1091
1092 fn control_port_values(&self) -> Vec<Lv2StatePortValue> {
1093 let Ok(values) = self.scalar_values.lock() else {
1094 return vec![];
1095 };
1096 self.control_ports
1097 .iter()
1098 .filter_map(|port| {
1099 values.get(port.index as usize).map(|v| Lv2StatePortValue {
1100 index: port.index,
1101 value: *v,
1102 })
1103 })
1104 .collect()
1105 }
1106
1107 fn set_control_port_values(&mut self, port_values: &[Lv2StatePortValue]) {
1108 let Ok(mut values) = self.scalar_values.lock() else {
1109 return;
1110 };
1111 for port in port_values {
1112 if let Some(slot) = values.get_mut(port.index as usize) {
1113 *slot = port.value;
1114 }
1115 }
1116 }
1117
1118 pub fn set_state_base_dir(&mut self, base_dir: PathBuf) {
1119 self._state_path_feature.set_base_dir(base_dir);
1120 }
1121
1122 pub fn control_ports_with_values(&self) -> Vec<Lv2ControlPortInfo> {
1123 let Ok(values) = self.scalar_values.lock() else {
1124 return Vec::new();
1125 };
1126 self.control_ports
1127 .iter()
1128 .map(|port| Lv2ControlPortInfo {
1129 index: port.index,
1130 name: port.name.clone(),
1131 min: port.min,
1132 max: port.max,
1133 value: values.get(port.index as usize).copied().unwrap_or(0.0),
1134 })
1135 .collect()
1136 }
1137
1138 pub fn set_control_value(&mut self, index: u32, value: f32) -> Result<(), String> {
1139 let Some(port) = self.control_ports.iter().find(|port| port.index == index) else {
1140 return Err(format!("Unknown LV2 control port index: {index}"));
1141 };
1142 let clamped = value.clamp(port.min, port.max);
1143 let Ok(mut values) = self.scalar_values.lock() else {
1144 return Err("Failed to lock LV2 control values".to_string());
1145 };
1146 let Some(slot) = values.get_mut(index as usize) else {
1147 return Err(format!("LV2 control port index out of range: {index}"));
1148 };
1149 *slot = clamped;
1150 Ok(())
1151 }
1152
1153 pub fn midnam_note_names(&self) -> HashMap<u8, String> {
1154 self.midnam_note_names.lock().clone()
1155 }
1156
1157 fn query_midnam(&mut self) {
1158 let Some(instance) = &self.instance else {
1159 return;
1160 };
1161
1162 if !self.has_extension_data_callback(instance.instance()) {
1163 return;
1164 }
1165
1166 let interface_ptr = unsafe {
1167 instance
1168 .instance()
1169 .extension_data::<Lv2MidnamInterface>(LV2_MIDNAM__INTERFACE)
1170 };
1171 let Some(interface_ptr) = interface_ptr else {
1172 return;
1173 };
1174 let interface = unsafe { interface_ptr.as_ref() };
1175
1176 let Some(midnam_fn) = interface.midnam else {
1178 return;
1179 };
1180 let Some(free_fn) = interface.free else {
1181 return;
1182 };
1183
1184 let xml_ptr = unsafe { midnam_fn(instance.instance().handle()) };
1185 if xml_ptr.is_null() {
1186 return;
1187 }
1188
1189 let xml_cstr = unsafe { CStr::from_ptr(xml_ptr) };
1190 let Ok(xml_str) = xml_cstr.to_str() else {
1191 unsafe { free_fn(xml_ptr) };
1192 return;
1193 };
1194
1195 let note_names = self.parse_midnam_xml(xml_str);
1197 *self.midnam_note_names.lock() = note_names;
1198
1199 unsafe { free_fn(xml_ptr) };
1200 }
1201
1202 fn parse_midnam_xml(&self, xml: &str) -> HashMap<u8, String> {
1203 let mut note_names = HashMap::new();
1204
1205 for line in xml.lines() {
1207 let line = line.trim();
1208 if !line.starts_with("<Note ") {
1209 continue;
1210 }
1211
1212 let number = if let Some(start) = line.find("Number=\"") {
1214 let start = start + 8; if let Some(end) = line[start..].find('"') {
1216 line[start..start + end].parse::<u8>().ok()
1217 } else {
1218 None
1219 }
1220 } else {
1221 None
1222 };
1223
1224 let name = if let Some(start) = line.find("Name=\"") {
1226 let start = start + 6; line[start..]
1228 .find('"')
1229 .map(|end| line[start..start + end].to_string())
1230 } else {
1231 None
1232 };
1233
1234 if let (Some(num), Some(name_str)) = (number, name) {
1235 note_names.insert(num, name_str);
1236 }
1237 }
1238
1239 note_names
1240 }
1241
1242 fn write_midi_input_events(&mut self, port: usize, events: &[MidiEvent]) {
1243 let Some(buffer) = self.atom_inputs.get_mut(port) else {
1244 return;
1245 };
1246 let bytes = buffer.bytes_mut();
1247 if bytes.len() < std::mem::size_of::<LV2AtomSequence>() {
1248 return;
1249 }
1250 let seq = bytes.as_mut_ptr() as *mut LV2AtomSequence;
1251 let capacity = bytes
1252 .len()
1253 .saturating_sub(std::mem::size_of::<lv2_raw::LV2Atom>()) as u32;
1254 for event in events {
1255 if event.data.is_empty() {
1256 continue;
1257 }
1258 let mut raw =
1259 vec![0_u8; std::mem::size_of::<lv2_raw::LV2AtomEvent>() + event.data.len()];
1260 let raw_event = raw.as_mut_ptr() as *mut lv2_raw::LV2AtomEvent;
1261 unsafe {
1262 (*raw_event).time_in_frames = event.frame as i64;
1263 (*raw_event).body.mytype = self.midi_event_urid;
1264 (*raw_event).body.size = event.data.len() as u32;
1265 let data_ptr =
1266 (raw_event as *mut u8).add(std::mem::size_of::<lv2_raw::LV2AtomEvent>());
1267 std::ptr::copy_nonoverlapping(event.data.as_ptr(), data_ptr, event.data.len());
1268 if lv2_atom_sequence_append_event(seq, capacity, raw_event).is_null() {
1269 break;
1270 }
1271 }
1272 }
1273 }
1274
1275 fn write_transport_event(&mut self, port: usize, transport: Lv2TransportInfo) {
1276 let Some(buffer) = self.atom_inputs.get_mut(port) else {
1277 return;
1278 };
1279 let bytes = buffer.bytes_mut();
1280 if bytes.len() < std::mem::size_of::<LV2AtomSequence>() {
1281 return;
1282 }
1283 let seq = bytes.as_mut_ptr() as *mut LV2AtomSequence;
1284 let capacity = bytes
1285 .len()
1286 .saturating_sub(std::mem::size_of::<lv2_raw::LV2Atom>()) as u32;
1287
1288 let beats_per_bar = if transport.tsig_num == 0 {
1289 4.0
1290 } else {
1291 transport.tsig_num as f64
1292 };
1293 let beat_unit = if transport.tsig_denom == 0 {
1294 4_i64
1295 } else {
1296 transport.tsig_denom as i64
1297 };
1298 let bpm = if transport.bpm > 0.0 {
1299 transport.bpm
1300 } else {
1301 120.0
1302 };
1303 let speed = if transport.playing { 1.0 } else { 0.0 };
1304 let sample = transport.transport_sample as i64;
1305 let seconds = (transport.transport_sample as f64) / self.sample_rate.max(1.0);
1306 let absolute_beats = seconds * bpm / 60.0;
1307 let bar = (absolute_beats / beats_per_bar).floor().max(0.0) as i64;
1308 let bar_beat = absolute_beats - (bar as f64 * beats_per_bar);
1309
1310 let mut payload =
1311 Vec::<u8>::with_capacity(std::mem::size_of::<LV2AtomObjectBody>() + (7 * 32));
1312 let object_body = LV2AtomObjectBody {
1313 id: 0,
1314 otype: self.time_position_urid,
1315 };
1316 let object_body_bytes = unsafe {
1317 std::slice::from_raw_parts(
1318 (&object_body as *const LV2AtomObjectBody).cast::<u8>(),
1319 std::mem::size_of::<LV2AtomObjectBody>(),
1320 )
1321 };
1322 payload.extend_from_slice(object_body_bytes);
1323
1324 append_object_long_property(
1325 &mut payload,
1326 self.time_frame_urid,
1327 self.atom_long_urid,
1328 sample,
1329 );
1330 append_object_double_property(
1331 &mut payload,
1332 self.time_speed_urid,
1333 self.atom_double_urid,
1334 speed,
1335 );
1336 append_object_double_property(&mut payload, self.time_bpm_urid, self.atom_double_urid, bpm);
1337 append_object_long_property(&mut payload, self.time_bar_urid, self.atom_long_urid, bar);
1338 append_object_double_property(
1339 &mut payload,
1340 self.time_bar_beat_urid,
1341 self.atom_double_urid,
1342 bar_beat,
1343 );
1344 append_object_double_property(
1345 &mut payload,
1346 self.time_beats_per_bar_urid,
1347 self.atom_double_urid,
1348 beats_per_bar,
1349 );
1350 append_object_long_property(
1351 &mut payload,
1352 self.time_beat_unit_urid,
1353 self.atom_long_urid,
1354 beat_unit,
1355 );
1356
1357 if payload.len() > (u32::MAX as usize) {
1358 return;
1359 }
1360
1361 let mut raw = vec![0_u8; std::mem::size_of::<LV2AtomEvent>() + payload.len()];
1362 let raw_event = raw.as_mut_ptr() as *mut LV2AtomEvent;
1363 unsafe {
1364 (*raw_event).time_in_frames = 0;
1365 (*raw_event).body.mytype = self.atom_object_urid;
1366 (*raw_event).body.size = payload.len() as u32;
1367 let data_ptr = (raw_event as *mut u8).add(std::mem::size_of::<LV2AtomEvent>());
1368 std::ptr::copy_nonoverlapping(payload.as_ptr(), data_ptr, payload.len());
1369 let _ = lv2_atom_sequence_append_event(seq, capacity, raw_event);
1370 }
1371 }
1372
1373 fn read_midi_output_events(&mut self, port: usize) -> Vec<MidiEvent> {
1374 let Some(buffer) = self.atom_outputs.get_mut(port) else {
1375 return vec![];
1376 };
1377 let bytes = buffer.bytes_mut();
1378 if bytes.len() < std::mem::size_of::<LV2AtomSequence>() {
1379 return vec![];
1380 }
1381
1382 let mut result = Vec::new();
1383 let seq = bytes.as_mut_ptr() as *mut LV2AtomSequence;
1384 unsafe {
1385 let body = &(*seq).body as *const LV2AtomSequenceBody;
1386 let size = (*seq).atom.size;
1387 let mut it = lv2_atom_sequence_begin(body);
1388 while !lv2_atom_sequence_is_end(body, size, it) {
1389 let event = &*it;
1390 if event.body.mytype == self.midi_event_urid && event.body.size > 0 {
1391 let data_ptr =
1392 (it as *const u8).add(std::mem::size_of::<lv2_raw::LV2AtomEvent>());
1393 let data_len = event.body.size as usize;
1394 let data = std::slice::from_raw_parts(data_ptr, data_len).to_vec();
1395 result.push(MidiEvent::new(event.time_in_frames.max(0) as u32, data));
1396 }
1397 it = lv2_atom_sequence_next(it);
1398 }
1399 }
1400 result
1401 }
1402}
1403
1404impl Drop for Lv2Processor {
1405 fn drop(&mut self) {
1406 let Some(instance) = self.instance.take() else {
1407 return;
1408 };
1409 drop(instance);
1410 }
1411}
1412
1413impl Lv2Host {
1414 pub fn new(sample_rate: f64) -> Self {
1415 let world = World::new();
1416 world.load_all();
1417 Self {
1418 world,
1419 sample_rate,
1420 loaded_plugins: HashMap::new(),
1421 }
1422 }
1423
1424 pub fn list_plugins(&self) -> Vec<Lv2PluginInfo> {
1425 let input_port = self.world.new_uri("http://lv2plug.in/ns/lv2core#InputPort");
1426 let output_port = self
1427 .world
1428 .new_uri("http://lv2plug.in/ns/lv2core#OutputPort");
1429 let audio_port = self.world.new_uri("http://lv2plug.in/ns/lv2core#AudioPort");
1430 let atom_port = self.world.new_uri("http://lv2plug.in/ns/ext/atom#AtomPort");
1431 let event_port = self
1432 .world
1433 .new_uri("http://lv2plug.in/ns/ext/event#EventPort");
1434 let midi_event = self
1435 .world
1436 .new_uri("http://lv2plug.in/ns/ext/midi#MidiEvent");
1437
1438 let mut plugins = self
1439 .world
1440 .plugins()
1441 .iter()
1442 .filter(|plugin| plugin.verify())
1443 .filter_map(|plugin| {
1444 let uri = plugin.uri().as_uri()?.to_string();
1445 let name = plugin.name().as_str().unwrap_or(&uri).to_string();
1446 let class_label = plugin
1447 .class()
1448 .label()
1449 .as_str()
1450 .unwrap_or("Unknown")
1451 .to_string();
1452 let bundle_uri = plugin.bundle_uri().as_uri().unwrap_or("").to_string();
1453 let required_features = plugin_feature_uris(&plugin);
1454 let (audio_inputs, audio_outputs, midi_inputs, midi_outputs) = plugin_port_counts(
1455 &plugin,
1456 &input_port,
1457 &output_port,
1458 &audio_port,
1459 &atom_port,
1460 &event_port,
1461 &midi_event,
1462 );
1463
1464 Some(Lv2PluginInfo {
1465 uri,
1466 name,
1467 class_label,
1468 bundle_uri,
1469 required_features,
1470 audio_inputs,
1471 audio_outputs,
1472 midi_inputs,
1473 midi_outputs,
1474 })
1475 })
1476 .collect::<Vec<_>>();
1477
1478 plugins.sort_by(|left, right| left.name.cmp(&right.name));
1479 plugins
1480 }
1481
1482 pub fn load_plugin(&mut self, uri: &str) -> Result<(), String> {
1483 if self.loaded_plugins.contains_key(uri) {
1484 return Err(format!("Plugin is already loaded: {uri}"));
1485 }
1486
1487 let plugin = self
1488 .plugin_by_uri(uri)
1489 .ok_or_else(|| format!("Plugin not found for URI: {uri}"))?;
1490
1491 let mut urid_feature = UridMapFeature::new()?;
1492 let mut state_path_feature = StatePathFeature::new(default_state_base_dir());
1493 let (instance, instantiate_features) = instantiate_plugin(
1494 &plugin,
1495 self.sample_rate,
1496 uri,
1497 &mut urid_feature,
1498 &mut state_path_feature,
1499 )?;
1500 let active_instance = unsafe { instance.activate() };
1501 self.loaded_plugins.insert(
1502 uri.to_string(),
1503 LoadedPlugin {
1504 instance: active_instance,
1505 _urid_feature: urid_feature,
1506 _state_path_feature: state_path_feature,
1507 _instantiate_features: instantiate_features,
1508 },
1509 );
1510 Ok(())
1511 }
1512
1513 pub fn unload_plugin(&mut self, uri: &str) -> Result<(), String> {
1514 let loaded_plugin = self
1515 .loaded_plugins
1516 .remove(uri)
1517 .ok_or_else(|| format!("Plugin is not currently loaded: {uri}"))?;
1518 let _ = unsafe { loaded_plugin.instance.deactivate() };
1519 Ok(())
1520 }
1521
1522 pub fn unload_all(&mut self) {
1523 let uris = self.loaded_plugins();
1524 for uri in uris {
1525 let _ = self.unload_plugin(&uri);
1526 }
1527 }
1528
1529 pub fn loaded_plugins(&self) -> Vec<String> {
1530 let mut uris = self.loaded_plugins.keys().cloned().collect::<Vec<_>>();
1531 uris.sort();
1532 uris
1533 }
1534
1535 pub fn loaded_count(&self) -> usize {
1536 self.loaded_plugins.len()
1537 }
1538
1539 fn plugin_by_uri(&self, uri: &str) -> Option<Plugin> {
1540 let uri_node = self.world.new_uri(uri);
1541 self.world.plugins().plugin(&uri_node)
1542 }
1543}
1544
1545impl Drop for Lv2Host {
1546 fn drop(&mut self) {
1547 self.unload_all();
1548 }
1549}
1550
1551fn plugin_feature_uris(plugin: &Plugin) -> Vec<String> {
1552 plugin
1553 .required_features()
1554 .iter()
1555 .filter_map(|feature| {
1556 feature
1557 .as_uri()
1558 .map(str::to_string)
1559 .or_else(|| feature.as_str().map(str::to_string))
1560 })
1561 .collect()
1562}
1563
1564fn instantiate_plugin(
1565 plugin: &Plugin,
1566 sample_rate: f64,
1567 uri: &str,
1568 urid_feature: &mut UridMapFeature,
1569 state_path_feature: &mut StatePathFeature,
1570) -> Result<(lilv::instance::Instance, InstantiateFeatureSet), String> {
1571 let required_features = plugin_feature_uris(plugin);
1572 let feature_set =
1573 build_instantiate_features(&required_features, urid_feature, state_path_feature)?;
1574 let feature_refs: Vec<&LV2Feature> = feature_set.features.iter().collect();
1575 let instance = unsafe { plugin.instantiate(sample_rate, feature_refs) }.ok_or_else(|| {
1576 if required_features.is_empty() {
1577 format!(
1578 "Failed to instantiate '{uri}'. It likely requires LV2 host features that are not wired yet."
1579 )
1580 } else {
1581 format!(
1582 "Failed to instantiate '{uri}'. Required features: {}",
1583 required_features.join(", ")
1584 )
1585 }
1586 })?;
1587 Ok((instance, feature_set))
1588}
1589
1590fn build_instantiate_features(
1591 required_features: &[String],
1592 urid_feature: &UridMapFeature,
1593 state_path_feature: &StatePathFeature,
1594) -> Result<InstantiateFeatureSet, String> {
1595 let mut seen = HashSet::<String>::new();
1596 let mut feature_uris = Vec::<CString>::new();
1597 let mut features = Vec::<LV2Feature>::new();
1598
1599 let mut push_feature =
1600 |uri: &str, data: *mut c_void, allow_duplicate: bool| -> Result<(), String> {
1601 if !allow_duplicate && !seen.insert(uri.to_string()) {
1602 return Ok(());
1603 }
1604 let c_uri = CString::new(uri)
1605 .map_err(|e| format!("Invalid LV2 feature URI '{uri}' for instantiate: {e}"))?;
1606 let feature = LV2Feature {
1607 uri: c_uri.as_ptr(),
1608 data,
1609 };
1610 feature_uris.push(c_uri);
1611 features.push(feature);
1612 Ok(())
1613 };
1614
1615 push_feature(LV2_URID__MAP, urid_feature.map_feature().data, false)?;
1616 push_feature(LV2_URID__UNMAP, urid_feature.unmap_feature().data, false)?;
1617 let worker_feature = WorkerFeature::new()?;
1618 push_feature(LV2_WORKER__SCHEDULE, worker_feature.feature.data, false)?;
1619
1620 let state_features = state_path_feature.feature_ptrs();
1621 for feature_ptr in state_features {
1622 if feature_ptr.is_null() {
1623 continue;
1624 }
1625 let feature = unsafe { &*feature_ptr };
1626 let uri = unsafe { CStr::from_ptr(feature.uri) }
1627 .to_str()
1628 .map_err(|e| format!("Invalid LV2 feature URI from state path feature: {e}"))?;
1629 push_feature(uri, feature.data, false)?;
1630 }
1631
1632 let option_values = vec![1_u32, 8192_u32, 1024_u32];
1633 let int_type = urid_feature.map_uri(LV2_ATOM__INT);
1634 let min_key = urid_feature.map_uri(LV2_BUF_SIZE__MIN_BLOCK_LENGTH.as_bytes());
1635 let max_key = urid_feature.map_uri(LV2_BUF_SIZE__MAX_BLOCK_LENGTH.as_bytes());
1636 let nominal_key = urid_feature.map_uri(LV2_BUF_SIZE__NOMINAL_BLOCK_LENGTH.as_bytes());
1637 let mut options = vec![
1638 LV2OptionsOption {
1639 context: 0,
1640 subject: 0,
1641 key: min_key,
1642 size: std::mem::size_of::<u32>() as u32,
1643 type_: int_type,
1644 value: (&option_values[0] as *const u32).cast::<c_void>(),
1645 },
1646 LV2OptionsOption {
1647 context: 0,
1648 subject: 0,
1649 key: max_key,
1650 size: std::mem::size_of::<u32>() as u32,
1651 type_: int_type,
1652 value: (&option_values[1] as *const u32).cast::<c_void>(),
1653 },
1654 LV2OptionsOption {
1655 context: 0,
1656 subject: 0,
1657 key: nominal_key,
1658 size: std::mem::size_of::<u32>() as u32,
1659 type_: int_type,
1660 value: (&option_values[2] as *const u32).cast::<c_void>(),
1661 },
1662 LV2OptionsOption {
1663 context: 0,
1664 subject: 0,
1665 key: 0,
1666 size: 0,
1667 type_: 0,
1668 value: std::ptr::null(),
1669 },
1670 ];
1671 push_feature(
1672 LV2_OPTIONS__OPTIONS,
1673 options.as_mut_ptr().cast::<c_void>(),
1674 false,
1675 )?;
1676
1677 let flag_feature_data = Box::new(0_u8);
1678
1679 for required in required_features {
1680 let data = match required.as_str() {
1681 LV2_OPTIONS__OPTIONS => options.as_mut_ptr().cast::<c_void>(),
1682 LV2_BUF_SIZE__BOUNDED_BLOCK_LENGTH => (&*flag_feature_data as *const u8)
1683 .cast_mut()
1684 .cast::<c_void>(),
1685 LV2_URID__MAP_URI_TYPO_COMPAT => urid_feature.map_feature().data,
1686 LV2_WORKER__SCHEDULE => worker_feature.feature.data,
1687 _ => (&*flag_feature_data as *const u8)
1688 .cast_mut()
1689 .cast::<c_void>(),
1690 };
1691 push_feature(required, data, false)?;
1692 }
1693
1694 Ok(InstantiateFeatureSet {
1695 _feature_uris: feature_uris,
1696 features,
1697 _worker_feature: worker_feature,
1698 _option_values: option_values,
1699 _options: options,
1700 _flag_feature_data: flag_feature_data,
1701 })
1702}
1703
1704impl WorkerFeature {
1705 fn new() -> Result<Self, String> {
1706 let mut schedule = Box::new(Lv2WorkerSchedule {
1707 handle: std::ptr::null_mut(),
1708 schedule_work: Some(lv2_worker_schedule_work_callback),
1709 });
1710 let state = Box::new(WorkerScheduleState {
1711 jobs: UnsafeMutex::new(vec![]),
1712 responses: UnsafeMutex::new(vec![]),
1713 });
1714 schedule.handle = &*state as *const WorkerScheduleState as *mut c_void;
1715 let uri =
1716 CString::new(LV2_WORKER__SCHEDULE).map_err(|e| format!("Invalid worker URI: {e}"))?;
1717 let feature = LV2Feature {
1718 uri: uri.as_ptr(),
1719 data: (&mut *schedule as *mut Lv2WorkerSchedule).cast::<c_void>(),
1720 };
1721 Ok(Self {
1722 _uri: uri,
1723 _schedule: schedule,
1724 feature,
1725 state,
1726 })
1727 }
1728}
1729
1730unsafe extern "C" fn lv2_worker_schedule_work_callback(
1731 handle: *mut c_void,
1732 size: u32,
1733 data: *const c_void,
1734) -> u32 {
1735 if handle.is_null() || (size > 0 && data.is_null()) {
1736 return LV2_WORKER_ERR_UNKNOWN;
1737 }
1738 let state = unsafe { &*(handle as *const WorkerScheduleState) };
1739 let bytes = if size == 0 {
1740 vec![]
1741 } else {
1742 unsafe { std::slice::from_raw_parts(data.cast::<u8>(), size as usize).to_vec() }
1743 };
1744 state.jobs.lock().push(bytes);
1745 LV2_WORKER_SUCCESS
1746}
1747
1748unsafe extern "C" fn lv2_worker_respond_callback(
1749 handle: *mut c_void,
1750 size: u32,
1751 data: *const c_void,
1752) -> u32 {
1753 if handle.is_null() || (size > 0 && data.is_null()) {
1754 return LV2_WORKER_ERR_UNKNOWN;
1755 }
1756 let state = unsafe { &*(handle as *const WorkerScheduleState) };
1757 let bytes = if size == 0 {
1758 vec![]
1759 } else {
1760 unsafe { std::slice::from_raw_parts(data.cast::<u8>(), size as usize).to_vec() }
1761 };
1762 state.responses.lock().push(bytes);
1763 LV2_WORKER_SUCCESS
1764}
1765
1766fn append_object_long_property(buffer: &mut Vec<u8>, key: LV2Urid, atom_type: LV2Urid, value: i64) {
1767 let prop = LV2AtomPropertyBody {
1768 key,
1769 context: 0,
1770 value: LV2Atom {
1771 size: std::mem::size_of::<i64>() as u32,
1772 mytype: atom_type,
1773 },
1774 };
1775 let prop_size = std::mem::size_of::<LV2AtomPropertyBody>();
1776 let prop_bytes = unsafe {
1777 std::slice::from_raw_parts(
1778 (&prop as *const LV2AtomPropertyBody).cast::<u8>(),
1779 prop_size,
1780 )
1781 };
1782 buffer.extend_from_slice(prop_bytes);
1783 let atom = LV2AtomLong {
1784 atom: LV2Atom {
1785 size: std::mem::size_of::<i64>() as u32,
1786 mytype: atom_type,
1787 },
1788 body: value,
1789 };
1790 let value_bytes = unsafe {
1791 std::slice::from_raw_parts(
1792 (&atom.body as *const i64).cast::<u8>(),
1793 std::mem::size_of::<i64>(),
1794 )
1795 };
1796 buffer.extend_from_slice(value_bytes);
1797 let written = (prop_size + std::mem::size_of::<i64>()) as u32;
1798 let padded = lv2_atom_pad_size(written) as usize;
1799 if padded > (prop_size + std::mem::size_of::<i64>()) {
1800 buffer.resize(
1801 buffer.len() + (padded - prop_size - std::mem::size_of::<i64>()),
1802 0,
1803 );
1804 }
1805}
1806
1807fn append_object_double_property(
1808 buffer: &mut Vec<u8>,
1809 key: LV2Urid,
1810 atom_type: LV2Urid,
1811 value: f64,
1812) {
1813 let prop = LV2AtomPropertyBody {
1814 key,
1815 context: 0,
1816 value: LV2Atom {
1817 size: std::mem::size_of::<f64>() as u32,
1818 mytype: atom_type,
1819 },
1820 };
1821 let prop_size = std::mem::size_of::<LV2AtomPropertyBody>();
1822 let prop_bytes = unsafe {
1823 std::slice::from_raw_parts(
1824 (&prop as *const LV2AtomPropertyBody).cast::<u8>(),
1825 prop_size,
1826 )
1827 };
1828 buffer.extend_from_slice(prop_bytes);
1829 let atom = LV2AtomDouble {
1830 atom: LV2Atom {
1831 size: std::mem::size_of::<f64>() as u32,
1832 mytype: atom_type,
1833 },
1834 body: value,
1835 };
1836 let value_bytes = unsafe {
1837 std::slice::from_raw_parts(
1838 (&atom.body as *const f64).cast::<u8>(),
1839 std::mem::size_of::<f64>(),
1840 )
1841 };
1842 buffer.extend_from_slice(value_bytes);
1843 let written = (prop_size + std::mem::size_of::<f64>()) as u32;
1844 let padded = lv2_atom_pad_size(written) as usize;
1845 if padded > (prop_size + std::mem::size_of::<f64>()) {
1846 buffer.resize(
1847 buffer.len() + (padded - prop_size - std::mem::size_of::<f64>()),
1848 0,
1849 );
1850 }
1851}
1852
1853impl UridMapFeature {
1854 fn new() -> Result<Self, String> {
1855 let mut map = Box::new(LV2UridMap {
1856 handle: std::ptr::null_mut(),
1857 map: urid_map_callback,
1858 });
1859 let mut unmap = Box::new(LV2UridUnmap {
1860 handle: std::ptr::null_mut(),
1861 unmap: urid_unmap_callback,
1862 });
1863 let state = Box::new(Mutex::new(UridMapState {
1864 next_urid: 1,
1865 by_uri: HashMap::new(),
1866 by_urid: HashMap::new(),
1867 }));
1868 map.handle = (&*state as *const Mutex<UridMapState>) as *mut c_void;
1869 unmap.handle = (&*state as *const Mutex<UridMapState>) as *mut c_void;
1870
1871 let map_uri =
1872 CString::new(LV2_URID__MAP).map_err(|e| format!("Invalid URID feature URI: {e}"))?;
1873 let map_feature = LV2Feature {
1874 uri: map_uri.as_ptr(),
1875 data: (&mut *map as *mut LV2UridMap).cast::<c_void>(),
1876 };
1877 let unmap_uri =
1878 CString::new(LV2_URID__UNMAP).map_err(|e| format!("Invalid URID feature URI: {e}"))?;
1879 let unmap_feature = LV2Feature {
1880 uri: unmap_uri.as_ptr(),
1881 data: (&mut *unmap as *mut LV2UridUnmap).cast::<c_void>(),
1882 };
1883
1884 Ok(Self {
1885 _map_uri: map_uri,
1886 _unmap_uri: unmap_uri,
1887 map_feature,
1888 unmap_feature,
1889 _map: map,
1890 _unmap: unmap,
1891 _state: state,
1892 })
1893 }
1894
1895 fn map_feature(&self) -> &LV2Feature {
1896 &self.map_feature
1897 }
1898
1899 fn unmap_feature(&self) -> &LV2Feature {
1900 &self.unmap_feature
1901 }
1902
1903 fn map_uri(&self, uri: &[u8]) -> LV2Urid {
1904 let Ok(uri_str) = std::str::from_utf8(uri) else {
1905 return 0;
1906 };
1907 let uri_str = uri_str.trim_end_matches('\0');
1908 let Ok(mut state) = self._state.lock() else {
1909 return 0;
1910 };
1911 if let Some(existing) = state.by_uri.get(uri_str).copied() {
1912 return existing;
1913 }
1914 let mapped = state.next_urid;
1915 state.next_urid = state.next_urid.saturating_add(1);
1916 state.by_uri.insert(uri_str.to_string(), mapped);
1917 if let Ok(uri_c) = CString::new(uri_str) {
1918 state.by_urid.insert(mapped, uri_c);
1919 }
1920 mapped
1921 }
1922
1923 fn unmap_urid(&self, urid: LV2Urid) -> Option<String> {
1924 let Ok(state) = self._state.lock() else {
1925 return None;
1926 };
1927 state
1928 .by_urid
1929 .get(&urid)
1930 .and_then(|uri| uri.to_str().ok().map(str::to_string))
1931 }
1932}
1933
1934fn default_state_base_dir() -> PathBuf {
1935 std::env::temp_dir().join("maolan-lv2-state")
1936}
1937
1938impl StatePathFeature {
1939 fn new(base_dir: PathBuf) -> Self {
1940 let context = Box::new(Mutex::new(StatePathContext {
1941 base_dir,
1942 copy_counter: 0,
1943 }));
1944 let handle = (&*context as *const Mutex<StatePathContext>) as *mut c_void;
1945
1946 let map = Box::new(Lv2StateMapPath {
1947 handle,
1948 abstract_path: Some(lv2_state_abstract_path_callback),
1949 absolute_path: Some(lv2_state_absolute_path_callback),
1950 });
1951 let make = Box::new(Lv2StateMakePath {
1952 handle,
1953 path: Some(lv2_state_make_path_callback),
1954 });
1955 let free = Box::new(Lv2StateFreePath {
1956 handle,
1957 free_path: Some(lv2_state_free_path_callback),
1958 });
1959
1960 let map_uri = CString::new(LV2_STATE_MAP_PATH_URI).expect("valid LV2 state mapPath URI");
1961 let make_uri = CString::new(LV2_STATE_MAKE_PATH_URI).expect("valid LV2 state makePath URI");
1962 let free_uri = CString::new(LV2_STATE_FREE_PATH_URI).expect("valid LV2 state freePath URI");
1963
1964 let map_feature = LV2Feature {
1965 uri: map_uri.as_ptr(),
1966 data: (&*map as *const Lv2StateMapPath)
1967 .cast_mut()
1968 .cast::<c_void>(),
1969 };
1970 let make_feature = LV2Feature {
1971 uri: make_uri.as_ptr(),
1972 data: (&*make as *const Lv2StateMakePath)
1973 .cast_mut()
1974 .cast::<c_void>(),
1975 };
1976 let free_feature = LV2Feature {
1977 uri: free_uri.as_ptr(),
1978 data: (&*free as *const Lv2StateFreePath)
1979 .cast_mut()
1980 .cast::<c_void>(),
1981 };
1982
1983 let instance = Self {
1984 _map_uri: map_uri,
1985 _make_uri: make_uri,
1986 _free_uri: free_uri,
1987 _map: map,
1988 _make: make,
1989 _free: free,
1990 map_feature,
1991 make_feature,
1992 free_feature,
1993 _context: context,
1994 };
1995 instance.ensure_base_dir();
1996 instance
1997 }
1998
1999 fn ensure_base_dir(&self) {
2000 if let Ok(ctx) = self._context.lock() {
2001 let _ = std::fs::create_dir_all(&ctx.base_dir);
2002 }
2003 }
2004
2005 fn set_base_dir(&self, base_dir: PathBuf) {
2006 if let Ok(mut ctx) = self._context.lock() {
2007 ctx.base_dir = base_dir;
2008 let _ = std::fs::create_dir_all(&ctx.base_dir);
2009 }
2010 }
2011
2012 fn feature_ptrs(&self) -> [*const LV2Feature; 3] {
2013 [
2014 &self.map_feature as *const LV2Feature,
2015 &self.make_feature as *const LV2Feature,
2016 &self.free_feature as *const LV2Feature,
2017 ]
2018 }
2019}
2020
2021extern "C" fn lv2_state_free_path_callback(_handle: *mut c_void, path: *mut c_char) {
2022 if path.is_null() {
2023 return;
2024 }
2025 unsafe {
2026 let _ = CString::from_raw(path);
2027 }
2028}
2029
2030fn state_ctx_from_handle(handle: *mut c_void) -> Option<&'static Mutex<StatePathContext>> {
2031 if handle.is_null() {
2032 return None;
2033 }
2034 Some(unsafe { &*(handle as *const Mutex<StatePathContext>) })
2035}
2036
2037fn copy_into_state_assets(ctx: &mut StatePathContext, src: &Path) -> Option<String> {
2038 let file_name = src.file_name()?.to_str()?.to_string();
2039 let assets_dir = ctx.base_dir.join("assets");
2040 let _ = std::fs::create_dir_all(&assets_dir);
2041 ctx.copy_counter = ctx.copy_counter.saturating_add(1);
2042 let dst_name = format!("{}-{}", ctx.copy_counter, file_name);
2043 let dst = assets_dir.join(&dst_name);
2044 std::fs::copy(src, &dst).ok()?;
2045 Some(format!("assets/{dst_name}"))
2046}
2047
2048extern "C" fn lv2_state_abstract_path_callback(
2049 handle: *mut c_void,
2050 absolute_path: *const c_char,
2051) -> *mut c_char {
2052 let Some(ctx_lock) = state_ctx_from_handle(handle) else {
2053 return std::ptr::null_mut();
2054 };
2055 if absolute_path.is_null() {
2056 return std::ptr::null_mut();
2057 }
2058 let Some(path_str) = (unsafe { CStr::from_ptr(absolute_path) }).to_str().ok() else {
2059 return std::ptr::null_mut();
2060 };
2061 let path = PathBuf::from(path_str);
2062 let mut mapped = None;
2063 if let Ok(mut ctx) = ctx_lock.lock() {
2064 if let Ok(rel) = path.strip_prefix(&ctx.base_dir) {
2065 mapped = Some(rel.to_string_lossy().to_string());
2066 } else if path.exists() {
2067 mapped = copy_into_state_assets(&mut ctx, &path);
2068 }
2069 }
2070 let out = mapped.unwrap_or_else(|| path_str.to_string());
2071 CString::new(out)
2072 .ok()
2073 .map(CString::into_raw)
2074 .unwrap_or(std::ptr::null_mut())
2075}
2076
2077extern "C" fn lv2_state_absolute_path_callback(
2078 handle: *mut c_void,
2079 abstract_path: *const c_char,
2080) -> *mut c_char {
2081 let Some(ctx_lock) = state_ctx_from_handle(handle) else {
2082 return std::ptr::null_mut();
2083 };
2084 if abstract_path.is_null() {
2085 return std::ptr::null_mut();
2086 }
2087 let Some(path_str) = (unsafe { CStr::from_ptr(abstract_path) }).to_str().ok() else {
2088 return std::ptr::null_mut();
2089 };
2090 let output = if Path::new(path_str).is_absolute() {
2091 path_str.to_string()
2092 } else if let Ok(ctx) = ctx_lock.lock() {
2093 ctx.base_dir.join(path_str).to_string_lossy().to_string()
2094 } else {
2095 path_str.to_string()
2096 };
2097 CString::new(output)
2098 .ok()
2099 .map(CString::into_raw)
2100 .unwrap_or(std::ptr::null_mut())
2101}
2102
2103extern "C" fn lv2_state_make_path_callback(
2104 handle: *mut c_void,
2105 requested: *const c_char,
2106) -> *mut c_char {
2107 let Some(ctx_lock) = state_ctx_from_handle(handle) else {
2108 return std::ptr::null_mut();
2109 };
2110
2111 let requested_name = if requested.is_null() {
2112 "state.bin".to_string()
2113 } else {
2114 (unsafe { CStr::from_ptr(requested) })
2115 .to_str()
2116 .ok()
2117 .filter(|s| !s.is_empty())
2118 .map(|s| s.replace("..", "_"))
2119 .unwrap_or_else(|| "state.bin".to_string())
2120 };
2121
2122 let output = if let Ok(mut ctx) = ctx_lock.lock() {
2123 ctx.copy_counter = ctx.copy_counter.saturating_add(1);
2124 let file_name = format!("generated-{}-{}", ctx.copy_counter, requested_name);
2125 let path = ctx.base_dir.join("generated").join(file_name);
2126 if let Some(parent) = path.parent() {
2127 let _ = std::fs::create_dir_all(parent);
2128 }
2129 path.to_string_lossy().to_string()
2130 } else {
2131 requested_name
2132 };
2133
2134 CString::new(output)
2135 .ok()
2136 .map(CString::into_raw)
2137 .unwrap_or(std::ptr::null_mut())
2138}
2139
2140extern "C" fn lv2_state_store_callback(
2141 handle: Lv2StateHandle,
2142 key: u32,
2143 value: *const c_void,
2144 size: usize,
2145 type_: u32,
2146 flags: u32,
2147) -> Lv2StateStatus {
2148 if handle.is_null() || value.is_null() || size == 0 {
2149 return LV2_STATE_STATUS_ERR_NO_PROPERTY;
2150 }
2151 let ctx = unsafe { &mut *(handle as *mut StateSaveContext) };
2152 let bytes = unsafe { std::slice::from_raw_parts(value.cast::<u8>(), size) };
2153 ctx.properties.push(RawStateProperty {
2154 key,
2155 type_,
2156 flags,
2157 value: bytes.to_vec(),
2158 });
2159 LV2_STATE_STATUS_SUCCESS
2160}
2161
2162extern "C" fn lv2_state_retrieve_callback(
2163 handle: Lv2StateHandle,
2164 key: u32,
2165 size: *mut usize,
2166 type_: *mut u32,
2167 flags: *mut u32,
2168) -> *const c_void {
2169 if handle.is_null() {
2170 return std::ptr::null();
2171 }
2172 let ctx = unsafe { &mut *(handle as *mut StateRestoreContext) };
2173 let Some(idx) = ctx.by_key.get(&key).copied() else {
2174 return std::ptr::null();
2175 };
2176 let Some(prop) = ctx.properties.get(idx) else {
2177 return std::ptr::null();
2178 };
2179 if !size.is_null() {
2180 unsafe {
2181 *size = prop.value.len();
2182 }
2183 }
2184 if !type_.is_null() {
2185 unsafe {
2186 *type_ = prop.type_;
2187 }
2188 }
2189 if !flags.is_null() {
2190 unsafe {
2191 *flags = prop.flags;
2192 }
2193 }
2194 prop.value.as_ptr().cast::<c_void>()
2195}
2196
2197fn prepare_empty_atom_sequence(
2198 buffer: &mut [u8],
2199 sequence_urid: LV2Urid,
2200 frame_time_urid: LV2Urid,
2201) {
2202 buffer.fill(0);
2203 if buffer.len() < std::mem::size_of::<LV2AtomSequence>() {
2204 return;
2205 }
2206 let seq = buffer.as_mut_ptr() as *mut LV2AtomSequence;
2207 unsafe {
2208 (*seq).atom.mytype = sequence_urid;
2209 (*seq).atom.size = std::mem::size_of::<LV2AtomSequenceBody>() as u32;
2210 (*seq).body.unit = frame_time_urid;
2211 (*seq).body.pad = 0;
2212 }
2213}
2214
2215fn prepare_output_atom_sequence(
2216 buffer: &mut [u8],
2217 sequence_urid: LV2Urid,
2218 frame_time_urid: LV2Urid,
2219) {
2220 buffer.fill(0);
2221 if buffer.len() < std::mem::size_of::<LV2AtomSequence>() {
2222 return;
2223 }
2224 let seq = buffer.as_mut_ptr() as *mut LV2AtomSequence;
2225 let body_capacity = buffer
2226 .len()
2227 .saturating_sub(std::mem::size_of::<lv2_raw::LV2Atom>()) as u32;
2228 unsafe {
2229 (*seq).atom.mytype = sequence_urid;
2230 (*seq).atom.size = body_capacity;
2231 (*seq).body.unit = frame_time_urid;
2232 (*seq).body.pad = 0;
2233 }
2234}
2235
2236extern "C" fn urid_map_callback(handle: LV2UridMapHandle, uri: *const c_char) -> LV2Urid {
2237 if handle.is_null() || uri.is_null() {
2238 return 0;
2239 }
2240 let Some(uri_str) = unsafe { CStr::from_ptr(uri) }.to_str().ok() else {
2241 return 0;
2242 };
2243
2244 let state_mutex = unsafe { &*(handle as *const Mutex<UridMapState>) };
2245 let Ok(mut state) = state_mutex.lock() else {
2246 return 0;
2247 };
2248
2249 if let Some(existing) = state.by_uri.get(uri_str).copied() {
2250 return existing;
2251 }
2252
2253 let mapped = state.next_urid;
2254 state.next_urid = state.next_urid.saturating_add(1);
2255 state.by_uri.insert(uri_str.to_string(), mapped);
2256 if let Ok(uri_c) = CString::new(uri_str) {
2257 state.by_urid.insert(mapped, uri_c);
2258 }
2259 mapped
2260}
2261
2262extern "C" fn urid_unmap_callback(handle: LV2UridMapHandle, urid: LV2Urid) -> *const c_char {
2263 if handle.is_null() || urid == 0 {
2264 return std::ptr::null();
2265 }
2266 let state_mutex = unsafe { &*(handle as *const Mutex<UridMapState>) };
2267 let Ok(state) = state_mutex.lock() else {
2268 return std::ptr::null();
2269 };
2270 state
2271 .by_urid
2272 .get(&urid)
2273 .map(|uri| uri.as_ptr())
2274 .unwrap_or(std::ptr::null())
2275}
2276
2277fn plugin_port_counts(
2278 plugin: &Plugin,
2279 input_port: &Node,
2280 output_port: &Node,
2281 audio_port: &Node,
2282 atom_port: &Node,
2283 event_port: &Node,
2284 midi_event: &Node,
2285) -> (usize, usize, usize, usize) {
2286 let mut audio_inputs = 0;
2287 let mut audio_outputs = 0;
2288 let mut midi_inputs = 0;
2289 let mut midi_outputs = 0;
2290
2291 for port in plugin.iter_ports() {
2292 let is_input = port.is_a(input_port);
2293 let is_output = port.is_a(output_port);
2294
2295 if port.is_a(audio_port) {
2296 if is_input {
2297 audio_inputs += 1;
2298 }
2299 if is_output {
2300 audio_outputs += 1;
2301 }
2302 }
2303
2304 let is_event_or_atom = port.is_a(atom_port) || port.is_a(event_port);
2305 let is_midi = is_event_or_atom && port.supports_event(midi_event);
2306 if is_midi {
2307 if is_input {
2308 midi_inputs += 1;
2309 }
2310 if is_output {
2311 midi_outputs += 1;
2312 }
2313 }
2314 }
2315
2316 (audio_inputs, audio_outputs, midi_inputs, midi_outputs)
2317}
2318
2319fn count_main_audio_ports(
2320 plugin: &Plugin,
2321 main_group_predicate: &Node,
2322 port_group_predicate: &Node,
2323 audio_port: &Node,
2324 direction_port: &Node,
2325) -> Option<usize> {
2326 let main_groups: Vec<Node> = plugin.value(main_group_predicate).iter().collect();
2327 if main_groups.is_empty() {
2328 return None;
2329 }
2330
2331 let count = plugin
2332 .iter_ports()
2333 .filter(|port| port.is_a(audio_port) && port.is_a(direction_port))
2334 .filter(|port| {
2335 port.get(port_group_predicate)
2336 .is_some_and(|group| main_groups.contains(&group))
2337 })
2338 .count();
2339
2340 Some(count)
2341}
2342
2343fn infer_missing_control_default(
2344 min: f32,
2345 max: f32,
2346 is_toggled: bool,
2347 is_discrete: bool,
2348) -> (f32, &'static str) {
2349 if !min.is_finite() || !max.is_finite() {
2350 return (0.0, "non-finite-range->0");
2351 }
2352 if is_toggled {
2353 let off = if min <= 0.0 && max >= 0.0 { 0.0 } else { min };
2354 return (off.clamp(min, max), "toggled->off");
2355 }
2356 if is_discrete {
2357 if min < 0.0 && max > 0.0 {
2358 return (0.0, "discrete-bipolar->0");
2359 }
2360 return (min, "discrete->min");
2361 }
2362 if min < 0.0 && max > 0.0 {
2363 return (0.0, "bipolar->0");
2364 }
2365 if min <= 1.0 && max >= 1.0 {
2366 return (1.0, "range-has-unity->1");
2367 }
2368 (min + (max - min) * 0.5, "midpoint")
2369}
2370
2371fn lv2_node_to_f32(node: &Node) -> Option<f32> {
2372 if let Some(v) = node.as_float() {
2373 return Some(v);
2374 }
2375 if let Some(v) = node.as_int() {
2376 return Some(v as f32);
2377 }
2378 node.as_bool().map(|v| if v { 1.0 } else { 0.0 })
2379}