Skip to content

Instantly share code, notes, and snippets.

@ciniml
Created May 15, 2025 08:37
Show Gist options
  • Save ciniml/6421a1f3742eeb04e020e5424e2cd728 to your computer and use it in GitHub Desktop.
Save ciniml/6421a1f3742eeb04e020e5424e2cd728 to your computer and use it in GitHub Desktop.
ATOMS3 + ATOMIC SPK WAV Playback
#include <SD.h>
#include <M5Unified.h>
#include <esp_log.h>
#include <vector>
static constexpr const gpio_num_t SDCARD_CSPIN = GPIO_NUM_NC;
static constexpr const size_t buf_num = 3;
static constexpr const size_t buf_size = 1024;
static uint8_t wav_data[buf_num][buf_size];
struct __attribute__((packed)) wav_header_t
{
char RIFF[4];
uint32_t chunk_size;
char WAVEfmt[8];
uint32_t fmt_chunk_size;
uint16_t audiofmt;
uint16_t channel;
uint32_t sample_rate;
uint32_t byte_per_sec;
uint16_t block_size;
uint16_t bit_per_sample;
};
struct __attribute__((packed)) sub_chunk_t
{
char identifier[4];
uint32_t chunk_size;
uint8_t data[1];
};
static bool playSdWav(const char* filename)
{
auto file = SD.open(filename);
if (!file) {
M5.Log(ESP_LOG_ERROR, "Failed to open file %s", filename);
return false;
}
wav_header_t wav_header;
file.read((uint8_t*)&wav_header, sizeof(wav_header_t));
ESP_LOGD("wav", "RIFF : %.4s" , wav_header.RIFF );
ESP_LOGD("wav", "chunk_size : %d" , wav_header.chunk_size );
ESP_LOGD("wav", "WAVEfmt : %.8s" , wav_header.WAVEfmt );
ESP_LOGD("wav", "fmt_chunk_size : %d" , wav_header.fmt_chunk_size);
ESP_LOGD("wav", "audiofmt : %d" , wav_header.audiofmt );
ESP_LOGD("wav", "channel : %d" , wav_header.channel );
ESP_LOGD("wav", "sample_rate : %d" , wav_header.sample_rate );
ESP_LOGD("wav", "byte_per_sec : %d" , wav_header.byte_per_sec );
ESP_LOGD("wav", "block_size : %d" , wav_header.block_size );
ESP_LOGD("wav", "bit_per_sample : %d" , wav_header.bit_per_sample);
if ( memcmp(wav_header.RIFF, "RIFF", 4)
|| memcmp(wav_header.WAVEfmt, "WAVEfmt ", 8)
|| wav_header.audiofmt != 1
|| wav_header.bit_per_sample < 8
|| wav_header.bit_per_sample > 16
|| wav_header.channel == 0
|| wav_header.channel > 2
)
{
file.close();
return false;
}
file.seek(offsetof(wav_header_t, audiofmt) + wav_header.fmt_chunk_size);
sub_chunk_t sub_chunk;
file.read((uint8_t*)&sub_chunk, 8);
ESP_LOGD("wav", "sub id : %.4s" , sub_chunk.identifier);
ESP_LOGD("wav", "sub chunk_size : %d" , sub_chunk.chunk_size);
while(memcmp(sub_chunk.identifier, "data", 4))
{
if (!file.seek(sub_chunk.chunk_size, SeekMode::SeekCur)) { break; }
file.read((uint8_t*)&sub_chunk, 8);
ESP_LOGD("wav", "sub id : %.4s" , sub_chunk.identifier);
ESP_LOGD("wav", "sub chunk_size : %d" , sub_chunk.chunk_size);
}
if (memcmp(sub_chunk.identifier, "data", 4))
{
file.close();
return false;
}
int32_t data_len = sub_chunk.chunk_size;
bool flg_16bit = (wav_header.bit_per_sample >> 4);
size_t idx = 0;
while (data_len > 0) {
M5.update();
if( M5.BtnA.isPressed()) break;
size_t len = data_len < buf_size ? data_len : buf_size;
len = file.read(wav_data[idx], len);
data_len -= len;
if (flg_16bit) {
M5.Speaker.playRaw((const int16_t*)wav_data[idx], len >> 1, wav_header.sample_rate, wav_header.channel > 1, 1, 0);
} else {
M5.Speaker.playRaw((const uint8_t*)wav_data[idx], len, wav_header.sample_rate, wav_header.channel > 1, 1, 0);
}
idx = idx < (buf_num - 1) ? idx + 1 : 0;
}
file.close();
return true;
}
static std::vector<String> files;
void setup(void)
{
auto cfg = M5.config();
// If you want to play sound from ATOMIC Speaker, write this
cfg.external_speaker.atomic_spk = true;
M5.begin(cfg);
M5.Speaker.begin();
SPI.end();
SPI.begin(GPIO_NUM_7, GPIO_NUM_8, GPIO_NUM_6, SDCARD_CSPIN);
if( !SD.begin(SDCARD_CSPIN, SPI, 25000000) ) { M5.Log(ESP_LOG_ERROR, "Failed to mount SD card"); }
M5.Log(ESP_LOG_INFO, "Start");
M5.Speaker.setVolume(230);
auto root = SD.open("/");
while(true) {
auto filename = root.getNextFileName();
if( filename == "" ) break;
if( filename.endsWith(".wav") ) {
files.push_back(filename);
}
}
root.close();
}
void loop(void)
{
for (auto filename : files) {
playSdWav(filename.c_str());
M5.delay(500);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment