use core::ffi::c_int;
use core::num::NonZeroU32;
use bela_sys::BelaInitSettings;
use crate::error::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
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 Default for Settings {
fn default() -> Self {
Self::new()
}
}
impl Settings {
#[must_use]
pub const fn new() -> Self {
Self {
period_size: None,
use_analog: None,
use_digital: None,
num_analog_in_channels: None,
num_analog_out_channels: None,
num_digital_channels: None,
detect_underruns: None,
verbose: None,
high_performance_mode: None,
uniform_sample_rate: None,
stop_button_pin: None,
thread_count: None,
cpu_monitoring: None,
begin_muted: None,
}
}
#[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)
}
const MULTIPLEXER_ANALOG_CHANNELS: c_int = 8;
#[cfg_attr(
not(bela_device),
allow(
dead_code,
reason = "only the device-gated audio system initialises; still unit-tested on the host"
)
)]
pub(crate) const fn check_resolved(raw: &BelaInitSettings) -> Result<(), Error> {
if raw.audioSampleRate == 0.0 {
return Err(Error::SampleRate);
}
if raw.pruNumber < 0 || raw.pruNumber > 1 {
return Err(Error::PruNumber(raw.pruNumber));
}
if raw.numMuxChannels == 0 {
return Ok(());
}
if !matches!(raw.numMuxChannels, 2 | 4 | 8) {
return Err(Error::MultiplexerChannels(raw.numMuxChannels));
}
if raw.pruNumber != 1 {
return Err(Error::MultiplexerPru(raw.pruNumber));
}
if raw.useAnalog == 0 {
return Err(Error::MultiplexerWithoutAnalog);
}
if raw.numAnalogInChannels != MULTIPLEXER_ANALOG_CHANNELS {
return Err(Error::MultiplexerAnalogChannels(raw.numAnalogInChannels));
}
Ok(())
}
#[cfg_attr(
not(bela_device),
allow(
dead_code,
reason = "only the device-gated audio system applies settings; still unit-tested on the host"
)
)]
pub(crate) 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.audioSampleRate = 44100.0;
raw.periodSize = 16;
raw.useAnalog = 1;
raw.numAnalogInChannels = 8;
raw.numAnalogOutChannels = 8;
raw.numMuxChannels = 0;
raw.pruNumber = 1;
raw.uniformSampleRate = 1;
raw.stopButtonPin = 115;
raw.verbose = 0;
raw
}
fn with_multiplexer(channels: c_int) -> BelaInitSettings {
let mut raw = fake_defaults();
raw.numMuxChannels = channels;
raw
}
#[test]
fn a_whole_configuration_can_be_a_const() {
const SETTINGS: Settings = Settings::new().period_size(64).thread_count(4);
let mut raw = fake_defaults();
SETTINGS.apply_to(&mut raw);
assert_eq!(raw.periodSize, 64);
assert_eq!(raw.threadCount, 4);
}
#[test]
fn default_is_the_same_empty_set_of_overrides_as_new() {
assert_eq!(Settings::default(), Settings::new());
}
#[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);
}
#[test]
fn belas_own_defaults_are_accepted() {
assert_eq!(check_resolved(&fake_defaults()), Ok(()));
}
#[test]
fn a_sample_rate_of_zero_is_refused() {
let mut raw = fake_defaults();
raw.audioSampleRate = 0.0;
assert_eq!(check_resolved(&raw), Err(Error::SampleRate));
}
#[test]
fn both_prus_are_accepted() {
for number in [0, 1] {
let mut raw = fake_defaults();
raw.pruNumber = number;
assert_eq!(check_resolved(&raw), Ok(()), "PRU {number}");
}
}
#[test]
fn a_pru_the_board_does_not_have_is_refused() {
for number in [-1, 2, 5] {
let mut raw = fake_defaults();
raw.pruNumber = number;
assert_eq!(
check_resolved(&raw),
Err(Error::PruNumber(number)),
"PRU {number}"
);
}
}
#[test]
fn the_multiplexer_channel_counts_libbela_takes_are_accepted() {
for channels in [0, 2, 4, 8] {
assert_eq!(
check_resolved(&with_multiplexer(channels)),
Ok(()),
"{channels} multiplexer channels"
);
}
}
#[test]
fn any_other_multiplexer_channel_count_is_refused() {
for channels in [-1, 1, 3, 16] {
assert_eq!(
check_resolved(&with_multiplexer(channels)),
Err(Error::MultiplexerChannels(channels)),
"{channels} multiplexer channels"
);
}
}
#[test]
fn the_multiplexer_is_refused_on_the_wrong_pru() {
let mut raw = with_multiplexer(8);
raw.pruNumber = 0;
assert_eq!(check_resolved(&raw), Err(Error::MultiplexerPru(0)));
}
#[test]
fn the_multiplexer_is_refused_without_the_analog_inputs() {
let mut raw = with_multiplexer(8);
raw.useAnalog = 0;
assert_eq!(check_resolved(&raw), Err(Error::MultiplexerWithoutAnalog));
}
#[test]
fn the_multiplexer_is_refused_with_any_other_number_of_analog_inputs() {
for channels in [2, 4, 16] {
let mut raw = with_multiplexer(8);
raw.numAnalogInChannels = channels;
assert_eq!(
check_resolved(&raw),
Err(Error::MultiplexerAnalogChannels(channels)),
"{channels} analog input channels"
);
}
}
#[test]
fn the_multiplexer_rules_only_apply_when_it_is_on() {
let mut raw = fake_defaults();
raw.pruNumber = 0;
raw.useAnalog = 0;
raw.numAnalogInChannels = 2;
assert_eq!(check_resolved(&raw), Ok(()));
}
#[test]
fn a_settings_and_a_command_line_half_make_the_same_refusal() {
let mut from_settings = fake_defaults();
Settings::new()
.num_analog_in_channels(4)
.apply_to(&mut from_settings);
from_settings.numMuxChannels = 8;
assert_eq!(
check_resolved(&from_settings),
Err(Error::MultiplexerAnalogChannels(4))
);
}
}