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>
43 lines
No EOL
1.4 KiB
C++
43 lines
No EOL
1.4 KiB
C++
// SPDX-License-Identifier: GPL-3.0-only
|
|
#include "audio.h"
|
|
|
|
#include <ESP_I2S.h> // bundled with Arduino-ESP32 3.x
|
|
|
|
#include "audio_config.h"
|
|
#include "pins.h"
|
|
|
|
namespace {
|
|
I2SClass i2s;
|
|
}
|
|
|
|
namespace audio {
|
|
|
|
bool begin() {
|
|
#if defined(OPENSCRIBE_BOARD_XIAO)
|
|
// XIAO ESP32-S3 Sense onboard PDM microphone.
|
|
i2s.setPinsPdmRx(PIN_PDM_CLK, PIN_PDM_DIN);
|
|
return i2s.begin(I2S_MODE_PDM_RX, OSC_SAMPLE_RATE,
|
|
I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO);
|
|
#else
|
|
// I2S standard mode from an external MEMS mic (INMP441 / ICS-43434).
|
|
// Args: bclk, ws, dout(unused for RX = -1), din, mclk(unused = -1).
|
|
i2s.setPins(PIN_I2S_BCLK, PIN_I2S_WS, -1, PIN_I2S_DIN, -1);
|
|
return i2s.begin(I2S_MODE_STD, OSC_SAMPLE_RATE,
|
|
I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO);
|
|
#endif
|
|
}
|
|
|
|
size_t audio::read(uint8_t *dst, size_t maxBytes) {
|
|
// I2SClass::readBytes blocks until data is available or the DMA times out.
|
|
int n = i2s.readBytes(reinterpret_cast<char *>(dst), maxBytes);
|
|
return n < 0 ? 0 : static_cast<size_t>(n);
|
|
}
|
|
|
|
void end() { i2s.end(); }
|
|
|
|
} // namespace audio
|
|
|
|
// NOTE (calibration, revisit with hardware): INMP441 emits 24-bit samples in a 32-bit
|
|
// slot. Reading in 16-bit mode captures the top bits and is usable for speech but may be
|
|
// quiet. If levels are low on the dev build, switch to 32-bit capture and right-shift into
|
|
// int16 here. Tracked in state/TODO.md.
|