#![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::f32::consts::TAU;
#[cfg(not(bela_device))]
use std::process::ExitCode;
use bela::{BelaApplication, BlockContext, RenderContext, SetupContext, ThreadInfo};
const FREQUENCY: f32 = 440.0;
const AMPLITUDE: f32 = 0.3;
const LINE_OUT_LEVEL: f32 = -12.0;
const HEADPHONE_LEVEL: f32 = -20.0;
const INPUT_GAIN: f32 = 30.0;
const MISSING_CHANNEL: usize = 64;
struct Sine {
phase: f32,
phase_increment: f32,
}
impl Sine {
const fn new() -> Self {
Self {
phase: 0.0,
phase_increment: 0.0,
}
}
}
struct Phase {
first_frame: usize,
phase: f32,
}
impl BelaApplication for Sine {
type RenderState = Phase;
fn setup(&mut self, context: &SetupContext) -> bool {
self.phase_increment = TAU * FREQUENCY / context.audio_sample_rate();
true
}
fn create_render_state(&mut self, thread: ThreadInfo, context: &SetupContext) -> Phase {
Phase {
first_frame: thread.frame_range(context.audio_frames()).start,
phase: 0.0,
}
}
fn render_pre(&mut self, states: &mut [Phase], _context: &mut BlockContext) {
for state in states {
#[allow(
clippy::cast_precision_loss,
reason = "a frame index within a block is far below f32's exact integer range"
)]
let offset = state.first_frame as f32 * self.phase_increment;
state.phase = self.phase + offset;
}
}
fn render(&self, state: &mut Phase, context: &mut RenderContext) {
for frame in context.audio_frame_range() {
let sample = AMPLITUDE * state.phase.sin();
for channel in 0..context.audio_out_channels() {
context.audio_write(frame, channel, sample);
}
state.phase += self.phase_increment;
}
}
fn render_post(&mut self, _states: &mut [Phase], context: &mut BlockContext) {
#[allow(
clippy::cast_precision_loss,
reason = "a block's frame count is far below f32's exact integer range"
)]
let advanced = context.audio_frames() as f32 * self.phase_increment;
self.phase = (self.phase + advanced) % TAU;
}
}
#[cfg(bela_device)]
fn outcome(result: Result<(), bela::Error>) -> String {
match result {
Ok(()) => "ok".to_owned(),
Err(error) => format!("failed({error})"),
}
}
#[cfg(bela_device)]
fn main() -> Result<(), bela::Error> {
use bela::{Bela, Channel, Settings};
let mut bela = Bela::new(Sine::new(), &Settings::new())?;
let line_out = outcome(bela.set_line_out_level(Channel::All, LINE_OUT_LEVEL));
let headphone = outcome(bela.set_headphone_level(Channel::All, HEADPHONE_LEVEL));
let input_gain = outcome(bela.set_audio_input_gain(Channel::All, INPUT_GAIN));
let unmuted = outcome(bela.mute_speakers(false));
let missing = match bela.set_line_out_level(Channel::One(MISSING_CHANNEL), LINE_OUT_LEVEL) {
Err(bela::Error::LineOutLevel(_)) => "refused".to_owned(),
Ok(()) => "accepted".to_owned(),
Err(error) => format!("other-error({error})"),
};
let not_a_number = match bela.set_line_out_level(Channel::All, f32::NAN) {
Err(bela::Error::Decibels) => "refused".to_owned(),
Ok(()) => "accepted".to_owned(),
Err(error) => format!("other-error({error})"),
};
println!(
"levels: line-out={line_out} headphone={headphone} input-gain={input_gain} \
unmute={unmuted} missing-channel={missing} not-a-number={not_a_number}"
);
bela.until_stopped()
}
#[cfg(not(bela_device))]
fn main() -> ExitCode {
eprintln!("This example must be cross-compiled for Bela Gem (aarch64-unknown-linux-gnu).");
ExitCode::FAILURE
}