use core::ffi::{c_int, c_uint};
use core::fmt;
use bela_sys::{
BELA_BUGFIX_VERSION, BELA_MAJOR_VERSION, BELA_MINOR_VERSION, BelaHw, BelaHw_Batch, BelaHw_Bela,
BelaHw_BelaEs9080, BelaHw_BelaMini, BelaHw_BelaMiniMultiAudio, BelaHw_BelaMiniMultiI2s,
BelaHw_BelaMiniMultiTdm, BelaHw_BelaMultiTdm, BelaHw_BelaRevC, BelaHw_CtagBeast,
BelaHw_CtagBeastBela, BelaHw_CtagFace, BelaHw_CtagFaceBela, BelaHw_GemMulti, BelaHw_GemStereo,
BelaHw_NoHw, BelaHw_Salt, BelaHwDetectMode, BelaHwDetectMode_Cache, BelaHwDetectMode_CacheOnly,
BelaHwDetectMode_Scan, BelaHwDetectMode_User, BelaHwDetectMode_UserOnly,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Board {
NoHardware,
Bela,
BelaMini,
GemStereo,
GemMulti,
Salt,
CtagFace,
CtagBeast,
CtagFaceBela,
CtagBeastBela,
BelaMiniMultiAudio,
BelaMiniMultiTdm,
BelaMultiTdm,
BelaMiniMultiI2s,
BelaEs9080,
BelaRevC,
Batch,
#[non_exhaustive]
Unrecognised {
raw: c_int,
},
}
impl Board {
#[cfg(bela_device)]
#[must_use]
pub fn detect(mode: DetectMode) -> Self {
Self::from_sys(unsafe { bela_sys::Bela_detectHw(mode.to_sys()) })
}
#[must_use]
pub const fn from_sys(raw: BelaHw) -> Self {
if raw == BelaHw_NoHw {
Self::NoHardware
} else if raw == BelaHw_Bela {
Self::Bela
} else if raw == BelaHw_BelaMini {
Self::BelaMini
} else if raw == BelaHw_GemStereo {
Self::GemStereo
} else if raw == BelaHw_GemMulti {
Self::GemMulti
} else if raw == BelaHw_Salt {
Self::Salt
} else if raw == BelaHw_CtagFace {
Self::CtagFace
} else if raw == BelaHw_CtagBeast {
Self::CtagBeast
} else if raw == BelaHw_CtagFaceBela {
Self::CtagFaceBela
} else if raw == BelaHw_CtagBeastBela {
Self::CtagBeastBela
} else if raw == BelaHw_BelaMiniMultiAudio {
Self::BelaMiniMultiAudio
} else if raw == BelaHw_BelaMiniMultiTdm {
Self::BelaMiniMultiTdm
} else if raw == BelaHw_BelaMultiTdm {
Self::BelaMultiTdm
} else if raw == BelaHw_BelaMiniMultiI2s {
Self::BelaMiniMultiI2s
} else if raw == BelaHw_BelaEs9080 {
Self::BelaEs9080
} else if raw == BelaHw_BelaRevC {
Self::BelaRevC
} else if raw == BelaHw_Batch {
Self::Batch
} else {
Self::Unrecognised { raw }
}
}
#[must_use]
pub const fn to_sys(self) -> BelaHw {
match self {
Self::NoHardware => BelaHw_NoHw,
Self::Bela => BelaHw_Bela,
Self::BelaMini => BelaHw_BelaMini,
Self::GemStereo => BelaHw_GemStereo,
Self::GemMulti => BelaHw_GemMulti,
Self::Salt => BelaHw_Salt,
Self::CtagFace => BelaHw_CtagFace,
Self::CtagBeast => BelaHw_CtagBeast,
Self::CtagFaceBela => BelaHw_CtagFaceBela,
Self::CtagBeastBela => BelaHw_CtagBeastBela,
Self::BelaMiniMultiAudio => BelaHw_BelaMiniMultiAudio,
Self::BelaMiniMultiTdm => BelaHw_BelaMiniMultiTdm,
Self::BelaMultiTdm => BelaHw_BelaMultiTdm,
Self::BelaMiniMultiI2s => BelaHw_BelaMiniMultiI2s,
Self::BelaEs9080 => BelaHw_BelaEs9080,
Self::BelaRevC => BelaHw_BelaRevC,
Self::Batch => BelaHw_Batch,
Self::Unrecognised { raw } => raw,
}
}
#[must_use]
pub const fn is_recognised(self) -> bool {
!matches!(self, Self::Unrecognised { .. })
}
}
impl From<Board> for BelaHw {
fn from(board: Board) -> Self {
board.to_sys()
}
}
impl fmt::Display for Board {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::NoHardware => f.write_str("NoHw"),
Self::Bela => f.write_str("Bela"),
Self::BelaMini => f.write_str("BelaMini"),
Self::GemStereo => f.write_str("GemStereo"),
Self::GemMulti => f.write_str("GemMulti"),
Self::Salt => f.write_str("Salt"),
Self::CtagFace => f.write_str("CtagFace"),
Self::CtagBeast => f.write_str("CtagBeast"),
Self::CtagFaceBela => f.write_str("CtagFaceBela"),
Self::CtagBeastBela => f.write_str("CtagBeastBela"),
Self::BelaMiniMultiAudio => f.write_str("BelaMiniMultiAudio"),
Self::BelaMiniMultiTdm => f.write_str("BelaMiniMultiTdm"),
Self::BelaMultiTdm => f.write_str("BelaMultiTdm"),
Self::BelaMiniMultiI2s => f.write_str("BelaMiniMultiI2s"),
Self::BelaEs9080 => f.write_str("BelaEs9080"),
Self::BelaRevC => f.write_str("BelaRevC"),
Self::Batch => f.write_str("Batch"),
Self::Unrecognised { raw } => write!(f, "unrecognised({raw})"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum DetectMode {
Scan,
Cache,
CacheOnly,
User,
UserOnly,
}
impl DetectMode {
#[must_use]
pub const fn to_sys(self) -> BelaHwDetectMode {
match self {
Self::Scan => BelaHwDetectMode_Scan,
Self::Cache => BelaHwDetectMode_Cache,
Self::CacheOnly => BelaHwDetectMode_CacheOnly,
Self::User => BelaHwDetectMode_User,
Self::UserOnly => BelaHwDetectMode_UserOnly,
}
}
pub const ALL: &'static [Self] = &[
Self::Scan,
Self::Cache,
Self::CacheOnly,
Self::User,
Self::UserOnly,
];
}
impl From<DetectMode> for BelaHwDetectMode {
fn from(mode: DetectMode) -> Self {
mode.to_sys()
}
}
impl fmt::Display for DetectMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match *self {
Self::Scan => "scan",
Self::Cache => "cache",
Self::CacheOnly => "cache-only",
Self::User => "user",
Self::UserOnly => "user-only",
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Version {
pub major: i32,
pub minor: i32,
pub bugfix: i32,
}
impl Version {
#[allow(
clippy::cast_possible_wrap,
reason = "bindgen types these macros as u32; they are small version numbers"
)]
pub const HEADERS: Self = Self {
major: BELA_MAJOR_VERSION as i32,
minor: BELA_MINOR_VERSION as i32,
bugfix: BELA_BUGFIX_VERSION as i32,
};
#[must_use]
pub const fn new(major: i32, minor: i32, bugfix: i32) -> Self {
Self {
major,
minor,
bugfix,
}
}
#[cfg(bela_device)]
#[must_use]
pub fn running() -> Self {
let mut major: c_int = 0;
let mut minor: c_int = 0;
let mut bugfix: c_int = 0;
unsafe { bela_sys::Bela_getVersion(&raw mut major, &raw mut minor, &raw mut bugfix) };
Self::new(major, minor, bugfix)
}
}
impl fmt::Display for Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.bugfix)
}
}
const _: () = {
assert!(
size_of::<BelaHw>() == size_of::<c_int>(),
"BelaHw is no longer the C int Board::Unrecognised carries"
);
assert!(
size_of::<BelaHwDetectMode>() == size_of::<c_uint>(),
"BelaHwDetectMode is no longer the C unsigned int DetectMode::to_sys produces"
);
};
#[cfg(test)]
mod tests {
use super::*;
const NAMED: [(Board, BelaHw); 17] = [
(Board::NoHardware, BelaHw_NoHw),
(Board::Bela, BelaHw_Bela),
(Board::BelaMini, BelaHw_BelaMini),
(Board::GemStereo, BelaHw_GemStereo),
(Board::GemMulti, BelaHw_GemMulti),
(Board::Salt, BelaHw_Salt),
(Board::CtagFace, BelaHw_CtagFace),
(Board::CtagBeast, BelaHw_CtagBeast),
(Board::CtagFaceBela, BelaHw_CtagFaceBela),
(Board::CtagBeastBela, BelaHw_CtagBeastBela),
(Board::BelaMiniMultiAudio, BelaHw_BelaMiniMultiAudio),
(Board::BelaMiniMultiTdm, BelaHw_BelaMiniMultiTdm),
(Board::BelaMultiTdm, BelaHw_BelaMultiTdm),
(Board::BelaMiniMultiI2s, BelaHw_BelaMiniMultiI2s),
(Board::BelaEs9080, BelaHw_BelaEs9080),
(Board::BelaRevC, BelaHw_BelaRevC),
(Board::Batch, BelaHw_Batch),
];
#[test]
fn every_named_board_reads_back_as_the_constant_it_is() {
for (board, raw) in NAMED {
assert_eq!(Board::from_sys(raw), board, "BelaHw {raw}");
assert_eq!(board.to_sys(), raw, "{board}");
}
}
#[test]
fn a_board_the_headers_do_not_name_keeps_its_number() {
for raw in [BelaHw_Batch + 1, 99, c_int::MAX, c_int::MIN] {
let board = Board::from_sys(raw);
assert_eq!(board, Board::Unrecognised { raw });
assert_eq!(
board.to_sys(),
raw,
"the number must survive the round trip"
);
assert!(!board.is_recognised());
}
}
#[test]
fn the_conversion_out_is_also_a_from_impl() {
for (board, raw) in NAMED {
assert_eq!(BelaHw::from(board), raw, "{board}");
}
let unrecognised = Board::from_sys(99);
assert_eq!(BelaHw::from(unrecognised), 99);
for mode in DetectMode::ALL {
assert_eq!(BelaHwDetectMode::from(*mode), mode.to_sys(), "{mode}");
}
}
#[test]
fn no_hardware_is_a_detection_result_rather_than_an_unknown_board() {
assert_eq!(Board::from_sys(-1), Board::NoHardware);
assert!(Board::NoHardware.is_recognised());
}
#[test]
fn a_board_prints_the_name_libbela_gives_it() {
assert_eq!(Board::GemStereo.to_string(), "GemStereo");
assert_eq!(Board::NoHardware.to_string(), "NoHw");
assert_eq!(
Board::from_sys(17).to_string(),
"unrecognised(17)",
"a board with no name still says which number it is"
);
}
#[test]
fn every_detect_mode_is_the_constant_it_names() {
let pairs = [
(DetectMode::Scan, BelaHwDetectMode_Scan),
(DetectMode::Cache, BelaHwDetectMode_Cache),
(DetectMode::CacheOnly, BelaHwDetectMode_CacheOnly),
(DetectMode::User, BelaHwDetectMode_User),
(DetectMode::UserOnly, BelaHwDetectMode_UserOnly),
];
for (mode, raw) in pairs {
assert_eq!(mode.to_sys(), raw, "{mode}");
}
let ordered: Vec<BelaHwDetectMode> = DetectMode::ALL.iter().map(|m| m.to_sys()).collect();
assert_eq!(
ordered,
vec![0, 1, 2, 3, 4],
"ALL is not in declaration order"
);
assert_eq!(DetectMode::ALL.len(), pairs.len());
for (mode, _) in pairs {
assert!(
DetectMode::ALL.contains(&mode),
"{mode} is missing from ALL"
);
}
}
#[test]
fn versions_compare_by_major_then_minor_then_bugfix() {
assert!(Version::new(1, 18, 0) > Version::new(1, 17, 9));
assert!(Version::new(2, 0, 0) > Version::new(1, 99, 99));
assert!(Version::new(1, 18, 1) > Version::new(1, 18, 0));
assert_eq!(Version::new(1, 18, 0), Version::new(1, 18, 0));
}
#[test]
fn a_version_prints_as_a_version() {
assert_eq!(Version::new(1, 18, 0).to_string(), "1.18.0");
}
#[test]
fn the_header_version_is_the_one_the_bindings_were_vendored_from() {
assert_eq!(
Version::HEADERS,
Version::new(1, 18, 0),
"the vendored headers moved; update what quotes the version"
);
}
}