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;