Audio Backend - piggei/wled-usermod-buzzer GitHub Wiki
v0.2.0 introduces a third playback backend for boards that already contain a real audio codec/amplifier path.
The feature is compile-time optional. It is not present in normal active/passive firmware unless an audio board profile is explicitly enabled.
First supported profile
v0.2.0 targets the Waveshare ESP32-S3 RGB Matrix profile found in the manufacturer's ESP32-S3-RGB-Matrix example package.
The documented board wiring is:
| Function | GPIO |
|---|---|
| ES8311 I2C SDA | 47 |
| ES8311 I2C SCL | 48 |
| I2S BCLK | 43 |
| I2S LRCK / WS | 38 |
| I2S DOUT | 21 |
| I2S MCLK | 12 |
| Power-amplifier enable | 11 |
The codec uses address 0x18 and the backend uses I2S_NUM_1. Standalone mode uses 48 kHz / 16-bit stereo. Shared AudioReactive mode follows the tested AudioReactive clock domain at 22.05 kHz and its compile-time slot width (normally 32 bit, or 16 bit with I2S_USE_16BIT_SAMPLES).
Enabling the backend
Add the board profile to the WLED PlatformIO environment:
build_flags =
${env.build_flags}
-D WLED_BUZZER_AUDIO_WAVESHARE_S3_MATRIX
The profile automatically defines WLED_BUZZER_ENABLE_AUDIO and enables the bundled sample set by default. Add -D WLED_BUZZER_DISABLE_AUDIO_SAMPLES if you want the I2S/ES8311 backend without embedded PCM data.
Without this flag:
- the Audio type is not shown;
- ES8311/I2S source code contributes no runtime implementation;
- no I2S producer task or audio buffers exist;
- C3 and other active/passive builds retain the existing footprint and behavior.
Playback model
The Audio backend is now hybrid. Existing public sound IDs and APIs are unchanged:
play("victory")
play("alarm")
repeat=3
loop=1
The selected backend decides how each logical sound is rendered:
Active -> rhythm only
Passive -> LEDC pitch + rhythm
Audio I2S -> embedded PCM sample when available
otherwise sine-wave pitch + rhythm
The bundled sample-backed IDs are:
alarmattentionconnectdisconnecterrorfailimperial_march(Audio-only)notificationstar_warssuccesstrumpet(Audio-only)victorywakeup(Audio-only)warningyankee_doodle
The samples are stored as 16 kHz mono / 16-bit PCM in flash and resampled by the I2S producer to the active output stream (48 kHz standalone or 22.05 kHz shared). star_wars uses the maintainer-trimmed WAV exactly as supplied; engine repeat/loop performs subsequent executions. The connect, disconnect, star_wars, notification, and yankee_doodle samples use the maintainer-trimmed files directly. fail and alarm retain the previously qualified samples.
beep, double_beep, and triple_beep retain sine-wave synthesis on Audio (I2S). victory keeps its note melody as the Active/Passive and no-samples fallback. Active and Passive behavior of the original 15 common sounds is unchanged.
Per-sound backend capabilities
Sound definitions declare the backends they support. The original 15 common sounds remain available everywhere. v0.2.0 includes three PCM-only entries (imperial_march, trumpet, wakeup) marked Audio only. They are omitted from non-audio/no-samples firmware and are filtered out of the Sound dropdown when Active or Passive is selected.
This allows the audio library to grow independently without adding meaningless entries or sample payloads to C3/Active/Passive builds.
Configuration behavior
When compiled, Audio (I2S) appears in Buzzer Type.
- Volume controls the ES8311 DAC output level.
- Night Mode applies normally because mute policy is above the hardware backend.
GPIO PinandTrigger levelare hidden while Audio (I2S) is selected because the board profile uses fixed pins.- The fixed board profile pins are used instead.
Resource/conflict notes
The initial backend uses I2S_NUM_1. The Waveshare manufacturer's music-player example demonstrates simultaneous HUB75 display and audio operation, but coexistence must still be qualified in the actual WLED build.
Check for conflicts with other firmware components using:
- I2S1;
- GPIO 11, 12, 21, 38 or 43;
- the I2C bus on GPIO 47/48.
Hardware qualification
The v0.2.0 hardware qualification covers:
- firmware compilation and boot with the Waveshare profile;
- Audio appearing in the Buzzer Type dropdown;
/buzzerreportingbackend: audioandready: true;- speaker playback of synthesized sounds and the expanded sample-backed Audio catalog;
- saved Volume, cold-boot playback and Stop behavior;
- repeat/loop and Night Mode behavior through the common playback service;
- stable HUB75 output and smooth AudioReactive effects during playback;
- Audio <-> Active/Passive switching without regression;
- correct
source: sample/source: tonereporting; - ESP32-C3 operation with the Audio-only IDs absent, plus real iDotMatrix and Web API consumption.
Cold-boot volume initialization
The backend now waits until the I2S producer has successfully written a DMA buffer before re-applying the saved ES8311 volume. This addresses the observed cold-boot case where playback could remain silent until the Volume control was changed.
Qualified buffering and telemetry
The shared-clock fix originally left poor speaker quality and visibly jerky AudioReactive bars. The qualified fix preserved the clock topology and increased the producer block from 128 to 256 stereo frames and TX DMA depth from 4 to 8 descriptors. This gives about 92.9 ms of buffered coverage at 22.05 kHz shared mode and about 42.7 ms at 48 kHz standalone mode.
Real-hardware qualification then showed smooth speaker playback and smooth AudioReactive bars. A captured shared-mode run exceeded 20,000 writes with err=0, short=0, late=0, maxWrite about 12.9 ms and maxGap about 13.0 ms. AudioReactive OFF -> ON -> OFF transitions also passed. The producer remains priority 1 and unpinned; v0.2.0 keeps this qualified runtime and telemetry unchanged.
AudioReactive shared-I2S mode (Waveshare)
The Waveshare ESP32-S3 RGB Matrix routes the ES7210 microphone input and ES8311 speaker output over a common I2S clock bus. Shared mode is selected only when AudioReactive owns all of the board-side clock/RX pins:
- BCLK: GPIO43
- LRCK/WS: GPIO38
- MCLK: GPIO12
- microphone DIN: GPIO39
Why the first shared-I2S attempt was not sufficient
The first shared-I2S experiment used i2s_set_pin() on GPIO43/GPIO38 while configuring I2S1 as a slave transmitter. That was unsafe because the legacy I2S pin helper can reconfigure the physical pad direction/routing. On this board those pads are already being driven by AudioReactive/I2S0 as master clock outputs. Reconfiguring them for I2S1 can remove the clocks from I2S0; AudioReactive then stops receiving meaningful samples, while I2S1 cannot prime because its slave clocks have disappeared.
The characteristic failure state is:
Buzzer state: not ready
Buzzer audio: not ready
AudioReactive: running / quiet or otherwise non-responsive
Therefore the early shared-I2S observation was not valid qualification evidence.
Qualified clock-routing tap
The qualified implementation no longer asks the legacy I2S driver to configure the shared BCLK/LRCK pads. Instead:
- AudioReactive keeps I2S0 MASTER RX and remains the sole physical driver/owner of MCLK/BCLK/LRCK.
- I2S1 is installed as TX SLAVE.
i2s_set_pin()configures only Buzzer DOUT GPIO21 in shared mode.- GPIO43 and GPIO38 are tapped internally through the ESP32-S3 GPIO matrix into the I2S1 TX-slave BCLK/WS input signals.
- The GPIO-matrix tap does not change the pad direction and does not replace the existing I2S0 output routing.
- MCLK GPIO12 is never routed by the Buzzer in shared mode.
- Buzzer does not call
i2s_set_clk()in shared mode. - Buzzer never installs, resets, stops, or uninstalls I2S0.
- The global I2C bus is not reinitialized; Buzzer only addresses ES8311 at
0x18. - Shared playback follows 22050 Hz and the AudioReactive build word width (32 bit normally, 16 bit with
I2S_USE_16BIT_SAMPLES).
When the Buzzer shared backend is stopped, only its internal I2S1 clock taps are disconnected; AudioReactive GPIO configuration is untouched.
If AudioReactive releases the shared pins, Buzzer can reinitialize into the existing standalone 48 kHz I2S1 MASTER TX mode. If ownership returns, it switches back to shared-I2S.
/json/info reports the selected mode and, when shared mode is ready, includes clock tap GPIO-matrix | RX I2S0 untouched. If initialization fails, the same entry includes a detailed backend error such as shared clock/DMA priming timeout.
Shared-bus transition safety
If AudioReactive owns only some of BCLK/LRCK/MCLK/DIN while it is starting or stopping, Buzzer treats the bus as being in transition and leaves its audio backend stopped. It does not fall back to standalone-master mode until all AudioReactive ownership is released. This avoids briefly driving the shared clock pins while AudioReactive is changing state.
Shared-I2S clock sensing
Hardware testing showed that a GPIO-matrix tap alone was insufficient on the tested ESP32-S3. The qualified implementation explicitly enables the GPIO input buffers on BCLK/LRCK without removing the existing I2S0 master output routes, verifies physical LRCK activity before installing the slave TX path, and leaves PA/codec/I2S1 untouched when the shared clock is not yet running.
This is intentionally a Buzzer-side change only. AudioReactive/I2S0 remains the owner of RX and of the physical MCLK/BCLK/LRCK outputs.