6.1 KiB
Executable File
6.1 KiB
Executable File
Parakeet Short-Audio Padding Plan
Problem Statement
- FluidAudio's Parakeet TDT v3 decoder refuses to run (or produces unstable text) when the input WAV is shorter than its internal chunk size.
- Hex often emits <250 ms clips when users tap-to-talk, so we must “pack” (pad) the PCM before
AsrManager.transcribe(_:)is called. - WhisperKit already zero-pads to 30 s internally, but Parakeet expects callers to hand it an audio segment that spans an entire chunk.
Goals (Parakeet-first)
- Detect when a captured recording is below Parakeet’s minimum duration (TBD) and ensure the on-disk file we hand to FluidAudio meets or exceeds that window.
- Keep padding transparent to users: no audible artifacts, no extra latency, and logs that explain when/why padding ran (
HexLog.parakeet). - Limit scope to Parakeet for now; Whisper will keep using its existing flow unless we discover regressions.
Constraints & Research Tasks
- Confirm Parakeet’s required sample count / duration. FluidAudio docs hint that v3 expects whole “chunk_duration” intervals (default 1.5 s @ 16 kHz mono). Need an authoritative value from:
FluidAudio/Documentation/Models/ASR/LastChunkHandling.md- Hugging Face card for
parakeet-tdt-0.6b-v3-coreml - Any sample scripts in
FluidAudiorepo (look forchunk_duration,min_samples, or padding helpers).
- Determine whether Parakeet tolerates zero-padding vs. repeating tail samples. Preference is zero-padding unless docs warn about VAD slip.
- Verify whether padding must retain the original WAV container (RIFF header). If
AsrManageronly reads PCM floats, we can rewrite the whole file; otherwise we may need to append silence samples and fix the header lengths.
Current Pipeline Touchpoints
Hex/Clients/RecordingClient.swift:667sets upAVAudioRecorderat 16 kHz mono float and writes torecording.wavinFileManager.default.temporaryDirectory.Hex/Features/Transcription/TranscriptionFeature.swift:320callsrecording.stopRecording()and passes the resulting URL intoTranscriptionClient.transcribe.Hex/Clients/TranscriptionClient.swift:242routes Parakeet variants straight toParakeetClient.transcribe(_:)with no preprocessing.Hex/Clients/ParakeetClient.swift:118hands the URL toAsrManager.transcribe(url)and returns the text.
These spots give us two obvious injection points: (a) mutate the WAV immediately after recording.stopRecording() returns, or (b) intercept inside ParakeetClient before calling FluidAudio.
Implementation Plan
Phase 1 – Instrumentation & Guardrails
- Add a lightweight helper (e.g.,
ShortClipInspector) that loads the WAV header, validates sample rate/channels, and returns total duration. - Insert the inspector inside
TranscriptionClientright before the Parakeet branch so we can log current clip lengths (HexLog.parakeet.debug("clip=0.18s")). - Ship this logging first (behind a debug flag if needed) to confirm real-world distributions before we enable padding.
Phase 2 – Padding Helper (Parakeet Only)
- Create a new utility in
Hex/Audio/ShortClipPadder.swift(or similar) that:- Accepts a source
URL, desired minimum samples, and outputURL(can be the same file if we rewrite safely). - Reads PCM floats via
AVAudioFileorExtAudioFile, counts samples, and if below threshold, appends zeros untilminSamplesis met. - Rewrites the WAV header chunk sizes so the file stays valid.
- Accepts a source
- Unit test this helper in
HexCoreTestswith fixtures (<50 ms clip, >threshold clip) to guarantee correct math and metadata. - Make the minimum configurable (env var or
HexSettings) so we can tweak without hard-coding; default to whatever FluidAudio recommends.
Phase 3 – Integration Path
- Inside
TranscriptionClient.transcribe, whenisParakeet(model)is true:- Inspect clip duration.
- If below threshold, call the padder and produce a temporary padded file (e.g.,
recording_padded.wav). - Pass the padded URL to
parakeet.transcribeand clean up temp files afterward.
- Alternatively (if we want to contain logic), embed the padding call in
ParakeetClient.transcribeso every backend use (future features/tests) benefits without touching the rest of the app flow. - Ensure padding happens off the main actor to avoid UI stalls—
TranscriptionClientalready runs inside an async task, so heavy I/O should stay there.
Phase 4 – Telemetry & Recovery UX
- Emit structured logs when padding occurs, including original duration and pad length, so we can grep Console for “Parakeet padded clip”.
- If FluidAudio still rejects the clip after padding, surface a user-friendly error and optionally auto-retry with a slightly longer pad.
- Consider capturing anonymized counts (number of padded clips per session) for future tuning once analytics hooks exist.
Validation Strategy
- Unit tests:
ShortClipPadderTestsverifying sample count math, header rewrites, and idempotent behavior when clip already meets threshold.
- Integration smoke test:
- CLI harness that feeds a synthetic 150 ms WAV through
ParakeetClientwith padding enabled; ensure the decoder returns text (or at least does not throw).
- CLI harness that feeds a synthetic 150 ms WAV through
- Manual QA:
- Tap hotkey quickly on macOS 15 (Intel + Apple Silicon). Confirm transcripts appear instead of “clip too short” errors, and latency increase is negligible (<5 ms for padding).
Open Questions
- Does
AsrManager.transcribestream audio internally (making padding unnecessary if we tweak its chunk config) or does it expect fully sized files only? - Should we preserve raw, unpadded recordings for history exports while only padding the temporary file handed to Parakeet?
- Do we need to bump the
RecordingDecisionEnginethresholds so we still discard accidental tap-noise clips, or is padding enough? - Could insanely short clips (e.g., <10 ms) produce audible pops once padded? If so, we may need a pre-smoothing step (fade in/out) before zero-fill.
Next action: gather the concrete min-duration requirement from FluidAudio docs and pick an injection point (TranscriptionClient vs. ParakeetClient) so we can spike the padding helper.