fmod/core/channel_control/
callback.rs

1// Copyright (c) 2024 Melody Madeline Lyons
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at https://mozilla.org/MPL/2.0/.
6
7use fmod_sys::*;
8use std::{
9    ffi::{c_float, c_int},
10    ops::Deref,
11    os::raw::c_void,
12};
13
14use crate::{Channel, ChannelControl, ChannelGroup, panic_wrapper};
15use crate::{FmodResultExt, Result};
16
17/// Enum used to distinguish between [`Channel`] and [`ChannelGroup`] in the [`ChannelControl`] callback.
18#[derive(Debug, Clone, Copy)]
19pub enum ChannelControlType {
20    /// [`ChannelControl`] is a [`Channel`].
21    Channel(Channel),
22    /// [`ChannelControl`] is a [`ChannelGroup`].
23    ChannelGroup(ChannelGroup),
24}
25
26/// Trait for this particular FMOD callback.
27///
28/// No `self` parameter is passed to the callback!
29#[allow(unused_variables)]
30pub trait ChannelControlCallback {
31    /// Called when a sound ends. Supported by [`Channel`] only.
32    fn end(channel_control: ChannelControlType) -> Result<()> {
33        Ok(())
34    }
35
36    /// Called when a [`Channel`] is made virtual or real. Supported by [`Channel`] objects only.
37    fn virtual_voice(channel_control: ChannelControlType, is_virtual: bool) -> Result<()> {
38        Ok(())
39    }
40
41    /// Called when a syncpoint is encountered.
42    /// Can be from wav file markers or user added.
43    /// Supported by [`Channel`] only.
44    fn sync_point(channel_control: ChannelControlType, sync_point: c_int) -> Result<()> {
45        Ok(())
46    }
47
48    /// Called when geometry occlusion values are calculated.
49    /// Can be used to clamp or change the value.
50    /// Supported by [`Channel`] and [`ChannelGroup`].
51    // FIXME: is this &mut safe?
52    fn occlusion(
53        channel_control: ChannelControlType,
54        direct: &mut c_float,
55        reverb: &mut c_float,
56    ) -> Result<()> {
57        Ok(())
58    }
59}
60
61impl Deref for ChannelControlType {
62    type Target = ChannelControl;
63
64    fn deref(&self) -> &Self::Target {
65        match self {
66            ChannelControlType::Channel(channel) => channel,
67            ChannelControlType::ChannelGroup(channel_group) => channel_group,
68        }
69    }
70}
71
72unsafe extern "C" fn callback_impl<C: ChannelControlCallback>(
73    channel_control: *mut FMOD_CHANNELCONTROL,
74    control_type: FMOD_CHANNELCONTROL_TYPE,
75    callback_type: FMOD_CHANNELCONTROL_CALLBACK_TYPE,
76    commanddata1: *mut c_void,
77    commanddata2: *mut c_void,
78) -> FMOD_RESULT {
79    panic_wrapper(|| {
80        let channel_control = match control_type {
81            FMOD_CHANNELCONTROL_CHANNEL => {
82                let channel = unsafe { Channel::from_ffi(channel_control.cast()) };
83                ChannelControlType::Channel(channel)
84            }
85            FMOD_CHANNELCONTROL_CHANNELGROUP => {
86                let channel_group = unsafe { ChannelGroup::from_ffi(channel_control.cast()) };
87                ChannelControlType::ChannelGroup(channel_group)
88            }
89            _ => return FMOD_RESULT::FMOD_ERR_INVALID_PARAM, // this should never happen
90        };
91
92        let result = match callback_type {
93            FMOD_CHANNELCONTROL_CALLBACK_END => C::end(channel_control),
94            FMOD_CHANNELCONTROL_CALLBACK_VIRTUALVOICE => {
95                let is_virtual = unsafe { *commanddata1.cast::<i32>() } != 0;
96                C::virtual_voice(channel_control, is_virtual)
97            }
98            FMOD_CHANNELCONTROL_CALLBACK_SYNCPOINT => {
99                let sync_point = unsafe { *commanddata1.cast::<c_int>() };
100                C::sync_point(channel_control, sync_point)
101            }
102            FMOD_CHANNELCONTROL_CALLBACK_OCCLUSION => {
103                let direct = unsafe { &mut *commanddata1.cast::<c_float>() };
104                let reverb = unsafe { &mut *commanddata2.cast::<c_float>() };
105                C::occlusion(channel_control, &mut *direct, &mut *reverb)
106            }
107            _ => {
108                eprintln!("warning: unknown callback type {callback_type}");
109                return FMOD_RESULT::FMOD_OK;
110            }
111        };
112        FMOD_RESULT::from_result(result)
113    })
114}
115
116impl ChannelControl {
117    /// Sets the callback for [`ChannelControl`] level notifications.
118    pub fn set_callback<C: ChannelControlCallback>(&self) -> Result<()> {
119        unsafe {
120            FMOD_ChannelControl_SetCallback(self.inner.as_ptr(), Some(callback_impl::<C>))
121                .to_result()
122        }
123    }
124}