1pub 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
213pub const NARROWBAND_8000_HZ: u32 = 8_000;
216pub const VOICE_16000_HZ: u32 = 16_000;
218pub const VOICE_22050_HZ: u32 = 22_050;
223pub const CD_44100_HZ: u32 = 44_100;
225pub const PRO_48000_HZ: u32 = 48_000;
227
228#[derive(Clone, Copy, Debug, PartialEq, Eq)]
241pub struct Volume(i16);
242
243impl Volume {
244 pub const MUTE: Self = Self(0);
246
247 pub const MAX: Self = Self(i16::MAX);
249
250 #[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 #[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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
324pub struct Gain(i32);
325
326impl Gain {
327 pub const MUTE: Self = Self(0);
329
330 #[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 #[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 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#[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 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 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
468pub enum AtEnd {
475 Loop,
477 Stop,
479}
480
481pub 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 pub const SAMPLE_RATE_HZ: u32 = SAMPLE_RATE_HZ;
494}
495
496impl<const SAMPLE_RATE_HZ: u32> AudioClip<SAMPLE_RATE_HZ> {
497 #[must_use]
499 pub const fn samples(&self) -> &[i16] {
500 &self.samples
501 }
502
503 #[must_use]
508 pub const fn sample_count(&self) -> usize {
509 self.samples.len()
510 }
511}
512
513pub type AudioClipBuf<const SAMPLE_RATE_HZ: u32, const SAMPLE_COUNT: usize> =
525 AudioClip<SAMPLE_RATE_HZ, [i16; SAMPLE_COUNT]>;
526
527impl<const SAMPLE_RATE_HZ: u32, const SAMPLE_COUNT: usize>
533 AudioClip<SAMPLE_RATE_HZ, [i16; SAMPLE_COUNT]>
534{
535 #[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 #[must_use]
549 pub const fn samples(&self) -> &[i16; SAMPLE_COUNT] {
550 &self.samples
551 }
552
553 pub const SAMPLE_COUNT: usize = SAMPLE_COUNT;
558
559 #[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 #[must_use]
590 pub const fn silence() -> Self {
591 Self::new([0; SAMPLE_COUNT])
592 }
593
594 #[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#[doc(hidden)]
619pub trait IntoAudioClip<const SAMPLE_RATE_HZ: u32> {
620 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#[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 #[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 #[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 #[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#[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 #[must_use]
721 pub const fn new_static() -> AudioPlayerStatic<MAX_CLIPS, SAMPLE_RATE_HZ> {
722 AudioPlayerStatic::new_static()
723 }
724
725 #[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 #[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 #[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 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 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 pub fn stop(&self) {
810 self.audio_player_static.signal(AudioCommand::Stop);
811 }
812
813 pub fn set_volume(&self, volume: Volume) {
824 self.audio_player_static.set_runtime_volume(volume);
825 }
826
827 #[must_use]
832 pub fn volume(&self) -> Volume {
833 self.audio_player_static.runtime_volume()
834 }
835}
836
837#[doc(hidden)]
840pub trait AudioPlayerPio: crate::pio_irqs::PioIrqMap {}
841
842impl<PioResource: crate::pio_irqs::PioIrqMap> AudioPlayerPio for PioResource {}
843
844#[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#[doc(hidden)]
980pub use paste;
981
982#[derive(Clone, Copy, Debug, Eq, PartialEq)]
989pub enum AudioFormat {
990 S16le,
992}
993
994#[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#[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#[doc(hidden)]
1324#[macro_export]
1325macro_rules! audio_player {
1326 ($($tt:tt)*) => { $crate::__audio_player_impl! { $($tt)* } };
1327}
1328
1329#[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 pub const SAMPLE_RATE_HZ: u32 = $sample_rate_hz;
1776 pub const INITIAL_VOLUME: $crate::audio_player::Volume = $initial_volume;
1778 pub const MAX_VOLUME: $crate::audio_player::Volume = $max_volume;
1780
1781 #[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 #[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 #[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 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;