use core::ffi::c_int;
use core::num::NonZeroU32;
use bela_sys::BelaInitSettings;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Settings {
period_size: Option<u32>,
use_analog: Option<bool>,
use_digital: Option<bool>,
num_analog_in_channels: Option<u32>,
num_analog_out_channels: Option<u32>,
num_digital_channels: Option<u32>,
detect_underruns: Option<bool>,
verbose: Option<bool>,
high_performance_mode: Option<bool>,
uniform_sample_rate: Option<bool>,
stop_button_pin: Option<i32>,
thread_count: Option<u32>,
cpu_monitoring: Option<NonZeroU32>,
begin_muted: Option<bool>,
}
impl Settings {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub const fn period_size(mut self, frames: u32) -> Self {
self.period_size = Some(frames);
self
}
#[must_use]
pub const fn use_analog(mut self, enabled: bool) -> Self {
self.use_analog = Some(enabled);
self
}
#[must_use]
pub const fn use_digital(mut self, enabled: bool) -> Self {
self.use_digital = Some(enabled);
self
}
#[must_use]
pub const fn num_analog_in_channels(mut self, channels: u32) -> Self {
self.num_analog_in_channels = Some(channels);
self
}
#[must_use]
pub const fn num_analog_out_channels(mut self, channels: u32) -> Self {
self.num_analog_out_channels = Some(channels);
self
}
#[must_use]
pub const fn num_digital_channels(mut self, channels: u32) -> Self {
self.num_digital_channels = Some(channels);
self
}
#[must_use]
pub const fn detect_underruns(mut self, enabled: bool) -> Self {
self.detect_underruns = Some(enabled);
self
}
#[must_use]
pub const fn verbose(mut self, enabled: bool) -> Self {
self.verbose = Some(enabled);
self
}
#[must_use]
pub const fn high_performance_mode(mut self, enabled: bool) -> Self {
self.high_performance_mode = Some(enabled);
self
}
#[must_use]
pub const fn uniform_sample_rate(mut self, enabled: bool) -> Self {
self.uniform_sample_rate = Some(enabled);
self
}
#[must_use]
pub const fn stop_button_pin(mut self, pin: i32) -> Self {
self.stop_button_pin = Some(pin);
self
}
#[must_use]
pub const fn thread_count(mut self, threads: u32) -> Self {
self.thread_count = Some(threads);
self
}
#[must_use]
pub const fn cpu_monitoring(mut self, measurements_per_cycle: NonZeroU32) -> Self {
self.cpu_monitoring = Some(measurements_per_cycle);
self
}
#[must_use]
pub const fn begin_muted(mut self, muted: bool) -> Self {
self.begin_muted = Some(muted);
self
}
#[cfg_attr(
not(bela_device),
allow(
dead_code,
reason = "only the device-gated audio system applies it; still unit-tested on the host"
)
)]
pub(crate) const fn cpu_monitoring_cycle(&self) -> Option<NonZeroU32> {
self.cpu_monitoring
}
pub fn apply_to(&self, raw: &mut BelaInitSettings) {
if let Some(v) = self.period_size {
raw.periodSize = to_c_int(v);
}
if let Some(v) = self.use_analog {
raw.useAnalog = c_int::from(v);
}
if let Some(v) = self.use_digital {
raw.useDigital = c_int::from(v);
}
if let Some(v) = self.num_analog_in_channels {
raw.numAnalogInChannels = to_c_int(v);
}
if let Some(v) = self.num_analog_out_channels {
raw.numAnalogOutChannels = to_c_int(v);
}
if let Some(v) = self.num_digital_channels {
raw.numDigitalChannels = to_c_int(v);
}
if let Some(v) = self.detect_underruns {
raw.detectUnderruns = c_int::from(v);
}
if let Some(v) = self.verbose {
raw.verbose = c_int::from(v);
}
if let Some(v) = self.high_performance_mode {
raw.highPerformanceMode = c_int::from(v);
}
if let Some(v) = self.uniform_sample_rate {
raw.uniformSampleRate = c_int::from(v);
}
if let Some(v) = self.stop_button_pin {
raw.stopButtonPin = v;
}
if let Some(v) = self.thread_count {
raw.threadCount = v;
}
if let Some(v) = self.begin_muted {
raw.beginMuted = c_int::from(v);
}
}
}
fn to_c_int(value: u32) -> c_int {
c_int::try_from(value).unwrap_or(c_int::MAX)
}
#[cfg_attr(
not(bela_device),
allow(
dead_code,
reason = "only the device-gated audio system applies settings; still unit-tested on the host"
)
)]
pub fn render_threads(raw: &BelaInitSettings) -> usize {
(raw.threadCount as usize).max(1)
}
#[cfg(test)]
mod tests {
use core::mem;
use super::*;
fn fake_defaults() -> BelaInitSettings {
let mut raw: BelaInitSettings = unsafe { mem::zeroed() };
raw.periodSize = 16;
raw.useAnalog = 1;
raw.uniformSampleRate = 1;
raw.stopButtonPin = 115;
raw.verbose = 0;
raw
}
#[test]
fn empty_settings_leave_defaults_untouched() {
let mut raw = fake_defaults();
Settings::new().apply_to(&mut raw);
assert_eq!(raw.periodSize, 16);
assert_eq!(raw.useAnalog, 1);
assert_eq!(raw.uniformSampleRate, 1);
assert_eq!(raw.stopButtonPin, 115);
}
#[test]
fn set_fields_override_defaults() {
let mut raw = fake_defaults();
Settings::new()
.period_size(64)
.use_analog(false)
.verbose(true)
.stop_button_pin(-1)
.thread_count(4)
.apply_to(&mut raw);
assert_eq!(raw.periodSize, 64);
assert_eq!(raw.useAnalog, 0);
assert_eq!(raw.verbose, 1);
assert_eq!(raw.stopButtonPin, -1);
assert_eq!(raw.threadCount, 4);
assert_eq!(raw.uniformSampleRate, 1);
}
#[test]
fn bools_map_to_c_ints() {
let mut raw = fake_defaults();
Settings::new()
.use_digital(true)
.detect_underruns(false)
.high_performance_mode(true)
.uniform_sample_rate(false)
.begin_muted(true)
.apply_to(&mut raw);
assert_eq!(raw.useDigital, 1);
assert_eq!(raw.detectUnderruns, 0);
assert_eq!(raw.highPerformanceMode, 1);
assert_eq!(raw.uniformSampleRate, 0);
assert_eq!(raw.beginMuted, 1);
}
#[test]
fn coming_up_unmuted_is_still_said_out_loud() {
let mut raw = fake_defaults();
raw.beginMuted = 1;
Settings::new().begin_muted(false).apply_to(&mut raw);
assert_eq!(raw.beginMuted, 0);
}
#[test]
fn cpu_monitoring_is_recorded_but_not_an_init_setting() {
let cycle = NonZeroU32::new(2000).expect("2000 is not zero");
let settings = Settings::new().cpu_monitoring(cycle);
assert_eq!(settings.cpu_monitoring_cycle(), Some(cycle));
assert_eq!(
Settings::new().cpu_monitoring_cycle(),
None,
"monitoring should be off unless it was asked for"
);
let mut raw = fake_defaults();
let untouched = fake_defaults();
settings.apply_to(&mut raw);
assert_eq!(raw.periodSize, untouched.periodSize);
assert_eq!(raw.useAnalog, untouched.useAnalog);
assert_eq!(raw.uniformSampleRate, untouched.uniformSampleRate);
assert_eq!(raw.stopButtonPin, untouched.stopButtonPin);
}
#[test]
fn both_spellings_of_one_render_thread_count_as_one() {
for spelling in [0, 1] {
let mut raw: BelaInitSettings = unsafe { mem::zeroed() };
raw.threadCount = spelling;
assert_eq!(render_threads(&raw), 1, "threadCount {spelling}");
}
}
#[test]
fn extra_render_threads_are_counted_as_asked_for() {
let mut raw: BelaInitSettings = unsafe { mem::zeroed() };
raw.threadCount = 4;
assert_eq!(render_threads(&raw), 4);
}
}