Skip to content

Instantly share code, notes, and snippets.

@JayGoldberg
Last active August 4, 2025 00:01
Show Gist options
  • Save JayGoldberg/37aba8b00cd326db1aa2df69f9d4cc6a to your computer and use it in GitHub Desktop.
Save JayGoldberg/37aba8b00cd326db1aa2df69f9d4cc6a to your computer and use it in GitHub Desktop.
Play WAV files from SPIFFS to i2s using arduino-audio-tools (e.g. ESP8266 & ESP32)
// using https://github.com/pschatzmann/arduino-audio-tools
// upload to SPIFFS with https://github.com/espx-cz/arduino-spiffs-upload for Arduino 2.x (non-JAR),
// place your WAV files in data/ in the sketch folder
#include "AudioTools.h"
#include "AudioTools/Disk/AudioSourceSPIFFS.h"
#include "AudioTools/AudioCodecs/CodecWAV.h"
// Define your custom I2S pins for ESP32
// IMPORTANT: These are GPIO numbers.
const int I2S_BCLK_PIN = 2;
const int I2S_WS_PIN = 1;
const int I2S_DATA_PIN = 3;
const char* startFilePath = "/";
const char* ext = "wav";
const char* STATIC_AUDIO_FILE = "/horn.wav";
AudioSourceSPIFFS source(startFilePath, ext); // for playing as a playlist
WAVDecoder decoder;
I2SStream i2s;
AudioPlayer player(source, i2s, decoder); // connect source to sink
void printMetaData(MetaDataType type, const char* str, int len) {
Serial.print("==> ");
Serial.print(toStr(type));
Serial.print(": ");
Serial.println(str);
}
void setup() {
Serial.begin(115200);
AudioToolsLogger.begin(Serial, AudioToolsLogLevel::Info);
delay(100);
// get a Stream* from our source, override playlist functionality
Stream* audioFileStream = source.selectStream(STATIC_AUDIO_FILE);
// Configure I2S output with custom pins
auto config = i2s.defaultConfig(TX_MODE); // Get default config for transmission
config.pin_bck = I2S_BCLK_PIN;
config.pin_ws = I2S_WS_PIN;
config.pin_data = I2S_DATA_PIN;
// IMPORTANT: For many DACs (like MAX98357A), you might need I2S_LSB_FORMAT
// Check your DAC's datasheet for the correct I2S format.
// config.i2s_format = I2S_LSB_FORMAT; // Uncomment if your DAC needs this
i2s.begin(config); // Start the I2S stream with the custom configuration
Serial.println("Using AudioPlayer for audio playback.");
//source.setFileFilter("*Bob Dylan*");
player.setMetadataCallback(printMetaData);
player.begin();
// player.setVolume(1.0);
}
void loop() {
player.copy(); // supercedes StreamCopy copier(i2s, decoder)
if (!player.isActive()) {
Serial.println("Playback finished.");
// You could add logic here to close the stream, re-open it,
// or simply halt. For demonstration, we'll just stop.
delay(2000);
while (true) { delay(100); } // Idle after playback
}
}
// plays a WAV file from SPIFFS storage as long as a button is held low.
// Releasing and pressing the button again will reset the audio playback to the start.
//
// Based on the example from the audio-tools library: https://github.com/pschatzle/arduino-audio-tools
// To upload the audio file to SPIFFS, use
// https://github.com/espx-cz/arduino-spiffs-upload, place the .vsix file in the plugins/ folder in
// your Sketches directory
// Place your WAV file in the 'data' folder of your sketch
#include "AudioTools.h"
#include "AudioTools/Disk/AudioSourceSPIFFS.h"
#include "AudioTools/AudioCodecs/CodecWAV.h"
// --- Configuration ---
// Define your custom I2S GPIO pins for ESP32
const int I2S_BCLK_PIN = 2;
const int I2S_WS_PIN = 1;
const int I2S_DATA_PIN = 3;
// Define the GPIO pin for the button
// We'll use INPUT_PULLUP, so the button connects the pin to GND when pressed.
const int BUTTON_PIN = 6;
// --- Audio Objects ---
AudioSourceSPIFFS source("/", "wav");
WAVDecoder decoder;
I2SStream i2s;
AudioPlayer player(source, i2s, decoder);
const char* STATIC_AUDIO_FILE = "/horn.wav";
// track if the audio is currently playing
bool isPlaying = false;
// --- Helper Functions ---
void printMetaData(MetaDataType type, const char* str, int len) {
Serial.print("==> ");
Serial.print(toStr(type));
Serial.print(": ");
Serial.println(str);
}
// --- Setup Function ---
void setup() {
Serial.begin(115200);
while (!Serial) // wait for serial to be ready
;
AudioToolsLogger.begin(Serial, AudioToolsLogLevel::Warning);
delay(10);
// Configure the button pin as an input with a pull-up resistor
pinMode(BUTTON_PIN, INPUT_PULLUP);
Serial.println("Starting I2S...");
auto config = i2s.defaultConfig(TX_MODE);
config.pin_bck = I2S_BCLK_PIN;
config.pin_ws = I2S_WS_PIN;
config.pin_data = I2S_DATA_PIN;
i2s.begin(config);
Serial.println("Audio setup complete. Ready for button press.");
player.setMetadataCallback(printMetaData);
}
// --- Main Loop ---
void loop() {
// Read the current state of the button
int buttonState = digitalRead(BUTTON_PIN);
// Check if the button is pressed (LOW) and playback is NOT active
if (buttonState == LOW && !isPlaying) {
Serial.println("Button pressed - starting playback...");
// The audio player needs a stream to play. We get a new one to reset it.
// Stream* audioFileStream = source.selectStream(STATIC_AUDIO_FILE);
player.begin();
player.setPath(STATIC_AUDIO_FILE);
isPlaying = true;
}
// Process the audio data if we are in the playing state
if (isPlaying) {
player.copy();
// Check if the audio file has finished playing on its own
if (!player.isActive()) {
Serial.println("Playback finished on its own.");
isPlaying = false; // Reset the flag
}
}
// If the button is released (HIGH) and playback IS active
if (buttonState == HIGH && isPlaying) {
Serial.println("Button released - stopping playback.");
player.stop(); // Stop the audio player
isPlaying = false; // Reset the flag
}
}
// plays multiple WAV files from SPIFFS storage when their respective pins go to GND.
// Releasing and pressing the button again will reset the audio playback to the start.
//
// Based on the example from the audio-tools library: https://github.com/pschatzle/arduino-audio-tools
// To upload the audio file to SPIFFS, use
// https://github.com/espx-cz/arduino-spiffs-upload, place the .vsix file in the plugins/ folder in
// your Sketches directory
// Place your WAV file in the 'data' folder of your sketch
#include "AudioTools.h"
#include "AudioTools/Disk/AudioSourceSPIFFS.h"
#include "AudioTools/AudioCodecs/CodecWAV.h"
// --- Configuration ---
// Define your custom I2S GPIO pins for ESP32
const int I2S_BCLK_PIN = 2;
const int I2S_WS_PIN = 1;
const int I2S_DATA_PIN = 3;
// Define the maximum number of simultaneous audio players.
// Be mindful of your ESP32's memory and processing capabilities.
// Each player consumes resources. If you have many, you might run into issues.
const int MAX_AUDIO_PLAYERS = 3; // Example: supporting up to 3 different audio events
// Structure to hold information for each audio event
struct AudioEvent {
int buttonPin;
const char* filePath;
bool isPlaying;
// Note: For multiple independent playback, you would typically need
// a separate AudioPlayer, AudioSourceSPIFFS, and WAVDecoder for each.
// However, the audio-tools library's I2SStream is a shared resource.
// We'll manage playback one at a time for simplicity and to avoid resource conflicts.
// If true simultaneous playback is critical, consider more advanced audio mixing
// techniques or dedicated hardware. For this example, only one audio can play at a time.
};
// Array of AudioEvent structures
AudioEvent audioEvents[] = {
{ 6, "/horn.wav", false },
{ 8, "/horn2.wav", false },
{ 10, "/dayman.wav", false }
// Add more as needed, ensure the MAX_AUDIO_PLAYERS is updated
};
const int NUM_AUDIO_EVENTS = sizeof(audioEvents) / sizeof(audioEvents[0]);
// --- Audio Objects (shared, as I2S can typically only play one stream at a time) ---
AudioSourceSPIFFS source("/", "wav"); // Source initialized generally, files selected later
WAVDecoder decoder;
I2SStream i2s;
AudioPlayer player(source, i2s, decoder); // Single player instance
// --- Helper Functions ---
void printMetaData(MetaDataType type, const char* str, int len) {
Serial.print("==> ");
Serial.print(toStr(type));
Serial.print(": ");
Serial.println(str);
}
// --- Setup Function ---
void setup() {
Serial.begin(115200);
while (!Serial) // wait for serial to be ready
;
AudioToolsLogger.begin(Serial, AudioToolsLogLevel::Warning);
delay(100);
// Configure all button pins
for (int i = 0; i < NUM_AUDIO_EVENTS; i++) {
pinMode(audioEvents[i].buttonPin, INPUT_PULLUP);
Serial.print("Configured button pin: ");
Serial.println(audioEvents[i].buttonPin);
}
Serial.println("Starting I2S...");
auto config = i2s.defaultConfig(TX_MODE);
config.pin_bck = I2S_BCLK_PIN;
config.pin_ws = I2S_WS_PIN;
config.pin_data = I2S_DATA_PIN;
i2s.begin(config);
Serial.println("Audio setup complete. Ready for button presses.");
player.setMetadataCallback(printMetaData);
}
// --- Main Loop ---
void loop() {
static int currentPlayingIndex = -1; // -1 means no audio is currently playing
// Check if any audio is currently playing
if (player.isActive()) {
// Continue processing the current audio if it's playing
player.copy();
if (!player.isActive()) { // Check if playback finished on its own
Serial.print("Playback finished for: ");
Serial.println(audioEvents[currentPlayingIndex].filePath);
audioEvents[currentPlayingIndex].isPlaying = false;
currentPlayingIndex = -1; // No audio playing anymore
}
} else {
// If no audio is currently playing, check for new button presses
for (int i = 0; i < NUM_AUDIO_EVENTS; i++) {
int buttonState = digitalRead(audioEvents[i].buttonPin);
// Check if this button is pressed (LOW) and no other audio is playing
if (buttonState == LOW && currentPlayingIndex == -1) {
Serial.print("Button pressed on pin ");
Serial.print(audioEvents[i].buttonPin);
Serial.print(" - starting playback for: ");
Serial.println(audioEvents[i].filePath);
// source.selectStream(audioEvents[i].filePath);
// source.setPath();
source.begin();
// player.setPath(audioEvents[i].filePath);
player.begin();
player.setPath(audioEvents[i].filePath); // must come after .begin()
audioEvents[i].isPlaying = true;
currentPlayingIndex = i; // Mark which audio is now playing
break; // Start playing this audio and stop checking other buttons for now
}
}
}
// Handle button release to stop currently playing audio (optional, but requested in original)
if (currentPlayingIndex != -1) {
int buttonState = digitalRead(audioEvents[currentPlayingIndex].buttonPin);
if (buttonState == HIGH && audioEvents[currentPlayingIndex].isPlaying) {
Serial.print("Button released on pin ");
Serial.print(audioEvents[currentPlayingIndex].buttonPin);
Serial.println(" - stopping playback.");
player.stop();
audioEvents[currentPlayingIndex].isPlaying = false;
currentPlayingIndex = -1;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment