diff --git a/native-bridge/src/protocol/server.rs b/native-bridge/src/protocol/server.rs index b4bbb672..6ed5d130 100644 --- a/native-bridge/src/protocol/server.rs +++ b/native-bridge/src/protocol/server.rs @@ -248,8 +248,9 @@ impl BridgeServer { } else { empty_read_counter += 1; } - } else if loop_counter % 1000 == 0 { - warn!("[Server] Audio engine not running (loop {})", loop_counter); + } else if loop_counter == 5000 { + // Only log once after ~5 seconds if audio hasn't started yet + info!("[Server] Waiting for audio engine to start..."); } } diff --git a/src/components/daw/user-track-header.tsx b/src/components/daw/user-track-header.tsx index ffc11b95..53775b2c 100644 --- a/src/components/daw/user-track-header.tsx +++ b/src/components/daw/user-track-header.tsx @@ -170,6 +170,7 @@ export function UserTrackHeader({ setTrackMuted, setTrackSolo, setTrackVolume, + setTrackPan, setTrackArmed, updateTrack, } = useUserTracksStore(); @@ -486,9 +487,10 @@ export function UserTrackHeader({ - {/* Expanded Controls - Volume */} + {/* Expanded Controls - Volume & Pan */} {isExpanded && ( -
+
+ {/* Volume */}
+ {/* Pan */} +
+ L + setTrackPan(track.id, v * 2 - 1)} + className="flex-1" + /> + R + + {track.pan === 0 ? 'C' : track.pan < 0 ? `L${Math.round(Math.abs(track.pan) * 100)}` : `R${Math.round(track.pan * 100)}`} + +
)} diff --git a/src/hooks/useTrackAudioSync.ts b/src/hooks/useTrackAudioSync.ts index 5ca3a78b..e59defaa 100644 --- a/src/hooks/useTrackAudioSync.ts +++ b/src/hooks/useTrackAudioSync.ts @@ -12,6 +12,7 @@ interface TrackSyncState { isMuted: boolean; isSolo: boolean; volume: number; + pan: number; inputGainDb: number; monitoringEnabled: boolean; monitoringVolume: number; @@ -73,6 +74,7 @@ export function useTrackAudioSync(currentUserId: string | undefined) { isMuted: isEffectivelyMuted, isSolo: track.isSolo, volume: track.volume, + pan: track.pan ?? 0, inputGainDb: track.audioSettings.inputGain || 0, monitoringEnabled: directMonitoring, monitoringVolume: track.audioSettings.monitoringVolume ?? 1, @@ -85,6 +87,7 @@ export function useTrackAudioSync(currentUserId: string | undefined) { isMuted: isEffectivelyMuted, isSolo: track.isSolo, volume: track.volume, + pan: track.pan ?? 0, inputGainDb: track.audioSettings.inputGain || 0, monitoringEnabled: directMonitoring, monitoringVolume: track.audioSettings.monitoringVolume ?? 1, @@ -151,6 +154,7 @@ export function useTrackAudioSync(currentUserId: string | undefined) { isMuted: isEffectivelyMuted, isSolo: track.isSolo, volume: track.volume, + pan: track.pan ?? 0, inputGainDb: track.audioSettings.inputGain || 0, monitoringEnabled: track.audioSettings.directMonitoring ?? true, monitoringVolume: track.audioSettings.monitoringVolume ?? 1, @@ -206,6 +210,7 @@ export function useTrackAudioSync(currentUserId: string | undefined) { lastState.isMuted !== currentState.isMuted || lastState.isSolo !== currentState.isSolo || lastState.volume !== currentState.volume || + lastState.pan !== currentState.pan || lastState.inputGainDb !== currentState.inputGainDb || lastState.monitoringEnabled !== currentState.monitoringEnabled || lastState.monitoringVolume !== currentState.monitoringVolume; @@ -216,6 +221,7 @@ export function useTrackAudioSync(currentUserId: string | undefined) { isMuted: currentState.isMuted, isSolo: currentState.isSolo, volume: currentState.volume, + pan: currentState.pan, inputGainDb: currentState.inputGainDb, monitoringEnabled: currentState.monitoringEnabled, monitoringVolume: currentState.monitoringVolume, diff --git a/src/lib/audio/native-bridge.ts b/src/lib/audio/native-bridge.ts index 2dc71dbc..a1c65b68 100644 --- a/src/lib/audio/native-bridge.ts +++ b/src/lib/audio/native-bridge.ts @@ -567,6 +567,7 @@ export class NativeBridge { isMuted?: boolean; isSolo?: boolean; volume?: number; + pan?: number; inputGainDb?: number; monitoringEnabled?: boolean; monitoringVolume?: number; @@ -581,6 +582,7 @@ export class NativeBridge { isMuted: state.isMuted, isSolo: state.isSolo, volume: state.volume, + pan: state.pan, inputGainDb: state.inputGainDb, monitoringEnabled: state.monitoringEnabled, monitoringVolume: state.monitoringVolume, diff --git a/src/stores/user-tracks-store.ts b/src/stores/user-tracks-store.ts index a103edcc..80a2b743 100644 --- a/src/stores/user-tracks-store.ts +++ b/src/stores/user-tracks-store.ts @@ -4,6 +4,7 @@ import type { UserTrack, TrackAudioSettings, ExtendedEffectsChain, InputChannelC import { DEFAULT_FULL_EFFECTS } from '@/lib/audio/effects-defaults'; import { EFFECT_PRESETS } from '@/lib/audio/effects/presets'; import { GUITAR_PRESETS } from '@/lib/audio/effects/guitar'; +import { nativeBridge } from '@/lib/audio/native-bridge'; // Track color palette - exported for use in color picker export const TRACK_COLORS = [ @@ -94,6 +95,7 @@ interface UserTracksState { setTrackMuted: (trackId: string, muted: boolean) => void; setTrackSolo: (trackId: string, solo: boolean) => void; setTrackVolume: (trackId: string, volume: number) => void; + setTrackPan: (trackId: string, pan: number) => void; setTrackArmed: (trackId: string, armed: boolean) => void; setTrackRecording: (trackId: string, recording: boolean) => void; setTrackStream: (trackId: string, stream: MediaStream | undefined) => void; @@ -162,6 +164,7 @@ export const useUserTracksStore = create()( isMuted: false, isSolo: false, volume: 1, + pan: 0, // Center isArmed: false, // New tracks are not armed by default isRecording: false, createdAt: Date.now(), @@ -203,6 +206,7 @@ export const useUserTracksStore = create()( isMuted: false, isSolo: false, volume: 1, + pan: 0, // Center isArmed: false, isRecording: false, createdAt: Date.now(), @@ -332,6 +336,10 @@ export const useUserTracksStore = create()( activePreset: undefined, // Clear preset when manually editing }, }); + + // Send effects to native bridge for real-time processing + nativeBridge.updateEffects(trackId, newEffects); + return { tracks }; }), @@ -348,6 +356,10 @@ export const useUserTracksStore = create()( channelConfig, }, }); + + // Send channel config to native bridge + nativeBridge.setChannelConfig(channelConfig); + return { tracks }; }), @@ -393,14 +405,19 @@ export const useUserTracksStore = create()( if (!preset) return state; const tracks = new Map(state.tracks); + const newEffects = JSON.parse(JSON.stringify(preset.effects)); // Deep clone tracks.set(trackId, { ...track, audioSettings: { ...track.audioSettings, - effects: JSON.parse(JSON.stringify(preset.effects)), // Deep clone + effects: newEffects, activePreset: presetId, }, }); + + // Send effects to native bridge + nativeBridge.updateEffects(trackId, newEffects); + return { tracks }; }), @@ -438,12 +455,17 @@ export const useUserTracksStore = create()( activePreset: presetId, }, }); + + // Send effects to native bridge + nativeBridge.updateEffects(trackId, newEffects); + return { tracks }; }), setTrackMuted: (trackId, muted) => get().updateTrack(trackId, { isMuted: muted }), setTrackSolo: (trackId, solo) => get().updateTrack(trackId, { isSolo: solo }), setTrackVolume: (trackId, volume) => get().updateTrack(trackId, { volume }), + setTrackPan: (trackId, pan) => get().updateTrack(trackId, { pan: Math.max(-1, Math.min(1, pan)) }), setTrackArmed: (trackId, armed) => get().updateTrack(trackId, { isArmed: armed }), setTrackRecording: (trackId, recording) => get().updateTrack(trackId, { isRecording: recording }), setTrackStream: (trackId, stream) => get().updateTrack(trackId, { stream }), diff --git a/src/types/index.ts b/src/types/index.ts index c3ae9a0c..99e7e8af 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -314,6 +314,7 @@ export interface UserTrack { isMuted: boolean; isSolo: boolean; volume: number; + pan: number; // -1 (full left) to 1 (full right), 0 = center isArmed: boolean; // Ready to record isRecording: boolean; stream?: MediaStream;