use core::slice;
use crate::raw::{
self, Data, HINT_BOUNDED_ABOVE, HINT_BOUNDED_BELOW, HINT_DEFAULT_0, HINT_DEFAULT_1,
HINT_DEFAULT_100, HINT_DEFAULT_440, HINT_INTEGER, HINT_LOGARITHMIC, HINT_SAMPLE_RATE,
HINT_TOGGLED,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PortKind {
AudioInput,
AudioOutput,
ControlInput,
ControlOutput,
}
impl PortKind {
pub(crate) const fn ladspa_bits(self) -> raw::PortDescriptor {
match self {
Self::AudioInput => raw::PORT_AUDIO | raw::PORT_INPUT,
Self::AudioOutput => raw::PORT_AUDIO | raw::PORT_OUTPUT,
Self::ControlInput => raw::PORT_CONTROL | raw::PORT_INPUT,
Self::ControlOutput => raw::PORT_CONTROL | raw::PORT_OUTPUT,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct PortHints {
pub(crate) descriptor: raw::PortRangeHintDescriptor,
pub(crate) lower: Data,
pub(crate) upper: Data,
}
impl PortHints {
pub const EMPTY: Self = Self {
descriptor: 0,
lower: 0.0,
upper: 0.0,
};
}
impl Default for PortHints {
fn default() -> Self {
Self::EMPTY
}
}
#[derive(Debug, Clone, Copy)]
pub struct PortDescriptor {
pub(crate) name: &'static str,
pub(crate) kind: PortKind,
pub(crate) hints: PortHints,
}
impl PortDescriptor {
pub const fn audio_input(name: &'static str) -> Self {
Self {
name,
kind: PortKind::AudioInput,
hints: PortHints::EMPTY,
}
}
pub const fn audio_output(name: &'static str) -> Self {
Self {
name,
kind: PortKind::AudioOutput,
hints: PortHints::EMPTY,
}
}
pub const fn control_input(name: &'static str) -> Self {
Self {
name,
kind: PortKind::ControlInput,
hints: PortHints::EMPTY,
}
}
pub const fn control_output(name: &'static str) -> Self {
Self {
name,
kind: PortKind::ControlOutput,
hints: PortHints::EMPTY,
}
}
pub const fn with_bounds(mut self, lower: f32, upper: f32) -> Self {
self.hints.descriptor |= HINT_BOUNDED_BELOW | HINT_BOUNDED_ABOVE;
self.hints.lower = lower;
self.hints.upper = upper;
self
}
pub const fn with_default(mut self, default: PortDefault) -> Self {
self.hints.descriptor |= default.bits();
self
}
pub const fn integer(mut self) -> Self {
self.hints.descriptor |= HINT_INTEGER;
self
}
pub const fn toggled(mut self) -> Self {
self.hints.descriptor |= HINT_TOGGLED;
self
}
pub const fn logarithmic(mut self) -> Self {
self.hints.descriptor |= HINT_LOGARITHMIC;
self
}
pub const fn sample_rate(mut self) -> Self {
self.hints.descriptor |= HINT_SAMPLE_RATE;
self
}
pub const fn name(&self) -> &'static str {
self.name
}
pub const fn kind(&self) -> PortKind {
self.kind
}
pub const fn hints(&self) -> &PortHints {
&self.hints
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PortDefault {
Minimum,
Low,
Middle,
High,
Maximum,
Zero,
One,
Hundred,
Hz440,
}
impl PortDefault {
pub(crate) const fn bits(self) -> raw::PortRangeHintDescriptor {
match self {
Self::Minimum => raw::HINT_DEFAULT_MINIMUM,
Self::Low => raw::HINT_DEFAULT_LOW,
Self::Middle => raw::HINT_DEFAULT_MIDDLE,
Self::High => raw::HINT_DEFAULT_HIGH,
Self::Maximum => raw::HINT_DEFAULT_MAXIMUM,
Self::Zero => HINT_DEFAULT_0,
Self::One => HINT_DEFAULT_1,
Self::Hundred => HINT_DEFAULT_100,
Self::Hz440 => HINT_DEFAULT_440,
}
}
}
pub struct Ports<'host> {
pub(crate) ptrs: &'host [*mut Data],
pub(crate) descriptors: &'static [PortDescriptor],
pub(crate) frames: usize,
}
impl<'host> Ports<'host> {
pub fn frames(&self) -> usize {
self.frames
}
pub fn audio_input(&self, port: usize) -> &[Data] {
self.assert_kind(port, PortKind::AudioInput);
let ptr = self.ptrs[port];
unsafe { slice::from_raw_parts(ptr as *const Data, self.frames) }
}
pub fn audio_output(&mut self, port: usize) -> &mut [Data] {
self.assert_kind(port, PortKind::AudioOutput);
let ptr = self.ptrs[port];
unsafe { slice::from_raw_parts_mut(ptr, self.frames) }
}
pub fn control_input(&self, port: usize) -> Data {
self.assert_kind(port, PortKind::ControlInput);
let ptr = self.ptrs[port];
unsafe { *(ptr as *const Data) }
}
pub fn control_output(&mut self, port: usize) -> &mut Data {
self.assert_kind(port, PortKind::ControlOutput);
let ptr = self.ptrs[port];
unsafe { &mut *ptr }
}
pub fn audio_in_out(&mut self, in_port: usize, out_port: usize) -> (&[Data], &mut [Data]) {
assert_ne!(
in_port, out_port,
"audio_in_out: input and output port indices must differ"
);
self.assert_kind(in_port, PortKind::AudioInput);
self.assert_kind(out_port, PortKind::AudioOutput);
let in_ptr = self.ptrs[in_port];
let out_ptr = self.ptrs[out_port];
unsafe {
let input = slice::from_raw_parts(in_ptr as *const Data, self.frames);
let output = slice::from_raw_parts_mut(out_ptr, self.frames);
(input, output)
}
}
fn assert_kind(&self, port: usize, expected: PortKind) {
let actual = self.descriptors[port].kind;
assert_eq!(
actual, expected,
"port {port} is declared as {actual:?}, but accessed as {expected:?}",
);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn const_constructors_compose_into_static_table() {
static PORTS: &[PortDescriptor] = &[
PortDescriptor::audio_input("In"),
PortDescriptor::audio_output("Out"),
PortDescriptor::control_input("Gain")
.with_default(PortDefault::One)
.with_bounds(0.0, 4.0),
];
assert_eq!(PORTS.len(), 3);
assert_eq!(PORTS[0].kind(), PortKind::AudioInput);
assert_eq!(PORTS[0].name(), "In");
assert_eq!(PORTS[1].kind(), PortKind::AudioOutput);
assert_eq!(PORTS[2].kind(), PortKind::ControlInput);
let gain = &PORTS[2];
assert_eq!(gain.hints().descriptor & HINT_DEFAULT_1, HINT_DEFAULT_1,);
assert_eq!(gain.hints().lower, 0.0);
assert_eq!(gain.hints().upper, 4.0);
}
#[test]
fn ladspa_bits_match_spec() {
assert_eq!(
PortKind::AudioInput.ladspa_bits(),
raw::PORT_AUDIO | raw::PORT_INPUT
);
assert_eq!(
PortKind::ControlOutput.ladspa_bits(),
raw::PORT_CONTROL | raw::PORT_OUTPUT
);
}
#[test]
fn literal_default_encodes_correctly() {
let p = PortDescriptor::control_input("Freq").with_default(PortDefault::Hz440);
assert_eq!(
p.hints().descriptor & raw::HINT_DEFAULT_MASK,
raw::HINT_DEFAULT_440,
);
}
#[test]
fn symbolic_default_encodes_correctly() {
let p = PortDescriptor::control_input("Q")
.with_bounds(0.0, 1.0)
.with_default(PortDefault::Middle);
assert_eq!(
p.hints().descriptor & raw::HINT_DEFAULT_MASK,
raw::HINT_DEFAULT_MIDDLE,
);
}
#[test]
fn modifier_bits_compose() {
let p = PortDescriptor::control_input("Freq")
.with_bounds(20.0, 20_000.0)
.logarithmic()
.sample_rate();
let d = p.hints().descriptor;
assert!(d & HINT_BOUNDED_BELOW != 0);
assert!(d & HINT_BOUNDED_ABOVE != 0);
assert!(d & HINT_LOGARITHMIC != 0);
assert!(d & HINT_SAMPLE_RATE != 0);
assert!(d & HINT_TOGGLED == 0);
}
#[test]
fn ports_runtime_access_pattern() {
static DESCS: &[PortDescriptor] = &[
PortDescriptor::audio_input("In"),
PortDescriptor::audio_output("Out"),
PortDescriptor::control_input("Gain"),
];
let mut input_buf: [Data; 4] = [0.5, 1.0, 1.5, 2.0];
let mut output_buf: [Data; 4] = [0.0; 4];
let mut gain: Data = 2.0;
let ptrs: [*mut Data; 3] = [
input_buf.as_mut_ptr(),
output_buf.as_mut_ptr(),
&mut gain as *mut Data,
];
let mut ports = Ports {
ptrs: &ptrs,
descriptors: DESCS,
frames: input_buf.len(),
};
let g = ports.control_input(2);
assert_eq!(g, 2.0);
let (i, o) = ports.audio_in_out(0, 1);
for (idx, (&sample, slot)) in i.iter().zip(o.iter_mut()).enumerate() {
*slot = sample * g;
let _ = idx;
}
assert_eq!(output_buf, [1.0, 2.0, 3.0, 4.0]);
}
#[test]
#[should_panic(expected = "port 0 is declared as AudioInput")]
fn wrong_kind_access_panics() {
static DESCS: &[PortDescriptor] = &[PortDescriptor::audio_input("In")];
let mut buf: [Data; 1] = [0.0];
let ptrs: [*mut Data; 1] = [buf.as_mut_ptr()];
let ports = Ports {
ptrs: &ptrs,
descriptors: DESCS,
frames: 1,
};
let _ = ports.control_input(0);
}
}