A polyphonic WAV file player for ESP32 microcontrollers, designed for use in pinball machines. The device reads WAV files from an SD card and outputs them through a DAC — multiple tracks can be played and mixed simultaneously.
(C) 2025 colrhon.org – CC BY-NC-SA 4.0
- Polyphony: Multiple WAV files are played simultaneously and mixed in real time.
- SD card: WAV files and configuration are read from a FAT-formatted SD card.
- GPIO events: Up to 10 input pins trigger sound playback (directly or binary-encoded for Williams System 11 / Gottlieb System 1/80/80A/80B).
- Serial control: Sounds can be triggered via UART (RX on GPIO 36, RX-only).
- USB Config Editor: Browser-based editor (
webapp/) for managing the SD card and configuration over USB (Chrome/Edge). - WiFi / HTTP access (optional): The same editor can also talk to the device over WiFi (HTTP REST on port 8080), in parallel to USB. STA-mode only, credentials in
config.txt. - File attributes: Individual WAV files can be configured as loop, background sound, init sound, or with kill behavior.
- Sound groups: Multiple sounds can be combined into a group from which one is selected randomly or sequentially.
- Firmware update: New firmware can be placed as
update.binon the SD card and is automatically flashed on the next boot.
The file config.txt must be placed in the root directory of the SD card. Each line uses the format key=value. Missing entries fall back to default values.
| Key | Values | Default | Description |
|---|---|---|---|
dac |
12, 16 |
12 |
DAC output bit depth |
mix |
sum, div2, sqrt |
div2 |
Mixing mode for multiple simultaneous tracks |
evt |
none, flat, flat0, bw11, bg80, by35 |
bg80 |
GPIO event mode |
deb |
number (ms) | 10 |
Debounce time in milliseconds |
rpd |
number (ms) | 60 |
Rest period in milliseconds (flat mode only) |
ser |
none, uart |
none |
Serial interface for sound commands |
usbbaud |
115200, 230400, 460800, 921600 |
115200 |
Baud rate of the USB config-editor port (UART0) |
stheme |
string | orgsnd |
Active sound-theme directory under the SD root (sounds and groups are loaded from there) |
log |
no, yes, only |
no |
Persistent event log on SD (log.txt); only writes the log but suppresses audio playback |
volv |
0–100 | 100 |
Voice-class volume scaling in % (applied to files with attribute v) |
vols |
0–100 | 100 |
Sound-class volume scaling in % (applied to non-voice files) |
wifi_enable |
yes, no |
no |
Enables STA-mode WiFi and the HTTP server on port 8080 |
wifi_ssid |
string | (empty) | WiFi network SSID — required if wifi_enable=yes |
wifi_pwd |
string | (empty) | WiFi password |
Example:
dac=12
mix=div2
evt=bg80
deb=10
rpd=40
ser=uart
usbbaud=921600
wifi_enable=yes
wifi_ssid=MyNetwork
wifi_pwd=secret
Note: If
config.txtis missing, the firmware starts with the default values above. Theusbbauddefault of115200keepsidf.py monitorworking out of the box. Raise it (e.g.921600) for faster file transfers with the config editor — then match the editor's "Baud" drop-down to the same value before reconnecting.
| Value | Behavior |
|---|---|
sum |
All track samples are added together (may clip) |
div2 |
Each sample is halved before adding |
sqrt |
Samples are summed and divided by the square root of the track count |
| Value | Description |
|---|---|
none |
No GPIO events |
flat |
10 input pins, each pin triggers the sound with the same number (from rel. 0.9.2, with rest period) |
flat0 |
Like flat, older variant without rest period (up to rel. 0.9.1) |
bw11 |
Williams System 11 – binary-encoded sound bus |
bg80 |
Gottlieb System 1/80/80A/80B – binary-encoded sound bus |
by35 |
Bally -35 / Stern MPU – binary-encoded sound bus, read on external strobe (GPIO34, positive edge) |
For flat / flat0: pins 1–10 trigger sound IDs 1–10. Inputs are active-low with an external 10 kΩ pull-up resistor.
Debounce and rest period (flat mode):
deb: After a detected edge, the pin is locked for this duration.rpd: Within this time after the last event, a further event on the same pin is ignored and the time window is extended.
- RX on GPIO 36 (input-only pad; no internal pull-up — use an external 10 kΩ pull-up to 3.3 V if needed)
- TX not used
- 115200 baud, 8N1, no handshake
- Playback command:
p <id>followed by a newline — example:p 19plays sound file 19 - GPIOs 21 and 22 remain available for the on-board LEDs regardless of this setting
Sound files are placed in the active sound-theme directory of the SD card
(<sdroot>/<stheme>/, default <sdroot>/orgsnd/) and follow this naming scheme:
NNNN-AAAA-VVV-description.wav
| Field | Description |
|---|---|
NNNN |
4-digit numeric ID (e.g. 0012) |
AAAA |
4 attribute characters (see table below); pad unused positions with x |
VVV |
Volume 0–100 |
description |
Any text label (may contain hyphens) |
Example: 0012-bxxx-100-ring-my-bell.wav
The 4 attribute characters are position-independent — order does not matter,
and each character may appear at most once. Pad with x to fill four positions.
| Character | Meaning |
|---|---|
l |
Loop — file repeats indefinitely |
b |
Break — a running instance of the same sound is stopped before restarting |
i |
Init/background — played automatically on startup; if l is also set, acts as a background music loop |
v |
Voice — uses volv (voice volume) instead of vols for volume scaling |
k |
Kill — all running tracks are stopped before this sound starts |
c |
Soft-kill — all tracks except init/background are stopped |
q |
Quit — soft-kill that preserves looping sounds and voices |
x |
Placeholder (no attribute) |
k, c, and q are mutually exclusive — only one kill mode per file.
Groups combine multiple sounds. When a group ID is triggered, one member is selected and played.
Group files are placed alongside the sound files in the active theme directory
(<sdroot>/<stheme>/):
NNNN-A-M1-M2-M3-description.grp
| Field | Description |
|---|---|
NNNN |
4-digit group ID |
A |
Selection mode: m = random, r = sequential (round-robin) |
M1, M2, … |
Sound IDs of the group members |
Example: 0009-m-15-71-12-exit-lane-left.grp – randomly selects from sounds 15, 71, and 12.
On startup, a spoken version announcement is played automatically if the matching file is present:
/spokenvers/version-X-Y-Z.wav
where X, Y, Z are the three parts of the version number (e.g. version-0-9-3.wav for version 0.9.3).
New firmware can be copied to the SD card:
update.bin
On the next boot, the device checks whether the file is present and valid, flashes it into the inactive OTA partition, and restarts with the new version. The file is not automatically deleted after a successful update.
The directory webapp/ contains PWAVplayer_config_editor.html — a browser-based editor for Chrome or Edge (desktop). It can talk to the device over USB (Web Serial) or over WiFi (HTTP REST on port 8080). The mode is chosen in the Device tab; both transports run in parallel on the firmware side.
- Configuration: Edit
config.txt, upload it to the device or download it (incl. WiFi settings in the General tab) - Soundfiles: Per-slot table (IDs 0001–0031) with two assign buttons — PC uploads a local WAV, SD picks a WAV already on the card and renames it into the slot
- Soundgroups: Create, edit and delete
.grpfiles on the SD card; pick member WAVs from a checkbox list, switch between random (m) and round-robin (r) playback - SD card: View file list (name, size, date), download, rename, and delete files
- Firmware update: Transfer a local
.binfile asupdate.binto the SD card; full flash utility via esptool-js (browser USB) - Default configuration: Load a default
config.txtfrom lisy.dev - WiFi status (USB): The Device tab has a Get IP Status button that queries the device over USB and copies the assigned IP into the IP-mode address field
- Debug tab: Low-level USB protocol log, moved out of the Device tab
- Chrome or Edge (desktop) — Web Serial API required for USB mode
- ESP32 connected via USB, or reachable on the LAN (WiFi STA)
- Firmware running (
UsbSerialtask is always active; HTTP server starts only whenwifi_enable=yesand the device got an IP)
- USB: frame-based text protocol on
UART_NUM_0; baud rate fromusbbaud(default115200). - HTTP: REST on port 8080, CORS-enabled, no authentication (designed for a trusted LAN).
See webapp/API.md for wire-level details of both transports and webapp/README.md for
operating instructions.