#![cfg_attr(
not(bela_device),
allow(
dead_code,
reason = "only the fallback main is reachable off-device; the probe code should still compile and lint"
)
)]
use std::process::ExitCode;
use bela::{BelaApplication, BlockContext, RenderContext, SetupContext, ThreadInfo, rt_println};
struct Report {
first_block: bool,
}
impl BelaApplication for Report {
type RenderState = ();
fn setup(&mut self, context: &SetupContext) -> bool {
report_context("setup", context);
true
}
fn create_render_state(&mut self, _thread: ThreadInfo, _context: &SetupContext) {}
fn render_pre(&mut self, _states: &mut [()], context: &mut BlockContext) {
if !self.first_block {
return;
}
self.first_block = false;
rt_println!(
"io-config: block-audio=frames:{},in:{},out:{},rate:{}",
context.audio_frames(),
context.audio_in_channels(),
context.audio_out_channels(),
context.audio_sample_rate()
);
rt_println!(
"io-config: block-analog=frames:{},in:{},out:{},rate:{}",
context.analog_frames(),
context.analog_in_channels(),
context.analog_out_channels(),
context.analog_sample_rate()
);
rt_println!(
"io-config: block-digital=frames:{},channels:{},rate:{}",
context.digital_frames(),
context.digital_channels(),
context.digital_sample_rate()
);
rt_println!(
"io-config: block-threads=this:{},count:{},counted:{}",
context.as_sys().thisThread,
context.as_sys().threadCount,
context.thread_count()
);
}
fn render(&self, _state: &mut (), _context: &mut RenderContext) {}
}
fn report(key: &str, value: &str) {
use std::io::{self, Write};
println!("io-config: {key}={value}");
let _ = io::stdout().flush();
}
fn report_context(phase: &str, context: &SetupContext) {
report(
&format!("{phase}-audio"),
&format!(
"frames:{},in:{},out:{},rate:{}",
context.audio_frames(),
context.audio_in_channels(),
context.audio_out_channels(),
context.audio_sample_rate()
),
);
report(
&format!("{phase}-analog"),
&format!(
"frames:{},in:{},out:{},rate:{}",
context.analog_frames(),
context.analog_in_channels(),
context.analog_out_channels(),
context.analog_sample_rate()
),
);
report(
&format!("{phase}-digital"),
&format!(
"frames:{},channels:{},rate:{}",
context.digital_frames(),
context.digital_channels(),
context.digital_sample_rate()
),
);
report(
&format!("{phase}-threads"),
&format!(
"this:{},count:{},counted:{}",
context.as_sys().thisThread,
context.as_sys().threadCount,
context.thread_count()
),
);
}
#[cfg(bela_device)]
mod probes {
use core::time::Duration;
use std::thread;
use bela::bela_sys::{
Bela_HwConfig_delete, Bela_HwConfig_new, Bela_InitSettings_alloc, Bela_InitSettings_free,
Bela_defaultSettings,
};
use bela::{Bela, Board, DetectMode, Settings, Version};
use super::{Report, report};
const RENDER_TIME: Duration = Duration::from_secs(1);
const DETECT_CACHED: DetectMode = DetectMode::Cache;
fn hardware_name(board: Board) -> String {
if board.is_recognised() {
format!("{board}({raw})", raw = board.to_sys())
} else {
board.to_string()
}
}
pub(crate) fn hardware() {
let board = Board::detect(DETECT_CACHED);
report("detect-hw", &hardware_name(board));
report("version", &Version::running().to_string());
let hw = board.to_sys();
let config = unsafe { Bela_HwConfig_new(hw) };
if config.is_null() {
report("hw-config", "null");
} else {
let config_ref = unsafe { &*config };
report(
"hw-config",
&format!(
"rate:{},audio-in:{},audio-out:{},analog-in:{},analog-out:{},digital:{}",
config_ref.audioSampleRate,
config_ref.audioInChannels,
config_ref.audioOutChannels,
config_ref.analogInChannels,
config_ref.analogOutChannels,
config_ref.digitalChannels
),
);
unsafe { Bela_HwConfig_delete(config) };
}
let raw = unsafe { Bela_InitSettings_alloc() };
if raw.is_null() {
report("defaults", "alloc-failed");
return;
}
unsafe { Bela_defaultSettings(raw) };
let defaults = unsafe { &*raw };
report(
"defaults-analog",
&format!(
"use:{},in:{},out:{},uniform:{}",
defaults.useAnalog,
defaults.numAnalogInChannels,
defaults.numAnalogOutChannels,
defaults.uniformSampleRate
),
);
report(
"defaults-digital",
&format!(
"use:{},channels:{}",
defaults.useDigital, defaults.numDigitalChannels
),
);
report(
"defaults-audio",
&format!(
"period:{},rate:{},threads:{}",
defaults.periodSize, defaults.audioSampleRate, defaults.threadCount
),
);
unsafe { Bela_InitSettings_free(raw) };
}
pub(crate) fn context(settings: &Settings) {
let app = Report { first_block: true };
let mut bela = match Bela::new(app, settings) {
Ok(bela) => bela,
Err(error) => {
report("init", &format!("failed-{error:?}"));
return;
}
};
report("init", "created");
if let Err(error) = bela.start() {
report("start", &format!("failed-{error:?}"));
return;
}
report("start", "started");
thread::sleep(RENDER_TIME);
drop(bela);
report("run", "stopped");
}
}
#[cfg(bela_device)]
fn parse_settings(arguments: &[String]) -> Option<bela::Settings> {
fn switch(value: &str) -> Option<bool> {
match value {
"on" => Some(true),
"off" => Some(false),
_ => None,
}
}
let mut settings = bela::Settings::new();
let mut rest = arguments.iter();
while let Some(option) = rest.next() {
let value = rest.next()?;
settings = match option.as_str() {
"--period" => settings.period_size(value.parse().ok()?),
"--uniform" => settings.uniform_sample_rate(switch(value)?),
"--analog" => settings.use_analog(switch(value)?),
"--digital" => settings.use_digital(switch(value)?),
"--analog-in" => settings.num_analog_in_channels(value.parse().ok()?),
"--analog-out" => settings.num_analog_out_channels(value.parse().ok()?),
"--digital-channels" => settings.num_digital_channels(value.parse().ok()?),
_ => return None,
};
}
Some(settings)
}
#[cfg(bela_device)]
fn main() -> ExitCode {
use std::env::args;
let arguments: Vec<String> = args().skip(1).collect();
match arguments.split_first() {
Some((probe, rest)) if probe == "hardware" && rest.is_empty() => probes::hardware(),
Some((probe, rest)) if probe == "context" => {
let Some(settings) = parse_settings(rest) else {
eprintln!("context: cannot read the options {rest:?}");
return ExitCode::FAILURE;
};
let asked_for = if rest.is_empty() {
"defaults".to_owned()
} else {
rest.join(" ")
};
report("settings", &asked_for);
probes::context(&settings);
}
_ => {
eprintln!(
"usage: io_config (hardware | context [options])\n\
\x20 options: --period <frames> | --uniform on|off | --analog on|off\n\
\x20 | --digital on|off | --analog-in <channels>\n\
\x20 | --analog-out <channels> | --digital-channels <channels>\n\
one configuration per run: a failed initialisation poisons its process"
);
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
}