From bc69d3c9e4222bce0650eb9257ab35f874730316 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Jan 2026 01:03:35 +0000 Subject: [PATCH 1/2] fix(audio): Fix mono panning and effects issues Mono panning fixes: - Default to MONO (channelCount: 1) instead of stereo for new tracks - Default to MONO in bridge-audio-store for global config - This fixes the common case of single microphone/instrument input Effects fixes: - Add serde alias "ampSimulator" for amp field (TS uses ampSimulator) - Add debug logging to update_effects to track which effects are enabled - Disabled auto-start feature (was causing issues with no device selected) --- native-bridge/src/audio/engine.rs | 29 +++++++++++++++++++++++++++-- native-bridge/src/effects/types.rs | 2 +- src/hooks/useNativeBridge.ts | 10 ++++++++-- src/stores/bridge-audio-store.ts | 7 +++++-- src/stores/user-tracks-store.ts | 7 ++++--- 5 files changed, 45 insertions(+), 10 deletions(-) diff --git a/native-bridge/src/audio/engine.rs b/native-bridge/src/audio/engine.rs index 08b67a2c..f879b531 100644 --- a/native-bridge/src/audio/engine.rs +++ b/native-bridge/src/audio/engine.rs @@ -382,8 +382,33 @@ impl AudioEngine { } pub fn update_effects(&self, effects: crate::effects::EffectsSettings) { - if let Ok(mut state) = self.processing_state.write() { - state.effects_chain.update_settings(effects); + // Log which effects are enabled for debugging + let enabled_effects: Vec<&str> = [ + ("wah", effects.wah.enabled), + ("overdrive", effects.overdrive.enabled), + ("distortion", effects.distortion.enabled), + ("amp", effects.amp.enabled), + ("reverb", effects.reverb.enabled), + ("delay", effects.delay.enabled), + ("chorus", effects.chorus.enabled), + ("compressor", effects.compressor.enabled), + ("eq", effects.eq.enabled), + ] + .iter() + .filter(|(_, enabled)| *enabled) + .map(|(name, _)| *name) + .collect(); + + info!("Updating effects chain. Enabled: {:?}", enabled_effects); + + match self.processing_state.write() { + Ok(mut state) => { + state.effects_chain.update_settings(effects); + info!("Effects chain updated successfully"); + } + Err(e) => { + tracing::error!("Failed to acquire write lock for effects update: {:?}", e); + } } } diff --git a/native-bridge/src/effects/types.rs b/native-bridge/src/effects/types.rs index 2b7089c1..5e48c2e4 100644 --- a/native-bridge/src/effects/types.rs +++ b/native-bridge/src/effects/types.rs @@ -13,7 +13,7 @@ pub struct EffectsSettings { pub overdrive: OverdriveSettings, #[serde(default)] pub distortion: DistortionSettings, - #[serde(default)] + #[serde(default, alias = "ampSimulator")] pub amp: AmpSettings, #[serde(default)] pub cabinet: CabinetSettings, diff --git a/src/hooks/useNativeBridge.ts b/src/hooks/useNativeBridge.ts index c31c441e..24c26ecd 100644 --- a/src/hooks/useNativeBridge.ts +++ b/src/hooks/useNativeBridge.ts @@ -252,17 +252,21 @@ export function useNativeBridge() { // Auto-start native bridge when room is connected and bridge is preferred // This ensures users don't have to manually start audio after joining a room + // DISABLED: This was causing issues by trying to start without an ASIO device selected + // Users must manually start audio from settings after selecting their device + /* useEffect(() => { // Subscribe to room store for connection changes const unsubscribeRoom = useRoomStore.subscribe((roomState) => { const bridgeState = useBridgeAudioStore.getState(); - // Check if we should auto-start: room connected + bridge connected + bridge preferred + not already running + // Check if we should auto-start: room connected + bridge connected + bridge preferred + not already running + device selected if ( roomState.isConnected && bridgeState.isConnected && bridgeState.preferNativeBridge && - !bridgeState.isRunning + !bridgeState.isRunning && + bridgeState.inputDevice // Must have a device selected ) { console.log('[useNativeBridge] Room connected with bridge preferred, auto-starting audio...'); // Use a small delay to ensure all other initialization has completed @@ -275,6 +279,7 @@ export function useNativeBridge() { latestBridge.isConnected && latestBridge.preferNativeBridge && !latestBridge.isRunning && + latestBridge.inputDevice && startAudioRef.current ) { console.log('[useNativeBridge] Executing auto-start via ref...'); @@ -292,6 +297,7 @@ export function useNativeBridge() { unsubscribeRoom(); }; }, []); + */ // Connect to bridge const connect = useCallback(async () => { diff --git a/src/stores/bridge-audio-store.ts b/src/stores/bridge-audio-store.ts index 237b275c..0610507c 100644 --- a/src/stores/bridge-audio-store.ts +++ b/src/stores/bridge-audio-store.ts @@ -98,11 +98,14 @@ export const useBridgeAudioStore = create()( // Persisted settings selectedInputDeviceId: null, selectedOutputDeviceId: null, + // Default to MONO for most common use case (single microphone/instrument) + // Stereo users will configure this in settings inputChannelConfig: { - channelCount: 2, + channelCount: 1, leftChannel: 0, - rightChannel: 1, + rightChannel: undefined, }, + // Output stays stereo for monitoring outputChannelConfig: { channelCount: 2, leftChannel: 0, diff --git a/src/stores/user-tracks-store.ts b/src/stores/user-tracks-store.ts index 80a2b743..38d8e414 100644 --- a/src/stores/user-tracks-store.ts +++ b/src/stores/user-tracks-store.ts @@ -20,11 +20,12 @@ export const TRACK_COLORS = [ '#c084fc', // Purple ]; -// Default channel configuration (stereo) +// Default channel configuration (MONO - most common for single input) +// Users with stereo inputs can change this in settings const DEFAULT_CHANNEL_CONFIG: InputChannelConfig = { - channelCount: 2, + channelCount: 1, leftChannel: 0, - rightChannel: 1, + rightChannel: undefined, }; // Default audio settings with extended effects chain From 9b3f4dd9b524f5db7bbbfa68819d35572a7db344 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Jan 2026 01:10:53 +0000 Subject: [PATCH 2/2] fix(effects): Apply effects before browser stream so everyone hears them Previously effects were only applied for local monitoring, meaning: - Local user heard effects through their audio interface - Other users in the session heard raw/dry audio via browser stream Now effects are processed immediately after converting to stereo, before: 1. Sending to browser stream (so remote users hear effects) 2. Local monitoring (so local user still hears effects) The monitoring section now only applies volume and pan adjustments since effects are already applied earlier in the signal chain. --- native-bridge/src/audio/engine.rs | 38 ++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/native-bridge/src/audio/engine.rs b/native-bridge/src/audio/engine.rs index f879b531..8ac6b222 100644 --- a/native-bridge/src/audio/engine.rs +++ b/native-bridge/src/audio/engine.rs @@ -208,6 +208,10 @@ pub struct AudioEngine { // Connection health tracking last_browser_read_time: Arc, + + // Diagnostic counters for effects processing + effects_applied_count: Arc, + effects_skipped_count: Arc, } impl AudioEngine { @@ -248,6 +252,9 @@ impl AudioEngine { browser_stream_overflow_count: Arc::new(AtomicU64::new(0)), browser_stream_overflow_samples: Arc::new(AtomicU64::new(0)), last_browser_read_time: Arc::new(AtomicU64::new(0)), + // Diagnostic counters for effects processing + effects_applied_count: Arc::new(AtomicU64::new(0)), + effects_skipped_count: Arc::new(AtomicU64::new(0)), }) } @@ -1005,7 +1012,22 @@ impl AudioEngine { stereo_buffer.push(right_sample); } + // Apply effects FIRST so they're included in both browser stream and local monitoring + // This ensures other users in the session hear the effects too + if let Ok(mut state) = processing_state.try_write() { + let gain = state.track_state.input_gain_linear(); + + // Apply input gain + for sample in stereo_buffer.iter_mut() { + *sample *= gain; + } + + // Process through effects chain + state.effects_chain.process(stereo_buffer); + } + // Calculate input levels (stereo) - interleaved L/R samples + // Note: levels are post-effects now let (level_l, level_r) = stereo_buffer .chunks_exact(2) .fold((0.0_f32, 0.0_f32), |(max_l, max_r), chunk| { @@ -1040,23 +1062,17 @@ impl AudioEngine { false // Default to not muted - let audio through }; - // WET monitoring: apply effects chain for local monitoring + // Local monitoring: apply volume, pan, and push to output + // Note: Effects were already applied above (before browser stream) let should_monitor = monitoring_enabled && !is_muted; if should_monitor { - if let Ok(mut state) = processing_state.try_write() { - let gain = state.track_state.input_gain_linear(); + // Apply volume and pan for local monitoring + // Effects are already applied, just need volume/pan + if let Ok(state) = processing_state.try_read() { let volume = state.track_state.volume; let (pan_left, pan_right) = state.track_state.pan_gains(); - // Apply input gain first - for sample in stereo_buffer.iter_mut() { - *sample *= gain; - } - - // Process through effects chain (if any effects are enabled) - state.effects_chain.process(stereo_buffer); - // Apply volume, pan, and monitoring volume to stereo pairs for chunk in stereo_buffer.chunks_exact_mut(2) { let base_gain = volume * mon_vol;