paired_io_check/paired_io_check.rs
1//! Confirms on real hardware that `PairedIo`'s `audio_io().frames()`
2//! path (#110) copies the same samples the indexed `audio_read` /
3//! `audio_write` path does — the two ways to write `passthrough.rs`.
4//!
5//! # The claim under test
6//!
7//! Every block, `render` first fills the output with the indexed path,
8//! exactly as `passthrough.rs` does. It then walks `audio_io().frames()`
9//! over the same channels: for each sample, it reads what the indexed
10//! path just wrote (still sitting in the output buffer), compares it
11//! bit-for-bit against the sample `frames()` pairs with it on the input
12//! side, and only then overwrites it. A mismatch means the two paths
13//! disagree about which input sample belongs to which output sample —
14//! exactly the off-by-partition mistake `frames()` exists to rule out
15//! (see the type documentation on `PairedIo`).
16//!
17//! No known input signal is needed: whatever is on the analog/audio
18//! inputs, both paths read the same live buffer, so agreement is a
19//! property of the two accessors, not of the signal. `cleanup` reports
20//! `checked` and `mismatches`; a correct build never sees the second
21//! move off zero.
22//!
23//! Cross-compile and run on the board (see docs/cross-compile.md):
24//!
25//! ```sh
26//! cargo build -p bela --release --target aarch64-unknown-linux-gnu --example paired_io_check
27//! ```
28
29#![cfg_attr(
30 not(bela_device),
31 allow(
32 dead_code,
33 reason = "only the fallback main is reachable off-device; the application code should still compile and lint"
34 )
35)]
36
37use core::sync::atomic::{AtomicU64, Ordering};
38#[cfg(not(bela_device))]
39use std::process::ExitCode;
40
41use bela::{BelaApplication, CleanupContext, RenderContext, SetupContext, ThreadInfo, rt_println};
42
43#[derive(Default)]
44struct PairedIoCheck {
45 checked: AtomicU64,
46 mismatches: AtomicU64,
47}
48
49impl BelaApplication for PairedIoCheck {
50 type RenderState = ();
51
52 fn create_render_state(&mut self, _thread: ThreadInfo, _context: &SetupContext) {}
53
54 fn render(&self, _state: &mut (), context: &mut RenderContext) {
55 let channels = context
56 .audio_in_channels()
57 .min(context.audio_out_channels());
58 let range = context.audio_frame_range();
59
60 // Ground truth: the indexed path, exactly as `passthrough.rs`
61 // uses it.
62 for frame in range {
63 for channel in 0..channels {
64 let sample = context.audio_read(frame, channel);
65 context.audio_write(frame, channel, sample);
66 }
67 }
68
69 // The paired path, over the same channels — compared against
70 // what the indexed path already wrote, before it overwrites it.
71 let mut io = context.audio_io();
72 for (input, output) in io.frames() {
73 for channel in 0..channels {
74 let expected = output[channel];
75 let paired = input[channel];
76 self.checked.fetch_add(1, Ordering::Relaxed);
77 if paired.to_bits() != expected.to_bits() {
78 self.mismatches.fetch_add(1, Ordering::Relaxed);
79 }
80 output[channel] = paired;
81 }
82 }
83 }
84
85 fn cleanup(&mut self, _states: &mut [()], _context: &CleanupContext) {
86 rt_println!(
87 "paired_io_check: checked={} mismatches={}",
88 self.checked.load(Ordering::Relaxed),
89 self.mismatches.load(Ordering::Relaxed)
90 );
91 }
92}
93
94#[cfg(bela_device)]
95fn main() -> Result<(), bela::Error> {
96 bela::Bela::run(PairedIoCheck::default(), &bela::Settings::new())
97}
98
99#[cfg(not(bela_device))]
100fn main() -> ExitCode {
101 eprintln!("This example must be cross-compiled for Bela Gem (aarch64-unknown-linux-gnu).");
102 ExitCode::FAILURE
103}