From 44ae7d079a615fdbedaecd2d27f8c18b48eeeef3 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 6 Jan 2026 02:25:41 +0000 Subject: [PATCH] fix: Multiple DAW fixes for effects, sync, and monitoring - Remove signal flow display from effects rack UI - Harmonizer now uses global key from transport instead of own key selection - Wire up TUI dynamics section by syncing effects metering to shared state - Fix direct monitoring double audio by removing duplicate output path - Fix multi-user sync: master now broadcasts all tracks to new users on join - Add broadcastUserTrackAdd callback to support track creation broadcasts --- native-bridge/src/audio/engine.rs | 10 ++++- src/components/daw/add-track-modal.tsx | 10 +++-- src/components/daw/effects-rack.tsx | 45 +++++----------------- src/components/daw/track-headers-panel.tsx | 5 ++- src/hooks/useRoom.ts | 19 ++++++++- src/lib/audio/audio-engine.ts | 5 +-- 6 files changed, 50 insertions(+), 44 deletions(-) diff --git a/native-bridge/src/audio/engine.rs b/native-bridge/src/audio/engine.rs index d9f8de92..f0284773 100644 --- a/native-bridge/src/audio/engine.rs +++ b/native-bridge/src/audio/engine.rs @@ -996,7 +996,7 @@ impl AudioEngine { is_monitoring: &Arc, monitoring_volume: &Arc, processing_state: &Arc>, - _effects_metering: &Arc>, + effects_metering: &Arc>, overflow_count: &Arc, overflow_samples: &Arc, ) { @@ -1043,6 +1043,14 @@ impl AudioEngine { // Process through effects chain state.effects_chain.process(stereo_buffer); + + // Update effects metering for TUI display + if let Ok(mut metering) = effects_metering.try_write() { + let chain_metering = state.effects_chain.get_metering(); + metering.noise_gate_open = chain_metering.noise_gate_open; + metering.compressor_reduction = chain_metering.compressor_reduction; + metering.limiter_reduction = chain_metering.limiter_reduction; + } } // Calculate input levels (stereo) - interleaved L/R samples diff --git a/src/components/daw/add-track-modal.tsx b/src/components/daw/add-track-modal.tsx index 1c93f268..ffadd963 100644 --- a/src/components/daw/add-track-modal.tsx +++ b/src/components/daw/add-track-modal.tsx @@ -8,7 +8,7 @@ import { useUserTracksStore } from '@/stores/user-tracks-store'; import { DEFAULT_FULL_EFFECTS } from '@/lib/audio/effects-defaults'; import { MidiDeviceSelector } from '../midi/midi-device-selector'; import { useNativeBridge } from '@/hooks/useNativeBridge'; -import type { TrackAudioSettings, MidiInputSettings } from '@/types'; +import type { TrackAudioSettings, MidiInputSettings, UserTrack } from '@/types'; import { Mic, Monitor, @@ -156,6 +156,7 @@ interface AddTrackModalProps { userId: string; userName?: string; roomId?: string; + onTrackCreated?: (track: UserTrack) => void; } const DEFAULT_SETTINGS: TrackAudioSettings = { @@ -185,7 +186,7 @@ const DEFAULT_MIDI_SETTINGS: MidiInputSettings = { velocityCurve: 'linear', }; -export function AddTrackModal({ isOpen, onClose, userId, userName, roomId }: AddTrackModalProps) { +export function AddTrackModal({ isOpen, onClose, userId, userName, roomId, onTrackCreated }: AddTrackModalProps) { const { inputDevices, devicesLoaded, loadDevices, addTrack, addMidiTrack, getTracksByUser } = useUserTracksStore(); const { isConnected: nativeBridgeConnected, @@ -334,8 +335,11 @@ export function AddTrackModal({ isOpen, onClose, userId, userName, roomId }: Add } } + // Broadcast the new track to other users + onTrackCreated?.(newTrack); + onClose(); - }, [userId, userName, roomId, trackName, settings, addTrack, onClose, stopTesting]); + }, [userId, userName, roomId, trackName, settings, addTrack, onClose, stopTesting, onTrackCreated]); return ( s.key); + const globalScale = useSessionTempoStore((s) => s.keyScale); if (!harm) return null; - const keys = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']; const harmonies = [ { value: 'third', label: '3rd' }, { value: 'fifth', label: '5th' }, @@ -640,13 +643,17 @@ function HarmonizerUI({ settings, onChange }: EffectProps) { { value: 'powerChord', label: 'Power' }, ]; + // Display current global key + const keyDisplay = globalKey ? `${globalKey} ${globalScale || ''}`.trim() : 'No key set'; + return (
onChange({ harmonizer: { ...harm, enabled: !harm.enabled } })} expanded={expanded} onExpandToggle={() => setExpanded(!expanded)} color="purple" /> {expanded && (
-
- {keys.map((k) => )} +
+ Key: {keyDisplay} + (from transport)
{harmonies.map((h) => )} @@ -1381,38 +1388,6 @@ export function EffectsRack({ track, onClose }: EffectsRackProps) { filteredEffects.map((effect) => renderEffect(effect.id)) )}
- - {/* Signal Flow Indicator */} -
-
Signal Flow:
-
- Wah - - OD - - Dist - - Amp - - Cab - - Gate - - EQ - - Comp - - Mod - - Dly - - Trem - - Verb - - Limit -
-
); } diff --git a/src/components/daw/track-headers-panel.tsx b/src/components/daw/track-headers-panel.tsx index c6de3b78..0aa9ce50 100644 --- a/src/components/daw/track-headers-panel.tsx +++ b/src/components/daw/track-headers-panel.tsx @@ -7,7 +7,7 @@ import { InactiveTrackHeader } from './inactive-track-header'; import { AddTrackModal } from './add-track-modal'; import { useUserTracksStore } from '@/stores/user-tracks-store'; import { Plus } from 'lucide-react'; -import type { User } from '@/types'; +import type { User, UserTrack } from '@/types'; interface TrackHeadersPanelProps { users: User[]; @@ -19,6 +19,7 @@ interface TrackHeadersPanelProps { onMuteSelf: () => void; width?: number; roomId?: string; + onTrackCreated?: (track: UserTrack) => void; } // Track color palette for remote users @@ -45,6 +46,7 @@ export function TrackHeadersPanel({ onMuteSelf, width, roomId, + onTrackCreated, }: TrackHeadersPanelProps) { const [showAddTrackModal, setShowAddTrackModal] = useState(false); @@ -250,6 +252,7 @@ export function TrackHeadersPanel({ userId={currentUser?.id || ''} userName={currentUser?.name} roomId={roomId} + onTrackCreated={onTrackCreated} />
); diff --git a/src/hooks/useRoom.ts b/src/hooks/useRoom.ts index 33f70186..f87c2a34 100644 --- a/src/hooks/useRoom.ts +++ b/src/hooks/useRoom.ts @@ -331,6 +331,7 @@ export function useRoom(roomId: string, options: UseRoomOptions = {}) { // Persist the new track authFetchJson(`/api/rooms/${roomId}/user-tracks`, 'POST', newTrack) .catch(err => console.error('Failed to persist new track:', err)); + // Note: Track will be broadcast via presence:join handler when other users join } } @@ -537,7 +538,7 @@ export function useRoom(roomId: string, options: UseRoomOptions = {}) { if (hasNewUsers && useRoomStore.getState().isMaster) { // Small delay to ensure new user's broadcast handlers are ready // The new user needs time to complete subscription before receiving broadcasts - setTimeout(() => { + setTimeout(async () => { console.log('[useRoom] Master broadcasting room state to new users...'); const { useSessionTempoStore } = require('@/stores/session-tempo-store'); const tempoState = useSessionTempoStore.getState(); @@ -558,6 +559,16 @@ export function useRoom(roomId: string, options: UseRoomOptions = {}) { console.log(`[useRoom] Broadcasting queue with ${currentQueue.tracks.length} tracks to new users`); realtime.broadcastQueueUpdate(currentQueue); } + + // Broadcast all user tracks so new joiners see everyone's tracks + const userTracksStore = useUserTracksStore.getState(); + const allTracks = userTracksStore.getAllTracks(); + for (const track of allTracks) { + if (track.isActive) { + await realtime.broadcastUserTrackAdd(track); + } + } + console.log(`[useRoom] Broadcast ${allTracks.filter(t => t.isActive).length} active tracks to new users`); }, 500); } }); @@ -1560,6 +1571,11 @@ export function useRoom(roomId: string, options: UseRoomOptions = {}) { realtimeRef.current?.broadcastUserTrackUpdate(trackId, updates); }, []); + // Broadcast new user track creation + const broadcastUserTrackAdd = useCallback((track: UserTrack) => { + realtimeRef.current?.broadcastUserTrackAdd(track); + }, []); + // Broadcast tempo updates (for real-time sync of BPM/time signature) const broadcastTempoUpdate = useCallback((tempo: number, source: string) => { realtimeRef.current?.broadcastTempoUpdate(tempo, source); @@ -1640,6 +1656,7 @@ export function useRoom(roomId: string, options: UseRoomOptions = {}) { kickUser, banUser, // Real-time broadcast functions + broadcastUserTrackAdd, broadcastUserTrackUpdate, broadcastTempoUpdate, broadcastTempoSource, diff --git a/src/lib/audio/audio-engine.ts b/src/lib/audio/audio-engine.ts index 36560e7e..cb209530 100644 --- a/src/lib/audio/audio-engine.ts +++ b/src/lib/audio/audio-engine.ts @@ -939,10 +939,9 @@ export class AudioEngine { onEffectsChange ); - // Connect track output to master gain - processor.connect(this.masterGain); - // Connect track monitor output to master gain for local monitoring + // This is the only path to speakers - controlled by monitorGainNode + // (the broadcast path via getBroadcastNode() goes to WebRTC, not speakers) processor.connectMonitor(this.masterGain); // If this is the first track, set it as primary for backward compat