M1: Firmware recording core #1

Merged
laurence merged 1 commit from feature/fw-recorder-core into main 2026-07-03 11:36:30 +01:00
Owner

M1: Firmware recording core

Feature

The device now records. Short-press the button to start/stop capturing microphone audio to
a WAV file on the microSD card, with the status LED showing idle/recording/error and a
sidecar JSON metadata file written per recording.

What was achieved

From a bare scaffold, the firmware gained a working capture pipeline: mic -> 16-bit PCM ->
streaming WAV file on SD, plus button control, LED feedback, and metadata that matches the
Recording schema in api/openapi.yaml. Both board profiles are supported (external I2S
mic on the dev board; onboard PDM mic on the XIAO).

How to verify

  • CI (Forgejo Actions, .forgejo/workflows/ci.yml) builds both profiles on push:
    pio run -e esp32s3_dev and pio run -e xiao_esp32s3.
  • On hardware (once parts arrive): flash, open the serial monitor at 115200, short-press
    the button. Expect "Recording started/stopped" logs and a rec_*.wav + rec_*.json
    pair under /recordings on the SD card, with the WAV playable.

Not yet compiled locally (no PlatformIO on the dev host) or hardware-verified - see
Follow-ups.

Tools used

C++ (Arduino-ESP32 3.x), PlatformIO, the core-bundled ESP_I2S and SD/FS libraries.
No extra dependencies.

How it works

  • audio (src/audio.{h,cpp}) - brings up the mic via ESP_I2S's I2SClass. Dev board:
    I2S_MODE_STD for INMP441/ICS-43434. XIAO: I2S_MODE_PDM_RX for the onboard mic.
    Presents 16-bit PCM mono to callers regardless of board.
  • storage (src/storage.{h,cpp}) - mounts the SD card on a dedicated HSPI bus and
    provides WavWriter, which writes a 44-byte PCM WAV header, streams samples, and patches
    the RIFF/data sizes on close. Also writes sidecar JSON metadata.
  • recorder (src/recorder.{h,cpp}) - the idle/recording state machine: creates
    /recordings/<id>.wav, pumps mic chunks into it on update(), and on stop finalises the
    file and writes <id>.json (Recording schema).
  • ux (src/ux.{h,cpp}) - debounced button (short press = toggle) and status LED
    patterns (idle heartbeat, recording blink, error fast-blink), active-low aware.
  • main.cpp wires them: ux::begin() + recorder::begin() in setup; loop() toggles on
    button and calls recorder::update().
  • Config in include/audio_config.h (16 kHz mono 16-bit, chunk size, paths); pins in
    include/pins.h per board.

State updated

  • state/TODO.md (M1 in PR; audio-calibration + real-timestamp follow-ups)
  • state/ARCHITECTURE.md (M1 modules marked landed)
  • state/NOTES.md (firmware specifics)
  • state/DECISIONS.md (no new decision beyond those recorded at M0)

Follow ups

  • Audio calibration: INMP441 emits 24-bit in a 32-bit slot; 16-bit capture may be quiet.
    If so, capture 32-bit and right-shift into int16 in audio.cpp.
  • Real timestamps: ids/started_at are uptime-based until NTP arrives in M2.
  • M2 next: WiFi manager + on-device REST API (list/download/delete) implementing the
    device paths in api/openapi.yaml.
