diff --git a/native-bridge/src/audio/engine.rs b/native-bridge/src/audio/engine.rs index 8ac6b222..d9f8de92 100644 --- a/native-bridge/src/audio/engine.rs +++ b/native-bridge/src/audio/engine.rs @@ -59,7 +59,7 @@ impl Default for EngineConfig { fn default() -> Self { Self { sample_rate: SampleRate::Hz48000, - buffer_size: BufferSize::Samples128, + buffer_size: BufferSize::Samples256, input_device_id: None, output_device_id: None, channel_config: ChannelConfig::default(), @@ -598,6 +598,25 @@ impl AudioEngine { } } + /// Get current input device ID + pub fn get_input_device_id(&self) -> Option { + self.config.input_device_id.clone() + } + + /// Get current output device ID + pub fn get_output_device_id(&self) -> Option { + self.config.output_device_id.clone() + } + + /// Get current channel configuration + pub fn get_channel_config(&self) -> ChannelConfig { + if let Ok(state) = self.processing_state.try_read() { + state.channel_config.clone() + } else { + self.config.channel_config.clone() + } + } + /// Get raw audio samples for streaming to browser (for WebRTC broadcast) /// Returns up to `max_samples` stereo samples, or fewer if buffer has less pub fn get_browser_stream_audio(&self, max_samples: usize) -> Vec { diff --git a/native-bridge/src/protocol/server.rs b/native-bridge/src/protocol/server.rs index 6ed5d130..2409c742 100644 --- a/native-bridge/src/protocol/server.rs +++ b/native-bridge/src/protocol/server.rs @@ -508,11 +508,32 @@ impl BridgeServer { 256 => crate::audio::BufferSize::Samples256, 512 => crate::audio::BufferSize::Samples512, 1024 => crate::audio::BufferSize::Samples1024, - _ => crate::audio::BufferSize::Samples128, + _ => crate::audio::BufferSize::Samples256, }; match app.audio_engine.set_buffer_size(buffer_size) { - Ok(_) => None, + Ok(_) => { + // Send updated device info to TUI + let device_info = app.audio_engine.get_device_info(); + if let Some(ref tx) = app.tui_tx { + let _ = tx.try_send(AppEvent::DeviceInfo { + input_device: device_info.input_device.clone(), + output_device: device_info.output_device.clone(), + sample_rate: device_info.sample_rate, + buffer_size: device_info.buffer_size, + }); + } + // Send DeviceConfig back to browser to confirm + Some(NativeMessage::DeviceConfig { + input_device: app.audio_engine.get_input_devices().ok() + .and_then(|devs| devs.into_iter().find(|d| Some(d.id.clone()) == app.audio_engine.get_input_device_id())), + output_device: app.audio_engine.get_output_devices().ok() + .and_then(|devs| devs.into_iter().find(|d| Some(d.id.clone()) == app.audio_engine.get_output_device_id())), + sample_rate: device_info.sample_rate, + buffer_size: device_info.buffer_size, + channel_config: app.audio_engine.get_channel_config(), + }) + } Err(e) => Some(NativeMessage::Error { code: "CONFIG_ERROR".to_string(), message: e.to_string(), @@ -531,7 +552,28 @@ impl BridgeServer { }; match app.audio_engine.set_sample_rate(sample_rate) { - Ok(_) => None, + Ok(_) => { + // Send updated device info to TUI + let device_info = app.audio_engine.get_device_info(); + if let Some(ref tx) = app.tui_tx { + let _ = tx.try_send(AppEvent::DeviceInfo { + input_device: device_info.input_device.clone(), + output_device: device_info.output_device.clone(), + sample_rate: device_info.sample_rate, + buffer_size: device_info.buffer_size, + }); + } + // Send DeviceConfig back to browser to confirm + Some(NativeMessage::DeviceConfig { + input_device: app.audio_engine.get_input_devices().ok() + .and_then(|devs| devs.into_iter().find(|d| Some(d.id.clone()) == app.audio_engine.get_input_device_id())), + output_device: app.audio_engine.get_output_devices().ok() + .and_then(|devs| devs.into_iter().find(|d| Some(d.id.clone()) == app.audio_engine.get_output_device_id())), + sample_rate: device_info.sample_rate, + buffer_size: device_info.buffer_size, + channel_config: app.audio_engine.get_channel_config(), + }) + } Err(e) => Some(NativeMessage::Error { code: "CONFIG_ERROR".to_string(), message: e.to_string(), diff --git a/src/hooks/useNativeBridge.ts b/src/hooks/useNativeBridge.ts index 24c26ecd..5359c043 100644 --- a/src/hooks/useNativeBridge.ts +++ b/src/hooks/useNativeBridge.ts @@ -170,6 +170,21 @@ export function useNativeBridge() { s.setError(data); }; + // Handle device config confirmation from native bridge - sync actual values back to store + const handleDeviceConfig = (data: { + inputDevice: BridgeDevice | null; + outputDevice: BridgeDevice | null; + sampleRate: number; + bufferSize: number; + channelConfig: { channelCount: 1 | 2; leftChannel: number; rightChannel?: number }; + }) => { + console.log('[useNativeBridge] DeviceConfig confirmed:', data.sampleRate, 'Hz,', data.bufferSize, 'samples'); + const s = useBridgeAudioStore.getState(); + // Update store with actual values from native bridge + s.setSampleRate(data.sampleRate as 44100 | 48000); + s.setBufferSize(data.bufferSize as 32 | 64 | 128 | 256 | 512 | 1024); + }; + // Handle audio levels from native bridge - update track levels for waveform display const handleLevels = (data: { inputLevel: number; @@ -211,6 +226,7 @@ export function useNativeBridge() { nativeBridge.on('disconnected', handleDisconnected); nativeBridge.on('audioStatus', handleAudioStatus); nativeBridge.on('devices', handleDevices); + nativeBridge.on('deviceConfig', handleDeviceConfig); nativeBridge.on('error', handleError); nativeBridge.on('levels', handleLevels); @@ -244,6 +260,7 @@ export function useNativeBridge() { nativeBridge.off('disconnected', handleDisconnected); nativeBridge.off('audioStatus', handleAudioStatus); nativeBridge.off('devices', handleDevices); + nativeBridge.off('deviceConfig', handleDeviceConfig); nativeBridge.off('error', handleError); nativeBridge.off('levels', handleLevels); initialized.current = false; diff --git a/src/lib/audio/native-bridge.ts b/src/lib/audio/native-bridge.ts index e9cac34f..6489fddc 100644 --- a/src/lib/audio/native-bridge.ts +++ b/src/lib/audio/native-bridge.ts @@ -50,12 +50,25 @@ interface BridgeStreamHealth { msSinceLastRead: number; } +interface BridgeDeviceConfig { + inputDevice: BridgeDevice | null; + outputDevice: BridgeDevice | null; + sampleRate: number; + bufferSize: number; + channelConfig: { + channelCount: 1 | 2; + leftChannel: number; + rightChannel?: number; + }; +} + // Native bridge sends snake_case, we normalize to camelCase type NativeMessage = | { type: 'welcome'; version: string; driverType?: string; driver_type?: string } | { type: 'pong'; timestamp: number; nativeTime: number } | { type: 'error'; code: string; message: string } | { type: 'devices'; inputs: BridgeDevice[]; outputs: BridgeDevice[] } + | { type: 'deviceConfig' } & BridgeDeviceConfig | { type: 'audioStatus' } & BridgeAudioStatus | { type: 'levels' } & BridgeLevels | { type: 'streamHealth' } & BridgeStreamHealth @@ -88,6 +101,7 @@ export type BridgeEventType = | 'connected' | 'disconnected' | 'devices' + | 'deviceConfig' | 'levels' | 'audioStatus' | 'streamHealth' @@ -99,6 +113,7 @@ type BridgeEventData = { connected: { version: string; driverType: string }; disconnected: { reason: string }; devices: { inputs: BridgeDevice[]; outputs: BridgeDevice[] }; + deviceConfig: BridgeDeviceConfig; levels: BridgeLevels; audioStatus: BridgeAudioStatus; streamHealth: BridgeStreamHealth; @@ -320,6 +335,17 @@ export class NativeBridge { this.emit('devices', { inputs: msg.inputs, outputs: msg.outputs }); break; + case 'deviceConfig': + console.log('[NativeBridge] DeviceConfig received:', msg.sampleRate, 'Hz,', msg.bufferSize, 'samples'); + this.emit('deviceConfig', { + inputDevice: msg.inputDevice, + outputDevice: msg.outputDevice, + sampleRate: msg.sampleRate, + bufferSize: msg.bufferSize, + channelConfig: msg.channelConfig, + }); + break; + case 'levels': this.emit('levels', { inputLevel: msg.inputLevel,