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

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>
This commit is contained in:
Laurence 2026-07-03 11:35:34 +01:00
parent 031074c9a9
commit 054b2f6981
14 changed files with 508 additions and 43 deletions

39
firmware/src/storage.h Normal file
View file

@ -0,0 +1,39 @@
// SPDX-License-Identifier: GPL-3.0-only
// microSD storage: a streaming WAV writer plus sidecar JSON metadata.
#pragma once
#include <Arduino.h>
#include <FS.h>
#include <SD.h>
namespace storage {
// Mount the microSD card over SPI using the pins in pins.h. Creates OSC_REC_DIR.
bool begin();
uint64_t totalBytes();
uint64_t freeBytes();
// Streaming writer for a canonical 16-bit PCM WAV file. Writes a placeholder header on
// open(), appends PCM with write(), and patches the RIFF/data sizes on close().
class WavWriter {
public:
bool open(const String &path, uint32_t sampleRate, uint16_t bitsPerSample,
uint16_t channels);
size_t write(const uint8_t *data, size_t len);
bool close();
uint32_t dataBytes() const { return dataBytes_; }
bool isOpen() const { return (bool)file_; }
private:
File file_;
uint32_t dataBytes_ = 0;
uint32_t sampleRate_ = 0;
uint16_t bits_ = 0;
uint16_t channels_ = 0;
};
// Write a sidecar JSON metadata file next to a recording.
bool writeMetadata(const String &path, const String &json);
} // namespace storage