feat(ui): adaptive 3-panel layout, chat panel switching, audio metering fix
- Add responsive breakpoints (compact <600, medium 600-1023, expanded >=1024) - Add ViewportInfo InheritedWidget for layout-aware descendants - Add inline ChatPanel (380dp right column) for expanded desktop layout - Add channel right-click context menu with Chat option for in-place switching - Add per-target draft persistence via restoredDraft/onDraftChanged callbacks - Fix header chat button to switch to current voice channel when panel open - Fix close = dismiss (preserves last target and draft for reopen) - Add unread dot indicator on channel tiles when chat is closed - Fix audio regression: decimate dBFS computation to every 3rd callback (~31 Hz) to avoid buffer underruns on macOS CoreAudio real-time thread - Add tools/build-macos.sh release build script (7-step process) - Add chat panel switching implementation plan and 3-panel design spec Tests: 183 passed, 2 skipped. Flutter analyze clean.
This commit is contained in:
@@ -1812,6 +1812,16 @@ struct CaptureState {
|
||||
/// after warmup. Same precedent as `mono_scratch` above.
|
||||
frame_scratch: Vec<f32>,
|
||||
audio_processing_stats: Arc<crate::SharedAudioProcessingStats>,
|
||||
/// Decimation counter for the level meter. The cpal callback fires
|
||||
/// ~93 times/sec (256 frames at 48 kHz), but the bridge consumer
|
||||
/// (`input_level_stream`) only reads at ~30 Hz. Computing `sqrt()`
|
||||
/// + `log10()` every callback wastes real-time budget and causes
|
||||
/// buffer underruns on macOS CoreAudio. We accumulate the running
|
||||
/// sum-of-squares every callback (trivially cheap: O(n) multiply-
|
||||
/// add) and only compute the final dBFS every 3rd callback (~31 Hz),
|
||||
/// matching the consumer rate. See commit history for the metering
|
||||
/// regression that motivated this.
|
||||
level_decimation_counter: u32,
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "android")))]
|
||||
@@ -1841,6 +1851,7 @@ impl CaptureState {
|
||||
mono_scratch: Vec::with_capacity(4096),
|
||||
frame_scratch: Vec::with_capacity(FRAME_SAMPLES),
|
||||
audio_processing_stats,
|
||||
level_decimation_counter: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1860,10 +1871,14 @@ impl CaptureState {
|
||||
self.mono_scratch.push(sum / frame.len() as f32);
|
||||
}
|
||||
|
||||
// Measure dBFS from pre-gain samples so the level meter
|
||||
// reflects the raw mic input, not the amplified signal.
|
||||
self.audio_processing_stats
|
||||
.set_input_dbfs(crate::frame::dbfs(&self.mono_scratch));
|
||||
// Level meter: accumulate sum-of-squares on every callback
|
||||
// (trivially cheap), but only pay for sqrt() + log10() every
|
||||
// 3rd callback (~31 Hz, matching the bridge consumer rate).
|
||||
self.level_decimation_counter = self.level_decimation_counter.wrapping_add(1);
|
||||
if self.level_decimation_counter % 3 == 0 {
|
||||
self.audio_processing_stats
|
||||
.set_input_dbfs(crate::frame::dbfs(&self.mono_scratch));
|
||||
}
|
||||
|
||||
if mic_gain != 1.0 {
|
||||
for s in &mut self.mono_scratch {
|
||||
|
||||
Reference in New Issue
Block a user