Skip to main content

reaper_medium/
control_surface.rs

1use super::MediaTrack;
2use crate::{
3    require_non_null_panic, AutomationMode, Bpm, InputMonitoringMode, PlaybackSpeedFactor,
4    ReaperNormalizedFxParamValue, ReaperPanValue, ReaperVersion, ReaperVolumeValue,
5    TrackFxChainType, TrackFxLocation, TryFromRawError,
6};
7
8use reaper_low;
9use reaper_low::raw;
10use std::borrow::Cow;
11
12use std::ffi::CStr;
13use std::fmt::Debug;
14use std::os::raw::c_void;
15use std::panic::RefUnwindSafe;
16use std::ptr::null_mut;
17
18/// Consumers need to implement this trait in order to get notified about various REAPER events.
19///
20/// All callbacks are invoked in the main thread.
21///
22/// See [`plugin_register_add_csurf_inst`].
23///
24/// [`plugin_register_add_csurf_inst`]: struct.Reaper.html#method.plugin_register_add_csurf_inst
25pub trait MediumReaperControlSurface: RefUnwindSafe + Debug {
26    /// Should return the control surface type.
27    ///
28    /// Must be a simple unique string with only A-Z, 0-9, no spaces or other characters.
29    ///
30    /// Return `None` if this is a control surface behind the scenes.
31    fn get_type_string(&self) -> Option<Cow<'static, CStr>> {
32        None
33    }
34
35    /// Should return the control surface description.
36    ///
37    /// Should be a human readable description, can include instance-specific information.
38    ///
39    /// Return `None` if this is a control surface behind the scenes.
40    fn get_desc_string(&self) -> Option<Cow<'static, CStr>> {
41        None
42    }
43
44    /// Should return a string of configuration data.
45    ///
46    /// Return `None` if this is a control surface behind the scenes.
47    fn get_config_string(&self) -> Option<Cow<'static, CStr>> {
48        None
49    }
50
51    /// Should close the control surface without sending *reset* messages.
52    ///
53    /// Prevent *reset* being sent in the destructor.
54    fn close_no_reset(&self) {}
55
56    /// Called on each main loop cycle.
57    ///
58    /// Called about 30 times per second.
59    fn run(&mut self) {}
60
61    /// Called when the track list has changed.
62    ///
63    /// This is called for each track once.
64    fn set_track_list_change(&self) {}
65
66    /// Called when the volume of a track has changed.
67    fn set_surface_volume(&self, _args: SetSurfaceVolumeArgs) {}
68
69    /// Called when the pan of a track has changed.
70    fn set_surface_pan(&self, _args: SetSurfacePanArgs) {}
71
72    /// Called when a track has been muted or unmuted.
73    fn set_surface_mute(&self, _args: SetSurfaceMuteArgs) {}
74
75    /// Called when a track has been selected or unselected.
76    fn set_surface_selected(&self, _args: SetSurfaceSelectedArgs) {}
77
78    /// Called when a track has been soloed or unsoloed.
79    ///
80    /// If it's the master track, it means "any solo".
81    fn set_surface_solo(&self, _args: SetSurfaceSoloArgs) {}
82
83    /// Called when a track has been armed or unarmed for recording.
84    fn set_surface_rec_arm(&self, _args: SetSurfaceRecArmArgs) {}
85
86    /// Called when the transport state has changed (playing, paused, recording).
87    fn set_play_state(&self, _args: SetPlayStateArgs) {}
88
89    /// Called when repeat has been enabled or disabled.
90    fn set_repeat_state(&self, _args: SetRepeatStateArgs) {}
91
92    /// Called when a track name has changed.
93    fn set_track_title(&self, _args: SetTrackTitleArgs) {}
94
95    fn get_touch_state(&self, _args: GetTouchStateArgs) -> bool {
96        false
97    }
98
99    /// Called when the automation mode of the current track has changed.
100    fn set_auto_mode(&self, _args: SetAutoModeArgs) {}
101
102    /// Should flush the control states.
103    fn reset_cached_vol_pan_states(&self) {}
104
105    /// Called when a track has been selected.
106    fn on_track_selection(&self, _args: OnTrackSelectionArgs) {}
107
108    /// Should return whether the given modifier key is currently pressed on the surface.
109    fn is_key_down(&self, _args: IsKeyDownArgs) -> bool {
110        false
111    }
112
113    /// Generic method which is called for many kinds of events. Prefer implementing the type-safe
114    /// `ext_` methods instead!
115    ///
116    /// *reaper-rs* calls this method only if you didn't process the event already in one of the
117    /// `ext_` methods. The meaning of the return value depends on the particular event type
118    /// ([`args.call`]). In any case returning 0 means that the event has not been handled.
119    ///
120    /// # Safety
121    ///
122    /// Implementing this is unsafe because you need to deal with raw pointers.
123    ///
124    /// [`args.call`]: struct.ExtendedArgs.html#structfield.call
125    unsafe fn extended(&self, _args: ExtendedArgs) -> i32 {
126        0
127    }
128
129    /// Called when the input monitoring mode of a track has has changed.
130    fn ext_set_input_monitor(&self, _args: ExtSetInputMonitorArgs) -> i32 {
131        0
132    }
133
134    /// Called when a parameter of an FX in the normal FX chain has changed its value.
135    ///
136    /// For REAPER < 5.95 this is also called for an FX in the input FX chain. In this case there's
137    /// no way to know whether the given FX index refers to the normal or input FX chain.
138    fn ext_set_fx_param(&self, _args: ExtSetFxParamArgs) -> i32 {
139        0
140    }
141
142    /// Called when a parameter of an FX in the input FX chain has changed its value.
143    ///
144    /// Only called for REAPER >= 5.95.
145    fn ext_set_fx_param_rec_fx(&self, _args: ExtSetFxParamArgs) -> i32 {
146        0
147    }
148
149    /// Called when a an FX has been enabled or disabled.
150    fn ext_set_fx_enabled(&self, _args: ExtSetFxEnabledArgs) -> i32 {
151        0
152    }
153
154    /// Called when the volume of a track send has changed.
155    fn ext_set_send_volume(&self, _args: ExtSetSendVolumeArgs) -> i32 {
156        0
157    }
158
159    /// Called when the pan of a track send has changed.
160    fn ext_set_send_pan(&self, _args: ExtSetSendPanArgs) -> i32 {
161        0
162    }
163
164    /// Called when a certain FX has gained focus.
165    fn ext_set_focused_fx(&self, _args: ExtSetFocusedFxArgs) -> i32 {
166        0
167    }
168
169    /// Called when a certain FX has been touched.
170    fn ext_set_last_touched_fx(&self, _args: ExtSetLastTouchedFxArgs) -> i32 {
171        0
172    }
173
174    /// Called when the user interface of a certain FX has been opened.
175    fn ext_set_fx_open(&self, _args: ExtSetFxOpenArgs) -> i32 {
176        0
177    }
178
179    /// Called when an FX has been added, removed or when it changed its position in the chain.
180    fn ext_set_fx_change(&self, _args: ExtSetFxChangeArgs) -> i32 {
181        0
182    }
183
184    /// Called when the master tempo or play rate has changed.
185    fn ext_set_bpm_and_play_rate(&self, _args: ExtSetBpmAndPlayRateArgs) -> i32 {
186        0
187    }
188}
189
190#[derive(Copy, Clone, PartialEq, Debug)]
191pub struct SetSurfaceVolumeArgs {
192    pub track: MediaTrack,
193    pub volume: ReaperVolumeValue,
194}
195
196#[derive(Copy, Clone, PartialEq, Debug)]
197pub struct SetSurfacePanArgs {
198    pub track: MediaTrack,
199    pub pan: ReaperPanValue,
200}
201
202#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
203pub struct SetSurfaceMuteArgs {
204    pub track: MediaTrack,
205    pub is_mute: bool,
206}
207
208#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
209pub struct SetSurfaceSelectedArgs {
210    pub track: MediaTrack,
211    pub is_selected: bool,
212}
213
214#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
215pub struct SetSurfaceSoloArgs {
216    pub track: MediaTrack,
217    pub is_solo: bool,
218}
219
220#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
221pub struct SetSurfaceRecArmArgs {
222    pub track: MediaTrack,
223    pub is_armed: bool,
224}
225
226#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
227pub struct SetPlayStateArgs {
228    pub is_playing: bool,
229    pub is_paused: bool,
230    pub is_recording: bool,
231}
232
233#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
234pub struct SetRepeatStateArgs {
235    pub is_enabled: bool,
236}
237
238#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
239pub struct SetTrackTitleArgs<'a> {
240    pub track: MediaTrack,
241    pub name: &'a CStr,
242}
243
244#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
245pub struct GetTouchStateArgs {
246    pub track: MediaTrack,
247    pub is_pan: bool,
248}
249
250#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
251pub struct SetAutoModeArgs {
252    pub mode: AutomationMode,
253}
254
255#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
256pub struct OnTrackSelectionArgs {
257    pub track: MediaTrack,
258}
259
260#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
261pub struct IsKeyDownArgs {
262    pub key: ModKey,
263}
264
265#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
266pub struct ExtendedArgs {
267    /// Represents the type of event.
268    call: i32,
269    parm_1: *mut c_void,
270    parm_2: *mut c_void,
271    parm_3: *mut c_void,
272}
273
274#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
275pub struct ExtSetInputMonitorArgs {
276    pub track: MediaTrack,
277    pub mode: InputMonitoringMode,
278}
279
280#[derive(Copy, Clone, PartialEq, Debug)]
281pub struct ExtSetFxParamArgs {
282    pub track: MediaTrack,
283    pub fx_index: u32,
284    pub param_index: u32,
285    pub param_value: ReaperNormalizedFxParamValue,
286}
287
288#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
289pub struct ExtSetFxEnabledArgs {
290    pub track: MediaTrack,
291    pub fx_location: VersionDependentTrackFxLocation,
292    pub is_enabled: bool,
293}
294
295#[derive(Copy, Clone, PartialEq, Debug)]
296pub struct ExtSetSendVolumeArgs {
297    pub track: MediaTrack,
298    pub send_index: u32,
299    pub volume: ReaperVolumeValue,
300}
301
302#[derive(Copy, Clone, PartialEq, Debug)]
303pub struct ExtSetSendPanArgs {
304    pub track: MediaTrack,
305    pub send_index: u32,
306    pub pan: ReaperPanValue,
307}
308
309#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
310pub struct ExtSetFocusedFxArgs {
311    pub fx_location: Option<QualifiedFxLocation>,
312}
313
314#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
315pub struct ExtSetLastTouchedFxArgs {
316    pub fx_location: Option<QualifiedFxLocation>,
317}
318
319#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
320pub struct ExtSetFxOpenArgs {
321    pub track: MediaTrack,
322    pub fx_location: VersionDependentTrackFxLocation,
323    pub is_open: bool,
324}
325
326#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
327pub struct ExtSetFxChangeArgs {
328    pub track: MediaTrack,
329    /// In REAPER < 5.95 this is `None` because we can't know if the change happened in the normal
330    /// or input FX chain.
331    pub fx_chain_type: Option<TrackFxChainType>,
332}
333
334#[derive(Copy, Clone, PartialEq, Debug)]
335pub struct ExtSetBpmAndPlayRateArgs {
336    pub tempo: Option<Bpm>,
337    pub play_rate: Option<PlaybackSpeedFactor>,
338}
339
340/// A modifier key.
341#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
342pub enum ModKey {
343    /// SHIFT key.
344    Shift,
345    /// CTRL key.
346    Control,
347    /// ALT key.
348    Menu,
349    /// Custom modifier key according to
350    /// [this list](https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes).
351    Custom(u32),
352}
353
354impl ModKey {
355    /// Converts an integer as returned by the low-level API to a mod key.
356    pub fn try_from_raw(value: i32) -> Result<ModKey, TryFromRawError<i32>> {
357        if value < 0 {
358            return Err(TryFromRawError::new("couldn't convert to mod key", value));
359        };
360        let value = value as u32;
361        use ModKey::*;
362        let key = match value {
363            raw::VK_SHIFT => Shift,
364            raw::VK_CONTROL => Control,
365            raw::VK_MENU => Menu,
366            _ => Custom(value),
367        };
368        Ok(key)
369    }
370}
371
372/// Location of a track or take FX including the parent track.
373#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
374pub struct QualifiedFxLocation {
375    /// Parent track.
376    pub track: MediaTrack,
377    /// Location of FX on the parent track.
378    pub fx_location: VersionDependentFxLocation,
379}
380
381/// Location of a track or take FX.
382#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
383pub enum VersionDependentFxLocation {
384    /// It's a take FX.
385    ///
386    /// The take index is currently not exposed by REAPER.
387    TakeFx {
388        /// Index of the item on that track.
389        item_index: u32,
390        /// Index of the FX within the take FX chain.
391        fx_index: u32,
392    },
393    /// It's a track FX.
394    TrackFx(VersionDependentTrackFxLocation),
395}
396
397/// Location of a track FX.
398#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
399pub enum VersionDependentTrackFxLocation {
400    /// This is REAPER < 5.95.
401    ///
402    /// The given index can refer either to the input or output FX chain - we don't know.
403    Old(u32),
404    /// This is REAPER >= 5.95.
405    ///
406    /// It's possible to distinguish between input and output FX.
407    New(TrackFxLocation),
408}
409
410#[derive(Debug)]
411pub(crate) struct DelegatingControlSurface {
412    delegate: Box<dyn MediumReaperControlSurface>,
413    // Capabilities depending on REAPER version
414    supports_detection_of_input_fx: bool,
415    supports_detection_of_input_fx_in_set_fx_change: bool,
416}
417
418impl DelegatingControlSurface {
419    pub fn new(
420        delegate: impl MediumReaperControlSurface + 'static,
421        reaper_version: &ReaperVersion,
422    ) -> DelegatingControlSurface {
423        let reaper_version_5_95: ReaperVersion = ReaperVersion::new("5.95");
424        DelegatingControlSurface {
425            delegate: Box::new(delegate),
426            // since pre1,
427            supports_detection_of_input_fx: reaper_version >= &reaper_version_5_95,
428            // since pre2 to be accurate but so what
429            supports_detection_of_input_fx_in_set_fx_change: reaper_version >= &reaper_version_5_95,
430        }
431    }
432
433    unsafe fn get_as_qualified_fx_ref(
434        &self,
435        media_track_ptr: *mut c_void,
436        media_item_ptr: *mut c_void,
437        fx_index_ptr: *mut c_void,
438    ) -> Option<QualifiedFxLocation> {
439        if media_track_ptr.is_null() {
440            return None;
441        }
442        Some(QualifiedFxLocation {
443            track: require_non_null_panic(media_track_ptr as *mut raw::MediaTrack),
444            fx_location: if media_item_ptr.is_null() {
445                VersionDependentFxLocation::TrackFx(
446                    self.get_as_version_dependent_track_fx_ref(fx_index_ptr),
447                )
448            } else {
449                VersionDependentFxLocation::TakeFx {
450                    item_index: deref_as::<i32>(media_item_ptr).expect("media item pointer is null")
451                        as u32,
452                    fx_index: deref_as::<i32>(fx_index_ptr).expect("FX index pointer is null")
453                        as u32,
454                }
455            },
456        })
457    }
458
459    unsafe fn get_as_version_dependent_track_fx_ref(
460        &self,
461        ptr: *mut c_void,
462    ) -> VersionDependentTrackFxLocation {
463        let fx_index = deref_as::<i32>(ptr).expect("FX index is null");
464        if self.supports_detection_of_input_fx {
465            VersionDependentTrackFxLocation::New(
466                TrackFxLocation::try_from_raw(fx_index).expect("weird FX index"),
467            )
468        } else {
469            VersionDependentTrackFxLocation::Old(fx_index as u32)
470        }
471    }
472}
473
474#[allow(non_snake_case)]
475impl reaper_low::IReaperControlSurface for DelegatingControlSurface {
476    fn GetTypeString(&self) -> *const i8 {
477        self.delegate
478            .get_type_string()
479            .map(|o| o.as_ptr())
480            .unwrap_or(null_mut())
481    }
482
483    fn GetDescString(&self) -> *const i8 {
484        self.delegate
485            .get_desc_string()
486            .map(|o| o.as_ptr())
487            .unwrap_or(null_mut())
488    }
489
490    fn GetConfigString(&self) -> *const i8 {
491        self.delegate
492            .get_config_string()
493            .map(|o| o.as_ptr())
494            .unwrap_or(null_mut())
495    }
496
497    fn CloseNoReset(&self) {
498        self.delegate.close_no_reset()
499    }
500
501    fn Run(&mut self) {
502        self.delegate.run()
503    }
504
505    fn SetTrackListChange(&self) {
506        self.delegate.set_track_list_change()
507    }
508
509    fn SetSurfaceVolume(&self, trackid: *mut raw::MediaTrack, volume: f64) {
510        self.delegate.set_surface_volume(SetSurfaceVolumeArgs {
511            track: require_non_null_panic(trackid),
512            volume: ReaperVolumeValue(volume),
513        })
514    }
515
516    fn SetSurfacePan(&self, trackid: *mut raw::MediaTrack, pan: f64) {
517        self.delegate.set_surface_pan(SetSurfacePanArgs {
518            track: require_non_null_panic(trackid),
519            pan: ReaperPanValue(pan),
520        })
521    }
522
523    fn SetSurfaceMute(&self, trackid: *mut raw::MediaTrack, mute: bool) {
524        self.delegate.set_surface_mute(SetSurfaceMuteArgs {
525            track: require_non_null_panic(trackid),
526            is_mute: mute,
527        })
528    }
529
530    fn SetSurfaceSelected(&self, trackid: *mut raw::MediaTrack, selected: bool) {
531        self.delegate.set_surface_selected(SetSurfaceSelectedArgs {
532            track: require_non_null_panic(trackid),
533            is_selected: selected,
534        })
535    }
536
537    fn SetSurfaceSolo(&self, trackid: *mut raw::MediaTrack, solo: bool) {
538        self.delegate.set_surface_solo(SetSurfaceSoloArgs {
539            track: require_non_null_panic(trackid),
540            is_solo: solo,
541        })
542    }
543
544    fn SetSurfaceRecArm(&self, trackid: *mut raw::MediaTrack, recarm: bool) {
545        self.delegate.set_surface_rec_arm(SetSurfaceRecArmArgs {
546            track: require_non_null_panic(trackid),
547            is_armed: recarm,
548        })
549    }
550
551    fn SetPlayState(&self, play: bool, pause: bool, rec: bool) {
552        self.delegate.set_play_state(SetPlayStateArgs {
553            is_playing: play,
554            is_paused: pause,
555            is_recording: rec,
556        })
557    }
558
559    fn SetRepeatState(&self, rep: bool) {
560        self.delegate
561            .set_repeat_state(SetRepeatStateArgs { is_enabled: rep })
562    }
563
564    fn SetTrackTitle(&self, trackid: *mut raw::MediaTrack, title: *const i8) {
565        self.delegate.set_track_title(SetTrackTitleArgs {
566            track: require_non_null_panic(trackid),
567            name: unsafe { CStr::from_ptr(title) },
568        })
569    }
570
571    fn GetTouchState(&self, trackid: *mut raw::MediaTrack, isPan: i32) -> bool {
572        self.delegate.get_touch_state(GetTouchStateArgs {
573            track: require_non_null_panic(trackid),
574            is_pan: isPan != 0,
575        })
576    }
577
578    fn SetAutoMode(&self, mode: i32) {
579        self.delegate.set_auto_mode(SetAutoModeArgs {
580            mode: AutomationMode::try_from_raw(mode).expect("unknown automation mode"),
581        })
582    }
583
584    fn ResetCachedVolPanStates(&self) {
585        self.delegate.reset_cached_vol_pan_states()
586    }
587
588    fn OnTrackSelection(&self, trackid: *mut raw::MediaTrack) {
589        self.delegate.on_track_selection(OnTrackSelectionArgs {
590            track: require_non_null_panic(trackid),
591        })
592    }
593
594    fn IsKeyDown(&self, key: i32) -> bool {
595        self.delegate.is_key_down(IsKeyDownArgs {
596            key: ModKey::try_from_raw(key).expect("unknown key code"),
597        })
598    }
599
600    fn Extended(
601        &self,
602        call: i32,
603        parm1: *mut c_void,
604        parm2: *mut c_void,
605        parm3: *mut c_void,
606    ) -> i32 {
607        let result = unsafe {
608            // TODO-low Delegate all known CSURF_EXT_ constants
609            match call as u32 {
610                raw::CSURF_EXT_SETINPUTMONITOR => {
611                    let recmon: i32 = deref_as(parm2).expect("recmon pointer is null");
612                    self.delegate.ext_set_input_monitor(ExtSetInputMonitorArgs {
613                        track: require_non_null_panic(parm1 as *mut raw::MediaTrack),
614                        mode: InputMonitoringMode::try_from_raw(recmon)
615                            .expect("unknown input monitoring mode"),
616                    })
617                }
618                raw::CSURF_EXT_SETFXPARAM | raw::CSURF_EXT_SETFXPARAM_RECFX => {
619                    let fxidx_and_paramidx: i32 =
620                        deref_as(parm2).expect("fx/param index pointer is null");
621                    let normalized_value: f64 = deref_as(parm3).expect("value pointer is null");
622                    let fx_index = (fxidx_and_paramidx >> 16) & 0xffff;
623                    let param_index = fxidx_and_paramidx & 0xffff;
624                    let args = ExtSetFxParamArgs {
625                        track: require_non_null_panic(parm1 as *mut raw::MediaTrack),
626                        fx_index: fx_index as u32,
627                        param_index: param_index as u32,
628                        param_value: ReaperNormalizedFxParamValue::new(normalized_value),
629                    };
630                    match call as u32 {
631                        raw::CSURF_EXT_SETFXPARAM => self.delegate.ext_set_fx_param(args),
632                        raw::CSURF_EXT_SETFXPARAM_RECFX => {
633                            self.delegate.ext_set_fx_param_rec_fx(args)
634                        }
635                        _ => unreachable!(),
636                    }
637                }
638                raw::CSURF_EXT_SETFOCUSEDFX => {
639                    self.delegate.ext_set_focused_fx(ExtSetFocusedFxArgs {
640                        fx_location: self.get_as_qualified_fx_ref(parm1, parm2, parm3),
641                    })
642                }
643                raw::CSURF_EXT_SETLASTTOUCHEDFX => {
644                    self.delegate
645                        .ext_set_last_touched_fx(ExtSetLastTouchedFxArgs {
646                            fx_location: self.get_as_qualified_fx_ref(parm1, parm2, parm3),
647                        })
648                }
649                raw::CSURF_EXT_SETFXOPEN => self.delegate.ext_set_fx_open(ExtSetFxOpenArgs {
650                    track: require_non_null_panic(parm1 as *mut raw::MediaTrack),
651                    fx_location: self.get_as_version_dependent_track_fx_ref(parm2),
652                    is_open: interpret_as_bool(parm3),
653                }),
654                raw::CSURF_EXT_SETFXENABLED => {
655                    if parm1.is_null() {
656                        // Don't know how to handle that case. Maybe a bug in REAPER.
657                        0
658                    } else {
659                        self.delegate.ext_set_fx_enabled(ExtSetFxEnabledArgs {
660                            track: require_non_null_panic(parm1 as *mut raw::MediaTrack),
661                            fx_location: self.get_as_version_dependent_track_fx_ref(parm2),
662                            is_enabled: interpret_as_bool(parm3),
663                        })
664                    }
665                }
666                raw::CSURF_EXT_SETSENDVOLUME => {
667                    self.delegate.ext_set_send_volume(ExtSetSendVolumeArgs {
668                        track: require_non_null_panic(parm1 as *mut raw::MediaTrack),
669                        send_index: deref_as::<i32>(parm2).expect("send index pointer is null")
670                            as u32,
671                        volume: deref_as(parm3).expect("volume pointer is null"),
672                    })
673                }
674                raw::CSURF_EXT_SETSENDPAN => self.delegate.ext_set_send_pan(ExtSetSendPanArgs {
675                    track: require_non_null_panic(parm1 as *mut raw::MediaTrack),
676                    send_index: deref_as::<i32>(parm2).expect("send index pointer is null") as u32,
677                    pan: deref_as(parm3).expect("pan pointer is null"),
678                }),
679                raw::CSURF_EXT_SETFXCHANGE => self.delegate.ext_set_fx_change(ExtSetFxChangeArgs {
680                    track: require_non_null_panic(parm1 as *mut raw::MediaTrack),
681                    fx_chain_type: {
682                        if self.supports_detection_of_input_fx_in_set_fx_change {
683                            let flags = parm2 as usize as u32;
684                            let fx_chain_type = if (flags & 1) == 1 {
685                                TrackFxChainType::InputFxChain
686                            } else {
687                                TrackFxChainType::NormalFxChain
688                            };
689                            Some(fx_chain_type)
690                        } else {
691                            None
692                        }
693                    },
694                }),
695                raw::CSURF_EXT_SETBPMANDPLAYRATE => {
696                    self.delegate
697                        .ext_set_bpm_and_play_rate(ExtSetBpmAndPlayRateArgs {
698                            tempo: deref_as(parm1),
699                            play_rate: deref_as(parm2),
700                        })
701                }
702                _ => 0,
703            }
704        };
705        if result != 0 {
706            // Call was processed in one of the type-safe methods. No need to call `extended`.
707            return result;
708        }
709        unsafe {
710            self.delegate.extended(ExtendedArgs {
711                call,
712                parm_1: parm1,
713                parm_2: parm2,
714                parm_3: parm3,
715            })
716        }
717    }
718}
719
720unsafe fn deref_as<T: Copy>(ptr: *mut c_void) -> Option<T> {
721    if ptr.is_null() {
722        return None;
723    }
724    let ptr = ptr as *mut T;
725    Some(*ptr)
726}
727
728unsafe fn interpret_as_bool(ptr: *mut c_void) -> bool {
729    !ptr.is_null()
730}