# Platform Backend Extraction Evaluation — chanora_audio **Date:** 2026-06-11 **Status:** Evaluation (not implementation) **Crate:** `chanora_audio` (~18,500 lines total) ## Current Structure Platform-specific code is isolated into dedicated files with `#[cfg]` gates at the module boundary. The engine module (`engine/mod.rs`, `engine/lifecycle.rs`) uses pervasive inline `cfg` attributes to dispatch across platforms. ### Lines Per Platform (exclusive files only) | Platform | Files | Lines | Key Dependencies | |----------|-------|-------|-----------------| | **iOS/macOS** | `ios_voice_unit.rs`, `vad/apple_coreml.rs`, `ptt_backends/macos.rs`, `voice_render.rs` | ~2,600 | `coreaudio-rs`, `dispatch2` | | **Android** | `android_voice_unit.rs`, `android_render_ring.rs`, `audio_event_queue.rs` | ~2,130 | `oboe`, `jni`, `ndk-context`, `bytemuck` | | **Desktop** | `engine/capture.rs`, `engine/render.rs`, `sdl_output.rs`, `ptt_backends/windows*.rs`, `ptt_backends/linux.rs`, `vad/silero_onnx.rs` | ~4,400 | `cpal`, `sdl2`, `ort`, `windows`, `zbus` | ### Shared Code (cross-platform) | Module | Lines | Notes | |--------|-------|-------| | `mobile_voice_backend.rs` | 813 | Trait + types for iOS/Android backends | | `engine/mod.rs` + `engine/lifecycle.rs` | 2,255 | Heavy inline `cfg` dispatch | | `processor/` | ~500 | AudioProcessor trait + backends | | `vad/mod.rs` + `vad/resampler.rs` | 393 | VAD trait + WebRTC fallback | | Other shared (`frame`, `opus_voice`, `ptt`, `voice_activity`, etc.) | ~3,200 | Platform-neutral | ## Assessment ### Would splitting help? **No — not recommended at this time.** ### Reasons Against Splitting 1. **cfg gating already works.** Platform files are cleanly isolated at the module boundary. The compiler strips unused code per-target; a separate crate doesn't add compilation speed for the active target. 2. **Shared types are deeply embedded.** `AudioError`, `AudioEffects`, `AudioProcessingConfig`, `VoiceActivityStateMachine`, `OpusEncoder` setup, `frame::*` helpers, and the `AudioProcessor` trait are used by every platform. Extracting these into a `chanora_audio_common` crate is mandatory before splitting, adding a dependency node every platform crate must pull in. 3. **engine/lifecycle.rs is the real problem — but it's an integration point, not a platform backend.** This 1,149-line file dispatches `start_audio` / `stop_audio` across all platforms with inline `cfg`. Splitting backends into separate crates wouldn't reduce this file's complexity — it would just move the cross-crate import surface here. 4. **Dependency graph complexity.** The current single crate has 5 `cfg`-gated dependency blocks in Cargo.toml. Splitting into 3+ crates means each platform crate needs its own Cargo.toml with the shared types dependency, and the top-level `chanora_audio` (or `chanora_core`) must depend on all of them with target-conditional `cfg` features. 5. **Test surface stays the same.** Platform-specific tests already compile only on their target OS. A crate boundary doesn't improve test isolation. 6. **The `MobileVoiceAudioBackend` trait is the natural seam — and it already exists.** `mobile_voice_backend.rs` defines the cross-platform interface. The iOS backend will implement it under SDD-117. This is the correct abstraction boundary without adding a crate boundary. ### When Splitting Would Make Sense - If build times for **cross-compilation** become painful (building all 3 platform variants from CI) - If platform-specific dependencies cause **feature flag conflicts** (not observed today) - If the crate exceeds ~30k lines and the `cfg` density makes navigation difficult - If a platform team needs to own a crate independently ## Recommendation Keep the current single-crate structure. Focus cleanup effort on: 1. **Reducing inline `cfg` in `engine/lifecycle.rs`** — extract platform dispatch into the existing `IosVoiceBackend` enum pattern 2. **Back-filling `IosVoiceUnit` to `MobileVoiceAudioBackend`** (SDD-117) — this unifies the mobile interface 3. **Documenting the cfg convention** — module-level `cfg` at the file boundary (current pattern) vs. inline `cfg` blocks in shared files