Skip to main content

nice_plug/wrapper/vst3/
wrapper.rs

1use atomic_refcell::AtomicRefMut;
2use nice_plug_core::audio_setup::{AuxiliaryBuffers, BufferConfig, ProcessMode};
3use nice_plug_core::context::process::Transport;
4#[cfg(feature = "editor")]
5use nice_plug_core::editor::Editor;
6use nice_plug_core::midi::{Channel, Key, MidiConfig, NoteEvent, VoiceID};
7use nice_plug_core::params::ParamFlags;
8use nice_plug_core::plugin::ProcessStatus;
9use std::ffi::c_void;
10use std::mem::{self, MaybeUninit};
11use std::num::NonZeroU32;
12use std::ptr::NonNull;
13use std::sync::Arc;
14use std::sync::atomic::Ordering;
15use std::time::{Duration, Instant};
16use vst3::Steinberg::Vst::ProcessContext_::StatesAndFlags_::{
17    kBarPositionValid, kCycleActive, kCycleValid, kPlaying, kProjectTimeMusicValid, kRecording,
18    kTempoValid, kTimeSigValid,
19};
20use vst3::Steinberg::Vst::{
21    BusDirection, CString,
22    ChannelContext::{IInfoListener, IInfoListenerTrait},
23    CtrlNumber,
24    Event_::EventTypes_,
25    IAttributeList, IAudioProcessor, IAudioProcessorTrait, IComponent, IComponentHandler,
26    IComponentTrait, IEditController, IEditControllerTrait, IEventListTrait, IMidiMapping,
27    IMidiMappingTrait, INoteExpressionController, INoteExpressionControllerTrait,
28    IParamValueQueueTrait, IParameterChangesTrait, IProcessContextRequirements,
29    IProcessContextRequirements_, IProcessContextRequirementsTrait, IUnitInfo, IUnitInfoTrait,
30    IoMode, MediaType, NoteExpressionTypeID, NoteExpressionTypeInfo, NoteExpressionValue,
31    NoteExpressionValueDescription, ParamID, ParamValue, ParameterInfo,
32    ParameterInfo_::ParameterFlags_,
33    ProcessData, ProcessModes_, ProcessSetup, ProgramListID, ProgramListInfo, SpeakerArrangement,
34    String128, TChar, UnitID, UnitInfo, kNoParamId, kNoParentUnitId, kNoProgramListId, kRootUnitId,
35};
36use vst3::Steinberg::{
37    FIDString, FUnknown, IBStream, IBStreamTrait, IPlugView, IPluginBaseTrait, TBool, TUID, int16,
38    int32, kInvalidArgument, kNoInterface, kResultFalse, kResultOk, tresult, uint32,
39};
40use vst3::{Class, ComRef};
41use widestring::U16CStr;
42
43use super::inner::{ProcessEvent, WrapperInner};
44use super::note_expressions;
45use super::util::{VST3_MIDI_CCS, VST3_MIDI_NUM_PARAMS, VST3_MIDI_PARAMS_START, u16strlcpy};
46use super::util::{VST3_MIDI_CHANNELS, VST3_MIDI_PARAMS_END};
47use crate::util::permit_alloc;
48use crate::wrapper::state;
49use crate::wrapper::util::buffer_management::{BufferManager, ChannelPointers};
50use crate::wrapper::util::{clamp_input_event_timing, process_wrapper};
51use crate::wrapper::vst3::Vst3Plugin;
52
53#[allow(clippy::unnecessary_cast)]
54const K_SYMBOLIC_SAMPLE_SIZE_32: i32 = vst3::Steinberg::Vst::SymbolicSampleSizes_::kSample32 as i32;
55#[allow(clippy::unnecessary_cast)]
56const K_MEDIA_TYPE_AUDIO: i32 = vst3::Steinberg::Vst::MediaTypes_::kAudio as i32;
57#[allow(clippy::unnecessary_cast)]
58const K_MEDIA_TYPE_EVENT: i32 = vst3::Steinberg::Vst::MediaTypes_::kEvent as i32;
59#[allow(clippy::unnecessary_cast)]
60const K_BUS_DIRECTION_INPUT: i32 = vst3::Steinberg::Vst::BusDirections_::kInput as i32;
61#[allow(clippy::unnecessary_cast)]
62const K_BUS_DIRECTION_OUTPUT: i32 = vst3::Steinberg::Vst::BusDirections_::kOutput as i32;
63#[allow(clippy::unnecessary_cast)]
64const K_BUS_TYPE_MAIN: i32 = vst3::Steinberg::Vst::BusTypes_::kMain as i32;
65#[allow(clippy::unnecessary_cast)]
66const K_BUS_TYPE_AUX: i32 = vst3::Steinberg::Vst::BusTypes_::kAux as i32;
67
68pub struct Wrapper<P: Vst3Plugin> {
69    inner: Arc<WrapperInner<P>>,
70}
71
72impl<P: Vst3Plugin> Class for Wrapper<P> {
73    type Interfaces = (
74        IComponent,
75        IEditController,
76        IAudioProcessor,
77        IMidiMapping,
78        INoteExpressionController,
79        IProcessContextRequirements,
80        IUnitInfo,
81        IInfoListener,
82    );
83}
84
85impl<P: Vst3Plugin> Wrapper<P> {
86    pub fn new() -> Self {
87        Self {
88            inner: WrapperInner::new(),
89        }
90    }
91}
92
93impl<P: Vst3Plugin> Default for Wrapper<P> {
94    fn default() -> Self {
95        Self::new()
96    }
97}
98
99impl<P: Vst3Plugin> Drop for Wrapper<P> {
100    fn drop(&mut self) {
101        crate::nice_debug_assert_eq!(Arc::strong_count(&self.inner), 1);
102    }
103}
104
105impl<P: Vst3Plugin> IPluginBaseTrait for Wrapper<P> {
106    unsafe fn initialize(&self, _context: *mut FUnknown) -> tresult {
107        // We currently don't need or allow any initialization logic
108        kResultOk
109    }
110
111    unsafe fn terminate(&self) -> tresult {
112        kResultOk
113    }
114}
115
116impl<P: Vst3Plugin> IComponentTrait for Wrapper<P> {
117    unsafe fn getControllerClassId(&self, _class_id: *mut TUID) -> tresult {
118        // We won't separate the edit controller to keep the implementation a bit smaller
119        kNoInterface
120    }
121
122    unsafe fn setIoMode(&self, _mode: IoMode) -> tresult {
123        // Not quite sure what the point of this is when the processing setup also receives similar
124        // information
125        kResultOk
126    }
127
128    unsafe fn getBusCount(
129        &self,
130        type_: vst3::Steinberg::Vst::MediaType,
131        dir: vst3::Steinberg::Vst::BusDirection,
132    ) -> int32 {
133        let current_audio_io_layout = self.inner.current_audio_io_layout.load();
134
135        // A plugin has a main input and output bus if the default number of channels is non-zero,
136        // and a plugin can also have auxiliary input and output busses
137        match type_ {
138            x if x == K_MEDIA_TYPE_AUDIO && dir == K_BUS_DIRECTION_INPUT => {
139                let main_busses = if current_audio_io_layout.main_input_channels.is_some() {
140                    1
141                } else {
142                    0
143                };
144                let aux_busses = current_audio_io_layout.aux_input_ports.len() as i32;
145
146                main_busses + aux_busses
147            }
148            x if x == K_MEDIA_TYPE_AUDIO && dir == K_BUS_DIRECTION_OUTPUT => {
149                let main_busses = if current_audio_io_layout.main_output_channels.is_some() {
150                    1
151                } else {
152                    0
153                };
154                let aux_busses = current_audio_io_layout.aux_output_ports.len() as i32;
155
156                main_busses + aux_busses
157            }
158            x if x == K_MEDIA_TYPE_EVENT
159                && dir == K_BUS_DIRECTION_INPUT
160                && P::MIDI_INPUT >= MidiConfig::Basic =>
161            {
162                1
163            }
164            x if x == K_MEDIA_TYPE_EVENT
165                && dir == K_BUS_DIRECTION_OUTPUT
166                && P::MIDI_OUTPUT >= MidiConfig::Basic =>
167            {
168                1
169            }
170            _ => 0,
171        }
172    }
173
174    unsafe fn getBusInfo(
175        &self,
176        type_: vst3::Steinberg::Vst::MediaType,
177        dir: vst3::Steinberg::Vst::BusDirection,
178        index: int32,
179        info: *mut vst3::Steinberg::Vst::BusInfo,
180    ) -> tresult {
181        check_null_ptr!(info);
182
183        let current_audio_io_layout = self.inner.current_audio_io_layout.load();
184
185        match (type_, dir, index) {
186            (t, d, _) if t == K_MEDIA_TYPE_AUDIO && d == K_BUS_DIRECTION_INPUT => {
187                unsafe { *info = mem::zeroed() };
188
189                let info = unsafe { &mut *info };
190                info.mediaType = K_MEDIA_TYPE_AUDIO;
191                info.direction = dir;
192                #[allow(clippy::unnecessary_cast)]
193                {
194                    info.flags = vst3::Steinberg::Vst::BusInfo_::BusFlags_::kDefaultActive as u32;
195                }
196
197                let has_main_input = current_audio_io_layout.main_input_channels.is_some();
198                let aux_input_start_idx = if has_main_input { 1 } else { 0 };
199                let aux_input_idx = (index - aux_input_start_idx).max(0) as usize;
200                if index == 0 && has_main_input {
201                    info.busType = K_BUS_TYPE_MAIN;
202                    info.channelCount =
203                        current_audio_io_layout.main_input_channels.unwrap().get() as i32;
204                    u16strlcpy(&mut info.name, &current_audio_io_layout.main_input_name());
205
206                    kResultOk
207                } else if aux_input_idx < current_audio_io_layout.aux_input_ports.len() {
208                    info.busType = K_BUS_TYPE_AUX;
209                    info.channelCount =
210                        current_audio_io_layout.aux_input_ports[aux_input_idx].get() as i32;
211                    u16strlcpy(
212                        &mut info.name,
213                        &current_audio_io_layout
214                            .aux_input_name(aux_input_idx)
215                            .expect("Out of bounds auxiliary input port"),
216                    );
217
218                    kResultOk
219                } else {
220                    kInvalidArgument
221                }
222            }
223            (t, d, _) if t == K_MEDIA_TYPE_AUDIO && d == K_BUS_DIRECTION_OUTPUT => {
224                unsafe { *info = mem::zeroed() };
225
226                let info = unsafe { &mut *info };
227                info.mediaType = K_MEDIA_TYPE_AUDIO;
228                info.direction = dir;
229                #[allow(clippy::unnecessary_cast)]
230                {
231                    info.flags = vst3::Steinberg::Vst::BusInfo_::BusFlags_::kDefaultActive as u32;
232                }
233
234                let has_main_output = current_audio_io_layout.main_output_channels.is_some();
235                let aux_output_start_idx = if has_main_output { 1 } else { 0 };
236                let aux_output_idx = (index - aux_output_start_idx).max(0) as usize;
237                if index == 0 && has_main_output {
238                    info.busType = K_BUS_TYPE_MAIN;
239                    // NOTE: See above, this becomes a 0 channel output if the plugin doesn't have a
240                    //       main output
241                    info.channelCount = current_audio_io_layout
242                        .main_output_channels
243                        .map(NonZeroU32::get)
244                        .unwrap_or_default() as i32;
245                    u16strlcpy(&mut info.name, &current_audio_io_layout.main_output_name());
246
247                    kResultOk
248                } else if aux_output_idx < current_audio_io_layout.aux_output_ports.len() {
249                    info.busType = K_BUS_TYPE_AUX;
250                    info.channelCount =
251                        current_audio_io_layout.aux_output_ports[aux_output_idx].get() as i32;
252                    u16strlcpy(
253                        &mut info.name,
254                        &current_audio_io_layout
255                            .aux_output_name(aux_output_idx)
256                            .expect("Out of bounds auxiliary output port"),
257                    );
258
259                    kResultOk
260                } else {
261                    kInvalidArgument
262                }
263            }
264            (t, d, 0)
265                if t == K_MEDIA_TYPE_EVENT
266                    && d == K_BUS_DIRECTION_INPUT
267                    && P::MIDI_INPUT >= MidiConfig::Basic =>
268            {
269                unsafe { *info = mem::zeroed() };
270
271                let info = unsafe { &mut *info };
272                info.mediaType = K_MEDIA_TYPE_EVENT;
273                info.direction = K_BUS_DIRECTION_INPUT;
274                info.channelCount = 16;
275                u16strlcpy(&mut info.name, "Note Input");
276                info.busType = K_BUS_TYPE_MAIN;
277                #[allow(clippy::unnecessary_cast)]
278                {
279                    info.flags = vst3::Steinberg::Vst::BusInfo_::BusFlags_::kDefaultActive as u32;
280                }
281                kResultOk
282            }
283            (t, d, 0)
284                if t == K_MEDIA_TYPE_EVENT
285                    && d == K_BUS_DIRECTION_OUTPUT
286                    && P::MIDI_OUTPUT >= MidiConfig::Basic =>
287            {
288                unsafe { *info = mem::zeroed() };
289
290                let info = unsafe { &mut *info };
291                info.mediaType = K_MEDIA_TYPE_EVENT;
292                info.direction = K_BUS_DIRECTION_OUTPUT;
293                info.channelCount = 16;
294                u16strlcpy(&mut info.name, "Note Output");
295                info.busType = K_BUS_TYPE_MAIN;
296                #[allow(clippy::unnecessary_cast)]
297                {
298                    info.flags = vst3::Steinberg::Vst::BusInfo_::BusFlags_::kDefaultActive as u32;
299                }
300                kResultOk
301            }
302            _ => kInvalidArgument,
303        }
304    }
305
306    unsafe fn getRoutingInfo(
307        &self,
308        in_info: *mut vst3::Steinberg::Vst::RoutingInfo,
309        out_info: *mut vst3::Steinberg::Vst::RoutingInfo,
310    ) -> tresult {
311        check_null_ptr!(in_info, out_info);
312
313        let current_audio_io_layout = self.inner.current_audio_io_layout.load();
314
315        unsafe { *out_info = mem::zeroed() };
316
317        let in_info = unsafe { &*in_info };
318        let out_info = unsafe { &mut *out_info };
319        match (in_info.mediaType, in_info.busIndex) {
320            (t, 0)
321                if t == K_MEDIA_TYPE_AUDIO
322                    // We only have an IO pair when the plugin has both a main input and a main output
323                    && current_audio_io_layout.main_input_channels.is_some()
324                    && current_audio_io_layout.main_output_channels.is_some() =>
325            {
326                out_info.mediaType = K_MEDIA_TYPE_AUDIO;
327                out_info.busIndex = in_info.busIndex;
328                out_info.channel = in_info.channel;
329
330                kResultOk
331            }
332            (t, 0)
333                if t == K_MEDIA_TYPE_EVENT
334                    && P::MIDI_INPUT >= MidiConfig::Basic
335                    && P::MIDI_OUTPUT >= MidiConfig::Basic =>
336            {
337                out_info.mediaType = K_MEDIA_TYPE_EVENT;
338                out_info.busIndex = in_info.busIndex;
339                out_info.channel = in_info.channel;
340
341                kResultOk
342            }
343            _ => kResultFalse,
344        }
345    }
346
347    unsafe fn activateBus(
348        &self,
349        type_: vst3::Steinberg::Vst::MediaType,
350        dir: vst3::Steinberg::Vst::BusDirection,
351        index: int32,
352        _state: TBool,
353    ) -> tresult {
354        let current_audio_io_layout = self.inner.current_audio_io_layout.load();
355
356        // We don't support this, but the validator will get very angry with us if we let it know
357        // that
358        match (type_, dir, index) {
359            (t, d, _) if t == K_MEDIA_TYPE_AUDIO && d == K_BUS_DIRECTION_INPUT => {
360                let main_busses = if current_audio_io_layout.main_input_channels.is_some() {
361                    1
362                } else {
363                    0
364                };
365                let aux_busses = current_audio_io_layout.aux_input_ports.len() as i32;
366
367                if (0..main_busses + aux_busses).contains(&index) {
368                    kResultOk
369                } else {
370                    kInvalidArgument
371                }
372            }
373            (t, d, _) if t == K_MEDIA_TYPE_AUDIO && d == K_BUS_DIRECTION_OUTPUT => {
374                let main_busses = if current_audio_io_layout.main_output_channels.is_some() {
375                    1
376                } else {
377                    0
378                };
379                let aux_busses = current_audio_io_layout.aux_output_ports.len() as i32;
380
381                if (0..main_busses + aux_busses).contains(&index) {
382                    kResultOk
383                } else {
384                    kInvalidArgument
385                }
386            }
387            (t, d, 0)
388                if t == K_MEDIA_TYPE_EVENT
389                    && d == K_BUS_DIRECTION_INPUT
390                    && P::MIDI_INPUT >= MidiConfig::Basic =>
391            {
392                kResultOk
393            }
394            (t, d, 0)
395                if t == K_MEDIA_TYPE_EVENT
396                    && d == K_BUS_DIRECTION_OUTPUT
397                    && P::MIDI_OUTPUT >= MidiConfig::Basic =>
398            {
399                kResultOk
400            }
401            _ => kInvalidArgument,
402        }
403    }
404
405    unsafe fn setActive(&self, state: TBool) -> tresult {
406        // We could call activate in `IAudioProcessor::setup_processing()`, but REAPER will set
407        // the bus arrangements between that function and this function. So to be able to handle
408        // custom channel layout overrides we need to activate here.
409        match (state != 0, self.inner.current_buffer_config.load()) {
410            (true, Some(buffer_config)) => {
411                // Before initializing the plugin, make sure all smoothers are set the the default values
412                for param in self.inner.param_by_hash.values() {
413                    unsafe { param._internal_update_smoother(buffer_config.sample_rate, true) };
414                }
415
416                let mut activate_context = self.inner.make_activate_context();
417                let audio_io_layout = self.inner.current_audio_io_layout.load();
418
419                // In the case a host misbehaves and tries to activate the plugin without waiting for the
420                // `process` method to finish, manually wait for that method to finish.
421                let now = Instant::now();
422                let mut result = kResultFalse;
423                loop {
424                    if let Some(mut plugin) = self.inner.plugin.try_lock() {
425                        if plugin.activate(&audio_io_layout, &buffer_config, &mut activate_context)
426                        {
427                            // NOTE: We don't call `Plugin::reset()` here. The call is done in `set_process()`
428                            //       instead. Otherwise we would call the function twice, and `set_process()` needs
429                            //       to be called after this function before the plugin may process audio again.
430
431                            // Likewise, make sure that the buffers are also not currently being used by the process
432                            // method.
433                            let now_2 = Instant::now();
434                            loop {
435                                if let Ok(mut buffer_manager) =
436                                    self.inner.buffer_manager.try_borrow_mut()
437                                {
438                                    // This preallocates enough space so we can transform all of the host's raw
439                                    // channel pointers into a set of `Buffer` objects for the plugin's main and
440                                    // auxiliary IO
441                                    *buffer_manager = BufferManager::for_audio_io_layout(
442                                        buffer_config.max_buffer_size as usize,
443                                        audio_io_layout,
444                                    );
445
446                                    result = kResultOk;
447
448                                    break;
449                                } else if now_2.elapsed() > Duration::from_secs(1) {
450                                    crate::nice_error!(
451                                        "Failed to acquire lock on buffers while activating"
452                                    );
453                                    break;
454                                } else {
455                                    std::thread::sleep(Duration::from_millis(1));
456                                }
457                            }
458                        }
459
460                        break;
461                    } else if now.elapsed() > Duration::from_secs(1) {
462                        crate::nice_error!("Failed to acquire lock on plugin while activating");
463                        break;
464                    } else {
465                        std::thread::sleep(Duration::from_millis(1));
466                    }
467                }
468
469                // NOTE: This needs to be dropped after the `plugin` lock to avoid deadlocks
470                drop(activate_context);
471
472                result
473            }
474            (true, None) => kResultFalse,
475            (false, _) => {
476                // In the case a host misbehaves and tries to activate the plugin without waiting for the
477                // `process` method to finish, manually wait for that method to finish.
478                let now = Instant::now();
479                loop {
480                    if let Some(mut plugin) = self.inner.plugin.try_lock() {
481                        plugin.deactivate();
482                        break;
483                    } else if now.elapsed() > Duration::from_secs(1) {
484                        crate::nice_error!("Failed to acquire lock on plugin while deactivating");
485                        break;
486                    } else {
487                        std::thread::sleep(Duration::from_millis(1));
488                    }
489                }
490
491                kResultOk
492            }
493        }
494    }
495
496    unsafe fn setState(&self, state: *mut IBStream) -> tresult {
497        use vst3::Steinberg::IBStream_::IStreamSeekMode_::*;
498
499        check_null_ptr!(state);
500
501        let state = unsafe { ComRef::from_raw(state).unwrap() };
502
503        // We need to know how large the state is before we can read it. The current position can be
504        // zero, but it can also be something else. Bitwig prepends the preset header in the stream,
505        // while some other hosts don't expose that to the plugin.
506        let mut current_pos = 0;
507        let mut eof_pos = 0;
508        if unsafe {
509            state.tell(&mut current_pos) != kResultOk
510                || state.seek(0, kIBSeekEnd as int32, &mut eof_pos) != kResultOk
511                || state.seek(current_pos, kIBSeekSet as int32, std::ptr::null_mut()) != kResultOk
512        } {
513            crate::nice_debug_assert_failure!("Could not get the stream length");
514            return kResultFalse;
515        }
516
517        let stream_byte_size = (eof_pos - current_pos) as i32;
518        let mut num_bytes_read = 0;
519
520        let mut read_buffer: Vec<u8> = Vec::new();
521
522        if read_buffer
523            .try_reserve_exact(stream_byte_size as usize)
524            .is_err()
525        {
526            crate::nice_error!("Failed to load state: Failed to allocate buffer for state stream");
527            return kResultFalse;
528        }
529
530        unsafe {
531            state.read(
532                read_buffer.as_mut_ptr() as *mut c_void,
533                read_buffer.capacity() as i32,
534                &mut num_bytes_read,
535            );
536        }
537        unsafe { read_buffer.set_len(num_bytes_read as usize) };
538
539        // If the size is zero, some hosts will always return `kResultFalse` even if the read was
540        // 'successful', so we can't check the return value but we can check the number of bytes
541        // read.
542        if read_buffer.len() != stream_byte_size as usize {
543            crate::nice_debug_assert_failure!("Unexpected stream length");
544            return kResultFalse;
545        }
546
547        match unsafe { state::deserialize_json(&read_buffer) } {
548            Some(mut state) => {
549                if self.inner.set_state_inner(&mut state) {
550                    crate::nice_trace!("Loaded state ({} bytes)", read_buffer.len());
551                    kResultOk
552                } else {
553                    kResultFalse
554                }
555            }
556            None => kResultFalse,
557        }
558    }
559
560    unsafe fn getState(&self, state: *mut IBStream) -> tresult {
561        check_null_ptr!(state);
562
563        let state = unsafe { ComRef::from_raw(state).unwrap() };
564
565        let serialized = unsafe {
566            state::serialize_json::<P>(
567                self.inner.params.clone(),
568                state::make_params_iter(&self.inner.param_by_hash, &self.inner.param_id_to_hash),
569            )
570        };
571        match serialized {
572            Ok(serialized) => {
573                let mut num_bytes_written = 0;
574                let result = unsafe {
575                    state.write(
576                        serialized.as_ptr() as *mut c_void,
577                        serialized.len() as i32,
578                        &mut num_bytes_written,
579                    )
580                };
581
582                crate::nice_debug_assert_eq!(result, kResultOk);
583                crate::nice_debug_assert_eq!(num_bytes_written as usize, serialized.len());
584
585                crate::nice_trace!("Saved state ({} bytes)", serialized.len());
586
587                kResultOk
588            }
589            Err(err) => {
590                crate::nice_debug_assert_failure!("Could not save state: {:#}", err);
591                kResultFalse
592            }
593        }
594    }
595}
596
597impl<P: Vst3Plugin> IEditControllerTrait for Wrapper<P> {
598    unsafe fn setComponentState(&self, _state: *mut IBStream) -> tresult {
599        // We have a single file component, so we don't need to do anything here
600        kResultOk
601    }
602
603    unsafe fn setState(&self, _state: *mut IBStream) -> tresult {
604        // We don't store any separate state here. The plugin's state will have been restored
605        // through the component. Calling that same function here will likely lead to duplicate
606        // state restores
607        kResultOk
608    }
609
610    unsafe fn getState(&self, _state: *mut IBStream) -> tresult {
611        // Same for this function
612        kResultOk
613    }
614
615    unsafe fn getParameterCount(&self) -> int32 {
616        // We need to add a whole bunch of parameters if the plugin accepts MIDI CCs
617        if P::MIDI_INPUT >= MidiConfig::MidiCCs {
618            self.inner.param_hashes.len() as i32 + VST3_MIDI_NUM_PARAMS as i32
619        } else {
620            self.inner.param_hashes.len() as i32
621        }
622    }
623
624    unsafe fn getParameterInfo(&self, param_index: int32, info: *mut ParameterInfo) -> tresult {
625        check_null_ptr!(info);
626
627        if param_index < 0 || param_index > unsafe { self.getParameterCount() } {
628            return kInvalidArgument;
629        }
630
631        unsafe { *info = std::mem::zeroed() };
632        let info = unsafe { &mut *info };
633
634        // If the parameter is a generated MIDI CC/channel pressure/pitch bend then it needs to be
635        // handled separately
636        let num_actual_params = self.inner.param_hashes.len() as i32;
637        if P::MIDI_INPUT >= MidiConfig::MidiCCs && param_index >= num_actual_params {
638            let midi_param_relative_idx = (param_index - num_actual_params) as u32;
639            // This goes up to 130 for the 128 CCs followed by channel pressure and pitch bend
640            let midi_cc = midi_param_relative_idx % VST3_MIDI_CCS;
641            let midi_channel = midi_param_relative_idx / VST3_MIDI_CCS;
642            let name = match midi_cc {
643                // kAfterTouch
644                128 => format!("MIDI Ch. {} Channel Pressure", midi_channel + 1),
645                // kPitchBend
646                129 => format!("MIDI Ch. {} Pitch Bend", midi_channel + 1),
647                n => format!("MIDI Ch. {} CC {}", midi_channel + 1, n),
648            };
649
650            info.id = VST3_MIDI_PARAMS_START + midi_param_relative_idx;
651            u16strlcpy(&mut info.title, &name);
652            u16strlcpy(&mut info.shortTitle, &name);
653            info.flags = ParameterFlags_::kIsReadOnly | (1 << 4); // kIsHidden
654        } else {
655            let param_hash = &self.inner.param_hashes[param_index as usize];
656            let param_unit = &self
657                .inner
658                .param_units
659                .get_vst3_unit_id(*param_hash)
660                .expect("Inconsistent parameter data");
661            let param_ptr = &self.inner.param_by_hash[param_hash];
662            let default_value = unsafe { param_ptr.default_normalized_value() };
663            let flags = unsafe { param_ptr.flags() };
664            let automatable = !flags.contains(ParamFlags::NON_AUTOMATABLE);
665            let hidden = flags.contains(ParamFlags::HIDDEN);
666            let is_bypass = flags.contains(ParamFlags::BYPASS);
667
668            info.id = *param_hash;
669            u16strlcpy(&mut info.title, unsafe { param_ptr.name() });
670            u16strlcpy(&mut info.shortTitle, unsafe { param_ptr.name() });
671            u16strlcpy(&mut info.units, unsafe { param_ptr.unit() });
672            info.stepCount = unsafe { param_ptr.step_count().unwrap_or(0) } as i32;
673            info.defaultNormalizedValue = default_value as f64;
674            info.unitId = *param_unit;
675            info.flags = 0;
676            if automatable && !hidden {
677                info.flags |= ParameterFlags_::kCanAutomate;
678            }
679            if hidden {
680                info.flags |= ParameterFlags_::kIsReadOnly | (1 << 4); // kIsHidden
681            }
682            if is_bypass {
683                info.flags |= ParameterFlags_::kIsBypass;
684            }
685        }
686
687        kResultOk
688    }
689
690    unsafe fn getParamStringByValue(
691        &self,
692        id: ParamID,
693        value_normalized: ParamValue,
694        string: *mut String128,
695    ) -> tresult {
696        check_null_ptr!(string);
697
698        let dest = unsafe { &mut *(string) };
699
700        // TODO: We don't implement these methods at all for our generated MIDI CC parameters,
701        //       should be fine right? They should be hidden anyways.
702        match self.inner.param_by_hash.get(&id) {
703            Some(param_ptr) => {
704                unsafe {
705                    u16strlcpy(
706                        dest,
707                        &param_ptr.normalized_value_to_string(value_normalized as f32, false),
708                    );
709                }
710
711                kResultOk
712            }
713            _ => kInvalidArgument,
714        }
715    }
716
717    unsafe fn getParamValueByString(
718        &self,
719        id: ParamID,
720        string: *mut TChar,
721        value_normalized: *mut ParamValue,
722    ) -> tresult {
723        check_null_ptr!(string, value_normalized);
724
725        let string = match unsafe { U16CStr::from_ptr_str(string as *const u16).to_string() } {
726            Ok(s) => s,
727            Err(_) => return kInvalidArgument,
728        };
729
730        match self.inner.param_by_hash.get(&id) {
731            Some(param_ptr) => {
732                let value = match unsafe { param_ptr.string_to_normalized_value(&string) } {
733                    Some(v) => v as f64,
734                    None => return kResultFalse,
735                };
736                unsafe { *value_normalized = value };
737
738                kResultOk
739            }
740            _ => kInvalidArgument,
741        }
742    }
743
744    unsafe fn normalizedParamToPlain(
745        &self,
746        id: ParamID,
747        value_normalized: ParamValue,
748    ) -> ParamValue {
749        match self.inner.param_by_hash.get(&id) {
750            Some(param_ptr) => unsafe { param_ptr.preview_plain(value_normalized as f32) as f64 },
751            _ => value_normalized,
752        }
753    }
754
755    unsafe fn plainParamToNormalized(&self, id: ParamID, plain_value: ParamValue) -> ParamValue {
756        match self.inner.param_by_hash.get(&id) {
757            Some(param_ptr) => unsafe { param_ptr.preview_normalized(plain_value as f32) as f64 },
758            _ => plain_value,
759        }
760    }
761
762    unsafe fn getParamNormalized(&self, id: ParamID) -> ParamValue {
763        match self.inner.param_by_hash.get(&id) {
764            Some(param_ptr) => unsafe { param_ptr.modulated_normalized_value() as f64 },
765            _ => 0.5,
766        }
767    }
768
769    unsafe fn setParamNormalized(&self, id: ParamID, value: ParamValue) -> tresult {
770        // If the plugin is currently processing audio, then this parameter change will also be sent
771        // to the process function
772        if self.inner.is_processing.load(Ordering::SeqCst) {
773            return kResultOk;
774        }
775
776        let sample_rate = self
777            .inner
778            .current_buffer_config
779            .load()
780            .map(|c| c.sample_rate);
781        self.inner
782            .set_normalized_value_by_hash(id, value as f32, sample_rate)
783    }
784
785    unsafe fn setComponentHandler(&self, handler: *mut IComponentHandler) -> tresult {
786        *self.inner.component_handler.borrow_mut() =
787            unsafe { ComRef::from_raw(handler) }.map(|r| r.to_com_ptr());
788
789        kResultOk
790    }
791
792    unsafe fn createView(&self, _name: FIDString) -> *mut IPlugView {
793        #[cfg(not(feature = "editor"))]
794        return std::ptr::null_mut();
795
796        // Without specialization this is the least redundant way to check if the plugin has an
797        // editor. The default implementation returns a None here.
798        #[cfg(feature = "editor")]
799        match self.inner.editor.borrow().as_ref() {
800            Some(editor) => {
801                use vst3::ComWrapper;
802
803                use crate::wrapper::vst3::view::WrapperView;
804
805                let view = ComWrapper::new(WrapperView::new(
806                    Arc::downgrade(&self.inner),
807                    Arc::downgrade(editor),
808                ));
809                let plug_view_ptr = view.to_com_ptr::<IPlugView>().unwrap().into_raw();
810                *self.inner.plug_view.write() = Some(view);
811                plug_view_ptr
812            }
813            None => std::ptr::null_mut(),
814        }
815    }
816}
817
818impl<P: Vst3Plugin> IAudioProcessorTrait for Wrapper<P> {
819    unsafe fn setBusArrangements(
820        &self,
821        inputs: *mut SpeakerArrangement,
822        num_ins: int32,
823        outputs: *mut SpeakerArrangement,
824        num_outs: int32,
825    ) -> tresult {
826        check_null_ptr!(inputs, outputs);
827
828        // Why are these signed integers again?
829        if num_ins < 0 || num_outs < 0 {
830            return kInvalidArgument;
831        }
832
833        // nice-plug no longer supports flexible IO layouts. Instead we'll try to find an audio IO
834        // layout that matches the host's requested layout.
835        let matching_layout = P::AUDIO_IO_LAYOUTS
836            .iter()
837            .find(|layout| {
838                // If the number of ports/busses doesn't match then we can immediately discard the
839                // layout. VST3 doesn't allow for optional switchable ports like CLAP does. Only the
840                // channel counts can change.
841                let num_layout_ins = if layout.main_input_channels.is_some() {
842                    1
843                } else {
844                    0
845                } + layout.aux_input_ports.len();
846                let num_layout_outs = if layout.main_output_channels.is_some() {
847                    1
848                } else {
849                    0
850                } + layout.aux_output_ports.len();
851                if num_ins as usize != num_layout_ins || num_outs as usize != num_layout_outs {
852                    return false;
853                }
854
855                // NOTE: We completely ignore the speaker arrangements and only look at the channel
856                //       counts here. This may cause issues at some point, but it works for now.
857                let has_main_input = layout.main_input_channels.is_some();
858                let aux_input_start_idx = if has_main_input { 0 } else { 1 };
859                if has_main_input
860                    && unsafe {
861                        (*inputs).count_ones() != layout.main_input_channels.unwrap().get()
862                    }
863                {
864                    return false;
865                }
866                for (aux_input_idx, channel_count) in layout.aux_input_ports.iter().enumerate() {
867                    if unsafe {
868                        (*inputs.add(aux_input_idx + aux_input_start_idx)).count_ones()
869                            != channel_count.get()
870                    } {
871                        return false;
872                    }
873                }
874
875                let has_main_output = layout.main_output_channels.is_some();
876                let aux_output_start_idx = if has_main_output { 0 } else { 1 };
877                if unsafe {
878                    (*outputs).count_ones()
879                        != layout
880                            .main_output_channels
881                            .map(NonZeroU32::get)
882                            .unwrap_or_default()
883                } {
884                    return false;
885                }
886                for (aux_output_idx, channel_count) in layout.aux_output_ports.iter().enumerate() {
887                    if unsafe {
888                        (*outputs.add(aux_output_idx + aux_output_start_idx)).count_ones()
889                            != channel_count.get()
890                    } {
891                        return false;
892                    }
893                }
894
895                true
896            })
897            .copied();
898
899        match matching_layout {
900            Some(layout) => {
901                // This layout is used from hereon onwards, at least until this function is called
902                // again
903                self.inner.current_audio_io_layout.store(layout);
904
905                kResultOk
906            }
907            None => kResultFalse,
908        }
909    }
910
911    unsafe fn getBusArrangement(
912        &self,
913        dir: BusDirection,
914        index: i32,
915        arr: *mut SpeakerArrangement,
916    ) -> tresult {
917        check_null_ptr!(arr);
918
919        let channel_count_to_map = |count| match count {
920            0 => vst3::Steinberg::Vst::SpeakerArr::kEmpty,
921            1 => vst3::Steinberg::Vst::SpeakerArr::kMono,
922            2 => vst3::Steinberg::Vst::SpeakerArr::kStereo,
923            5 => vst3::Steinberg::Vst::SpeakerArr::k50,
924            6 => vst3::Steinberg::Vst::SpeakerArr::k51,
925            7 => vst3::Steinberg::Vst::SpeakerArr::k70Cine,
926            8 => vst3::Steinberg::Vst::SpeakerArr::k71Cine,
927            n => {
928                crate::nice_debug_assert_failure!(
929                    "No defined layout for {} channels, making something up on the spot...",
930                    n
931                );
932                (1 << n) - 1
933            }
934        };
935
936        let current_audio_io_layout = self.inner.current_audio_io_layout.load();
937        let num_channels = if dir == K_BUS_DIRECTION_INPUT {
938            let has_main_input = current_audio_io_layout.main_input_channels.is_some();
939            let aux_input_start_idx = if has_main_input { 1 } else { 0 };
940            let aux_input_idx = (index - aux_input_start_idx).max(0) as usize;
941            if index == 0 && has_main_input {
942                current_audio_io_layout.main_input_channels.unwrap().get()
943            } else if aux_input_idx < current_audio_io_layout.aux_input_ports.len() {
944                current_audio_io_layout.aux_input_ports[aux_input_idx].get()
945            } else {
946                return kInvalidArgument;
947            }
948        } else if dir == K_BUS_DIRECTION_OUTPUT {
949            let has_main_output = current_audio_io_layout.main_output_channels.is_some();
950            let aux_output_start_idx = if has_main_output { 1 } else { 0 };
951            let aux_output_idx = (index - aux_output_start_idx).max(0) as usize;
952            if index == 0 && has_main_output {
953                current_audio_io_layout.main_output_channels.unwrap().get()
954            } else if aux_output_idx < current_audio_io_layout.aux_output_ports.len() {
955                current_audio_io_layout.aux_output_ports[aux_output_idx].get()
956            } else {
957                return kInvalidArgument;
958            }
959        } else {
960            return kInvalidArgument;
961        };
962        let channel_map = channel_count_to_map(num_channels);
963
964        crate::nice_debug_assert_eq!(num_channels, channel_map.count_ones());
965        unsafe { *arr = channel_map };
966
967        kResultOk
968    }
969
970    unsafe fn canProcessSampleSize(&self, symbolic_sample_size: int32) -> tresult {
971        if symbolic_sample_size == K_SYMBOLIC_SAMPLE_SIZE_32 {
972            kResultOk
973        } else {
974            kResultFalse
975        }
976    }
977
978    unsafe fn getLatencySamples(&self) -> uint32 {
979        self.inner.current_latency.load(Ordering::SeqCst)
980    }
981
982    unsafe fn setupProcessing(&self, setup: *mut ProcessSetup) -> tresult {
983        check_null_ptr!(setup);
984
985        // There's no special handling for offline processing at the moment
986        let setup = unsafe { &*setup };
987        crate::nice_debug_assert_eq!(setup.symbolicSampleSize, K_SYMBOLIC_SAMPLE_SIZE_32);
988
989        // This is needed when activating the plugin and when restoring state
990        self.inner.current_buffer_config.store(Some(BufferConfig {
991            sample_rate: setup.sampleRate as f32,
992            min_buffer_size: None,
993            max_buffer_size: setup.maxSamplesPerBlock as u32,
994            process_mode: self.inner.current_process_mode.load(),
995        }));
996
997        #[allow(clippy::unnecessary_cast)]
998        const K_REALTIME: i32 = ProcessModes_::kRealtime as i32;
999        #[allow(clippy::unnecessary_cast)]
1000        const K_PREFETCH: i32 = ProcessModes_::kPrefetch as i32;
1001        #[allow(clippy::unnecessary_cast)]
1002        const K_OFFLINE: i32 = ProcessModes_::kOffline as i32;
1003
1004        let mode = match setup.processMode {
1005            n if n == K_REALTIME => ProcessMode::Realtime,
1006            n if n == K_PREFETCH => ProcessMode::Buffered,
1007            n if n == K_OFFLINE => ProcessMode::Offline,
1008            n => {
1009                crate::nice_debug_assert_failure!(
1010                    "Unknown rendering mode '{}', defaulting to realtime",
1011                    n
1012                );
1013                ProcessMode::Realtime
1014            }
1015        };
1016        self.inner.current_process_mode.store(mode);
1017
1018        // Initializing the plugin happens in `IAudioProcessor::set_active()` because the host may
1019        // still change the channel layouts at this point
1020
1021        kResultOk
1022    }
1023
1024    unsafe fn setProcessing(&self, state: TBool) -> tresult {
1025        let state = state != 0;
1026
1027        // Always reset the processing status when the plugin gets activated or deactivated
1028        self.inner.last_process_status.store(ProcessStatus::Normal);
1029        self.inner.is_processing.store(state, Ordering::SeqCst);
1030
1031        // This function is also used to reset buffers on the plugin, so we should do the same
1032        // thing. We don't call `reset()` in `setup_processing()` for that same reason.
1033        if state {
1034            // HACK: See the comment in `IComponent::setActive()`. This is needed to work around
1035            //       Ardour bugs.
1036            let mut plugin = match self.inner.plugin.try_lock() {
1037                Some(plugin) => plugin,
1038                None => {
1039                    crate::nice_debug_assert_failure!(
1040                        "The host tried to call IAudioProcessor::setProcessing(true) during a \
1041                         reentrent call to IComponent::setActive(true), returning kResultOk. If \
1042                         this is Ardour then it will still call \
1043                         IAudioProcessor::setProcessing(true) later and everything will be fine. \
1044                         Hopefully."
1045                    );
1046                    return kResultOk;
1047                }
1048            };
1049
1050            process_wrapper(|| plugin.reset());
1051        }
1052
1053        // We don't have any special handling for suspending and resuming plugins, yet
1054        kResultOk
1055    }
1056
1057    // Clippy doesn't understand our `event_start_idx`
1058    #[allow(clippy::mut_range_bound)]
1059    unsafe fn process(&self, data: *mut ProcessData) -> tresult {
1060        check_null_ptr!(data);
1061
1062        // Panic on allocations if the `assert_process_allocs` feature has been enabled, and make
1063        // sure that FTZ is set up correctly
1064        process_wrapper(|| {
1065            // We need to handle incoming automation first
1066            let data = unsafe { &*data };
1067            let sample_rate = self
1068                .inner
1069                .current_buffer_config
1070                .load()
1071                .expect("Process call without prior setup call")
1072                .sample_rate;
1073
1074            crate::nice_debug_assert!(data.numInputs >= 0 && data.numOutputs >= 0);
1075            crate::nice_debug_assert_eq!(data.symbolicSampleSize, K_SYMBOLIC_SAMPLE_SIZE_32);
1076            crate::nice_debug_assert!(data.numSamples >= 0);
1077
1078            let total_buffer_len = data.numSamples as usize;
1079
1080            let current_audio_io_layout = self.inner.current_audio_io_layout.load();
1081            let has_main_input = current_audio_io_layout.main_input_channels.is_some();
1082            let has_main_output = current_audio_io_layout.main_output_channels.is_some();
1083            let aux_input_start_idx = if has_main_input { 1 } else { 0 };
1084            let aux_output_start_idx = if has_main_output { 1 } else { 0 };
1085
1086            // NOTE: VST3 hosts may trigger a 'parameter flush' by calling the process function for
1087            //       0 input samples. If this is the case then we'll only handle events and skip all
1088            //       audio processing. Some hosts, like Ableton Live, implement this in a broken way
1089            //       and instead only set the number of channels to 0. In that case the
1090            //       'buffer_is_valid' check from below should still prevent audio processing.
1091            let mut is_param_flush = total_buffer_len == 0;
1092            if (data.numOutputs == 0 || data.outputs.is_null())
1093                && (has_main_output || !current_audio_io_layout.aux_output_ports.is_empty())
1094            {
1095                is_param_flush = true;
1096            }
1097
1098            // If `P::SAMPLE_ACCURATE_AUTOMATION` is set, then we'll split up the audio buffer into
1099            // chunks whenever a parameter change occurs. To do that, we'll store all of those
1100            // parameter changes in a vector. Otherwise all parameter changes are handled right here
1101            // and now. We'll also need to store the note events in the same vector because MIDI CC
1102            // messages are sent through parameter changes. This vector gets sorted at the end so we
1103            // can treat it as a sort of queue.
1104            let mut process_events = self.inner.process_events.borrow_mut();
1105            process_events.clear();
1106
1107            let push_process_event = |process_events: &mut AtomicRefMut<Vec<ProcessEvent<P>>>,
1108                                      event: ProcessEvent<P>| {
1109                permit_alloc(|| {
1110                    // In the rare case the host sends a very large amount of events at once, there
1111                    // is not much we can do except to just accept the allocation.
1112                    if process_events.len() == process_events.capacity() {
1113                        crate::nice_warn!(
1114                            "Input event buffer filled up! This will cause an allocation."
1115                        );
1116                    }
1117
1118                    // Make sure events are (stable) sorted by timing. The majority of the time hosts
1119                    // will send events in order, so sorting like this shouldn't be too much of a
1120                    // performance hit.
1121                    //
1122                    // NOTE: It's important that this sort is stable, because parameter changes need to be
1123                    //       processed before note events. Otherwise you'll get out of bounds note events
1124                    //       with block splitting when the note event occurs at one index after the end (or
1125                    //       on the exclusive end index) of the block.
1126                    if let Some(last_event) = process_events.last() {
1127                        if event.timing() >= last_event.timing() {
1128                            process_events.push(event);
1129                        } else {
1130                            let insert_i =
1131                                process_events.partition_point(|x| x.timing() <= event.timing());
1132                            process_events.insert(insert_i, event);
1133                        }
1134                    } else {
1135                        process_events.push(event);
1136                    }
1137                });
1138            };
1139
1140            // First we'll go through the parameter changes. This may also include MIDI CC messages
1141            // if the plugin supports those
1142            if let Some(param_changes) = unsafe { ComRef::from_raw(data.inputParameterChanges) } {
1143                let num_param_queues = unsafe { param_changes.getParameterCount() };
1144                for change_queue_idx in 0..num_param_queues {
1145                    if let Some(param_change_queue) = unsafe {
1146                        ComRef::from_raw(param_changes.getParameterData(change_queue_idx))
1147                    } {
1148                        let param_hash = unsafe { param_change_queue.getParameterId() };
1149                        let num_changes = unsafe { param_change_queue.getPointCount() };
1150                        if num_changes <= 0 {
1151                            continue;
1152                        }
1153
1154                        let mut sample_offset = 0i32;
1155                        let mut value = 0.0f64;
1156                        for change_idx in 0..num_changes {
1157                            if unsafe {
1158                                param_change_queue.getPoint(
1159                                    change_idx,
1160                                    &mut sample_offset,
1161                                    &mut value,
1162                                ) == kResultOk
1163                            } {
1164                                // Later this timing will be compensated for block splits by calling
1165                                // `event.subtract_timing(block_start)` before it is passed to the
1166                                // plugin. Out of bounds events are clamped to the buffer>
1167                                let timing = clamp_input_event_timing(
1168                                    sample_offset as u32,
1169                                    total_buffer_len as u32,
1170                                );
1171                                let value = value as f32;
1172
1173                                // MIDI CC messages, channel pressure, and pitch bend are also sent
1174                                // as parameter changes
1175                                if P::MIDI_INPUT >= MidiConfig::MidiCCs
1176                                    && (VST3_MIDI_PARAMS_START..VST3_MIDI_PARAMS_END)
1177                                        .contains(&param_hash)
1178                                {
1179                                    let midi_param_relative_idx =
1180                                        param_hash - VST3_MIDI_PARAMS_START;
1181                                    // This goes up to 130 for the 128 CCs followed by channel pressure and pitch bend
1182                                    let midi_cc = (midi_param_relative_idx % VST3_MIDI_CCS) as u8;
1183                                    let midi_channel =
1184                                        (midi_param_relative_idx / VST3_MIDI_CCS) as u8;
1185
1186                                    push_process_event(
1187                                        &mut process_events,
1188                                        ProcessEvent::NoteEvent(match midi_cc {
1189                                            // kAfterTouch
1190                                            128 => NoteEvent::MidiChannelPressure {
1191                                                timing,
1192                                                channel: midi_channel,
1193                                                pressure: value,
1194                                            },
1195                                            // kPitchBend
1196                                            129 => NoteEvent::MidiPitchBend {
1197                                                timing,
1198                                                channel: midi_channel,
1199                                                value,
1200                                            },
1201                                            n => NoteEvent::MidiCC {
1202                                                timing,
1203                                                channel: midi_channel,
1204                                                cc: n,
1205                                                value,
1206                                            },
1207                                        }),
1208                                    );
1209                                } else if P::SAMPLE_ACCURATE_AUTOMATION {
1210                                    push_process_event(
1211                                        &mut process_events,
1212                                        ProcessEvent::ParameterChange {
1213                                            timing,
1214                                            hash: param_hash,
1215                                            normalized_value: value,
1216                                        },
1217                                    );
1218                                } else {
1219                                    self.inner.set_normalized_value_by_hash(
1220                                        param_hash,
1221                                        value,
1222                                        Some(sample_rate),
1223                                    );
1224                                }
1225                            }
1226                        }
1227                    }
1228                }
1229            }
1230
1231            // Then we'll add all of our input events
1232            if P::MIDI_INPUT >= MidiConfig::Basic {
1233                let mut note_expression_controller =
1234                    self.inner.note_expression_controller.borrow_mut();
1235                if let Some(events) = unsafe { ComRef::from_raw(data.inputEvents) } {
1236                    let num_events = unsafe { events.getEventCount() };
1237
1238                    fn voice_from_i32(v: i32) -> VoiceID {
1239                        if v >= 0 {
1240                            VoiceID::ID(v)
1241                        } else {
1242                            VoiceID::Wildcard
1243                        }
1244                    }
1245                    fn channel_from_i16(c: i16) -> Channel {
1246                        if (0..=15).contains(&c) {
1247                            Channel::Number(c as u8)
1248                        } else {
1249                            Channel::Wildcard
1250                        }
1251                    }
1252                    fn key_from_i16(k: i16) -> Key {
1253                        if (0..=127).contains(&k) {
1254                            Key::Number(k as u8)
1255                        } else {
1256                            Key::Wildcard
1257                        }
1258                    }
1259
1260                    let mut event: MaybeUninit<_> = MaybeUninit::uninit();
1261                    for i in 0..num_events {
1262                        let result = unsafe { events.getEvent(i, event.as_mut_ptr()) };
1263                        crate::nice_debug_assert_eq!(result, kResultOk);
1264
1265                        let event = unsafe { event.assume_init() };
1266                        let timing = clamp_input_event_timing(
1267                            event.sampleOffset as u32,
1268                            total_buffer_len as u32,
1269                        );
1270
1271                        if event.r#type == EventTypes_::kNoteOnEvent as u16 {
1272                            let event = unsafe { event.__field0.noteOn };
1273
1274                            // We need to keep track of note IDs to be able to handle not
1275                            // expression value events
1276                            note_expression_controller.register_note(&event);
1277
1278                            push_process_event(
1279                                &mut process_events,
1280                                ProcessEvent::NoteEvent(NoteEvent::NoteOn {
1281                                    timing,
1282                                    voice_id: voice_from_i32(event.noteId),
1283                                    channel: channel_from_i16(event.channel),
1284                                    key: key_from_i16(event.pitch),
1285                                    velocity: event.velocity,
1286                                }),
1287                            );
1288                        } else if event.r#type == EventTypes_::kNoteOffEvent as u16 {
1289                            let event = unsafe { event.__field0.noteOff };
1290
1291                            push_process_event(
1292                                &mut process_events,
1293                                ProcessEvent::NoteEvent(NoteEvent::NoteOff {
1294                                    timing,
1295                                    voice_id: voice_from_i32(event.noteId),
1296                                    channel: channel_from_i16(event.channel),
1297                                    key: key_from_i16(event.pitch),
1298                                    velocity: event.velocity,
1299                                }),
1300                            );
1301                        } else if event.r#type == EventTypes_::kPolyPressureEvent as u16 {
1302                            let event = unsafe { event.__field0.polyPressure };
1303
1304                            push_process_event(
1305                                &mut process_events,
1306                                ProcessEvent::NoteEvent(NoteEvent::PolyPressure {
1307                                    timing,
1308                                    voice_id: voice_from_i32(event.noteId),
1309                                    channel: channel_from_i16(event.channel),
1310                                    key: key_from_i16(event.pitch),
1311                                    pressure: event.pressure,
1312                                }),
1313                            );
1314                        } else if event.r#type == EventTypes_::kNoteExpressionValueEvent as u16 {
1315                            let event = unsafe { event.__field0.noteExpressionValue };
1316                            match note_expression_controller.translate_event(timing, &event) {
1317                                Some(translated_event) => {
1318                                    push_process_event(
1319                                        &mut process_events,
1320                                        ProcessEvent::NoteEvent(translated_event),
1321                                    );
1322                                }
1323                                None => crate::nice_trace!(
1324                                    "Unhandled note expression type: {}",
1325                                    event.typeId
1326                                ),
1327                            }
1328                        } else if event.r#type == EventTypes_::kDataEvent as u16
1329                            && unsafe { event.__field0.data.r#type } == 0
1330                        {
1331                            // 0 = kMidiSysEx
1332                            let event = unsafe { event.__field0.data };
1333
1334                            // `NoteEvent::from_midi` prints some tracing if parsing fails, which is
1335                            // not necessarily an error
1336                            assert!(!event.bytes.is_null());
1337                            let sysex_buffer = unsafe {
1338                                std::slice::from_raw_parts(event.bytes, event.size as usize)
1339                            };
1340                            if let Ok(note_event) = NoteEvent::from_midi(timing, sysex_buffer) {
1341                                push_process_event(
1342                                    &mut process_events,
1343                                    ProcessEvent::NoteEvent(note_event),
1344                                );
1345                            };
1346                        }
1347                    }
1348                }
1349            }
1350
1351            let mut block_start = 0usize;
1352            let mut block_end;
1353            let mut event_start_idx = 0;
1354            let result = loop {
1355                // In sample-accurate automation mode we'll handle all parameter changes from the
1356                // sorted process event array until we run into for the current sample, and then
1357                // process the block between the current sample and the sample containing the next
1358                // parameter change, if any. All timings also need to be compensated for this. As
1359                // mentioned above, for this to work correctly parameter changes need to be ordered
1360                // before note events at the same index.
1361                // The extra scope is here to make sure we release the borrow on input_events
1362                {
1363                    let mut input_note_events = self.inner.input_note_events.borrow_mut();
1364                    input_note_events.clear();
1365
1366                    block_end = total_buffer_len;
1367                    for event_idx in event_start_idx..process_events.len() {
1368                        match &process_events[event_idx] {
1369                            ProcessEvent::ParameterChange {
1370                                timing,
1371                                hash,
1372                                normalized_value,
1373                            } => {
1374                                // If this parameter change happens after the start of this block, then
1375                                // we'll split the block here and handle this parameter change after
1376                                // we've processed this block
1377                                if *timing != block_start as u32 {
1378                                    event_start_idx = event_idx;
1379                                    block_end = *timing as usize;
1380                                    break;
1381                                }
1382
1383                                self.inner.set_normalized_value_by_hash(
1384                                    *hash,
1385                                    *normalized_value,
1386                                    Some(sample_rate),
1387                                );
1388                            }
1389                            ProcessEvent::NoteEvent(event) => {
1390                                // We need to make sure to compensate the event for any block splitting,
1391                                // since we had to create the event object beforehand
1392                                let mut event = event.clone();
1393                                event.subtract_timing(block_start as u32);
1394
1395                                permit_alloc(|| {
1396                                    // In the rare case the host sends a very large amount of events at once, there
1397                                    // is not much we can do except to just accept the allocation.
1398                                    if input_note_events.len() == input_note_events.capacity() {
1399                                        crate::nice_warn!(
1400                                            "Input note event buffer filled up! This will cause \
1401                                             an allocation."
1402                                        );
1403                                    }
1404                                    input_note_events.push_back(event);
1405                                });
1406                            }
1407                        }
1408                    }
1409                }
1410
1411                let result = if is_param_flush {
1412                    kResultOk
1413                } else {
1414                    // After processing the events we now know where/if the block should be split,
1415                    // and we can start preparing audio processing
1416                    let block_len = block_end - block_start;
1417
1418                    // The buffer manager preallocated buffer slices for all the IO and storage for
1419                    // any axuiliary inputs.
1420                    let mut buffer_manager = self.inner.buffer_manager.borrow_mut();
1421                    let buffers = unsafe {
1422                        buffer_manager.create_buffers(block_start, block_len, |buffer_source| {
1423                            if data.numOutputs > 0
1424                                && !data.outputs.is_null()
1425                                && !(*data.outputs).__field0.channelBuffers32.is_null()
1426                                && has_main_output
1427                            {
1428                                let audio_output = &*data.outputs;
1429                                let ptrs =
1430                                    NonNull::new(audio_output.__field0.channelBuffers32).unwrap();
1431                                let num_channels = audio_output.numChannels as usize;
1432
1433                                *buffer_source.main_output_channel_pointers =
1434                                    Some(ChannelPointers { ptrs, num_channels });
1435                            }
1436
1437                            if data.numInputs > 0
1438                                && !data.inputs.is_null()
1439                                && !(*data.inputs).__field0.channelBuffers32.is_null()
1440                                && has_main_input
1441                            {
1442                                let audio_input = &*data.inputs;
1443                                let ptrs =
1444                                    NonNull::new(audio_input.__field0.channelBuffers32).unwrap();
1445                                let num_channels = audio_input.numChannels as usize;
1446
1447                                *buffer_source.main_input_channel_pointers =
1448                                    Some(ChannelPointers { ptrs, num_channels });
1449                            }
1450
1451                            if !data.inputs.is_null() {
1452                                for (aux_input_no, aux_input_channel_pointers) in buffer_source
1453                                    .aux_input_channel_pointers
1454                                    .iter_mut()
1455                                    .enumerate()
1456                                {
1457                                    let aux_input_idx = aux_input_no + aux_input_start_idx;
1458                                    if aux_input_idx > data.numOutputs as usize {
1459                                        break;
1460                                    }
1461
1462                                    let audio_input = &*data.inputs.add(aux_input_idx);
1463                                    match NonNull::new(audio_input.__field0.channelBuffers32) {
1464                                        Some(ptrs) => {
1465                                            let num_channels = audio_input.numChannels as usize;
1466
1467                                            *aux_input_channel_pointers =
1468                                                Some(ChannelPointers { ptrs, num_channels });
1469                                        }
1470                                        None => continue,
1471                                    }
1472                                }
1473                            }
1474
1475                            if !data.outputs.is_null() {
1476                                for (aux_output_no, aux_output_channel_pointers) in buffer_source
1477                                    .aux_output_channel_pointers
1478                                    .iter_mut()
1479                                    .enumerate()
1480                                {
1481                                    let aux_output_idx = aux_output_no + aux_output_start_idx;
1482                                    if aux_output_idx > data.numOutputs as usize {
1483                                        break;
1484                                    }
1485
1486                                    let audio_output = &*data.outputs.add(aux_output_idx);
1487                                    match NonNull::new(audio_output.__field0.channelBuffers32) {
1488                                        Some(ptrs) => {
1489                                            let num_channels = audio_output.numChannels as usize;
1490
1491                                            *aux_output_channel_pointers =
1492                                                Some(ChannelPointers { ptrs, num_channels });
1493                                        }
1494                                        None => continue,
1495                                    }
1496                                }
1497                            }
1498                        })
1499                    };
1500
1501                    // We already checked whether the host has initiated a parameter flush, but in
1502                    // case it still did something unexpected that we did not catch we'll still try
1503                    // to prevent processing audio when the slices don't contain the values we
1504                    // expect.
1505                    let mut buffer_is_valid = true;
1506                    for output_buffer_slice in
1507                        buffers.main_buffer.as_slice_immutable().iter().chain(
1508                            buffers
1509                                .aux_outputs
1510                                .iter()
1511                                .flat_map(|buffer| buffer.as_slice_immutable().iter()),
1512                        )
1513                    {
1514                        if output_buffer_slice.is_empty() {
1515                            buffer_is_valid = false;
1516                            break;
1517                        }
1518                    }
1519                    crate::nice_debug_assert!(buffer_is_valid);
1520
1521                    // Some of the fields are left empty because VST3 does not provide this
1522                    // information, but the methods on [`Transport`] can reconstruct these values
1523                    // from the other fields
1524                    let mut transport = Transport::new(sample_rate);
1525                    if !data.processContext.is_null() {
1526                        let context = unsafe { &*data.processContext };
1527
1528                        #[allow(clippy::unnecessary_cast)]
1529                        {
1530                            transport.playing = context.state & kPlaying as u32 != 0;
1531                            transport.recording = context.state & kRecording as u32 != 0;
1532
1533                            if context.state & kTempoValid as u32 != 0 {
1534                                transport.tempo = Some(context.tempo);
1535                            }
1536
1537                            if context.state & kTimeSigValid as u32 != 0 {
1538                                transport.time_sig_numerator = Some(context.timeSigNumerator);
1539                                transport.time_sig_denominator = Some(context.timeSigDenominator);
1540                            }
1541                        }
1542
1543                        // We need to compensate for the block splitting here
1544                        transport.pos_samples =
1545                            Some(context.projectTimeSamples + block_start as i64);
1546                        #[allow(clippy::unnecessary_cast)]
1547                        if context.state & kProjectTimeMusicValid as u32 != 0 {
1548                            if P::SAMPLE_ACCURATE_AUTOMATION
1549                                && block_start > 0
1550                                && (context.state & kTempoValid as u32 != 0)
1551                            {
1552                                transport.pos_beats = Some(
1553                                    context.projectTimeMusic
1554                                        + (block_start as f64 / sample_rate as f64 / 60.0
1555                                            * context.tempo),
1556                                );
1557                            } else {
1558                                transport.pos_beats = Some(context.projectTimeMusic);
1559                            }
1560                        }
1561
1562                        #[allow(clippy::unnecessary_cast)]
1563                        if context.state & kBarPositionValid as u32 != 0 {
1564                            if P::SAMPLE_ACCURATE_AUTOMATION && block_start > 0 {
1565                                // The transport object knows how to recompute this from the other information
1566                                transport.bar_start_pos_beats =
1567                                    match transport.bar_start_pos_beats() {
1568                                        Some(updated) => Some(updated),
1569                                        None => Some(context.barPositionMusic),
1570                                    };
1571                            } else {
1572                                transport.bar_start_pos_beats = Some(context.barPositionMusic);
1573                            }
1574                        }
1575                        #[allow(clippy::unnecessary_cast)]
1576                        if context.state & kCycleActive as u32 != 0
1577                            && context.state & kCycleValid as u32 != 0
1578                        {
1579                            transport.loop_range_beats =
1580                                Some((context.cycleStartMusic, context.cycleEndMusic));
1581                        }
1582                    }
1583
1584                    let result = if buffer_is_valid {
1585                        // In the case the host misbehaves and tries to activate/deactive the plugin while the
1586                        // process loop is still running, just return an error.
1587                        if let Some(mut plugin) = self.inner.plugin.try_lock() {
1588                            let mut aux = AuxiliaryBuffers {
1589                                inputs: buffers.aux_inputs,
1590                                outputs: buffers.aux_outputs,
1591                            };
1592
1593                            let host_out_events = unsafe { ComRef::from_raw(data.outputEvents) };
1594
1595                            let mut context = self.inner.make_process_context(
1596                                transport,
1597                                host_out_events,
1598                                total_buffer_len,
1599                                block_start,
1600                            );
1601
1602                            let result =
1603                                plugin.process(buffers.main_buffer, &mut aux, &mut context);
1604                            self.inner.last_process_status.store(result);
1605                            result
1606                        } else {
1607                            ProcessStatus::Error("Failed to acquire plugin lock")
1608                        }
1609                    } else {
1610                        ProcessStatus::Normal
1611                    };
1612
1613                    match result {
1614                        ProcessStatus::Error(err) => {
1615                            crate::nice_debug_assert_failure!("Process error: {}", err);
1616
1617                            return kResultFalse;
1618                        }
1619                        _ => kResultOk,
1620                    }
1621                };
1622
1623                // If our block ends at the end of the buffer then that means there are no more
1624                // unprocessed (parameter) events. If there are more events, we'll just keep going
1625                // through this process until we've processed the entire buffer.
1626                if block_end == total_buffer_len {
1627                    break result;
1628                } else {
1629                    block_start = block_end;
1630                }
1631            };
1632
1633            // After processing audio, we'll check if the editor has sent us updated plugin state.
1634            // We'll restore that here on the audio thread to prevent changing the values during the
1635            // process call and also to prevent inconsistent state when the host also wants to load
1636            // plugin state.
1637            // FIXME: Zero capacity channels allocate on receiving, find a better alternative that
1638            //        doesn't do that
1639            let updated_state = permit_alloc(|| self.inner.updated_state_receiver.try_recv());
1640            if let Ok(mut state) = updated_state {
1641                self.inner.set_state_inner(&mut state);
1642
1643                // We'll pass the state object back to the GUI thread so deallocation can happen
1644                // there without potentially blocking the audio thread
1645                if let Err(err) = self.inner.updated_state_sender.send(state) {
1646                    crate::nice_debug_assert_failure!(
1647                        "Failed to send state object back to GUI thread: {}",
1648                        err
1649                    );
1650                };
1651            }
1652
1653            result
1654        })
1655    }
1656
1657    unsafe fn getTailSamples(&self) -> uint32 {
1658        // https://github.com/steinbergmedia/vst3_pluginterfaces/blob/2ad397ade5b51007860bedb3b01b8afd2c5f6fba/vst/ivstaudioprocessor.h#L145-L159
1659        match self.inner.last_process_status.load() {
1660            ProcessStatus::Tail(samples) => samples,
1661            ProcessStatus::KeepAlive => u32::MAX, // kInfiniteTail
1662            _ => 0,                               // kNoTail
1663        }
1664    }
1665}
1666
1667impl<P: Vst3Plugin> IMidiMappingTrait for Wrapper<P> {
1668    unsafe fn getMidiControllerAssignment(
1669        &self,
1670        bus_index: int32,
1671        channel: int16,
1672        midi_cc_number: CtrlNumber,
1673        param_id: *mut ParamID,
1674    ) -> tresult {
1675        if P::MIDI_INPUT < MidiConfig::MidiCCs
1676            || bus_index != 0
1677            || !(0..VST3_MIDI_CHANNELS as i16).contains(&channel)
1678            || !(0..VST3_MIDI_CCS as i16).contains(&midi_cc_number)
1679        {
1680            return kResultFalse;
1681        }
1682
1683        check_null_ptr!(param_id);
1684
1685        // We reserve a contiguous parameter range right at the end of the allowed parameter indices
1686        // for these MIDI CC parameters
1687        unsafe {
1688            *param_id =
1689                VST3_MIDI_PARAMS_START + midi_cc_number as u32 + (channel as u32 * VST3_MIDI_CCS)
1690        };
1691
1692        kResultOk
1693    }
1694}
1695
1696impl<P: Vst3Plugin> INoteExpressionControllerTrait for Wrapper<P> {
1697    unsafe fn getNoteExpressionCount(&self, bus_idx: int32, _channel: int16) -> int32 {
1698        // Apparently you need to define the predefined note expressions. Thanks VST3.
1699        if P::MIDI_INPUT >= MidiConfig::Basic && bus_idx == 0 {
1700            note_expressions::KNOWN_NOTE_EXPRESSIONS.len() as i32
1701        } else {
1702            0
1703        }
1704    }
1705
1706    unsafe fn getNoteExpressionInfo(
1707        &self,
1708        bus_idx: int32,
1709        _channel: int16,
1710        note_expression_idx: int32,
1711        info: *mut NoteExpressionTypeInfo,
1712    ) -> tresult {
1713        if P::MIDI_INPUT < MidiConfig::Basic
1714            || bus_idx != 0
1715            || !(0..note_expressions::KNOWN_NOTE_EXPRESSIONS.len() as i32)
1716                .contains(&note_expression_idx)
1717        {
1718            return kInvalidArgument;
1719        }
1720
1721        check_null_ptr!(info);
1722
1723        unsafe { *info = mem::zeroed() };
1724
1725        let info = unsafe { &mut *info };
1726        let note_expression_info =
1727            &note_expressions::KNOWN_NOTE_EXPRESSIONS[note_expression_idx as usize];
1728        info.typeId = note_expression_info.type_id;
1729        u16strlcpy(&mut info.title, note_expression_info.title);
1730        u16strlcpy(&mut info.shortTitle, note_expression_info.title);
1731        u16strlcpy(&mut info.units, note_expression_info.unit);
1732        info.unitId = kNoParentUnitId;
1733        // This should not be needed since they're predefined, but then again you'd think you also
1734        // wouldn't need to define predefined note expressions now do you?
1735        info.valueDesc = NoteExpressionValueDescription {
1736            defaultValue: 0.5,
1737            minimum: 0.0,
1738            maximum: 1.0,
1739            stepCount: 0,
1740        };
1741        info.associatedParameterId = kNoParamId;
1742        info.flags = 1 << 2; // kIsAbsolute
1743
1744        kResultOk
1745    }
1746
1747    unsafe fn getNoteExpressionStringByValue(
1748        &self,
1749        _bus_idx: int32,
1750        _channel: int16,
1751        _id: NoteExpressionTypeID,
1752        _value: NoteExpressionValue,
1753        _string: *mut String128,
1754    ) -> tresult {
1755        kResultFalse
1756    }
1757
1758    unsafe fn getNoteExpressionValueByString(
1759        &self,
1760        _bus_idx: int32,
1761        _channel: int16,
1762        _id: NoteExpressionTypeID,
1763        _string: *const TChar,
1764        _value: *mut NoteExpressionValue,
1765    ) -> tresult {
1766        kResultFalse
1767    }
1768}
1769
1770impl<P: Vst3Plugin> IProcessContextRequirementsTrait for Wrapper<P> {
1771    #[allow(clippy::unnecessary_cast)]
1772    unsafe fn getProcessContextRequirements(&self) -> uint32 {
1773        (IProcessContextRequirements_::Flags_::kNeedProjectTimeMusic
1774            | IProcessContextRequirements_::Flags_::kNeedBarPositionMusic
1775            | IProcessContextRequirements_::Flags_::kNeedCycleMusic
1776            | IProcessContextRequirements_::Flags_::kNeedTimeSignature
1777            | IProcessContextRequirements_::Flags_::kNeedTempo
1778            | IProcessContextRequirements_::Flags_::kNeedTransportState) as u32
1779    }
1780}
1781
1782impl<P: Vst3Plugin> IUnitInfoTrait for Wrapper<P> {
1783    unsafe fn getUnitCount(&self) -> int32 {
1784        self.inner.param_units.len() as i32
1785    }
1786
1787    unsafe fn getUnitInfo(&self, unit_index: int32, info: *mut UnitInfo) -> tresult {
1788        check_null_ptr!(info);
1789
1790        match self.inner.param_units.info(unit_index as usize) {
1791            Some((unit_id, unit_info)) => {
1792                unsafe { *info = mem::zeroed() };
1793
1794                let info = unsafe { &mut *info };
1795                info.id = unit_id;
1796                info.parentUnitId = unit_info.parent_id;
1797                u16strlcpy(&mut info.name, &unit_info.name);
1798                info.programListId = kNoProgramListId;
1799
1800                kResultOk
1801            }
1802            None => kInvalidArgument,
1803        }
1804    }
1805
1806    unsafe fn getProgramListCount(&self) -> int32 {
1807        // TODO: Do we want program lists? Probably not, CLAP doesn't even support them.
1808        0
1809    }
1810
1811    unsafe fn getProgramListInfo(
1812        &self,
1813        _list_index: int32,
1814        _info: *mut ProgramListInfo,
1815    ) -> tresult {
1816        kInvalidArgument
1817    }
1818
1819    unsafe fn getProgramName(
1820        &self,
1821        _list_id: ProgramListID,
1822        _program_index: int32,
1823        _name: *mut String128,
1824    ) -> tresult {
1825        kInvalidArgument
1826    }
1827
1828    unsafe fn getProgramInfo(
1829        &self,
1830        _list_id: ProgramListID,
1831        _program_index: int32,
1832        _attribute_id: CString,
1833        _attribute_value: *mut String128,
1834    ) -> tresult {
1835        kInvalidArgument
1836    }
1837
1838    unsafe fn hasProgramPitchNames(&self, _id: ProgramListID, _index: int32) -> tresult {
1839        // TODO: Support note names once someone requests it
1840        kInvalidArgument
1841    }
1842
1843    unsafe fn getProgramPitchName(
1844        &self,
1845        _id: ProgramListID,
1846        _index: int32,
1847        _pitch: int16,
1848        _name: *mut String128,
1849    ) -> tresult {
1850        kInvalidArgument
1851    }
1852
1853    unsafe fn getSelectedUnit(&self) -> UnitID {
1854        // No! Steinberg! I don't want any of this! I just want to group parameters!
1855        kRootUnitId
1856    }
1857
1858    unsafe fn selectUnit(&self, _id: UnitID) -> tresult {
1859        kResultFalse
1860    }
1861
1862    unsafe fn getUnitByBus(
1863        &self,
1864        _type_: MediaType,
1865        _dir: BusDirection,
1866        _bus_index: int32,
1867        _channel: int32,
1868        _unit_id: *mut UnitID,
1869    ) -> tresult {
1870        // Stahp it!
1871        kResultFalse
1872    }
1873
1874    unsafe fn setUnitProgramData(
1875        &self,
1876        _list_or_unit: int32,
1877        _program_idx: int32,
1878        _data: *mut IBStream,
1879    ) -> tresult {
1880        kInvalidArgument
1881    }
1882}
1883
1884impl<P: Vst3Plugin> IInfoListenerTrait for Wrapper<P> {
1885    unsafe fn setChannelContextInfos(&self, list: *mut IAttributeList) -> tresult {
1886        #[cfg(not(feature = "editor"))]
1887        {
1888            let _ = list;
1889            return kResultOk;
1890        }
1891
1892        #[cfg(feature = "editor")]
1893        {
1894            use nice_plug_core::plugin::{TrackColor, TrackInfo};
1895            use vst3::Steinberg::Vst::{ChannelContext, IAttributeListTrait};
1896
1897            fn track_color_from_vst3_color(color: u32) -> TrackColor {
1898                TrackColor::new(
1899                    ((color >> 16) & 0xFF) as u8,
1900                    ((color >> 8) & 0xFF) as u8,
1901                    (color & 0xFF) as u8,
1902                    ((color >> 24) & 0xFF) as u8,
1903                )
1904            }
1905            check_null_ptr!(list);
1906
1907            let list = unsafe { ComRef::from_raw(list) };
1908            let Some(list) = list else {
1909                return kInvalidArgument;
1910            };
1911
1912            permit_alloc(|| {
1913                let mut current_track_info = self.inner.current_track_info.borrow_mut();
1914                let mut name = current_track_info.name().to_owned();
1915                let mut color = current_track_info.color();
1916
1917                let mut name_buf: String128 = [0; 128];
1918                if unsafe {
1919                    list.getString(
1920                        ChannelContext::kChannelNameKey,
1921                        name_buf.as_mut_ptr(),
1922                        mem::size_of::<String128>() as u32,
1923                    )
1924                } == kResultOk
1925                    && let Ok(cstr) = U16CStr::from_slice_truncate(&name_buf)
1926                {
1927                    name = cstr.to_string_lossy();
1928                } // Else if getting the string failed or if there is no null terminator, do nothing with the name.
1929
1930                let mut color_value = 0i64;
1931                if unsafe { list.getInt(ChannelContext::kChannelColorKey, &mut color_value) }
1932                    == kResultOk
1933                {
1934                    color = Some(track_color_from_vst3_color(color_value as u32));
1935                }
1936
1937                let track_info = TrackInfo::new(name, color);
1938                *current_track_info = track_info.clone();
1939
1940                if let Some(editor) = self.inner.editor.borrow().as_ref() {
1941                    editor.lock().track_info_updated(track_info);
1942                }
1943            });
1944
1945            kResultOk
1946        }
1947    }
1948}