#![cfg_attr(not(target_os = "none"), allow(dead_code))]
pub mod adpcm_clip_generated;
pub mod audio_player_generated;
pub mod pcm_clip_generated;
#[doc = "Macro to \"compile in\" a compressed (ADPCM) WAV clip from an external file (includes syntax details)."]
#[doc = include_str!("audio_player/adpcm_clip_docs.md")]
#[doc = include_str!("audio_player/audio_prep_steps_1_2.md")]
#[doc = include_str!("audio_player/adpcm_clip_step_3.md")]
#[doc(inline)]
pub use device_envoy_core::audio_player::adpcm_clip;
#[doc = "Macro to \"compile in\" an uncompressed (PCM) clip from an external file (includes syntax details)."]
#[doc = include_str!("audio_player/pcm_clip_docs.md")]
#[doc = include_str!("audio_player/audio_prep_steps_1_2.md")]
#[doc = include_str!("audio_player/pcm_clip_step_3.md")]
#[doc(inline)]
pub use device_envoy_core::audio_player::pcm_clip;
pub use device_envoy_core::audio_player::*;
#[cfg(target_os = "none")]
use embassy_futures::yield_now;
#[cfg(target_os = "none")]
use esp_hal::{
dma::DmaChannelFor,
gpio::interconnect::PeripheralOutput,
i2s::{
AnyI2s,
master::{Channels, Config, DataFormat, I2s},
},
time::Rate,
};
#[cfg(target_os = "none")]
use log::warn;
#[cfg(target_os = "none")]
const SAMPLE_BUFFER_LEN: usize = 1024;
const DMA_TX_BYTES: usize = 16384;
#[cfg(target_os = "none")]
const MAX_ZERO_PUSH_STREAK: usize = 4096;
#[cfg(target_os = "none")]
const I2S_PUSH_CHUNK_BYTES: usize = 256;
#[cfg(target_os = "none")]
type AudioI2sTxTransfer = esp_hal::i2s::master::asynch::I2sWriteDmaTransferAsync<
'static,
&'static mut [u8; DMA_TX_BYTES],
>;
#[doc(hidden)]
pub struct AudioPlayerEsp<const MAX_CLIPS: usize, const SAMPLE_RATE_HZ: u32> {
audio_player_static: &'static AudioPlayerStatic<MAX_CLIPS, SAMPLE_RATE_HZ>,
}
impl<const MAX_CLIPS: usize, const SAMPLE_RATE_HZ: u32> AudioPlayerEsp<MAX_CLIPS, SAMPLE_RATE_HZ> {
#[doc(hidden)]
pub const fn new_static() -> AudioPlayerStatic<MAX_CLIPS, SAMPLE_RATE_HZ> {
AudioPlayerStatic::new_static()
}
#[doc(hidden)]
pub const fn new_static_with_max_volume(
max_volume: Volume,
) -> AudioPlayerStatic<MAX_CLIPS, SAMPLE_RATE_HZ> {
AudioPlayerStatic::new_static_with_max_volume(max_volume)
}
#[doc(hidden)]
pub const fn new_static_with_max_volume_and_initial_volume(
max_volume: Volume,
initial_volume: Volume,
) -> AudioPlayerStatic<MAX_CLIPS, SAMPLE_RATE_HZ> {
AudioPlayerStatic::new_static_with_max_volume_and_initial_volume(max_volume, initial_volume)
}
#[doc(hidden)]
#[must_use]
pub const fn new(
audio_player_static: &'static AudioPlayerStatic<MAX_CLIPS, SAMPLE_RATE_HZ>,
) -> Self {
Self {
audio_player_static,
}
}
#[doc(hidden)]
pub fn __audio_player_static(&self) -> &'static AudioPlayerStatic<MAX_CLIPS, SAMPLE_RATE_HZ> {
self.audio_player_static
}
}
#[cfg(target_os = "none")]
struct EspAudioOutputSink<'a> {
i2s_tx_transfer: &'a mut AudioI2sTxTransfer,
}
#[cfg(target_os = "none")]
impl AudioOutputSink<SAMPLE_BUFFER_LEN> for EspAudioOutputSink<'_> {
async fn write_stereo_words(
&mut self,
stereo_words: &[u32; SAMPLE_BUFFER_LEN],
stereo_word_count: usize,
) -> core::result::Result<(), ()> {
write_words_to_i2s_with_recovery(self.i2s_tx_transfer, stereo_words, stereo_word_count)
.await
}
async fn after_write(&mut self) {
yield_now().await;
}
}
#[cfg(target_os = "none")]
#[doc(hidden)]
pub async fn device_loop<
const MAX_CLIPS: usize,
const SAMPLE_RATE_HZ: u32,
Dma: DmaChannelFor<AnyI2s<'static>>,
DataPin: PeripheralOutput<'static>,
BitBlockPin: PeripheralOutput<'static>,
WordSelectPin: PeripheralOutput<'static>,
>(
audio_player_static: &'static AudioPlayerStatic<MAX_CLIPS, SAMPLE_RATE_HZ>,
i2s: esp_hal::peripherals::I2S0<'static>,
dma: Dma,
data_pin: DataPin,
bit_clock_pin: BitBlockPin,
word_select_pin: WordSelectPin,
) -> ! {
let i2s = I2s::new(
i2s,
dma,
Config::new_tdm_philips()
.with_sample_rate(Rate::from_hz(SAMPLE_RATE_HZ))
.with_data_format(DataFormat::Data16Channel16)
.with_channels(Channels::STEREO),
)
.expect("I2S init failed")
.into_async();
let (_, _, tx_buffer, tx_descriptors) = esp_hal::dma_circular_buffers!(0, DMA_TX_BYTES);
let mut i2s_tx_transfer = i2s
.i2s_tx
.with_bclk(bit_clock_pin)
.with_ws(word_select_pin)
.with_dout(data_pin)
.build(tx_descriptors)
.write_dma_circular_async(tx_buffer)
.expect("I2S circular DMA setup failed");
let _ = fill_dma_ring_with_silence(&mut i2s_tx_transfer).await;
let mut sample_buffer = [0_u32; SAMPLE_BUFFER_LEN];
loop {
let mut audio_command = wait_for_audio_command_while_feeding_silence(
&mut i2s_tx_transfer,
&mut sample_buffer,
audio_player_static,
)
.await;
loop {
match audio_command {
AudioCommand::Play {
audio_clips,
at_end,
} => {
audio_player_static.mark_playing();
let next_audio_command = match at_end {
AtEnd::Loop => loop {
let mut esp_audio_output_sink = EspAudioOutputSink {
i2s_tx_transfer: &mut i2s_tx_transfer,
};
if let Some(next_audio_command) = play_clip_sequence_once(
&mut esp_audio_output_sink,
&audio_clips,
&mut sample_buffer,
audio_player_static,
)
.await
{
break Some(next_audio_command);
}
},
AtEnd::Stop => {
let mut esp_audio_output_sink = EspAudioOutputSink {
i2s_tx_transfer: &mut i2s_tx_transfer,
};
play_clip_sequence_once(
&mut esp_audio_output_sink,
&audio_clips,
&mut sample_buffer,
audio_player_static,
)
.await
}
};
if let Some(next_audio_command) = next_audio_command {
audio_command = next_audio_command;
continue;
}
if clear_i2s_output_after_stop::<SAMPLE_RATE_HZ>(&mut i2s_tx_transfer)
.await
.is_err()
{
warn!(
"audio_player: post-stop clear exhausted retries (sample_rate_hz={})",
SAMPLE_RATE_HZ
);
}
audio_player_static.mark_stopped();
}
AudioCommand::Stop => {
if clear_i2s_output_after_stop::<SAMPLE_RATE_HZ>(&mut i2s_tx_transfer)
.await
.is_err()
{
warn!(
"audio_player: explicit-stop clear exhausted retries (sample_rate_hz={})",
SAMPLE_RATE_HZ
);
}
audio_player_static.mark_stopped();
}
}
break;
}
}
}
#[cfg(target_os = "none")]
async fn wait_for_audio_command_while_feeding_silence<
const MAX_CLIPS: usize,
const SAMPLE_RATE_HZ: u32,
>(
i2s_tx_transfer: &mut AudioI2sTxTransfer,
_sample_buffer: &mut [u32; SAMPLE_BUFFER_LEN],
audio_player_static: &'static AudioPlayerStatic<MAX_CLIPS, SAMPLE_RATE_HZ>,
) -> AudioCommand<MAX_CLIPS, SAMPLE_RATE_HZ> {
loop {
if let Some(audio_command) = audio_player_static.try_take_command() {
return audio_command;
}
if fill_dma_ring_with_silence(i2s_tx_transfer).await.is_err() {
yield_now().await;
}
}
}
#[cfg(target_os = "none")]
async fn write_words_to_i2s(
i2s_tx_transfer: &mut AudioI2sTxTransfer,
sample_words: &[u32; SAMPLE_BUFFER_LEN],
sample_word_count: usize,
) -> Result<(), ()> {
let byte_count = sample_word_count * 4;
let mut sample_bytes = [0_u8; SAMPLE_BUFFER_LEN * 4];
for (write_index, sample_word_ref) in sample_words[..sample_word_count].iter().enumerate() {
let sample_bytes_le = sample_word_ref.to_le_bytes();
let byte_offset = write_index * 4;
sample_bytes[byte_offset..byte_offset + 4].copy_from_slice(&sample_bytes_le);
}
let mut write_index = 0usize;
let mut zero_push_streak = 0usize;
while write_index < byte_count {
let remaining_byte_count = byte_count - write_index;
let push_byte_count = remaining_byte_count.min(I2S_PUSH_CHUNK_BYTES);
let pushed_byte_count = match i2s_tx_transfer
.push(&sample_bytes[write_index..write_index + push_byte_count])
.await
{
Ok(pushed_byte_count) => pushed_byte_count,
Err(_) => return Err(()),
};
if pushed_byte_count == 0 {
zero_push_streak += 1;
if zero_push_streak >= MAX_ZERO_PUSH_STREAK {
return Err(());
}
yield_now().await;
continue;
}
zero_push_streak = 0;
write_index += pushed_byte_count;
}
Ok(())
}
#[cfg(target_os = "none")]
async fn write_words_to_i2s_with_recovery(
i2s_tx_transfer: &mut AudioI2sTxTransfer,
sample_words: &[u32; SAMPLE_BUFFER_LEN],
sample_word_count: usize,
) -> Result<(), ()> {
const MAX_RECOVERY_ATTEMPTS: usize = 3;
for _recovery_attempt in 0..MAX_RECOVERY_ATTEMPTS {
if write_words_to_i2s(i2s_tx_transfer, sample_words, sample_word_count)
.await
.is_ok()
{
return Ok(());
}
let _ = fill_dma_ring_with_silence(i2s_tx_transfer).await;
yield_now().await;
}
Err(())
}
#[cfg(target_os = "none")]
async fn fill_dma_ring_with_silence(i2s_tx_transfer: &mut AudioI2sTxTransfer) -> Result<(), ()> {
let silence_bytes = [0_u8; DMA_TX_BYTES];
let mut write_index = 0usize;
let mut zero_push_streak = 0usize;
while write_index < DMA_TX_BYTES {
let remaining_byte_count = DMA_TX_BYTES - write_index;
let push_byte_count = remaining_byte_count.min(I2S_PUSH_CHUNK_BYTES);
let pushed_byte_count = match i2s_tx_transfer
.push(&silence_bytes[write_index..write_index + push_byte_count])
.await
{
Ok(pushed_byte_count) => pushed_byte_count,
Err(_) => return Err(()),
};
if pushed_byte_count == 0 {
zero_push_streak += 1;
if zero_push_streak >= MAX_ZERO_PUSH_STREAK {
return Err(());
}
yield_now().await;
continue;
}
zero_push_streak = 0;
write_index += pushed_byte_count;
}
Ok(())
}
#[cfg(target_os = "none")]
async fn flush_silence_after_stop<const SAMPLE_RATE_HZ: u32>(
i2s_tx_transfer: &mut AudioI2sTxTransfer,
) -> Result<(), ()> {
const HIGH_RATE_FLUSH_ROUNDS: usize = 16;
const LOW_RATE_FLUSH_ROUNDS: usize = 4;
let silence_words = [stereo_sample(0); SAMPLE_BUFFER_LEN];
let silence_word_count = SAMPLE_BUFFER_LEN;
let flush_round_count = if SAMPLE_RATE_HZ >= 22_050 {
HIGH_RATE_FLUSH_ROUNDS
} else {
LOW_RATE_FLUSH_ROUNDS
};
let mut flush_round_index = 0usize;
while flush_round_index < flush_round_count {
write_words_to_i2s_with_recovery(i2s_tx_transfer, &silence_words, silence_word_count)
.await?;
yield_now().await;
flush_round_index += 1;
}
Ok(())
}
#[cfg(target_os = "none")]
async fn clear_i2s_output_after_stop<const SAMPLE_RATE_HZ: u32>(
i2s_tx_transfer: &mut AudioI2sTxTransfer,
) -> Result<(), ()> {
const MAX_CLEAR_ATTEMPTS: usize = 32;
let mut clear_attempt_index = 0usize;
while clear_attempt_index < MAX_CLEAR_ATTEMPTS {
let fill_silence_result = fill_dma_ring_with_silence(i2s_tx_transfer).await;
let flush_silence_result = if fill_silence_result.is_ok() {
flush_silence_after_stop::<SAMPLE_RATE_HZ>(i2s_tx_transfer).await
} else {
Err(())
};
if fill_silence_result.is_ok() && flush_silence_result.is_ok() {
return Ok(());
}
yield_now().await;
clear_attempt_index += 1;
}
Err(())
}
#[doc(hidden)]
#[macro_export]
macro_rules! audio_player {
($($tt:tt)*) => { $crate::__audio_player_impl! { $($tt)* } };
}
#[doc(hidden)]
#[macro_export]
macro_rules! __audio_player_impl {
(
$name:ident {
$($fields:tt)*
}
) => {
$crate::__validate_keyword_fields_expr! {
macro_name: "audio_player!",
allowed_macro: $crate::__audio_player_allowed_field,
fields: [ $($fields)* ]
}
$crate::__audio_player_impl! {
@__fill_defaults
vis: pub(self),
name: $name,
data_pin: _UNSET_,
bit_clock_pin: _UNSET_,
word_select_pin: _UNSET_,
sample_rate_hz: _UNSET_,
i2s: I2S0,
dma: _UNSET_,
max_clips: 16,
max_volume: $crate::audio_player::Volume::MAX,
initial_volume: $crate::audio_player::Volume::MAX,
fields: [ $($fields)* ]
}
};
(
$vis:vis $name:ident {
$($fields:tt)*
}
) => {
$crate::__validate_keyword_fields_expr! {
macro_name: "audio_player!",
allowed_macro: $crate::__audio_player_allowed_field,
fields: [ $($fields)* ]
}
$crate::__audio_player_impl! {
@__fill_defaults
vis: $vis,
name: $name,
data_pin: _UNSET_,
bit_clock_pin: _UNSET_,
word_select_pin: _UNSET_,
sample_rate_hz: _UNSET_,
i2s: I2S0,
dma: _UNSET_,
max_clips: 16,
max_volume: $crate::audio_player::Volume::MAX,
initial_volume: $crate::audio_player::Volume::MAX,
fields: [ $($fields)* ]
}
};
(@__fill_defaults
vis: $vis:vis,
name: $name:ident,
data_pin: $data_pin:tt,
bit_clock_pin: $bit_clock_pin:tt,
word_select_pin: $word_select_pin:tt,
sample_rate_hz: $sample_rate_hz:expr,
i2s: $i2s:ident,
dma: $dma:ident,
max_clips: $max_clips:expr,
max_volume: $max_volume:expr,
initial_volume: $initial_volume:expr,
fields: [ data_pin: $din_pin_value:ident $(, $($rest:tt)* )? ]
) => {
$crate::__audio_player_impl! {
@__fill_defaults
vis: $vis,
name: $name,
data_pin: $din_pin_value,
bit_clock_pin: $bit_clock_pin,
word_select_pin: $word_select_pin,
sample_rate_hz: $sample_rate_hz,
i2s: $i2s,
dma: $dma,
max_clips: $max_clips,
max_volume: $max_volume,
initial_volume: $initial_volume,
fields: [ $($($rest)*)? ]
}
};
(@__fill_defaults
vis: $vis:vis,
name: $name:ident,
data_pin: $data_pin:tt,
bit_clock_pin: $bit_clock_pin:tt,
word_select_pin: $word_select_pin:tt,
sample_rate_hz: $sample_rate_hz:expr,
i2s: $i2s:ident,
dma: $dma:ident,
max_clips: $max_clips:expr,
max_volume: $max_volume:expr,
initial_volume: $initial_volume:expr,
fields: [ sample_rate_hz: $sample_rate_hz_value:expr $(, $($rest:tt)* )? ]
) => {
$crate::__audio_player_impl! {
@__fill_defaults
vis: $vis,
name: $name,
data_pin: $data_pin,
bit_clock_pin: $bit_clock_pin,
word_select_pin: $word_select_pin,
sample_rate_hz: $sample_rate_hz_value,
i2s: $i2s,
dma: $dma,
max_clips: $max_clips,
max_volume: $max_volume,
initial_volume: $initial_volume,
fields: [ $($($rest)*)? ]
}
};
(@__fill_defaults
vis: $vis:vis,
name: $name:ident,
data_pin: $data_pin:tt,
bit_clock_pin: $bit_clock_pin:tt,
word_select_pin: $word_select_pin:tt,
sample_rate_hz: $sample_rate_hz:expr,
i2s: $i2s:ident,
dma: $dma:ident,
max_clips: $max_clips:expr,
max_volume: $max_volume:expr,
initial_volume: $initial_volume:expr,
fields: [ bit_clock_pin: $bclk_pin_value:ident $(, $($rest:tt)* )? ]
) => {
$crate::__audio_player_impl! {
@__fill_defaults
vis: $vis,
name: $name,
data_pin: $data_pin,
bit_clock_pin: $bclk_pin_value,
word_select_pin: $word_select_pin,
sample_rate_hz: $sample_rate_hz,
i2s: $i2s,
dma: $dma,
max_clips: $max_clips,
max_volume: $max_volume,
initial_volume: $initial_volume,
fields: [ $($($rest)*)? ]
}
};
(@__fill_defaults
vis: $vis:vis,
name: $name:ident,
data_pin: $data_pin:tt,
bit_clock_pin: $bit_clock_pin:tt,
word_select_pin: $word_select_pin:tt,
sample_rate_hz: $sample_rate_hz:expr,
i2s: $i2s:ident,
dma: $dma:ident,
max_clips: $max_clips:expr,
max_volume: $max_volume:expr,
initial_volume: $initial_volume:expr,
fields: [ word_select_pin: $lrc_pin_value:ident $(, $($rest:tt)* )? ]
) => {
$crate::__audio_player_impl! {
@__fill_defaults
vis: $vis,
name: $name,
data_pin: $data_pin,
bit_clock_pin: $bit_clock_pin,
word_select_pin: $lrc_pin_value,
sample_rate_hz: $sample_rate_hz,
i2s: $i2s,
dma: $dma,
max_clips: $max_clips,
max_volume: $max_volume,
initial_volume: $initial_volume,
fields: [ $($($rest)*)? ]
}
};
(@__fill_defaults
vis: $vis:vis,
name: $name:ident,
data_pin: $data_pin:tt,
bit_clock_pin: $bit_clock_pin:tt,
word_select_pin: $word_select_pin:tt,
sample_rate_hz: $sample_rate_hz:expr,
i2s: $i2s:ident,
dma: $dma:ident,
max_clips: $max_clips:expr,
max_volume: $max_volume:expr,
initial_volume: $initial_volume:expr,
fields: [ i2s: $i2s_value:ident $(, $($rest:tt)* )? ]
) => {
$crate::__audio_player_impl! {
@__fill_defaults
vis: $vis,
name: $name,
data_pin: $data_pin,
bit_clock_pin: $bit_clock_pin,
word_select_pin: $word_select_pin,
sample_rate_hz: $sample_rate_hz,
i2s: $i2s_value,
dma: $dma,
max_clips: $max_clips,
max_volume: $max_volume,
initial_volume: $initial_volume,
fields: [ $($($rest)*)? ]
}
};
(@__fill_defaults
vis: $vis:vis,
name: $name:ident,
data_pin: $data_pin:tt,
bit_clock_pin: $bit_clock_pin:tt,
word_select_pin: $word_select_pin:tt,
sample_rate_hz: $sample_rate_hz:expr,
i2s: $i2s:ident,
dma: $dma:ident,
max_clips: $max_clips:expr,
max_volume: $max_volume:expr,
initial_volume: $initial_volume:expr,
fields: [ dma: $dma_value:ident $(, $($rest:tt)* )? ]
) => {
$crate::__audio_player_impl! {
@__fill_defaults
vis: $vis,
name: $name,
data_pin: $data_pin,
bit_clock_pin: $bit_clock_pin,
word_select_pin: $word_select_pin,
sample_rate_hz: $sample_rate_hz,
i2s: $i2s,
dma: $dma_value,
max_clips: $max_clips,
max_volume: $max_volume,
initial_volume: $initial_volume,
fields: [ $($($rest)*)? ]
}
};
(@__fill_defaults
vis: $vis:vis,
name: $name:ident,
data_pin: $data_pin:tt,
bit_clock_pin: $bit_clock_pin:tt,
word_select_pin: $word_select_pin:tt,
sample_rate_hz: $sample_rate_hz:expr,
i2s: $i2s:ident,
dma: $dma:ident,
max_clips: $max_clips:expr,
max_volume: $max_volume:expr,
initial_volume: $initial_volume:expr,
fields: [ max_clips: $max_clips_value:expr $(, $($rest:tt)* )? ]
) => {
$crate::__audio_player_impl! {
@__fill_defaults
vis: $vis,
name: $name,
data_pin: $data_pin,
bit_clock_pin: $bit_clock_pin,
word_select_pin: $word_select_pin,
sample_rate_hz: $sample_rate_hz,
i2s: $i2s,
dma: $dma,
max_clips: $max_clips_value,
max_volume: $max_volume,
initial_volume: $initial_volume,
fields: [ $($($rest)*)? ]
}
};
(@__fill_defaults
vis: $vis:vis,
name: $name:ident,
data_pin: $data_pin:tt,
bit_clock_pin: $bit_clock_pin:tt,
word_select_pin: $word_select_pin:tt,
sample_rate_hz: $sample_rate_hz:expr,
i2s: $i2s:ident,
dma: $dma:ident,
max_clips: $max_clips:expr,
max_volume: $max_volume:expr,
initial_volume: $initial_volume:expr,
fields: [ max_volume: $max_volume_value:expr $(, $($rest:tt)* )? ]
) => {
$crate::__audio_player_impl! {
@__fill_defaults
vis: $vis,
name: $name,
data_pin: $data_pin,
bit_clock_pin: $bit_clock_pin,
word_select_pin: $word_select_pin,
sample_rate_hz: $sample_rate_hz,
i2s: $i2s,
dma: $dma,
max_clips: $max_clips,
max_volume: $max_volume_value,
initial_volume: $initial_volume,
fields: [ $($($rest)*)? ]
}
};
(@__fill_defaults
vis: $vis:vis,
name: $name:ident,
data_pin: $data_pin:tt,
bit_clock_pin: $bit_clock_pin:tt,
word_select_pin: $word_select_pin:tt,
sample_rate_hz: $sample_rate_hz:expr,
i2s: $i2s:ident,
dma: $dma:ident,
max_clips: $max_clips:expr,
max_volume: $max_volume:expr,
initial_volume: $initial_volume:expr,
fields: [ initial_volume: $initial_volume_value:expr $(, $($rest:tt)* )? ]
) => {
$crate::__audio_player_impl! {
@__fill_defaults
vis: $vis,
name: $name,
data_pin: $data_pin,
bit_clock_pin: $bit_clock_pin,
word_select_pin: $word_select_pin,
sample_rate_hz: $sample_rate_hz,
i2s: $i2s,
dma: $dma,
max_clips: $max_clips,
max_volume: $max_volume,
initial_volume: $initial_volume_value,
fields: [ $($($rest)*)? ]
}
};
(@__fill_defaults
vis: $vis:vis,
name: $name:ident,
data_pin: _UNSET_,
bit_clock_pin: $bit_clock_pin:tt,
word_select_pin: $word_select_pin:tt,
sample_rate_hz: $sample_rate_hz:expr,
i2s: $i2s:ident,
dma: $dma:ident,
max_clips: $max_clips:expr,
max_volume: $max_volume:expr,
initial_volume: $initial_volume:expr,
fields: [ ]
) => {
compile_error!("audio_player! requires data_pin");
};
(@__fill_defaults
vis: $vis:vis,
name: $name:ident,
data_pin: $data_pin:ident,
bit_clock_pin: _UNSET_,
word_select_pin: $word_select_pin:tt,
sample_rate_hz: $sample_rate_hz:expr,
i2s: $i2s:ident,
dma: $dma:ident,
max_clips: $max_clips:expr,
max_volume: $max_volume:expr,
initial_volume: $initial_volume:expr,
fields: [ ]
) => {
compile_error!("audio_player! requires bit_clock_pin");
};
(@__fill_defaults
vis: $vis:vis,
name: $name:ident,
data_pin: $data_pin:ident,
bit_clock_pin: $bit_clock_pin:ident,
word_select_pin: _UNSET_,
sample_rate_hz: $sample_rate_hz:expr,
i2s: $i2s:ident,
dma: $dma:ident,
max_clips: $max_clips:expr,
max_volume: $max_volume:expr,
initial_volume: $initial_volume:expr,
fields: [ ]
) => {
compile_error!("audio_player! requires word_select_pin");
};
(@__fill_defaults
vis: $vis:vis,
name: $name:ident,
data_pin: $data_pin:ident,
bit_clock_pin: $bit_clock_pin:ident,
word_select_pin: $word_select_pin:ident,
sample_rate_hz: _UNSET_,
i2s: $i2s:ident,
dma: $dma:ident,
max_clips: $max_clips:expr,
max_volume: $max_volume:expr,
initial_volume: $initial_volume:expr,
fields: [ ]
) => {
compile_error!("audio_player! requires sample_rate_hz");
};
(@__fill_defaults
vis: $vis:vis,
name: $name:ident,
data_pin: $data_pin:ident,
bit_clock_pin: $bit_clock_pin:ident,
word_select_pin: $word_select_pin:ident,
sample_rate_hz: $sample_rate_hz:expr,
i2s: $i2s:ident,
dma: _UNSET_,
max_clips: $max_clips:expr,
max_volume: $max_volume:expr,
initial_volume: $initial_volume:expr,
fields: [ ]
) => {
compile_error!("audio_player! requires dma");
};
(@__fill_defaults
vis: $vis:vis,
name: $name:ident,
data_pin: $data_pin:ident,
bit_clock_pin: $bit_clock_pin:ident,
word_select_pin: $word_select_pin:ident,
sample_rate_hz: $sample_rate_hz:expr,
i2s: $i2s:ident,
dma: $dma:ident,
max_clips: $max_clips:expr,
max_volume: $max_volume:expr,
initial_volume: $initial_volume:expr,
fields: [ ]
) => {
$crate::__paste! {
static [<$name:upper _AUDIO_PLAYER_STATIC>]:
$crate::audio_player::AudioPlayerStatic<$max_clips, { $sample_rate_hz }> =
$crate::audio_player::AudioPlayerEsp::<$max_clips, { $sample_rate_hz }>::new_static_with_max_volume_and_initial_volume(
$max_volume,
$initial_volume,
);
static [<$name:upper _AUDIO_PLAYER_CELL>]: ::static_cell::StaticCell<$name> =
::static_cell::StaticCell::new();
#[doc = concat!(
"Audio player generated by [`audio_player!`](macro@crate::audio_player).\n\n",
"See the [audio_player module documentation](mod@crate::audio_player) for usage and examples."
)]
$vis struct $name {
player: $crate::audio_player::AudioPlayerEsp<$max_clips, { $sample_rate_hz }>,
}
#[doc = concat!(
"Trait-object clip source type at [`",
stringify!($name),
"::SAMPLE_RATE_HZ`](struct@",
stringify!($name),
").\n\n",
"Use this in signatures like `&'static ",
stringify!([<$name Playable>]),
"` instead of repeating `dyn Playable<{ ",
stringify!($name),
"::SAMPLE_RATE_HZ }>`."
)]
$vis type [<$name Playable>] =
dyn $crate::audio_player::Playable<{ $sample_rate_hz }>;
impl $name {
pub const SAMPLE_RATE_HZ: u32 = $sample_rate_hz;
pub const MAX_CLIPS: usize = $max_clips;
pub const INITIAL_VOLUME: $crate::audio_player::Volume = $initial_volume;
pub const MAX_VOLUME: $crate::audio_player::Volume = $max_volume;
pub fn new(
data_pin: $crate::esp_hal::peripherals::$data_pin<'static>,
bit_clock_pin: $crate::esp_hal::peripherals::$bit_clock_pin<'static>,
word_select_pin: $crate::esp_hal::peripherals::$word_select_pin<'static>,
i2s: $crate::esp_hal::peripherals::$i2s<'static>,
dma: $crate::esp_hal::peripherals::$dma<'static>,
spawner: ::embassy_executor::Spawner,
) -> $crate::Result<&'static Self> {
let token = [<$name:snake _audio_player_task>](
&[<$name:upper _AUDIO_PLAYER_STATIC>],
i2s,
dma,
data_pin,
bit_clock_pin,
word_select_pin,
);
spawner.spawn(token?);
let player =
$crate::audio_player::AudioPlayerEsp::new(&[<$name:upper _AUDIO_PLAYER_STATIC>]);
Ok([<$name:upper _AUDIO_PLAYER_CELL>].init(Self { player }))
}
}
impl $crate::audio_player::AudioPlayer<{ $sample_rate_hz }> for $name {
const SAMPLE_RATE_HZ: u32 = $sample_rate_hz;
const MAX_CLIPS: usize = $max_clips;
const INITIAL_VOLUME: $crate::audio_player::Volume = $initial_volume;
const MAX_VOLUME: $crate::audio_player::Volume = $max_volume;
fn play<I>(&self, audio_clips: I, at_end: $crate::audio_player::AtEnd)
where
I: IntoIterator<Item = &'static dyn $crate::audio_player::Playable<{ $sample_rate_hz }>>,
{
$crate::audio_player::__audio_player_play(
self.player.__audio_player_static(),
audio_clips,
at_end,
);
}
fn stop(&self) {
$crate::audio_player::__audio_player_stop(self.player.__audio_player_static());
}
async fn wait_until_stopped(&self) {
$crate::audio_player::__audio_player_wait_until_stopped(
self.player.__audio_player_static(),
)
.await;
}
fn set_volume(&self, volume: $crate::audio_player::Volume) {
$crate::audio_player::__audio_player_set_volume(
self.player.__audio_player_static(),
volume,
);
}
fn volume(&self) -> $crate::audio_player::Volume {
$crate::audio_player::__audio_player_volume(self.player.__audio_player_static())
}
}
#[::embassy_executor::task]
async fn [<$name:snake _audio_player_task>](
audio_player_static: &'static $crate::audio_player::AudioPlayerStatic<$max_clips, { $sample_rate_hz }>,
i2s: $crate::esp_hal::peripherals::$i2s<'static>,
dma: $crate::esp_hal::peripherals::$dma<'static>,
data_pin: $crate::esp_hal::peripherals::$data_pin<'static>,
bit_clock_pin: $crate::esp_hal::peripherals::$bit_clock_pin<'static>,
word_select_pin: $crate::esp_hal::peripherals::$word_select_pin<'static>,
) -> ! {
$crate::audio_player::device_loop::<
$max_clips,
{ $sample_rate_hz },
$crate::esp_hal::peripherals::$dma<'static>,
$crate::esp_hal::peripherals::$data_pin<'static>,
$crate::esp_hal::peripherals::$bit_clock_pin<'static>,
$crate::esp_hal::peripherals::$word_select_pin<'static>,
>(audio_player_static, i2s, dma, data_pin, bit_clock_pin, word_select_pin).await
}
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __audio_player_allowed_field {
(data_pin, $macro_name:literal) => {};
(sample_rate_hz, $macro_name:literal) => {};
(bit_clock_pin, $macro_name:literal) => {};
(word_select_pin, $macro_name:literal) => {};
(i2s, $macro_name:literal) => {};
(dma, $macro_name:literal) => {};
(max_clips, $macro_name:literal) => {};
(max_volume, $macro_name:literal) => {};
(initial_volume, $macro_name:literal) => {};
($field:ident, $macro_name:literal) => {
compile_error!(concat!(
$macro_name,
" unknown field; expected `data_pin`, `sample_rate_hz`, `bit_clock_pin`, `word_select_pin`, `i2s`, `dma`, `max_clips`, `max_volume`, or `initial_volume`"
));
};
}
#[doc(inline)]
pub use audio_player;
#[doc(inline)]
pub use tone;