#![cfg_attr(
not(bela_device),
allow(
dead_code,
reason = "only the fallback main is reachable off-device; the application code should still compile and lint"
)
)]
use core::num::NonZeroU32;
use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::process::ExitCode;
use std::sync::Arc;
use bela::{
BelaApplication, BlockContext, RenderContext, ResolvedSettings, SetupContext, ThreadInfo,
};
const MEASUREMENTS_PER_CYCLE: u32 = 2000;
const REQUIRED_THREADS: NonZeroU32 =
NonZeroU32::new(4).expect("the required thread count is a non-zero constant");
const WRONG_THREAD_COUNT: &str = "this application renders on four threads";
const RENDER_MILLIS: u64 = 500;
const fn cycle() -> NonZeroU32 {
NonZeroU32::new(MEASUREMENTS_PER_CYCLE).expect("the cycle length is a non-zero constant")
}
struct Observe {
monitored: Arc<AtomicBool>,
}
impl BelaApplication for Observe {
type RenderState = ();
fn setup(&mut self, context: &SetupContext) -> bool {
self.monitored
.store(context.cpu_usage().is_some(), Ordering::Relaxed);
false
}
fn create_render_state(&mut self, _thread: ThreadInfo, _context: &SetupContext) {}
fn render(&self, _state: &mut (), _context: &mut RenderContext) {}
}
struct Abort;
impl BelaApplication for Abort {
type RenderState = ();
fn setup(&mut self, _context: &SetupContext) -> bool {
false
}
fn create_render_state(&mut self, _thread: ThreadInfo, _context: &SetupContext) {}
fn render(&self, _state: &mut (), _context: &mut RenderContext) {}
}
struct NeedsFourThreads {
blocks: Arc<AtomicU32>,
}
impl BelaApplication for NeedsFourThreads {
type RenderState = ();
fn validate_settings(&self, settings: &ResolvedSettings<'_>) -> Result<(), &'static str> {
if settings.thread_count() == REQUIRED_THREADS.get() as usize {
Ok(())
} else {
Err(WRONG_THREAD_COUNT)
}
}
fn create_render_state(&mut self, _thread: ThreadInfo, _context: &SetupContext) {}
fn render_pre(&mut self, _states: &mut [()], _context: &mut BlockContext) {
self.blocks.fetch_add(1, Ordering::Relaxed);
}
fn render(&self, _state: &mut (), _context: &mut RenderContext) {}
}
struct Idle;
impl BelaApplication for Idle {
type RenderState = ();
fn create_render_state(&mut self, _thread: ThreadInfo, _context: &SetupContext) {}
fn render(&self, _state: &mut (), _context: &mut RenderContext) {}
}
#[cfg(bela_device)]
mod checks {
use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use core::time::Duration;
use std::sync::Arc;
use std::thread;
use bela::{Bela, Error, Settings};
use super::{
Abort, Idle, NeedsFourThreads, Observe, RENDER_MILLIS, REQUIRED_THREADS,
WRONG_THREAD_COUNT, cycle,
};
pub(crate) fn fifo_probe(period_size: u32) {
let settings = Settings::new().period_size(period_size).verbose(true);
drop(Bela::new(Abort, &settings));
}
pub(crate) fn second_new() {
let outcome = match Bela::new(Idle, &Settings::new()) {
Err(error) => format!("first-new-failed-{error}"),
Ok(first) => {
let second = match Bela::new(Idle, &Settings::new()) {
Err(Error::AudioSystemExists) => "refused",
Err(_) => "failed-otherwise",
Ok(_) => "created",
};
drop(first);
second.to_owned()
}
};
println!("rules: second-new={outcome}");
}
pub(crate) fn monitoring(requested: bool) {
let settings = if requested {
Settings::new().cpu_monitoring(cycle())
} else {
Settings::new()
};
let monitored = Arc::new(AtomicBool::new(false));
let app = Observe {
monitored: Arc::clone(&monitored),
};
let observed = match Bela::new(app, &settings) {
Err(Error::Init(_)) => true,
Err(_) => false,
Ok(bela) => {
drop(bela);
true
}
};
let seen = if !observed {
"setup-not-reached"
} else if monitored.load(Ordering::Relaxed) {
"some"
} else {
"none"
};
println!("rules: monitoring={seen}");
}
pub(crate) fn validate_settings() {
let blocks = Arc::new(AtomicU32::new(0));
let refusing = NeedsFourThreads {
blocks: Arc::clone(&blocks),
};
let refused = match Bela::new(refusing, &Settings::new()) {
Err(Error::SettingsRefused(reason)) if reason == WRONG_THREAD_COUNT => "refused",
Err(Error::SettingsRefused(_)) => "refused-with-another-reason",
Err(_) => "failed-otherwise",
Ok(bela) => {
drop(bela);
"created"
}
};
let accepting = NeedsFourThreads {
blocks: Arc::clone(&blocks),
};
let settings = Settings::new().thread_count(REQUIRED_THREADS);
let ran = match Bela::new(accepting, &settings) {
Err(error) => format!("failed-{error}"),
Ok(mut bela) => {
if let Err(error) = bela.start() {
format!("start-failed-{error}")
} else {
thread::sleep(Duration::from_millis(RENDER_MILLIS));
bela.stop();
format!("blocks-{}", blocks.load(Ordering::Relaxed))
}
}
};
println!("rules: settings-refusal={refused} then-audio={ran}");
}
pub(crate) fn poisoned() {
let first = match Bela::new(Abort, &Settings::new()) {
Err(Error::Init(_)) => "failed",
Err(_) => "failed-otherwise",
Ok(bela) => {
drop(bela);
"created"
}
};
let second = match Bela::new(Idle, &Settings::new()) {
Err(Error::AudioSystemPoisoned) => "refused",
Err(_) => "failed-otherwise",
Ok(bela) => {
drop(bela);
"created"
}
};
println!("rules: first-init={first} poisoned-new={second}");
}
}
#[cfg(bela_device)]
fn main() -> ExitCode {
use std::env::args;
let arguments: Vec<String> = args().skip(1).collect();
let check: Vec<&str> = arguments.iter().map(String::as_str).collect();
match check.as_slice() {
["fifo-probe", frames] => {
let Ok(frames) = frames.parse() else {
eprintln!("fifo-probe takes a period size in frames, not {frames:?}");
return ExitCode::FAILURE;
};
checks::fifo_probe(frames);
}
["second-new"] => checks::second_new(),
["monitoring", "on"] => checks::monitoring(true),
["monitoring", "off"] => checks::monitoring(false),
["poisoned"] => checks::poisoned(),
["validate-settings"] => checks::validate_settings(),
_ => {
eprintln!(
"usage: monitoring_rules (fifo-probe <frames> | second-new | monitoring on|off\n\
\x20 | poisoned | validate-settings)\n\
one check per run: three of these abort from `setup`, which makes \
`Bela::new` give up on it"
);
return ExitCode::FAILURE;
}
}
ExitCode::SUCCESS
}
#[cfg(not(bela_device))]
fn main() -> ExitCode {
eprintln!("This example must be cross-compiled for Bela Gem (aarch64-unknown-linux-gnu).");
ExitCode::FAILURE
}