Repository navigation
feat(mic)!: standalone SystemMic interface + shipped Resident::M5Mic driver - #24
Merged
Merged
Conversation
…sident::M5Mic SystemMic drops its Driver inheritance. The mic used nothing from Driver — no Lua surface, no events, no per-loop update — and the one coupling it had, the lifecycle set calling void begin(), is exactly what blocked convergence with hawthorn-firmware's HawthornMicrophone, whose consumers need begin() to report failure. Three backends with radically different internals (synchronous I2S, M5Unified's asynchronous request queue, a multi-mic array with a processing stage) had already converged on the same three methods; that is now the interface: bool begin(), void end() (default no-op, for shared-codec boards), and read(buf, maxSamples, timeoutMs). sampleRate() stays pure virtual; frameSamples() defaults to 512, the pump's cap. HawthornMicrophone can be deleted downstream. Capture is now owned by the pump: startMicStream() begins the mic and returns bool (false when there is no systemMic or begin() fails — previously that state silently "streamed" nothing), stopMicStream() ends it. Capture hardware is held only while streaming, which is what shared mic/speaker codecs need, and the sandbox no longer carries the mic in its extension lifecycle. Resident::M5Mic (src/ResidentM5Mic.h) is a shipped, opt-in driver for M5Unified boards — one include and one object where the example previously carried sixty lines of rotation logic for non-experts to hold wrong. It is the Hawthorn production M5Microphone ported whole: three-buffer rotation deriving completion from record()'s two-request lag, mic task pinned to core 1 at priority 18, audit() health counters. Two deliberate changes from that port: timeoutMs == 0 is a non-blocking poll per the SystemMic contract (was: block indefinitely), and underrun detection is queue-occupancy-based rather than wait-duration-based, so it works under both blocking and non-blocking callers. The header is not included by Resident.h and compiles to nothing without M5Unified, which stays the consumer's dependency. The m5stick-voice example deletes M5MicDriver.h and wires Resident::M5Mic in two lines. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Replaces #23 (auto-closed by GitHub when its stacked base branch was deleted on #21's merge — same branch and commits, now against
main). #21 has merged, so the diff here is exactly the SystemMic v2 change.SystemMic is no longer a Driver
The mic used nothing from
Driver— no Lua module, no events, noupdate()— and the one coupling it had (the lifecycle set callingvoid begin()) is exactly what blocked convergence:MicBusneedsbegin()to report failure, and C++ won't let a subclass change the return type. Three HRD backends with radically different internals (sync I2S, M5Unified's async queue, multi-mic array + AFE stage) had independently converged on the same three methods; that is now the interface:HawthornMicrophoneis this, verbatim, plus the two getters — downstream it gets deleted, not adapted. The five-rule contract (capture runsbegin()→end(); never hand hardware the caller's buffer;timeoutMs == 0= don't block; short reads legal; single caller) lives in the header andapi.md.The pump owns capture:
startMicStream()begins the mic and now returnsbool— false when there is nosystemMicorbegin()fails, a state that previously silently "streamed" nothing.stopMicStream()ends it. Capture hardware is held only while streaming (what shared mic/speaker codecs need); cost is ~2 frames of priming latency at stream start, imperceptible behind a 500 ms push-to-talk hold.Resident::M5Mic
src/ResidentM5Mic.h— opt-in (not included byResident.h), header-only, compiles to nothing without M5Unified, which stays the consumer's dependency. Wiring a mic on any M5 board is now:It is hawthorn-firmware #117's on-device-validated
M5Microphoneported whole — three-buffer rotation deriving completion fromrecord()'s two-request lag, mic task pinned core 1 / priority 18,audit()health counters — with two deliberate changes:timeoutMs == 0is a non-blocking poll per the contract. HRD'sadvance()treats<= 0as block indefinitely — the inversion flagged on #117 — which would hang the sandbox loop under the pump. An empty non-blocking poll is routine and does not count as anAudit::timeoutsfault.isRecording() == 0outside priming = mic task parked and discarded its DMA chunk), replacing the wait-duration heuristic (waited < 2 ms), which misfires under a non-blocking caller where zero wait is the healthy case. Works for both blocking (MicBus) and non-blocking (pump) callers.The m5stick-voice example deletes its 120-line
M5MicDriver.h; task priorities, memcpy and rotation logic now live once, in the shipped driver.Verification
ResidentM5Mic.h.ResidentSandbox.cpp).Downstream (hawthorn-firmware)
After #117 merges and this releases as v0.7.0: bump
vendor/resident; deleteHawthornMicrophone.hpp+lib/M5Microphone; rename the base class inMicBus/Microphone/MicArray+ addsampleRate()one-liners; boards adoptResident::M5Mic(itsAuditsurface is unchanged, so existingm5mic.audit()dumps keep working).MicBusneeds no behavioural change —begin()->bool,end(),read(...,1000)all already conform.🤖 Generated with Claude Code