Skip to main content

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}