Skip to main content

tympan_apo/
buffer.rs

1//! Audio buffer types — flags and connection properties.
2//!
3//! Mirrors the small set of types from `audioenginebaseapo.h` that
4//! the COM harness needs to interpret when forwarding host buffers
5//! to user [`crate::ProcessingObject::process`] implementations.
6//! Defined cross-platform so the realtime-safety properties can be
7//! unit-tested on any host.
8
9/// Status of an audio buffer crossing the APO boundary.
10///
11/// The enum values match the Windows audio engine's
12/// `APO_BUFFER_FLAGS` definitions in `audioenginebaseapo.h`:
13///
14/// | Variant | Value | Meaning |
15/// |---|---|---|
16/// | [`Self::INVALID`] | `0` | Buffer contents undefined; ignore. |
17/// | [`Self::VALID`] | `1` | Buffer contains valid audio data. |
18/// | [`Self::SILENT`] | `2` | Buffer represents pure silence and may be skipped by the APO without producing audible artefacts. |
19///
20/// Defined as a `#[repr(transparent)]` newtype rather than an enum
21/// so callers can round-trip the raw `i32` returned by the audio
22/// engine without panicking on out-of-range values.
23#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
24#[repr(transparent)]
25pub struct BufferFlags(pub i32);
26
27impl BufferFlags {
28    /// Buffer contents are undefined.
29    pub const INVALID: Self = Self(0);
30    /// Buffer contains valid audio data.
31    pub const VALID: Self = Self(1);
32    /// Buffer is entirely silence; the APO is free to skip
33    /// computation for this buffer.
34    pub const SILENT: Self = Self(2);
35
36    /// `true` iff this is exactly [`Self::VALID`].
37    #[inline]
38    #[must_use]
39    pub const fn is_valid(self) -> bool {
40        self.0 == Self::VALID.0
41    }
42
43    /// `true` iff this is exactly [`Self::SILENT`].
44    #[inline]
45    #[must_use]
46    pub const fn is_silent(self) -> bool {
47        self.0 == Self::SILENT.0
48    }
49
50    /// `true` iff this is exactly [`Self::INVALID`].
51    #[inline]
52    #[must_use]
53    pub const fn is_invalid(self) -> bool {
54        self.0 == Self::INVALID.0
55    }
56}
57
58impl Default for BufferFlags {
59    /// Defaults to [`Self::INVALID`], matching the Windows audio
60    /// engine's `APO_BUFFER_FLAGS::default()`.
61    #[inline]
62    fn default() -> Self {
63        Self::INVALID
64    }
65}
66
67/// Audio buffer descriptor handed to / from `APOProcess`.
68///
69/// Cross-platform mirror of the Windows
70/// `APO_CONNECTION_PROPERTY` C struct. The framework's COM harness
71/// translates one of these per input and per output connection
72/// when dispatching into [`crate::ProcessingObject::process`].
73///
74/// `buffer` is a raw address (`usize`) to match Windows's
75/// `UINT_PTR pBuffer`. Higher-level code that wraps the COM
76/// harness will turn this into a typed slice; doing so here is
77/// premature because the `Format` negotiated between the audio
78/// engine and the APO determines the element type and layout.
79///
80/// `signature` carries the `'APOC'` magic the host stamps onto
81/// every connection (see
82/// [`CONNECTION_PROPERTY_SIGNATURE`]); the harness checks it
83/// before trusting the rest of the struct.
84#[derive(Copy, Clone, PartialEq, Eq, Debug)]
85pub struct ConnectionProperty {
86    /// Raw address of the audio buffer (`pBuffer`).
87    pub buffer: usize,
88    /// Number of audio frames containing valid data
89    /// (`u32ValidFrameCount`).
90    pub valid_frame_count: u32,
91    /// Buffer status flags (`u32BufferFlags`).
92    pub flags: BufferFlags,
93    /// `'APOC'` magic for tamper detection (`u32Signature`). The
94    /// COM harness rejects buffers whose signature does not
95    /// match [`CONNECTION_PROPERTY_SIGNATURE`].
96    pub signature: u32,
97}
98
99impl ConnectionProperty {
100    /// Construct an empty descriptor: null buffer, zero frames,
101    /// `INVALID` flags, and the correct signature.
102    #[inline]
103    #[must_use]
104    pub const fn empty() -> Self {
105        Self {
106            buffer: 0,
107            valid_frame_count: 0,
108            flags: BufferFlags::INVALID,
109            signature: CONNECTION_PROPERTY_SIGNATURE,
110        }
111    }
112}
113
114/// `'APOC'` — magic value the Windows audio engine stamps on every
115/// `APO_CONNECTION_PROPERTY::u32Signature` field.
116///
117/// In big-endian byte order the bytes spell `A`, `P`, `O`, `C`.
118/// The Windows audio header (`audioenginebaseapo.h`) defines the
119/// constant via the four-character literal `'APOC'`, which MSVC
120/// evaluates as `0x4150_4F43`.
121pub const CONNECTION_PROPERTY_SIGNATURE: u32 = u32::from_be_bytes([b'A', b'P', b'O', b'C']);
122
123#[cfg(windows)]
124impl From<windows::Win32::Media::Audio::Apo::APO_BUFFER_FLAGS> for BufferFlags {
125    #[inline]
126    fn from(value: windows::Win32::Media::Audio::Apo::APO_BUFFER_FLAGS) -> Self {
127        Self(value.0)
128    }
129}
130
131#[cfg(windows)]
132impl From<BufferFlags> for windows::Win32::Media::Audio::Apo::APO_BUFFER_FLAGS {
133    #[inline]
134    fn from(value: BufferFlags) -> Self {
135        Self(value.0)
136    }
137}
138
139#[cfg(windows)]
140impl From<windows::Win32::Media::Audio::Apo::APO_CONNECTION_PROPERTY> for ConnectionProperty {
141    #[inline]
142    fn from(value: windows::Win32::Media::Audio::Apo::APO_CONNECTION_PROPERTY) -> Self {
143        Self {
144            buffer: value.pBuffer,
145            valid_frame_count: value.u32ValidFrameCount,
146            flags: value.u32BufferFlags.into(),
147            signature: value.u32Signature,
148        }
149    }
150}
151
152#[cfg(windows)]
153impl From<ConnectionProperty> for windows::Win32::Media::Audio::Apo::APO_CONNECTION_PROPERTY {
154    #[inline]
155    fn from(value: ConnectionProperty) -> Self {
156        Self {
157            pBuffer: value.buffer,
158            u32ValidFrameCount: value.valid_frame_count,
159            u32BufferFlags: value.flags.into(),
160            u32Signature: value.signature,
161        }
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168
169    #[test]
170    fn buffer_flag_constants_match_microsoft_values() {
171        assert_eq!(BufferFlags::INVALID.0, 0);
172        assert_eq!(BufferFlags::VALID.0, 1);
173        assert_eq!(BufferFlags::SILENT.0, 2);
174    }
175
176    #[test]
177    fn buffer_flag_predicates_classify_correctly() {
178        assert!(BufferFlags::VALID.is_valid());
179        assert!(!BufferFlags::VALID.is_silent());
180        assert!(!BufferFlags::VALID.is_invalid());
181
182        assert!(BufferFlags::SILENT.is_silent());
183        assert!(!BufferFlags::SILENT.is_valid());
184
185        assert!(BufferFlags::INVALID.is_invalid());
186        assert!(!BufferFlags::INVALID.is_valid());
187    }
188
189    #[test]
190    fn buffer_flags_default_is_invalid() {
191        assert_eq!(BufferFlags::default(), BufferFlags::INVALID);
192    }
193
194    #[test]
195    fn buffer_flags_unknown_value_round_trips() {
196        // Out-of-range values must round-trip without panic so
197        // hosts that introduce new flags do not crash this
198        // framework on observation.
199        let f = BufferFlags(99);
200        assert!(!f.is_valid());
201        assert!(!f.is_silent());
202        assert!(!f.is_invalid());
203        assert_eq!(f.0, 99);
204    }
205
206    #[test]
207    fn connection_property_empty_has_signature() {
208        let cp = ConnectionProperty::empty();
209        assert_eq!(cp.signature, CONNECTION_PROPERTY_SIGNATURE);
210        assert_eq!(cp.buffer, 0);
211        assert_eq!(cp.valid_frame_count, 0);
212        assert!(cp.flags.is_invalid());
213    }
214
215    #[test]
216    fn signature_value_spells_apoc_in_be_bytes() {
217        assert_eq!(CONNECTION_PROPERTY_SIGNATURE, 0x4150_4F43);
218        let bytes = CONNECTION_PROPERTY_SIGNATURE.to_be_bytes();
219        assert_eq!(&bytes, b"APOC");
220    }
221
222    #[cfg(windows)]
223    #[test]
224    fn buffer_flags_round_trip_through_windows_type() {
225        use windows::Win32::Media::Audio::Apo::APO_BUFFER_FLAGS;
226        for f in [
227            BufferFlags::INVALID,
228            BufferFlags::VALID,
229            BufferFlags::SILENT,
230            BufferFlags(42),
231        ] {
232            let w: APO_BUFFER_FLAGS = f.into();
233            let back: BufferFlags = w.into();
234            assert_eq!(f, back);
235        }
236    }
237
238    #[cfg(windows)]
239    #[test]
240    fn buffer_flags_constants_match_windows_constants() {
241        use windows::Win32::Media::Audio::Apo::{BUFFER_INVALID, BUFFER_SILENT, BUFFER_VALID};
242        assert_eq!(BufferFlags::INVALID, BUFFER_INVALID.into());
243        assert_eq!(BufferFlags::VALID, BUFFER_VALID.into());
244        assert_eq!(BufferFlags::SILENT, BUFFER_SILENT.into());
245    }
246
247    #[cfg(windows)]
248    #[test]
249    fn connection_property_round_trips_through_windows_type() {
250        use windows::Win32::Media::Audio::Apo::APO_CONNECTION_PROPERTY;
251        let cp = ConnectionProperty {
252            buffer: 0xDEAD_BEEF,
253            valid_frame_count: 1024,
254            flags: BufferFlags::VALID,
255            signature: CONNECTION_PROPERTY_SIGNATURE,
256        };
257        let w: APO_CONNECTION_PROPERTY = cp.into();
258        assert_eq!(w.pBuffer, 0xDEAD_BEEF);
259        assert_eq!(w.u32ValidFrameCount, 1024);
260        assert_eq!(w.u32BufferFlags.0, 1);
261        assert_eq!(w.u32Signature, CONNECTION_PROPERTY_SIGNATURE);
262
263        let back: ConnectionProperty = w.into();
264        assert_eq!(cp, back);
265    }
266}