# M1: Firmware recording core ## Feature The device now records. Short-press the button to start/stop capturing microphone audio to a WAV file on the microSD card, with the status LED showing idle/recording/error and a sidecar JSON metadata file written per recording. ## What was achieved From a bare scaffold, the firmware gained a working capture pipeline: mic -> 16-bit PCM -> streaming WAV file on SD, plus button control, LED feedback, and metadata that matches the `Recording` schema in `api/openapi.yaml`. Both board profiles are supported (external I2S mic on the dev board; onboard PDM mic on the XIAO). ## How to verify - CI (Forgejo Actions, `.forgejo/workflows/ci.yml`) builds both profiles on push: `pio run -e esp32s3_dev` and `pio run -e xiao_esp32s3`. - On hardware (once parts arrive): flash, open the serial monitor at 115200, short-press the button. Expect "Recording started/stopped" logs and a `rec_*.wav` + `rec_*.json` pair under `/recordings` on the SD card, with the WAV playable. Not yet compiled locally (no PlatformIO on the dev host) or hardware-verified - see Follow-ups. ## Tools used C++ (Arduino-ESP32 3.x), PlatformIO, the core-bundled `ESP_I2S` and `SD`/`FS` libraries. No extra dependencies. ## How it works - `audio` (`src/audio.{h,cpp}`) - brings up the mic via `ESP_I2S`'s `I2SClass`. Dev board: `I2S_MODE_STD` for INMP441/ICS-43434. XIAO: `I2S_MODE_PDM_RX` for the onboard mic. Presents 16-bit PCM mono to callers regardless of board. - `storage` (`src/storage.{h,cpp}`) - mounts the SD card on a dedicated HSPI bus and provides `WavWriter`, which writes a 44-byte PCM WAV header, streams samples, and patches the RIFF/data sizes on close. Also writes sidecar JSON metadata. - `recorder` (`src/recorder.{h,cpp}`) - the idle/recording state machine: creates `/recordings/<id>.wav`, pumps mic chunks into it on `update()`, and on stop finalises the file and writes `<id>.json` (Recording schema). - `ux` (`src/ux.{h,cpp}`) - debounced button (short press = toggle) and status LED patterns (idle heartbeat, recording blink, error fast-blink), active-low aware. - `main.cpp` wires them: `ux::begin()` + `recorder::begin()` in setup; `loop()` toggles on button and calls `recorder::update()`. - Config in `include/audio_config.h` (16 kHz mono 16-bit, chunk size, paths); pins in `include/pins.h` per board. ## State updated - [x] `state/TODO.md` (M1 in PR; audio-calibration + real-timestamp follow-ups) - [x] `state/ARCHITECTURE.md` (M1 modules marked landed) - [x] `state/NOTES.md` (firmware specifics) - [ ] `state/DECISIONS.md` (no new decision beyond those recorded at M0) ## Follow ups - Audio calibration: INMP441 emits 24-bit in a 32-bit slot; 16-bit capture may be quiet. If so, capture 32-bit and right-shift into int16 in `audio.cpp`. - Real timestamps: ids/`started_at` are uptime-based until NTP arrives in M2. - M2 next: WiFi manager + on-device REST API (list/download/delete) implementing the device paths in `api/openapi.yaml`.
laurence added 1 commit 2026-07-03 11:36:20 +01:00
feat(firmware): M1 recording core - mic capture to WAV on SD
Some checks failed
ci / server (pull_request) Failing after 34s
ci / firmware (pull_request) Failing after 36s
054b2f6981
Implements the first working feature: the device records audio to the microSD
card, toggled by the button, with LED status and per-recording JSON metadata.

What changed:
- firmware/src/audio.{h,cpp}: mic capture via the core-bundled ESP_I2S. Dev board
  uses I2S standard mode (INMP441/ICS-43434); XIAO uses PDM mode (onboard mic).
  Presents 16-bit PCM mono to callers regardless of board.
- firmware/src/storage.{h,cpp}: microSD on a dedicated HSPI bus + a streaming
  WavWriter that writes a 44-byte PCM header and patches RIFF/data sizes on close;
  plus sidecar JSON metadata writer.
- firmware/src/recorder.{h,cpp}: idle/recording state machine; creates
  /recordings/<id>.wav, pumps mic chunks in on update(), finalises + writes
  <id>.json (Recording schema from api/openapi.yaml) on stop.
- firmware/src/ux.{h,cpp}: debounced button (short press toggles) + status LED
  patterns (idle/recording/error), active-low aware.
- firmware/src/main.cpp: wires ux + recorder; loop toggles on button and drains
  the mic while recording.
- firmware/include/audio_config.h: 16 kHz mono 16-bit, chunk size, rec dir.
- firmware/include/pins.h: added XIAO PDM mic + onboard SD pins, LED active-low flag.
- state/: TODO, ARCHITECTURE, NOTES updated for M1 and the deferred follow-ups.

Why:
- Recording is the foundation every later milestone (transfer, upload, transcription)
  builds on. Kept dependency-free (only core-bundled ESP_I2S + SD) for simple CI builds.

Notes:
- Not compiled locally (no PlatformIO on the dev host) or hardware-verified; Forgejo
  Actions CI builds both board profiles. Follow-ups tracked in state/TODO.md:
  INMP441 16-bit level calibration, and real NTP timestamps (ids are uptime-based
  until M2 brings WiFi/NTP).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
laurence merged commit cfa81a8526 into main 2026-07-03 11:36:30 +01:00
laurence deleted branch feature/fw-recorder-core 2026-07-03 11:36:30 +01:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: laurence/openscribe#1
No description provided.