1#![allow(
32 clippy::cast_possible_truncation,
35 clippy::cast_ptr_alignment,
38 clippy::borrow_as_ptr,
40 clippy::ref_as_ptr,
41 clippy::ptr_as_ptr
42)]
43
44use truce_rack_core::buffer::AudioBuffer;
45use truce_rack_core::bus::{BusLayout, ChannelConfig};
46use truce_rack_core::editor::{PluginEditor, WindowHandle};
47use truce_rack_core::error::{Error, Result};
48use truce_rack_core::events::{Event, EventBody, EventList, MidiData};
49use truce_rack_core::info::{ParameterInfo, PluginCategory, PluginInfo, PresetInfo};
50use truce_rack_core::plugin::{Plugin, PluginCore, ProcessContext, ProcessStatus};
51use truce_rack_core::scanner::PluginScanner;
52use truce_rack_core::transport::TransportInfo;
53
54use lv2_raw::atom::{
55 LV2Atom, LV2AtomEvent, LV2AtomObjectBody, LV2AtomPropertyBody, LV2AtomSequence,
56 LV2AtomSequenceBody,
57};
58use lv2_raw::core::LV2Feature;
59use lv2_raw::ui::{LV2UIControllerRaw, LV2UIDescriptorRaw, LV2UIHandle, LV2UIWidget};
60use lv2_raw::urid::{LV2Urid, LV2UridMap, LV2UridMapHandle};
61
62use std::collections::HashMap;
63use std::ffi::{CStr, CString, c_char, c_void};
64use std::path::{Path, PathBuf};
65use std::sync::Mutex;
66
67pub const FORMAT: &str = "lv2";
69
70const LV2_URID_MAP_URI: &[u8] = b"http://lv2plug.in/ns/ext/urid#map\0";
71const LV2_ATOM_SEQUENCE_URI: &[u8] = b"http://lv2plug.in/ns/ext/atom#Sequence\0";
72const LV2_UI_PARENT_URI: &[u8] = b"http://lv2plug.in/ns/extensions/ui#parent\0";
73const LV2_UI_RESIZE_URI: &[u8] = b"http://lv2plug.in/ns/extensions/ui#resize\0";
74const LV2_UI_IDLE_INTERFACE_URI: &[u8] = b"http://lv2plug.in/ns/extensions/ui#idleInterface\0";
75const LV2_UI_RESIZE_INTERFACE_URI: &[u8] = LV2_UI_RESIZE_URI;
76
77#[cfg(target_os = "macos")]
78const NATIVE_UI_CLASS_URI: &[u8] = b"http://lv2plug.in/ns/extensions/ui#CocoaUI\0";
79#[cfg(target_os = "windows")]
80const NATIVE_UI_CLASS_URI: &[u8] = b"http://lv2plug.in/ns/extensions/ui#WindowsUI\0";
81#[cfg(all(unix, not(target_os = "macos")))]
82const NATIVE_UI_CLASS_URI: &[u8] = b"http://lv2plug.in/ns/extensions/ui#X11UI\0";
83
84#[derive(Debug, Default)]
86pub struct Lv2Scanner;
87
88impl Lv2Scanner {
89 #[must_use]
91 pub fn new() -> Self {
92 Self
93 }
94}
95
96impl PluginScanner for Lv2Scanner {
97 type Plugin = Lv2Plugin;
98
99 fn scan(&self) -> Result<Vec<PluginInfo>> {
100 let world = unsafe { World::new() }
101 .ok_or_else(|| Error::Other("lilv_world_new returned NULL".into()))?;
102 unsafe { lilv_sys::lilv_world_load_all(world.ptr) };
103 Ok(unsafe { world.collect_plugin_infos() })
104 }
105
106 fn scan_path(&self, _path: &Path) -> Result<Vec<PluginInfo>> {
107 Err(Error::Other(
113 "truce-rack-lv2 path-bounded scan not yet implemented".into(),
114 ))
115 }
116
117 fn load(&self, info: &PluginInfo) -> Result<Self::Plugin> {
118 Lv2Plugin::load_from(info)
119 }
120}
121
122struct World {
130 ptr: *mut lilv_sys::LilvWorld,
131}
132
133impl World {
134 unsafe fn new() -> Option<Self> {
135 let ptr = unsafe { lilv_sys::lilv_world_new() };
136 if ptr.is_null() {
137 None
138 } else {
139 Some(Self { ptr })
140 }
141 }
142
143 unsafe fn collect_plugin_infos(&self) -> Vec<PluginInfo> {
144 SCAN_WORLD.with(|w| w.set(self.ptr));
149 let mut out = Vec::new();
150 let plugins = unsafe { lilv_sys::lilv_world_get_all_plugins(self.ptr) };
151 if plugins.is_null() {
152 SCAN_WORLD.with(|w| w.set(std::ptr::null_mut()));
153 return out;
154 }
155 let audio_uri = unsafe { self.new_uri(lilv_sys::LILV_URI_AUDIO_PORT.as_ptr().cast()) };
156 let input_uri = unsafe { self.new_uri(lilv_sys::LILV_URI_INPUT_PORT.as_ptr().cast()) };
157 let output_uri = unsafe { self.new_uri(lilv_sys::LILV_URI_OUTPUT_PORT.as_ptr().cast()) };
158 let atom_uri = unsafe { self.new_uri(lilv_sys::LILV_URI_ATOM_PORT.as_ptr().cast()) };
159 let mut it = unsafe { lilv_sys::lilv_plugins_begin(plugins) };
160 loop {
161 if unsafe { lilv_sys::lilv_plugins_is_end(plugins, it) } {
162 break;
163 }
164 let plugin = unsafe { lilv_sys::lilv_plugins_get(plugins, it) };
165 if !plugin.is_null() {
166 out.push(unsafe {
167 plugin_to_info(plugin, audio_uri, input_uri, output_uri, atom_uri)
168 });
169 }
170 it = unsafe { lilv_sys::lilv_plugins_next(plugins, it) };
171 }
172 unsafe {
173 lilv_sys::lilv_node_free(audio_uri);
174 lilv_sys::lilv_node_free(input_uri);
175 lilv_sys::lilv_node_free(output_uri);
176 lilv_sys::lilv_node_free(atom_uri);
177 }
178 SCAN_WORLD.with(|w| w.set(std::ptr::null_mut()));
179 out
180 }
181
182 unsafe fn new_uri(&self, uri: *const c_char) -> *mut lilv_sys::LilvNode {
183 unsafe { lilv_sys::lilv_new_uri(self.ptr, uri.cast()) }
184 }
185}
186
187impl Drop for World {
188 fn drop(&mut self) {
189 unsafe { lilv_sys::lilv_world_free(self.ptr) };
190 }
191}
192
193unsafe impl Send for World {}
197
198unsafe fn plugin_to_info(
199 plugin: *const lilv_sys::LilvPlugin,
200 audio_uri: *mut lilv_sys::LilvNode,
201 input_uri: *mut lilv_sys::LilvNode,
202 output_uri: *mut lilv_sys::LilvNode,
203 atom_uri: *mut lilv_sys::LilvNode,
204) -> PluginInfo {
205 let uri_node = unsafe { lilv_sys::lilv_plugin_get_uri(plugin) };
206 let uri = unsafe { node_to_uri_string(uri_node) };
207
208 let name_node = unsafe { lilv_sys::lilv_plugin_get_name(plugin) };
209 let name = unsafe { node_to_string_owned(name_node) };
210 unsafe { lilv_sys::lilv_node_free(name_node) };
211
212 let author_node = unsafe { lilv_sys::lilv_plugin_get_author_name(plugin) };
213 let vendor = if author_node.is_null() {
214 String::new()
215 } else {
216 let v = unsafe { node_to_string_owned(author_node) };
217 unsafe { lilv_sys::lilv_node_free(author_node) };
218 v
219 };
220
221 let mut audio_in = 0u32;
222 let mut audio_out = 0u32;
223 let mut accepts_midi = false;
224 let count = unsafe { lilv_sys::lilv_plugin_get_num_ports(plugin) };
225 for idx in 0..count {
226 let port = unsafe { lilv_sys::lilv_plugin_get_port_by_index(plugin, idx) };
227 if port.is_null() {
228 continue;
229 }
230 let is_input = unsafe { lilv_sys::lilv_port_is_a(plugin, port, input_uri) };
231 if unsafe { lilv_sys::lilv_port_is_a(plugin, port, audio_uri) } {
232 if is_input {
233 audio_in += 1;
234 } else if unsafe { lilv_sys::lilv_port_is_a(plugin, port, output_uri) } {
235 audio_out += 1;
236 }
237 } else if is_input && unsafe { lilv_sys::lilv_port_is_a(plugin, port, atom_uri) } {
238 accepts_midi = true;
243 }
244 }
245 let category = if audio_in == 0 && audio_out > 0 {
246 PluginCategory::Instrument
247 } else {
248 PluginCategory::Effect
249 };
250
251 let has_editor = unsafe { has_native_ui(plugin) };
252
253 PluginInfo {
254 name,
255 vendor,
256 version: 0,
257 category,
258 path: std::path::PathBuf::new(),
259 unique_id: uri,
260 format: FORMAT,
261 has_editor,
262 accepts_midi,
263 }
264}
265
266unsafe fn has_native_ui(plugin: *const lilv_sys::LilvPlugin) -> bool {
269 let uis = unsafe { lilv_sys::lilv_plugin_get_uis(plugin) };
270 if uis.is_null() {
271 return false;
272 }
273 let world_ptr = unsafe { lilv_plugin_world(plugin) };
274 if world_ptr.is_null() {
275 unsafe { lilv_sys::lilv_uis_free(uis) };
276 return false;
277 }
278 let class_node =
279 unsafe { lilv_sys::lilv_new_uri(world_ptr, NATIVE_UI_CLASS_URI.as_ptr().cast()) };
280 let mut found = false;
281 let mut it = unsafe { lilv_sys::lilv_uis_begin(uis) };
282 while !unsafe { lilv_sys::lilv_uis_is_end(uis, it) } {
283 let ui = unsafe { lilv_sys::lilv_uis_get(uis, it) };
284 if !ui.is_null() && unsafe { lilv_sys::lilv_ui_is_a(ui, class_node) } {
285 found = true;
286 break;
287 }
288 it = unsafe { lilv_sys::lilv_uis_next(uis, it) };
289 }
290 unsafe {
291 lilv_sys::lilv_node_free(class_node);
292 lilv_sys::lilv_uis_free(uis);
293 }
294 found
295}
296
297unsafe fn lilv_plugin_world(_plugin: *const lilv_sys::LilvPlugin) -> *mut lilv_sys::LilvWorld {
301 SCAN_WORLD.with(std::cell::Cell::get)
302}
303
304thread_local! {
305 static SCAN_WORLD: std::cell::Cell<*mut lilv_sys::LilvWorld> =
309 const { std::cell::Cell::new(std::ptr::null_mut()) };
310}
311
312unsafe fn node_to_uri_string(node: *const lilv_sys::LilvNode) -> String {
313 if node.is_null() {
314 return String::new();
315 }
316 let p = unsafe { lilv_sys::lilv_node_as_uri(node) };
317 if p.is_null() {
318 return String::new();
319 }
320 unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned()
321}
322
323unsafe fn node_to_string_owned(node: *mut lilv_sys::LilvNode) -> String {
324 if node.is_null() {
325 return String::new();
326 }
327 let p = unsafe { lilv_sys::lilv_node_as_string(node) };
328 if p.is_null() {
329 return String::new();
330 }
331 unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned()
332}
333
334struct UridMap {
341 entries: Mutex<HashMap<CString, LV2Urid>>,
342 next_id: Mutex<LV2Urid>,
343}
344
345impl UridMap {
346 fn new() -> Self {
347 Self {
348 entries: Mutex::new(HashMap::new()),
349 next_id: Mutex::new(1),
351 }
352 }
353
354 fn map_uri(&self, uri: &CStr) -> LV2Urid {
355 let mut entries = self.entries.lock().expect("urid map mutex");
356 if let Some(&id) = entries.get(uri) {
357 return id;
358 }
359 let mut next = self.next_id.lock().expect("urid next mutex");
360 let id = *next;
361 *next += 1;
362 entries.insert(uri.to_owned(), id);
363 id
364 }
365}
366
367extern "C" fn map_callback(handle: LV2UridMapHandle, uri: *const c_char) -> LV2Urid {
368 if handle.is_null() || uri.is_null() {
369 return 0;
370 }
371 let map = unsafe { &*(handle as *const UridMap) };
374 let cstr = unsafe { CStr::from_ptr(uri) };
375 map.map_uri(cstr)
376}
377
378struct Features {
382 urid_map: UridMap,
383 map_struct: LV2UridMap,
387 feature_storage: Vec<LV2Feature>,
390 feature_ptrs: Vec<*const LV2Feature>,
393}
394
395impl Features {
396 fn new() -> Box<Self> {
397 let mut boxed = Box::new(Features {
400 urid_map: UridMap::new(),
401 map_struct: LV2UridMap {
402 handle: std::ptr::null_mut(),
403 map: map_callback,
404 },
405 feature_storage: Vec::with_capacity(1),
406 feature_ptrs: Vec::with_capacity(2),
407 });
408 let urid_map_handle: *mut UridMap = &mut boxed.urid_map as *mut UridMap;
409 boxed.map_struct.handle = urid_map_handle.cast::<c_void>();
410 boxed.feature_storage.push(LV2Feature {
411 uri: LV2_URID_MAP_URI.as_ptr().cast::<c_char>(),
412 data: (&boxed.map_struct) as *const LV2UridMap as *mut c_void,
413 });
414 boxed.feature_ptrs.push(&boxed.feature_storage[0]);
415 boxed.feature_ptrs.push(std::ptr::null());
416 boxed
417 }
418}
419
420#[derive(Debug, Clone)]
428struct UiBundle {
429 ui_uri: CString,
430 bundle_path: PathBuf,
431 binary_path: PathBuf,
432}
433
434struct EditorState {
439 _library: libloading::Library,
442 descriptor: *const LV2UIDescriptorRaw,
443 handle: LV2UIHandle,
444 widget: LV2UIWidget,
445 #[allow(dead_code)]
451 ui_features: Box<UiFeatureStorage>,
452 idle_iface: *const lv2_raw::ui::LV2UIIdleInterface,
456 resize_iface: *const Lv2UiResizeInterface,
460 last_pushed_values: Vec<f32>,
465}
466
467#[repr(C)]
472struct Lv2UiResizeInterface {
473 handle: *mut c_void,
476 ui_resize: extern "C" fn(handle: *mut c_void, width: i32, height: i32) -> i32,
477}
478
479struct UiFeatureStorage {
482 map_struct: LV2UridMap,
485 parent_feature_data: *mut c_void,
488 resize_struct: Lv2UiResizeInterface,
494 resize_notifier: Box<HostResizeNotifier>,
495 feature_storage: Vec<LV2Feature>,
496 feature_ptrs: Vec<*const LV2Feature>,
497}
498
499struct HostResizeNotifier {
503 pending: std::cell::Cell<Option<(i32, i32)>>,
509}
510
511extern "C" fn host_resize_callback(handle: *mut c_void, width: i32, height: i32) -> i32 {
512 if handle.is_null() {
513 return 1;
514 }
515 let notifier = unsafe { &*(handle as *const HostResizeNotifier) };
519 notifier.pending.set(Some((width, height)));
520 0
521}
522
523#[allow(clippy::struct_field_names)]
530struct Controller {
531 control_values_base: *mut f32,
538 control_values_len: usize,
539 control_value_offset: Vec<usize>,
542}
543
544extern "C" fn ui_write_callback(
545 controller: LV2UIControllerRaw,
546 port_index: libc::c_uint,
547 buffer_size: libc::c_uint,
548 port_protocol: libc::c_uint,
549 buffer: *const c_void,
550) {
551 if controller.is_null() || buffer.is_null() {
552 return;
553 }
554 if port_protocol != 0 || buffer_size != 4 {
558 return;
559 }
560 let ctrl = unsafe { &*(controller as *const Controller) };
564 let Some(&off) = ctrl.control_value_offset.get(port_index as usize) else {
565 return;
566 };
567 if off == usize::MAX || off >= ctrl.control_values_len {
568 return;
569 }
570 let value = unsafe { *(buffer as *const f32) };
571 unsafe {
574 *ctrl.control_values_base.add(off) = value;
575 }
576}
577
578impl UiFeatureStorage {
579 #[allow(clippy::unnecessary_box_returns)]
583 fn new(map_handle: *mut UridMap, parent: *mut c_void) -> Box<Self> {
584 let mut boxed = Box::new(UiFeatureStorage {
585 map_struct: LV2UridMap {
586 handle: std::ptr::null_mut(),
587 map: map_callback,
588 },
589 parent_feature_data: parent,
590 resize_struct: Lv2UiResizeInterface {
591 handle: std::ptr::null_mut(),
592 ui_resize: host_resize_callback,
593 },
594 resize_notifier: Box::new(HostResizeNotifier {
595 pending: std::cell::Cell::new(None),
596 }),
597 feature_storage: Vec::with_capacity(3),
598 feature_ptrs: Vec::with_capacity(4),
599 });
600 boxed.map_struct.handle = map_handle.cast::<c_void>();
601 boxed.resize_struct.handle =
602 (&*boxed.resize_notifier) as *const HostResizeNotifier as *mut c_void;
603 boxed.feature_storage.push(LV2Feature {
604 uri: LV2_URID_MAP_URI.as_ptr().cast::<c_char>(),
605 data: (&boxed.map_struct) as *const LV2UridMap as *mut c_void,
606 });
607 boxed.feature_storage.push(LV2Feature {
608 uri: LV2_UI_PARENT_URI.as_ptr().cast::<c_char>(),
609 data: boxed.parent_feature_data,
610 });
611 boxed.feature_storage.push(LV2Feature {
612 uri: LV2_UI_RESIZE_URI.as_ptr().cast::<c_char>(),
613 data: (&boxed.resize_struct) as *const Lv2UiResizeInterface as *mut c_void,
614 });
615 for i in 0..boxed.feature_storage.len() {
616 boxed.feature_ptrs.push(&boxed.feature_storage[i]);
617 }
618 boxed.feature_ptrs.push(std::ptr::null());
619 boxed
620 }
621}
622
623unsafe fn discover_ui(
626 world: *mut lilv_sys::LilvWorld,
627 plugin: *const lilv_sys::LilvPlugin,
628) -> Option<UiBundle> {
629 let uis = unsafe { lilv_sys::lilv_plugin_get_uis(plugin) };
630 if uis.is_null() {
631 return None;
632 }
633 let class_node = unsafe { lilv_sys::lilv_new_uri(world, NATIVE_UI_CLASS_URI.as_ptr().cast()) };
634 let mut chosen: Option<UiBundle> = None;
635 let mut it = unsafe { lilv_sys::lilv_uis_begin(uis) };
636 while !unsafe { lilv_sys::lilv_uis_is_end(uis, it) } {
637 let ui = unsafe { lilv_sys::lilv_uis_get(uis, it) };
638 if !ui.is_null() && unsafe { lilv_sys::lilv_ui_is_a(ui, class_node) } {
639 let uri_str = unsafe {
640 let n = lilv_sys::lilv_ui_get_uri(ui);
641 node_to_uri_string(n)
642 };
643 let bundle_path = unsafe { node_uri_to_path(lilv_sys::lilv_ui_get_bundle_uri(ui)) };
644 let binary_path = unsafe { node_uri_to_path(lilv_sys::lilv_ui_get_binary_uri(ui)) };
645 if let (Ok(uri_c), Some(bundle), Some(binary)) =
646 (CString::new(uri_str), bundle_path, binary_path)
647 {
648 chosen = Some(UiBundle {
649 ui_uri: uri_c,
650 bundle_path: bundle,
651 binary_path: binary,
652 });
653 break;
654 }
655 }
656 it = unsafe { lilv_sys::lilv_uis_next(uis, it) };
657 }
658 unsafe {
659 lilv_sys::lilv_node_free(class_node);
660 lilv_sys::lilv_uis_free(uis);
661 }
662 chosen
663}
664
665unsafe fn node_uri_to_path(node: *const lilv_sys::LilvNode) -> Option<PathBuf> {
666 if node.is_null() {
667 return None;
668 }
669 let uri = unsafe { lilv_sys::lilv_node_as_uri(node) };
670 if uri.is_null() {
671 return None;
672 }
673 let raw = unsafe { lilv_sys::lilv_file_uri_parse(uri, std::ptr::null_mut()) };
674 if raw.is_null() {
675 return None;
676 }
677 let s = unsafe { CStr::from_ptr(raw) }
678 .to_string_lossy()
679 .into_owned();
680 unsafe { lilv_sys::lilv_free(raw.cast()) };
681 Some(PathBuf::from(s))
682}
683
684#[derive(Debug, Clone, Copy)]
690struct PortInfo {
691 index: u32,
692 kind: PortKind,
693 is_input: bool,
694}
695
696#[derive(Debug, Clone, Copy)]
697enum PortKind {
698 Audio,
699 Control { default: f32 },
700 AtomSequence,
701 Other,
702}
703
704#[derive(Clone, Copy)]
708struct TimeUrids {
709 position: LV2Urid,
710 frame: LV2Urid,
711 speed: LV2Urid,
712 bar: LV2Urid,
713 bar_beat: LV2Urid,
714 beats_per_bar: LV2Urid,
715 beat_unit: LV2Urid,
716 bpm: LV2Urid,
717 atom_long: LV2Urid,
718 atom_float: LV2Urid,
719 atom_int: LV2Urid,
720 atom_object: LV2Urid,
721}
722
723pub struct Lv2Plugin {
729 info: PluginInfo,
730 layouts: Vec<BusLayout>,
731 active_layout: Option<BusLayout>,
732
733 #[allow(dead_code)]
737 world: Box<World>,
738 plugin: *const lilv_sys::LilvPlugin,
739 instance: *mut lilv_sys::LilvInstance,
740
741 sample_rate: f64,
742
743 ports: Vec<PortInfo>,
744 control_values: Vec<f32>,
748 control_value_offset: Vec<usize>,
750
751 midi_in_buf: Vec<u8>,
755 midi_out_buf: Vec<u8>,
759
760 features: Box<Features>,
761 midi_event_urid: LV2Urid,
762 atom_sequence_urid: LV2Urid,
763 time_urids: TimeUrids,
764
765 ui_bundle: Option<UiBundle>,
769 editor: Option<EditorState>,
771 controller: Option<Box<Controller>>,
775}
776
777unsafe impl Send for Lv2Plugin {}
780
781impl Lv2Plugin {
782 fn load_from(info: &PluginInfo) -> Result<Self> {
783 let world = Box::new(unsafe { World::new() }.ok_or_else(|| Error::LoadFailed {
784 path: info.path.clone(),
785 reason: "lilv_world_new returned NULL".into(),
786 })?);
787 unsafe { lilv_sys::lilv_world_load_all(world.ptr) };
788
789 let uri = CString::new(info.unique_id.clone()).map_err(|_| Error::LoadFailed {
790 path: info.path.clone(),
791 reason: format!("lv2 unique_id contains NUL: {:?}", info.unique_id),
792 })?;
793 let uri_node = unsafe { lilv_sys::lilv_new_uri(world.ptr, uri.as_ptr()) };
794 if uri_node.is_null() {
795 return Err(Error::LoadFailed {
796 path: info.path.clone(),
797 reason: format!("lilv_new_uri failed for {:?}", info.unique_id),
798 });
799 }
800 let plugins = unsafe { lilv_sys::lilv_world_get_all_plugins(world.ptr) };
801 let plugin = unsafe { lilv_sys::lilv_plugins_get_by_uri(plugins, uri_node) };
802 unsafe { lilv_sys::lilv_node_free(uri_node) };
803 if plugin.is_null() {
804 return Err(Error::LoadFailed {
805 path: info.path.clone(),
806 reason: format!("no LV2 plugin matching URI {:?}", info.unique_id),
807 });
808 }
809
810 let (ports, audio_in_count, audio_out_count, control_value_offset, control_values) =
813 unsafe { classify_ports(world.ptr, plugin) };
814
815 let features = Features::new();
817 let midi_event_urid = features
818 .urid_map
819 .map_uri(unsafe { CStr::from_ptr(lilv_sys::LILV_URI_MIDI_EVENT.as_ptr().cast()) });
820 let atom_sequence_urid = features
821 .urid_map
822 .map_uri(unsafe { CStr::from_ptr(LV2_ATOM_SEQUENCE_URI.as_ptr().cast()) });
823
824 let map_uri =
828 |uri: &[u8]| features.urid_map.map_uri(unsafe { CStr::from_ptr(uri.as_ptr().cast()) });
829 let time_urids = TimeUrids {
830 position: map_uri(lv2_raw::time::LV2_TIME__POSITION),
831 frame: map_uri(lv2_raw::time::LV2_TIME__FRAME),
832 speed: map_uri(lv2_raw::time::LV2_TIME__SPEED),
833 bar: map_uri(lv2_raw::time::LV2_TIME__BAR),
834 bar_beat: map_uri(lv2_raw::time::LV2_TIME__BARBEAT),
835 beats_per_bar: map_uri(lv2_raw::time::LV2_TIME__BEATSPERBAR),
836 beat_unit: map_uri(lv2_raw::time::LV2_TIME__BEATUNIT),
837 bpm: map_uri(lv2_raw::time::LV2_TIME__BEATSPERMINUTE),
838 atom_long: map_uri(lv2_raw::atom::LV2_ATOM__LONG),
839 atom_float: map_uri(lv2_raw::atom::LV2_ATOM__FLOAT),
840 atom_int: map_uri(lv2_raw::atom::LV2_ATOM__INT),
841 atom_object: map_uri(lv2_raw::atom::LV2_ATOM__OBJECT),
842 };
843
844 let layout = audio_layout(audio_in_count, audio_out_count);
845
846 let ui_bundle = unsafe { discover_ui(world.ptr, plugin) };
847 let mut info = info.clone();
848 info.has_editor = ui_bundle.is_some();
849
850 Ok(Self {
851 info,
852 layouts: vec![layout],
853 active_layout: None,
854 world,
855 plugin,
856 instance: std::ptr::null_mut(),
857 sample_rate: 0.0,
858 ports,
859 control_values,
860 control_value_offset,
861 midi_in_buf: Vec::new(),
862 midi_out_buf: Vec::new(),
863 features,
864 midi_event_urid,
865 atom_sequence_urid,
866 time_urids,
867 ui_bundle,
868 editor: None,
869 controller: None,
870 })
871 }
872
873 fn ensure_controller(&mut self) {
879 if self.controller.is_some() {
880 return;
881 }
882 self.controller = Some(Box::new(Controller {
883 control_values_base: self.control_values.as_mut_ptr(),
884 control_values_len: self.control_values.len(),
885 control_value_offset: self.control_value_offset.clone(),
886 }));
887 }
888
889 fn build_midi_sequence(&mut self, events: &EventList, transport: Option<TransportInfo>) {
890 let header_size = std::mem::size_of::<LV2AtomSequence>();
891 let cap = self.midi_in_buf.len();
892 if cap < header_size {
893 return;
894 }
895 let buf = self.midi_in_buf.as_mut_ptr();
896
897 let body_size = std::mem::size_of::<LV2AtomSequenceBody>();
900 let seq = unsafe { &mut *buf.cast::<LV2AtomSequence>() };
901 seq.atom = LV2Atom {
902 size: body_size as u32,
903 mytype: self.atom_sequence_urid,
904 };
905 seq.body = LV2AtomSequenceBody { unit: 0, pad: 0 };
906
907 let mut write_off = header_size;
908
909 if let Some(t) = transport {
913 write_off = unsafe { write_time_position(&self.time_urids, buf, write_off, cap, &t) };
914 }
915
916 let event_header = std::mem::size_of::<LV2AtomEvent>();
917 for ev in events {
918 let Some((status, d1, d2, len)) = midi_bytes(&ev.body) else {
919 continue;
920 };
921 let total = event_header + len;
922 let padded = (total + 7) & !7; if write_off + padded > cap {
924 break;
925 }
926 let event_ptr = unsafe { buf.add(write_off) }.cast::<LV2AtomEvent>();
927 unsafe {
928 (*event_ptr).time_in_frames = i64::from(ev.sample_offset);
929 (*event_ptr).body = LV2Atom {
930 size: len as u32,
931 mytype: self.midi_event_urid,
932 };
933 let data_ptr = (event_ptr as *mut u8).add(event_header);
934 if len >= 1 {
935 *data_ptr = status;
936 }
937 if len >= 2 {
938 *data_ptr.add(1) = d1;
939 }
940 if len >= 3 {
941 *data_ptr.add(2) = d2;
942 }
943 }
944 write_off += padded;
945 }
946 seq.atom.size = (write_off - std::mem::size_of::<LV2Atom>()) as u32;
948 }
949
950 fn prep_midi_out(&mut self) {
951 if self.midi_out_buf.len() < std::mem::size_of::<LV2AtomSequence>() {
954 return;
955 }
956 let buf = self.midi_out_buf.as_mut_ptr();
957 let seq = unsafe { &mut *buf.cast::<LV2AtomSequence>() };
958 seq.atom = LV2Atom {
959 size: std::mem::size_of::<LV2AtomSequenceBody>() as u32,
960 mytype: self.atom_sequence_urid,
961 };
962 seq.body = LV2AtomSequenceBody { unit: 0, pad: 0 };
963 }
964}
965
966const TIME_POSITION_MAX_BYTES: usize =
970 std::mem::size_of::<LV2AtomEvent>() + std::mem::size_of::<LV2AtomObjectBody>() + 8 * 32;
971
972unsafe fn write_property(buf: *mut u8, off: usize, key: LV2Urid, value_type: LV2Urid, value: &[u8]) -> usize {
980 let prop = unsafe { &mut *buf.add(off).cast::<LV2AtomPropertyBody>() };
981 prop.key = key;
982 prop.context = 0;
983 prop.value = LV2Atom {
984 size: u32::try_from(value.len()).unwrap_or(0),
985 mytype: value_type,
986 };
987 let data = unsafe { buf.add(off + std::mem::size_of::<LV2AtomPropertyBody>()) };
988 unsafe { std::ptr::copy_nonoverlapping(value.as_ptr(), data, value.len()) };
989 let total = std::mem::size_of::<LV2AtomPropertyBody>() + value.len();
990 off + ((total + 7) & !7)
991}
992
993#[allow(
1001 clippy::cast_possible_truncation,
1002 clippy::cast_possible_wrap,
1003 clippy::cast_precision_loss
1004)]
1005unsafe fn write_time_position(
1006 urids: &TimeUrids,
1007 buf: *mut u8,
1008 base: usize,
1009 cap: usize,
1010 t: &TransportInfo,
1011) -> usize {
1012 if base + TIME_POSITION_MAX_BYTES > cap {
1013 return base;
1014 }
1015 let event_header = std::mem::size_of::<LV2AtomEvent>();
1016 let obj_body_off = base + event_header;
1017
1018 let obj_body = unsafe { &mut *buf.add(obj_body_off).cast::<LV2AtomObjectBody>() };
1020 obj_body.id = 0;
1021 obj_body.otype = urids.position;
1022
1023 let mut off = obj_body_off + std::mem::size_of::<LV2AtomObjectBody>();
1024
1025 let speed: f32 = if t.playing { 1.0 } else { 0.0 };
1027 off = unsafe { write_property(buf, off, urids.speed, urids.atom_float, &speed.to_ne_bytes()) };
1028
1029 if let Some(samples) = t.song_position_samples {
1030 off = unsafe { write_property(buf, off, urids.frame, urids.atom_long, &samples.to_ne_bytes()) };
1031 }
1032 if let Some(tempo) = t.tempo_bpm {
1033 let bpm = tempo as f32;
1034 off = unsafe { write_property(buf, off, urids.bpm, urids.atom_float, &bpm.to_ne_bytes()) };
1035 }
1036 if let Some((num, den)) = t.time_signature {
1037 let beats_per_bar = num as f32;
1038 off = unsafe {
1039 write_property(buf, off, urids.beats_per_bar, urids.atom_float, &beats_per_bar.to_ne_bytes())
1040 };
1041 let beat_unit = den as i32;
1042 off = unsafe {
1043 write_property(buf, off, urids.beat_unit, urids.atom_int, &beat_unit.to_ne_bytes())
1044 };
1045
1046 let beats_per_bar_qn = f64::from(num) * 4.0 / f64::from(den.max(1));
1048 if let Some(bar_start) = t.bar_start_beats {
1049 let bar = (bar_start / beats_per_bar_qn.max(f64::EPSILON)).round() as i64;
1050 off = unsafe { write_property(buf, off, urids.bar, urids.atom_long, &bar.to_ne_bytes()) };
1051
1052 if let Some(beats) = t.song_position_beats {
1053 let bar_beat = ((beats - bar_start) * f64::from(den) / 4.0) as f32;
1056 off = unsafe {
1057 write_property(buf, off, urids.bar_beat, urids.atom_float, &bar_beat.to_ne_bytes())
1058 };
1059 }
1060 }
1061 }
1062
1063 let object_body_size = off - obj_body_off;
1065 let event = unsafe { &mut *buf.add(base).cast::<LV2AtomEvent>() };
1066 event.time_in_frames = 0;
1067 event.body = LV2Atom {
1068 size: object_body_size as u32,
1069 mytype: urids.atom_object,
1070 };
1071
1072 (off + 7) & !7
1074}
1075
1076fn audio_layout(in_count: usize, out_count: usize) -> BusLayout {
1077 let mut layout = BusLayout::new();
1078 if in_count > 0 {
1079 layout.inputs.push(truce_rack_core::bus::Bus::main(
1080 "Input",
1081 channel_config(in_count),
1082 ));
1083 }
1084 if out_count > 0 {
1085 layout.outputs.push(truce_rack_core::bus::Bus::main(
1086 "Output",
1087 channel_config(out_count),
1088 ));
1089 }
1090 layout
1091}
1092
1093fn channel_config(n: usize) -> ChannelConfig {
1094 match n {
1095 1 => ChannelConfig::Mono,
1096 2 => ChannelConfig::Stereo,
1097 6 => ChannelConfig::Surround5_1,
1098 8 => ChannelConfig::Surround7_1,
1099 n => ChannelConfig::Discrete(u32::try_from(n).unwrap_or(0)),
1100 }
1101}
1102
1103unsafe fn classify_ports(
1104 world: *mut lilv_sys::LilvWorld,
1105 plugin: *const lilv_sys::LilvPlugin,
1106) -> (Vec<PortInfo>, usize, usize, Vec<usize>, Vec<f32>) {
1107 let count = unsafe { lilv_sys::lilv_plugin_get_num_ports(plugin) };
1108 let audio_uri =
1109 unsafe { lilv_sys::lilv_new_uri(world, lilv_sys::LILV_URI_AUDIO_PORT.as_ptr().cast()) };
1110 let control_uri =
1111 unsafe { lilv_sys::lilv_new_uri(world, lilv_sys::LILV_URI_CONTROL_PORT.as_ptr().cast()) };
1112 let atom_uri =
1113 unsafe { lilv_sys::lilv_new_uri(world, lilv_sys::LILV_URI_ATOM_PORT.as_ptr().cast()) };
1114 let input_uri =
1115 unsafe { lilv_sys::lilv_new_uri(world, lilv_sys::LILV_URI_INPUT_PORT.as_ptr().cast()) };
1116 let output_uri =
1117 unsafe { lilv_sys::lilv_new_uri(world, lilv_sys::LILV_URI_OUTPUT_PORT.as_ptr().cast()) };
1118
1119 let mut ports = Vec::with_capacity(count as usize);
1120 let mut audio_in_count: usize = 0;
1121 let mut audio_out_count: usize = 0;
1122 let mut control_value_offset = Vec::with_capacity(count as usize);
1123 let mut control_values = Vec::new();
1124
1125 let mut defaults: Vec<f32> = vec![0.0; count as usize];
1126 unsafe {
1127 let defaults_ptr = defaults.as_mut_ptr();
1129 lilv_sys::lilv_plugin_get_port_ranges_float(
1130 plugin,
1131 std::ptr::null_mut(), std::ptr::null_mut(), defaults_ptr,
1134 );
1135 }
1136
1137 for idx in 0..count {
1138 let port = unsafe { lilv_sys::lilv_plugin_get_port_by_index(plugin, idx) };
1139 if port.is_null() {
1140 ports.push(PortInfo {
1141 index: idx,
1142 kind: PortKind::Other,
1143 is_input: false,
1144 });
1145 control_value_offset.push(usize::MAX);
1146 continue;
1147 }
1148 let is_input = unsafe { lilv_sys::lilv_port_is_a(plugin, port, input_uri) };
1149 let is_output = unsafe { lilv_sys::lilv_port_is_a(plugin, port, output_uri) };
1150 let kind = if unsafe { lilv_sys::lilv_port_is_a(plugin, port, audio_uri) } {
1151 if is_input {
1152 audio_in_count += 1;
1153 } else if is_output {
1154 audio_out_count += 1;
1155 }
1156 PortKind::Audio
1157 } else if unsafe { lilv_sys::lilv_port_is_a(plugin, port, control_uri) } {
1158 let default = if defaults[idx as usize].is_finite() {
1159 defaults[idx as usize]
1160 } else {
1161 0.0
1162 };
1163 PortKind::Control { default }
1164 } else if unsafe { lilv_sys::lilv_port_is_a(plugin, port, atom_uri) } {
1165 PortKind::AtomSequence
1171 } else {
1172 PortKind::Other
1173 };
1174 if let PortKind::Control { default } = kind {
1175 control_value_offset.push(control_values.len());
1176 control_values.push(default);
1177 } else {
1178 control_value_offset.push(usize::MAX);
1179 }
1180 ports.push(PortInfo {
1181 index: idx,
1182 kind,
1183 is_input,
1184 });
1185 }
1186
1187 unsafe {
1188 lilv_sys::lilv_node_free(audio_uri);
1189 lilv_sys::lilv_node_free(control_uri);
1190 lilv_sys::lilv_node_free(atom_uri);
1191 lilv_sys::lilv_node_free(input_uri);
1192 lilv_sys::lilv_node_free(output_uri);
1193 }
1194
1195 (
1196 ports,
1197 audio_in_count,
1198 audio_out_count,
1199 control_value_offset,
1200 control_values,
1201 )
1202}
1203
1204fn midi_bytes(body: &EventBody) -> Option<(u8, u8, u8, usize)> {
1208 match body {
1209 EventBody::Midi(MidiData::NoteOn {
1210 channel,
1211 note,
1212 velocity,
1213 }) => Some((0x90 | (channel & 0x0F), note & 0x7F, velocity & 0x7F, 3)),
1214 EventBody::Midi(MidiData::NoteOff {
1215 channel,
1216 note,
1217 velocity,
1218 }) => Some((0x80 | (channel & 0x0F), note & 0x7F, velocity & 0x7F, 3)),
1219 EventBody::Midi(MidiData::PolyAftertouch {
1220 channel,
1221 note,
1222 pressure,
1223 }) => Some((0xA0 | (channel & 0x0F), note & 0x7F, pressure & 0x7F, 3)),
1224 EventBody::Midi(MidiData::ControlChange {
1225 channel,
1226 controller,
1227 value,
1228 }) => Some((0xB0 | (channel & 0x0F), controller & 0x7F, value & 0x7F, 3)),
1229 EventBody::Midi(MidiData::ProgramChange { channel, program }) => {
1230 Some((0xC0 | (channel & 0x0F), program & 0x7F, 0, 2))
1231 }
1232 EventBody::Midi(MidiData::ChannelAftertouch { channel, pressure }) => {
1233 Some((0xD0 | (channel & 0x0F), pressure & 0x7F, 0, 2))
1234 }
1235 EventBody::Midi(MidiData::PitchBend { channel, value }) => Some((
1236 0xE0 | (channel & 0x0F),
1237 (value & 0x7F) as u8,
1238 ((value >> 7) & 0x7F) as u8,
1239 3,
1240 )),
1241 EventBody::Midi(MidiData::Raw { len, data }) if *len >= 1 && *len <= 3 => Some((
1242 data[0],
1243 *data.get(1).unwrap_or(&0),
1244 *data.get(2).unwrap_or(&0),
1245 *len as usize,
1246 )),
1247 _ => None,
1248 }
1249}
1250
1251#[allow(dead_code)]
1253fn _event_marker(_: &Event) {}
1254
1255impl Drop for Lv2Plugin {
1256 fn drop(&mut self) {
1257 self.close_editor();
1261 if !self.instance.is_null() {
1262 unsafe {
1263 lilv_sys::lilv_instance_deactivate(self.instance);
1264 lilv_sys::lilv_instance_free(self.instance);
1265 }
1266 self.instance = std::ptr::null_mut();
1267 }
1268 }
1270}
1271
1272impl Lv2Plugin {
1273 fn close_editor(&mut self) {
1276 if let Some(state) = self.editor.take() {
1277 unsafe {
1278 ((*state.descriptor).cleanup)(state.handle);
1279 }
1280 drop(state);
1282 }
1283 }
1284}
1285
1286impl PluginCore for Lv2Plugin {
1287 fn info(&self) -> &PluginInfo {
1288 &self.info
1289 }
1290 fn active_layout(&self) -> Option<&BusLayout> {
1291 self.active_layout.as_ref()
1292 }
1293 fn supported_layouts(&self) -> &[BusLayout] {
1294 &self.layouts
1295 }
1296 fn parameter_count(&self) -> usize {
1297 0
1302 }
1303 fn parameter_info(&self, index: usize) -> Result<ParameterInfo> {
1304 Err(Error::InvalidParameter(index))
1305 }
1306 fn parameter_value(&self, index: usize) -> Result<f64> {
1307 Err(Error::InvalidParameter(index))
1308 }
1309 fn parameter_value_string(&self, index: usize, _value: f64) -> Result<String> {
1310 Err(Error::InvalidParameter(index))
1311 }
1312 fn set_parameter(&mut self, index: usize, _value: f64) -> Result<()> {
1313 Err(Error::InvalidParameter(index))
1314 }
1315 fn preset_count(&self) -> usize {
1316 0
1317 }
1318 fn preset_info(&self, index: usize) -> Result<PresetInfo> {
1319 Err(Error::InvalidParameter(index))
1320 }
1321 fn load_preset(&mut self, _preset_number: i32) -> Result<()> {
1322 Err(Error::Other("lv2 preset loading not yet wired".into()))
1323 }
1324 fn save_state(&self) -> Result<Vec<u8>> {
1325 Err(Error::Other("lv2 state save not yet wired".into()))
1326 }
1327 fn load_state(&mut self, _bytes: &[u8]) -> Result<()> {
1328 Err(Error::Other("lv2 state load not yet wired".into()))
1329 }
1330
1331 fn activate(
1332 &mut self,
1333 layout: BusLayout,
1334 sample_rate: f64,
1335 max_block_size: usize,
1336 ) -> Result<()> {
1337 let needs_reinstantiate =
1341 self.instance.is_null() || (self.sample_rate - sample_rate).abs() > f64::EPSILON;
1342 if needs_reinstantiate {
1343 if !self.instance.is_null() {
1344 unsafe {
1345 lilv_sys::lilv_instance_deactivate(self.instance);
1346 lilv_sys::lilv_instance_free(self.instance);
1347 }
1348 self.instance = std::ptr::null_mut();
1349 }
1350 let inst = unsafe {
1351 lilv_sys::lilv_plugin_instantiate(
1352 self.plugin,
1353 sample_rate,
1354 self.features.feature_ptrs.as_ptr(),
1355 )
1356 };
1357 if inst.is_null() {
1358 return Err(Error::Other("lilv_plugin_instantiate returned NULL".into()));
1359 }
1360 self.instance = inst;
1361 self.sample_rate = sample_rate;
1362 }
1363
1364 let event_slot = std::mem::size_of::<LV2AtomEvent>() + 8;
1367 let cap = std::mem::size_of::<LV2AtomSequence>()
1368 + TIME_POSITION_MAX_BYTES
1369 + max_block_size * event_slot;
1370 self.midi_in_buf.resize(cap.max(64), 0);
1371 self.midi_out_buf.resize(cap.max(64), 0);
1372
1373 unsafe { lilv_sys::lilv_instance_activate(self.instance) };
1374 self.active_layout = Some(layout);
1375 Ok(())
1376 }
1377 fn deactivate(&mut self) {
1378 if !self.instance.is_null() {
1379 unsafe { lilv_sys::lilv_instance_deactivate(self.instance) };
1380 }
1381 self.active_layout = None;
1382 }
1383 fn is_active(&self) -> bool {
1384 self.active_layout.is_some()
1385 }
1386
1387 fn editor(&mut self) -> Option<&mut dyn PluginEditor> {
1388 if self.ui_bundle.is_some() {
1389 Some(self)
1390 } else {
1391 None
1392 }
1393 }
1394}
1395
1396impl PluginEditor for Lv2Plugin {
1397 #[allow(clippy::items_after_statements)]
1401 fn open(&mut self, parent: WindowHandle, _scale: f64) -> Result<()> {
1402 let bundle = self
1403 .ui_bundle
1404 .as_ref()
1405 .ok_or_else(|| Error::Other("lv2 plugin has no UI".into()))?
1406 .clone();
1407 if self.editor.is_some() {
1408 return Ok(());
1409 }
1410
1411 let parent_ptr: *mut c_void = match parent {
1412 WindowHandle::NSView(p) | WindowHandle::HWND(p) => p,
1413 WindowHandle::X11(xid) => xid as usize as *mut c_void,
1418 };
1419
1420 let lib = unsafe { libloading::Library::new(&bundle.binary_path) }.map_err(|e| {
1423 Error::Other(format!(
1424 "lv2 ui dlopen {}: {}",
1425 bundle.binary_path.display(),
1426 e
1427 ))
1428 })?;
1429 type DescFn = unsafe extern "C" fn(u32) -> *const LV2UIDescriptorRaw;
1430 let descriptor = unsafe {
1431 let sym: libloading::Symbol<DescFn> = lib
1432 .get(b"lv2ui_descriptor")
1433 .map_err(|e| Error::Other(format!("lv2 ui_descriptor symbol: {e}")))?;
1434 let mut chosen: *const LV2UIDescriptorRaw = std::ptr::null();
1438 for idx in 0u32..64 {
1439 let d = sym(idx);
1440 if d.is_null() {
1441 break;
1442 }
1443 let uri = (*d).uri;
1444 if !uri.is_null() && CStr::from_ptr(uri) == bundle.ui_uri.as_c_str() {
1445 chosen = d;
1446 break;
1447 }
1448 }
1449 chosen
1450 };
1451 if descriptor.is_null() {
1452 return Err(Error::Other(format!(
1453 "no lv2ui_descriptor matching {:?}",
1454 bundle.ui_uri
1455 )));
1456 }
1457
1458 let map_handle: *mut UridMap = (&mut self.features.urid_map) as *mut UridMap;
1462 let ui_features = UiFeatureStorage::new(map_handle, parent_ptr);
1463
1464 self.ensure_controller();
1468 let controller_ptr: *const Controller = self
1469 .controller
1470 .as_deref()
1471 .map_or(std::ptr::null(), |c| c as *const Controller);
1472
1473 let plugin_uri = CString::new(self.info.unique_id.clone())
1474 .map_err(|_| Error::Other("lv2 plugin uri contains NUL".into()))?;
1475 let bundle_path_c = path_to_cstring_with_trailing_sep(&bundle.bundle_path)?;
1476
1477 let mut widget: LV2UIWidget = std::ptr::null_mut();
1478 let handle = unsafe {
1479 ((*descriptor).instantiate_raw)(
1480 descriptor,
1481 plugin_uri.as_ptr(),
1482 bundle_path_c.as_ptr(),
1483 Some(ui_write_callback),
1484 controller_ptr.cast::<c_void>(),
1485 &raw mut widget,
1486 ui_features.feature_ptrs.as_ptr(),
1487 )
1488 };
1489 if handle.is_null() {
1490 return Err(Error::Other("lv2 ui instantiate returned NULL".into()));
1491 }
1492
1493 let idle_iface = unsafe {
1503 if let Some(ext) = (*descriptor).extension_data {
1504 ext(LV2_UI_IDLE_INTERFACE_URI.as_ptr().cast::<c_char>())
1505 as *const lv2_raw::ui::LV2UIIdleInterface
1506 } else {
1507 std::ptr::null()
1508 }
1509 };
1510 let resize_iface = unsafe {
1511 if let Some(ext) = (*descriptor).extension_data {
1512 ext(LV2_UI_RESIZE_INTERFACE_URI.as_ptr().cast::<c_char>())
1513 as *const Lv2UiResizeInterface
1514 } else {
1515 std::ptr::null()
1516 }
1517 };
1518
1519 let last_pushed_values = self.control_values.clone();
1520
1521 self.editor = Some(EditorState {
1522 _library: lib,
1523 descriptor,
1524 handle,
1525 widget,
1526 ui_features,
1527 idle_iface,
1528 resize_iface,
1529 last_pushed_values,
1530 });
1531 Ok(())
1532 }
1533
1534 fn close(&mut self) {
1535 self.close_editor();
1536 }
1537
1538 fn is_open(&self) -> bool {
1539 self.editor.is_some()
1540 }
1541
1542 fn size(&self) -> Option<(u32, u32)> {
1543 let state = self.editor.as_ref()?;
1544 native_widget_size(state.widget)
1545 }
1546
1547 fn is_resizable(&self) -> bool {
1548 self.editor
1551 .as_ref()
1552 .is_some_and(|e| !e.resize_iface.is_null())
1553 }
1554
1555 fn set_size(&mut self, width: u32, height: u32) -> Option<(u32, u32)> {
1556 let state = self.editor.as_ref()?;
1557 if state.resize_iface.is_null() {
1558 return None;
1559 }
1560 let r = unsafe {
1565 ((*state.resize_iface).ui_resize)(
1566 (*state.resize_iface).handle,
1567 i32::try_from(width).unwrap_or(i32::MAX),
1568 i32::try_from(height).unwrap_or(i32::MAX),
1569 )
1570 };
1571 if r == 0 { Some((width, height)) } else { None }
1572 }
1573
1574 fn show(&mut self) {
1575 }
1578
1579 fn hide(&mut self) {
1580 }
1583
1584 fn on_idle(&mut self) {
1585 let Some(state) = self.editor.as_mut() else {
1586 return;
1587 };
1588 if !state.idle_iface.is_null() {
1593 let rc = unsafe { ((*state.idle_iface).idle)(state.handle) };
1594 if rc != 0 {
1595 self.close_editor();
1598 return;
1599 }
1600 }
1601
1602 let Some(state) = self.editor.as_mut() else {
1608 return;
1609 };
1610 let port_event_fn = unsafe { (*state.descriptor).port_event };
1613 for (port_index, &offset) in self.control_value_offset.iter().enumerate() {
1614 if offset == usize::MAX || offset >= self.control_values.len() {
1615 continue;
1616 }
1617 let cur = self.control_values[offset];
1618 let prev = state
1621 .last_pushed_values
1622 .get(offset)
1623 .copied()
1624 .unwrap_or(cur + 1.0);
1625 #[allow(clippy::float_cmp)]
1630 let changed = cur != prev;
1631 if changed {
1632 let value = cur;
1633 let port_index_u32 = u32::try_from(port_index).unwrap_or(u32::MAX);
1634 port_event_fn(
1635 state.handle,
1636 port_index_u32,
1637 4,
1638 0, (&raw const value).cast::<c_void>(),
1640 );
1641 if offset < state.last_pushed_values.len() {
1642 state.last_pushed_values[offset] = cur;
1643 }
1644 }
1645 }
1646 let _ = port_event_fn;
1648
1649 }
1658}
1659
1660fn path_to_cstring_with_trailing_sep(p: &Path) -> Result<CString> {
1661 let mut s = p.to_string_lossy().into_owned();
1662 if !s.ends_with(std::path::MAIN_SEPARATOR) {
1663 s.push(std::path::MAIN_SEPARATOR);
1664 }
1665 CString::new(s).map_err(|_| Error::Other("lv2 ui bundle path contains NUL".into()))
1666}
1667
1668#[cfg(target_os = "macos")]
1669fn native_widget_size(widget: LV2UIWidget) -> Option<(u32, u32)> {
1670 use objc2::msg_send;
1671 use objc2_foundation::NSRect;
1672 if widget.is_null() {
1673 return None;
1674 }
1675 let view = widget as *mut objc2::runtime::AnyObject;
1679 let frame: NSRect = unsafe { msg_send![view, frame] };
1680 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
1683 Some((
1684 frame.size.width.max(0.0) as u32,
1685 frame.size.height.max(0.0) as u32,
1686 ))
1687}
1688
1689#[cfg(not(target_os = "macos"))]
1690fn native_widget_size(_widget: LV2UIWidget) -> Option<(u32, u32)> {
1691 None
1695}
1696
1697impl Plugin<f32> for Lv2Plugin {
1698 fn process(
1699 &mut self,
1700 buffer: &mut AudioBuffer<'_, f32>,
1701 events: &EventList,
1702 context: &mut ProcessContext<'_>,
1703 ) -> Result<ProcessStatus> {
1704 if !self.is_active() {
1705 return Err(Error::NotActivated);
1706 }
1707 let frames = buffer.num_frames();
1708
1709 self.build_midi_sequence(events, context.transport);
1712 self.prep_midi_out();
1713
1714 let in_ptrs: Vec<*mut f32> = buffer
1718 .main_inputs()
1719 .iter()
1720 .map(|s| s.as_ptr().cast_mut())
1721 .collect();
1722 let out_ptrs: Vec<*mut f32> = buffer
1723 .main_outputs()
1724 .iter_mut()
1725 .map(|s| s.as_mut_ptr())
1726 .collect();
1727 let control_base = self.control_values.as_mut_ptr();
1728 let midi_in_ptr = self.midi_in_buf.as_mut_ptr().cast::<c_void>();
1729 let midi_out_ptr = self.midi_out_buf.as_mut_ptr().cast::<c_void>();
1730
1731 let mut next_audio_in = 0usize;
1735 let mut next_audio_out = 0usize;
1736 for port in &self.ports {
1737 let data: *mut c_void = match port.kind {
1738 PortKind::Audio => {
1739 if port.is_input {
1740 let plane = in_ptrs
1741 .get(next_audio_in)
1742 .copied()
1743 .unwrap_or(std::ptr::null_mut());
1744 next_audio_in += 1;
1745 plane.cast::<c_void>()
1746 } else {
1747 let plane = out_ptrs
1748 .get(next_audio_out)
1749 .copied()
1750 .unwrap_or(std::ptr::null_mut());
1751 next_audio_out += 1;
1752 plane.cast::<c_void>()
1753 }
1754 }
1755 PortKind::Control { .. } => {
1756 let off = self.control_value_offset[port.index as usize];
1757 if off == usize::MAX {
1758 std::ptr::null_mut()
1759 } else {
1760 unsafe { control_base.add(off).cast::<c_void>() }
1764 }
1765 }
1766 PortKind::AtomSequence => {
1767 if port.is_input {
1768 midi_in_ptr
1769 } else {
1770 midi_out_ptr
1771 }
1772 }
1773 PortKind::Other => std::ptr::null_mut(),
1774 };
1775 unsafe {
1776 lilv_sys::lilv_instance_connect_port(self.instance, port.index, data);
1777 }
1778 }
1779
1780 unsafe {
1781 lilv_sys::lilv_instance_run(self.instance, u32::try_from(frames).unwrap_or(u32::MAX));
1782 }
1783
1784 self.drain_midi_out(context);
1787
1788 Ok(ProcessStatus::Continue)
1789 }
1790}
1791
1792impl Lv2Plugin {
1793 fn drain_midi_out(&mut self, context: &mut ProcessContext<'_>) {
1797 let header_size = std::mem::size_of::<LV2AtomSequence>();
1798 if self.midi_out_buf.len() < header_size {
1799 return;
1800 }
1801 let buf = self.midi_out_buf.as_ptr();
1802 let seq = unsafe { &*buf.cast::<LV2AtomSequence>() };
1805 let body_size = seq.atom.size;
1809 let event_header = std::mem::size_of::<LV2AtomEvent>();
1810 let body_offset = std::mem::size_of::<LV2Atom>();
1811 let mut cursor = body_offset + std::mem::size_of::<LV2AtomSequenceBody>();
1812 let body_end = body_offset + body_size as usize;
1813 while cursor + event_header <= body_end && cursor + event_header <= self.midi_out_buf.len()
1814 {
1815 let ev_ptr = unsafe { buf.add(cursor) }.cast::<LV2AtomEvent>();
1816 let ev = unsafe { &*ev_ptr };
1818 let payload_size = ev.body.size as usize;
1819 let payload_total = event_header + payload_size;
1820 if cursor + payload_total > body_end {
1821 break;
1822 }
1823 if ev.body.mytype == self.midi_event_urid && (1..=8).contains(&payload_size) {
1824 let data_ptr = unsafe { (ev_ptr as *const u8).add(event_header) };
1825 let bytes = unsafe { std::slice::from_raw_parts(data_ptr, payload_size) };
1828 if let Some(body) = decode_midi_atom(bytes) {
1829 let offset = u32::try_from(ev.time_in_frames.max(0)).unwrap_or(0);
1830 context.output_events.push(Event {
1831 sample_offset: offset,
1832 body,
1833 });
1834 }
1835 }
1836 cursor += (payload_total + 7) & !7;
1839 }
1840 }
1841}
1842
1843fn decode_midi_atom(bytes: &[u8]) -> Option<EventBody> {
1847 if bytes.is_empty() {
1848 return None;
1849 }
1850 let status = bytes[0];
1851 let channel = status & 0x0F;
1852 let kind = status & 0xF0;
1853 let body = match (kind, bytes) {
1854 (0x80, [_, note, vel]) => MidiData::NoteOff {
1855 channel,
1856 note: *note,
1857 velocity: *vel,
1858 },
1859 (0x90, [_, note, 0]) => MidiData::NoteOff {
1860 channel,
1861 note: *note,
1862 velocity: 0,
1863 },
1864 (0x90, [_, note, vel]) => MidiData::NoteOn {
1865 channel,
1866 note: *note,
1867 velocity: *vel,
1868 },
1869 (0xA0, [_, note, pressure]) => MidiData::PolyAftertouch {
1870 channel,
1871 note: *note,
1872 pressure: *pressure,
1873 },
1874 (0xB0, [_, controller, value]) => MidiData::ControlChange {
1875 channel,
1876 controller: *controller,
1877 value: *value,
1878 },
1879 (0xC0, [_, program]) => MidiData::ProgramChange {
1880 channel,
1881 program: *program,
1882 },
1883 (0xD0, [_, pressure]) => MidiData::ChannelAftertouch {
1884 channel,
1885 pressure: *pressure,
1886 },
1887 (0xE0, [_, lsb, msb]) => MidiData::PitchBend {
1888 channel,
1889 value: u16::from(*msb) << 7 | u16::from(*lsb),
1890 },
1891 _ if bytes.len() <= 8 => {
1892 let mut data = [0u8; 8];
1893 data[..bytes.len()].copy_from_slice(bytes);
1894 #[allow(clippy::cast_possible_truncation)]
1895 MidiData::Raw {
1896 len: bytes.len() as u8,
1897 data,
1898 }
1899 }
1900 _ => return None,
1901 };
1902 Some(EventBody::Midi(body))
1903}