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
10 changes: 9 additions & 1 deletion native-bridge/src/audio/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -996,7 +996,7 @@ impl AudioEngine {
is_monitoring: &Arc<AtomicBool>,
monitoring_volume: &Arc<AtomicU32>,
processing_state: &Arc<RwLock<AudioProcessingState>>,
_effects_metering: &Arc<RwLock<EffectsMetering>>,
effects_metering: &Arc<RwLock<EffectsMetering>>,
overflow_count: &Arc<AtomicU64>,
overflow_samples: &Arc<AtomicU64>,
) {
Expand Down Expand Up @@ -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
Expand Down
10 changes: 7 additions & 3 deletions src/components/daw/add-track-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -156,6 +156,7 @@ interface AddTrackModalProps {
userId: string;
userName?: string;
roomId?: string;
onTrackCreated?: (track: UserTrack) => void;
}

const DEFAULT_SETTINGS: TrackAudioSettings = {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 (
<Modal
Expand Down
45 changes: 10 additions & 35 deletions src/components/daw/effects-rack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { useState, useCallback, useMemo } from 'react';
import { cn } from '@/lib/utils';
import { useUserTracksStore } from '@/stores/user-tracks-store';
import { useSessionTempoStore } from '@/stores/session-tempo-store';
import { Knob } from '@/components/ui/knob';
import { EFFECT_PRESETS } from '@/lib/audio/effects/presets';
import { GUITAR_PRESETS } from '@/lib/audio/effects/guitar';
Expand Down Expand Up @@ -629,9 +630,11 @@ function FormantShifterUI({ settings, onChange }: EffectProps) {
function HarmonizerUI({ settings, onChange }: EffectProps) {
const harm = settings.harmonizer;
const [expanded, setExpanded] = useState(harm?.enabled ?? false);
// Use global key from session tempo store
const globalKey = useSessionTempoStore((s) => 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' },
Expand All @@ -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 (
<div className="border-b border-white/5">
<EffectHeader name="Harmonizer" icon={Music} enabled={harm.enabled} onToggle={() => onChange({ harmonizer: { ...harm, enabled: !harm.enabled } })} expanded={expanded} onExpandToggle={() => setExpanded(!expanded)} color="purple" />
{expanded && (
<div className="pb-3 px-2 space-y-3">
<div className="flex gap-1 flex-wrap">
{keys.map((k) => <button key={k} onClick={() => onChange({ harmonizer: { ...harm, key: k } })} disabled={!harm.enabled} className={cn('px-1.5 py-0.5 text-[9px] font-medium rounded', harm.key === k ? 'bg-purple-500/20 text-purple-400' : 'bg-white/5 text-zinc-500 hover:bg-white/10', !harm.enabled && 'opacity-50')}>{k}</button>)}
<div className="text-center text-[10px] text-zinc-400">
Key: <span className="text-purple-400 font-medium">{keyDisplay}</span>
<span className="text-zinc-600 ml-1">(from transport)</span>
</div>
<div className="flex gap-1">
{harmonies.map((h) => <button key={h.value} onClick={() => onChange({ harmonizer: { ...harm, harmonyType: h.value as typeof harm.harmonyType } })} disabled={!harm.enabled} className={cn('flex-1 px-1.5 py-1 text-[9px] font-medium rounded', harm.harmonyType === h.value ? 'bg-purple-500/20 text-purple-400' : 'bg-white/5 text-zinc-500 hover:bg-white/10', !harm.enabled && 'opacity-50')}>{h.label}</button>)}
Expand Down Expand Up @@ -1381,38 +1388,6 @@ export function EffectsRack({ track, onClose }: EffectsRackProps) {
filteredEffects.map((effect) => renderEffect(effect.id))
)}
</div>

{/* Signal Flow Indicator */}
<div className="px-4 py-2 bg-white/[0.02] border-t border-white/5">
<div className="text-[9px] text-zinc-600 mb-1">Signal Flow:</div>
<div className="flex flex-wrap items-center gap-1 text-[9px] text-zinc-600">
<span className={effectsSettings.wah.enabled ? 'text-purple-400' : ''}>Wah</span>
<span>→</span>
<span className={effectsSettings.overdrive.enabled ? 'text-yellow-400' : ''}>OD</span>
<span>→</span>
<span className={effectsSettings.distortion.enabled ? 'text-red-400' : ''}>Dist</span>
<span>→</span>
<span className={effectsSettings.ampSimulator.enabled ? 'text-orange-400' : ''}>Amp</span>
<span>→</span>
<span className={effectsSettings.cabinet.enabled ? 'text-amber-400' : ''}>Cab</span>
<span>→</span>
<span className={effectsSettings.noiseGate.enabled ? 'text-emerald-400' : ''}>Gate</span>
<span>→</span>
<span className={effectsSettings.eq.enabled ? 'text-cyan-400' : ''}>EQ</span>
<span>→</span>
<span className={effectsSettings.compressor.enabled ? 'text-amber-400' : ''}>Comp</span>
<span>→</span>
<span className={effectsSettings.chorus.enabled || effectsSettings.flanger.enabled || effectsSettings.phaser.enabled ? 'text-indigo-400' : ''}>Mod</span>
<span>→</span>
<span className={effectsSettings.delay.enabled ? 'text-cyan-400' : ''}>Dly</span>
<span>→</span>
<span className={effectsSettings.tremolo.enabled ? 'text-amber-400' : ''}>Trem</span>
<span>→</span>
<span className={effectsSettings.reverb.enabled ? 'text-indigo-400' : ''}>Verb</span>
<span>→</span>
<span className={effectsSettings.limiter.enabled ? 'text-rose-400' : ''}>Limit</span>
</div>
</div>
</div>
);
}
5 changes: 4 additions & 1 deletion src/components/daw/track-headers-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand All @@ -19,6 +19,7 @@ interface TrackHeadersPanelProps {
onMuteSelf: () => void;
width?: number;
roomId?: string;
onTrackCreated?: (track: UserTrack) => void;
}

// Track color palette for remote users
Expand All @@ -45,6 +46,7 @@ export function TrackHeadersPanel({
onMuteSelf,
width,
roomId,
onTrackCreated,
}: TrackHeadersPanelProps) {
const [showAddTrackModal, setShowAddTrackModal] = useState(false);

Expand Down Expand Up @@ -250,6 +252,7 @@ export function TrackHeadersPanel({
userId={currentUser?.id || ''}
userName={currentUser?.name}
roomId={roomId}
onTrackCreated={onTrackCreated}
/>
</div>
);
Expand Down
19 changes: 18 additions & 1 deletion src/hooks/useRoom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down Expand Up @@ -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();
Expand All @@ -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);
}
});
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -1640,6 +1656,7 @@ export function useRoom(roomId: string, options: UseRoomOptions = {}) {
kickUser,
banUser,
// Real-time broadcast functions
broadcastUserTrackAdd,
broadcastUserTrackUpdate,
broadcastTempoUpdate,
broadcastTempoSource,
Expand Down
5 changes: 2 additions & 3 deletions src/lib/audio/audio-engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading