Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion native-bridge/src/audio/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -598,6 +598,25 @@ impl AudioEngine {
}
}

/// Get current input device ID
pub fn get_input_device_id(&self) -> Option<String> {
self.config.input_device_id.clone()
}

/// Get current output device ID
pub fn get_output_device_id(&self) -> Option<String> {
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<f32> {
Expand Down
48 changes: 45 additions & 3 deletions native-bridge/src/protocol/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -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(),
Expand Down
17 changes: 17 additions & 0 deletions src/hooks/useNativeBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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;
Expand Down
26 changes: 26 additions & 0 deletions src/lib/audio/native-bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -88,6 +101,7 @@ export type BridgeEventType =
| 'connected'
| 'disconnected'
| 'devices'
| 'deviceConfig'
| 'levels'
| 'audioStatus'
| 'streamHealth'
Expand All @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
Loading