Repository navigation
ear: slice 1, shared ear + inbound gate (RFC-0002) - #159
Draft
chazmaniandinkle wants to merge 2 commits into
Draft
chazmaniandinkle wants to merge 2 commits into
chazmaniandinkle wants to merge 2 commits into
Conversation
Add ear/ with read(samples, sr) -> EarReading: kind (speech|music|noise| silence) with a confidence for each, level, voicing, spectral flatness, band energy and chroma. Silero VAD (vad.py) is the speech evidence; music vs noise comes from flatness and pitch-class concentration, numpy only. VoiceGate is now an EarGate: only kind=speech reaches STT. Non-speech returns a CognitiveEvent with empty content and metadata.non_speech/kind (bus .perceive_outcome, bus event modality.non_speech); perceive() keeps its contract. /v1/transcribe and /v1/bus/perceive report non-speech instead of transcribing it. The gate decodes float32 and WAV payloads (the old gate assumed int16 by default, but inbound sends float32). Tests use generated fixtures: committed TTS speech clips plus numpy chiptune/noise/silence.
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.
Slice 1 of RFC-0002 (#158): the shared ear library and the inbound gate. Stacked on
rfc/0002-shared-ear-music-modality, so the diff againstmainalso shows the RFC commit; the slice itself is one commit.What this adds
ear/package.ear.read(samples, sr) -> EarReadingfor a chunk,ear.read_clip(...)for a whole file or utterance (long files are sampled in 2 s windows, Silero still scans the whole clip).kindin {speech, music, noise, silence}, a confidence for each (they sum to 1), plusrms_db,peak_db,voiced_fraction,f0_hz, median spectralflatness,band_energy(sub/low/mid/high), 12-binchroma,chroma_entropy, and Silero's speech fraction/segments.vad.detect_speech. Music vs noise is spectral flatness plus pitch-class concentration, plain numpy FFTs (no librosa dependency, no per-frame Python loops).vad_available=False, notevad_unavailable) and nothing is called speech.ear.EarGate(amodality.Gate).VoiceGatenow subclasses it, so every caller that goes throughbus.perceiveis gated: onlykind=speechreaches STT. If the ear itself errors,VoiceGatefalls back to the old plain-Silero check; a bareEarGatefails closed.ModalityBus.perceive_outcome()returns(event, non_speech);non_speechis aCognitiveEventwith emptycontent,metadata = {non_speech: True, kind, confidences, reading, decoder: None}, and amodality.non_speechbus event is emitted.perceive()keeps its old contract (returnsNone)./v1/transcriberuns the gate before Whisper and returns{"transcript": "", "non_speech": true, "kind": ..., "confidences": ...}for music/noise/silence./v1/bus/perceivereturnsstatus: "non_speech"with the kind.Tests
tests/test_ear.py(60 tests), generated fixtures only:tests/fixtures/ear/, regenerable withscripts/make_ear_speech_fixtures.pyChecks: every fixture classified correctly as a whole clip, in 2 s chunks, and in 0.5 s chunks (see caveat below); music and noise never read as speech at four gain levels; speech under a noise bed is still speech; a pure 440 Hz tone gives the right level, pitch class (A) and f0; int16 / stereo / 48 kHz / empty / NaN inputs; the gate never calls the (spy) STT decoder on any music/noise/silence fixture, including the 174 BPM instrumental delivered as a WAV file;
/v1/transcribeand/v1/bus/perceivebehaviour; ear-error fallbacks; no-Silero degradation; speed floor of 5x real time.Suite before/after (same venv, run on the RFC base and on this branch):
rfc/0002-...)The 2 failures are the same on both:
tests/test_websocket_transport.py::test_websocket_library_installedand::test_real_websocket_upgrade_through_uvicorn. 60 new tests, 0 new failures. (Both runs also end in aBus error: 10at interpreter exit after the pytest summary; same on base.)tests/test_transcribe_endpoint.pygot a one-line bypass of the new gate in its two fixtures, because those tests post a silent WAV to check request handling and the ear correctly stops silence before STT.Speed
python scripts/bench_ear.py, warm, median of 5 passes, Apple Silicon, machine lightly loaded (load average ~6), Silero on CPU through torch:A 69 s instrumental through
read_cliptakes about 170 ms. The same benchmark taken while the full test suite was running on the same machine (load average ~70) gave 3.5-3.9 ms per 0.5 s chunk (~130x), so the margin over the 5x target holds under heavy load.Things to know
silence(a quiet sentence-final syllable read asmusicuntil the silence centre was raised to -40 dBFS). The test holds the line at: nevernoise, >= 70%speech. Callers that need a verdict on a stream should use utterance-length audio or smooth over a few chunks.true_peak_db, key, tempo, onsets, end-of-turn probability, anomalies, hallucination-risk flag, the outbound ear.channels.py's browser-channel_transcribecallsmlx_whisperdirectly and bypasses the bus (the browser runs Silero client-side). Music through that path still reaches Whisper.VoiceGate.checkdefaulted tosample_width=2(int16), butinbound.pycallsbus.perceivewith float32 bytes and nosample_width. The old gate therefore reinterpreted float32 as int16 (garbage at twice the length).EarGatedefaults to float32 and also parses RIFF/WAV payloads.vendor/pipecat_vad/data/silero_vad.onnxis committed as a 0-byte file on this base, so the vendored ONNX path (voice_confidence) cannot load from a clean checkout. The ear uses the torch-hub Silero path invad.py, which is whatdetect_speechalready did. Not touched here./v1/vadand the MCPvad_checknow reporthas_speech: falsefor music/noise, because they use the same gate.