Audio • ESP32
How to trigger audio sequences
with a button on ESP32
with MAX98357A
MAX98357A I2S Series - Step 4 of 4 - Button trigger and custom sequence
✓ Confirmed Working
This guide continues from Step 3 - How to play multiple audio files from ESP32 with MAX98357A → - have your clips converted and header files ready before starting here.
Before You Start
These guides are ordered by complexity but each one stands alone - if you believe and have courage, stand tall and boldly continue. If this is your first time, start at Step 1 - How to wire the MAX98357A I2S amp with ESP32 →
The amp wiring is the same as Step 3 - no changes needed there. The only addition for button mode is one button wired to a single GPIO.
Button Wiring
Connect a tactile button between the GPIO and GND. No resistor needed - the sketch uses the internal pull-up.
| Button leg | ESP32 Dev Board | ESP32-C3 |
|---|---|---|
| One leg | GPIO18 | GPIO10 |
| Other leg | GND | GND |
Custom Sequence needs no button.
Skip this wiring if you are only using the Custom Sequence sketch.
Button Mode
Each button press plays the next clip. After the last it loops back to the first.
/*
* We stand on the shoulders of giants when we build
* with knowledge gained from others' efforts.
* That doesn't make us giants. Be humble.
* Create with care. Open source is the way.
*
* MAX98357A I2S - Multiple Clips, Button Trigger
* -----------------------------------------------
* Cycles through audio clips on each button press.
*
* Board: ESP32 Dev Board (38-pin) or ESP32-C3
* Amp: MAX98357A
*
* Wiring (ESP32 Dev Board):
* BCLK -> GPIO27 LRC -> GPIO14 DIN -> GPIO13
* Button: GPIO18 -> GND
*
* Wiring (ESP32-C3):
* BCLK -> GPIO4 LRC -> GPIO5 DIN -> GPIO6
* Button: GPIO10 -> GND
*
* Open source - MIT Licence
* Electronic Zoology - field notes from the garage
* https://electroniczoology.com/guides/how-to-trigger-audio-sequences-button-esp32-max98357a
*/
#include <driver/i2s.h>
#include "clip1.h"
#include "clip2.h"
#include "clip3.h"
// ESP32 Dev Board (38-pin)
#define I2S_BCLK 27
#define I2S_LRCLK 14
#define I2S_DOUT 13
#define BUTTON_PIN 18
// ESP32-C3 - comment out the four lines above and uncomment these:
//#define I2S_BCLK 4
//#define I2S_LRCLK 5
//#define I2S_DOUT 6
//#define BUTTON_PIN 10
#define SAMPLE_RATE 44100
#define VOLUME 0.8f
#define BUF_SAMPLES 256
int16_t i2s_buf[BUF_SAMPLES * 2];
struct Clip {
const int16_t* data;
size_t len;
};
// Add or remove rows to match your clips.
// Also update the #include lines above.
const Clip clips[] = {
{ clip1_data, clip1_len },
{ clip2_data, clip2_len },
{ clip3_data, clip3_len },
};
const int CLIP_COUNT = sizeof(clips) / sizeof(clips[0]);
int currentClip = 0;
void playClip(const Clip& c) {
size_t total = c.len / sizeof(int16_t);
size_t written;
for (size_t pos = 0; pos < total; pos += BUF_SAMPLES) {
size_t count = min((size_t)BUF_SAMPLES, total - pos);
for (size_t i = 0; i < count; i++) {
int16_t s = (int16_t)(c.data[pos + i] * VOLUME);
i2s_buf[i * 2] = s;
i2s_buf[i * 2 + 1] = s;
}
i2s_write(I2S_NUM_0, i2s_buf, count * 2 * sizeof(int16_t), &written, portMAX_DELAY);
}
}
void setup() {
pinMode(BUTTON_PIN, INPUT_PULLUP);
i2s_config_t cfg = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
.sample_rate = SAMPLE_RATE,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 8,
.dma_buf_len = 64,
.use_apll = false,
.tx_desc_auto_clear = true,
};
i2s_pin_config_t pins = {
.bck_io_num = I2S_BCLK,
.ws_io_num = I2S_LRCLK,
.data_out_num = I2S_DOUT,
.data_in_num = I2S_PIN_NO_CHANGE,
};
i2s_driver_install(I2S_NUM_0, &cfg, 0, NULL);
i2s_set_pin(I2S_NUM_0, &pins);
}
void loop() {
if (digitalRead(BUTTON_PIN) == LOW) {
delay(30);
if (digitalRead(BUTTON_PIN) == LOW) {
playClip(clips[currentClip]);
currentClip = (currentClip + 1) % CLIP_COUNT;
while (digitalRead(BUTTON_PIN) == LOW);
}
}
} Custom Sequence
Define a playlist array to control the exact order - repeat clips, skip some, or build a specific sequence. The clips[] array is indexed from 0.
/*
* We stand on the shoulders of giants when we build
* with knowledge gained from others' efforts.
* That doesn't make us giants. Be humble.
* Create with care. Open source is the way.
*
* MAX98357A I2S - Multiple Clips, Custom Sequence
* ------------------------------------------------
* Plays clips in a user-defined playlist order.
*
* Board: ESP32 Dev Board (38-pin) or ESP32-C3
* Amp: MAX98357A
*
* Wiring (ESP32 Dev Board):
* BCLK -> GPIO27 LRC -> GPIO14 DIN -> GPIO13
*
* Wiring (ESP32-C3):
* BCLK -> GPIO4 LRC -> GPIO5 DIN -> GPIO6
*
* Open source - MIT Licence
* Electronic Zoology - field notes from the garage
* https://electroniczoology.com/guides/how-to-trigger-audio-sequences-button-esp32-max98357a
*/
#include <driver/i2s.h>
#include "clip1.h"
#include "clip2.h"
#include "clip3.h"
// ESP32 Dev Board (38-pin)
#define I2S_BCLK 27
#define I2S_LRCLK 14
#define I2S_DOUT 13
// ESP32-C3 - comment out the three lines above and uncomment these:
//#define I2S_BCLK 4
//#define I2S_LRCLK 5
//#define I2S_DOUT 6
#define SAMPLE_RATE 44100
#define VOLUME 0.8f
#define BUF_SAMPLES 256
int16_t i2s_buf[BUF_SAMPLES * 2];
struct Clip {
const int16_t* data;
size_t len;
};
// Add or remove rows to match your clips.
// Also update the #include lines above.
const Clip clips[] = {
{ clip1_data, clip1_len }, // index 0
{ clip2_data, clip2_len }, // index 1
{ clip3_data, clip3_len }, // index 2
};
// Edit this to set the playback order - repeat indices to replay a clip.
const int playlist[] = { 0, 1, 0, 2, 1 };
const int PLAYLIST_LEN = sizeof(playlist) / sizeof(playlist[0]);
void playClip(const Clip& c) {
size_t total = c.len / sizeof(int16_t);
size_t written;
for (size_t pos = 0; pos < total; pos += BUF_SAMPLES) {
size_t count = min((size_t)BUF_SAMPLES, total - pos);
for (size_t i = 0; i < count; i++) {
int16_t s = (int16_t)(c.data[pos + i] * VOLUME);
i2s_buf[i * 2] = s;
i2s_buf[i * 2 + 1] = s;
}
i2s_write(I2S_NUM_0, i2s_buf, count * 2 * sizeof(int16_t), &written, portMAX_DELAY);
}
}
void setup() {
i2s_config_t cfg = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
.sample_rate = SAMPLE_RATE,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 8,
.dma_buf_len = 64,
.use_apll = false,
.tx_desc_auto_clear = true,
};
i2s_pin_config_t pins = {
.bck_io_num = I2S_BCLK,
.ws_io_num = I2S_LRCLK,
.data_out_num = I2S_DOUT,
.data_in_num = I2S_PIN_NO_CHANGE,
};
i2s_driver_install(I2S_NUM_0, &cfg, 0, NULL);
i2s_set_pin(I2S_NUM_0, &pins);
}
void loop() {
for (int i = 0; i < PLAYLIST_LEN; i++) {
playClip(clips[playlist[i]]);
delay(500);
}
} How the index works
playlist[i] is a number pointing to a position in the clips[] array. 0 = clip1, 1 = clip2, 2 = clip3. Repeat an index to play that clip more than once. The example 1 plays: clip1, clip2, clip1 again, clip3, clip2 again.
Troubleshooting
Linker error: multiple definition of ‘audio_data’
- Two header files are using the same variable name. Check that you passed a different third argument for each clip when running
wav_to_i2s_header_multi.py.
Sketch too large - won’t fit in flash
- Switch to the Huge APP partition scheme in Arduino IDE under Tools → Partition Scheme. This gives around 1.2MB for your sketch and clips combined - see how ESP32 flash memory works.
- Drop the sample rate: re-convert your clips with
-ar 22050in ffmpeg and updateSAMPLE_RATEin the sketch. Speech stays clear at 22050Hz and clips are half the size. - Trim clips shorter - every second at 44100Hz 16-bit mono is ~86KB.
Button press plays nothing
- Check the button is wired between the correct GPIO and GND
- Confirm
BUTTON_PINin the sketch matches your wiring - Try pulling the pin LOW manually with a jumper wire to GND to confirm the sketch responds
Compile error: ‘clip1_data’ was not declared in this scope
- The
#includeline at the top of the sketch doesn’t match the.hfilename you generated. Check thatclip1.his in your sketch folder and that the filename in the#includematches exactly.
Want to start from the beginning?
Head back to Step 1 - How to wire the MAX98357A I2S amp with ESP32 →
Take a look at some of our other guides
Track how many times a clip has played How to use ESP32 NVS for persistent storage → - save a counter that survives power-off and reflashing.
Add a round display How to wire the GC9A01 round display with ESP32 → - show the current clip name or sequence step on screen.
Check your board first How to check a new ESP32 → - confirm flash, RAM, and chip before building your audio project.