What's done, what's partial, and what's next — with enough file paths and context that a fresh session can pick up any item.
- Engine status
- Model-family status
- Platform status
- Feature status
- Open roadmap items
- Adding a new backend
- Server alternative (not used)
- Codebase optimisation plan
Six rounds of work between May 2026 brought CrisperWeaver up to CrispASR 0.6.2 parity: 3 new screens (Translate, Voice Bake, Local HTTP server), 8 new backends in the catalog, runtime-tunable flash-attn / GPU layers / TTS sampling sliders, and 3 new export formats. Released as v0.4.1 paired with CrispASR v0.6.2.
Full per-round write-up: HISTORY.md → "May 2026 parity sweep".
Still deferred (each tracked upstream in ../CrispASR/PLAN.md):
- Wiring
flash_attninto every backend's compute graph — the toggle ships in the open-params struct (round 5) and threads through to each backend's session via the per-backendflash_attnfield on context_params (round 6, closing CrispASR #89). Only whisper consumes it at the kernel level today. Tracked as CrispASR PLAN.md #86 — full per-backend status table, recipe, and recommended order (orpheus + chatterbox-T3 first; ~2–3 focused days for the full sweep). gpu_backendselector (metal / cuda / vulkan / cpu-only as a runtime string) — needs ggml-side multi-backend dispatch first. Tracked as CrispASR PLAN.md #87.
The OpenAI-compatible server item that was previously deferred is
SHIPPED as a Dart-side shelf server in round 5 (see HISTORY +
the §7 note below). The CrispASR-side crispasr --server binary
remains available as the desktop-only alternative.
Two engines behind the TranscriptionEngine interface (lib/engines/transcription_engine.dart):
| Engine | State |
|---|---|
CrispASREngine |
✅ Primary. Dart FFI to libcrispasr / libwhisper. Dispatches across all 10 backends via CrispasrSession. |
MockEngine |
✅ Deterministic fake responses — used for UI work and CI. |
Earlier prototypes had separate WhisperCppEngine and CoreMLEngine values (method-channel wrappers). Dropped: whisper.cpp ships inside CrispASR, and CoreML acceleration will land as an opt-in inside libwhisper (WHISPER_USE_COREML) rather than a separate engine. EngineType.sherpaOnnx was dropped earlier for being a placeholder.
All 10 CrispASR backends are runtime-ready through CrispasrSession. The bundled libcrispasr must be linked with each backend's shared library; CrispasrSession.availableBackends() reports live at startup which ones this build has.
| Family | Download | Runtime FFI | Notes |
|---|---|---|---|
| Whisper | ✅ | ✅ (default path) | Full features (word-ts, lang-detect, streaming, VAD) |
| Parakeet | ✅ | ✅ via CrispasrSession |
Fast English ASR, native word timestamps |
| Canary | ✅ | ✅ via CrispasrSession |
Speech translation X ↔ en |
| Qwen3-ASR | ✅ | ✅ via CrispasrSession |
30+ langs incl. Chinese dialects |
| Cohere | ✅ | ✅ via CrispasrSession |
High-accuracy Conformer decoder |
| Granite Speech | ✅ | ✅ via CrispasrSession |
Instruction-tuned |
| FastConformer-CTC | ✅ | ✅ via CrispasrSession |
Low-latency CTC |
| Canary-CTC | ✅ | ✅ via CrispasrSession |
Shared canary_ctc_* pipeline |
| Voxtral Mini 3B | ✅ | ✅ via CrispasrSession |
Shared VoxtralFamilyOps loop |
| Voxtral Mini 4B | ✅ | ✅ via CrispasrSession |
Realtime variant, same loop |
| Wav2Vec2 | ✅ | ✅ via CrispasrSession |
Self-supervised, public C++ API sufficed |
| OmniASR (LLM) | ✅ | ✅ via CrispasrSession |
Multilingual LLM-based ASR (300M) |
| FireRed ASR2 | ✅ | ✅ via CrispasrSession |
AED Mandarin/English |
| Kyutai STT 1B | ✅ | ✅ via CrispasrSession |
Streaming-style STT |
| GLM-ASR Nano | ✅ | ✅ via CrispasrSession |
GLM-family multilingual |
| VibeVoice ASR | ✅ | ✅ via CrispasrSession |
Large multilingual (~4.5 GB) |
| MiMo ASR | ✅ | ✅ via CrispasrSession |
XiaomiMiMo MiMo-Audio |
| MOSS-Audio 4B | ✅ | ✅ via CrispasrSession |
ASR + audio QA + scene description |
| LFM2-Audio 1.5B | ✅ | ✅ via CrispasrSession |
ASR + TTS + S2S (English + Japanese) |
| Mini-Omni2 | ✅ | ✅ via CrispasrSession |
ASR + TTS + S2S (Whisper + Qwen2) |
| Parakeet-RNNT | ✅ | ✅ via CrispasrSession |
RNN-Transducer variants (0.6B/1.1B) |
| Higgs-STT | ✅ | ✅ via CrispasrSession |
Whisper-v3 enc + Qwen3-1.7B dec |
| ARK-ASR 3B | ✅ | ✅ via CrispasrSession |
Whisper-RoPE + Qwen2.5-3B, 19 langs |
| MOSS-Transcribe 2B | ✅ | ✅ via CrispasrSession |
Qwen3-Omni enc + Qwen3 dec, streaming |
| Gemma4-E4B | ✅ | ✅ via CrispasrSession |
Larger Gemma4 variant (4B dec) |
| ReazonSpeech v2 | ✅ | ✅ via CrispasrSession |
Japanese RNNT (619M, parakeet backend) |
| Parakeet-CTC JA | ✅ | ✅ via CrispasrSession |
Japanese CTC 1.1B (parakeet backend) |
| DoTs-TTS | ✅ | ✅ via CrispasrSession |
2B AR + flow-matching TTS, voice cloning |
The same unified dispatcher is shared with the Python (crispasr.Session) and Rust (crispasr::Session) wrappers — one C-ABI, three languages.
| Platform | State | Blocker |
|---|---|---|
| macOS | ✅ | None. flutter build macos + scripts/bundle_macos_dylibs.sh produces a runnable .app. |
| Linux | ✅ | None. CI build-linux job bundles all .so's; local build needs a Linux host. |
| Windows | ✅ | Released via release.yml; .zip with whisper.dll + sibling backend DLLs produced on every tag. AVX-512 disabled (GGML_NATIVE=OFF, pinned to AVX2+FMA) to avoid crash on Zen3 CPUs (#19). |
| Android | KTS gradle only (Groovy + legacy CMakeLists removed). APK builds with Mock engine out of the box; real ASR needs libwhisper.so cross-built via CrispASR/build-android.sh and dropped into android/app/src/main/jniLibs/<abi>/. That wiring isn't automated in CI. File picker uses FileType.audio (broad MIME) + post-filter for reliable extension display. Adaptive icon enabled (API 26+). |
|
| iOS | Podfile rewritten to a clean minimal Flutter template. pod install should now succeed, but hasn't been CI-verified; the Xcode project still contains a Runner-Bridging-Header.h reference that's now a no-op. App icons regenerated with remove_alpha_ios: true — App Store alpha-channel rejection fixed. |
|
| Web | ✅ | HfSpaceEngine routes ASR + TTS through the cstr/CrispASR HF Space via OpenAI-compatible HTTP API. Auto-selected on web; configurable server URL in settings. File picker uses PlatformFile.bytes (no filesystem). CrispEmbed WASM (1.1 MB) runs text embeddings client-side (~50-100ms/sentence) with Q4_K model (~19 MB) fetched from HuggingFace. platform_utils.dart guards all Platform.* calls. deploy-web.yml auto-deploys to Vercel on push. Live at crisperweaver-web.vercel.app. |
| Feature | State |
|---|---|
| Model download + resume + cancel + delete | ✅ |
| Quantised variants (q4_0 / q5_0 / q8_0) | ✅ from cstr/whisper-ggml-quants |
| Checksum skip toggle | ✅ in Settings → Debugging |
| History (persisted) | ✅ <app-docs>/history/*.json |
| Exports (TXT / SRT / VTT / JSON) | ✅ via share sheet |
| Performance readout (RTF, WPS) | ✅ |
| Logging + log viewer | ✅ ring buffer + optional file sink |
| Inbound share (audio → app) | ✅ Android intent filters, iOS doc types, macOS UTI open-in |
| Desktop drag-and-drop | ✅ desktop_drop on transcription screen |
| Audio decoding (WAV / MP3 / FLAC) | ✅ crispasr_audio_load FFI via miniaudio — no ffmpeg dep |
| Compressed decode (MP3 / AAC / Opus) | ✅ on-device via bundled libglint (§5.1.11) — tried before the ffmpeg/MediaCodec fallbacks, graceful WAV fallback when absent |
| Compressed export (MP3 / AAC / Opus) | ✅ GlintCodecService / AudioEditService.exportEncoded via libglint — no external ffmpeg (§5.1.11) |
| Word-level timestamps (Whisper) | ✅ via CrispASR 0.2.0 |
| Language auto-detect (Whisper) | ✅ via CrispASR 0.2.0 crispasr_detect_language |
| VAD (Silero) — end to end | ✅ shipped in v0.1.7 via CrispASR 0.4.4 crispasr_session_transcribe_vad; single Advanced Options toggle, Silero GGUF bundled as asset, whisper + session paths both wired |
| Streaming transcription (Whisper) | ✅ via CrispASR 0.3.0 crispasr_stream_* — 10s window / 3s step |
| i18n (en + de) | ✅ flutter_localizations + lib/l10n/*.arb (866 keys each, full en/de parity). All screens + widgets migrated, including the Model Management HF-repo dialogs and the speaker-enroll flow (May 2026) plus 33 newly-migrated strings for §5.25 features (June 2026). flutter gen-l10n succeeds on Flutter 3.35.1. Two paths stay deliberately inline (documented in-code): the Android-only "All files access" Settings dialog and the Translate auto-detect niche path — both carry an explicit "keep inline" rationale. |
| Real speaker diarization (library API) | ✅ via CrispASR 0.4.5 crispasr_diarize_segments_abi — lib/services/diarization_service.dart now calls the shared lib (energy / xcorr / vad-turns / pyannote). MFCC/k-means stopgap removed. |
| Language auto-detect for non-Whisper backends | ✅ via CrispASR 0.4.6 crispasr_detect_language_pcm — LidService (lib/services/lid_service.dart) runs whisper-tiny LID before session backends when the user picks "auto" and any multilingual whisper model is downloaded. |
| Word timestamps for LLM backends | ✅ via CrispASR 0.4.7 crispasr_align_words_abi — AlignerService (lib/services/aligner_service.dart) runs canary-CTC / qwen3-fa as a post-step for qwen3 / voxtral / granite when the user has word-timestamps enabled and an aligner GGUF is on disk. |
| Punctuation restoration (FireRedPunc) | ✅ via CrispASR 0.5.x PuncModel — PuncService (lib/services/punc_service.dart) plus an "Restore punctuation" toggle in Advanced Options. Loads fireredpunc-*.gguf lazily; silently no-ops when the model isn't downloaded. |
| Dynamic backend discovery from libcrispasr | ✅ ModelService.refreshFromCrispasrRegistry() — calls CrispasrSession.availableBackends() + crispasr.registryLookup per backend, merges every linked backend's canonical GGUF into the model picker without any CrisperWeaver code change. Runs on every Model Management screen open. |
Archived 2026-06-12: Shipped sections (§5.1 shipped items, §5.8 shipped items, §5.9, §5.23, §5.24 A/B/C/F-text-LID/G, §5.25, §5.26) moved to HISTORY.md → "June 2026 — Shipped items archived from PLAN.md". Earlier §5.2–§5.7, §5.11–§5.21 write-ups were already in HISTORY.md.
Most of §5.1 is shipped — full write-ups in HISTORY.md. Only pending items below.
-
Platform-native share / receive — remaining tail:
- Windows file association: manual smoke test on a real Windows machine still pending.
-
5.1.9 Subtitle burning into video — User selects a video file + transcript, gets a video with hardcoded subs. FFmpeg subprocess. ~1 day desktop-only. Misaligned with the cross-platform "no FFmpeg on the editing path" line we've held everywhere else — would need a Dart-side ffmpeg-kit wrapper or a pure-Dart muxer to fit. Deferred until either exists.
The sibling glint repo (../glint, MIT, clean-room, dependency-free)
is bundled as libglint so the app encodes/decodes MP3 / AAC-LC /
Opus on-device without an external ffmpeg binary — closing the
long-standing "WAV-only export, ffmpeg-punt decode" gap.
- Dart layer:
lib/native/glint_native{,_stub,_import}.dart(the same conditional-import pattern asvad_native; web → stub) →GlintCodecService(isAvailable,encodePcm[ToFile],decodeBytes,canDecodePath).dart:ffistays inside the wrapper so web still compiles. - Decode:
AudioServicetries glint for.mp3/.aac/.opus/.oggbefore the ffmpeg + Android-MediaCodec fallbacks; any failure falls through unchanged. - Encode:
AudioEditService.exportEncoded(...)writes a compressed file (optionally a[startSec,endSec)slice); WAV output paths are untouched. - Native bundling:
libglint.{dylib,so,dll}/glint.xcframeworkbuilt + shipped per platform byscripts/build_{macos,linux,windows}.sh, thebundle_*scripts, andscripts/build_ios_glint_xcframework.sh+wire_ios_glint.rb. Release CI (.github/workflows/release.yml) checks out../glintand rebuilds the lib fresh for all five platforms, mirroring the CrispASR steps. Everything degrades gracefully — when the lib isn't present,isAvailableis false and callers keep the WAV path. - iOS uses
DynamicLibrary.process(), so the framework is linked into Runner (wire script) rather than dlopen'd by name.
Verified end-to-end on macOS: MP3 + Opus encode→decode round-trips against
the real libglint.dylib (test/glint_codec_test.dart). Other platforms
build in CI.
- Cloud sync (high effort, splits the privacy story)
- Web UI on top of the HTTP server (desktop app covers this audience already)
- Final Cut / Premiere XML export (real niche)
- Voice commands during recording (low value vs. UX complexity)
Most of §5.8 is shipped — see HISTORY.md. What's still pending (low priority — v1 covers the common case):
- Alt-token follow-ups:
- Beam-search alt capture. Beam siblings are beam-conditional, not greedy alternatives, so v1 only fires on greedy. A meaningful beam-aware "show the K sibling beams' divergence point" UX would need a different whisper-side capture path and a different chip shape (sibling-walk rather than tap-to-pick). Defer until a user actually asks.
- Full word-level alt enumeration. Whisper tokens are
sub-word BPE, so a multi-token word like "kubectl" →
["kub","ect","l"]only surfaces alts for the first content token ("kub" → "cub" / "tu" / …). Computing alternative WHOLE words would require a per-word token-tree expansion — out of scope for v1; the first-token alts cover most real ambiguity in practice. Documented in the C-ABI helper comment + the UI help string.
MTLBinaryArchive pipeline cache shipped (38× cold-start speedup) — see HISTORY.md.
Still pending:
| Win | Projected speedup | Status |
|---|---|---|
CoreML for whisper on Apple Silicon (WHISPER_USE_COREML=1 + paired .mlmodelc) |
Whisper-tiny already 6 s; large-v3 → 2–3× | Deferred to a future CrispASR cycle |
| Re-download q4_k variants for vibevoice / orpheus | vibevoice 17:22 → ~4 min projected; orpheus 11:50 → ~5 min | vibevoice q4_k available (636 MB, baked June 2026); orpheus q4_k still pending on HF |
Static audit + xcframework bundling + plist cleanup all shipped — see HISTORY.md. What's left needs an iPhone:
- Mic permission prompt — First
record.hasPermission()must show the system mic prompt (NSMicrophoneUsageDescriptionalready set). Verify initial-grant + "denied → Settings → toggle on" recovery. - Streaming mic —
AudioRecorder.startStreamwith PCM16 @ 16 kHz is documented as iOS-supported but only the macOS path has been exercised. Confirm sub-second chunk cadence + live heartbeat. - Recording → playback transitions —
just_audioconfigured withAudioSessionConfiguration.speech(); needs on-device confirmation that switching mic → file → mic is smooth. - Background audio continuation —
UIBackgroundModes = [audio]declared; verify streaming mic survives screen-lock. - Share intake — "Open in CrisperWeaver" from Files / Mail
delivers a path through
receive_sharing_intent; verify the path is readable (security-scoped) and picked up correctly. FilePicker.pickFiles— UIDocumentPicker copies to temp; verify the returned path is openable byjust_audio.- CoreML companion
.mlmodelc— verifygetApplicationDocumentsDirectory()is writable for the unzip target and that the companion actually loads ("Loading Core ML model" in libwhisper logs). — shipped. Full manifest atPrivacyInfo.xcprivacyios/Runner/PrivacyInfo.xcprivacydeclaring NSUserDefaults (CA92.1), FileTimestamp (C617.1), DiskSpace (E174.1), SystemBootTime (35F9.1). Audio data declared as AppFunctionality, not linked, not tracked.
Risk: medium-high. Item 1 (xcframework bundling) was the only launch-blocker and is done. The rest are quality issues that surface in use.
Pre-existing detail on the xcframework bundling + auto-fix audit: HISTORY.md → "iOS feature parity".
- Tag-based code signing for macOS + notarization (currently ad-hoc sign only).
- Signed Android APK.
- Windows MSI / EXE installer.
Shipped items (A, B, C, F text-LID, G) archived to HISTORY.md on 2026-06-12.
D. Verification matrix — still pending:
- indextts clone audio — roundtrip with indextts as the TTS leg + a reference WAV.
- voxcpm2 clone audio — roundtrip with voxcpm2 + a reference WAV.
- madlad + m2m100-wmt21 translation — add an opt-in translate live test (translate a known phrase, assert target-language keywords); confirm output is sane.
- Android ANR (lazy whisper load + chunked pool) and iOS kokoro (espeak-ng) still need on-device runs (see §5.22).
E. Process. Each new engine backend follows: CrispASR dispatch (in a
worktree) → app ModelDefinition + BackendRepo → drop from guard
pending after the dylib rebuild → release. The guard test fails any
catalogue entry whose backend has no dispatch arm, so catalogue and
engine can't silently drift.
G. MOSS-Diarize + MOSS-TTS — DONE (2026-07).
Both already compiled into the CrispASR flutter-bundle lib (unconditional
add_library + auto-pulled into crispasr-lib), so this was pure catalog
wiring: moss-diarize (backend moss-diarize, repo
cstr/MOSS-Transcribe-Diarize-GGUF) — single-pass ASR + speaker
diarization + timestamps, surfaces as an ASR model with the Ask +
source-language fields enabled; and moss-tts (backend moss-tts, repo
cstr/moss-tts-v1.5-GGUF) — voice-cloning TTS (Qwen3-8B) with the
moss-tts-v1.5-codec companion (loaded via setCodecPath) + a reference
voice WAV. Added: 3 ModelDefinitions, 2 recommendedDefaultModels, 2
BackendRepos, kindForBackend tts entry, capability sets, and the
build-script BACKEND_TARGETS for early-error parity. Live download +
run validation pending (moss-tts is desktop-class: ~5 GB + ~3.4 GB codec).
H. CrispASR 0.8.10 backend catch-up — DONE (2026-07, v0.9.1).
The backend_dispatch guard (runs only with the real dylib) flagged
engine backends the catalog had drifted behind on. Added omnivoice
(TTS, +tokenizer companion), irodori-tts (TTS-JA, +DAC-VAE companion),
voxtral-tts (TTS, CC-BY-NC-4.0), canary-qwen (ASR), and completed the
partial nemotron (ASR) entry; fixed cosyvoice3-tts kindForBackend
classification. The dispatch guard's allowlists were reconciled:
tada-1b/tada-tts-1b/tada-3b-ml + reazonspeech are engine dispatch
aliases (→ engineOnly), and canary-ctc-aligner is an
AlignerService-consumed forced-aligner, never session-dispatched (→
permanent pending exemption). Nemotron ASR live-verified on jfk.wav.
I. detectBackendFromGguf binding bug — FIXED (2026-07, v0.9.1).
The crispasr package's detectBackendFromGguf checked if (rc != 0) return null, but the C ABI returns strlen(name) (>0) on a match / 0 on
no-match — so it returned null for EVERY detected backend, silently
killing the #30 GGUF auto-routing and ModelService's post-download
correction. CrisperWeaver now calls the ABI directly with the correct
contract via lib/native/crispasr_detect_native.dart; the engine +
ModelService use it. Verified live: the real cohere-transcribe-arabic
GGUF resolves to cohere and transcribes through the cohere arm (#30).
F. bidirlm-omni embedding — DONE (§12.3b, §12.8f).
bidirlm-omni-2.5b wired end-to-end: catalog entry, CrispEmbed
stub parity (encodeAudio, hasAudio), SemanticSearchService
cross-modal scoring, HistoryService.computeAudioEmbedding() path
verified with JSON round-trip test. Live validation pending on a
machine with the 1.7 GB omni model downloaded.
Three-step recipe:
- In
CrispASR/src/CMakeLists.txt, in the Dart FFI multi-backend linkage section, add:if (TARGET canary) target_link_libraries(whisper PUBLIC canary) endif()
- Add a
#if __has_include("canary.h")block toCrispASR/src/crispasr_dart_helpers.cpp, plus acase "canary":arm incrispasr_session_open_explicitandcrispasr_session_transcribe. cmake --build build --target whisperand copy bothlibwhisper.dylib+libcanary.dylibinto the app bundle'sContents/Frameworks/.
CrisperWeaver picks up new backends automatically through CrispasrSession.availableBackends() — no Dart changes needed. If the user picks a backend the bundled libwhisper wasn't linked with, the load error names exactly which backends ARE available.
Built-in (round 5, May 2026): CrisperWeaver ships its own
Dart-side shelf HTTP server, toggleable from Settings → Local
HTTP server. Bound to 127.0.0.1:8765 by default. Exposes:
POST /v1/audio/transcriptions(multipart upload, OpenAI-shaped json/text/srt/vtt response) — routes through the loaded ASR.POST /v1/audio/speech(JSON in, 24 kHz mono WAV out) — routes throughTtsService.POST /v1/translations(JSON in, JSON out) — routes throughTextTranslationService.GET /health(liveness check).
This is the parity path for both desktop and mobile (iOS can't spawn subprocesses), and avoids loading two copies of every backend into RAM.
CLI alternative (still available for advanced users): CrispASR
ships an HTTP server binary (examples/cli/crispasr_server.cpp)
with POST /inference, POST /v1/audio/transcriptions (OpenAI-
compatible), POST /load, GET /backends. Desktop builds could
bundle the crispasr binary and spawn it in server mode for users
who want process isolation or fewer dylibs. We don't bundle it —
the in-app server already covers the use case end-to-end.
Audit performed June 2026 against 111 Dart source files (~63 K
LOC), 67 test files (~13 K LOC), 51 widget/screen classes, and
259 setState call sites across 22 files.
The four largest non-generated files concentrate too much logic in single compilation units, making navigation, review, and incremental rebuilds harder.
| File | Lines | Problem | Proposed split |
|---|---|---|---|
lib/services/model_service.dart |
5 696 | Static model catalog + download/verify/delete logic + language lists all in one class | model_catalog.dart (static const model definitions + language lists), model_download_service.dart (download, checksum, unzip, resume), model_service.dart (thin public API facade) |
lib/screens/transcription_screen.dart |
3 858 | 29 setState calls, 3 inner widget classes (_PresetsDialog, _NarrowTabbedBody, plus the main state), complex init/load/transcribe state machine |
Extract _PresetsDialog → lib/widgets/presets_dialog.dart, _NarrowTabbedBody → lib/widgets/narrow_tabbed_body.dart. Break _TranscriptionScreenState build method into composable sub-widgets |
lib/services/baked_models_catalog.dart |
3 746 | Auto-generated static catalog data compiled into the binary | Move to a bundled JSON asset (assets/models/catalog.json) loaded at runtime. Keeps binary smaller, enables OTA catalog updates without a code release. Update scripts/bake_models_catalog.dart to emit JSON instead of Dart |
lib/widgets/transcription_output_widget.dart |
2 372 | 21 setState calls, 2 inner dialog classes (_CleanupDialog, _SummarizeDialog) |
Extract _CleanupDialog → lib/widgets/cleanup_dialog.dart, _SummarizeDialog → lib/widgets/summarize_dialog.dart |
Results (June 2026):
| File | Before | After | Extracted to |
|---|---|---|---|
model_service.dart |
5 696 | 1 396 | model_catalog.dart (4 318) |
transcription_output_widget.dart |
2 372 | 1 770 | cleanup_dialog.dart (295), summarize_dialog.dart (334) |
transcription_screen.dart |
3 858 | 3 533 | presets_dialog.dart (237), narrow_tabbed_body.dart (93) |
baked_models_catalog.dart |
3 746 | — | Deferred — generated data, low risk, JSON migration is separate work |
Migrated the 7 worst-offender screens/widgets from setState to
Riverpod Notifier providers with immutable state classes +
copyWith(). Total setState calls: 288 → 116 (−60%).
| Screen / Widget | Before | After | Provider file |
|---|---|---|---|
synthesize_screen.dart |
39 | 0 | synthesize_screen_provider.dart |
settings_screen.dart |
30 | 3 | (uses SettingsService directly) |
transcription_screen.dart |
28 | 0 | transcription_screen_provider.dart |
audio_recorder_widget.dart |
18 | 0 | audio_recorder_provider.dart |
speaker_management_screen.dart |
17 | 16 | speaker_management_provider.dart |
edit_audio_screen.dart |
16 | 0 | edit_audio_provider.dart |
translate_screen.dart |
14 | 3 | translate_screen_provider.dart |
Remaining 116 setState calls are in smaller files not in scope
(voice_clone_wizard, model_management, history, dialogs, etc.) and
the _EnrolSpeakerScreen inner widget (kept as local dialog flow).
Tests: 7 new unit test files (143 tests total) covering all
provider notifiers via ProviderContainer. Full suite: 838 pass,
0 fail, 63 skipped (live).
Widget files under lib/widgets/ have reasonable const usage
(363 occurrences) but screens do not. Key patterns to fix:
- Padding, SizedBox, Divider, Icon, and Text literals inside
build()that never change should useconst. - Extracted child widgets (from 8.2) should declare
constconstructors wherever possible. - Static decoration objects (
BoxDecoration,InputDecorationtemplates) should bestatic constclass members, not rebuilt every frame.
Run dart fix --apply after each batch — the
prefer_const_constructors lint catches most of these
automatically.
analysis_options.yaml already enables prefer_const_constructors,
prefer_const_declarations, and prefer_const_literals_to_create_immutables
as lint rules. dart fix --dry-run reports 0 fixable issues. No action
needed.
baked_models_catalog.dart was 3 746 lines of compiled-in static
ModelDefinition data. Migrated to a JSON asset loaded at runtime.
Changes:
| Action | Files |
|---|---|
Added fromJson/toJson to ModelDefinition |
lib/services/model_catalog.dart |
| Bake script now emits JSON | scripts/bake_models_catalog.dart → assets/models/catalog.json |
| New async loader with caching | lib/services/baked_catalog_loader.dart |
Wired into ModelService + startup |
lib/services/model_service.dart, lib/main.dart |
| Deleted old compiled catalog | lib/services/baked_models_catalog.dart (−3 746 LOC) |
Tests: 13 unit tests in test/baked_catalog_json_test.dart (JSON
round-trip, enum serde, defaults, full catalog load, key consistency,
loader lifecycle) + 2 live tests in test/baked_catalog_live_test.dart
(HF probe parity, catalog entry verification).
Language-list constants stay in Dart (small, compile-time referenced). OTA catalog updates are now feasible as a future enhancement.
49 service files. Most are accessed via ref.read() from
screens, but some niche services may be eagerly initialised:
AudioFingerprintService— only needed when watch-folder or dedup is active.AudioWatermarkService— only needed in the audio editor.SystemAudioCaptureService— only needed when system-audio recording is toggled on.ChapterDetectionService— only needed on export.AbTestService— only needed when the user runs a model comparison.
Audit: verify these are behind lazy Providers (not
ref.read() in initState of always-mounted widgets). If
any are eager, wrap them in Provider.autoDispose or gate
behind a feature flag check.
Result: all five services are standard Riverpod Providers
(lazy by default). None are referenced in main.dart or
eagerly initialized. No action needed.
The app depends on both dio (model downloads with progress +
resume) and http (lighter requests). This doubles the HTTP
stack in the dependency tree.
Options (pick one):
- Keep
dioonly — replace the ~5httpcall sites withdioequivalents.dioalready handles the hard cases (progress, resume, interceptors). - Keep
httponly — rewrite thediodownload logic withhttp.Client+ manual byte-stream progress. More work, smaller dependency.
Recommendation: keep dio, drop http. The download-resume
logic in model_service.dart is the critical path and dio
handles it natively.
Priority: low — small binary-size win, reduces dependency surface.
Status (June 2026): deferred. Only 3 files use http
(audio_service.dart, cloud_llm_cleanup_service.dart,
transcript_summarize_service.dart) with well-tested MockClient
infrastructure (20+ test cases). Migrating to dio would require
rewriting all mock infrastructure for modest gain. Not worth the
churn.
Started at 595 tests across 67 files. Added 100 new tests in 8 files, reaching 695 tests across 75 files (+17%).
New test files added:
| File | Tests | Covers |
|---|---|---|
model_service_lookup_test.dart |
18 | Model resolution chain, catalog data classes, kindForBackend, resolveLanguageCodes |
ab_test_service_test.dart |
11 | A/B result wins/ties/overallWinner, ModelRatings leaderboard + accumulation |
speaker_vocab_test.dart |
12 | JSON round-trip, mergeForSpeakers, file save/load/delete/listAll |
segment_tag_test.dart |
8 | Enum JSON serialization, list round-trip, unknown/null handling |
watch_folder_service_test.dart |
7 | start/stop lifecycle, extension filter, debounce detection |
log_service_test.dart |
11 | LogEntry formatting, LogLevel ranking, singleton stream/snapshot |
audio_utils_test.dart |
25 | Audio math (RMS, peak, silence, normalize, stereo→mono, sine wave, float32↔bytes) |
file_utils_test.dart |
8 | sanitizeFilename, generateUniqueFilename |
Remaining gaps (widget tests for screens — higher effort, lower priority than the pure-logic coverage above):
| File | Risk | Notes |
|---|---|---|
transcription_screen.dart |
Medium | Complex state machine; needs full Riverpod + l10n scaffolding |
synthesize_screen.dart |
Medium | 39 setState calls; speaker selection logic is unit-tested |
| Extracted dialogs | Low | Services behind them are well-tested |
The C++ native layer (CrispASR) dominates build time. Beyond
the existing CCACHE_DIR=/mnt/volume1/.ccache ninja convention:
- Pre-built native binaries for CI — the
analyze-and-testjob checks out CrispASR for the path dependency but does not build it. Release jobs build from source. Consider caching the builtlibcrispasrartifact between CI runs (keyed on CrispASR commit hash) to skip redundant rebuilds. - Incremental builds — verify
build_all.shdoes not clean before building. Acmake --buildwith ninja already does incremental; only clean on toolchain or CMake-variable changes. - Dart side —
flutter buildis already incremental. The main Dart-side build cost is the generated localizations (flutter gen-l10n) and thebuild_runnerJSON serialization; both are fast (<5 s).
Result: actions/cache@v4 added to both macOS and Linux build
jobs in ci.yml, keyed on CrispASR commit hash. Cache hit skips
the full native rebuild (~15-30 min saved).
| Directory | Size | Action |
|---|---|---|
assets/images/ |
1.4 MB | Verify images are compressed (WebP for raster, SVG where possible). Check for unused splash/icon variants |
assets/vad/ |
872 KB | Silero GGUF — already minimal |
assets/espeak-ng-data/ |
4 KB (placeholder) | No action — real data extracted at runtime on Android |
assets/models/ |
4 KB | Metadata only — no action |
Result: optipng -o2 lossless compression applied:
app_logo.png 1.4 MB → 883 KB (37%), AppLogo.png 1.8 MB → 1.2 MB
(31%). Both are the only images; no unused variants found.
Done: 8.1 (file splits), 8.2 (setState reduction — 288→116, +143 tests), 8.3 (const — already enforced), 8.4 (catalog as JSON — −3 746 LOC, +13 tests), 8.5 (lazy init — verified), 8.7 (test coverage — +100 tests), 8.8 (CI caching), 8.9 (asset compression). Deferred: 8.6 (HTTP consolidation — not worth test churn).
Goal (from owner): match CrispASR's capabilities where feasible;
unit + live tests for every feature; achieve CLI + HTTP-server
feature parity with the GUI; and map/resolve code paths reachable
from neither surface (orphans). Live tests must use only q4_k
models already on disk under /Volumes/backups/ai/crispasr/ — never
download anything >500 MB. This section is the canonical task tracker:
if work breaks, resume from the unchecked boxes.
Ground truth established (2026-06-20): the live-test chain works on
this machine — crispasr_live_test passes against the locally-built
../CrispASR/build/src/libcrispasr.dylib (v0.7.1) + on-disk
ggml-tiny.bin. So every gap below is fillable now with existing
models; no downloads needed.
-
test/support/crispasr_models.dart— single locator: resolves the dylib (CRISPASR_LIB→../CrispASR/build*/src/lib*.dylib) and the smallestq4_kmodel per family fromCRISPASR_MODELS_DIR(default/Volumes/backups/ai/crispasr), withCRISPASR_TEST_<X>_MODELoverrides. Returns null → tests self-skip. Replaces the per-file_resolveLibPathcopies. -
scripts/run_live_tests.sh— exportsCRISPASR_LIB+CRISPASR_MODELS_DIRand runsflutter test --tags slow(TMPDIR on the external volume per disk policy). - Exemplar live test
test/vad_live_test.dart— green (3/3: decode, Silero dispatcher, whisper-vad q4_k). Pattern locked. Two gotchas surfaced & encoded for all live tests: 1. TheCrispASR(modelPath)ctor loadsmodelPathas a whisper ASR context. Open the ctx on a real ASR model (tiny); pass auxiliary models (VAD/LID/punc) only as method args, elsedispose()SIGABRTs. 2. Verify the entrypoint against the C source —vad()(legacycrispasr_vad_segments) fails -2 on Silero/whisper-vad; the working call isvadSlices()(crispasr_vad_slices).
Each uses the 9.1 locator + smallest q4_k on disk; self-skips when
absent. Validate with flutter analyze + skip-mode run; the heavy
live decode pass is run serially afterward (no concurrent GPU thrash).
- LID live —
test/lid_live_test.dart(audio LID → 'en' via whisper tiny ctx; text LID → English via GlotLID/CLD3). Green. - VAD live —
test/vad_live_test.dart(Silero asset + whisper-vad q4_k viavadSlices). Green. - Diarization live —
test/diarization_live_test.dart(pyannote-seg labels tiny-ASR segments). Green after fixing alib: nullbug (diarizeSegments is a top-level fn taking aDynamicLibrary, not a libPath string — must passDynamicLibrary.open(lib)). - Aligner live —
test/aligner_live_test.dart(canary-ctc-aligner q4_k; forced word timings, monotonic, in-bounds). Green. - Punctuation / PCS / truecase live —
test/punc_live_test.dart(fireredpunc q4_k; truecase/PCS self-skip — models not on disk). Green. - Streaming ASR live —
test/streaming_asr_live_test.dart(crispasr_stream_*1 s chunks → committed transcript w/ JFK keyword). Green (dropped a brittle window-time upper bound — the rolling-buffer t1 isn't bounded by clip duration). - Alt-ASR-backend roundtrips —
test/alt_asr_backends_live_test.dart(moonshine-tiny, sensevoice, parakeet-tdt_ctc-110m, fastconformer- ctc, wav2vec2-xlsr — all 5 transcribe jfk.wav). Green (6/6). - Translation live —
test/translation_live_test.dart(madlad q4_k EN→DE + EN→FR). Green — but ~42 min wall-clock (madlad 3B q4_k beam-decode is very heavy; didn't OOM, just slow). Impractical for routine CI; consider the lighter m2m100 q8 (502 MB) as the default and keep madlad as an opt-in — owner call (m2m100 is q8, which bends the "q4_k-only" rule, but there's no small q4_k MT on disk). - Watermark detect live —
test/watermark_live_test.dart(pure DSP, no model: embed→detect margin + threshold). Green. Covers the §9.5 orphaned watermark-detect capability. - Local-LLM cleanup/summarize live —
gemma4-e2b-it-q4_k(2.6 G, heavy; lower priority) - Speech-to-speech live — lfm2-audio-1.5b-q5_k on Kaggle P100 (27.4x RT, lower priority)
- [~] Audit each
lib/services/*.dartfor pure-Dart logic lacking a unit test; add tests (no model) for the untested ones. Done so far:audio_watermark_format_test.dart(12),audio_utils_dsp_test.dart(16),audio_fingerprint_coarse_test(10) — 38 new leaf-level tests, all green. BLOCKED for the rest: see §9.7 — the broken CrispEmbed path dep makes any test that imports the engine graph fail to compile (multilingual grouping, file_utils generators, server SRT/VTT, note_export, etc. are written-able but not runnable until fixed). - Widget tests for the two flagged screens (§8.7): transcription, synthesize.
The broken class in CrispEmbed (CrispSwinirSr unterminated body) has
been fixed upstream. flutter analyze passes clean with the path dep.
The full flutter test suite is now runnable. CrispASR rebuilt to
v0.8.0 (libcrispasr.so.0.8.0).
-
bin/crisperweaver.dart— first-class CLI overpackage:crispasr(no Flutter coupling; runs viadart run). COMPLETE, smoke-tested live:backends,transcribe(+--srt/--vtt,--temperature,--best-of,--hotwords,--seed,--max-new-tokens,--frequency-penalty,--beam-size,--ask,--translate,--vad,--word-timestamps,--target-language),stream(+--hotwords,--temperature),vad,lid(audio+--text),diarize,align,speaker(enroll/match),punctuate,translate,synthesize(+--voice,--temperature,--seed),s2s,watermark(embed/--detect). Verified e.g.alignword timings,speaker match → jfk 1.000,streamfull JFK quote. - [~] Expand
lib/services/server_service.dartbeyond the 4 original endpoints. ADDED + tested (test/server_service_test.dart):POST /v1/audio/vad,/v1/audio/language(LID),/v1/text/punctuate,/v1/audio/diarize,/v1/audio/watermark(detect + embed),/v1/audio/align(§10, forced alignment with language-aware model selection),/v1/text/language(§10, text-LID via CLD3/GlotLID/FastText-176),/v1/audio/denoise(RNNoise),/v1/audio/s2s(speech-to-speech via lfm2-audio/mini-omni2). WebSocket streaming (/v1/audio/stream) — shipped: binary PCM in → JSON transcript segments out. Generation controls on the transcription endpoint — shipped:temperature,best_of,prompt,hotwords,translate,vad,diarize,punctuationform fields. Remaining: speaker (stateful device DB). - Unit/smoke test for the CLI —
test/cli_test.dart(help lists all commands, usage-error exit codes). Per-capability behaviour is covered by the*_live_test.dartfiles (same binding the CLI wraps). -
docs/PARITY.md— capability × {GUI, CLI, server} matrix + orphan notes + remaining-work list.
Reachability audit flagged these as reachable from no surface; grep showed several were false positives (hotwords, system-audio-capture, text-LID, S2S, aligner are referenced). Confirm each and either wire it or document it as intentionally internal:
- Watermark detect — resolved: reachable via CLI
(
watermark --detect) + server (/v1/audio/watermark) + live test. Only remaining gap is an optional GUI "verify" button (minor UX). - Speech-to-speech — verified reachable:
synthesize_screen.darthas ans2sModetoggle + audio input and callstts.speechToSpeech(). Plus CLIs2s. Not orphaned. - Aligner — verified reachable: the Settings word-timestamps
toggle (
settings_screen.dart) drivesenableWordTimestamps, andcrispasr_engine.dartrunsAlignerService.addWordTimestampswhen the backend emits no word timing. Plus CLIalign. Not orphaned. - VadService silent no-op — FIXED (2026-06-20). Now calls
vadSlicesNative(newlib/native/vad_native.dart, conditional web stub) → the freecrispasr_vad_slicesdispatcher, with no whisper context at all. Regression-tested invad_live_test.dart("vadSlicesNative … without a ctx"). Original two defects were: 1. Wrong entrypoint — it callsCrispASR.vad()=crispasr_vad_segments(whisper's native VAD loader), which returns -2 "model init failed" for the bundled Silero v6.2.0 asset AND the whisper-vad q4_k model. The exception is caught andconst []returned → no spans, ever. The working path isCrispASR.vadSlices()=crispasr_vad_slices(the unified dispatcher that handles Silero/FireRed/MarbleNet/ whisper-vad). Fix: switch VadService to vadSlices(). 2. Unsafe dispose — it doesCrispASR(vadModelPath)then.dispose(); the ctor loadsmodelPathas a whisper ctx, sowhisper_freeover a non-whisper ctx SIGABRTs (reproduced in flutter_tester). Open the ctx on the ASR model and pass the VAD model only tovadSlices(modelPath:). Confirm both against the app's actually-bundled dylib before shipping a fix (the in-app lib may predate this behaviour). - Final orphan list recorded in
docs/PARITY.md(§9.5 "Orphan audit — resolved"). Net result: no true orphans; only an optional GUI watermark-verify button remains as minor UX.
From CrispASR HISTORY (May–Jun 2026), not yet in CrisperWeaver:
- Streaming token callbacks for LLM-based ASR backends (#157) — per-segment (§12.8i) + per-token (12 backends)
- Wyoming protocol server (Home Assistant) (#172) — shipped §12.8h
- Local TTS speaker playback (#173) — preview button on speaker dropdown
- Global-scope diarization (pyannote/sherpa) (#110) —
diarizeFullAudio()method - SenseVoice emotion/event tags — parsed from
<|HAPPY|>etc. in transcript text, stripped from display, surfaced asemotion/audio_eventin segment metadata + orange/teal badges in TranscriptionOutputWidget. - Paraformer-zh — live test added (
paraformer_zh_live_test.dart). Already in catalog; now validated end-to-end. - WMT21 translation — live test added to
translation_live_test.dart(EN→DE, EN→FR via wmt21-dense-24-wide-en-x). (Surface + test only if owner prioritises; otherwise leave tracked.) - §10 catalog + aligner pipeline (canary-ctc-aligner + 10 wav2vec2
language variants + language-aware AlignerService + GUI aligner
picker + CLI
--language+ serveralignerparam) — archived to HISTORY.md 2026-06-21. - CLI
denoisecommand (RNNoise pre-processing, matches GUI's enhanceAudio toggle). - Server
/v1/audio/align+/v1/text/languageendpoints. - SenseVoice tag unit tests (13 tests) + aligner map unit tests.
- Server endpoint validation tests (3 new, 11 total).
Gap analysis performed 2026-06-21 by diffing every k_registry[] entry
in ../CrispASR/src/crispasr_model_registry.cpp (v0.7.1, 129 entries)
against every ModelDefinition + BackendRepo in
lib/services/model_catalog.dart.
Outcome: CrisperWeaver already covers 118/129 registry entries — the Dart FFI bindings are at 149/149 C-ABI symbol parity. Only 11 downloadable GGUFs have no catalog entry. Everything else (nemotron, parakeet variants, chatterbox-turbo, kartoffelbox-turbo, kartoffel- orpheus, gemma4-e2b, mega-asr, qwen3-1.7b ASR, qwen3-tts-customvoice, voxcpm2-tts, cosyvoice3, pocket-tts, tada, gwen-tts, melotts-v3, moonshine-de, omniasr-ctc-300m, etc.) was already present under slightly different catalog keys.
| # | Backend (CrispASR) | GGUF file | Kind | Gap |
|---|---|---|---|---|
| 1 | canary-ctc-aligner |
canary-ctc-aligner-q4_k.gguf (~442 MB) |
ASR (aligner) | No ModelDefinition or BackendRepo |
| 2 | wav2vec2-aligner-fr |
wav2vec2-large-xlsr-53-french-q4_k.gguf (~300 MB) |
ASR | No wav2vec2 FR entry at all |
| 3 | wav2vec2-aligner-es |
wav2vec2-large-xlsr-53-spanish-q4_k.gguf (~300 MB) |
ASR | No wav2vec2 ES entry |
| 4 | wav2vec2-aligner-it |
wav2vec2-large-xlsr-53-italian-q4_k.gguf (~300 MB) |
ASR | No wav2vec2 IT entry |
| 5 | wav2vec2-aligner-ja |
wav2vec2-large-xlsr-53-japanese-q4_k.gguf (~300 MB) |
ASR | No wav2vec2 JA entry |
| 6 | wav2vec2-aligner-zh |
wav2vec2-large-xlsr-53-chinese-zh-cn-q4_k.gguf (~300 MB) |
ASR | No wav2vec2 ZH entry |
| 7 | wav2vec2-aligner-nl |
wav2vec2-large-xlsr-53-dutch-q4_k.gguf (~300 MB) |
ASR | No wav2vec2 NL entry |
| 8 | wav2vec2-aligner-pt |
wav2vec2-large-xlsr-53-portuguese-q4_k.gguf (~300 MB) |
ASR | No wav2vec2 PT entry |
| 9 | wav2vec2-aligner-ar |
wav2vec2-large-xlsr-53-arabic-q4_k.gguf (~300 MB) |
ASR | No wav2vec2 AR entry |
| 10 | wav2vec2-aligner-cs |
wav2vec2-xls-r-300m-cs-250-q4_k.gguf (~300 MB) |
ASR | No wav2vec2 CS entry |
| 11 | wav2vec2-aligner-uk |
wav2vec2-xls-r-300m-uk-with-small-lm-q4_k.gguf (~300 MB) |
ASR | No wav2vec2 UK entry |
Not gaps (confirmed present under different catalog keys):
- wav2vec2-aligner / wav2vec2-aligner-en → same GGUF as existing
wav2vec2entry - wav2vec2-aligner-de → same GGUF as existing
wav2vec2-deentry - LID backends (lid-cld3/ecapa/firered/silero/glotlid/fasttext176) →
already catalogued with generic
backend: 'lid'(cld3-f16, ecapa-lid-107-f16, etc.)
-
test/crispasr_07x_parity_catalog_test.dart— pins the 11 new entries (canary-ctc-aligner + 10 wav2vec2 language variants) -
test/lid_dispatch_live_test.dart— exercises each LID dispatcher backend (Silero audio-LID, ECAPA, CLD3 text-LID, GlotLID, FastText176) viadetectTextLanguageanddetectLanguage; self-skips when model not on disk -
test/canary_ctc_aligner_live_test.dart— forced alignment via the new canary-ctc-aligner catalog entry; validates monotonic word onsets
CrispASR §206 changed LFM2-Audio to default to CPU (GPU backbone had Metal miscomputes). CrisperWeaver has no per-backend GPU override UI, so the engine-side default is authoritative — no CrisperWeaver change needed. Documented here for awareness.
Gap analysis performed 2026-06-30 by diffing the full 246-commit history between CrispASR v0.8.0 (2026-06-22) and HEAD (8fd9db8f). CrisperWeaver v0.8.4 shipped against CrispASR 0.8.0; since then CrispASR has added 4 entirely new backends, 3 new model variants for existing backends, and ~15 new capabilities/API surfaces not yet wired through the Dart layer.
Seven backends/variants exist in CrispASR but have no ModelDefinition
or BackendRepo in lib/services/model_catalog.dart:
| # | Backend | Type | GGUF | HF repo | Size | Companions | Notes |
|---|---|---|---|---|---|---|---|
| 1 | dots-tts |
TTS | dots-tts-soar-f16.gguf |
cstr/dots-tts-soar-GGUF |
~4.4 GB | vocoder dots-tts-soar-vocoder-f16.gguf (~345 MB), spk encoder dots-tts-soar-spk-f16.gguf (~15 MB) |
CAM++ voice cloning, 48 kHz, Metal GPU |
| 2 | higgs-stt |
ASR | higgs-stt-q4_k.gguf |
cstr/higgs-audio-v3-stt-GGUF |
~2.3 GB | none | Whisper-v3 enc + Qwen3-1.7B dec, internal chunking, beam search, --ask |
| 3 | ark-asr |
ASR | ark-asr-3b-q4_k.gguf |
cstr/ark-asr-3b-GGUF |
~2.2 GB | none | Whisper-RoPE enc + Qwen2.5-3B dec, 19 langs, cross-chunk lang conditioning. NB: registry says placeholder URL |
| 4 | moss-transcribe |
ASR | moss-transcribe-preview-2b-q4_k.gguf |
cstr/MOSS-Transcribe-preview-2B-GGUF |
~1.6 GB | none | Qwen3-Omni enc + Qwen3-1.7B dec, native punctuation, beam search, streaming |
| 5 | gemma4-e4b |
ASR | gemma4-e4b-it-q4_k.gguf |
cstr/gemma4-e4b-it-GGUF |
~4.1 GB | none | Larger Gemma4 decoder (42L×2560), reuses gemma4-e2b backend |
| 6 | reazonspeech |
ASR | reazonspeech-nemo-v2-q8_0.gguf |
cstr/reazonspeech-nemo-v2-GGUF |
~704 MB | none | Japanese RNNT (619M), reuses parakeet backend, Q8_0 (quant-sensitive) |
| 7 | parakeet-ctc-1.1b-ja |
ASR | parakeet-ctc-1.1b-ja-q8_0.gguf |
cstr/parakeet-ctc-1.1b-ja-GGUF |
~1.2 GB | none | Japanese FastConformer-CTC 1.1B, reuses parakeet backend, Q8_0 |
Each needs: ModelDefinition entry, BackendRepo entry (if new backend),
kCanonicalModel entry, language list, and a catalog unit test.
- dots-tts (ModelDefinition + vocoder + spk-encoder companions + BackendRepo)
- higgs-stt
- ark-asr
- moss-transcribe
- gemma4-e4b (shares gemma4-e2b BackendRepo — add variant ModelDefinition only)
- reazonspeech (shares parakeet BackendRepo — add variant ModelDefinition only)
- parakeet-ctc-1.1b-ja (shares parakeet BackendRepo — add variant ModelDefinition only)
- Unit test:
test/crispasr_08x_parity_catalog_test.dart
Capabilities added to CrispASR backends since 0.8.0 that need Dart-side FFI stubs + engine/UI wiring:
| # | Capability | Backends | FFI stub? | Engine call? | UI? | Work needed |
|---|---|---|---|---|---|---|
| 1 | setBeamSize(n) (beam width int) |
whisper, canary, cohere, higgs-stt, ark-asr, moss-transcribe | ✅ stub exists | ✅ transcription_worker calls it | ❌ no UI slider | Add beam-width slider in advanced options, gated on beamSearch == true |
| 2 | diarized_json response format |
server endpoint (POST /v1/audio/transcriptions) |
N/A (server) | N/A | ❌ | Add response_format=diarized_json option to server_service |
| 3 | --diarize-speakers / consent-gated speaker DB |
diarize | N/A | N/A | ❌ | Wire --diarize-speakers alias in CLI; consider consent UI for speaker DB |
| 4 | Granite KWB (keyword biasing) | granite-plus | ✅ via existing setHotwords |
✅ | ❌ boost slider missing | Add hotwords boost slider in advanced options (0.0–5.0) |
| 5 | Granite prefix_text (incremental decode) |
granite-plus | ❌ no C API setter yet | ❌ | ❌ | Blocked on CrispASR adding a crispasr_session_set_prefix_text C ABI. Track only. |
- Beam-size slider in advanced options
- Hotwords-boost slider in advanced options
-
diarized_jsonserver response format - Unit tests for all
| # | Capability | Backends | FFI stub? | TTS service call? | UI? | Work needed |
|---|---|---|---|---|---|---|
| 1 | setTopK(int) |
qwen3-tts, chatterbox, orpheus, dots-tts | ❌ | ❌ | ❌ | Add FFI stub + TTS service call + synthesize screen slider |
| 2 | setDoSample(bool) |
all TTS | ❌ | ❌ | ❌ | Add FFI stub + TTS service call + synthesize screen toggle |
| 3 | setTtsNumCandidates(int) |
chatterbox, kokoro, tada | ❌ | ❌ | ❌ | Add FFI stub + TTS service call + synthesize screen slider |
| 4 | setSpeakerId(int) |
melotts, piper, fastpitch | ❌ | ❌ | ❌ | Add FFI stub + TTS service call + adapt speaker picker for int IDs |
| 5 | setG2pDict(String) |
kokoro, vibevoice, speecht5 | ❌ | ❌ | ❌ | Add FFI stub + TTS service call + file picker in synthesize screen |
| 6 | setTtsNoiseTemp(double) |
kokoro, vibevoice | ❌ | ❌ | ❌ | Add FFI stub + TTS service call + synthesize screen slider |
| 7 | TADA per-request voice switch | tada | ✅ setVoice exists |
✅ | ✅ already wired | Verify works without session restart (CrispASR #201 fix) |
| 8 | TADA flow-matching knobs (top_k, do_sample, num_candidates) | tada | covered by items 1-3 above | — | — | Same FFI stubs as items 1-3 |
| 9 | TADA --make-ref (C++ voice ref creation) |
tada | ❌ no C API | ❌ | ❌ | Blocked on C ABI for make-ref. Track only. |
| 10 | TADA auto-download language voice refs on -l <lang> |
tada | ✅ auto-download is C-side | ✅ automatic | N/A | No Dart work — C-side handles it |
| 11 | TADA GPU runtime (Metal, quantized FM fallback) | tada | ✅ uses existing GPU toggle | ✅ automatic | N/A | No Dart work — C-side handles it |
| 12 | CosyVoice3 GPU + lazy-load cloning | cosyvoice3-tts | ✅ C-side | ✅ automatic | N/A | No Dart work |
-
setTopKFFI stub + TTS service + UI -
setDoSampleFFI stub + TTS service + UI -
setTtsNumCandidatesFFI stub + TTS service + UI -
setSpeakerIdFFI stub + TTS service + UI -
setG2pDictFFI stub + TTS service + UI -
setTtsNoiseTempFFI stub + TTS service + UI - Unit tests for all new FFI stubs + TTS service calls
| # | Feature | Status | Work needed |
|---|---|---|---|
| 1 | diarized_json response format (#206) |
❌ | Add to server_service transcription endpoint |
| 2 | --diarize-speakers CLI alias |
❌ | Add to CLI diarize command |
| 3 | New backends in CLI backends list |
✅ automatic | Dynamic from availableBackends() |
-
diarized_jsonserver support -
--diarize-speakersCLI alias - Unit tests
- Model catalog entries (11.1) — pure data, no FFI changes
- TTS FFI stubs (11.3) — add all 6 missing setters to
crispasr_stub.dart - TTS service wiring — call new setters from
tts_service.dart - Synthesize screen UI — expose new TTS controls
- ASR advanced options UI (11.2) — beam-size slider, hotwords-boost slider
- Server + CLI (11.4) —
diarized_json,--diarize-speakers - Unit tests for everything
Gap analysis performed 2026-07-04 by comparing CrispASR v0.8.7 (1541 commits in June) and CrispEmbed v0.13.0 (1568 commits since May 20) against CrisperWeaver HEAD (95c4e26). §11 covered catalog + TTS FFI + server parity; this section targets capabilities that landed since.
-
a.
.amrin file picker + constants.app_constants.darthas.aubut not.amr.file_utils.dart:getAudioFiles()is out of sync withsupportedAudioExtensions(missing.amr,.mp4,.wma,.aiff,.au,.ra). Fix: sync both lists + add.amr. Files:lib/constants/app_constants.dart,lib/utils/file_utils.dartTests: unit test asserting both lists are equal -
b. Qwen3-ASR-1.7B-JA catalog entry. Japanese anime/galgame fine-tune (dual -hf / non-hf format). Not in catalog. Files:
lib/services/model_catalog.dartTests: catalog invariant test -
c. Chatterbox emotion tag insert buttons. Chatterbox TTS supports
[laugh],[whispering],[angry]inline emotion tags. Add quick-insert buttons on the synthesize screen when chatterbox backend is selected. Files:lib/providers/synthesize_screen_provider.dartTests: unit test for tag insertion logic -
d. Engine version string bump.
crispasr_engine.dartreportsversion => '0.8.0'; should be0.8.7. Files:lib/engines/crispasr_engine.dart -
e. VAD empty-result guard. CrispASR now returns empty on silent audio (was hallucinating). Verify CrisperWeaver engine handles empty transcription result → "no speech detected" UX. Files:
lib/engines/crispasr_engine.dartTests: unit test for empty result path
The CrispEmbed Dart binding (v0.13.0) has grown significantly.
crispembed_stub.dart (web fallback) is missing most new APIs.
crispembed_web.dart is also incomplete. Both need updating to
match the real binding's API surface.
-
a. Stub: add reranker APIs.
rerank(query, doc)→ double,rerankBiencoder(query, docs, {topN})→ List,isReranker→ bool. PlusRerankResultclass. Files:lib/native/crispembed_stub.dartTests: unit test importing stub on web -
b. Stub: add sparse + ColBERT APIs.
encodeSparse(text)→ Map<int,double>,encodeMultivec(text)→ List,colbertScore(...)→ double,hasSparse→ bool,hasColbert→ bool. Files:lib/native/crispembed_stub.dart -
c. Stub: add vision APIs.
encodeImage(...),encodeImageRaw(...),encodeImageFile(path),encodeTextWithImageFile(text, path). Files:lib/native/crispembed_stub.dart,lib/native/crispembed_web.dart -
d. Stub: add config APIs.
setDim(int),setPrefix(String),dimgetter,ctxQueryPrefix,ctxPassagePrefix. Files:lib/native/crispembed_stub.dart,lib/native/crispembed_web.dart
-
a. Reranker integration. When CrispEmbed model
isRerankeror a secondary reranker model is loaded, run a cross-encoder reranking pass on the top-k cosine results. Falls back to bi-encoder reranking viarerankBiencoder()otherwise. Files:lib/services/semantic_search_service.dartTests: unit test with mock embedder verifying reorder -
b. BidirLM-Omni audio embedding. Wire
encodeAudio(pcm)intoSemanticSearchService._embeddingSearchfor cross-modal scoring (already stubbed in history_service.dart). When the embedderhasAudio, encode the audio segment and compare against the query vector. Files:lib/services/semantic_search_service.dartTests: unit test for audio embedding path
- CrispEmbed's registry now recommends imatrix quantizations
(IQ4_XS, Q4_K+imatrix) over plain quants. Update the embed
ModelDefinitionentries to point at the imatrix variants. Files:lib/services/model_catalog.dartTests: catalog invariant test
- Expose a "Re-align timestamps" action in the transcript detail
screen. Uses
CrispASR.alignWords()(already in Dart binding) to run CTC forced alignment on existing transcript + audio without a full ASR pass. Files:lib/engines/crispasr_engine.dart,lib/services/aligner_service.dart,lib/widgets/transcription_output_widget.dartTests: unit test for alignment-only path; live test
-
a. LoRA hot-swap for embeddings. Dart FFI binding added to CrispEmbed (setLora, getLora, listLora); CrisperWeaver stubs updated. CrispEmbed supports dynamic adapter switching. Needs: Dart binding for LoRA load, UI for adapter file selection alongside base model. Files:
lib/services/semantic_search_service.dart,lib/native/crispembed_stub.dartTests: unit test for adapter path propagation -
b. VLM OCR engine integration. CrispEmbed added 6 new VLM OCR backends. Expose an OCR action that runs document OCR on images via a new
OcrService. Files: newlib/services/ocr_service.dartTests: unit + live test -
c. Scan/document preprocessing. CrispEmbed has deskew, denoise, dewarping, super-resolution. Add preprocessing step before OCR. Files: new
lib/services/scan_preprocess_service.dartTests: unit test for pipeline orchestration -
d. WASM CrispEmbed IndexedDB caching. New WASM build caches models in IndexedDB. Improve web target's embedding experience. Files:
lib/native/crispembed_web.dartTests: integration test for WASM path
- LoRA Dart FFI binding added to CrispEmbed (
setLora,getLora,listLora); CrisperWeaver stubs + web stubs updated. - OCR service wired to real CrispEmbed FFI via conditional import
(
ocr_import.dart+ocr_stub.dart);recognizeMath+recognizeRaw. - Scan preprocessing wired to CrispEmbed
CrispScanCleanupFFI (scan_cleanup_import.dart+scan_cleanup_stub.dart);process(). - 6 OCR model catalog entries (pix2tex, HMER, BTTR, PosFormer,
Granite Vision, DeepSeek-OCR2) +
ModelKind.ocrenum. - 3 reranker catalog entries (MS MARCO MiniLM, mxbai XSmall, BGE M3)
+
ModelKind.rerankerenum + 3 BackendRepos. - 3 larger embed catalog entries (Nomic v1.5, E5 Small, Qwen3 0.6B) + 3 BackendRepos.
- 3 OCR BackendRepos (pix2tex, HMER, Granite Vision).
-
main.dartembed provider fix: searchescrispasrBackendModels(where imatrix entries live) in addition towhisperCppModels. - "OCR image" action in transcript output widget menu.
- Live tests: re-alignment, VAD silence, reranker scoring.
- Full regression: 1089 pass, 23 skip, 0 fail.
-
a. Rebake catalog JSON. Run
scripts/bake_models_catalog.dartto regenerateassets/models/catalog.jsonwith the ~15 new ModelDefinitions (OCR, reranker, embed, Qwen3-JA). Without this the Model Manager won't show the new entries. Files:scripts/bake_models_catalog.dart→assets/models/catalog.jsonTests:baked_catalog_json_test.dartround-trip check -
b. Commit + tag release. v0.9.0 tagged, CI green, release built. Commit all §12 work, tag as v0.8.8 or similar.
-
c. Reranker auto-load in search. Wire a
rerankerProvider(likecrispEmbedProvider) that probes for downloadedModelKind.rerankerGGUFs and loads one as a second CrispEmbed instance. Pass it asreranker:toSemanticSearchService.search(). This makes the reranker catalog entries actually functional. Files:lib/main.dart,lib/services/semantic_search_service.dartTests: unit test for provider logic; live test with real model -
d. Rebuild libcrispembed with LoRA symbols. Built locally (v0.13.0), LoRA + reranker symbols verified. The Dart binding is ready but the bundled
.so/.dylibpredates it. Until rebuilt,hasLorareturns false at runtime. Files: CrispEmbed build system Tests: live test verifyinghasLoraon rebuilt lib
-
e. OCR image picker flow. Finish the "OCR image" menu action: pick image via file_picker, decode to pixels, call
ocrService.recognizeMath(), show result in a dialog with copy-to-clipboard. Files:lib/widgets/transcription_output_widget.dartTests: widget test for dialog -
f. BidirLM-Omni audio embedding end-to-end. Verify the
HistoryService.computeAudioEmbedding()→encodeAudio()path works when the omni model is downloaded. Add a live test. Files:lib/services/history_service.dartTests: live test with BidirLM model -
g. Scan preprocessing UI. "Clean scan" button on the image import path that runs deskew + whitening before OCR. Wraps
ScanPreprocessService.process(). Files:lib/widgets/transcription_output_widget.dartor new widget Tests: widget test
-
h. Wyoming protocol server. Home Assistant integration — exposes CrispASR as a Wyoming STT provider over TCP. Niche. Files: new
lib/services/wyoming_service.dart -
i. Streaming segment callbacks for LLM-ASR. C-ABI added to CrispASR (
crispasr_segment_callback+ polling viadrain_streamed_segments). CrisperWeaver engine polls during transcription and firesonSegment. Live partial results from Qwen3/ARK/MOSS backends during decode. Needs UI for progressive text display. Files:lib/engines/crispasr_engine.dart -
j. Widget tests for transcription + synthesize screens. Remaining test coverage gap (§8.7/§9.3). High effort, lower priority than service/provider tests already covering the logic.