1use atomic_refcell::{AtomicRefCell, AtomicRefMut};
2use clap_sys::events::{
3 CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_IS_LIVE, CLAP_EVENT_MIDI, CLAP_EVENT_MIDI_SYSEX,
4 CLAP_EVENT_NOTE_CHOKE, CLAP_EVENT_NOTE_EXPRESSION, CLAP_EVENT_NOTE_OFF, CLAP_EVENT_NOTE_ON,
5 CLAP_EVENT_PARAM_GESTURE_BEGIN, CLAP_EVENT_PARAM_GESTURE_END, CLAP_EVENT_PARAM_MOD,
6 CLAP_EVENT_PARAM_VALUE, CLAP_EVENT_TRANSPORT, CLAP_NOTE_EXPRESSION_BRIGHTNESS,
7 CLAP_NOTE_EXPRESSION_EXPRESSION, CLAP_NOTE_EXPRESSION_PAN, CLAP_NOTE_EXPRESSION_PRESSURE,
8 CLAP_NOTE_EXPRESSION_TUNING, CLAP_NOTE_EXPRESSION_VIBRATO, CLAP_NOTE_EXPRESSION_VOLUME,
9 CLAP_TRANSPORT_HAS_BEATS_TIMELINE, CLAP_TRANSPORT_HAS_SECONDS_TIMELINE,
10 CLAP_TRANSPORT_HAS_TEMPO, CLAP_TRANSPORT_HAS_TIME_SIGNATURE, CLAP_TRANSPORT_IS_LOOP_ACTIVE,
11 CLAP_TRANSPORT_IS_PLAYING, CLAP_TRANSPORT_IS_RECORDING, CLAP_TRANSPORT_IS_WITHIN_PRE_ROLL,
12 clap_event_header, clap_event_midi, clap_event_midi_sysex, clap_event_note,
13 clap_event_note_expression, clap_event_param_gesture, clap_event_param_mod,
14 clap_event_param_value, clap_event_transport, clap_input_events, clap_output_events,
15};
16use clap_sys::ext::audio_ports::{
17 CLAP_AUDIO_PORT_IS_MAIN, CLAP_EXT_AUDIO_PORTS, CLAP_PORT_MONO, CLAP_PORT_STEREO,
18 clap_audio_port_info, clap_plugin_audio_ports,
19};
20use clap_sys::ext::audio_ports_config::{
21 CLAP_EXT_AUDIO_PORTS_CONFIG, clap_audio_ports_config, clap_plugin_audio_ports_config,
22};
23use clap_sys::ext::gui::CLAP_EXT_GUI;
24#[cfg(feature = "editor")]
25use clap_sys::ext::gui::{clap_host_gui, clap_plugin_gui};
26use clap_sys::ext::latency::{CLAP_EXT_LATENCY, clap_host_latency, clap_plugin_latency};
27use clap_sys::ext::note_ports::{
28 CLAP_EXT_NOTE_PORTS, CLAP_NOTE_DIALECT_CLAP, CLAP_NOTE_DIALECT_MIDI, clap_note_port_info,
29 clap_plugin_note_ports,
30};
31use clap_sys::ext::params::{
32 CLAP_EXT_PARAMS, CLAP_PARAM_IS_AUTOMATABLE, CLAP_PARAM_IS_BYPASS, CLAP_PARAM_IS_HIDDEN,
33 CLAP_PARAM_IS_MODULATABLE, CLAP_PARAM_IS_MODULATABLE_PER_NOTE_ID, CLAP_PARAM_IS_READONLY,
34 CLAP_PARAM_IS_STEPPED, CLAP_PARAM_RESCAN_VALUES, clap_host_params, clap_param_info,
35 clap_plugin_params,
36};
37use clap_sys::ext::remote_controls::{
38 CLAP_EXT_REMOTE_CONTROLS, clap_plugin_remote_controls, clap_remote_controls_page,
39};
40use clap_sys::ext::render::{
41 CLAP_EXT_RENDER, CLAP_RENDER_OFFLINE, CLAP_RENDER_REALTIME, clap_plugin_render,
42 clap_plugin_render_mode,
43};
44use clap_sys::ext::state::{CLAP_EXT_STATE, clap_plugin_state};
45use clap_sys::ext::tail::{CLAP_EXT_TAIL, clap_plugin_tail};
46use clap_sys::ext::thread_check::{CLAP_EXT_THREAD_CHECK, clap_host_thread_check};
47use clap_sys::ext::track_info::CLAP_EXT_TRACK_INFO;
48#[cfg(feature = "editor")]
49use clap_sys::ext::track_info::{
50 CLAP_TRACK_INFO_HAS_TRACK_COLOR, CLAP_TRACK_INFO_HAS_TRACK_NAME, clap_host_track_info,
51 clap_plugin_track_info, clap_track_info,
52};
53use clap_sys::ext::voice_info::{
54 CLAP_EXT_VOICE_INFO, CLAP_VOICE_INFO_SUPPORTS_OVERLAPPING_NOTES, clap_host_voice_info,
55 clap_plugin_voice_info, clap_voice_info,
56};
57use clap_sys::fixedpoint::{CLAP_BEATTIME_FACTOR, CLAP_SECTIME_FACTOR};
58use clap_sys::host::clap_host;
59use clap_sys::id::{CLAP_INVALID_ID, clap_id};
60use clap_sys::plugin::clap_plugin;
61use clap_sys::process::{
62 CLAP_PROCESS_CONTINUE, CLAP_PROCESS_CONTINUE_IF_NOT_QUIET, CLAP_PROCESS_ERROR, clap_process,
63 clap_process_status,
64};
65use clap_sys::stream::{clap_istream, clap_ostream};
66use crossbeam::atomic::AtomicCell;
67use crossbeam::channel::{self, SendTimeoutError};
68use crossbeam::queue::ArrayQueue;
69use nice_plug_core::audio_setup::{AudioIOLayout, AuxiliaryBuffers, BufferConfig, ProcessMode};
70#[cfg(feature = "editor")]
71use nice_plug_core::context::gui::GuiContext;
72use nice_plug_core::context::process::Transport;
73#[cfg(feature = "editor")]
74use nice_plug_core::editor::{Editor, SpawnedEditor};
75use nice_plug_core::midi::{Channel, Key, MidiConfig, NoteEvent, PluginNoteEvent, VoiceID};
76use nice_plug_core::params::internals::ParamPtr;
77use nice_plug_core::params::{ParamFlags, Params};
78use nice_plug_core::plugin::{Plugin, PluginState, ProcessStatus, TaskExecutor};
79#[cfg(feature = "editor")]
80use nice_plug_core::plugin::{TrackColor, TrackInfo};
81use parking_lot::Mutex;
82#[cfg(feature = "editor")]
83use std::borrow::Borrow;
84use std::collections::{HashMap, HashSet, VecDeque};
85use std::ffi::{CStr, c_void};
86use std::mem;
87use std::num::NonZeroU32;
88use std::os::raw::c_char;
89use std::ptr::NonNull;
90use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
91use std::sync::{Arc, Weak};
92use std::thread::{self, ThreadId};
93use std::time::{Duration, Instant};
94use try_lock::TryLock;
95
96use super::context::{WrapperActivateContext, WrapperProcessContext};
97use super::descriptor::PluginDescriptor;
98use super::util::ClapPtr;
99use crate::event_loop::{BackgroundThread, EventLoop, MainThreadExecutor, TASK_QUEUE_CAPACITY};
100use crate::util::permit_alloc;
101use crate::wrapper::clap::ClapPlugin;
102use crate::wrapper::clap::context::RemoteControlPages;
103#[cfg(feature = "editor")]
104use crate::wrapper::clap::context::WrapperGuiContext;
105use crate::wrapper::clap::util::{read_stream, write_stream};
106use crate::wrapper::state::{self};
107use crate::wrapper::util::buffer_management::{BufferManager, ChannelPointers};
108use crate::wrapper::util::{clamp_input_event_timing, hash_param_id, process_wrapper, strlcpy};
109
110const OUTPUT_EVENT_QUEUE_CAPACITY: usize = 2048;
113
114const MAX_STATE_BYTES: u64 = 268_435_456;
119
120pub struct Wrapper<P: ClapPlugin> {
121 this: AtomicRefCell<Weak<Self>>,
123
124 plugin: TryLock<P>,
126 pub task_executor: Mutex<TaskExecutor<P>>,
128 params: Arc<dyn Params>,
132 #[cfg(feature = "editor")]
136 editor: AtomicRefCell<Option<Mutex<P::Editor>>>,
137 #[cfg(feature = "editor")]
140 #[allow(clippy::type_complexity)]
141 editor_window:
142 AtomicRefCell<Option<fragile::Fragile<SpawnedEditor<<P::Editor as Editor>::Handle>>>>,
143 #[cfg(feature = "editor")]
148 fallback_scale_factor: AtomicCell<Option<f64>>,
149 is_activated: AtomicBool,
150 is_processing: AtomicBool,
151 current_audio_io_layout: AtomicCell<AudioIOLayout>,
154 current_buffer_config: AtomicCell<Option<BufferConfig>>,
157 pub current_process_mode: AtomicCell<ProcessMode>,
159 input_events: AtomicRefCell<VecDeque<PluginNoteEvent<P>>>,
165 last_process_status: AtomicCell<ProcessStatus>,
167 latency_changed: AtomicBool,
171 pub current_latency: AtomicU32,
175 buffer_manager: AtomicRefCell<BufferManager>,
178 updated_state_sender: channel::Sender<PluginState>,
187 updated_state_receiver: channel::Receiver<PluginState>,
189
190 host_callback: ClapPtr<clap_host>,
192
193 clap_plugin_audio_ports_config: clap_plugin_audio_ports_config,
194
195 pub clap_plugin: AtomicRefCell<clap_plugin>,
198 _plugin_descriptor: Box<PluginDescriptor>,
201
202 clap_plugin_audio_ports: clap_plugin_audio_ports,
203
204 #[cfg(feature = "editor")]
205 clap_plugin_gui: clap_plugin_gui,
206 #[cfg(feature = "editor")]
207 host_gui: AtomicRefCell<Option<ClapPtr<clap_host_gui>>>,
208
209 clap_plugin_latency: clap_plugin_latency,
210 host_latency: AtomicRefCell<Option<ClapPtr<clap_host_latency>>>,
211
212 clap_plugin_note_ports: clap_plugin_note_ports,
213
214 clap_plugin_params: clap_plugin_params,
215 host_params: AtomicRefCell<Option<ClapPtr<clap_host_params>>>,
216 param_hashes: Vec<u32>,
220 param_by_hash: HashMap<u32, ParamPtr>,
225 param_id_by_hash: HashMap<u32, String>,
228 param_group_by_hash: HashMap<u32, String>,
232 param_id_to_hash: HashMap<String, u32>,
235 pub param_ptr_to_hash: HashMap<ParamPtr, u32>,
240 poly_mod_ids_by_hash: HashMap<u32, u32>,
244 output_parameter_events: ArrayQueue<OutputParamEvent>,
251
252 host_thread_check: AtomicRefCell<Option<ClapPtr<clap_host_thread_check>>>,
253
254 clap_plugin_remote_controls: clap_plugin_remote_controls,
255 remote_control_pages: Vec<clap_remote_controls_page>,
257
258 clap_plugin_render: clap_plugin_render,
259
260 clap_plugin_state: clap_plugin_state,
261
262 clap_plugin_tail: clap_plugin_tail,
263
264 #[cfg(feature = "editor")]
265 clap_plugin_track_info: clap_plugin_track_info,
266 #[cfg(feature = "editor")]
267 host_track_info: AtomicRefCell<Option<ClapPtr<clap_host_track_info>>>,
268 #[cfg(feature = "editor")]
271 current_track_info: AtomicRefCell<TrackInfo>,
272
273 clap_plugin_voice_info: clap_plugin_voice_info,
274 host_voice_info: AtomicRefCell<Option<ClapPtr<clap_host_voice_info>>>,
275 current_voice_capacity: AtomicU32,
279
280 tasks: ArrayQueue<Task<P>>,
286 main_thread_id: ThreadId,
291 background_thread: AtomicRefCell<Option<BackgroundThread<Task<P>, Self>>>,
294}
295
296#[allow(clippy::enum_variant_names)]
300pub enum Task<P: Plugin> {
301 PluginTask(P::BackgroundTask),
303 #[cfg(feature = "editor")]
306 ParameterValueChanged(u32, f32),
307 #[cfg(feature = "editor")]
310 ParameterModulationChanged(u32, f32),
311 StateChanged,
312 LatencyChanged,
314 VoiceInfoChanged,
316 RescanParamValues,
318}
319
320pub enum ClapParamUpdate {
322 PlainValueSet(f64),
325 PlainValueMod(f64),
329}
330
331#[derive(Debug, Clone)]
334pub enum OutputParamEvent {
335 BeginGesture { param_hash: u32 },
337 SetValue {
340 param_hash: u32,
342 clap_plain_value: f64,
345 },
346 EndGesture { param_hash: u32 },
349}
350
351impl<P: ClapPlugin> EventLoop<Task<P>, Wrapper<P>> for Wrapper<P> {
354 fn new_and_spawn(_executor: Weak<Self>) -> Self {
355 panic!("What are you doing");
356 }
357
358 fn schedule_gui(&self, task: Task<P>) -> bool {
359 if self.is_main_thread() {
360 self.execute(task, true);
361 true
362 } else {
363 let success = self.tasks.push(task).is_ok();
364 if success {
365 let host = &self.host_callback;
367 unsafe_clap_call! { host=>request_callback(&**host) };
368 }
369
370 success
371 }
372 }
373
374 fn schedule_background(&self, task: Task<P>) -> bool {
375 self.background_thread
376 .borrow()
377 .as_ref()
378 .unwrap()
379 .schedule(task)
380 }
381
382 fn is_main_thread(&self) -> bool {
383 match &*self.host_thread_check.borrow() {
386 Some(thread_check) => {
387 unsafe_clap_call! { thread_check=>is_main_thread(&*self.host_callback) }
388 }
389 None => permit_alloc(|| thread::current().id() == self.main_thread_id),
392 }
393 }
394}
395
396impl<P: ClapPlugin> MainThreadExecutor<Task<P>> for Wrapper<P> {
397 fn execute(&self, task: Task<P>, is_gui_thread: bool) {
398 match task {
400 Task::PluginTask(task) => (self.task_executor.lock())(task),
401 #[cfg(feature = "editor")]
402 Task::ParameterValueChanged(param_hash, normalized_value) => {
403 use nice_plug_core::editor::EditorHandle;
404
405 if let Some(window) = self.editor_window.borrow().as_ref() {
406 let param_id = &self.param_id_by_hash[¶m_hash];
407 window
408 .get()
409 .handle
410 .param_value_changed(param_id, normalized_value);
411 }
412 }
413 Task::StateChanged => {
414 #[cfg(feature = "editor")]
415 {
416 use nice_plug_core::editor::EditorHandle;
417 if let Some(window) = self.editor_window.borrow().as_ref() {
418 window.get().handle.state_changed();
419 }
420 }
421
422 if let Some(host_params) = &*self.host_params.borrow() {
423 crate::nice_debug_assert!(is_gui_thread);
424 unsafe_clap_call! { host_params=>rescan(&*self.host_callback, CLAP_PARAM_RESCAN_VALUES) };
425 }
426 }
427 #[cfg(feature = "editor")]
428 Task::ParameterModulationChanged(param_hash, modulation_offset) => {
429 use nice_plug_core::editor::EditorHandle;
430
431 if let Some(window) = self.editor_window.borrow().as_ref() {
432 let param_id = &self.param_id_by_hash[¶m_hash];
433 window
434 .get()
435 .handle
436 .param_modulation_changed(param_id, modulation_offset);
437 }
438 }
439 Task::LatencyChanged => match &*self.host_latency.borrow() {
440 Some(host_latency) => {
441 crate::nice_debug_assert!(is_gui_thread);
442
443 if self.is_activated.load(Ordering::SeqCst) {
451 self.latency_changed.store(true, Ordering::SeqCst);
452 self.request_restart();
453 } else {
454 unsafe_clap_call! { host_latency=>changed(&*self.host_callback) };
455 }
456 }
457 None => {
458 #[cfg(debug_assertions)]
459 crate::nice_warn!("Host does not support the latency extension");
460 }
461 },
462 Task::VoiceInfoChanged => match &*self.host_voice_info.borrow() {
463 Some(host_voice_info) => {
464 crate::nice_debug_assert!(is_gui_thread);
465 unsafe_clap_call! { host_voice_info=>changed(&*self.host_callback) };
466 }
467 None => {
468 #[cfg(debug_assertions)]
469 crate::nice_warn!("Host does not support the voice-info extension");
470 }
471 },
472 Task::RescanParamValues => match &*self.host_params.borrow() {
473 Some(host_params) => {
474 crate::nice_debug_assert!(is_gui_thread);
475 unsafe_clap_call! { host_params=>rescan(&*self.host_callback, CLAP_PARAM_RESCAN_VALUES) };
476 }
477 None => {
478 #[cfg(debug_assertions)]
479 crate::nice_warn!("Host does not support the parameter extension");
480 }
481 },
482 };
483 }
484}
485
486impl<P: ClapPlugin> Wrapper<P> {
487 pub unsafe fn new(host_callback: *const clap_host) -> Arc<Self> {
491 let mut plugin = P::default();
492 let task_executor = Mutex::new(plugin.task_executor());
493
494 let (updated_state_sender, updated_state_receiver) = channel::bounded(0);
497
498 let plugin_descriptor: Box<PluginDescriptor> =
499 Box::new(PluginDescriptor::for_plugin::<P>());
500
501 assert!(!host_callback.is_null());
504 let host_callback = unsafe { ClapPtr::new(host_callback) };
505
506 let params = plugin.params();
512 let param_id_hashes_ptrs_groups: Vec<_> = params
513 .param_map()
514 .into_iter()
515 .map(|(id, ptr, group)| {
516 let hash = hash_param_id(&id);
517 (id, hash, ptr, group)
518 })
519 .collect();
520 let param_hashes = param_id_hashes_ptrs_groups
521 .iter()
522 .map(|(_, hash, _, _)| *hash)
523 .collect();
524 let param_by_hash = param_id_hashes_ptrs_groups
525 .iter()
526 .map(|(_, hash, ptr, _)| (*hash, *ptr))
527 .collect();
528 let param_id_by_hash = param_id_hashes_ptrs_groups
529 .iter()
530 .map(|(id, hash, _, _)| (*hash, id.clone()))
531 .collect();
532 let param_group_by_hash = param_id_hashes_ptrs_groups
533 .iter()
534 .map(|(_, hash, _, group)| (*hash, group.clone()))
535 .collect();
536 let param_id_to_hash = param_id_hashes_ptrs_groups
537 .iter()
538 .map(|(id, hash, _, _)| (id.clone(), *hash))
539 .collect();
540 let param_ptr_to_hash = param_id_hashes_ptrs_groups
541 .iter()
542 .map(|(_, hash, ptr, _)| (*ptr, *hash))
543 .collect();
544 let poly_mod_ids_by_hash: HashMap<u32, u32> = param_id_hashes_ptrs_groups
545 .iter()
546 .filter_map(|(_, hash, ptr, _)| unsafe {
547 ptr.poly_modulation_id().map(|id| (*hash, id))
548 })
549 .collect();
550
551 if cfg!(debug_assertions) {
552 let param_map = params.param_map();
553 let param_ids: HashSet<_> = param_id_hashes_ptrs_groups
554 .iter()
555 .map(|(id, _, _, _)| id.clone())
556 .collect();
557 crate::nice_debug_assert_eq!(
558 param_map.len(),
559 param_ids.len(),
560 "The plugin has duplicate parameter IDs, weird things may happen. Consider using \
561 6 character parameter IDs to avoid collisions."
562 );
563
564 let poly_mod_ids: HashSet<u32> = poly_mod_ids_by_hash.values().copied().collect();
565 crate::nice_debug_assert_eq!(
566 poly_mod_ids_by_hash.len(),
567 poly_mod_ids.len(),
568 "The plugin has duplicate poly modulation IDs. Polyphonic modulation will not be \
569 routed to the correct parameter."
570 );
571
572 let mut bypass_param_exists = false;
573 for (_, _, ptr, _) in ¶m_id_hashes_ptrs_groups {
574 let flags = unsafe { ptr.flags() };
575 let is_bypass = flags.contains(ParamFlags::BYPASS);
576
577 if is_bypass && bypass_param_exists {
578 crate::nice_debug_assert_failure!(
579 "Duplicate bypass parameters found, the host will only use the first one"
580 );
581 }
582
583 bypass_param_exists |= is_bypass;
584 }
585 }
586
587 let mut remote_control_pages = Vec::new();
589 RemoteControlPages::define_remote_control_pages(
590 &plugin,
591 &mut remote_control_pages,
592 ¶m_ptr_to_hash,
593 );
594
595 let wrapper = Self {
596 this: AtomicRefCell::new(Weak::new()),
597
598 plugin: TryLock::new(plugin),
599 task_executor,
600 params,
601 #[cfg(feature = "editor")]
603 editor: AtomicRefCell::new(None),
604 #[cfg(feature = "editor")]
605 editor_window: AtomicRefCell::new(None),
606 #[cfg(feature = "editor")]
607 fallback_scale_factor: AtomicCell::new(None),
608
609 is_activated: AtomicBool::new(false),
610 is_processing: AtomicBool::new(false),
611 current_audio_io_layout: AtomicCell::new(
612 P::AUDIO_IO_LAYOUTS.first().copied().unwrap_or_default(),
613 ),
614 current_buffer_config: AtomicCell::new(None),
615 current_process_mode: AtomicCell::new(ProcessMode::Realtime),
616 input_events: AtomicRefCell::new(VecDeque::with_capacity(P::INPUT_EVENT_CAPACITY)),
617 last_process_status: AtomicCell::new(ProcessStatus::Normal),
618 latency_changed: AtomicBool::new(false),
619 current_latency: AtomicU32::new(0),
620 buffer_manager: AtomicRefCell::new(BufferManager::for_audio_io_layout(
623 0,
624 AudioIOLayout::default(),
625 )),
626 updated_state_sender,
627 updated_state_receiver,
628
629 host_callback,
630
631 clap_plugin: AtomicRefCell::new(clap_plugin {
632 desc: plugin_descriptor.clap_plugin_descriptor(),
637 plugin_data: std::ptr::null_mut(),
639 init: Some(Self::init),
640 destroy: Some(Self::destroy),
641 activate: Some(Self::activate),
642 deactivate: Some(Self::deactivate),
643 start_processing: Some(Self::start_processing),
644 stop_processing: Some(Self::stop_processing),
645 reset: Some(Self::reset),
646 process: Some(Self::process),
647 get_extension: Some(Self::get_extension),
648 on_main_thread: Some(Self::on_main_thread),
649 }),
650 _plugin_descriptor: plugin_descriptor,
651
652 clap_plugin_audio_ports_config: clap_plugin_audio_ports_config {
653 count: Some(Self::ext_audio_ports_config_count),
654 get: Some(Self::ext_audio_ports_config_get),
655 select: Some(Self::ext_audio_ports_config_select),
656 },
657
658 clap_plugin_audio_ports: clap_plugin_audio_ports {
659 count: Some(Self::ext_audio_ports_count),
660 get: Some(Self::ext_audio_ports_get),
661 },
662
663 #[cfg(feature = "editor")]
664 clap_plugin_gui: clap_sys::ext::gui::clap_plugin_gui {
665 is_api_supported: Some(Self::ext_gui_is_api_supported),
666 get_preferred_api: Some(Self::ext_gui_get_preferred_api),
667 create: Some(Self::ext_gui_create),
668 destroy: Some(Self::ext_gui_destroy),
669 set_scale: Some(Self::ext_gui_set_scale),
670 get_size: Some(Self::ext_gui_get_size),
671 can_resize: Some(Self::ext_gui_can_resize),
672 get_resize_hints: Some(Self::ext_gui_get_resize_hints),
673 adjust_size: Some(Self::ext_gui_adjust_size),
674 set_size: Some(Self::ext_gui_set_size),
675 set_parent: Some(Self::ext_gui_set_parent),
676 set_transient: Some(Self::ext_gui_set_transient),
677 suggest_title: Some(Self::ext_gui_suggest_title),
678 show: Some(Self::ext_gui_show),
679 hide: Some(Self::ext_gui_hide),
680 },
681 #[cfg(feature = "editor")]
682 host_gui: AtomicRefCell::new(None),
683
684 clap_plugin_latency: clap_plugin_latency {
685 get: Some(Self::ext_latency_get),
686 },
687 host_latency: AtomicRefCell::new(None),
688
689 clap_plugin_note_ports: clap_plugin_note_ports {
690 count: Some(Self::ext_note_ports_count),
691 get: Some(Self::ext_note_ports_get),
692 },
693
694 clap_plugin_params: clap_plugin_params {
695 count: Some(Self::ext_params_count),
696 get_info: Some(Self::ext_params_get_info),
697 get_value: Some(Self::ext_params_get_value),
698 value_to_text: Some(Self::ext_params_value_to_text),
699 text_to_value: Some(Self::ext_params_text_to_value),
700 flush: Some(Self::ext_params_flush),
701 },
702 host_params: AtomicRefCell::new(None),
703 param_hashes,
704 param_by_hash,
705 param_id_by_hash,
706 param_group_by_hash,
707 param_id_to_hash,
708 param_ptr_to_hash,
709 poly_mod_ids_by_hash,
710 output_parameter_events: ArrayQueue::new(OUTPUT_EVENT_QUEUE_CAPACITY),
711
712 host_thread_check: AtomicRefCell::new(None),
713
714 clap_plugin_remote_controls: clap_plugin_remote_controls {
715 count: Some(Self::ext_remote_controls_count),
716 get: Some(Self::ext_remote_controls_get),
717 },
718 remote_control_pages,
719
720 clap_plugin_render: clap_plugin_render {
721 has_hard_realtime_requirement: Some(Self::ext_render_has_hard_realtime_requirement),
722 set: Some(Self::ext_render_set),
723 },
724
725 clap_plugin_state: clap_plugin_state {
726 save: Some(Self::ext_state_save),
727 load: Some(Self::ext_state_load),
728 },
729
730 clap_plugin_tail: clap_plugin_tail {
731 get: Some(Self::ext_tail_get),
732 },
733
734 #[cfg(feature = "editor")]
735 clap_plugin_track_info: clap_plugin_track_info {
736 changed: Some(Self::ext_track_info_changed),
737 },
738 #[cfg(feature = "editor")]
739 host_track_info: AtomicRefCell::new(None),
740 #[cfg(feature = "editor")]
741 current_track_info: AtomicRefCell::new(TrackInfo::default()),
742
743 clap_plugin_voice_info: clap_plugin_voice_info {
744 get: Some(Self::ext_voice_info_get),
745 },
746 host_voice_info: AtomicRefCell::new(None),
747 current_voice_capacity: AtomicU32::new(
748 P::CLAP_POLY_MODULATION_CONFIG
749 .map(|c| {
750 crate::nice_debug_assert!(
751 c.max_voice_capacity >= 1,
752 "The maximum voice capacity cannot be zero"
753 );
754 c.max_voice_capacity
755 })
756 .unwrap_or(1),
757 ),
758
759 tasks: ArrayQueue::new(TASK_QUEUE_CAPACITY),
760 main_thread_id: thread::current().id(),
761 background_thread: AtomicRefCell::new(None),
763 };
764
765 let wrapper = Arc::new(wrapper);
768 *wrapper.this.borrow_mut() = Arc::downgrade(&wrapper);
769
770 wrapper.clap_plugin.borrow_mut().plugin_data = Arc::as_ptr(&wrapper) as *mut _;
773
774 *wrapper.background_thread.borrow_mut() =
776 Some(BackgroundThread::get_or_create(Arc::downgrade(&wrapper)));
777
778 #[cfg(feature = "editor")]
780 {
781 *wrapper.editor.borrow_mut() = wrapper
782 .plugin
783 .try_lock()
784 .unwrap()
785 .editor(nice_plug_core::context::gui::AsyncExecutor::new(
786 Arc::new({
787 let wrapper = Arc::downgrade(&wrapper);
788 move |task| {
789 let wrapper = match wrapper.upgrade() {
790 Some(wrapper) => wrapper,
791 None => return,
792 };
793
794 let task_posted = wrapper.schedule_background(Task::PluginTask(task));
795 crate::nice_debug_assert!(
796 task_posted,
797 "The task queue is full, dropping task..."
798 );
799 }
800 }),
801 Arc::new({
802 let wrapper = Arc::downgrade(&wrapper);
803 move |task| {
804 let wrapper = match wrapper.upgrade() {
805 Some(wrapper) => wrapper,
806 None => return,
807 };
808
809 let task_posted = wrapper.schedule_gui(Task::PluginTask(task));
810 crate::nice_debug_assert!(
811 task_posted,
812 "The task queue is full, dropping task..."
813 );
814 }
815 }),
816 ))
817 .map(Mutex::new);
818 }
819
820 wrapper
821 }
822
823 #[cfg(feature = "editor")]
824 fn make_gui_context(self: Arc<Self>) -> GuiContext {
825 GuiContext::new(Arc::new(WrapperGuiContext {
826 wrapper: Arc::downgrade(&self),
827 #[cfg(debug_assertions)]
828 param_gesture_checker: Default::default(),
829 }))
830 }
831
832 fn make_activate_context(&self) -> WrapperActivateContext<'_, P> {
837 WrapperActivateContext {
838 wrapper: self,
839 pending_requests: Default::default(),
840 }
841 }
842
843 fn make_process_context(
844 &self,
845 transport: Transport,
846 total_buffer_len: usize,
847 current_sample_idx: usize,
848 host_out_events: *const clap_output_events,
849 ) -> WrapperProcessContext<'_, P> {
850 WrapperProcessContext {
851 wrapper: self,
852 input_events_guard: self.input_events.borrow_mut(),
853 transport,
854 total_buffer_len: total_buffer_len as u32,
855 current_sample_idx: current_sample_idx as u32,
856 host_out_events,
857 }
858 }
859
860 #[allow(unused)]
863 pub fn param_id_from_ptr(&self, param: ParamPtr) -> Option<&str> {
864 self.param_ptr_to_hash
865 .get(¶m)
866 .and_then(|hash| self.param_id_by_hash.get(hash))
867 .map(|s| s.as_str())
868 }
869
870 pub fn queue_parameter_event(&self, event: OutputParamEvent) -> bool {
878 let result = self.output_parameter_events.push(event).is_ok();
879
880 match &*self.host_params.borrow() {
882 Some(host_params) => {
883 unsafe_clap_call! { host_params=>request_flush(&*self.host_callback) }
884 }
885 None => {
886 crate::nice_debug_assert_failure!("The host does not support parameters? What?")
887 }
888 }
889
890 result
891 }
892
893 pub fn update_plain_value_by_hash(
901 &self,
902 hash: u32,
903 update_type: ClapParamUpdate,
904 sample_rate: Option<f32>,
905 ) -> bool {
906 match self.param_by_hash.get(&hash) {
907 Some(param_ptr) => {
908 match update_type {
909 ClapParamUpdate::PlainValueSet(clap_plain_value) => {
910 if !clap_plain_value.is_finite() {
911 return false;
912 }
913
914 let normalized_value = clap_plain_value as f32
915 / unsafe { param_ptr.step_count() }.unwrap_or(1) as f32;
916
917 if unsafe { param_ptr._internal_set_normalized_value(normalized_value) } {
918 if let Some(sample_rate) = sample_rate {
919 unsafe { param_ptr._internal_update_smoother(sample_rate, false) };
920 }
921
922 #[cfg(feature = "editor")]
923 {
924 let task_posted = self.schedule_gui(Task::ParameterValueChanged(
927 hash,
928 normalized_value,
929 ));
930 crate::nice_debug_assert!(
931 task_posted,
932 "The task queue is full, dropping task..."
933 );
934 }
935 }
936
937 true
938 }
939 ClapParamUpdate::PlainValueMod(clap_plain_delta) => {
940 if !clap_plain_delta.is_finite() {
941 return false;
942 }
943
944 let normalized_delta = clap_plain_delta as f32
945 / unsafe { param_ptr.step_count() }.unwrap_or(1) as f32;
946
947 if unsafe { param_ptr._internal_modulate_value(normalized_delta) } {
948 if let Some(sample_rate) = sample_rate {
949 unsafe { param_ptr._internal_update_smoother(sample_rate, false) };
950 }
951
952 #[cfg(feature = "editor")]
953 {
954 let task_posted = self.schedule_gui(
955 Task::ParameterModulationChanged(hash, normalized_delta),
956 );
957 crate::nice_debug_assert!(
958 task_posted,
959 "The task queue is full, dropping task..."
960 );
961 }
962 }
963
964 true
965 }
966 }
967 }
968 _ => false,
969 }
970 }
971
972 pub unsafe fn handle_in_events(
979 &self,
980 in_: &clap_input_events,
981 current_sample_idx: usize,
982 total_buffer_len: usize,
983 ) {
984 let mut input_events = self.input_events.borrow_mut();
985 input_events.clear();
986
987 unsafe {
988 let num_events = clap_call! { in_=>size(in_) };
989 for event_idx in 0..num_events {
990 let event = clap_call! { in_=>get(in_, event_idx) };
991 self.handle_input_event(
992 event,
993 &mut input_events,
994 None,
995 current_sample_idx,
996 total_buffer_len,
997 );
998 }
999 }
1000 }
1001
1002 pub unsafe fn handle_in_events_until(
1017 &self,
1018 in_: &clap_input_events,
1019 transport_info: &mut *const clap_event_transport,
1020 current_sample_idx: usize,
1021 total_buffer_len: usize,
1022 resume_from_event_idx: usize,
1023 stop_predicate: impl Fn(*const clap_event_header) -> bool,
1024 ) -> Option<(usize, usize)> {
1025 let mut input_events = self.input_events.borrow_mut();
1026 input_events.clear();
1027
1028 let num_events = unsafe {
1029 clap_call! { in_=>size(in_) }
1030 };
1031
1032 if resume_from_event_idx as u32 >= num_events {
1033 return None;
1034 }
1035
1036 for event_idx in (resume_from_event_idx as u32)..num_events {
1037 unsafe {
1038 let event: *const clap_event_header = clap_call! { in_=>get(in_, event_idx) };
1039 if event.is_null() {
1040 continue;
1041 }
1042
1043 if (*event).time > current_sample_idx as u32 && stop_predicate(event) {
1046 return Some(((*event).time as usize, event_idx as usize));
1047 }
1048
1049 self.handle_input_event(
1050 event,
1051 &mut input_events,
1052 Some(transport_info),
1053 current_sample_idx,
1054 total_buffer_len,
1055 );
1056 }
1057 }
1058
1059 None
1060 }
1061
1062 pub unsafe fn handle_out_events(&self, out: &clap_output_events, current_sample_idx: usize) {
1073 let sample_rate = self.current_buffer_config.load().map(|c| c.sample_rate);
1076 while let Some(change) = self.output_parameter_events.pop() {
1077 let push_successful = match change {
1078 OutputParamEvent::BeginGesture { param_hash } => {
1079 let event = clap_event_param_gesture {
1080 header: clap_event_header {
1081 size: mem::size_of::<clap_event_param_gesture>() as u32,
1082 time: current_sample_idx as u32,
1083 space_id: CLAP_CORE_EVENT_SPACE_ID,
1084 type_: CLAP_EVENT_PARAM_GESTURE_BEGIN,
1085 flags: CLAP_EVENT_IS_LIVE,
1086 },
1087 param_id: param_hash,
1088 };
1089
1090 unsafe {
1091 clap_call! { out=>try_push(out, &event.header) }
1092 }
1093 }
1094 OutputParamEvent::SetValue {
1095 param_hash,
1096 clap_plain_value,
1097 } => {
1098 self.update_plain_value_by_hash(
1099 param_hash,
1100 ClapParamUpdate::PlainValueSet(clap_plain_value),
1101 sample_rate,
1102 );
1103
1104 let event = clap_event_param_value {
1105 header: clap_event_header {
1106 size: mem::size_of::<clap_event_param_value>() as u32,
1107 time: current_sample_idx as u32,
1108 space_id: CLAP_CORE_EVENT_SPACE_ID,
1109 type_: CLAP_EVENT_PARAM_VALUE,
1110 flags: CLAP_EVENT_IS_LIVE,
1111 },
1112 param_id: param_hash,
1113 cookie: std::ptr::null_mut(),
1114 port_index: -1,
1115 note_id: -1,
1116 channel: -1,
1117 key: -1,
1118 value: clap_plain_value,
1119 };
1120
1121 unsafe {
1122 clap_call! { out=>try_push(out, &event.header) }
1123 }
1124 }
1125 OutputParamEvent::EndGesture { param_hash } => {
1126 let event = clap_event_param_gesture {
1127 header: clap_event_header {
1128 size: mem::size_of::<clap_event_param_gesture>() as u32,
1129 time: current_sample_idx as u32,
1130 space_id: CLAP_CORE_EVENT_SPACE_ID,
1131 type_: CLAP_EVENT_PARAM_GESTURE_END,
1132 flags: CLAP_EVENT_IS_LIVE,
1133 },
1134 param_id: param_hash,
1135 };
1136
1137 unsafe {
1138 clap_call! { out=>try_push(out, &event.header) }
1139 }
1140 }
1141 };
1142
1143 crate::nice_debug_assert!(push_successful);
1144 }
1145 }
1146
1147 pub unsafe fn handle_input_event(
1161 &self,
1162 event: *const clap_event_header,
1163 input_events: &mut AtomicRefMut<VecDeque<PluginNoteEvent<P>>>,
1164 transport_info: Option<&mut *const clap_event_transport>,
1165 current_sample_idx: usize,
1166 total_buffer_len: usize,
1167 ) {
1168 let raw_event = unsafe { &*event };
1169
1170 let timing = clamp_input_event_timing(
1172 raw_event.time - current_sample_idx as u32,
1173 total_buffer_len as u32,
1174 );
1175
1176 let push_event = |input_events: &mut AtomicRefMut<VecDeque<PluginNoteEvent<P>>>,
1177 event: PluginNoteEvent<P>| {
1178 permit_alloc(|| {
1179 if input_events.len() == input_events.capacity() {
1182 crate::nice_warn!(
1183 "Input event buffer filled up! This will cause an allocation."
1184 );
1185 }
1186 input_events.push_back(event);
1187 });
1188 };
1189
1190 fn voice_from_i32(v: i32) -> VoiceID {
1191 if v >= 0 {
1192 VoiceID::ID(v)
1193 } else {
1194 VoiceID::Wildcard
1195 }
1196 }
1197 fn channel_from_i16(c: i16) -> Channel {
1198 if (0..=15).contains(&c) {
1199 Channel::Number(c as u8)
1200 } else {
1201 Channel::Wildcard
1202 }
1203 }
1204 fn key_from_i16(k: i16) -> Key {
1205 if (0..=127).contains(&k) {
1206 Key::Number(k as u8)
1207 } else {
1208 Key::Wildcard
1209 }
1210 }
1211
1212 match (raw_event.space_id, raw_event.type_) {
1213 (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_PARAM_VALUE) => {
1214 let event = unsafe { &*(event as *const clap_event_param_value) };
1215 self.update_plain_value_by_hash(
1216 event.param_id,
1217 ClapParamUpdate::PlainValueSet(event.value),
1218 self.current_buffer_config.load().map(|c| c.sample_rate),
1219 );
1220
1221 if let Some(poly_modulation_id) = self.poly_mod_ids_by_hash.get(&event.param_id) {
1226 let param_ptr = self.param_by_hash[&event.param_id];
1229 let normalized_value =
1230 event.value as f32 / unsafe { param_ptr.step_count().unwrap_or(1) as f32 };
1231
1232 push_event(
1233 input_events,
1234 NoteEvent::MonoAutomation {
1235 timing,
1236 poly_modulation_id: *poly_modulation_id,
1237 normalized_value,
1238 },
1239 );
1240 }
1241 }
1242 (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_PARAM_MOD) => {
1243 let event = unsafe { &*(event as *const clap_event_param_mod) };
1244
1245 if event.note_id != -1 && P::MIDI_INPUT >= MidiConfig::Basic {
1246 match self.poly_mod_ids_by_hash.get(&event.param_id) {
1247 Some(poly_modulation_id) => {
1248 let param_ptr = self.param_by_hash[&event.param_id];
1251 let normalized_offset = event.amount as f32
1252 / unsafe { param_ptr.step_count().unwrap_or(1) as f32 };
1253
1254 push_event(
1258 input_events,
1259 NoteEvent::PolyModulation {
1260 timing,
1261 voice_id: event.note_id,
1262 poly_modulation_id: *poly_modulation_id,
1263 normalized_offset,
1264 },
1265 );
1266
1267 return;
1268 }
1269 None => crate::nice_debug_assert_failure!(
1270 "Polyphonic modulation sent for a parameter without a poly modulation \
1271 ID"
1272 ),
1273 }
1274 }
1275
1276 self.update_plain_value_by_hash(
1277 event.param_id,
1278 ClapParamUpdate::PlainValueMod(event.amount),
1279 self.current_buffer_config.load().map(|c| c.sample_rate),
1280 );
1281 }
1282 (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_TRANSPORT) => {
1283 let event = unsafe { &*(event as *const clap_event_transport) };
1284 if let Some(transport_info) = transport_info {
1285 *transport_info = event;
1286 }
1287 }
1288 (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_NOTE_ON) => {
1289 if P::MIDI_INPUT >= MidiConfig::Basic {
1290 let event = unsafe { &*(event as *const clap_event_note) };
1291
1292 push_event(
1293 input_events,
1294 NoteEvent::NoteOn {
1295 timing,
1298 voice_id: voice_from_i32(event.note_id),
1299 channel: channel_from_i16(event.channel),
1300 key: key_from_i16(event.key),
1301 velocity: event.velocity as f32,
1302 },
1303 );
1304 }
1305 }
1306 (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_NOTE_OFF) => {
1307 if P::MIDI_INPUT >= MidiConfig::Basic {
1308 let event = unsafe { &*(event as *const clap_event_note) };
1309
1310 push_event(
1311 input_events,
1312 NoteEvent::NoteOff {
1313 timing,
1314 voice_id: voice_from_i32(event.note_id),
1315 channel: channel_from_i16(event.channel),
1316 key: key_from_i16(event.key),
1317 velocity: event.velocity as f32,
1318 },
1319 );
1320 }
1321 }
1322 (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_NOTE_CHOKE) => {
1323 if P::MIDI_INPUT >= MidiConfig::Basic {
1324 let event = unsafe { &*(event as *const clap_event_note) };
1325
1326 push_event(
1327 input_events,
1328 NoteEvent::Choke {
1329 timing,
1330 voice_id: voice_from_i32(event.note_id),
1331 channel: channel_from_i16(event.channel),
1332 key: key_from_i16(event.key),
1333 },
1334 );
1335 }
1336 }
1337 (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_NOTE_EXPRESSION) => {
1338 if P::MIDI_INPUT >= MidiConfig::Basic {
1339 let event = unsafe { &*(event as *const clap_event_note_expression) };
1341 match event.expression_id {
1342 CLAP_NOTE_EXPRESSION_PRESSURE => {
1343 push_event(
1344 input_events,
1345 NoteEvent::PolyPressure {
1346 timing,
1347 voice_id: voice_from_i32(event.note_id),
1348 channel: channel_from_i16(event.channel),
1349 key: key_from_i16(event.key),
1350 pressure: event.value as f32,
1351 },
1352 );
1353 }
1354 CLAP_NOTE_EXPRESSION_VOLUME => {
1355 push_event(
1356 input_events,
1357 NoteEvent::PolyVolume {
1358 timing,
1359 voice_id: voice_from_i32(event.note_id),
1360 channel: channel_from_i16(event.channel),
1361 key: key_from_i16(event.key),
1362 gain: event.value as f32,
1363 },
1364 );
1365 }
1366 CLAP_NOTE_EXPRESSION_PAN => {
1367 push_event(
1368 input_events,
1369 NoteEvent::PolyPan {
1370 timing,
1371 voice_id: voice_from_i32(event.note_id),
1372 channel: channel_from_i16(event.channel),
1373 key: key_from_i16(event.key),
1374 pan: (event.value as f32 * 2.0) - 1.0,
1376 },
1377 );
1378 }
1379 CLAP_NOTE_EXPRESSION_TUNING => {
1380 push_event(
1381 input_events,
1382 NoteEvent::PolyTuning {
1383 timing,
1384 voice_id: voice_from_i32(event.note_id),
1385 channel: channel_from_i16(event.channel),
1386 key: key_from_i16(event.key),
1387 tuning: event.value as f32,
1388 },
1389 );
1390 }
1391 CLAP_NOTE_EXPRESSION_VIBRATO => {
1392 push_event(
1393 input_events,
1394 NoteEvent::PolyVibrato {
1395 timing,
1396 voice_id: voice_from_i32(event.note_id),
1397 channel: channel_from_i16(event.channel),
1398 key: key_from_i16(event.key),
1399 vibrato: event.value as f32,
1400 },
1401 );
1402 }
1403 CLAP_NOTE_EXPRESSION_EXPRESSION => {
1404 push_event(
1405 input_events,
1406 NoteEvent::PolyExpression {
1407 timing,
1408 voice_id: voice_from_i32(event.note_id),
1409 channel: channel_from_i16(event.channel),
1410 key: key_from_i16(event.key),
1411 expression: event.value as f32,
1412 },
1413 );
1414 }
1415 CLAP_NOTE_EXPRESSION_BRIGHTNESS => {
1416 push_event(
1417 input_events,
1418 NoteEvent::PolyBrightness {
1419 timing,
1420 voice_id: voice_from_i32(event.note_id),
1421 channel: channel_from_i16(event.channel),
1422 key: key_from_i16(event.key),
1423 brightness: event.value as f32,
1424 },
1425 );
1426 }
1427 n => {
1428 crate::nice_trace!("Unhandled note expression ID {}", n)
1429 }
1430 }
1431 }
1432 }
1433 (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_MIDI) => {
1434 let event = unsafe { &*(event as *const clap_event_midi) };
1438
1439 match NoteEvent::from_midi(timing, &event.data) {
1440 Ok(
1441 note_event @ (NoteEvent::NoteOn { .. }
1442 | NoteEvent::NoteOff { .. }
1443 | NoteEvent::PolyPressure { .. }),
1444 ) if P::MIDI_INPUT >= MidiConfig::Basic => {
1445 push_event(input_events, note_event);
1446 }
1447 Ok(note_event) if P::MIDI_INPUT >= MidiConfig::MidiCCs => {
1448 push_event(input_events, note_event);
1449 }
1450 Ok(_) => (),
1451 Err(n) => {
1452 crate::nice_trace!("Unhandled MIDI message type {}", n)
1453 }
1454 };
1455 }
1456 (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_MIDI_SYSEX)
1457 if P::MIDI_INPUT >= MidiConfig::Basic =>
1458 {
1459 let event = unsafe { &*(event as *const clap_event_midi_sysex) };
1460
1461 assert!(!event.buffer.is_null());
1464 let sysex_buffer =
1465 unsafe { std::slice::from_raw_parts(event.buffer, event.size as usize) };
1466 if let Ok(note_event) = NoteEvent::from_midi(timing, sysex_buffer) {
1467 push_event(input_events, note_event);
1468 };
1469 }
1470 _ => {
1471 crate::nice_trace!(
1472 "Unhandled CLAP event type {} for namespace {}",
1473 raw_event.type_,
1474 raw_event.space_id
1475 );
1476 }
1477 }
1478 }
1479
1480 pub fn get_state_object(&self) -> PluginState {
1484 unsafe {
1485 state::serialize_object::<P>(
1486 self.params.clone(),
1487 state::make_params_iter(&self.param_by_hash, &self.param_id_to_hash),
1488 )
1489 }
1490 }
1491
1492 pub fn set_state_object_from_gui(&self, mut state: PluginState) {
1496 let mut did_set_state_inner = false;
1497
1498 loop {
1501 if self.is_processing.load(Ordering::SeqCst) {
1502 match self
1508 .updated_state_sender
1509 .send_timeout(state, Duration::from_secs(1))
1510 {
1511 Ok(_) => {
1512 let state = self.updated_state_receiver.recv();
1515 drop(state);
1516 break;
1517 }
1518 Err(SendTimeoutError::Timeout(value)) => {
1519 state = value;
1520 continue;
1521 }
1522 Err(SendTimeoutError::Disconnected(_)) => {
1523 crate::nice_debug_assert_failure!("State update channel got disconnected");
1524 return;
1525 }
1526 }
1527 } else {
1528 self.set_state_inner(&mut state);
1531 did_set_state_inner = true;
1532 break;
1533 }
1534 }
1535
1536 if !did_set_state_inner {
1537 let task_posted = self.schedule_gui(Task::RescanParamValues);
1539 crate::nice_debug_assert!(task_posted, "The task queue is full, dropping task...");
1540 } }
1542
1543 pub fn set_latency_samples(&self, samples: u32) {
1544 let old_latency = self.current_latency.swap(samples, Ordering::SeqCst);
1548 if old_latency != samples {
1549 let task_posted = self.schedule_gui(Task::LatencyChanged);
1550 crate::nice_debug_assert!(task_posted, "The task queue is full, dropping task...");
1551 }
1552 }
1553
1554 pub fn set_current_voice_capacity(&self, capacity: u32) {
1555 match P::CLAP_POLY_MODULATION_CONFIG {
1556 Some(config) => {
1557 let clamped_capacity = capacity.clamp(1, config.max_voice_capacity);
1558 crate::nice_debug_assert_eq!(
1559 capacity,
1560 clamped_capacity,
1561 "The current voice capacity must be between 1 and the maximum capacity"
1562 );
1563
1564 if clamped_capacity != self.current_voice_capacity.load(Ordering::Relaxed) {
1565 self.current_voice_capacity
1566 .store(clamped_capacity, Ordering::SeqCst);
1567 let task_posted = self.schedule_gui(Task::VoiceInfoChanged);
1568 crate::nice_debug_assert!(
1569 task_posted,
1570 "The task queue is full, dropping task..."
1571 );
1572 }
1573 }
1574 None => crate::nice_debug_assert_failure!(
1575 "Configuring the current voice capacity is only possible when \
1576 'ClapPlugin::CLAP_POLY_MODULATION_CONFIG' is set"
1577 ),
1578 }
1579 }
1580
1581 #[cfg(feature = "editor")]
1583 fn update_track_info_from_host(&self) {
1584 let host_track_info = self.host_track_info.borrow();
1585 let Some(host_track_info) = host_track_info.as_ref() else {
1586 return;
1587 };
1588
1589 let editor = self.editor.borrow();
1590 let Some(editor) = editor.as_ref() else {
1591 return;
1592 };
1593
1594 permit_alloc(|| {
1595 let mut clap_info: clap_track_info = unsafe { mem::zeroed() };
1596 let success = unsafe_clap_call! {
1597 host_track_info=>get(&*self.host_callback, &mut clap_info)
1598 };
1599 if !success {
1600 return;
1601 }
1602
1603 let mut current_track_info = self.current_track_info.borrow_mut();
1604 let mut name = current_track_info.name().to_owned();
1605 let mut color = current_track_info.color();
1606
1607 if clap_info.flags & CLAP_TRACK_INFO_HAS_TRACK_NAME != 0 {
1608 let name_bytes = unsafe {
1609 std::slice::from_raw_parts(
1610 clap_info.name.as_ptr().cast::<u8>(),
1611 clap_sys::string_sizes::CLAP_NAME_SIZE,
1612 )
1613 };
1614 if let Ok(cstr) = CStr::from_bytes_until_nul(name_bytes) {
1615 name = cstr.to_string_lossy().into_owned()
1616 } }
1618
1619 if clap_info.flags & CLAP_TRACK_INFO_HAS_TRACK_COLOR != 0 {
1620 color = Some(TrackColor::new(
1621 clap_info.color.red,
1622 clap_info.color.green,
1623 clap_info.color.blue,
1624 clap_info.color.alpha,
1625 ));
1626 }
1627
1628 let track_info = TrackInfo::new(name, color);
1629 *current_track_info = track_info.clone();
1630
1631 editor.lock().track_info_updated(track_info);
1632 });
1633 }
1634
1635 pub fn set_state_inner(&self, state: &mut PluginState) -> bool {
1646 let success = permit_alloc(|| unsafe {
1651 state::deserialize_object::<P>(
1652 state,
1653 self.params.clone(),
1654 state::make_params_getter(&self.param_by_hash, &self.param_id_to_hash),
1655 self.current_buffer_config.load().as_ref(),
1656 )
1657 });
1658 if !success {
1659 crate::nice_debug_assert_failure!(
1660 "Deserializing plugin state from a state object failed"
1661 );
1662 return false;
1663 }
1664
1665 let task_posted = self.schedule_gui(Task::StateChanged);
1668 crate::nice_debug_assert!(task_posted, "The task queue is full, dropping task...");
1669
1670 success
1671 }
1672
1673 pub fn request_restart(&self) {
1674 unsafe_clap_call! { &*self.host_callback=>request_restart(&*self.host_callback) };
1675 }
1676
1677 unsafe extern "C" fn init(plugin: *const clap_plugin) -> bool {
1678 check_null_ptr!(false, plugin, unsafe { (*plugin).plugin_data });
1679 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
1680
1681 unsafe {
1683 #[cfg(feature = "editor")]
1684 {
1685 *wrapper.host_gui.borrow_mut() = query_host_extension::<
1686 clap_sys::ext::gui::clap_host_gui,
1687 >(
1688 &wrapper.host_callback, CLAP_EXT_GUI
1689 );
1690
1691 *wrapper.host_track_info.borrow_mut() = query_host_extension::<clap_host_track_info>(
1692 &wrapper.host_callback,
1693 CLAP_EXT_TRACK_INFO,
1694 );
1695 }
1696 *wrapper.host_latency.borrow_mut() =
1697 query_host_extension::<clap_host_latency>(&wrapper.host_callback, CLAP_EXT_LATENCY);
1698 *wrapper.host_params.borrow_mut() =
1699 query_host_extension::<clap_host_params>(&wrapper.host_callback, CLAP_EXT_PARAMS);
1700 *wrapper.host_voice_info.borrow_mut() = query_host_extension::<clap_host_voice_info>(
1701 &wrapper.host_callback,
1702 CLAP_EXT_VOICE_INFO,
1703 );
1704 *wrapper.host_thread_check.borrow_mut() = query_host_extension::<clap_host_thread_check>(
1705 &wrapper.host_callback,
1706 CLAP_EXT_THREAD_CHECK,
1707 );
1708 }
1709
1710 #[cfg(feature = "editor")]
1711 wrapper.update_track_info_from_host();
1712
1713 true
1714 }
1715
1716 unsafe extern "C" fn destroy(plugin: *const clap_plugin) {
1717 assert!(!plugin.is_null() && unsafe { !(*plugin).plugin_data.is_null() });
1718 let this = unsafe { Arc::from_raw((*plugin).plugin_data as *mut Self) };
1719 crate::nice_debug_assert_eq!(Arc::strong_count(&this), 1);
1720
1721 drop(this);
1722 }
1723
1724 unsafe extern "C" fn activate(
1725 plugin: *const clap_plugin,
1726 sample_rate: f64,
1727 min_frames_count: u32,
1728 max_frames_count: u32,
1729 ) -> bool {
1730 check_null_ptr!(false, plugin, unsafe { (*plugin).plugin_data });
1731 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
1732
1733 let audio_io_layout = wrapper.current_audio_io_layout.load();
1734 let buffer_config = BufferConfig {
1735 sample_rate: sample_rate as f32,
1736 min_buffer_size: Some(min_frames_count),
1737 max_buffer_size: max_frames_count,
1738 process_mode: wrapper.current_process_mode.load(),
1739 };
1740
1741 for param in wrapper.param_by_hash.values() {
1743 unsafe { param._internal_update_smoother(buffer_config.sample_rate, true) };
1744 }
1745
1746 if wrapper.latency_changed.swap(false, Ordering::SeqCst)
1749 && let Some(host_latency) = &*wrapper.host_latency.borrow()
1750 {
1751 unsafe_clap_call! { host_latency=>changed(&*wrapper.host_callback) };
1752 }
1753
1754 let mut activate_context = wrapper.make_activate_context();
1755
1756 let now = Instant::now();
1759 let mut result = false;
1760 loop {
1761 if let Some(mut plugin) = wrapper.plugin.try_lock() {
1762 if plugin.activate(&audio_io_layout, &buffer_config, &mut activate_context) {
1763 let now_2 = Instant::now();
1769 loop {
1770 if let Ok(mut buffer_manager) = wrapper.buffer_manager.try_borrow_mut() {
1771 *buffer_manager = BufferManager::for_audio_io_layout(
1774 max_frames_count as usize,
1775 audio_io_layout,
1776 );
1777
1778 wrapper.current_buffer_config.store(Some(buffer_config));
1780
1781 wrapper.is_activated.store(true, Ordering::SeqCst);
1782
1783 result = true;
1784
1785 break;
1786 } else if now_2.elapsed() > Duration::from_secs(1) {
1787 crate::nice_error!(
1788 "Failed to acquire lock on buffers while activating"
1789 );
1790 break;
1791 } else {
1792 std::thread::sleep(Duration::from_millis(1));
1793 }
1794 }
1795 }
1796
1797 break;
1798 } else if now.elapsed() > Duration::from_secs(1) {
1799 crate::nice_error!("Failed to acquire lock on plugin while activating");
1800 break;
1801 } else {
1802 std::thread::sleep(Duration::from_millis(1));
1803 }
1804 }
1805
1806 drop(activate_context);
1808
1809 result
1810 }
1811
1812 unsafe extern "C" fn deactivate(plugin: *const clap_plugin) {
1813 check_null_ptr!((), plugin, unsafe { (*plugin).plugin_data });
1814 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
1815
1816 let now = Instant::now();
1819 loop {
1820 if let Some(mut plugin) = wrapper.plugin.try_lock() {
1821 plugin.deactivate();
1822 break;
1823 } else if now.elapsed() > Duration::from_secs(1) {
1824 crate::nice_error!("Failed to acquire lock on plugin while deactivating");
1825 break;
1826 } else {
1827 std::thread::sleep(Duration::from_millis(1));
1828 }
1829 }
1830
1831 wrapper.is_activated.store(false, Ordering::SeqCst);
1832 }
1833
1834 unsafe extern "C" fn start_processing(plugin: *const clap_plugin) -> bool {
1835 check_null_ptr!(false, plugin, unsafe { (*plugin).plugin_data });
1838 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
1839
1840 wrapper.last_process_status.store(ProcessStatus::Normal);
1842 wrapper.is_processing.store(true, Ordering::SeqCst);
1843
1844 process_wrapper(|| {
1847 let now = Instant::now();
1850 loop {
1851 if let Some(mut plugin) = wrapper.plugin.try_lock() {
1852 plugin.reset();
1853 break;
1854 } else if now.elapsed() > Duration::from_millis(200) {
1855 crate::nice_error!(
1856 "Failed to acquire lock on plugin while starting processing"
1857 );
1858 break;
1859 } else {
1860 std::thread::sleep(Duration::from_millis(1));
1861 }
1862 }
1863 });
1864
1865 true
1866 }
1867
1868 unsafe extern "C" fn stop_processing(plugin: *const clap_plugin) {
1869 check_null_ptr!((), plugin, unsafe { (*plugin).plugin_data });
1870 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
1871
1872 wrapper.is_processing.store(false, Ordering::SeqCst);
1873
1874 process_wrapper(|| {
1875 let now = Instant::now();
1878 loop {
1879 if let Some(mut plugin) = wrapper.plugin.try_lock() {
1880 plugin.stop_processing();
1881 break;
1882 } else if now.elapsed() > Duration::from_millis(200) {
1883 crate::nice_error!(
1884 "Failed to acquire lock on plugin while stopping processing"
1885 );
1886 break;
1887 } else {
1888 std::thread::sleep(Duration::from_millis(1));
1889 }
1890 }
1891 });
1892 }
1893
1894 unsafe extern "C" fn reset(plugin: *const clap_plugin) {
1895 check_null_ptr!((), plugin, unsafe { (*plugin).plugin_data });
1896 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
1897
1898 process_wrapper(|| {
1899 let now = Instant::now();
1902 loop {
1903 if let Some(mut plugin) = wrapper.plugin.try_lock() {
1904 plugin.reset();
1905 break;
1906 } else if now.elapsed() > Duration::from_millis(200) {
1907 crate::nice_error!("Failed to acquire lock on plugin while resetting");
1908 break;
1909 } else {
1910 std::thread::sleep(Duration::from_millis(1));
1911 }
1912 }
1913 });
1914 }
1915
1916 unsafe extern "C" fn process(
1917 plugin: *const clap_plugin,
1918 process: *const clap_process,
1919 ) -> clap_process_status {
1920 check_null_ptr!(
1921 CLAP_PROCESS_ERROR,
1922 plugin,
1923 unsafe { (*plugin).plugin_data },
1924 process
1925 );
1926 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
1927
1928 process_wrapper(|| {
1931 let process = unsafe { &*process };
1935 let total_buffer_len = process.frames_count as usize;
1936
1937 let current_audio_io_layout = wrapper.current_audio_io_layout.load();
1938 let has_main_input = current_audio_io_layout.main_input_channels.is_some();
1939 let has_main_output = current_audio_io_layout.main_output_channels.is_some();
1940 let aux_input_start_idx = if has_main_input { 1 } else { 0 };
1941 let aux_output_start_idx = if has_main_output { 1 } else { 0 };
1942
1943 let mut block_start = 0;
1946 let mut block_end = total_buffer_len;
1947 let mut event_start_idx = 0;
1948
1949 let mut transport_info = process.transport;
1952
1953 let result = loop {
1954 if !process.in_events.is_null() {
1955 let split_result = unsafe {
1956 wrapper.handle_in_events_until(
1957 &*process.in_events,
1958 &mut transport_info,
1959 block_start,
1960 total_buffer_len,
1961 event_start_idx,
1962 |next_event| {
1963 if P::SAMPLE_ACCURATE_AUTOMATION {
1968 match ((*next_event).space_id, (*next_event).type_) {
1969 (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_PARAM_VALUE)
1970 | (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_TRANSPORT) => true,
1971 (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_PARAM_MOD) => {
1972 let next_event =
1973 &*(next_event as *const clap_event_param_mod);
1974
1975 !(next_event.note_id >= 0
1978 && wrapper
1979 .poly_mod_ids_by_hash
1980 .contains_key(&next_event.param_id))
1981 }
1982 _ => false,
1983 }
1984 } else {
1985 matches!(
1986 ((*next_event).space_id, (*next_event).type_,),
1987 (CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_TRANSPORT)
1988 )
1989 }
1990 },
1991 )
1992 };
1993
1994 match split_result {
1999 Some((next_param_change_sample_idx, next_param_change_event_idx)) => {
2000 block_end = next_param_change_sample_idx;
2001 event_start_idx = next_param_change_event_idx;
2002 }
2003 None => block_end = total_buffer_len,
2004 }
2005 }
2006
2007 let block_len = block_end - block_start;
2010
2011 let Ok(mut buffer_manager) = wrapper.buffer_manager.try_borrow_mut() else {
2012 crate::nice_error!(
2015 "Host tried to activate/deactivate plugin while process method is still \
2016 running"
2017 );
2018
2019 return CLAP_PROCESS_ERROR;
2020 };
2021
2022 let buffers = unsafe {
2028 buffer_manager.create_buffers(block_start, block_len, |buffer_source| {
2029 if process.audio_outputs_count > 0
2033 && !process.audio_outputs.is_null()
2034 && !(*process.audio_outputs).data32.is_null()
2035 && has_main_output
2036 {
2037 let audio_output = &*process.audio_outputs;
2038 let ptrs = NonNull::new(audio_output.data32).unwrap();
2039 let num_channels = audio_output.channel_count as usize;
2040
2041 *buffer_source.main_output_channel_pointers =
2042 Some(ChannelPointers { ptrs, num_channels });
2043 }
2044
2045 if process.audio_inputs_count > 0
2046 && !process.audio_inputs.is_null()
2047 && !(*process.audio_inputs).data32.is_null()
2048 && has_main_input
2049 {
2050 let audio_input = &*process.audio_inputs;
2051 let ptrs = NonNull::new(audio_input.data32).unwrap();
2052 let num_channels = audio_input.channel_count as usize;
2053
2054 *buffer_source.main_input_channel_pointers =
2055 Some(ChannelPointers { ptrs, num_channels });
2056 }
2057
2058 if !process.audio_inputs.is_null() {
2059 for (aux_input_no, aux_input_channel_pointers) in buffer_source
2060 .aux_input_channel_pointers
2061 .iter_mut()
2062 .enumerate()
2063 {
2064 let aux_input_idx = aux_input_no + aux_input_start_idx;
2065 if aux_input_idx > process.audio_inputs_count as usize {
2066 break;
2067 }
2068
2069 let audio_input = &*process.audio_inputs.add(aux_input_idx);
2070 match NonNull::new(audio_input.data32) {
2071 Some(ptrs) => {
2072 let num_channels = audio_input.channel_count as usize;
2073
2074 *aux_input_channel_pointers =
2075 Some(ChannelPointers { ptrs, num_channels });
2076 }
2077 None => continue,
2078 }
2079 }
2080 }
2081
2082 if !process.audio_outputs.is_null() {
2083 for (aux_output_no, aux_output_channel_pointers) in buffer_source
2084 .aux_output_channel_pointers
2085 .iter_mut()
2086 .enumerate()
2087 {
2088 let aux_output_idx = aux_output_no + aux_output_start_idx;
2089 if aux_output_idx > process.audio_outputs_count as usize {
2090 break;
2091 }
2092
2093 let audio_output = &*process.audio_outputs.add(aux_output_idx);
2094 match NonNull::new(audio_output.data32) {
2095 Some(ptrs) => {
2096 let num_channels = audio_output.channel_count as usize;
2097
2098 *aux_output_channel_pointers =
2099 Some(ChannelPointers { ptrs, num_channels });
2100 }
2101 None => continue,
2102 }
2103 }
2104 }
2105 })
2106 };
2107
2108 let mut buffer_is_valid = true;
2115 for output_buffer_slice in buffers.main_buffer.as_slice_immutable().iter().chain(
2116 buffers
2117 .aux_outputs
2118 .iter()
2119 .flat_map(|buffer| buffer.as_slice_immutable().iter()),
2120 ) {
2121 if output_buffer_slice.is_empty() {
2122 buffer_is_valid = false;
2123 break;
2124 }
2125 }
2126
2127 crate::nice_debug_assert!(buffer_is_valid);
2128
2129 let sample_rate = wrapper
2133 .current_buffer_config
2134 .load()
2135 .expect("Process call without prior initialization call")
2136 .sample_rate;
2137 let mut transport = Transport::new(sample_rate);
2138 if !transport_info.is_null() {
2139 let context = unsafe { &*transport_info };
2140
2141 transport.playing = context.flags & CLAP_TRANSPORT_IS_PLAYING != 0;
2142 transport.recording = context.flags & CLAP_TRANSPORT_IS_RECORDING != 0;
2143 transport.preroll_active =
2144 Some(context.flags & CLAP_TRANSPORT_IS_WITHIN_PRE_ROLL != 0);
2145 if context.flags & CLAP_TRANSPORT_HAS_TEMPO != 0 {
2146 transport.tempo = Some(context.tempo);
2147 }
2148 if context.flags & CLAP_TRANSPORT_HAS_TIME_SIGNATURE != 0 {
2149 transport.time_sig_numerator = Some(context.tsig_num as i32);
2150 transport.time_sig_denominator = Some(context.tsig_denom as i32);
2151 }
2152 if context.flags & CLAP_TRANSPORT_HAS_BEATS_TIMELINE != 0 {
2153 let beats = context.song_pos_beats as f64 / CLAP_BEATTIME_FACTOR as f64;
2154
2155 if P::SAMPLE_ACCURATE_AUTOMATION
2159 && block_start > 0
2160 && (context.flags & CLAP_TRANSPORT_HAS_TEMPO != 0)
2161 {
2162 transport.pos_beats = Some(
2163 beats
2164 + (block_start as f64 / sample_rate as f64 / 60.0
2165 * context.tempo),
2166 );
2167 } else {
2168 transport.pos_beats = Some(beats);
2169 }
2170 }
2171 if context.flags & CLAP_TRANSPORT_HAS_SECONDS_TIMELINE != 0 {
2172 let seconds = context.song_pos_seconds as f64 / CLAP_SECTIME_FACTOR as f64;
2173
2174 if P::SAMPLE_ACCURATE_AUTOMATION
2176 && block_start > 0
2177 && (context.flags & CLAP_TRANSPORT_HAS_TEMPO != 0)
2178 {
2179 transport.pos_seconds =
2180 Some(seconds + (block_start as f64 / sample_rate as f64));
2181 } else {
2182 transport.pos_seconds = Some(seconds);
2183 }
2184 }
2185 if P::SAMPLE_ACCURATE_AUTOMATION && block_start > 0 {
2187 transport.bar_start_pos_beats = match transport.bar_start_pos_beats() {
2188 Some(updated) => Some(updated),
2189 None => Some(context.bar_start as f64 / CLAP_BEATTIME_FACTOR as f64),
2190 };
2191 transport.bar_number = match transport.bar_number() {
2192 Some(updated) => Some(updated),
2193 None => Some(context.bar_number),
2194 };
2195 } else {
2196 transport.bar_start_pos_beats =
2197 Some(context.bar_start as f64 / CLAP_BEATTIME_FACTOR as f64);
2198 transport.bar_number = Some(context.bar_number);
2199 }
2200 if context.flags & CLAP_TRANSPORT_IS_LOOP_ACTIVE != 0
2204 && context.flags & CLAP_TRANSPORT_HAS_BEATS_TIMELINE != 0
2205 {
2206 transport.loop_range_beats = Some((
2207 context.loop_start_beats as f64 / CLAP_BEATTIME_FACTOR as f64,
2208 context.loop_end_beats as f64 / CLAP_BEATTIME_FACTOR as f64,
2209 ));
2210 }
2211 if context.flags & CLAP_TRANSPORT_IS_LOOP_ACTIVE != 0
2212 && context.flags & CLAP_TRANSPORT_HAS_SECONDS_TIMELINE != 0
2213 {
2214 transport.loop_range_seconds = Some((
2215 context.loop_start_seconds as f64 / CLAP_SECTIME_FACTOR as f64,
2216 context.loop_end_seconds as f64 / CLAP_SECTIME_FACTOR as f64,
2217 ));
2218 }
2219 }
2220
2221 let result = if buffer_is_valid {
2222 let Some(mut plugin) = wrapper.plugin.try_lock() else {
2223 crate::nice_error!(
2226 "Host tried to activate/deactivate plugin while process method is \
2227 still running"
2228 );
2229
2230 return CLAP_PROCESS_ERROR;
2231 };
2232
2233 let mut aux = AuxiliaryBuffers {
2237 inputs: buffers.aux_inputs,
2238 outputs: buffers.aux_outputs,
2239 };
2240
2241 let mut context = wrapper.make_process_context(
2242 transport,
2243 total_buffer_len,
2244 block_start,
2245 process.out_events,
2246 );
2247
2248 let result = plugin.process(buffers.main_buffer, &mut aux, &mut context);
2249
2250 wrapper.last_process_status.store(result);
2251 result
2252 } else {
2253 ProcessStatus::Normal
2254 };
2255
2256 let clap_result = match result {
2257 ProcessStatus::Error(err) => {
2258 crate::nice_debug_assert_failure!("Process error: {}", err);
2259
2260 return CLAP_PROCESS_ERROR;
2261 }
2262 ProcessStatus::Normal => CLAP_PROCESS_CONTINUE_IF_NOT_QUIET,
2263 ProcessStatus::Tail(_) => CLAP_PROCESS_CONTINUE,
2264 ProcessStatus::KeepAlive => CLAP_PROCESS_CONTINUE,
2265 };
2266
2267 if !process.out_events.is_null() && !wrapper.output_parameter_events.is_empty() {
2268 unsafe { wrapper.handle_out_events(&*process.out_events, block_start) };
2269 }
2270
2271 if block_end == total_buffer_len {
2275 break clap_result;
2276 } else {
2277 block_start = block_end;
2278 }
2279 };
2280
2281 let updated_state = permit_alloc(|| wrapper.updated_state_receiver.try_recv());
2288 if let Ok(mut state) = updated_state {
2289 wrapper.set_state_inner(&mut state);
2290
2291 if let Err(err) = wrapper.updated_state_sender.send(state) {
2294 crate::nice_debug_assert_failure!(
2295 "Failed to send state object back to GUI thread: {}",
2296 err
2297 );
2298 };
2299 }
2300
2301 result
2302 })
2303 }
2304
2305 unsafe extern "C" fn get_extension(
2306 plugin: *const clap_plugin,
2307 id: *const c_char,
2308 ) -> *const c_void {
2309 check_null_ptr!(
2310 std::ptr::null(),
2311 plugin,
2312 unsafe { (*plugin).plugin_data },
2313 id
2314 );
2315 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
2316
2317 let id = unsafe { CStr::from_ptr(id) };
2318
2319 if id == CLAP_EXT_PARAMS {
2320 &wrapper.clap_plugin_params as *const _ as *const c_void
2321 } else if id == CLAP_EXT_TAIL {
2322 &wrapper.clap_plugin_tail as *const _ as *const c_void
2323 } else if id == CLAP_EXT_GUI {
2324 #[cfg(not(feature = "editor"))]
2325 return std::ptr::null();
2326
2327 #[cfg(feature = "editor")]
2328 if wrapper.editor.borrow().is_some() {
2329 &wrapper.clap_plugin_gui as *const _ as *const c_void
2331 } else {
2332 std::ptr::null()
2333 }
2334 } else if id == CLAP_EXT_AUDIO_PORTS_CONFIG {
2335 &wrapper.clap_plugin_audio_ports_config as *const _ as *const c_void
2336 } else if id == CLAP_EXT_AUDIO_PORTS {
2337 &wrapper.clap_plugin_audio_ports as *const _ as *const c_void
2338 } else if id == CLAP_EXT_LATENCY {
2339 &wrapper.clap_plugin_latency as *const _ as *const c_void
2340 } else if id == CLAP_EXT_NOTE_PORTS {
2341 if P::MIDI_INPUT >= MidiConfig::Basic || P::MIDI_OUTPUT >= MidiConfig::Basic {
2342 &wrapper.clap_plugin_note_ports as *const _ as *const c_void
2343 } else {
2344 std::ptr::null()
2345 }
2346 } else if id == CLAP_EXT_REMOTE_CONTROLS {
2347 &wrapper.clap_plugin_remote_controls as *const _ as *const c_void
2348 } else if id == CLAP_EXT_RENDER {
2349 &wrapper.clap_plugin_render as *const _ as *const c_void
2350 } else if id == CLAP_EXT_STATE {
2351 &wrapper.clap_plugin_state as *const _ as *const c_void
2352 } else if id == CLAP_EXT_TRACK_INFO {
2353 #[cfg(not(feature = "editor"))]
2354 return std::ptr::null();
2355
2356 #[cfg(feature = "editor")]
2357 return &wrapper.clap_plugin_track_info as *const _ as *const c_void;
2358 } else if id == CLAP_EXT_VOICE_INFO {
2359 if P::CLAP_POLY_MODULATION_CONFIG.is_some() {
2360 &wrapper.clap_plugin_voice_info as *const _ as *const c_void
2361 } else {
2362 std::ptr::null()
2363 }
2364 } else {
2365 crate::nice_trace!("Host tried to query unknown extension {:?}", id);
2366 std::ptr::null()
2367 }
2368 }
2369
2370 unsafe extern "C" fn on_main_thread(plugin: *const clap_plugin) {
2371 check_null_ptr!((), plugin, unsafe { (*plugin).plugin_data });
2372 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
2373
2374 #[cfg(feature = "editor")]
2375 {
2376 use nice_plug_core::editor::EditorHandle;
2377
2378 if let Some(editor_window) = wrapper.editor_window.borrow().as_ref() {
2379 let editor_window = editor_window.get();
2380 editor_window
2381 .handle
2382 .host_main_thread_callback(&editor_window.window);
2383 }
2384 }
2385
2386 while let Some(task) = wrapper.tasks.pop() {
2389 wrapper.execute(task, true);
2390 }
2391 }
2392
2393 unsafe extern "C" fn ext_audio_ports_config_count(plugin: *const clap_plugin) -> u32 {
2394 check_null_ptr!(0, plugin, unsafe { (*plugin).plugin_data });
2395
2396 P::AUDIO_IO_LAYOUTS.len() as u32
2397 }
2398
2399 unsafe extern "C" fn ext_audio_ports_config_get(
2400 plugin: *const clap_plugin,
2401 index: u32,
2402 config: *mut clap_audio_ports_config,
2403 ) -> bool {
2404 check_null_ptr!(false, plugin, unsafe { (*plugin).plugin_data }, config);
2405
2406 match P::AUDIO_IO_LAYOUTS.get(index as usize) {
2409 Some(audio_io_layout) => {
2410 let name = audio_io_layout.name();
2411
2412 let main_input_channels = audio_io_layout.main_input_channels.map(NonZeroU32::get);
2413 let main_output_channels =
2414 audio_io_layout.main_output_channels.map(NonZeroU32::get);
2415 let input_port_type = match main_input_channels {
2416 Some(1) => CLAP_PORT_MONO.as_ptr(),
2417 Some(2) => CLAP_PORT_STEREO.as_ptr(),
2418 _ => std::ptr::null(),
2419 };
2420 let output_port_type = match main_output_channels {
2421 Some(1) => CLAP_PORT_MONO.as_ptr(),
2422 Some(2) => CLAP_PORT_STEREO.as_ptr(),
2423 _ => std::ptr::null(),
2424 };
2425
2426 unsafe { *config = std::mem::zeroed() };
2427
2428 let config = unsafe { &mut *config };
2429 config.id = index;
2430 strlcpy(&mut config.name, &name);
2431 config.input_port_count = (if main_input_channels.is_some() { 1 } else { 0 }
2432 + audio_io_layout.aux_input_ports.len())
2433 as u32;
2434 config.output_port_count = (if main_output_channels.is_some() { 1 } else { 0 }
2435 + audio_io_layout.aux_output_ports.len())
2436 as u32;
2437 config.has_main_input = main_input_channels.is_some();
2438 config.main_input_channel_count = main_input_channels.unwrap_or_default();
2439 config.main_input_port_type = input_port_type;
2440 config.has_main_output = main_output_channels.is_some();
2441 config.main_output_channel_count = main_output_channels.unwrap_or_default();
2442 config.main_output_port_type = output_port_type;
2443
2444 true
2445 }
2446 None => {
2447 crate::nice_debug_assert_failure!(
2448 "Host tried to query out of bounds audio port config {}",
2449 index
2450 );
2451
2452 false
2453 }
2454 }
2455 }
2456
2457 unsafe extern "C" fn ext_audio_ports_config_select(
2458 plugin: *const clap_plugin,
2459 config_id: clap_id,
2460 ) -> bool {
2461 check_null_ptr!(false, plugin, unsafe { (*plugin).plugin_data });
2462 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
2463
2464 match P::AUDIO_IO_LAYOUTS.get(config_id as usize) {
2466 Some(audio_io_layout) => {
2467 wrapper.current_audio_io_layout.store(*audio_io_layout);
2468
2469 true
2470 }
2471 None => {
2472 crate::nice_debug_assert_failure!(
2473 "Host tried to select out of bounds audio port config {}",
2474 config_id
2475 );
2476
2477 false
2478 }
2479 }
2480 }
2481
2482 unsafe extern "C" fn ext_audio_ports_count(plugin: *const clap_plugin, is_input: bool) -> u32 {
2483 check_null_ptr!(0, plugin, unsafe { (*plugin).plugin_data });
2484 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
2485
2486 let audio_io_layout = wrapper.current_audio_io_layout.load();
2487 if is_input {
2488 let main_ports = if audio_io_layout.main_input_channels.is_some() {
2489 1
2490 } else {
2491 0
2492 };
2493 let aux_ports = audio_io_layout.aux_input_ports.len();
2494
2495 (main_ports + aux_ports) as u32
2496 } else {
2497 let main_ports = if audio_io_layout.main_output_channels.is_some() {
2498 1
2499 } else {
2500 0
2501 };
2502 let aux_ports = audio_io_layout.aux_output_ports.len();
2503
2504 (main_ports + aux_ports) as u32
2505 }
2506 }
2507
2508 unsafe extern "C" fn ext_audio_ports_get(
2509 plugin: *const clap_plugin,
2510 index: u32,
2511 is_input: bool,
2512 info: *mut clap_audio_port_info,
2513 ) -> bool {
2514 check_null_ptr!(false, plugin, unsafe { (*plugin).plugin_data }, info);
2515 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
2516
2517 let num_input_ports = unsafe { Self::ext_audio_ports_count(plugin, true) };
2518 let num_output_ports = unsafe { Self::ext_audio_ports_count(plugin, false) };
2519 if (is_input && index >= num_input_ports) || (!is_input && index >= num_output_ports) {
2520 crate::nice_debug_assert_failure!(
2521 "Host tried to query information for out of bounds audio port {} (input: {})",
2522 index,
2523 is_input
2524 );
2525
2526 return false;
2527 }
2528
2529 let current_audio_io_layout = wrapper.current_audio_io_layout.load();
2530 let has_main_input = current_audio_io_layout.main_input_channels.is_some();
2531 let has_main_output = current_audio_io_layout.main_output_channels.is_some();
2532
2533 let is_main_port =
2535 index == 0 && ((is_input && has_main_input) || (!is_input && has_main_output));
2536
2537 let stable_id = if is_input {
2540 index
2541 } else {
2542 index + num_input_ports
2543 };
2544
2545 let can_process_in_place = current_audio_io_layout.main_input_channels
2547 == current_audio_io_layout.main_output_channels;
2548 let pair_stable_id = match (is_input, is_main_port) {
2549 (true, true) if has_main_output && can_process_in_place => num_input_ports,
2552 (false, true) if has_main_input && can_process_in_place => 0,
2553 _ => CLAP_INVALID_ID,
2554 };
2555
2556 let channel_count = match (index, is_input) {
2557 (0, true) if has_main_input => {
2558 current_audio_io_layout.main_input_channels.unwrap().get()
2559 }
2560 (0, false) if has_main_output => {
2561 current_audio_io_layout.main_output_channels.unwrap().get()
2562 }
2563 (n, true) if has_main_input => {
2565 current_audio_io_layout.aux_input_ports[n as usize - 1].get()
2566 }
2567 (n, false) if has_main_output => {
2568 current_audio_io_layout.aux_output_ports[n as usize - 1].get()
2569 }
2570 (n, true) => current_audio_io_layout.aux_input_ports[n as usize].get(),
2571 (n, false) => current_audio_io_layout.aux_output_ports[n as usize].get(),
2572 };
2573
2574 let port_type = match channel_count {
2575 1 => CLAP_PORT_MONO.as_ptr(),
2576 2 => CLAP_PORT_STEREO.as_ptr(),
2577 _ => std::ptr::null(),
2578 };
2579
2580 unsafe { *info = std::mem::zeroed() };
2581
2582 let info = unsafe { &mut *info };
2583 info.id = stable_id;
2584 match (is_input, is_main_port) {
2585 (true, true) => strlcpy(&mut info.name, ¤t_audio_io_layout.main_input_name()),
2586 (false, true) => strlcpy(&mut info.name, ¤t_audio_io_layout.main_output_name()),
2587 (true, false) => {
2588 let aux_input_idx = if has_main_input { index - 1 } else { index } as usize;
2589 strlcpy(
2590 &mut info.name,
2591 ¤t_audio_io_layout
2592 .aux_input_name(aux_input_idx)
2593 .expect("Out of bounds auxiliary input port"),
2594 );
2595 }
2596 (false, false) => {
2597 let aux_output_idx = if has_main_output { index - 1 } else { index } as usize;
2598 strlcpy(
2599 &mut info.name,
2600 ¤t_audio_io_layout
2601 .aux_output_name(aux_output_idx)
2602 .expect("Out of bounds auxiliary output port"),
2603 );
2604 }
2605 };
2606 info.flags = if is_main_port {
2607 CLAP_AUDIO_PORT_IS_MAIN
2608 } else {
2609 0
2610 };
2611 info.channel_count = channel_count;
2612 info.port_type = port_type;
2613 info.in_place_pair = pair_stable_id;
2614
2615 true
2616 }
2617
2618 #[cfg(feature = "editor")]
2619 unsafe extern "C" fn ext_gui_is_api_supported(
2620 _plugin: *const clap_plugin,
2621 api: *const c_char,
2622 is_floating: bool,
2623 ) -> bool {
2624 if is_floating {
2626 return false;
2627 }
2628
2629 unsafe {
2630 #[cfg(all(target_family = "unix", not(target_os = "macos")))]
2631 if CStr::from_ptr(api) == clap_sys::ext::gui::CLAP_WINDOW_API_X11 {
2632 return true;
2633 }
2634 #[cfg(target_os = "macos")]
2635 if CStr::from_ptr(api) == clap_sys::ext::gui::CLAP_WINDOW_API_COCOA {
2636 return true;
2637 }
2638 #[cfg(target_os = "windows")]
2639 if CStr::from_ptr(api) == clap_sys::ext::gui::CLAP_WINDOW_API_WIN32 {
2640 return true;
2641 }
2642 }
2643
2644 false
2645 }
2646
2647 #[cfg(feature = "editor")]
2648 unsafe extern "C" fn ext_gui_get_preferred_api(
2649 _plugin: *const clap_plugin,
2650 api: *mut *const c_char,
2651 is_floating: *mut bool,
2652 ) -> bool {
2653 check_null_ptr!(false, api, is_floating);
2654
2655 unsafe {
2656 #[cfg(all(target_family = "unix", not(target_os = "macos")))]
2657 {
2658 *api = clap_sys::ext::gui::CLAP_WINDOW_API_X11.as_ptr();
2659 }
2660 #[cfg(target_os = "macos")]
2661 {
2662 *api = clap_sys::ext::gui::CLAP_WINDOW_API_COCOA.as_ptr();
2663 }
2664 #[cfg(target_os = "windows")]
2665 {
2666 *api = clap_sys::ext::gui::CLAP_WINDOW_API_WIN32.as_ptr();
2667 }
2668
2669 *is_floating = false;
2671 }
2672
2673 true
2674 }
2675
2676 #[cfg(feature = "editor")]
2677 unsafe extern "C" fn ext_gui_create(
2678 plugin: *const clap_plugin,
2679 api: *const c_char,
2680 is_floating: bool,
2681 ) -> bool {
2682 if unsafe { !Self::ext_gui_is_api_supported(plugin, api, is_floating) } {
2684 return false;
2685 }
2686
2687 check_null_ptr!(false, plugin, unsafe { (*plugin).plugin_data });
2688 let wrapper = unsafe { Arc::from_raw((*plugin).plugin_data as *const Self) };
2690
2691 let result = {
2692 if wrapper.editor_window.borrow().is_none() {
2693 use std::error::Error;
2694
2695 use nice_plug_core::editor::{
2696 HostCallbacks, HostMainThreadCaller, HostMethods, dpi::Size,
2697 };
2698
2699 #[derive(Debug, thiserror::Error)]
2700 enum ResizeError {
2701 #[error("Host refused window size: {0:?}")]
2702 HostRefusedSize(Size),
2703 #[error("Attempted to close window when plugin was closed")]
2704 PluginClosed,
2705 }
2706
2707 struct ClapHostCallbacks<P: ClapPlugin> {
2708 wrapper: Weak<Wrapper<P>>,
2709 host_gui: ClapPtr<clap_host_gui>,
2710 }
2711
2712 impl<P: ClapPlugin> HostCallbacks for ClapHostCallbacks<P> {
2713 fn request_resize(
2714 &mut self,
2715 new_size: Size,
2716 scale_factor: f64,
2717 ) -> Result<(), Box<dyn Error>> {
2718 if let Some(wrapper) = self.wrapper.upgrade() {
2719 use nice_plug_core::editor::dpi::NativeSize;
2720
2721 let native_size = NativeSize::from_size(new_size, scale_factor);
2722
2723 if unsafe_clap_call! {
2724 &*self.host_gui=>request_resize(
2725 &*wrapper.host_callback,
2726 native_size.width,
2727 native_size.height,
2728 )
2729 } {
2730 Ok(())
2731 } else {
2732 Err(ResizeError::HostRefusedSize(new_size).into())
2733 }
2734 } else {
2735 Err(ResizeError::PluginClosed.into())
2736 }
2737 }
2738
2739 fn destroyed(&mut self) {
2740 if let Some(wrapper) = self.wrapper.upgrade() {
2741 unsafe_clap_call! {
2742 &*self.host_gui=>closed(
2743 &*wrapper.host_callback,
2744 true,
2745 )
2746 }
2747 }
2748 }
2749 }
2750
2751 let callbacks: Box<dyn HostCallbacks> = Box::new(ClapHostCallbacks {
2752 wrapper: wrapper.this.borrow().clone(),
2753 host_gui: ClapPtr::clone(wrapper.host_gui.borrow().as_ref().unwrap()),
2754 });
2755
2756 struct ClapHostMainThreadCaller<P: ClapPlugin> {
2757 wrapper: Weak<Wrapper<P>>,
2758 }
2759
2760 impl<P: ClapPlugin> HostMainThreadCaller for ClapHostMainThreadCaller<P> {
2761 fn call_main_thread(&mut self) {
2762 if let Some(wrapper) = self.wrapper.upgrade() {
2763 unsafe_clap_call! { &*wrapper.host_callback=>request_callback(&*wrapper.host_callback) };
2764 }
2765 }
2766 }
2767
2768 let main_thread_caller: Box<dyn HostMainThreadCaller> =
2769 Box::new(ClapHostMainThreadCaller {
2770 wrapper: wrapper.this.borrow().clone(),
2771 });
2772
2773 let fallback_scale_factor = wrapper.fallback_scale_factor.load();
2774
2775 match wrapper.editor.borrow().as_ref().unwrap().lock().spawn(
2776 None,
2777 true,
2778 fallback_scale_factor,
2779 wrapper.clone().make_gui_context(),
2780 Some(HostMethods {
2781 callbacks,
2782 main_thread_caller,
2783 }),
2784 ) {
2785 Ok(editor_window) => {
2786 *wrapper.editor_window.borrow_mut() =
2787 Some(fragile::Fragile::new(editor_window));
2788 true
2789 }
2790 Err(e) => {
2791 crate::nice_error!("Failed to open editor: {}", e);
2792 false
2793 }
2794 }
2795 } else {
2796 #[cfg(debug_assertions)]
2797 crate::nice_warn!("Host tried to create editor while editor is already open");
2798
2799 false
2800 }
2801 };
2802
2803 let _ = Arc::into_raw(wrapper);
2805
2806 result
2807 }
2808
2809 #[cfg(feature = "editor")]
2810 unsafe extern "C" fn ext_gui_set_parent(
2811 plugin: *const clap_plugin,
2812 window: *const clap_sys::ext::gui::clap_window,
2813 ) -> bool {
2814 use nice_plug_core::editor::{EditorHandle, ParentWindowHandle};
2815 use std::ffi::c_ulong;
2816 use std::num::NonZeroIsize;
2817
2818 check_null_ptr!(false, plugin, unsafe { (*plugin).plugin_data }, window);
2819 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
2820 let window = unsafe { &*window };
2821
2822 if let Some(editor_window) = wrapper.editor_window.borrow().as_ref() {
2823 let editor_window = editor_window.get();
2824
2825 let api = unsafe { CStr::from_ptr(window.api) };
2826 let parent_handle = unsafe {
2827 if api == clap_sys::ext::gui::CLAP_WINDOW_API_X11 {
2828 #[allow(clippy::unnecessary_cast)]
2829 let w = window.specific.x11 as c_ulong;
2830 ParentWindowHandle::XlibWindow(w)
2831 } else if api == clap_sys::ext::gui::CLAP_WINDOW_API_COCOA {
2832 check_null_ptr!(false, window.specific.cocoa);
2833 let w = NonNull::new(window.specific.cocoa).unwrap();
2834 ParentWindowHandle::AppKitNsView(w)
2835 } else if api == clap_sys::ext::gui::CLAP_WINDOW_API_WIN32 {
2836 check_null_ptr!(false, window.specific.win32);
2837 let w = NonZeroIsize::new(window.specific.win32 as isize).unwrap();
2838 ParentWindowHandle::Win32Hwnd(w)
2839 } else {
2840 crate::nice_debug_assert_failure!("Host passed an invalid API");
2841 return false;
2842 }
2843 };
2844
2845 if let Err(e) = editor_window
2846 .handle
2847 .set_parent(parent_handle, editor_window.window.borrow())
2848 {
2849 crate::nice_error!("Failed to set editor parent window: {}", e);
2850
2851 false
2852 } else {
2853 true
2854 }
2855 } else {
2856 #[cfg(debug_assertions)]
2857 crate::nice_warn!("Host tried to set parent window while editor is not open");
2858
2859 false
2860 }
2861 }
2862
2863 #[cfg(feature = "editor")]
2864 unsafe extern "C" fn ext_gui_destroy(plugin: *const clap_plugin) {
2865 check_null_ptr!((), plugin, unsafe { (*plugin).plugin_data });
2866 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
2867
2868 let mut editor_handle = wrapper.editor_window.borrow_mut();
2869 if editor_handle.is_some() {
2870 *editor_handle = None;
2871 } else {
2872 #[cfg(debug_assertions)]
2873 crate::nice_warn!("Tried destroying editor while the editor was not active");
2874 }
2875 }
2876
2877 #[cfg(feature = "editor")]
2878 unsafe extern "C" fn ext_gui_get_size(
2879 plugin: *const clap_plugin,
2880 width: *mut u32,
2881 height: *mut u32,
2882 ) -> bool {
2883 check_null_ptr!(
2884 false,
2885 plugin,
2886 unsafe { (*plugin).plugin_data },
2887 width,
2888 height
2889 );
2890 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
2891
2892 if let Some(editor) = wrapper.editor.borrow().as_ref() {
2893 let size = editor.lock().size();
2894
2895 unsafe {
2896 *width = size.width;
2897 *height = size.height;
2898 }
2899
2900 true
2901 } else {
2902 false
2903 }
2904 }
2905
2906 #[cfg(feature = "editor")]
2907 unsafe extern "C" fn ext_gui_can_resize(plugin: *const clap_plugin) -> bool {
2908 check_null_ptr!(false, plugin, unsafe { (*plugin).plugin_data });
2909 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
2910
2911 match wrapper.editor.borrow().as_ref() {
2913 Some(editor) => editor.lock().resize_hint().can_resize,
2914 None => false,
2915 }
2916 }
2917
2918 #[cfg(feature = "editor")]
2919 unsafe extern "C" fn ext_gui_get_resize_hints(
2920 plugin: *const clap_plugin,
2921 hints: *mut clap_sys::ext::gui::clap_gui_resize_hints,
2922 ) -> bool {
2923 check_null_ptr!(false, plugin, unsafe { (*plugin).plugin_data }, hints);
2924 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
2925
2926 let hint = match wrapper.editor.borrow().as_ref() {
2927 Some(editor) => editor.lock().resize_hint(),
2928 None => return false,
2929 };
2930 if !hint.can_resize {
2931 return false;
2932 }
2933
2934 let hints = unsafe { &mut *hints };
2935 hints.can_resize_horizontally = hint.can_resize_horizontally;
2936 hints.can_resize_vertically = hint.can_resize_vertically;
2937 hints.preserve_aspect_ratio = hint.preserve_aspect_ratio;
2938 hints.aspect_ratio_width = hint.aspect_ratio_width;
2939 hints.aspect_ratio_height = hint.aspect_ratio_height;
2940
2941 true
2942 }
2943
2944 #[cfg(feature = "editor")]
2945 unsafe extern "C" fn ext_gui_adjust_size(
2946 plugin: *const clap_plugin,
2947 width: *mut u32,
2948 height: *mut u32,
2949 ) -> bool {
2950 use nice_plug_core::editor::EditorHandle;
2951 use nice_plug_core::editor::dpi::NativeSize;
2952
2953 check_null_ptr!(false, plugin, unsafe { (*plugin).plugin_data });
2954 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
2955
2956 if let Some(editor_window) = wrapper.editor_window.borrow().as_ref() {
2957 let editor_window = editor_window.get();
2958
2959 let size = unsafe { NativeSize::new(*width, *height) };
2960
2961 if let Some(new_size) = editor_window
2962 .handle
2963 .adjust_size(size, editor_window.window.borrow())
2964 {
2965 unsafe {
2966 *width = new_size.width;
2967 *height = new_size.height;
2968 }
2969
2970 true
2971 } else {
2972 false
2973 }
2974 } else {
2975 false
2976 }
2977 }
2978
2979 #[cfg(feature = "editor")]
2980 unsafe extern "C" fn ext_gui_set_scale(plugin: *const clap_plugin, scale: f64) -> bool {
2981 use nice_plug_core::editor::EditorHandle;
2982
2983 check_null_ptr!(false, plugin, unsafe { (*plugin).plugin_data });
2984 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
2985
2986 if let Some(editor_window) = wrapper.editor_window.borrow().as_ref() {
2987 let editor_window = editor_window.get();
2988
2989 if let Err(e) = editor_window
2990 .handle
2991 .set_fallback_scale_factor(scale, editor_window.window.borrow())
2992 {
2993 crate::nice_error!("Failed to set suggested scale factor: {}", e);
2994 false
2995 } else {
2996 wrapper.fallback_scale_factor.store(Some(scale));
2997 true
2998 }
2999 } else {
3000 false
3001 }
3002 }
3003
3004 #[cfg(feature = "editor")]
3005 unsafe extern "C" fn ext_gui_set_size(
3006 plugin: *const clap_plugin,
3007 width: u32,
3008 height: u32,
3009 ) -> bool {
3010 use nice_plug_core::editor::EditorHandle;
3011
3012 check_null_ptr!(false, plugin, unsafe { (*plugin).plugin_data });
3016 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
3017
3018 if let Some(editor_window) = wrapper.editor_window.borrow().as_ref() {
3021 let editor_window = editor_window.get();
3022
3023 if let Err(e) = editor_window.handle.set_size(
3024 nice_plug_core::editor::dpi::NativeSize { width, height },
3025 editor_window.window.borrow(),
3026 ) {
3027 crate::nice_error!("Failed to resize window to ({}, {}): {}", width, height, e);
3028 false
3029 } else {
3030 true
3031 }
3032 } else {
3033 false
3034 }
3035 }
3036
3037 #[cfg(feature = "editor")]
3038 unsafe extern "C" fn ext_gui_set_transient(
3039 _plugin: *const clap_plugin,
3040 _window: *const clap_sys::ext::gui::clap_window,
3041 ) -> bool {
3042 false
3044 }
3045
3046 #[cfg(feature = "editor")]
3047 unsafe extern "C" fn ext_gui_suggest_title(_plugin: *const clap_plugin, _title: *const c_char) {
3048 }
3050
3051 #[cfg(feature = "editor")]
3052 unsafe extern "C" fn ext_gui_show(plugin: *const clap_plugin) -> bool {
3053 use nice_plug_core::editor::EditorHandle;
3054
3055 check_null_ptr!(false, plugin, unsafe { (*plugin).plugin_data });
3056 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
3057
3058 if let Some(editor_window) = wrapper.editor_window.borrow().as_ref() {
3059 let editor_window = editor_window.get();
3060
3061 if let Err(e) = editor_window.handle.show(editor_window.window.borrow()) {
3062 crate::nice_error!("Failed to show editor window: {}", e);
3063 false
3064 } else {
3065 true
3066 }
3067 } else {
3068 false
3069 }
3070 }
3071
3072 #[cfg(feature = "editor")]
3073 unsafe extern "C" fn ext_gui_hide(plugin: *const clap_plugin) -> bool {
3074 use nice_plug_core::editor::EditorHandle;
3075
3076 check_null_ptr!(false, plugin, unsafe { (*plugin).plugin_data });
3077 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
3078
3079 if let Some(editor_window) = wrapper.editor_window.borrow().as_ref() {
3080 let editor_window = editor_window.get();
3081
3082 if let Err(e) = editor_window.handle.hide(editor_window.window.borrow()) {
3083 crate::nice_error!("Failed to hide editor window: {}", e);
3084 false
3085 } else {
3086 true
3087 }
3088 } else {
3089 false
3090 }
3091 }
3092
3093 unsafe extern "C" fn ext_latency_get(plugin: *const clap_plugin) -> u32 {
3094 check_null_ptr!(0, plugin, unsafe { (*plugin).plugin_data });
3095 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
3096
3097 wrapper.current_latency.load(Ordering::SeqCst)
3098 }
3099
3100 unsafe extern "C" fn ext_note_ports_count(_plugin: *const clap_plugin, is_input: bool) -> u32 {
3101 match is_input {
3102 true if P::MIDI_INPUT >= MidiConfig::Basic => 1,
3103 false if P::MIDI_OUTPUT >= MidiConfig::Basic => 1,
3104 _ => 0,
3105 }
3106 }
3107
3108 unsafe extern "C" fn ext_note_ports_get(
3109 _plugin: *const clap_plugin,
3110 index: u32,
3111 is_input: bool,
3112 info: *mut clap_note_port_info,
3113 ) -> bool {
3114 match (index, is_input) {
3115 (0, true) if P::MIDI_INPUT >= MidiConfig::Basic => {
3116 unsafe {
3117 *info = std::mem::zeroed();
3118 }
3119
3120 let info = unsafe { &mut *info };
3121 info.id = 0;
3122 info.supported_dialects = CLAP_NOTE_DIALECT_CLAP | CLAP_NOTE_DIALECT_MIDI;
3125 info.preferred_dialect = CLAP_NOTE_DIALECT_CLAP;
3126 strlcpy(&mut info.name, "Note Input");
3127
3128 true
3129 }
3130 (0, false) if P::MIDI_OUTPUT >= MidiConfig::Basic => {
3131 unsafe { *info = std::mem::zeroed() };
3132
3133 let info = unsafe { &mut *info };
3134 info.id = 0;
3135 info.supported_dialects = CLAP_NOTE_DIALECT_CLAP | CLAP_NOTE_DIALECT_MIDI;
3139 info.preferred_dialect = CLAP_NOTE_DIALECT_CLAP;
3140 strlcpy(&mut info.name, "Note Output");
3141
3142 true
3143 }
3144 _ => false,
3145 }
3146 }
3147
3148 unsafe extern "C" fn ext_params_count(plugin: *const clap_plugin) -> u32 {
3149 check_null_ptr!(0, plugin, unsafe { (*plugin).plugin_data });
3150 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
3151
3152 wrapper.param_hashes.len() as u32
3153 }
3154
3155 unsafe extern "C" fn ext_params_get_info(
3156 plugin: *const clap_plugin,
3157 param_index: u32,
3158 param_info: *mut clap_param_info,
3159 ) -> bool {
3160 check_null_ptr!(false, plugin, unsafe { (*plugin).plugin_data }, param_info);
3161 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
3162
3163 if param_index > unsafe { Self::ext_params_count(plugin) } {
3164 return false;
3165 }
3166
3167 let param_hash = &wrapper.param_hashes[param_index as usize];
3168 let param_group = &wrapper.param_group_by_hash[param_hash];
3169 let param_ptr = &wrapper.param_by_hash[param_hash];
3170 let default_value = unsafe { param_ptr.default_normalized_value() };
3171 let step_count = unsafe { param_ptr.step_count() };
3172 let flags = unsafe { param_ptr.flags() };
3173 let automatable = !flags.contains(ParamFlags::NON_AUTOMATABLE);
3174 let hidden = flags.contains(ParamFlags::HIDDEN);
3175 let is_bypass = flags.contains(ParamFlags::BYPASS);
3176
3177 unsafe {
3178 *param_info = std::mem::zeroed();
3179 }
3180
3181 let param_info = unsafe { &mut *param_info };
3184 param_info.id = *param_hash;
3185 param_info.flags = 0;
3187 if automatable && !hidden {
3188 param_info.flags |= CLAP_PARAM_IS_AUTOMATABLE | CLAP_PARAM_IS_MODULATABLE;
3189 if wrapper.poly_mod_ids_by_hash.contains_key(param_hash) {
3190 param_info.flags |= CLAP_PARAM_IS_MODULATABLE_PER_NOTE_ID;
3191 }
3192 }
3193 if hidden {
3194 param_info.flags |= CLAP_PARAM_IS_HIDDEN | CLAP_PARAM_IS_READONLY;
3195 }
3196 if is_bypass {
3197 param_info.flags |= CLAP_PARAM_IS_BYPASS
3198 }
3199 if step_count.is_some() {
3200 param_info.flags |= CLAP_PARAM_IS_STEPPED
3201 }
3202 param_info.cookie = std::ptr::null_mut();
3203 strlcpy(&mut param_info.name, unsafe { param_ptr.name() });
3204 strlcpy(&mut param_info.module, param_group);
3205 param_info.min_value = 0.0;
3209 param_info.max_value = step_count.unwrap_or(1) as f64;
3213 param_info.default_value = default_value as f64 * step_count.unwrap_or(1) as f64;
3214
3215 true
3216 }
3217
3218 unsafe extern "C" fn ext_params_get_value(
3219 plugin: *const clap_plugin,
3220 param_id: clap_id,
3221 value: *mut f64,
3222 ) -> bool {
3223 check_null_ptr!(false, plugin, unsafe { (*plugin).plugin_data }, value);
3224 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
3225
3226 match wrapper.param_by_hash.get(¶m_id) {
3227 Some(param_ptr) => {
3228 unsafe {
3229 *value = param_ptr.modulated_normalized_value() as f64
3230 * param_ptr.step_count().unwrap_or(1) as f64;
3231 }
3232
3233 true
3234 }
3235 _ => false,
3236 }
3237 }
3238
3239 unsafe extern "C" fn ext_params_value_to_text(
3240 plugin: *const clap_plugin,
3241 param_id: clap_id,
3242 value: f64,
3243 display: *mut c_char,
3244 size: u32,
3245 ) -> bool {
3246 check_null_ptr!(false, plugin, unsafe { (*plugin).plugin_data }, display);
3247 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
3248
3249 let dest = unsafe { std::slice::from_raw_parts_mut(display, size as usize) };
3250
3251 match wrapper.param_by_hash.get(¶m_id) {
3252 Some(param_ptr) => {
3253 unsafe {
3254 strlcpy(
3255 dest,
3256 ¶m_ptr.normalized_value_to_string(
3258 value as f32 / param_ptr.step_count().unwrap_or(1) as f32,
3259 true,
3260 ),
3261 );
3262 }
3263
3264 true
3265 }
3266 _ => false,
3267 }
3268 }
3269
3270 unsafe extern "C" fn ext_params_text_to_value(
3271 plugin: *const clap_plugin,
3272 param_id: clap_id,
3273 display: *const c_char,
3274 value: *mut f64,
3275 ) -> bool {
3276 check_null_ptr!(
3277 false,
3278 plugin,
3279 unsafe { (*plugin).plugin_data },
3280 display,
3281 value
3282 );
3283 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
3284
3285 let display = match unsafe { CStr::from_ptr(display).to_str() } {
3286 Ok(s) => s,
3287 Err(_) => return false,
3288 };
3289
3290 match wrapper.param_by_hash.get(¶m_id) {
3291 Some(param_ptr) => {
3292 let normalized_value =
3293 match unsafe { param_ptr.string_to_normalized_value(display) } {
3294 Some(v) => v as f64,
3295 None => return false,
3296 };
3297 unsafe {
3298 *value = normalized_value * param_ptr.step_count().unwrap_or(1) as f64;
3299 }
3300
3301 true
3302 }
3303 _ => false,
3304 }
3305 }
3306
3307 unsafe extern "C" fn ext_params_flush(
3308 plugin: *const clap_plugin,
3309 in_: *const clap_input_events,
3310 out: *const clap_output_events,
3311 ) {
3312 check_null_ptr!((), plugin, unsafe { (*plugin).plugin_data });
3313 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
3314
3315 if !in_.is_null() {
3316 unsafe {
3317 wrapper.handle_in_events(&*in_, 0, 0);
3318 }
3319 }
3320
3321 if !out.is_null() {
3322 unsafe {
3323 wrapper.handle_out_events(&*out, 0);
3324 }
3325 }
3326 }
3327
3328 unsafe extern "C" fn ext_remote_controls_count(plugin: *const clap_plugin) -> u32 {
3329 check_null_ptr!(0, plugin, unsafe { (*plugin).plugin_data });
3330 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
3331
3332 wrapper.remote_control_pages.len() as u32
3333 }
3334
3335 unsafe extern "C" fn ext_remote_controls_get(
3336 plugin: *const clap_plugin,
3337 page_index: u32,
3338 page: *mut clap_remote_controls_page,
3339 ) -> bool {
3340 check_null_ptr!(false, plugin, unsafe { (*plugin).plugin_data }, page);
3341 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
3342
3343 crate::nice_debug_assert!(page_index as usize <= wrapper.remote_control_pages.len());
3344 match wrapper.remote_control_pages.get(page_index as usize) {
3345 Some(p) => {
3346 unsafe {
3347 *page = *p;
3348 }
3349 true
3350 }
3351 None => false,
3352 }
3353 }
3354
3355 unsafe extern "C" fn ext_render_has_hard_realtime_requirement(
3356 _plugin: *const clap_plugin,
3357 ) -> bool {
3358 P::HARD_REALTIME_ONLY
3359 }
3360
3361 unsafe extern "C" fn ext_render_set(
3362 plugin: *const clap_plugin,
3363 mode: clap_plugin_render_mode,
3364 ) -> bool {
3365 check_null_ptr!(false, plugin, unsafe { (*plugin).plugin_data });
3366 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
3367
3368 let mode = match mode {
3369 CLAP_RENDER_REALTIME => ProcessMode::Realtime,
3370 CLAP_RENDER_OFFLINE => ProcessMode::Offline,
3372 n => {
3373 crate::nice_error!("Unknown rendering mode '{}', defaulting to realtime", n);
3374 ProcessMode::Realtime
3375 }
3376 };
3377
3378 if wrapper.current_process_mode.swap(mode) != mode
3379 && wrapper.is_activated.load(Ordering::SeqCst)
3380 {
3381 wrapper.request_restart();
3384 }
3385
3386 true
3387 }
3388
3389 unsafe extern "C" fn ext_state_save(
3390 plugin: *const clap_plugin,
3391 stream: *const clap_ostream,
3392 ) -> bool {
3393 check_null_ptr!(false, plugin, unsafe { (*plugin).plugin_data }, stream);
3394 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
3395
3396 let serialized = unsafe {
3397 state::serialize_json::<P>(
3398 wrapper.params.clone(),
3399 state::make_params_iter(&wrapper.param_by_hash, &wrapper.param_id_to_hash),
3400 )
3401 };
3402 match serialized {
3403 Ok(serialized) => {
3404 let length_bytes = (serialized.len() as u64).to_le_bytes();
3407 if !write_stream(unsafe { &*stream }, &length_bytes) {
3408 crate::nice_error!(
3409 "Failed to save state: Error or end of stream while writing the state length"
3410 );
3411 return false;
3412 }
3413 if !write_stream(unsafe { &*stream }, &serialized) {
3414 crate::nice_error!(
3415 "Failed to save state: Error or end of stream while writing the state buffer"
3416 );
3417 return false;
3418 }
3419
3420 crate::nice_trace!("Saved state ({} bytes)", serialized.len());
3421
3422 true
3423 }
3424 Err(err) => {
3425 crate::nice_error!("Failed to save state: {}", err);
3426 false
3427 }
3428 }
3429 }
3430
3431 unsafe extern "C" fn ext_state_load(
3432 plugin: *const clap_plugin,
3433 stream: *const clap_istream,
3434 ) -> bool {
3435 check_null_ptr!(false, plugin, unsafe { (*plugin).plugin_data }, stream);
3436 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
3437
3438 let mut length_bytes = [0u8; 8];
3441 let bytes_read = read_stream(unsafe { &*stream }, length_bytes.as_mut_slice());
3442 if bytes_read != Some(8) {
3443 crate::nice_error!(
3444 "Failed to load state: Error or end of stream while reading the state length"
3445 );
3446 return false;
3447 }
3448 let length = u64::from_le_bytes(length_bytes);
3449 if length > MAX_STATE_BYTES {
3451 crate::nice_error!("Failed to load state: Malformed length");
3452 return false;
3453 }
3454
3455 let mut read_buffer: Vec<u8> = Vec::new();
3456
3457 if read_buffer.try_reserve_exact(length as usize).is_err() {
3458 crate::nice_error!("Failed to load state: Failed to allocate buffer for state stream");
3459 return false;
3460 }
3461
3462 let bytes_read = read_stream(unsafe { &*stream }, read_buffer.spare_capacity_mut());
3463 let Some(bytes_read) = bytes_read else {
3464 crate::nice_error!("Failed to load state: Error while reading the state buffer");
3465 return false;
3466 };
3467 unsafe {
3468 read_buffer.set_len(bytes_read);
3469 }
3470
3471 match unsafe { state::deserialize_json(&read_buffer) } {
3472 Some(mut state) => {
3473 let success = wrapper.set_state_inner(&mut state);
3474 if success {
3475 crate::nice_trace!("Loaded state ({} bytes)", read_buffer.len());
3476 }
3477
3478 success
3479 }
3480 None => false,
3481 }
3482 }
3483
3484 #[cfg(feature = "editor")]
3485 unsafe extern "C" fn ext_track_info_changed(plugin: *const clap_plugin) {
3486 check_null_ptr!((), plugin, unsafe { (*plugin).plugin_data });
3487 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
3488
3489 wrapper.update_track_info_from_host();
3490 }
3491
3492 unsafe extern "C" fn ext_tail_get(plugin: *const clap_plugin) -> u32 {
3493 check_null_ptr!(0, plugin, unsafe { (*plugin).plugin_data });
3494 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
3495
3496 match wrapper.last_process_status.load() {
3497 ProcessStatus::Tail(samples) => samples,
3498 ProcessStatus::KeepAlive => u32::MAX,
3499 _ => 0,
3500 }
3501 }
3502
3503 unsafe extern "C" fn ext_voice_info_get(
3504 plugin: *const clap_plugin,
3505 info: *mut clap_voice_info,
3506 ) -> bool {
3507 check_null_ptr!(false, plugin, unsafe { (*plugin).plugin_data }, info);
3508 let wrapper = unsafe { &*((*plugin).plugin_data as *const Self) };
3509
3510 match P::CLAP_POLY_MODULATION_CONFIG {
3511 Some(config) => {
3512 unsafe {
3513 *info = clap_voice_info {
3514 voice_count: wrapper.current_voice_capacity.load(Ordering::Relaxed),
3515 voice_capacity: config.max_voice_capacity,
3516 flags: if config.supports_overlapping_voices {
3517 CLAP_VOICE_INFO_SUPPORTS_OVERLAPPING_NOTES
3518 } else {
3519 0
3520 },
3521 };
3522 }
3523
3524 true
3525 }
3526 None => false,
3527 }
3528 }
3529}
3530
3531unsafe fn query_host_extension<T>(
3537 host_callback: &ClapPtr<clap_host>,
3538 name: &CStr,
3539) -> Option<ClapPtr<T>> {
3540 let extension_ptr = unsafe {
3541 clap_call! { host_callback=>get_extension(&**host_callback, name.as_ptr()) }
3542 };
3543 if !extension_ptr.is_null() {
3544 unsafe { Some(ClapPtr::new(extension_ptr as *const T)) }
3545 } else {
3546 None
3547 }
3548}