use std::{
marker::PhantomData,
os::raw,
mem::{MaybeUninit, transmute},
ffi::{CString, CStr},
ops::{Bound, RangeBounds},
};
use bitflags::bitflags;
use crate::{ffi, Result, result_from_ptr};
#[repr(transparent)]
pub struct Settings {
handle: *mut ffi::fluid_settings_t,
}
unsafe impl Send for Settings {}
#[repr(transparent)]
pub struct SettingsRef<'a> {
handle: *mut ffi::fluid_settings_t,
phantom: PhantomData<&'a ()>,
}
impl Drop for Settings {
fn drop(&mut self) {
unsafe { ffi::delete_fluid_settings(self.handle) }
}
}
impl Settings {
pub fn new() -> Result<Self> {
result_from_ptr(unsafe { ffi::new_fluid_settings() })
.map(|handle| Self { handle })
}
pub(crate) fn into_ptr(self) -> *mut ffi::fluid_settings_t {
unsafe { transmute(self) }
}
pub(crate) fn from_ptr(handle: *mut ffi::fluid_settings_t) -> Self {
Self { handle }
}
}
impl<'a> SettingsRef<'a> {
pub(crate) fn from_ptr(handle: *mut ffi::fluid_settings_t) -> Self {
Self { handle, phantom: PhantomData }
}
}
pub trait IsSettings {
fn pick<S, T>(&self, name: S) -> Option<Setting<'_, T>>
where
S: Into<Vec<u8>>,
T: IsSetting + ?Sized;
fn str_<S>(&self, name: S) -> Option<Setting<'_, str>>
where
S: Into<Vec<u8>>;
fn num<S>(&self, name: S) -> Option<Setting<'_, f64>>
where
S: Into<Vec<u8>>;
fn int<S>(&self, name: S) -> Option<Setting<'_, i32>>
where
S: Into<Vec<u8>>;
}
mod private {
use std::{
ffi::CString,
marker::PhantomData,
};
use crate::{ffi, IsSettings, IsSetting, Setting, Settings, SettingsRef, private::HasHandle};
impl<X> IsSettings for X where X: HasHandle<Handle = ffi::fluid_settings_t> {
fn pick<S, T>(&self, name: S) -> Option<Setting<'_, T>>
where
S: Into<Vec<u8>>,
T: IsSetting + ?Sized,
{
let handle = self.get_handle();
let name = CString::new(name).ok()?;
if T::TYPE == unsafe { ffi::fluid_settings_get_type(handle, name.as_ptr() as *const _) } {
Some(Setting { handle, name, phantom: PhantomData })
} else {
None
}
}
fn str_<S>(&self, name: S) -> Option<Setting<'_, str>>
where
S: Into<Vec<u8>>,
{
self.pick(name)
}
fn num<S>(&self, name: S) -> Option<Setting<'_, f64>>
where
S: Into<Vec<u8>>,
{
self.pick(name)
}
fn int<S>(&self, name: S) -> Option<Setting<'_, i32>>
where
S: Into<Vec<u8>>,
{
self.pick(name)
}
}
impl HasHandle for Settings {
type Handle = ffi::fluid_settings_t;
fn get_handle(&self) -> *mut Self::Handle {
self.handle
}
}
impl<'a> HasHandle for SettingsRef<'a> {
type Handle = ffi::fluid_settings_t;
fn get_handle(&self) -> *mut Self::Handle {
self.handle
}
}
}
pub trait IsSetting {
const TYPE: ffi::fluid_types_enum;
}
impl IsSetting for str {
const TYPE: ffi::fluid_types_enum = ffi::fluid_types_enum_FLUID_STR_TYPE;
}
impl IsSetting for f64 {
const TYPE: ffi::fluid_types_enum = ffi::fluid_types_enum_FLUID_NUM_TYPE;
}
impl IsSetting for i32 {
const TYPE: ffi::fluid_types_enum = ffi::fluid_types_enum_FLUID_INT_TYPE;
}
impl IsSetting for () {
const TYPE: ffi::fluid_types_enum = ffi::fluid_types_enum_FLUID_SET_TYPE;
}
bitflags! {
pub struct Hints: i32 {
const BOUNDED_BELOW = ffi::FLUID_HINT_BOUNDED_BELOW as i32;
const BOUNDED_ABOVE = ffi::FLUID_HINT_BOUNDED_ABOVE as i32;
const TOGGLED = ffi::FLUID_HINT_TOGGLED as i32;
const SAMPLE_RATE = ffi::FLUID_HINT_SAMPLE_RATE as i32;
const LOGARITHMIC = ffi::FLUID_HINT_LOGARITHMIC as i32;
const INTEGER = ffi::FLUID_HINT_INTEGER as i32;
const FILENAME = ffi::FLUID_HINT_FILENAME as i32;
const OPTIONLIST = ffi::FLUID_HINT_OPTIONLIST as i32;
}
}
pub struct Setting<'a, T: ?Sized> {
handle: *mut ffi::fluid_settings_t,
name: CString,
phantom: PhantomData<(&'a (), T)>
}
impl<'a, T> Setting<'a, T>
where
T: ?Sized,
{
#[inline]
fn name_ptr(&self) -> *const raw::c_char {
self.name.as_ptr() as *const _
}
pub fn hints(&self) -> Hints {
Hints::from_bits_truncate(unsafe { ffi::fluid_settings_get_hints(self.handle, self.name_ptr()) })
}
pub fn is_realtime(&self) -> bool {
0 < unsafe { ffi::fluid_settings_is_realtime(self.handle, self.name_ptr()) }
}
}
impl<'a> Setting<'a, str> {
pub fn set<S: Into<String>>(&self, value: S) -> bool {
let mut value = value.into();
value.push('\0');
0 < unsafe { ffi::fluid_settings_setstr(self.handle, self.name_ptr(), value.as_ptr() as *const _) }
}
pub fn get(&self) -> Option<&str> {
let mut value = MaybeUninit::uninit();
if 0 < unsafe { ffi::fluid_settings_getstr(self.handle, self.name_ptr(), value.as_mut_ptr()) } {
let value = unsafe { value.assume_init() };
let value = unsafe { CStr::from_ptr(value) };
value.to_str().ok()
} else {
None
}
}
pub fn default(&self) -> &str {
let value = unsafe { ffi::fluid_settings_getstr_default(self.handle, self.name_ptr()) };
let value = unsafe { CStr::from_ptr(value) };
value.to_str().unwrap()
}
}
impl<'a, S> PartialEq<S> for Setting<'a, str>
where
S: AsRef<str>,
{
fn eq(&self, other: &S) -> bool {
let mut other = String::from(other.as_ref());
other.push('\0');
0 < unsafe { ffi::fluid_settings_str_equal(self.handle, self.name_ptr(), other.as_ptr() as *mut _) }
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Range<T> {
pub min: Option<T>,
pub max: Option<T>,
}
impl<T> Range<T> {
pub fn new(min: Option<T>, max: Option<T>) -> Self {
Self { min, max }
}
fn new_unsafe(min: MaybeUninit<T>, max: MaybeUninit<T>, hints: Hints) -> Self {
Self::new(
if hints.contains(Hints::BOUNDED_BELOW) {
Some(unsafe { min.assume_init() })
} else {
None
},
if hints.contains(Hints::BOUNDED_ABOVE) {
Some(unsafe { max.assume_init() })
} else {
None
},
)
}
}
impl<T> RangeBounds<T> for Range<T> {
fn start_bound(&self) -> Bound<&T> {
if let Some(value) = &self.min {
Bound::Included(value)
} else {
Bound::Unbounded
}
}
fn end_bound(&self) -> Bound<&T> {
if let Some(value) = &self.max {
Bound::Included(value)
} else {
Bound::Unbounded
}
}
}
impl<'a> Setting<'a, f64> {
pub fn set(&self, value: f64) -> bool {
0 < unsafe { ffi::fluid_settings_setnum(self.handle, self.name_ptr(), value) }
}
pub fn get(&self) -> Option<f64> {
let mut value = MaybeUninit::uninit();
if 0 < unsafe { ffi::fluid_settings_getnum(self.handle, self.name_ptr(), value.as_mut_ptr()) } {
let value = unsafe { value.assume_init() };
Some(value)
} else {
None
}
}
pub fn default(&self) -> f64 {
unsafe { ffi::fluid_settings_getnum_default(self.handle, self.name_ptr()) }
}
pub fn range(&self) -> Range<f64> {
let mut min = MaybeUninit::uninit();
let mut max = MaybeUninit::uninit();
unsafe { ffi::fluid_settings_getnum_range(self.handle, self.name_ptr(), min.as_mut_ptr(), max.as_mut_ptr()); }
let hints = self.hints();
Range::new_unsafe(min, max, hints)
}
}
impl<'a> Setting<'a, i32> {
pub fn set(&self, value: i32) -> bool {
0 < unsafe { ffi::fluid_settings_setint(self.handle, self.name_ptr(), value) }
}
pub fn get(&self) -> Option<i32> {
let mut value = MaybeUninit::uninit();
if 0 < unsafe { ffi::fluid_settings_getint(self.handle, self.name_ptr(), value.as_mut_ptr()) } {
let value = unsafe { value.assume_init() };
Some(value)
} else {
None
}
}
pub fn default(&self) -> i32 {
unsafe { ffi::fluid_settings_getint_default(self.handle, self.name_ptr()) }
}
pub fn range(&self) -> Range<i32> {
let mut min = MaybeUninit::uninit();
let mut max = MaybeUninit::uninit();
unsafe { ffi::fluid_settings_getint_range(self.handle, self.name_ptr(), min.as_mut_ptr(), max.as_mut_ptr()); }
let hints = self.hints();
Range::new_unsafe(min, max, hints)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn settings() {
let settings = Settings::new().unwrap();
drop(settings);
}
#[test]
fn num_setting() {
let settings = Settings::new().unwrap();
let gain = settings.num("synth.gain").unwrap();
assert_eq!(gain.default(), 0.2f32 as f64);
assert_eq!(gain.get(), Some(0.2f32 as f64));
assert!(gain.set(0.5));
assert_eq!(gain.get(), Some(0.5));
}
#[test]
fn int_setting() {
let settings = Settings::new().unwrap();
let polyphony = settings.int("synth.polyphony").unwrap();
assert_eq!(polyphony.default(), 256);
assert_eq!(polyphony.get(), Some(256));
assert!(polyphony.set(512));
assert_eq!(polyphony.get(), Some(512));
}
#[test]
fn str_setting() {
let settings = Settings::new().unwrap();
let active = settings.str_("synth.drums-channel.active").unwrap();
assert_eq!(active.default(), "yes");
assert_eq!(active.get(), Some("yes"));
assert!(active.set("no"));
assert_eq!(active.get(), Some("no"));
}
}