Files
ESP32_ZACUS/box3_voice/main/cmd_exec.h
T
clement fd7af95bcf
CI / platformio (push) Failing after 6m54s
CI / platformio (pull_request) Failing after 16m36s
feat(box3): audio narration for the touch gamebook
Context:
The BOX-3 touch gamebook showed text + tappable choices but was silent.
We want narration through its ES8311 speaker while the text is on
screen — and tapping a choice must cut it instantly.

Approach:
Add a dedicated async, interruptible WAV player to cmd_exec (which owns
the speaker handle), streaming straight from the SD in small chunks so
any length plays without a big malloc. The gamebook plays each passage
WAV on render; a new tap (or returning to the menu) stops it.

Changes:
- cmd_exec.c/.h: cmd_exec_play_file_async(path) + cmd_exec_stop_play().
  One persistent task streams a 16 kHz mono 16-bit WAV from the SD in
  2 KB chunks (RAM-safe), and a new request / stop flag breaks the
  stream mid-clip so playback is interruptible.
- gamebook.c: on entering a passage, play /sdcard/gamebook/<wav> if the
  pack has audio (non-fatal when absent → stays text-only); stop the
  narration when returning to the library.

Impact:
The BOX-3 reads the story aloud (16 kHz, matches AUDIO_SAMPLE_RATE)
while showing the text, and a tap cuts to the next passage. Audio is
optional — text-only packs still work.
2026-06-20 01:02:29 +02:00

48 lines
1.8 KiB
C

// cmd_exec.h — BOX-3 executor for structured ESP-NOW CMD frames (D5 contract).
//
// Wire format (SCENARIO_MESH_TEXT_CMD, ≤200 bytes):
// {"op":"<verb>","a":{<args>}}
//
// Supported ops:
// screen {"t":"title","u":"subtitle","y":"symbol","e":"effect"} — display on LCD
// effect: "pulse"|"glitch"|"gyro"|"none"
// play {"p":"<path>","l":0|1} — stream WAV via I2S; embedded cue if no file
// evt {"n":"<name>"} — log the event (future: relay to master)
// led {"p":"<pattern>"} — log the pattern (stub)
// <any> unknown op — log warn, ignore
//
// Tolerant by design: malformed JSON, missing keys, or unknown ops log a warning
// and return without crashing. Never asserts on network input.
#pragma once
#include <stddef.h>
#include "esp_err.h"
#include "driver/i2s_std.h"
#ifdef __cplusplus
extern "C" {
#endif
// Parse and execute one CMD frame. `data` is the raw UTF-8 payload received via
// scenario_mesh text callback (NUL-terminated). Returns ESP_OK if the op was
// recognised and dispatched, ESP_ERR_INVALID_ARG on bad JSON or missing `op`,
// ESP_ERR_NOT_SUPPORTED for unknown ops (all logged, no crash).
esp_err_t cmd_exec_handle(const char *data, size_t len);
// Provide the speaker I2S channel handle so exec_play() can stream audio.
// Must be called from main.c after speaker_init(), before any CMD arrives.
void cmd_exec_set_spk_handle(i2s_chan_handle_t h);
// Play a 16 kHz mono 16-bit WAV from the SD asynchronously, streamed in chunks
// (RAM-safe for long narration) and INTERRUPTIBLE: a new call stops the current
// clip and starts the new one. Used by the touch gamebook for passage audio.
void cmd_exec_play_file_async(const char *path);
// Stop the async narration (no-op if nothing is playing).
void cmd_exec_stop_play(void);
#ifdef __cplusplus
}
#endif