diff --git a/effects/rust/raindrop/src/lib.rs b/effects/rust/raindrop/src/lib.rs index 98114c8..4255837 100644 --- a/effects/rust/raindrop/src/lib.rs +++ b/effects/rust/raindrop/src/lib.rs @@ -1,6 +1,6 @@ use rand::Rng; use std::sync::Mutex; -use turbo_plugin::{make_plugin, Color, Plugin, VTable}; +use turbo_plugin::{make_native_effect_plugin, effect_plugin::NativeEffectPlugin, Color}; #[derive(Clone, Copy, Debug)] pub struct RaindropSettings { @@ -31,7 +31,7 @@ impl Raindrop { } } -impl Plugin for Raindrop { +impl NativeEffectPlugin for Raindrop { fn name(&self) -> *const std::ffi::c_char { static NAME: &[u8] = b"Raindrop\0"; static CSTR_NAME: &std::ffi::CStr = @@ -118,4 +118,4 @@ impl Plugin for Raindrop { fn unload() {} } -make_plugin!(Raindrop, Raindrop::new()); +make_native_effect_plugin!(Raindrop, Raindrop::new()); diff --git a/turbo_audio/Cargo.toml b/turbo_audio/Cargo.toml index 66d9b6b..21cefa8 100644 --- a/turbo_audio/Cargo.toml +++ b/turbo_audio/Cargo.toml @@ -11,6 +11,7 @@ bytemuck = { version = "1.14.0", features = ["derive"] } chrono = "0.4.19" clap = { version = "4.4.8", features = ["derive"] } cpal = { version = "0.15.2" } +ctrlc = "3.4.4" dasp = "0.11.0" dasp_ring_buffer = "0.11.0" dasp_signal = "0.11.0" diff --git a/turbo_audio/src/audio/audio_processing.rs b/turbo_audio/src/audio/audio_processing.rs index 85e48b0..e94b5e1 100644 --- a/turbo_audio/src/audio/audio_processing.rs +++ b/turbo_audio/src/audio/audio_processing.rs @@ -10,6 +10,12 @@ pub struct FftResult { fft_resolution: f32, } +impl Drop for FftResult { + fn drop(&mut self) { + log::debug!("Dropping FftResult"); + } +} + impl FftResult { pub fn new(raw_bins: Vec, fft_resolution: f32) -> Self { Self { diff --git a/turbo_audio/src/audio/pipewire_listener.rs b/turbo_audio/src/audio/pipewire_listener.rs index 43153ca..a7b04cd 100644 --- a/turbo_audio/src/audio/pipewire_listener.rs +++ b/turbo_audio/src/audio/pipewire_listener.rs @@ -311,7 +311,7 @@ impl PipewireState { .unwrap_or_default() .to_string(); - log::info!("Pipewire node: {name}"); + log::debug!("Pipewire node: {name}"); self.nodes.insert( node.id, diff --git a/turbo_audio/src/connections/tcp.rs b/turbo_audio/src/connections/tcp.rs index 7d5cd29..d967440 100644 --- a/turbo_audio/src/connections/tcp.rs +++ b/turbo_audio/src/connections/tcp.rs @@ -14,11 +14,13 @@ pub struct TcpConnection { should_quit: Arc>, } +#[allow(dead_code)] enum TcpConnectionError { ConnectionFailed(ConnectionAttemptError), UnableToReconnect(ConnectionAttemptError, std::io::Error), } +#[allow(dead_code)] enum ConnectionAttemptError { Unreachable(std::net::SocketAddr), ConfigurationFailed(std::io::Error), diff --git a/turbo_audio/src/controller.rs b/turbo_audio/src/controller.rs index 54ea463..e439d65 100644 --- a/turbo_audio/src/controller.rs +++ b/turbo_audio/src/controller.rs @@ -1,7 +1,7 @@ use crate::{ audio::audio_processing::AudioSignalProcessor, - effects::{lua::LuaEffectsManager, native::NativeEffectsManager}, hot_reloader::{HotReloader, WatchablePath}, + plugins::effects::{lua::LuaEffectsManager, native::NativeEffectsManager}, resources::ledstrip::LedStrip, Connection, Effect, EffectSettings, }; diff --git a/turbo_audio/src/main.rs b/turbo_audio/src/main.rs index beadfd9..31596ca 100644 --- a/turbo_audio/src/main.rs +++ b/turbo_audio/src/main.rs @@ -2,8 +2,8 @@ mod audio; mod config_parser; mod connections; mod controller; -mod effects; mod hot_reloader; +mod plugins; mod resources; use crate::hot_reloader::{HotReloader, WatchablePath}; @@ -14,8 +14,11 @@ use clap::Parser; use config_parser::{ConnectionConfigType, EffectConfigType, SettingsConfigType, TurboAudioConfig}; use connections::{tcp::TcpConnection, usb::UsbConnection, Connection}; use controller::Controller; -use effects::{lua::LuaEffectSettings, native::NativeEffectSettings, Effect, EffectSettings}; +use plugins::effects::{ + lua::LuaEffectSettings, native::NativeEffectSettings, Effect, EffectSettings, +}; use std::path::PathBuf; +use std::sync::atomic::{self, AtomicBool}; use std::{fs::File, path::Path}; #[derive(Parser, Debug)] @@ -33,6 +36,8 @@ enum RunLoopError { StartPipewireStream, } +pub static SHOULD_QUIT: AtomicBool = AtomicBool::new(false); + fn run_loop( mut audio_processor: AudioSignalProcessor, mut controller: Controller, @@ -52,6 +57,11 @@ fn run_loop( let duration_per_tick: chrono::Duration = chrono::Duration::seconds(1) / 60; let mut last_loop_start = std::time::Instant::now(); loop { + if SHOULD_QUIT.load(atomic::Ordering::Relaxed) { + log::info!("Quitting"); + break Ok(()); + } + lag = lag .checked_add(&chrono::Duration::from_std(last_loop_start.elapsed()).unwrap()) .unwrap(); @@ -156,6 +166,13 @@ fn load_controller( fn main() -> Result<(), RunLoopError> { env_logger::init(); + + ctrlc::set_handler(|| { + log::info!("Received ctrl-c, requesting to quit"); + SHOULD_QUIT.store(true, atomic::Ordering::Relaxed); + }) + .expect("Couldn't set the CTRL-C handler"); + let Args { settings_file } = Args::parse(); loop { @@ -193,6 +210,10 @@ fn main() -> Result<(), RunLoopError> { log::info!("Starting run loop."); run_loop(audio_processor, controller)?; + if SHOULD_QUIT.load(atomic::Ordering::Relaxed) { + log::info!("Quitting"); + break Ok(()); + } std::thread::sleep(std::time::Duration::from_millis(100)); } } diff --git a/turbo_audio/src/plugins/audio_api.rs b/turbo_audio/src/plugins/audio_api.rs new file mode 100644 index 0000000..e4e11f7 --- /dev/null +++ b/turbo_audio/src/plugins/audio_api.rs @@ -0,0 +1,61 @@ +use turbo_plugin::audio_api::AudioApi; + +use crate::audio::audio_processing::FftResult; +use std::{ + boxed::Box, + sync::{Arc, RwLock}, +}; + +pub fn create_audio_api(fft_result: Arc>) -> AudioApi { + extern "C" fn get_average_amplitude( + instance: *const std::ffi::c_void, + lower_frequency: std::ffi::c_float, + upper_frequency: std::ffi::c_float, + ) -> std::ffi::c_float { + let fft_result = unsafe { &*(instance as *const Arc>) }; + fft_result + .read() + .unwrap() + .get_average_amplitude(lower_frequency, upper_frequency) + .unwrap_or_else(|| { + log::error!("Invalid frequencies: {lower_frequency} & {upper_frequency}"); + 0.0f32 + }) + } + + extern "C" fn get_frequency_amplitude( + instance: *const std::ffi::c_void, + frequency: std::ffi::c_float, + ) -> std::ffi::c_float { + let fft_result = unsafe { &*(instance as *const Arc>) }; + fft_result + .read() + .unwrap() + .get_frequency_amplitude(frequency) + .unwrap_or_else(|| { + log::error!("Invalid frequency: {frequency}"); + 0.0f32 + }) + } + + extern "C" fn get_max_frequency(instance: *const std::ffi::c_void) -> std::ffi::c_float { + let fft_result = unsafe { &*(instance as *const Arc>) }; + fft_result.read().unwrap().get_max_frequency() + } + + extern "C" fn free(instance: *const std::ffi::c_void) { + unsafe { + drop(Box::from_raw(instance as *mut Arc>)); + } + } + + let fft_result = Box::new(fft_result); + + AudioApi::new( + Box::into_raw(fft_result) as *const _, + get_average_amplitude, + get_frequency_amplitude, + get_max_frequency, + free, + ) +} diff --git a/turbo_audio/src/effects/lua.rs b/turbo_audio/src/plugins/effects/lua.rs similarity index 100% rename from turbo_audio/src/effects/lua.rs rename to turbo_audio/src/plugins/effects/lua.rs diff --git a/turbo_audio/src/effects/mod.rs b/turbo_audio/src/plugins/effects/mod.rs similarity index 100% rename from turbo_audio/src/effects/mod.rs rename to turbo_audio/src/plugins/effects/mod.rs diff --git a/turbo_audio/src/effects/native.rs b/turbo_audio/src/plugins/effects/native.rs similarity index 68% rename from turbo_audio/src/effects/native.rs rename to turbo_audio/src/plugins/effects/native.rs index a986151..ec2bfd3 100644 --- a/turbo_audio/src/effects/native.rs +++ b/turbo_audio/src/plugins/effects/native.rs @@ -1,4 +1,7 @@ -use crate::audio::audio_processing::{AudioSignalProcessor, FftResult}; +use crate::{ + audio::audio_processing::{AudioSignalProcessor, FftResult}, + plugins::audio_api::create_audio_api, +}; use libloading::os::unix::{RTLD_LOCAL, RTLD_NOW}; use std::{ collections::HashMap, @@ -7,7 +10,7 @@ use std::{ sync::{Arc, RwLock}, }; use thiserror::Error; -use turbo_plugin::{Color, VTable}; +use turbo_plugin::{effect_plugin::NativeEffectPluginVTable, Color}; use super::Effect; @@ -30,7 +33,7 @@ pub struct NativeEffectsManager { #[derive(Debug)] struct Library { library: Option, - vtable: *const VTable, + vtable: *const NativeEffectPluginVTable, } unsafe impl Send for Library {} @@ -53,6 +56,7 @@ impl NativeEffectsManager { fft_result: audio_processor.fft_result.clone(), } } + pub fn create_effect(&mut self, effect_path: impl AsRef) -> Result { let path = std::fs::canonicalize(&effect_path).unwrap(); @@ -112,52 +116,10 @@ impl NativeEffectsManager { let vtable_fn = library.get:: *const std::ffi::c_void>(b"_plugin_vtable")?; - let vtable = vtable_fn() as *const turbo_plugin::VTable; - - extern "C" fn get_average_amplitude( - instance: *const std::ffi::c_void, - lower_frequency: std::ffi::c_float, - upper_frequency: std::ffi::c_float, - ) -> std::ffi::c_float { - let fft_result = unsafe { &*(instance as *const Arc>) }; - fft_result - .read() - .unwrap() - .get_average_amplitude(lower_frequency, upper_frequency) - .unwrap_or_else(|| { - log::error!("Invalid frequencies: {lower_frequency} & {upper_frequency}"); - 0.0f32 - }) - } - - extern "C" fn get_frequency_amplitude( - instance: *const std::ffi::c_void, - frequency: std::ffi::c_float, - ) -> std::ffi::c_float { - let fft_result = unsafe { &*(instance as *const Arc>) }; - fft_result - .read() - .unwrap() - .get_frequency_amplitude(frequency) - .unwrap_or_else(|| { - log::error!("Invalid frequency: {frequency}"); - 0.0f32 - }) - } - - extern "C" fn get_max_frequency( - instance: *const std::ffi::c_void, - ) -> std::ffi::c_float { - let fft_result = unsafe { &*(instance as *const Arc>) }; - fft_result.read().unwrap().get_max_frequency() - } + let vtable = + vtable_fn() as *const turbo_plugin::effect_plugin::NativeEffectPluginVTable; - let audio_api = turbo_plugin::AudioApi { - instance: fft_result as *const _ as *const _, - get_average_amplitude, - get_frequency_amplitude, - get_max_frequency, - }; + let audio_api = create_audio_api(fft_result.clone()); ((*vtable).load)(audio_api); diff --git a/turbo_audio/src/plugins/mod.rs b/turbo_audio/src/plugins/mod.rs new file mode 100644 index 0000000..4c2ed24 --- /dev/null +++ b/turbo_audio/src/plugins/mod.rs @@ -0,0 +1,2 @@ +pub mod audio_api; +pub mod effects; diff --git a/turbo_plugin/src/audio_api.rs b/turbo_plugin/src/audio_api.rs new file mode 100644 index 0000000..b432baa --- /dev/null +++ b/turbo_plugin/src/audio_api.rs @@ -0,0 +1,97 @@ +use std::{ + process::abort, + sync::{Mutex, OnceLock}, +}; + +#[derive(Copy, Clone)] +#[repr(C)] +pub struct AudioApi { + instance: *const std::ffi::c_void, + get_average_amplitude: extern "C" fn( + *const std::ffi::c_void, + std::ffi::c_float, + std::ffi::c_float, + ) -> std::ffi::c_float, + get_frequency_amplitude: + extern "C" fn(*const std::ffi::c_void, std::ffi::c_float) -> std::ffi::c_float, + get_max_frequency: extern "C" fn(*const std::ffi::c_void) -> std::ffi::c_float, + free: extern "C" fn(*const std::ffi::c_void), +} + +unsafe impl Send for AudioApi {} +unsafe impl Sync for AudioApi {} + +impl AudioApi { + pub fn new( + instance: *const std::ffi::c_void, + + get_average_amplitude: extern "C" fn( + *const std::ffi::c_void, + std::ffi::c_float, + std::ffi::c_float, + ) -> std::ffi::c_float, + get_frequency_amplitude: extern "C" fn( + *const std::ffi::c_void, + std::ffi::c_float, + ) -> std::ffi::c_float, + get_max_frequency: extern "C" fn(*const std::ffi::c_void) -> std::ffi::c_float, + free: extern "C" fn(*const std::ffi::c_void), + ) -> Self { + Self { + instance, + get_average_amplitude, + get_frequency_amplitude, + get_max_frequency, + free, + } + } +} + +static AUDIO_API_INSTANCE: OnceLock> = OnceLock::new(); + +pub fn on_load(audio_api: AudioApi) { + let mut api = AUDIO_API_INSTANCE + .get_or_init(|| Mutex::new(audio_api)) + .lock() + .unwrap(); + *api = audio_api; +} + +pub fn get_average_amplitude(lower_freq: f32, upper_freq: f32) -> f32 { + let Some(api) = AUDIO_API_INSTANCE.get() else { + eprintln!("PLUGIN ERROR: Couldn't get the audio api pointer"); + abort(); + }; + let api = api.lock().unwrap(); + (api.get_average_amplitude)(api.instance, lower_freq, upper_freq) +} + +pub fn get_frequency_amplitude(frequency: f32) -> f32 { + let Some(api) = AUDIO_API_INSTANCE.get() else { + eprintln!("PLUGIN ERROR: Couldn't get the audio api pointer"); + abort(); + }; + let api = api.lock().unwrap(); + + (api.get_frequency_amplitude)(api.instance, frequency) +} + +pub fn get_max_frequency() -> std::ffi::c_float { + let Some(api) = AUDIO_API_INSTANCE.get() else { + eprintln!("PLUGIN ERROR: Couldn't get the audio api pointer"); + abort(); + }; + let api = api.lock().unwrap(); + + (api.get_max_frequency)(api.instance) +} + +pub fn free() { + let Some(api) = AUDIO_API_INSTANCE.get() else { + eprintln!("PLUGIN ERROR: Couldn't get the audio api pointer"); + abort(); + }; + let api = api.lock().unwrap(); + + (api.free)(api.instance) +} diff --git a/turbo_plugin/src/effect_plugin.rs b/turbo_plugin/src/effect_plugin.rs new file mode 100644 index 0000000..ddc45c1 --- /dev/null +++ b/turbo_plugin/src/effect_plugin.rs @@ -0,0 +1,98 @@ +use crate::{audio_api, Color}; +use std::any::Any; + +pub trait NativeEffectPlugin: Any + Send + Sync { + /// Get a name describing the `Plugin`. + fn name(&self) -> *const std::ffi::c_char; + + /// Tick fn + fn tick(&self, leds: &mut [Color]); + + /// A callback called immediately after the plugin is loaded. Usually used + /// for initialization. + fn load(); + + /// A callback called immediately before the plugin is unloaded. Use this if + /// you need to do any cleanup. + fn unload(); +} + +#[macro_export] +macro_rules! make_native_effect_plugin { + ($plugin:ty, $ctor:expr) => { + #[no_mangle] + extern "C" fn _plugin_vtable() -> *const std::ffi::c_void { + extern "C" fn plugin_create() -> *mut std::ffi::c_void { + let plugin = Box::new($ctor); + Box::into_raw(plugin) as *mut _ + } + + extern "C" fn plugin_destroy(plugin: *mut std::ffi::c_void) { + unsafe { + drop(Box::from_raw(plugin as *mut $plugin)); + } + } + + extern "C" fn name(plugin: *const std::ffi::c_void) -> *const std::ffi::c_char { + let plugin = unsafe { &*(plugin as *const $plugin) }; + plugin.name() + } + + extern "C" fn tick( + plugin: *const std::ffi::c_void, + colors: *mut Color, + len: std::ffi::c_ulong, + ) { + let plugin = unsafe { &*(plugin as *const $plugin) }; + let slice = unsafe { std::slice::from_raw_parts_mut(colors, len as _) }; + plugin.tick(slice); + } + + extern "C" fn load(audio_api: turbo_plugin::audio_api::AudioApi) { + turbo_plugin::audio_api::on_load(audio_api); + <$plugin>::load(); + } + + extern "C" fn unload() { + turbo_plugin::audio_api::free(); + <$plugin>::unload(); + } + + static VTABLE: turbo_plugin::effect_plugin::NativeEffectPluginVTable = + turbo_plugin::effect_plugin::NativeEffectPluginVTable { + plugin_create, + plugin_destroy, + name, + tick, + load, + unload, + }; + + &VTABLE as *const turbo_plugin::effect_plugin::NativeEffectPluginVTable as *const _ + } + }; +} + +#[derive(Copy, Clone)] +#[repr(C)] +pub struct NativeEffectPluginVTable { + /// Function that returns a pointer to a heap allocated plugin + pub plugin_create: extern "C" fn() -> *mut std::ffi::c_void, + + /// Function that destroys a heap allocated plugin + pub plugin_destroy: extern "C" fn(*mut std::ffi::c_void), + + /// Function that returns the name of the plugin + pub name: extern "C" fn(*const std::ffi::c_void) -> *const std::ffi::c_char, + + /// Function that ticks the plugin + pub tick: extern "C" fn(*const std::ffi::c_void, *mut Color, std::ffi::c_ulong), + + /// Function that gets called when the shared library gets loaded + /// Useful for making initialization that is shared between plugin instances + pub load: extern "C" fn(audio_api::AudioApi), + + /// Function that gets called when the shared library gets unloaded + /// Useful for cleaning up anything that was initialized during the `on_load` function + pub unload: extern "C" fn(), +} diff --git a/turbo_plugin/src/general_plugin.rs b/turbo_plugin/src/general_plugin.rs new file mode 100644 index 0000000..40d72ad --- /dev/null +++ b/turbo_plugin/src/general_plugin.rs @@ -0,0 +1,97 @@ +use crate::audio_api; +use std::any::Any; + +pub trait NativeGeneralPlugin: Any + Send + Sync { + /// Get a name describing the `Plugin`. + fn name(&self) -> *const std::ffi::c_char; + + /// Tick fn + fn tick(&self); + + /// A callback called immediately after the plugin is loaded. Usually used + /// for initialization. + fn load(); + + /// A callback called immediately before the plugin is unloaded. Use this if + /// you need to do any cleanup. + fn unload(); +} + +#[macro_export] +macro_rules! make_general_effect_plugin { + ($plugin:ty, $ctor:expr) => { + #[no_mangle] + extern "C" fn _plugin_vtable() -> *const std::ffi::c_void { + extern "C" fn plugin_create() -> *mut std::ffi::c_void { + let plugin = Box::new($ctor); + Box::into_raw(plugin) as *mut _ + } + + extern "C" fn plugin_destroy(plugin: *mut std::ffi::c_void) { + unsafe { + drop(Box::from_raw(plugin as *mut $plugin)); + } + } + + extern "C" fn name(plugin: *const std::ffi::c_void) -> *const std::ffi::c_char { + let plugin = unsafe { &*(plugin as *const $plugin) }; + plugin.name() + } + + extern "C" fn tick( + plugin: *const std::ffi::c_void, + colors: *mut Color, + len: std::ffi::c_ulong, + ) { + let plugin = unsafe { &*(plugin as *const $plugin) }; + let slice = unsafe { std::slice::from_raw_parts_mut(colors, len as _) }; + plugin.tick(slice); + } + + extern "C" fn load(audio_api: turbo_plugin::audio_api::AudioApi) { + turbo_plugin::audio_api::on_load(audio_api); + <$plugin>::load(); + } + + extern "C" fn unload() { + <$plugin>::unload(); + } + + static VTABLE: turbo_plugin::general_plugin::NativeGeneralPluginVTable = + turbo_plugin::general_plugin::NativeGeneralPluginVTable { + plugin_create, + plugin_destroy, + name, + tick, + load, + unload, + }; + + &VTABLE as *const turbo_plugin::general_plugin::NativeGeneralPluginVTable as *const _ + } + }; +} + +#[derive(Copy, Clone)] +#[repr(C)] +pub struct NativeGeneralPluginVTable { + /// Function that returns a pointer to a heap allocated plugin + pub plugin_create: extern "C" fn() -> *mut std::ffi::c_void, + + /// Function that destroys a heap allocated plugin + pub plugin_destroy: extern "C" fn(*mut std::ffi::c_void), + + /// Function that returns the name of the plugin + pub name: extern "C" fn(*const std::ffi::c_void) -> *const std::ffi::c_char, + + /// Function that ticks the plugin + pub tick: extern "C" fn(*const std::ffi::c_void), + + /// Function that gets called when the shared library gets loaded + /// Useful for making initialization that is shared between plugin instances + pub load: extern "C" fn(audio_api::AudioApi), + + /// Function that gets called when the shared library gets unloaded + /// Useful for cleaning up anything that was initialized during the `on_load` function + pub unload: extern "C" fn(), +} diff --git a/turbo_plugin/src/lib.rs b/turbo_plugin/src/lib.rs index d006a6e..362c276 100644 --- a/turbo_plugin/src/lib.rs +++ b/turbo_plugin/src/lib.rs @@ -1,10 +1,9 @@ +pub mod audio_api; +pub mod effect_plugin; +pub mod general_plugin; + use bytemuck::{Pod, Zeroable}; use serde::{Deserialize, Serialize}; -use std::{ - any::Any, - process::abort, - sync::{Mutex, OnceLock}, -}; #[derive(Default, Clone, Copy, Debug, Pod, Zeroable, Deserialize, Serialize)] #[repr(C)] @@ -14,152 +13,3 @@ pub struct Color { pub b: u8, } -pub trait Plugin: Any + Send + Sync { - /// Get a name describing the `Plugin`. - fn name(&self) -> *const std::ffi::c_char; - - /// Tick fn - fn tick(&self, leds: &mut [Color]); - - /// A callback called immediately after the plugin is loaded. Usually used - /// for initialization. - fn load(); - - /// A callback called immediately before the plugin is unloaded. Use this if - /// you need to do any cleanup. - fn unload(); -} - -#[macro_export] -macro_rules! make_plugin { - ($plugin:ty, $ctor:expr) => { - #[no_mangle] - extern "C" fn _plugin_vtable() -> *const std::ffi::c_void { - extern "C" fn plugin_create() -> *mut std::ffi::c_void { - let plugin = Box::new($ctor); - Box::into_raw(plugin) as *mut _ - } - - extern "C" fn plugin_destroy(plugin: *mut std::ffi::c_void) { - unsafe { - drop(Box::from_raw(plugin as *mut $plugin)); - } - } - - extern "C" fn name(plugin: *const std::ffi::c_void) -> *const std::ffi::c_char { - let plugin = unsafe { &*(plugin as *const $plugin) }; - plugin.name() - } - - extern "C" fn tick( - plugin: *const std::ffi::c_void, - colors: *mut Color, - len: std::ffi::c_ulong, - ) { - let plugin = unsafe { &*(plugin as *const $plugin) }; - let slice = unsafe { std::slice::from_raw_parts_mut(colors, len as _) }; - plugin.tick(slice); - } - - extern "C" fn load(audio_api: turbo_plugin::AudioApi) { - turbo_plugin::on_load(audio_api); - <$plugin>::load(); - } - - extern "C" fn unload() { - <$plugin>::unload(); - } - - static VTABLE: VTable = VTable { - plugin_create, - plugin_destroy, - name, - tick, - load, - unload, - }; - - &VTABLE as *const VTable as *const _ - } - }; -} - -#[derive(Copy, Clone)] -#[repr(C)] -pub struct VTable { - /// Function that returns a pointer to a heap allocated plugin - pub plugin_create: extern "C" fn() -> *mut std::ffi::c_void, - - /// Function that destroys a heap allocated plugin - pub plugin_destroy: extern "C" fn(*mut std::ffi::c_void), - - /// Function that returns the name of the plugin - pub name: extern "C" fn(*const std::ffi::c_void) -> *const std::ffi::c_char, - - /// Function that ticks the plugin - pub tick: extern "C" fn(*const std::ffi::c_void, *mut Color, std::ffi::c_ulong), - - /// Function that gets called when the shared library gets loaded - /// Useful for making initialization that is shared between plugin instances - pub load: extern "C" fn(AudioApi), - - /// Function that gets called when the shared library gets unloaded - /// Useful for cleaning up anything that was initialized during the `on_load` function - pub unload: extern "C" fn(), -} - -#[derive(Copy, Clone)] -#[repr(C)] -pub struct AudioApi { - pub instance: *const std::ffi::c_void, - pub get_average_amplitude: extern "C" fn( - *const std::ffi::c_void, - std::ffi::c_float, - std::ffi::c_float, - ) -> std::ffi::c_float, - pub get_frequency_amplitude: - extern "C" fn(*const std::ffi::c_void, std::ffi::c_float) -> std::ffi::c_float, - pub get_max_frequency: extern "C" fn(*const std::ffi::c_void) -> std::ffi::c_float, -} - -unsafe impl Send for AudioApi {} -unsafe impl Sync for AudioApi {} - -static API_INSTANCE: OnceLock> = OnceLock::new(); - -pub fn on_load(audio_api: AudioApi) { - let mut api = API_INSTANCE - .get_or_init(|| Mutex::new(audio_api)) - .lock() - .unwrap(); - *api = audio_api; -} - -pub fn get_average_amplitude(lower_freq: f32, upper_freq: f32) -> f32 { - let Some(api) = API_INSTANCE.get() else { - eprintln!("PLUGIN ERROR: Couldn't get the audio api pointer"); - abort(); - }; - let api = api.lock().unwrap(); - (api.get_average_amplitude)(api.instance, lower_freq, upper_freq) -} - -pub fn get_frequency_amplitude(frequency: f32) -> f32 { - let Some(api) = API_INSTANCE.get() else { - eprintln!("PLUGIN ERROR: Couldn't get the audio api pointer"); - abort(); - }; - let api = api.lock().unwrap(); - - (api.get_frequency_amplitude)(api.instance, frequency) -} - -pub fn get_max_frequency() -> std::ffi::c_float { - let Some(api) = API_INSTANCE.get() else { - eprintln!("PLUGIN ERROR: Couldn't get the audio api pointer"); - abort(); - }; - let api = api.lock().unwrap(); - - (api.get_max_frequency)(api.instance) -}