use std::ffi::c_void;
use std::sync::Mutex;
use std::sync::OnceLock;
use crate::error::Result;
pub const DECIBEL_MINIMUM: f64 = -200.0;
const DECIBEL_LO_G100: f64 = 4.60517018599;
pub fn decibel_from_linear(linear: f64) -> Result<f64> {
let v = 20.0 * linear.log10();
if v.is_infinite() {
Ok(DECIBEL_MINIMUM)
} else {
Ok(v)
}
}
pub fn decibel_to_linear(db: f64) -> Result<f64> {
let v = 10.0_f64.powf(db / 20.0);
if v < 0.000001 {
Ok(0.0)
} else {
Ok(v)
}
}
pub fn decibel_from_logarithmic(logarithmic: f64) -> Result<f64> {
if logarithmic < 0.001 {
Ok(DECIBEL_MINIMUM)
} else if logarithmic > 0.99 {
Ok(0.0)
} else {
Ok(20.0 * (-(1.0 - logarithmic).ln() / DECIBEL_LO_G100).log10())
}
}
pub fn decibel_to_logarithmic(db: f64) -> Result<f64> {
if db.abs() <= 1e-12 {
Ok(1.0)
} else {
Ok(1.0 - (-(10.0_f64.powf(db / 20.0)) * DECIBEL_LO_G100).exp())
}
}
pub fn decibel_linear_to_logarithmic(linear: f64) -> Result<f64> {
Ok(1.0 - (-linear * DECIBEL_LO_G100).exp())
}
pub fn decibel_logarithmic_to_linear(logarithmic: f64) -> Result<f64> {
if logarithmic > 0.99 {
Ok(1.0)
} else {
Ok(-(1.0 - logarithmic).ln() / DECIBEL_LO_G100)
}
}
pub fn lerp(a: f64, b: f64, t: f64) -> Result<f64> {
Ok(a * (1.0 - t) + b * t)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LoopMode {
Off = 0,
Loop = 1,
Clamp = 2,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DropWorkflowBehavior {
Ask = 0,
Auto = 1,
Manual = 2,
Disable = 3,
}
impl DropWorkflowBehavior {
pub fn is_valid(value: i32) -> bool {
matches!(value, 0..=3)
}
pub fn name(value: i32) -> &'static str {
match value {
0 => "ASK",
1 => "AUTO",
2 => "MANUAL",
3 => "DISABLE",
_ => "UNKNOWN",
}
}
}
pub fn power_ceil_to_power_of_2(value: u32) -> Result<u32> {
let mut v = value.wrapping_sub(1);
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
Ok(v.wrapping_add(1))
}
pub fn power_floor_to_power_of_2(value: u32) -> Result<u32> {
let mut x = value;
x = x | (x >> 1);
x = x | (x >> 2);
x = x | (x >> 4);
x = x | (x >> 8);
x = x | (x >> 16);
Ok(x - (x >> 1))
}
pub type DestroyFn = Option<unsafe extern "C" fn(*mut c_void)>;
struct Slot {
ptr: *mut c_void,
destroy: DestroyFn,
}
impl Slot {
fn empty() -> Self {
Self {
ptr: std::ptr::null_mut(),
destroy: None,
}
}
}
unsafe impl Send for Slot {}
unsafe impl Sync for Slot {}
pub struct Current {
video_params: Mutex<Slot>,
audio_params: Mutex<Slot>,
plugin_host: Mutex<Slot>,
plugin_cache: Mutex<Slot>,
is_interactive: bool,
}
impl Current {
pub fn instance() -> &'static Current {
static INSTANCE: OnceLock<Current> = OnceLock::new();
INSTANCE.get_or_init(|| Current {
video_params: Mutex::new(Slot::empty()),
audio_params: Mutex::new(Slot::empty()),
plugin_host: Mutex::new(Slot::empty()),
plugin_cache: Mutex::new(Slot::empty()),
is_interactive: true,
})
}
fn set_slot(slot: &Mutex<Slot>, obj: *mut c_void, destroy: DestroyFn) -> Result<()> {
let mut s = slot.lock().unwrap_or_else(|e| e.into_inner());
let old = std::mem::replace(&mut *s, Slot { ptr: obj, destroy });
if !old.ptr.is_null() {
if let Some(d) = old.destroy {
unsafe { d(old.ptr) };
}
}
Ok(())
}
fn get_slot(slot: &Mutex<Slot>) -> Result<*mut c_void> {
let s = slot.lock().unwrap_or_else(|e| e.into_inner());
Ok(s.ptr)
}
pub fn set_video_params(&self, obj: *mut c_void, destroy: DestroyFn) -> Result<()> {
Self::set_slot(&self.video_params, obj, destroy)
}
pub fn set_audio_params(&self, obj: *mut c_void, destroy: DestroyFn) -> Result<()> {
Self::set_slot(&self.audio_params, obj, destroy)
}
pub fn set_plugin_host(&self, obj: *mut c_void, destroy: DestroyFn) -> Result<()> {
Self::set_slot(&self.plugin_host, obj, destroy)
}
pub fn set_plugin_cache(&self, obj: *mut c_void, destroy: DestroyFn) -> Result<()> {
Self::set_slot(&self.plugin_cache, obj, destroy)
}
pub fn get_video_params(&self) -> Result<*mut c_void> {
Self::get_slot(&self.video_params)
}
pub fn get_audio_params(&self) -> Result<*mut c_void> {
Self::get_slot(&self.audio_params)
}
pub fn get_plugin_host(&self) -> Result<*mut c_void> {
Self::get_slot(&self.plugin_host)
}
pub fn get_plugin_cache(&self) -> Result<*mut c_void> {
Self::get_slot(&self.plugin_cache)
}
pub fn is_interactive(&self) -> Result<bool> {
Ok(self.is_interactive)
}
}
pub fn hide_console_window(command: &mut std::process::Command) {
#[cfg(windows)]
{
use std::os::windows::process::CommandExt;
const CREATE_NO_WINDOW: u32 = 0x0800_0000;
command.creation_flags(CREATE_NO_WINDOW);
}
#[cfg(not(windows))]
{
let _ = command;
}
}
#[cfg(test)]
mod tests {
use std::ffi::c_void;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use super::*;
#[test]
fn decibel_from_linear_known_values() {
assert_eq!(decibel_from_linear(1.0).unwrap(), 0.0);
assert!((decibel_from_linear(10.0).unwrap() - 20.0).abs() < 1e-12);
assert!((decibel_from_linear(100.0).unwrap() - 40.0).abs() < 1e-12);
assert_eq!(decibel_from_linear(0.0).unwrap(), DECIBEL_MINIMUM);
assert!(decibel_from_linear(-1.0).unwrap().is_nan());
}
#[test]
fn decibel_to_linear_known_values() {
assert_eq!(decibel_to_linear(0.0).unwrap(), 1.0);
assert!((decibel_to_linear(20.0).unwrap() - 10.0).abs() < 1e-12);
assert_eq!(decibel_to_linear(-200.0).unwrap(), 0.0);
assert_eq!(decibel_to_linear(-120.0).unwrap(), 0.000001);
}
#[test]
fn decibel_logarithmic_branch_thresholds() {
assert_eq!(decibel_from_logarithmic(0.0).unwrap(), DECIBEL_MINIMUM);
assert_eq!(decibel_from_logarithmic(1.0).unwrap(), 0.0);
let db = decibel_from_logarithmic(0.5).unwrap();
assert!(db.is_finite());
assert_eq!(decibel_to_logarithmic(0.0).unwrap(), 1.0);
}
#[test]
fn decibel_round_trips() {
for linear in [0.001, 0.01, 0.1, 0.5, 0.9, 0.99] {
let log = decibel_linear_to_logarithmic(linear).unwrap();
let back = decibel_logarithmic_to_linear(log).unwrap();
assert!(
(back - linear).abs() < 1e-6,
"linear {} -> {} -> {}",
linear,
log,
back
);
}
for db in [-60.0, -30.0, -12.0, -6.0, -3.0, -1.0] {
let log = decibel_to_logarithmic(db).unwrap();
let back = decibel_from_logarithmic(log).unwrap();
assert!((back - db).abs() < 1e-3, "db {} -> {} -> {}", db, log, back);
}
let log6 = decibel_to_logarithmic(6.0).unwrap();
assert!(log6 > 0.99);
assert_eq!(decibel_from_logarithmic(log6).unwrap(), 0.0);
assert_eq!(decibel_logarithmic_to_linear(0.999).unwrap(), 1.0);
}
#[test]
fn lerp_matches_cpp() {
assert_eq!(lerp(0.0, 10.0, 0.0).unwrap(), 0.0);
assert_eq!(lerp(0.0, 10.0, 1.0).unwrap(), 10.0);
assert_eq!(lerp(0.0, 10.0, 0.5).unwrap(), 5.0);
assert_eq!(lerp(2.0, 4.0, 0.25).unwrap(), 2.5);
}
#[test]
fn loop_mode_discriminants_match_cpp() {
assert_eq!(LoopMode::Off as i32, 0);
assert_eq!(LoopMode::Loop as i32, 1);
assert_eq!(LoopMode::Clamp as i32, 2);
let a = LoopMode::Loop;
let b = a;
assert_eq!(a, b);
assert_ne!(LoopMode::Off, LoopMode::Clamp);
}
#[test]
fn drop_workflow_behavior_discriminants_match_cpp() {
assert_eq!(DropWorkflowBehavior::Ask as i32, 0);
assert_eq!(DropWorkflowBehavior::Auto as i32, 1);
assert_eq!(DropWorkflowBehavior::Manual as i32, 2);
assert_eq!(DropWorkflowBehavior::Disable as i32, 3);
}
#[test]
fn drop_workflow_behavior() {
for (v, expected_valid) in [(0, true), (1, true), (2, true), (3, true)] {
assert_eq!(DropWorkflowBehavior::is_valid(v), expected_valid);
}
assert!(!DropWorkflowBehavior::is_valid(-1));
assert!(!DropWorkflowBehavior::is_valid(4));
assert_eq!(DropWorkflowBehavior::name(0), "ASK");
assert_eq!(DropWorkflowBehavior::name(1), "AUTO");
assert_eq!(DropWorkflowBehavior::name(2), "MANUAL");
assert_eq!(DropWorkflowBehavior::name(3), "DISABLE");
assert_eq!(DropWorkflowBehavior::name(99), "UNKNOWN");
assert_eq!(DropWorkflowBehavior::name(-5), "UNKNOWN");
}
#[test]
fn power_of_two() {
assert_eq!(power_ceil_to_power_of_2(1).unwrap(), 1);
assert_eq!(power_ceil_to_power_of_2(2).unwrap(), 2);
assert_eq!(power_ceil_to_power_of_2(3).unwrap(), 4);
assert_eq!(power_ceil_to_power_of_2(5).unwrap(), 8);
assert_eq!(power_ceil_to_power_of_2(8).unwrap(), 8);
assert_eq!(power_ceil_to_power_of_2(0).unwrap(), 0);
assert_eq!(power_floor_to_power_of_2(1).unwrap(), 1);
assert_eq!(power_floor_to_power_of_2(2).unwrap(), 2);
assert_eq!(power_floor_to_power_of_2(5).unwrap(), 4);
assert_eq!(power_floor_to_power_of_2(9).unwrap(), 8);
assert_eq!(power_floor_to_power_of_2(8).unwrap(), 8);
assert_eq!(power_floor_to_power_of_2(0).unwrap(), 0);
assert_eq!(power_ceil_to_power_of_2(0x8000_0001).unwrap(), 0u32);
assert_eq!(power_floor_to_power_of_2(0x8000_0000).unwrap(), 0x8000_0000);
}
static DESTROY_COUNT: AtomicUsize = AtomicUsize::new(0);
static CURRENT_LOCK: Mutex<()> = Mutex::new(());
unsafe extern "C" fn count_destroy(_p: *mut c_void) {
DESTROY_COUNT.fetch_add(1, Ordering::SeqCst);
}
#[test]
fn current_slot_semantics() {
let _guard = CURRENT_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let cur = Current::instance();
assert!(cur.is_interactive().unwrap());
DESTROY_COUNT.store(0, Ordering::SeqCst);
assert!(cur.get_video_params().unwrap().is_null());
let p1 = std::ptr::dangling_mut::<c_void>();
assert!(cur.set_video_params(p1, Some(count_destroy)).is_ok());
assert_eq!(cur.get_video_params().unwrap(), p1);
let p2 = 0x2 as *mut c_void;
assert!(cur.set_video_params(p2, Some(count_destroy)).is_ok());
assert_eq!(cur.get_video_params().unwrap(), p2);
assert_eq!(DESTROY_COUNT.load(Ordering::SeqCst), 1);
assert!(cur.set_video_params(std::ptr::null_mut(), None).is_ok());
assert!(cur.get_video_params().unwrap().is_null());
assert_eq!(DESTROY_COUNT.load(Ordering::SeqCst), 2);
assert!(cur.set_video_params(std::ptr::null_mut(), None).is_ok());
assert_eq!(DESTROY_COUNT.load(Ordering::SeqCst), 2);
}
#[test]
fn current_slots_independent() {
let _guard = CURRENT_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let cur = Current::instance();
let pa = 0x10 as *mut c_void;
let ph = 0x20 as *mut c_void;
let pc = 0x30 as *mut c_void;
assert!(cur.set_audio_params(pa, None).is_ok());
assert!(cur.set_plugin_host(ph, None).is_ok());
assert!(cur.set_plugin_cache(pc, None).is_ok());
assert_eq!(cur.get_audio_params().unwrap(), pa);
assert_eq!(cur.get_plugin_host().unwrap(), ph);
assert_eq!(cur.get_plugin_cache().unwrap(), pc);
}
}