Last active
August 4, 2025 00:01
-
-
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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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