voicemeeter/
interface.rs

1//! The interface for Voicemeeter remote.
2//!
3//! See the methods on [`VoicemeeterRemote`] for how to use the interface
4use crate::{types::VoicemeeterApplication, LoadError};
5
6use self::{
7    communication_login_logout::LoginError, general_information::GetVoicemeeterInformationError,
8};
9
10pub mod callback;
11pub mod communication_login_logout;
12pub mod device;
13pub mod general_information;
14pub mod get_levels;
15pub mod macro_buttons;
16pub mod parameters;
17
18/// Interface for voicemeeter.
19#[derive(Clone)]
20#[cfg(feature = "interface")] // for doc_cfg
21pub struct VoicemeeterRemote {
22    raw: &'static crate::bindings::VoicemeeterRemoteRaw,
23    logout_handle: Option<std::sync::Arc<bool>>,
24    /// The type of the running Voicemeeter instance.
25    pub program: VoicemeeterApplication,
26}
27
28pub(crate) static LOGOUT_HANDLE: std::sync::OnceLock<
29    std::sync::Mutex<Option<std::sync::Arc<bool>>>,
30> = std::sync::OnceLock::new();
31
32impl std::fmt::Debug for VoicemeeterRemote {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        match self.program {
35            VoicemeeterApplication::None => write!(f, "[no voicemeeter program running]"),
36            p => write!(f, "[{p}]"),
37        }
38    }
39}
40
41impl VoicemeeterRemote {
42    /// Creates a new [`VoicemeeterRemote`] instance that is logged in with the client.
43    ///
44    /// # Notes
45    ///
46    /// If you create [`VoicemeeterRemote`] instances, when the last instance remaining is dropped,
47    /// you will automatically be logged out of the API, meaning you cannot login again.
48    ///
49    /// This is done to prevent leaving the API logged in when your program is closed,
50    /// causing a visual bug in the voicemeeter application.
51    #[tracing::instrument]
52    pub fn new() -> Result<Self, InitializationError> {
53        let raw = crate::get_voicemeeter_raw()?;
54        let mut s = VoicemeeterRemote::from_raw(raw);
55        if s.logout_handle.is_none() {
56            return Err(InitializationError::AlreadyLoggedOut);
57        }
58        match s.login() {
59            Ok(_) => {}
60            Err(LoginError::AlreadyLoggedIn(_)) => {}
61            e => {
62                e?;
63            }
64        };
65        s.update_program()?;
66        Ok(s)
67    }
68
69    fn from_raw(raw: &'static crate::VoicemeeterRemoteRaw) -> VoicemeeterRemote {
70        Self {
71            raw,
72            program: VoicemeeterApplication::Other,
73            logout_handle: LOGOUT_HANDLE
74                .get_or_init(|| std::sync::Mutex::new(Some(std::sync::Arc::new(false))))
75                .lock()
76                .unwrap()
77                .clone(),
78        }
79    }
80
81    /// Update the current program type.
82    pub fn update_program(&mut self) -> Result<(), GetVoicemeeterInformationError> {
83        match self.get_voicemeeter_type() {
84            Ok(t) => self.program = t,
85            Err(GetVoicemeeterInformationError::NoServer) => {
86                self.program = VoicemeeterApplication::None
87            }
88            Err(e) => return Err(e),
89        }
90        Ok(())
91    }
92}
93
94impl Drop for VoicemeeterRemote {
95    fn drop(&mut self) {
96        // This logout only happens if this is the only voicemeeter handle that exists.
97        let _ = self._logout();
98    }
99}
100
101/// Errors that can occur when initializing the Voicemeeter remote DLL.
102#[derive(Debug, thiserror::Error)]
103#[non_exhaustive]
104pub enum InitializationError {
105    /// Error while loading the DLL.
106    #[error("could not load the client")]
107    LoadError(#[from] LoadError),
108    /// Error when logging in.
109    #[error("could not login")]
110    LoginError(#[from] LoginError),
111    /// Application has already logged out.
112    #[error("application has already logged out, so cannot login again")]
113    AlreadyLoggedOut,
114    /// Error when getting the Voicemeeter application type.
115    #[error("could not get voicemeeter type")]
116    InformationError(#[from] GetVoicemeeterInformationError),
117}