Skip to main content

device_envoy/
audio_player.rs

1//! A device abstraction for playing audio clips over I²S hardware,
2//! with runtime sequencing and volume control.
3//!
4//! This page provides the primary documentation for generated audio player
5//! types and clip utilities.
6//!
7//! Audio clip sample data is defined at compile time as static values.
8//! At runtime, you select which clips to play and in what order.
9//! Playback runs in the background while the application does other work.
10//! Volume can be adjusted on the fly, and playback can be stopped or
11//! interrupted mid-clip.
12//! Audio samples are stored in flash. Only a small DMA buffer is used at
13//! runtime.
14//!
15//! **Supported audio formats**
16//!
17//! - Any sample rate
18//! - 16-bit signed little-endian PCM audio data (`s16le`)
19//! - Mono input audio (duplicated to left/right on I²S output)
20//!
21//! **After reading the examples below, see also:**
22//!
23//! - [`audio_player!`] - Macro to generate an audio player struct type
24//!   (includes syntax details). See
25//!   [`AudioPlayerGenerated`](audio_player_generated::AudioPlayerGenerated)
26//!   for a sample of a generated type.
27//! - [`AudioPlayerGenerated`](audio_player_generated::AudioPlayerGenerated) -
28//!   Sample struct type showing methods and associated constants generated by
29//!   [`audio_player!`].
30//! - [`audio_clip!`] - Macro to "compile in" an audio clip from an external file (includes syntax details).
31//!   See [`AudioClipGenerated`](audio_clip_generated::AudioClipGenerated)
32//!   for a sample of a generated items.
33//! - [`AudioClipGenerated`](audio_clip_generated::AudioClipGenerated) -
34//!   Sample module showing `AudioClip` and `audio_clip()` generated by
35//!   [`audio_clip!`].
36//! - [`AudioClipBuf`] - Sized, const-friendly storage for static audio clip
37//!   data. You can write your own compile-time (`const fn`) clip transforms
38//!   using [`AudioClipBuf::new`] and [`AudioClipBuf::samples`].
39//! - [`AudioClip`] - Unsized view of static audio clip data. `&AudioClip` (of varying lengths) can be sequenced together.
40//!
41//! # Example: Play "Mary Had a Little Lamb" (Phrase) Once
42//!
43//! This example plays the opening phrase (`E D C D E E E`) and then stops.
44//!
45//! ```rust,no_run
46//! # #![no_std]
47//! # #![no_main]
48//! # use panic_probe as _;
49//! # use core::convert::Infallible;
50//! # use core::result::Result::Ok;
51//! use device_envoy::{Result, audio_player::{AtEnd, Volume, audio_player, samples_ms, VOICE_22050_HZ}};
52//!
53//! // Generate `AudioPlayer8`, a struct type with the specified configuration.
54//! audio_player! {
55//!     AudioPlayer8 {
56//!         data_pin: PIN_8,
57//!         bit_clock_pin: PIN_9,
58//!         word_select_pin: PIN_10,
59//!         sample_rate_hz: VOICE_22050_HZ, // Convenience constant for this example; any hardware-supported sample rate can be used.
60//!         max_volume: Volume::percent(50),
61//!     }
62//! }
63//!
64//! # #[embassy_executor::main]
65//! # async fn main(spawner: embassy_executor::Spawner) -> ! {
66//! #     let err = example(spawner).await.unwrap_err();
67//! #     core::panic!("{err}");
68//! # }
69//! async fn example(spawner: embassy_executor::Spawner) -> Result<Infallible> {
70//!     // Define REST_MS as a static clip of silence, 80 milliseconds long.
71//!     static REST_MS: samples_ms! { AudioPlayer8, 80 } = AudioPlayer8::silence();
72//!     // Define each note as a static clip of a sine wave at the appropriate frequency, 220 ms long.
73//!     static NOTE_E4: samples_ms! { AudioPlayer8, 220 } = AudioPlayer8::tone(330);
74//!     static NOTE_D4: samples_ms! { AudioPlayer8, 220 } = AudioPlayer8::tone(294);
75//!     static NOTE_C4: samples_ms! { AudioPlayer8, 220 } = AudioPlayer8::tone(262);
76//!
77//!     let p = embassy_rp::init(Default::default());
78//!     // Create an `AudioPlayer8` instance with the specified pins and resources.
79//!     let audio_player8 = AudioPlayer8::new(p.PIN_8, p.PIN_9, p.PIN_10, p.PIO0, p.DMA_CH0, spawner)?;
80//!
81//!     audio_player8.play(
82//!         [
83//!             &NOTE_E4, &REST_MS,
84//!             &NOTE_D4, &REST_MS,
85//!             &NOTE_C4, &REST_MS,
86//!             &NOTE_D4, &REST_MS,
87//!             &NOTE_E4, &REST_MS,
88//!             &NOTE_E4, &REST_MS,
89//!             &NOTE_E4,
90//!         ],
91//!         AtEnd::Stop,
92//!     );
93//!
94//!     // Audio plays in the background while we can do other things here, like blink an LED or read a button.
95//!
96//!     core::future::pending().await // run forever
97//!
98//! }
99//! ```
100//!
101//! # Example: Compiling in an External Audio Clip and Runtime Volume Changes
102//!
103//! This example shows how to "compile in" an audio clip from an external file,
104//! adjust its loudness at compile time, and then play it in a loop while changing the volume
105//! while it plays. This also demonstrates how to stop playback and reset the volume.
106//!
107//! ```rust,no_run
108//! # #![no_std]
109//! # #![no_main]
110//! # use panic_probe as _;
111//! # use core::convert::Infallible;
112//! # use core::result::Result::Ok;
113//! use device_envoy::{
114//!     Result,
115//!     audio_player::{
116//!         AtEnd, Gain, Volume, audio_clip, audio_player, samples_ms, VOICE_22050_HZ,
117//!     },
118//!     button::{Button, PressedTo},
119//! };
120//! use embassy_futures::select::{Either, select};
121//! use embassy_time::{Duration, Timer};
122//!
123//! audio_player! {
124//!     AudioPlayer10 {
125//!         data_pin: PIN_8,
126//!         bit_clock_pin: PIN_9,
127//!         word_select_pin: PIN_10,
128//!         sample_rate_hz: VOICE_22050_HZ,
129//!         pio: PIO0,                             // optional, defaults to PIO0
130//!         dma: DMA_CH1,                          // optional, defaults to DMA_CH0
131//!         max_clips: 8,                          // optional, defaults to 16
132//!         max_volume: Volume::spinal_tap(11),    // optional, defaults to Volume::MAX
133//!         initial_volume: Volume::spinal_tap(5), // optional, defaults to Volume::MAX
134//!     }
135//! }
136//!
137//! // Define a `const` function that, if called, will return the audio from this file.
138//! audio_clip! {
139//!     Nasa {
140//!         sample_rate_hz: VOICE_22050_HZ,  // To avoid a compiler error, this must match the player sample rate.
141//!         file: concat!(env!("CARGO_MANIFEST_DIR"), "/examples/data/audio/nasa_22k.s16"),
142//!     }
143//! }
144//!
145//! # #[embassy_executor::main]
146//! # async fn main(spawner: embassy_executor::Spawner) -> ! {
147//! #     let err = example(spawner).await.unwrap_err();
148//! #     core::panic!("{err}");
149//! # }
150//! async fn example(spawner: embassy_executor::Spawner) -> Result<Infallible> {
151//!     // After lower its loudness (at compile time), materialize the clip as a static value.
152//!     static NASA: Nasa::AudioClip = Nasa::audio_clip().with_gain(Gain::percent(25));
153//!     static GAP: samples_ms! { AudioPlayer10, 80 } = AudioPlayer10::silence();
154//!
155//!     let p = embassy_rp::init(Default::default());
156//!     let mut button = Button::new(p.PIN_13, PressedTo::Ground);
157//!     let audio_player10 =
158//!         AudioPlayer10::new(p.PIN_8, p.PIN_9, p.PIN_10, p.PIO0, p.DMA_CH1, spawner)?;
159//!
160//!     const VOLUME_STEPS_PERCENT: [u8; 7] = [50, 25, 12, 6, 3, 1, 0];
161//!
162//!     loop {
163//!         // Wait for user input before starting.
164//!         button.wait_for_press().await;
165//!
166//!         // Start playing the NASA clip, over and over.
167//!         audio_player10.play([&NASA, &GAP], AtEnd::Loop);
168//!
169//!         // Lower runtime volume over time, unless the button is pressed.
170//!         for volume_percent in VOLUME_STEPS_PERCENT {
171//!             match select(
172//!                 button.wait_for_press(),
173//!                 Timer::after(Duration::from_secs(1)),
174//!             )
175//!             .await
176//!             {
177//!                 Either::First(()) => {
178//!                     // Button pressed: leave inner loop.
179//!                     break;
180//!                 }
181//!                 Either::Second(()) => {
182//!                     // Timer elapsed: lower volume and keep looping.
183//!                     audio_player10.set_volume(Volume::percent(volume_percent));
184//!                 }
185//!             }
186//!         }
187//!         audio_player10.stop();
188//!         audio_player10.set_volume(AudioPlayer10::INITIAL_VOLUME);
189//!
190//!     }
191//!
192//!     core::future::pending().await // run forever
193//! }
194//! ```
195pub mod audio_clip_generated;
196pub mod audio_player_generated;
197
198use core::ops::ControlFlow;
199use core::sync::atomic::{AtomicI32, Ordering};
200
201use embassy_rp::Peri;
202use embassy_rp::dma::Channel;
203use embassy_rp::gpio::Pin;
204use embassy_rp::pio::{Instance, Pio, PioPin};
205use embassy_rp::pio_programs::i2s::{PioI2sOut, PioI2sOutProgram};
206use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal};
207use heapless::Vec;
208
209const BIT_DEPTH_BITS: u32 = 16;
210const SAMPLE_BUFFER_LEN: usize = 256;
211const I16_ABS_MAX_I64: i64 = -(i16::MIN as i64);
212
213/// Common audio sample-rate constants in hertz.
214/// Narrowband telephony sample rate.
215pub const NARROWBAND_8000_HZ: u32 = 8_000;
216/// Wideband voice sample rate.
217pub const VOICE_16000_HZ: u32 = 16_000;
218/// Common low-memory voice/music sample rate.
219///
220/// Convenience constant: any sample rate supported by your hardware setup may
221/// be used.
222pub const VOICE_22050_HZ: u32 = 22_050;
223/// Compact-disc sample rate.
224pub const CD_44100_HZ: u32 = 44_100;
225/// Pro-audio sample rate.
226pub const PRO_48000_HZ: u32 = 48_000;
227
228/// Absolute playback loudness setting for the whole player.
229///
230/// `Volume` is used by the player-level controls
231/// [`max_volume`, `initial_volume`](macro@crate::audio_player), and
232/// [`set_volume`](audio_player_generated::AudioPlayerGenerated::set_volume),
233/// which set the absolute playback loudness behavior for the whole player.
234///
235/// This is different from [`Gain`] and [`AudioClipBuf::with_gain`], which
236/// adjust the relative loudness of individual clips.
237///
238/// See the [audio_player module documentation](mod@crate::audio_player) for
239/// usage examples.
240#[derive(Clone, Copy, Debug, PartialEq, Eq)]
241pub struct Volume(i16);
242
243impl Volume {
244    /// Silence.
245    pub const MUTE: Self = Self(0);
246
247    /// Maximum playback volume.
248    pub const MAX: Self = Self(i16::MAX);
249
250    /// Creates a volume from a percentage of full scale.
251    ///
252    /// Values above `100` are clamped to `100`.
253    ///
254    /// See the [audio_player module documentation](mod@crate::audio_player) for
255    /// usage examples.
256    #[must_use]
257    pub const fn percent(percent: u8) -> Self {
258        let percent = if percent > 100 { 100 } else { percent };
259        let value_i32 = (percent as i32 * i16::MAX as i32) / 100;
260        Self(value_i32 as i16)
261    }
262
263    /// Creates a humorous "goes to 11" demo volume scale.
264    ///
265    /// `0..=11` maps to `0..=100%` using a perceptual curve
266    /// (roughly logarithmic, but not mathematically exact).
267    ///
268    /// Values above `11` clamp to `11`.
269    ///
270    /// See the [audio_player module documentation](mod@crate::audio_player) for
271    /// usage examples.
272    #[must_use]
273    pub const fn spinal_tap(spinal_tap: u8) -> Self {
274        let spinal_tap = if spinal_tap > 11 { 11 } else { spinal_tap };
275        let percent = match spinal_tap {
276            0 => 0,
277            1 => 1,
278            2 => 3,
279            3 => 6,
280            4 => 13,
281            5 => 25,
282            6 => 35,
283            7 => 50,
284            8 => 71,
285            9 => 89,
286            10 => 100,
287            11 => 100,
288            _ => 100,
289        };
290        Self::percent(percent)
291    }
292
293    #[must_use]
294    const fn to_i16(self) -> i16 {
295        self.0
296    }
297
298    #[must_use]
299    const fn from_i16(value_i16: i16) -> Self {
300        Self(value_i16)
301    }
302}
303
304/// Relative loudness adjustment for audio clips.
305///
306/// Use `Gain` with [`AudioClipBuf::with_gain`] to make a clip louder or quieter
307/// before playback.
308///
309/// `with_gain` is intended for const clip definitions, so the adjusted samples
310/// are precomputed at compile time with no extra runtime work.
311///
312/// You can set gain by percent or by dB:
313/// - [`Gain::percent`] where `100` means unchanged and values above `100` are louder.
314/// - [`Gain::db`] where positive dB is louder and negative dB is quieter.
315///
316/// This is different from [`Volume`] used by
317/// [`max_volume`, `initial_volume`](macro@crate::audio_player), and
318/// [`set_volume`](audio_player_generated::AudioPlayerGenerated::set_volume),
319/// which set the absolute playback loudness behavior for the whole player.
320///
321/// See the [audio_player module documentation](mod@crate::audio_player) for
322/// usage examples.
323#[derive(Clone, Copy, Debug, PartialEq, Eq)]
324pub struct Gain(i32);
325
326impl Gain {
327    /// Silence.
328    pub const MUTE: Self = Self(0);
329
330    /// Creates a gain from percentage.
331    ///
332    /// `100` is unity gain. Values above `100` boost the signal.
333    ///
334    /// See the [audio_player module documentation](mod@crate::audio_player) for
335    /// usage examples.
336    #[must_use]
337    pub const fn percent(percent: u16) -> Self {
338        let value_i32 = (percent as i32 * i16::MAX as i32) / 100;
339        Self(value_i32)
340    }
341
342    /// Creates gain from dB with a bounded boost range.
343    ///
344    /// Values above `+12 dB` clamp to `+12 dB`.
345    /// Values below `-96 dB` clamp to `-96 dB`.
346    ///
347    /// See [`AudioClipBuf::with_gain`] for usage.
348    #[must_use]
349    pub const fn db(db: i8) -> Self {
350        const DB_UPPER_LIMIT: i8 = 12;
351        const DB_LOWER_LIMIT: i8 = -96;
352        let db = if db > DB_UPPER_LIMIT {
353            DB_UPPER_LIMIT
354        } else if db < DB_LOWER_LIMIT {
355            DB_LOWER_LIMIT
356        } else {
357            db
358        };
359
360        if db == 0 {
361            return Self::percent(100);
362        }
363
364        // Fixed-point multipliers for 10^(+/-1/20) (approximately +/-1 dB in amplitude).
365        const DB_STEP_DOWN_Q15: i32 = 29_205;
366        const DB_STEP_UP_Q15: i32 = 36_781;
367        const ONE_Q15: i32 = 32_768;
368        const ROUND_Q15: i32 = 16_384;
369        let step_q15_i32 = if db > 0 {
370            DB_STEP_UP_Q15
371        } else {
372            DB_STEP_DOWN_Q15
373        };
374        let db_steps_u8 = if db > 0 { db as u8 } else { (-db) as u8 };
375        let mut scale_q15_i32 = ONE_Q15;
376        let mut step_index = 0_u8;
377        while step_index < db_steps_u8 {
378            scale_q15_i32 = (scale_q15_i32 * step_q15_i32 + ROUND_Q15) / ONE_Q15;
379            step_index += 1;
380        }
381
382        let gain_i64 = (i16::MAX as i64 * scale_q15_i32 as i64 + ROUND_Q15 as i64) / ONE_Q15 as i64;
383        let gain_i32 = if gain_i64 > i32::MAX as i64 {
384            i32::MAX
385        } else {
386            gain_i64 as i32
387        };
388        Self(gain_i32)
389    }
390
391    #[must_use]
392    const fn linear(self) -> i32 {
393        self.0
394    }
395}
396
397/// Returns how many samples are needed for a duration in milliseconds.
398///
399/// Use this in const contexts to size static audio arrays.
400///
401/// See the [audio_player module documentation](mod@crate::audio_player) for
402/// usage examples.
403#[must_use]
404#[doc(hidden)]
405pub const fn samples_for_duration_ms(duration_ms: u32, sample_rate_hz: u32) -> usize {
406    assert!(sample_rate_hz > 0, "sample_rate_hz must be > 0");
407    ((duration_ms as u64 * sample_rate_hz as u64) / 1_000) as usize
408}
409
410#[inline]
411const fn sine_sample_from_phase(phase_u32: u32) -> i16 {
412    let half_cycle_u64 = 1_u64 << 31;
413    let one_q31_u64 = 1_u64 << 31;
414    let phase_u64 = phase_u32 as u64;
415    let (half_phase_u64, sign_i64) = if phase_u64 < half_cycle_u64 {
416        (phase_u64, 1_i64)
417    } else {
418        (phase_u64 - half_cycle_u64, -1_i64)
419    };
420
421    // Bhaskara approximation on a normalized half-cycle:
422    // sin(pi * t) ~= 16 t (1 - t) / (5 - 4 t (1 - t)), for t in [0, 1].
423    let product_q31_u64 = (half_phase_u64 * (one_q31_u64 - half_phase_u64)) >> 31;
424    let denominator_q31_u64 = 5 * one_q31_u64 - 4 * product_q31_u64;
425    let sine_q31_u64 = ((16 * product_q31_u64) << 31) / denominator_q31_u64;
426
427    let sample_i64 = (sine_q31_u64 as i64 * sign_i64) >> 16;
428    clamp_i64_to_i16(sample_i64)
429}
430
431#[inline]
432const fn scale_sample_with_linear(sample_i16: i16, linear_i32: i32) -> i16 {
433    if linear_i32 == 0 {
434        return 0;
435    }
436    // Use signed full-scale magnitude (32768) so i16::MIN is handled correctly.
437    // Full-scale linear is 32767, so add one to map it to exact unity gain.
438    let unity_scaled_linear_i64 = linear_i32 as i64 + 1;
439    let scaled_i64 = (sample_i16 as i64 * unity_scaled_linear_i64) / I16_ABS_MAX_I64;
440    clamp_i64_to_i16(scaled_i64)
441}
442
443#[inline]
444const fn scale_linear(linear_i32: i32, volume: Volume) -> i32 {
445    if volume.to_i16() == 0 || linear_i32 == 0 {
446        return 0;
447    }
448    let unity_scaled_volume_i64 = volume.to_i16() as i64 + 1;
449    ((linear_i32 as i64 * unity_scaled_volume_i64) / I16_ABS_MAX_I64) as i32
450}
451
452#[inline]
453const fn scale_sample(sample_i16: i16, volume: Volume) -> i16 {
454    scale_sample_with_linear(sample_i16, volume.to_i16() as i32)
455}
456
457#[inline]
458const fn clamp_i64_to_i16(value_i64: i64) -> i16 {
459    if value_i64 > i16::MAX as i64 {
460        i16::MAX
461    } else if value_i64 < i16::MIN as i64 {
462        i16::MIN
463    } else {
464        value_i64 as i16
465    }
466}
467
468/// End-of-sequence behavior for playback.
469///
470/// `AudioPlayer` supports looping or stopping at the end of a clip sequence.
471///
472/// See the [audio_player module documentation](mod@crate::audio_player) for
473/// usage examples.
474pub enum AtEnd {
475    /// Repeat the full clip sequence forever.
476    Loop,
477    /// Stop after one full clip sequence pass.
478    Stop,
479}
480
481/// Unsized view of static audio clip data. `&AudioClip` values of different lengths can be sequenced together.
482///
483/// For fixed-size, const-friendly storage, see [`AudioClipBuf`].
484///
485/// See the [audio_player module documentation](mod@crate::audio_player) for
486/// usage examples.
487pub struct AudioClip<const SAMPLE_RATE_HZ: u32, T: ?Sized = [i16]> {
488    samples: T,
489}
490
491impl<const SAMPLE_RATE_HZ: u32, T: ?Sized> AudioClip<SAMPLE_RATE_HZ, T> {
492    /// Clip sample rate in hertz.
493    pub const SAMPLE_RATE_HZ: u32 = SAMPLE_RATE_HZ;
494}
495
496impl<const SAMPLE_RATE_HZ: u32> AudioClip<SAMPLE_RATE_HZ> {
497    /// Clip samples as an `i16` slice.
498    #[must_use]
499    pub const fn samples(&self) -> &[i16] {
500        &self.samples
501    }
502
503    /// Number of samples in this clip.
504    ///
505    /// See the [audio_player module documentation](mod@crate::audio_player) for
506    /// usage examples.
507    #[must_use]
508    pub const fn sample_count(&self) -> usize {
509        self.samples.len()
510    }
511}
512
513/// Sized, const-friendly storage for static audio clip data.
514///
515/// For unsized clip references (for sequencing different clip lengths), see
516/// [`AudioClip`].
517///
518/// Use [`AudioClipBuf::new`] and [`AudioClipBuf::samples`] to build your own
519/// compile-time clip transforms as `const fn` (for example: trim, fade, or
520/// resample helpers).
521///
522/// See the [audio_player module documentation](mod@crate::audio_player) for
523/// usage examples.
524pub type AudioClipBuf<const SAMPLE_RATE_HZ: u32, const SAMPLE_COUNT: usize> =
525    AudioClip<SAMPLE_RATE_HZ, [i16; SAMPLE_COUNT]>;
526
527/// Implementation for fixed-size clips (`AudioClipBuf`).
528///
529/// This impl applies to [`AudioClip`] with array-backed storage:
530/// `AudioClip<SAMPLE_RATE_HZ, [i16; SAMPLE_COUNT]>`
531/// (which is what [`AudioClipBuf`] aliases).
532impl<const SAMPLE_RATE_HZ: u32, const SAMPLE_COUNT: usize>
533    AudioClip<SAMPLE_RATE_HZ, [i16; SAMPLE_COUNT]>
534{
535    /// Creates a clip from i16 samples.
536    ///
537    /// This is the primary constructor for custom clip-generation and
538    /// transform helpers written as `const fn`.
539    #[must_use]
540    pub const fn new(samples: [i16; SAMPLE_COUNT]) -> Self {
541        assert!(SAMPLE_RATE_HZ > 0, "sample_rate_hz must be > 0");
542        Self { samples }
543    }
544
545    /// Returns the clip samples as a fixed-size array reference.
546    ///
547    /// This is intended for custom clip-transform helpers written as `const fn`.
548    #[must_use]
549    pub const fn samples(&self) -> &[i16; SAMPLE_COUNT] {
550        &self.samples
551    }
552
553    /// Number of samples in this clip.
554    ///
555    /// See the [audio_player module documentation](mod@crate::audio_player) for
556    /// clip usage examples.
557    pub const SAMPLE_COUNT: usize = SAMPLE_COUNT;
558
559    /// Returns a new clip with linear sample gain applied.
560    ///
561    /// This is intended to be used in const clip definitions so the adjusted
562    /// samples are computed ahead of time.
563    ///
564    /// You can also write your own compile-time clip transforms by reading
565    /// samples with [`AudioClipBuf::samples`] and building a new clip with
566    /// [`AudioClipBuf::new`].
567    ///
568    /// Gain multiplication uses i32 math and saturates to i16 sample bounds.
569    /// Large boosts can hard-clip peaks and introduce distortion.
570    ///
571    /// See the [audio_player module documentation](mod@crate::audio_player) for
572    /// usage examples.
573    #[must_use]
574    pub const fn with_gain(self, gain: Gain) -> Self {
575        let mut scaled_samples = [0_i16; SAMPLE_COUNT];
576        let mut sample_index = 0_usize;
577        while sample_index < SAMPLE_COUNT {
578            scaled_samples[sample_index] =
579                scale_sample_with_linear(self.samples[sample_index], gain.linear());
580            sample_index += 1;
581        }
582        Self::new(scaled_samples)
583    }
584
585    /// Creates a silent clip.
586    ///
587    /// See the [audio_player module documentation](mod@crate::audio_player) for
588    /// usage examples.
589    #[must_use]
590    pub const fn silence() -> Self {
591        Self::new([0; SAMPLE_COUNT])
592    }
593
594    /// Creates a sine-wave clip.
595    ///
596    /// See the [audio_player module documentation](mod@crate::audio_player) for
597    /// usage examples.
598    #[must_use]
599    pub const fn tone(frequency_hz: u32) -> Self {
600        assert!(SAMPLE_RATE_HZ > 0, "sample_rate_hz must be > 0");
601        let mut samples = [0_i16; SAMPLE_COUNT];
602        let phase_step_u64 = ((frequency_hz as u64) << 32) / SAMPLE_RATE_HZ as u64;
603        let phase_step_u32 = phase_step_u64 as u32;
604        let mut phase_u32 = 0_u32;
605
606        let mut sample_index = 0_usize;
607        while sample_index < SAMPLE_COUNT {
608            samples[sample_index] = sine_sample_from_phase(phase_u32);
609            phase_u32 = phase_u32.wrapping_add(phase_step_u32);
610            sample_index += 1;
611        }
612
613        Self::new(samples)
614    }
615}
616
617/// Supported clip input types for [`AudioPlayer::play_iter`].
618#[doc(hidden)]
619pub trait IntoAudioClip<const SAMPLE_RATE_HZ: u32> {
620    /// Converts this clip input into a static audio clip reference.
621    fn into_audio_clip(self) -> &'static AudioClip<SAMPLE_RATE_HZ>;
622}
623
624impl<const SAMPLE_RATE_HZ: u32> IntoAudioClip<SAMPLE_RATE_HZ>
625    for &'static AudioClip<SAMPLE_RATE_HZ>
626{
627    fn into_audio_clip(self) -> &'static AudioClip<SAMPLE_RATE_HZ> {
628        self
629    }
630}
631
632impl<const SAMPLE_RATE_HZ: u32, const SAMPLE_COUNT: usize> IntoAudioClip<SAMPLE_RATE_HZ>
633    for &'static AudioClipBuf<SAMPLE_RATE_HZ, SAMPLE_COUNT>
634{
635    fn into_audio_clip(self) -> &'static AudioClip<SAMPLE_RATE_HZ> {
636        self
637    }
638}
639
640enum AudioCommand<const MAX_CLIPS: usize, const SAMPLE_RATE_HZ: u32> {
641    Play {
642        audio_clips: Vec<&'static AudioClip<SAMPLE_RATE_HZ>, MAX_CLIPS>,
643        at_end: AtEnd,
644    },
645    Stop,
646}
647
648/// Static resources for [`AudioPlayer`].
649// Must be `pub` so `audio_player!` expansions in downstream crates can reference this type.
650#[doc(hidden)]
651pub struct AudioPlayerStatic<const MAX_CLIPS: usize, const SAMPLE_RATE_HZ: u32> {
652    command_signal: Signal<CriticalSectionRawMutex, AudioCommand<MAX_CLIPS, SAMPLE_RATE_HZ>>,
653    max_volume_linear: i32,
654    runtime_volume_relative_linear: AtomicI32,
655}
656
657impl<const MAX_CLIPS: usize, const SAMPLE_RATE_HZ: u32>
658    AudioPlayerStatic<MAX_CLIPS, SAMPLE_RATE_HZ>
659{
660    /// Creates static resources for a player.
661    #[must_use]
662    pub const fn new_static() -> Self {
663        Self::new_static_with_max_volume_and_initial_volume(Volume::MAX, Volume::MAX)
664    }
665
666    /// Creates static resources for a player with a runtime volume ceiling.
667    #[must_use]
668    pub const fn new_static_with_max_volume(max_volume: Volume) -> Self {
669        Self::new_static_with_max_volume_and_initial_volume(max_volume, Volume::MAX)
670    }
671
672    /// Creates static resources for a player with a runtime volume ceiling
673    /// and an initial runtime volume relative to that ceiling.
674    #[must_use]
675    pub const fn new_static_with_max_volume_and_initial_volume(
676        max_volume: Volume,
677        initial_volume: Volume,
678    ) -> Self {
679        Self {
680            command_signal: Signal::new(),
681            max_volume_linear: max_volume.to_i16() as i32,
682            runtime_volume_relative_linear: AtomicI32::new(initial_volume.to_i16() as i32),
683        }
684    }
685
686    fn signal(&self, audio_command: AudioCommand<MAX_CLIPS, SAMPLE_RATE_HZ>) {
687        self.command_signal.signal(audio_command);
688    }
689
690    async fn wait(&self) -> AudioCommand<MAX_CLIPS, SAMPLE_RATE_HZ> {
691        self.command_signal.wait().await
692    }
693
694    fn set_runtime_volume(&self, volume: Volume) {
695        self.runtime_volume_relative_linear
696            .store(volume.to_i16() as i32, Ordering::Relaxed);
697    }
698
699    fn runtime_volume(&self) -> Volume {
700        Volume::from_i16(self.runtime_volume_relative_linear.load(Ordering::Relaxed) as i16)
701    }
702
703    fn effective_runtime_volume(&self) -> Volume {
704        let runtime_volume_relative = self.runtime_volume();
705        Volume::from_i16(scale_linear(self.max_volume_linear, runtime_volume_relative) as i16)
706    }
707}
708
709/// Plays static audio clips with preemptive command handling in the background device task.
710///
711/// See the [`audio_player!`] macro for the normal construction pattern.
712// Must be `pub` so `audio_player!` expansions in downstream crates can reference this type.
713#[doc(hidden)]
714pub struct AudioPlayer<const MAX_CLIPS: usize, const SAMPLE_RATE_HZ: u32> {
715    audio_player_static: &'static AudioPlayerStatic<MAX_CLIPS, SAMPLE_RATE_HZ>,
716}
717
718impl<const MAX_CLIPS: usize, const SAMPLE_RATE_HZ: u32> AudioPlayer<MAX_CLIPS, SAMPLE_RATE_HZ> {
719    /// Creates static resources for a player.
720    #[must_use]
721    pub const fn new_static() -> AudioPlayerStatic<MAX_CLIPS, SAMPLE_RATE_HZ> {
722        AudioPlayerStatic::new_static()
723    }
724
725    /// Creates static resources for a player with a runtime volume ceiling.
726    #[must_use]
727    pub const fn new_static_with_max_volume(
728        max_volume: Volume,
729    ) -> AudioPlayerStatic<MAX_CLIPS, SAMPLE_RATE_HZ> {
730        AudioPlayerStatic::new_static_with_max_volume(max_volume)
731    }
732
733    /// Creates static resources for a player with a runtime volume ceiling
734    /// and an initial runtime volume relative to that ceiling.
735    #[must_use]
736    pub const fn new_static_with_max_volume_and_initial_volume(
737        max_volume: Volume,
738        initial_volume: Volume,
739    ) -> AudioPlayerStatic<MAX_CLIPS, SAMPLE_RATE_HZ> {
740        AudioPlayerStatic::new_static_with_max_volume_and_initial_volume(max_volume, initial_volume)
741    }
742
743    /// Creates a player handle. The device task must already be running.
744    #[must_use]
745    pub const fn new(
746        audio_player_static: &'static AudioPlayerStatic<MAX_CLIPS, SAMPLE_RATE_HZ>,
747    ) -> Self {
748        Self {
749            audio_player_static,
750        }
751    }
752
753    /// Starts playback of one or more statically defined audio clips.
754    ///
755    /// This array-based API supports concise mixed-length clip literals like
756    /// `[&tone_a4, &silence_100ms, &tone_a4]`.
757    ///
758    /// Clip samples are predeclared static data, but sequence order is chosen
759    /// at runtime and copied into a fixed-capacity clip list defined by `MAX_CLIPS`.
760    /// A newer call to [`Self::play`] interrupts current playback as soon as possible
761    /// (at the next DMA chunk boundary).
762    ///
763    /// See the [audio_player module documentation](mod@crate::audio_player) for
764    /// usage examples.
765    pub fn play<const CLIP_COUNT: usize>(
766        &self,
767        audio_clips: [&'static AudioClip<SAMPLE_RATE_HZ>; CLIP_COUNT],
768        at_end: AtEnd,
769    ) {
770        self.play_iter(audio_clips, at_end);
771    }
772
773    /// Starts playback from a generic iterator of static clip-like values.
774    ///
775    /// This allows runtime-selected sequencing while still requiring static
776    /// clip sample storage.
777    pub fn play_iter<I>(&self, audio_clips: I, at_end: AtEnd)
778    where
779        I: IntoIterator,
780        I::Item: IntoAudioClip<SAMPLE_RATE_HZ>,
781    {
782        assert!(MAX_CLIPS > 0, "play disabled: max_clips is 0");
783        let mut audio_clip_sequence: Vec<&'static AudioClip<SAMPLE_RATE_HZ>, MAX_CLIPS> =
784            Vec::new();
785        for audio_clip in audio_clips {
786            let audio_clip = audio_clip.into_audio_clip();
787            assert!(
788                audio_clip_sequence.push(audio_clip).is_ok(),
789                "play sequence fits within max_clips"
790            );
791        }
792        assert!(
793            !audio_clip_sequence.is_empty(),
794            "play requires at least one clip"
795        );
796
797        self.audio_player_static.signal(AudioCommand::Play {
798            audio_clips: audio_clip_sequence,
799            at_end,
800        });
801    }
802
803    /// Stops current playback as soon as possible.
804    ///
805    /// If playback is active, it is interrupted at the next DMA chunk boundary.
806    ///
807    /// See the [audio_player module documentation](mod@crate::audio_player) for
808    /// usage examples.
809    pub fn stop(&self) {
810        self.audio_player_static.signal(AudioCommand::Stop);
811    }
812
813    /// Sets runtime playback volume relative to [`Self::MAX_VOLUME`].
814    ///
815    /// - `Volume::percent(100)` plays at exactly `max_volume`.
816    /// - `Volume::percent(50)` plays at half of `max_volume`.
817    ///
818    /// This relative scale composes multiplicatively with any per-clip gain
819    /// pre-applied via [`AudioClipBuf::with_gain`].
820    ///
821    /// See the [audio_player module documentation](mod@crate::audio_player) for
822    /// usage examples.
823    pub fn set_volume(&self, volume: Volume) {
824        self.audio_player_static.set_runtime_volume(volume);
825    }
826
827    /// Returns the current runtime playback volume relative to [`Self::MAX_VOLUME`].
828    ///
829    /// See the [audio_player module documentation](mod@crate::audio_player) for
830    /// usage examples.
831    #[must_use]
832    pub fn volume(&self) -> Volume {
833        self.audio_player_static.runtime_volume()
834    }
835}
836
837// todo0 does this really need to be different that other device abstraction traits?
838/// Trait mapping a PIO peripheral to its interrupt binding.
839#[doc(hidden)]
840pub trait AudioPlayerPio: crate::pio_irqs::PioIrqMap {}
841
842impl<PioResource: crate::pio_irqs::PioIrqMap> AudioPlayerPio for PioResource {}
843
844// Called by macro-generated code in downstream crates; must be public.
845#[doc(hidden)]
846pub async fn device_loop<
847    const MAX_CLIPS: usize,
848    const SAMPLE_RATE_HZ: u32,
849    PIO: AudioPlayerPio,
850    DMA: Channel,
851    DinPin: Pin + PioPin,
852    BclkPin: Pin + PioPin,
853    LrcPin: Pin + PioPin,
854>(
855    audio_player_static: &'static AudioPlayerStatic<MAX_CLIPS, SAMPLE_RATE_HZ>,
856    pio: Peri<'static, PIO>,
857    dma: Peri<'static, DMA>,
858    data_pin: Peri<'static, DinPin>,
859    bit_clock_pin: Peri<'static, BclkPin>,
860    word_select_pin: Peri<'static, LrcPin>,
861) -> ! {
862    let mut pio = Pio::new(pio, <PIO as crate::pio_irqs::PioIrqMap>::irqs());
863    let pio_i2s_out_program = PioI2sOutProgram::new(&mut pio.common);
864    let mut pio_i2s_out = PioI2sOut::new(
865        &mut pio.common,
866        pio.sm0,
867        dma,
868        data_pin,
869        bit_clock_pin,
870        word_select_pin,
871        SAMPLE_RATE_HZ,
872        BIT_DEPTH_BITS,
873        &pio_i2s_out_program,
874    );
875
876    let _pio_i2s_out_program = pio_i2s_out_program;
877    let mut sample_buffer = [0_u32; SAMPLE_BUFFER_LEN];
878
879    loop {
880        let mut audio_command = audio_player_static.wait().await;
881
882        loop {
883            match audio_command {
884                AudioCommand::Play {
885                    audio_clips,
886                    at_end,
887                } => {
888                    let next_audio_command = match at_end {
889                        AtEnd::Loop => loop {
890                            if let Some(next_audio_command) = play_clip_sequence_once(
891                                &mut pio_i2s_out,
892                                &audio_clips,
893                                &mut sample_buffer,
894                                audio_player_static,
895                            )
896                            .await
897                            {
898                                break Some(next_audio_command);
899                            }
900                        },
901                        AtEnd::Stop => {
902                            play_clip_sequence_once(
903                                &mut pio_i2s_out,
904                                &audio_clips,
905                                &mut sample_buffer,
906                                audio_player_static,
907                            )
908                            .await
909                        }
910                    };
911
912                    if let Some(next_audio_command) = next_audio_command {
913                        audio_command = next_audio_command;
914                        continue;
915                    }
916                }
917                AudioCommand::Stop => {}
918            }
919
920            break;
921        }
922    }
923}
924
925async fn play_clip_sequence_once<
926    PIO: Instance,
927    const MAX_CLIPS: usize,
928    const SAMPLE_RATE_HZ: u32,
929>(
930    pio_i2s_out: &mut PioI2sOut<'static, PIO, 0>,
931    audio_clips: &[&'static AudioClip<SAMPLE_RATE_HZ>],
932    sample_buffer: &mut [u32; SAMPLE_BUFFER_LEN],
933    audio_player_static: &'static AudioPlayerStatic<MAX_CLIPS, SAMPLE_RATE_HZ>,
934) -> Option<AudioCommand<MAX_CLIPS, SAMPLE_RATE_HZ>> {
935    for audio_clip in audio_clips {
936        if let ControlFlow::Break(next_audio_command) =
937            play_full_clip_once(pio_i2s_out, audio_clip, sample_buffer, audio_player_static).await
938        {
939            return Some(next_audio_command);
940        }
941    }
942    None
943}
944
945async fn play_full_clip_once<PIO: Instance, const MAX_CLIPS: usize, const SAMPLE_RATE_HZ: u32>(
946    pio_i2s_out: &mut PioI2sOut<'static, PIO, 0>,
947    audio_clip: &AudioClip<SAMPLE_RATE_HZ>,
948    sample_buffer: &mut [u32; SAMPLE_BUFFER_LEN],
949    audio_player_static: &'static AudioPlayerStatic<MAX_CLIPS, SAMPLE_RATE_HZ>,
950) -> ControlFlow<AudioCommand<MAX_CLIPS, SAMPLE_RATE_HZ>, ()> {
951    for audio_sample_chunk in audio_clip.samples().chunks(SAMPLE_BUFFER_LEN) {
952        let runtime_volume = audio_player_static.effective_runtime_volume();
953        for (sample_buffer_slot, sample_value_ref) in
954            sample_buffer.iter_mut().zip(audio_sample_chunk.iter())
955        {
956            let sample_value = *sample_value_ref;
957            let scaled_sample_value = scale_sample(sample_value, runtime_volume);
958            *sample_buffer_slot = stereo_sample(scaled_sample_value);
959        }
960
961        sample_buffer[audio_sample_chunk.len()..].fill(stereo_sample(0));
962        pio_i2s_out.write(sample_buffer).await;
963
964        if let Some(next_audio_command) = audio_player_static.command_signal.try_take() {
965            return ControlFlow::Break(next_audio_command);
966        }
967    }
968
969    ControlFlow::Continue(())
970}
971
972#[inline]
973const fn stereo_sample(sample: i16) -> u32 {
974    let sample_bits = sample as u16 as u32;
975    (sample_bits << 16) | sample_bits
976}
977
978// Must be `pub` so macro expansion works in downstream crates.
979#[doc(hidden)]
980pub use paste;
981
982/// Audio clip source formats for [`audio_clip!`]. Currently, only one format
983/// is supported.
984///
985/// For ffmpeg conversion directions, see
986/// [`audio_clip!`](macro@crate::audio_player::audio_clip) (the "Preparing
987/// audio files for `audio_clip!`" section).
988#[derive(Clone, Copy, Debug, Eq, PartialEq)]
989pub enum AudioFormat {
990    /// 16-bit signed little-endian mono PCM bytes (`s16le`).
991    S16le,
992}
993
994/// Macro to "compile in" an audio clip from an external file (includes syntax details). See
995/// [`AudioClipGenerated`](crate::audio_player::audio_clip_generated::AudioClipGenerated)
996/// for a sample of generated items.
997///
998/// **See the [audio_player module documentation](mod@crate::audio_player) for
999/// usage examples.**
1000///
1001/// The generated clip can be modified at compile time (for example with
1002/// [`Gain`](crate::audio_player::Gain) via `with_gain(...)`) and only
1003/// increases binary size when you store it in a `static`.
1004///
1005/// **Syntax:**
1006///
1007/// ```text
1008/// audio_clip! {
1009///     [<visibility>] <Name> {
1010///         sample_rate_hz: <sample_rate_expr>,
1011///         file: <file_path_expr>,
1012///         format: <AudioFormat_expr>, // optional
1013///     }
1014/// }
1015/// ```
1016///
1017/// **Inputs:**
1018///
1019/// - `$vis` - Optional module visibility for the generated namespace (for
1020///   example: `pub`, `pub(crate)`, `pub(self)`). Defaults to private visibility
1021///   when omitted.
1022/// - `$name` - Module name for the generated namespace (for example: `Nasa`)
1023///
1024/// **Required fields:**
1025///
1026/// - `sample_rate_hz` - Sample rate in hertz (for example:
1027///   [`VOICE_22050_HZ`](crate::audio_player::VOICE_22050_HZ))
1028/// - `file` - Path to an external audio file (for example: `"nasa_22k.s16"`)
1029///
1030/// **Optional fields:**
1031///
1032/// - `format` - Audio format (default: [`AudioFormat::S16le`])
1033///
1034/// **Generated items:**
1035///
1036/// - `audio_clip()` - `const` function that returns the generated audio clip
1037/// - `AudioClip` - concrete return type of `audio_clip()`
1038///
1039/// **Mental model (lifecycle):**
1040///
1041/// Each `audio_clip!` invocation generates:
1042///
1043/// - a module namespace
1044/// - a concrete clip type
1045/// - a `const fn audio_clip()` constructor
1046///
1047/// Audio bytes are embedded in program flash via `include_bytes!`.
1048/// The clip value can be constructed at compile time when used in `const` or
1049/// `static` definitions.
1050/// When you take `&Name::audio_clip()` in a `static` context, the compiler
1051/// promotes that clip value into flash storage.
1052///
1053/// # Example
1054///
1055/// See [`AudioClipGenerated`](crate::audio_player::audio_clip_generated::AudioClipGenerated)
1056/// and the [audio_player module documentation](mod@crate::audio_player).
1057///
1058/// # Preparing audio files for `audio_clip!`
1059///
1060/// This macro expects audio in a simple raw format:
1061///
1062/// - mono
1063/// - 16-bit signed samples
1064/// - little-endian
1065/// - a fixed sample rate (for example, 22050 Hz)
1066///
1067/// The easiest way to produce that format is with `ffmpeg`.
1068///
1069/// ## 1) Download an example clip (NASA)
1070///
1071/// Download the MP3:
1072///
1073/// ```bash
1074/// curl -L -o nasa.mp3 \
1075///   "https://www.nasa.gov/wp-content/uploads/2015/01/640149main_Computers20are20in20Control.mp3"
1076/// ```
1077///
1078/// (Windows 10/11 includes `curl` by default.)
1079///
1080/// ## 2) Install `ffmpeg` (or confirm it is installed)
1081///
1082/// General: see the official download page:
1083///
1084/// ```text
1085/// https://ffmpeg.org/download.html
1086/// ```
1087///
1088/// ### Ubuntu / Debian
1089///
1090/// ```bash
1091/// sudo apt update
1092/// sudo apt install ffmpeg
1093/// ffmpeg -version
1094/// ```
1095///
1096/// ### Windows (recommended: `pixi`)
1097///
1098/// 1. Install `pixi`:
1099///
1100/// ```text
1101/// https://pixi.sh
1102/// ```
1103///
1104/// 2. Install `ffmpeg` globally:
1105///
1106/// ```powershell
1107/// pixi global install ffmpeg
1108/// ffmpeg -version
1109/// ```
1110///
1111/// ## 3) Convert to raw PCM (`.s16`) for `audio_clip!`
1112///
1113/// This produces a file you can embed and process at compile time:
1114///
1115/// ```bash
1116/// ffmpeg -y -i nasa.mp3 \
1117///   -vn \
1118///   -ac 1 \
1119///   -ar 22050 \
1120///   -f s16le \
1121///   nasa_22k.s16
1122/// ```
1123///
1124/// What the arguments mean:
1125///
1126/// - `-i nasa.mp3` - input file
1127/// - `-vn` - ignore any video track (safe even for audio-only inputs)
1128/// - `-ac 1` - force mono
1129/// - `-ar 22050` - set the sample rate (Hz)
1130/// - `-f s16le` - write raw 16-bit little-endian PCM (no WAV header)
1131/// - `nasa_22k.s16` - output file (ready for `audio_clip!`)
1132///
1133/// Tip: keep the sample rate consistent with the `sample_rate_hz:` passed to
1134/// `audio_clip! { ... }`.
1135#[doc(hidden)]
1136#[macro_export]
1137macro_rules! audio_clip {
1138    ($($tt:tt)*) => { $crate::__audio_clip_parse! { $($tt)* } };
1139}
1140
1141#[doc(hidden)]
1142#[macro_export]
1143macro_rules! __audio_clip_parse {
1144    (
1145        $vis:vis $name:ident {
1146            sample_rate_hz: $sample_rate_hz:expr,
1147            file: $file:expr,
1148            format: $format:expr $(,)?
1149        }
1150    ) => {
1151        $crate::__audio_clip_dispatch! {
1152            vis: $vis,
1153            name: $name,
1154            sample_rate_hz: $sample_rate_hz,
1155            file: $file,
1156            format: $format,
1157        }
1158    };
1159    (
1160        $vis:vis $name:ident {
1161            sample_rate_hz: $sample_rate_hz:expr,
1162            file: $file:expr $(,)?
1163        }
1164    ) => {
1165        $crate::__audio_clip_dispatch! {
1166            vis: $vis,
1167            name: $name,
1168            sample_rate_hz: $sample_rate_hz,
1169            file: $file,
1170            format: $crate::audio_player::AudioFormat::S16le,
1171        }
1172    };
1173}
1174
1175#[doc(hidden)]
1176#[macro_export]
1177macro_rules! __audio_clip_dispatch {
1178    (
1179        vis: $vis:vis,
1180        name: $name:ident,
1181        sample_rate_hz: $sample_rate_hz:expr,
1182        file: $file:expr,
1183        format: $format:expr $(,)?
1184    ) => {
1185        $crate::__audio_clip_impl! {
1186            vis: $vis,
1187            name: $name,
1188            sample_rate_hz: $sample_rate_hz,
1189            file: $file,
1190            format: $format,
1191        }
1192    };
1193}
1194
1195#[doc(hidden)]
1196#[macro_export]
1197macro_rules! __audio_clip_impl {
1198    (
1199        vis: $vis:vis,
1200        name: $name:ident,
1201        sample_rate_hz: $sample_rate_hz:expr,
1202        file: $file:expr,
1203        format: $format:expr $(,)?
1204    ) => {
1205        $crate::audio_player::paste::paste! {
1206            const [<$name:upper _SAMPLE_RATE_HZ>]: u32 = $sample_rate_hz;
1207            const [<$name:upper _AUDIO_FORMAT>]: $crate::audio_player::AudioFormat = $format;
1208
1209            #[allow(non_snake_case)]
1210            #[doc = concat!(
1211                "Audio clip namespace generated by [`audio_clip!`](macro@crate::audio_player::audio_clip).\n\n",
1212                "Contains [`AudioClip`](Self::AudioClip) and [`audio_clip`](Self::audio_clip)."
1213            )]
1214            $vis mod $name {
1215                const SAMPLE_RATE_HZ: u32 = super::[<$name:upper _SAMPLE_RATE_HZ>];
1216                const AUDIO_SAMPLE_BYTES_LEN: usize = include_bytes!($file).len();
1217                const AUDIO_FORMAT: $crate::audio_player::AudioFormat =
1218                    super::[<$name:upper _AUDIO_FORMAT>];
1219
1220                #[doc = "Concrete clip type generated by [`audio_clip!`](macro@crate::audio_player::audio_clip)."]
1221                pub type AudioClip = $crate::audio_player::AudioClipBuf<
1222                    { SAMPLE_RATE_HZ },
1223                    { AUDIO_SAMPLE_BYTES_LEN / 2 },
1224                >;
1225
1226                #[doc = "Const constructor generated by [`audio_clip!`](macro@crate::audio_player::audio_clip)."]
1227                #[must_use]
1228                pub const fn audio_clip() -> AudioClip {
1229                    match AUDIO_FORMAT {
1230                        $crate::audio_player::AudioFormat::S16le => {}
1231                    }
1232                    assert!(
1233                        AUDIO_SAMPLE_BYTES_LEN % 2 == 0,
1234                        "audio byte length must be even for s16le"
1235                    );
1236
1237                    const SAMPLE_COUNT: usize = AUDIO_SAMPLE_BYTES_LEN / 2;
1238                    let audio_sample_s16le: &[u8; AUDIO_SAMPLE_BYTES_LEN] = include_bytes!($file);
1239                    let mut samples = [0_i16; SAMPLE_COUNT];
1240                    let mut sample_index = 0_usize;
1241                    while sample_index < SAMPLE_COUNT {
1242                        let byte_index = sample_index * 2;
1243                        samples[sample_index] = i16::from_le_bytes([
1244                            audio_sample_s16le[byte_index],
1245                            audio_sample_s16le[byte_index + 1],
1246                        ]);
1247                        sample_index += 1;
1248                    }
1249                    AudioClip::new(samples)
1250                }
1251            }
1252        }
1253    };
1254}
1255
1256/// Macro that expands to an [`AudioClipBuf`] type sized from a player type and milliseconds.
1257///
1258/// Example: `samples_ms!{AudioPlayer8, 500}`.
1259///
1260/// See the [audio_player module documentation](mod@crate::audio_player) for
1261/// usage examples.
1262#[doc(hidden)]
1263#[macro_export]
1264macro_rules! samples_ms {
1265    ($player:ident, $duration_ms:expr) => {
1266        $crate::audio_player::AudioClipBuf<
1267            { $player::SAMPLE_RATE_HZ },
1268            { $player::samples_ms($duration_ms) },
1269        >
1270    };
1271}
1272
1273/// Macro to generate an audio player struct type (includes syntax details). See
1274/// [`AudioPlayerGenerated`](crate::audio_player::audio_player_generated::AudioPlayerGenerated)
1275/// for a sample of a generated type.
1276///
1277/// **See the [audio_player module documentation](mod@crate::audio_player) for
1278/// usage examples.**
1279///
1280/// **Syntax:**
1281///
1282/// ```text
1283/// audio_player! {
1284///     [<visibility>] <Name> {
1285///         data_pin: <pin_ident>,
1286///         bit_clock_pin: <pin_ident>,
1287///         word_select_pin: <pin_ident>,
1288///         sample_rate_hz: <sample_rate_expr>,
1289///         pio: <pio_ident>,                 // optional
1290///         dma: <dma_ident>,                 // optional
1291///         max_clips: <usize_expr>,          // optional
1292///         max_volume: <Volume_expr>,        // optional
1293///         initial_volume: <Volume_expr>,    // optional
1294///     }
1295/// }
1296/// ```
1297///
1298/// **Inputs:**
1299///
1300/// - `$vis` - Optional generated type visibility (for example: `pub`,
1301///   `pub(crate)`, `pub(self)`). Defaults to private visibility when omitted.
1302/// - `$name` - Generated type name (for example: `AudioPlayer10`)
1303///
1304/// **Required fields:**
1305///
1306/// - `data_pin` - GPIO pin carrying I²S data (`DIN`)
1307/// - `bit_clock_pin` - GPIO pin carrying I²S bit clock (`BCLK`)
1308/// - `word_select_pin` - GPIO pin carrying I²S word-select / LR clock (`LRC` / `LRCLK`)
1309/// - `sample_rate_hz` - Playback sample rate in hertz (for example:
1310///   [`VOICE_22050_HZ`](crate::audio_player::VOICE_22050_HZ))
1311///
1312/// **Optional fields:**
1313///
1314/// - `pio` - PIO resource (default: `PIO0`)
1315/// - `dma` - DMA channel (default: `DMA_CH0`)
1316/// - `max_clips` - Maximum clips per queued play request (default: `16`)
1317/// - `max_volume` - Runtime volume ceiling (default: [`Volume::MAX`])
1318/// - `initial_volume` - Initial runtime volume relative to `max_volume`
1319///   (default: [`Volume::MAX`])
1320///
1321/// The generated type contains static resources and spawns its background device
1322/// task from `new(...)`.
1323#[doc(hidden)]
1324#[macro_export]
1325macro_rules! audio_player {
1326    ($($tt:tt)*) => { $crate::__audio_player_impl! { $($tt)* } };
1327}
1328
1329/// Internal implementation macro for [`audio_player!`].
1330#[doc(hidden)]
1331#[macro_export]
1332macro_rules! __audio_player_impl {
1333    (
1334        $name:ident {
1335            $($fields:tt)*
1336        }
1337    ) => {
1338        $crate::__audio_player_impl! {
1339            @__fill_defaults
1340            vis: pub(self),
1341            name: $name,
1342            data_pin: _UNSET_,
1343            bit_clock_pin: _UNSET_,
1344            word_select_pin: _UNSET_,
1345            sample_rate_hz: _UNSET_,
1346            pio: PIO0,
1347            dma: DMA_CH0,
1348            max_clips: 16,
1349            max_volume: $crate::audio_player::Volume::MAX,
1350            initial_volume: $crate::audio_player::Volume::MAX,
1351            fields: [ $($fields)* ]
1352        }
1353    };
1354
1355    (
1356        $vis:vis $name:ident {
1357            $($fields:tt)*
1358        }
1359    ) => {
1360        $crate::__audio_player_impl! {
1361            @__fill_defaults
1362            vis: $vis,
1363            name: $name,
1364            data_pin: _UNSET_,
1365            bit_clock_pin: _UNSET_,
1366            word_select_pin: _UNSET_,
1367            sample_rate_hz: _UNSET_,
1368            pio: PIO0,
1369            dma: DMA_CH0,
1370            max_clips: 16,
1371            max_volume: $crate::audio_player::Volume::MAX,
1372            initial_volume: $crate::audio_player::Volume::MAX,
1373            fields: [ $($fields)* ]
1374        }
1375    };
1376
1377    (@__fill_defaults
1378        vis: $vis:vis,
1379        name: $name:ident,
1380        data_pin: $data_pin:tt,
1381        bit_clock_pin: $bit_clock_pin:tt,
1382        word_select_pin: $word_select_pin:tt,
1383        sample_rate_hz: $sample_rate_hz:expr,
1384        pio: $pio:ident,
1385        dma: $dma:ident,
1386        max_clips: $max_clips:expr,
1387        max_volume: $max_volume:expr,
1388        initial_volume: $initial_volume:expr,
1389        fields: [ data_pin: $din_pin_value:ident $(, $($rest:tt)* )? ]
1390    ) => {
1391        $crate::__audio_player_impl! {
1392            @__fill_defaults
1393            vis: $vis,
1394            name: $name,
1395            data_pin: $din_pin_value,
1396            bit_clock_pin: $bit_clock_pin,
1397            word_select_pin: $word_select_pin,
1398            sample_rate_hz: $sample_rate_hz,
1399            pio: $pio,
1400            dma: $dma,
1401            max_clips: $max_clips,
1402            max_volume: $max_volume,
1403            initial_volume: $initial_volume,
1404            fields: [ $($($rest)*)? ]
1405        }
1406    };
1407
1408    (@__fill_defaults
1409        vis: $vis:vis,
1410        name: $name:ident,
1411        data_pin: $data_pin:tt,
1412        bit_clock_pin: $bit_clock_pin:tt,
1413        word_select_pin: $word_select_pin:tt,
1414        sample_rate_hz: $sample_rate_hz:expr,
1415        pio: $pio:ident,
1416        dma: $dma:ident,
1417        max_clips: $max_clips:expr,
1418        max_volume: $max_volume:expr,
1419        initial_volume: $initial_volume:expr,
1420        fields: [ sample_rate_hz: $sample_rate_hz_value:expr $(, $($rest:tt)* )? ]
1421    ) => {
1422        $crate::__audio_player_impl! {
1423            @__fill_defaults
1424            vis: $vis,
1425            name: $name,
1426            data_pin: $data_pin,
1427            bit_clock_pin: $bit_clock_pin,
1428            word_select_pin: $word_select_pin,
1429            sample_rate_hz: $sample_rate_hz_value,
1430            pio: $pio,
1431            dma: $dma,
1432            max_clips: $max_clips,
1433            max_volume: $max_volume,
1434            initial_volume: $initial_volume,
1435            fields: [ $($($rest)*)? ]
1436        }
1437    };
1438
1439    (@__fill_defaults
1440        vis: $vis:vis,
1441        name: $name:ident,
1442        data_pin: $data_pin:tt,
1443        bit_clock_pin: $bit_clock_pin:tt,
1444        word_select_pin: $word_select_pin:tt,
1445        sample_rate_hz: $sample_rate_hz:expr,
1446        pio: $pio:ident,
1447        dma: $dma:ident,
1448        max_clips: $max_clips:expr,
1449        max_volume: $max_volume:expr,
1450        initial_volume: $initial_volume:expr,
1451        fields: [ bit_clock_pin: $bclk_pin_value:ident $(, $($rest:tt)* )? ]
1452    ) => {
1453        $crate::__audio_player_impl! {
1454            @__fill_defaults
1455            vis: $vis,
1456            name: $name,
1457            data_pin: $data_pin,
1458            bit_clock_pin: $bclk_pin_value,
1459            word_select_pin: $word_select_pin,
1460            sample_rate_hz: $sample_rate_hz,
1461            pio: $pio,
1462            dma: $dma,
1463            max_clips: $max_clips,
1464            max_volume: $max_volume,
1465            initial_volume: $initial_volume,
1466            fields: [ $($($rest)*)? ]
1467        }
1468    };
1469
1470    (@__fill_defaults
1471        vis: $vis:vis,
1472        name: $name:ident,
1473        data_pin: $data_pin:tt,
1474        bit_clock_pin: $bit_clock_pin:tt,
1475        word_select_pin: $word_select_pin:tt,
1476        sample_rate_hz: $sample_rate_hz:expr,
1477        pio: $pio:ident,
1478        dma: $dma:ident,
1479        max_clips: $max_clips:expr,
1480        max_volume: $max_volume:expr,
1481        initial_volume: $initial_volume:expr,
1482        fields: [ word_select_pin: $lrc_pin_value:ident $(, $($rest:tt)* )? ]
1483    ) => {
1484        $crate::__audio_player_impl! {
1485            @__fill_defaults
1486            vis: $vis,
1487            name: $name,
1488            data_pin: $data_pin,
1489            bit_clock_pin: $bit_clock_pin,
1490            word_select_pin: $lrc_pin_value,
1491            sample_rate_hz: $sample_rate_hz,
1492            pio: $pio,
1493            dma: $dma,
1494            max_clips: $max_clips,
1495            max_volume: $max_volume,
1496            initial_volume: $initial_volume,
1497            fields: [ $($($rest)*)? ]
1498        }
1499    };
1500
1501    (@__fill_defaults
1502        vis: $vis:vis,
1503        name: $name:ident,
1504        data_pin: $data_pin:tt,
1505        bit_clock_pin: $bit_clock_pin:tt,
1506        word_select_pin: $word_select_pin:tt,
1507        sample_rate_hz: $sample_rate_hz:expr,
1508        pio: $pio:ident,
1509        dma: $dma:ident,
1510        max_clips: $max_clips:expr,
1511        max_volume: $max_volume:expr,
1512        initial_volume: $initial_volume:expr,
1513        fields: [ pio: $pio_value:ident $(, $($rest:tt)* )? ]
1514    ) => {
1515        $crate::__audio_player_impl! {
1516            @__fill_defaults
1517            vis: $vis,
1518            name: $name,
1519            data_pin: $data_pin,
1520            bit_clock_pin: $bit_clock_pin,
1521            word_select_pin: $word_select_pin,
1522            sample_rate_hz: $sample_rate_hz,
1523            pio: $pio_value,
1524            dma: $dma,
1525            max_clips: $max_clips,
1526            max_volume: $max_volume,
1527            initial_volume: $initial_volume,
1528            fields: [ $($($rest)*)? ]
1529        }
1530    };
1531
1532    (@__fill_defaults
1533        vis: $vis:vis,
1534        name: $name:ident,
1535        data_pin: $data_pin:tt,
1536        bit_clock_pin: $bit_clock_pin:tt,
1537        word_select_pin: $word_select_pin:tt,
1538        sample_rate_hz: $sample_rate_hz:expr,
1539        pio: $pio:ident,
1540        dma: $dma:ident,
1541        max_clips: $max_clips:expr,
1542        max_volume: $max_volume:expr,
1543        initial_volume: $initial_volume:expr,
1544        fields: [ dma: $dma_value:ident $(, $($rest:tt)* )? ]
1545    ) => {
1546        $crate::__audio_player_impl! {
1547            @__fill_defaults
1548            vis: $vis,
1549            name: $name,
1550            data_pin: $data_pin,
1551            bit_clock_pin: $bit_clock_pin,
1552            word_select_pin: $word_select_pin,
1553            sample_rate_hz: $sample_rate_hz,
1554            pio: $pio,
1555            dma: $dma_value,
1556            max_clips: $max_clips,
1557            max_volume: $max_volume,
1558            initial_volume: $initial_volume,
1559            fields: [ $($($rest)*)? ]
1560        }
1561    };
1562
1563    (@__fill_defaults
1564        vis: $vis:vis,
1565        name: $name:ident,
1566        data_pin: $data_pin:tt,
1567        bit_clock_pin: $bit_clock_pin:tt,
1568        word_select_pin: $word_select_pin:tt,
1569        sample_rate_hz: $sample_rate_hz:expr,
1570        pio: $pio:ident,
1571        dma: $dma:ident,
1572        max_clips: $max_clips:expr,
1573        max_volume: $max_volume:expr,
1574        initial_volume: $initial_volume:expr,
1575        fields: [ max_clips: $max_clips_value:expr $(, $($rest:tt)* )? ]
1576    ) => {
1577        $crate::__audio_player_impl! {
1578            @__fill_defaults
1579            vis: $vis,
1580            name: $name,
1581            data_pin: $data_pin,
1582            bit_clock_pin: $bit_clock_pin,
1583            word_select_pin: $word_select_pin,
1584            sample_rate_hz: $sample_rate_hz,
1585            pio: $pio,
1586            dma: $dma,
1587            max_clips: $max_clips_value,
1588            max_volume: $max_volume,
1589            initial_volume: $initial_volume,
1590            fields: [ $($($rest)*)? ]
1591        }
1592    };
1593
1594    (@__fill_defaults
1595        vis: $vis:vis,
1596        name: $name:ident,
1597        data_pin: $data_pin:tt,
1598        bit_clock_pin: $bit_clock_pin:tt,
1599        word_select_pin: $word_select_pin:tt,
1600        sample_rate_hz: $sample_rate_hz:expr,
1601        pio: $pio:ident,
1602        dma: $dma:ident,
1603        max_clips: $max_clips:expr,
1604        max_volume: $max_volume:expr,
1605        initial_volume: $initial_volume:expr,
1606        fields: [ max_volume: $max_volume_value:expr $(, $($rest:tt)* )? ]
1607    ) => {
1608        $crate::__audio_player_impl! {
1609            @__fill_defaults
1610            vis: $vis,
1611            name: $name,
1612            data_pin: $data_pin,
1613            bit_clock_pin: $bit_clock_pin,
1614            word_select_pin: $word_select_pin,
1615            sample_rate_hz: $sample_rate_hz,
1616            pio: $pio,
1617            dma: $dma,
1618            max_clips: $max_clips,
1619            max_volume: $max_volume_value,
1620            initial_volume: $initial_volume,
1621            fields: [ $($($rest)*)? ]
1622        }
1623    };
1624
1625    (@__fill_defaults
1626        vis: $vis:vis,
1627        name: $name:ident,
1628        data_pin: $data_pin:tt,
1629        bit_clock_pin: $bit_clock_pin:tt,
1630        word_select_pin: $word_select_pin:tt,
1631        sample_rate_hz: $sample_rate_hz:expr,
1632        pio: $pio:ident,
1633        dma: $dma:ident,
1634        max_clips: $max_clips:expr,
1635        max_volume: $max_volume:expr,
1636        initial_volume: $initial_volume:expr,
1637        fields: [ initial_volume: $initial_volume_value:expr $(, $($rest:tt)* )? ]
1638    ) => {
1639        $crate::__audio_player_impl! {
1640            @__fill_defaults
1641            vis: $vis,
1642            name: $name,
1643            data_pin: $data_pin,
1644            bit_clock_pin: $bit_clock_pin,
1645            word_select_pin: $word_select_pin,
1646            sample_rate_hz: $sample_rate_hz,
1647            pio: $pio,
1648            dma: $dma,
1649            max_clips: $max_clips,
1650            max_volume: $max_volume,
1651            initial_volume: $initial_volume_value,
1652            fields: [ $($($rest)*)? ]
1653        }
1654    };
1655
1656    (@__fill_defaults
1657        vis: $vis:vis,
1658        name: $name:ident,
1659        data_pin: $data_pin:tt,
1660        bit_clock_pin: $bit_clock_pin:tt,
1661        word_select_pin: $word_select_pin:tt,
1662        sample_rate_hz: $sample_rate_hz:expr,
1663        pio: $pio:ident,
1664        dma: $dma:ident,
1665        max_clips: $max_clips:expr,
1666        max_volume: $max_volume:expr,
1667        initial_volume: $initial_volume:expr,
1668        fields: [ volume: $volume_value:expr $(, $($rest:tt)* )? ]
1669    ) => {
1670        compile_error!("audio_player! field `volume` was renamed to `max_volume`");
1671    };
1672
1673    (@__fill_defaults
1674        vis: $vis:vis,
1675        name: $name:ident,
1676        data_pin: _UNSET_,
1677        bit_clock_pin: $bit_clock_pin:tt,
1678        word_select_pin: $word_select_pin:tt,
1679        sample_rate_hz: $sample_rate_hz:expr,
1680        pio: $pio:ident,
1681        dma: $dma:ident,
1682        max_clips: $max_clips:expr,
1683        max_volume: $max_volume:expr,
1684        initial_volume: $initial_volume:expr,
1685        fields: [ ]
1686    ) => {
1687        compile_error!("audio_player! requires data_pin");
1688    };
1689
1690    (@__fill_defaults
1691        vis: $vis:vis,
1692        name: $name:ident,
1693        data_pin: $data_pin:ident,
1694        bit_clock_pin: _UNSET_,
1695        word_select_pin: $word_select_pin:tt,
1696        sample_rate_hz: $sample_rate_hz:expr,
1697        pio: $pio:ident,
1698        dma: $dma:ident,
1699        max_clips: $max_clips:expr,
1700        max_volume: $max_volume:expr,
1701        initial_volume: $initial_volume:expr,
1702        fields: [ ]
1703    ) => {
1704        compile_error!("audio_player! requires bit_clock_pin");
1705    };
1706
1707    (@__fill_defaults
1708        vis: $vis:vis,
1709        name: $name:ident,
1710        data_pin: $data_pin:ident,
1711        bit_clock_pin: $bit_clock_pin:ident,
1712        word_select_pin: _UNSET_,
1713        sample_rate_hz: $sample_rate_hz:expr,
1714        pio: $pio:ident,
1715        dma: $dma:ident,
1716        max_clips: $max_clips:expr,
1717        max_volume: $max_volume:expr,
1718        initial_volume: $initial_volume:expr,
1719        fields: [ ]
1720    ) => {
1721        compile_error!("audio_player! requires word_select_pin");
1722    };
1723
1724    (@__fill_defaults
1725        vis: $vis:vis,
1726        name: $name:ident,
1727        data_pin: $data_pin:ident,
1728        bit_clock_pin: $bit_clock_pin:ident,
1729        word_select_pin: $word_select_pin:ident,
1730        sample_rate_hz: _UNSET_,
1731        pio: $pio:ident,
1732        dma: $dma:ident,
1733        max_clips: $max_clips:expr,
1734        max_volume: $max_volume:expr,
1735        initial_volume: $initial_volume:expr,
1736        fields: [ ]
1737    ) => {
1738        compile_error!("audio_player! requires sample_rate_hz");
1739    };
1740
1741    (@__fill_defaults
1742        vis: $vis:vis,
1743        name: $name:ident,
1744        data_pin: $data_pin:ident,
1745        bit_clock_pin: $bit_clock_pin:ident,
1746        word_select_pin: $word_select_pin:ident,
1747        sample_rate_hz: $sample_rate_hz:expr,
1748        pio: $pio:ident,
1749        dma: $dma:ident,
1750        max_clips: $max_clips:expr,
1751        max_volume: $max_volume:expr,
1752        initial_volume: $initial_volume:expr,
1753        fields: [ ]
1754    ) => {
1755        $crate::audio_player::paste::paste! {
1756            static [<$name:upper _AUDIO_PLAYER_STATIC>]:
1757                $crate::audio_player::AudioPlayerStatic<$max_clips, { $sample_rate_hz }> =
1758                $crate::audio_player::AudioPlayer::<$max_clips, { $sample_rate_hz }>::new_static_with_max_volume_and_initial_volume(
1759                    $max_volume,
1760                    $initial_volume,
1761                );
1762            static [<$name:upper _AUDIO_PLAYER_CELL>]: ::static_cell::StaticCell<$name> =
1763                ::static_cell::StaticCell::new();
1764
1765            #[doc = concat!(
1766                "Audio player generated by [`audio_player!`](macro@crate::audio_player).\n\n",
1767                "See the [audio_player module documentation](mod@crate::audio_player) for usage and examples."
1768            )]
1769            $vis struct $name {
1770                player: $crate::audio_player::AudioPlayer<$max_clips, { $sample_rate_hz }>,
1771            }
1772
1773            impl $name {
1774                /// Sample rate used for audio playback by this generated player type.
1775                pub const SAMPLE_RATE_HZ: u32 = $sample_rate_hz;
1776                /// Initial runtime volume relative to [`Self::MAX_VOLUME`].
1777                pub const INITIAL_VOLUME: $crate::audio_player::Volume = $initial_volume;
1778                /// Runtime volume ceiling for this generated player type.
1779                pub const MAX_VOLUME: $crate::audio_player::Volume = $max_volume;
1780
1781                /// Returns how many samples are needed for a duration in milliseconds
1782                /// at this player's sample rate.
1783                #[must_use]
1784                pub const fn samples_ms(duration_ms: u32) -> usize {
1785                    $crate::audio_player::samples_for_duration_ms(duration_ms, Self::SAMPLE_RATE_HZ)
1786                }
1787
1788                /// Creates a silent clip at this player's sample rate.
1789                ///
1790                /// See the [audio_player module documentation](mod@crate::audio_player)
1791                /// for usage examples.
1792                #[must_use]
1793                pub const fn silence<const SAMPLE_COUNT: usize>(
1794                ) -> $crate::audio_player::AudioClipBuf<{ Self::SAMPLE_RATE_HZ }, SAMPLE_COUNT> {
1795                    $crate::audio_player::AudioClipBuf::silence()
1796                }
1797
1798                /// Creates a sine-wave clip at this player's sample rate.
1799                ///
1800                /// See the [audio_player module documentation](mod@crate::audio_player)
1801                /// for usage examples.
1802                #[must_use]
1803                pub const fn tone<const SAMPLE_COUNT: usize>(
1804                    frequency_hz: u32,
1805                ) -> $crate::audio_player::AudioClipBuf<{ Self::SAMPLE_RATE_HZ }, SAMPLE_COUNT> {
1806                    $crate::audio_player::AudioClipBuf::tone(frequency_hz)
1807                }
1808
1809                /// Creates and spawns the generated audio player instance.
1810                ///
1811                /// See the [audio_player module documentation](mod@crate::audio_player)
1812                /// for example usage.
1813                pub fn new(
1814                    data_pin: impl Into<::embassy_rp::Peri<'static, ::embassy_rp::peripherals::$data_pin>>,
1815                    bit_clock_pin: impl Into<::embassy_rp::Peri<'static, ::embassy_rp::peripherals::$bit_clock_pin>>,
1816                    word_select_pin: impl Into<::embassy_rp::Peri<'static, ::embassy_rp::peripherals::$word_select_pin>>,
1817                    pio: impl Into<::embassy_rp::Peri<'static, ::embassy_rp::peripherals::$pio>>,
1818                    dma: impl Into<::embassy_rp::Peri<'static, ::embassy_rp::peripherals::$dma>>,
1819                    spawner: ::embassy_executor::Spawner,
1820                ) -> $crate::Result<&'static Self> {
1821                    let token = [<$name:snake _audio_player_task>](
1822                        &[<$name:upper _AUDIO_PLAYER_STATIC>],
1823                        pio.into(),
1824                        dma.into(),
1825                        data_pin.into(),
1826                        bit_clock_pin.into(),
1827                        word_select_pin.into(),
1828                    );
1829                    spawner.spawn(token)?;
1830                    let player =
1831                        $crate::audio_player::AudioPlayer::new(&[<$name:upper _AUDIO_PLAYER_STATIC>]);
1832                    Ok([<$name:upper _AUDIO_PLAYER_CELL>].init(Self { player }))
1833                }
1834            }
1835
1836            impl ::core::ops::Deref for $name {
1837                type Target = $crate::audio_player::AudioPlayer<$max_clips, { $sample_rate_hz }>;
1838
1839                fn deref(&self) -> &Self::Target {
1840                    &self.player
1841                }
1842            }
1843
1844            #[::embassy_executor::task]
1845            async fn [<$name:snake _audio_player_task>](
1846                audio_player_static: &'static $crate::audio_player::AudioPlayerStatic<$max_clips, { $sample_rate_hz }>,
1847                pio: ::embassy_rp::Peri<'static, ::embassy_rp::peripherals::$pio>,
1848                dma: ::embassy_rp::Peri<'static, ::embassy_rp::peripherals::$dma>,
1849                data_pin: ::embassy_rp::Peri<'static, ::embassy_rp::peripherals::$data_pin>,
1850                bit_clock_pin: ::embassy_rp::Peri<'static, ::embassy_rp::peripherals::$bit_clock_pin>,
1851                word_select_pin: ::embassy_rp::Peri<'static, ::embassy_rp::peripherals::$word_select_pin>,
1852            ) -> ! {
1853                $crate::audio_player::device_loop::<
1854                    $max_clips,
1855                    { $sample_rate_hz },
1856                    ::embassy_rp::peripherals::$pio,
1857                    ::embassy_rp::peripherals::$dma,
1858                    ::embassy_rp::peripherals::$data_pin,
1859                    ::embassy_rp::peripherals::$bit_clock_pin,
1860                    ::embassy_rp::peripherals::$word_select_pin,
1861                >(audio_player_static, pio, dma, data_pin, bit_clock_pin, word_select_pin).await
1862            }
1863        }
1864    };
1865}
1866
1867#[doc(inline)]
1868pub use audio_clip;
1869#[doc(inline)]
1870pub use audio_player;
1871#[doc(inline)]
1872pub use samples_ms;