|
|
|
|
@@ -50,9 +50,19 @@ import {
|
|
|
|
|
import { getAudioContext } from '@/lib/audio/context';
|
|
|
|
|
|
|
|
|
|
export function AudioEditor() {
|
|
|
|
|
// Settings hook
|
|
|
|
|
const {
|
|
|
|
|
settings,
|
|
|
|
|
updateAudioSettings,
|
|
|
|
|
updateUISettings,
|
|
|
|
|
updateEditorSettings,
|
|
|
|
|
updatePerformanceSettings,
|
|
|
|
|
resetCategory,
|
|
|
|
|
} = useSettings();
|
|
|
|
|
|
|
|
|
|
const [importDialogOpen, setImportDialogOpen] = React.useState(false);
|
|
|
|
|
const [selectedTrackId, setSelectedTrackId] = React.useState<string | null>(null);
|
|
|
|
|
const [zoom, setZoom] = React.useState(1);
|
|
|
|
|
const [zoom, setZoom] = React.useState(settings.editor.defaultZoom);
|
|
|
|
|
const [masterVolume, setMasterVolume] = React.useState(0.8);
|
|
|
|
|
const [masterPan, setMasterPan] = React.useState(0);
|
|
|
|
|
const [isMasterMuted, setIsMasterMuted] = React.useState(false);
|
|
|
|
|
@@ -66,6 +76,13 @@ export function AudioEditor() {
|
|
|
|
|
const [exportDialogOpen, setExportDialogOpen] = React.useState(false);
|
|
|
|
|
const [isExporting, setIsExporting] = React.useState(false);
|
|
|
|
|
const [analyzerView, setAnalyzerView] = React.useState<'frequency' | 'spectrogram' | 'phase' | 'lufs' | 'stats'>('frequency');
|
|
|
|
|
|
|
|
|
|
// Switch away from spectrogram if it gets disabled
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
|
if (analyzerView === 'spectrogram' && !settings.performance.enableSpectrogram) {
|
|
|
|
|
setAnalyzerView('frequency');
|
|
|
|
|
}
|
|
|
|
|
}, [analyzerView, settings.performance.enableSpectrogram]);
|
|
|
|
|
const [projectsDialogOpen, setProjectsDialogOpen] = React.useState(false);
|
|
|
|
|
const [projects, setProjects] = React.useState<ProjectMetadata[]>([]);
|
|
|
|
|
const [currentProjectId, setCurrentProjectId] = React.useState<string | null>(null);
|
|
|
|
|
@@ -90,15 +107,10 @@ export function AudioEditor() {
|
|
|
|
|
setSampleRate,
|
|
|
|
|
} = useRecording();
|
|
|
|
|
|
|
|
|
|
// Settings hook
|
|
|
|
|
const {
|
|
|
|
|
settings,
|
|
|
|
|
updateAudioSettings,
|
|
|
|
|
updateUISettings,
|
|
|
|
|
updateEditorSettings,
|
|
|
|
|
updatePerformanceSettings,
|
|
|
|
|
resetCategory,
|
|
|
|
|
} = useSettings();
|
|
|
|
|
// Sync recording sample rate with global settings
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
|
setSampleRate(settings.audio.sampleRate);
|
|
|
|
|
}, [settings.audio.sampleRate, setSampleRate]);
|
|
|
|
|
|
|
|
|
|
// Multi-track hooks
|
|
|
|
|
const {
|
|
|
|
|
@@ -122,19 +134,19 @@ export function AudioEditor() {
|
|
|
|
|
// Wrap addTrack to auto-select first track when adding to empty project
|
|
|
|
|
const addTrack = React.useCallback((name?: string) => {
|
|
|
|
|
const shouldAutoSelect = shouldAutoSelectRef.current;
|
|
|
|
|
const track = addTrackOriginal(name);
|
|
|
|
|
const track = addTrackOriginal(name, settings.ui.defaultTrackHeight);
|
|
|
|
|
if (shouldAutoSelect) {
|
|
|
|
|
setSelectedTrackId(track.id);
|
|
|
|
|
shouldAutoSelectRef.current = false; // Only auto-select once
|
|
|
|
|
}
|
|
|
|
|
return track;
|
|
|
|
|
}, [addTrackOriginal]);
|
|
|
|
|
}, [addTrackOriginal, settings.ui.defaultTrackHeight]);
|
|
|
|
|
|
|
|
|
|
// Wrap addTrackFromBuffer to auto-select first track when adding to empty project
|
|
|
|
|
const addTrackFromBuffer = React.useCallback((buffer: AudioBuffer, name?: string) => {
|
|
|
|
|
console.log(`[AudioEditor] addTrackFromBuffer wrapper called: ${name}, shouldAutoSelect: ${shouldAutoSelectRef.current}`);
|
|
|
|
|
const shouldAutoSelect = shouldAutoSelectRef.current;
|
|
|
|
|
const track = addTrackFromBufferOriginal(buffer, name);
|
|
|
|
|
const track = addTrackFromBufferOriginal(buffer, name, settings.ui.defaultTrackHeight);
|
|
|
|
|
console.log(`[AudioEditor] Track created: ${track.name} (${track.id})`);
|
|
|
|
|
if (shouldAutoSelect) {
|
|
|
|
|
console.log(`[AudioEditor] Auto-selecting track: ${track.id}`);
|
|
|
|
|
@@ -142,7 +154,7 @@ export function AudioEditor() {
|
|
|
|
|
shouldAutoSelectRef.current = false; // Only auto-select once
|
|
|
|
|
}
|
|
|
|
|
return track;
|
|
|
|
|
}, [addTrackFromBufferOriginal]);
|
|
|
|
|
}, [addTrackFromBufferOriginal, settings.ui.defaultTrackHeight]);
|
|
|
|
|
|
|
|
|
|
// Track which parameters are being touched (for touch/latch modes)
|
|
|
|
|
const [touchedParameters, setTouchedParameters] = React.useState<Set<string>>(new Set());
|
|
|
|
|
@@ -1606,7 +1618,7 @@ export function AudioEditor() {
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Analyzer Toggle */}
|
|
|
|
|
<div className="grid grid-cols-5 gap-0.5 bg-muted/20 border border-border/50 rounded-md p-0.5 max-w-[192px] mx-auto">
|
|
|
|
|
<div className={`grid ${settings.performance.enableSpectrogram ? 'grid-cols-5' : 'grid-cols-4'} gap-0.5 bg-muted/20 border border-border/50 rounded-md p-0.5 max-w-[192px] mx-auto`}>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => setAnalyzerView('frequency')}
|
|
|
|
|
className={`px-1 py-1 rounded text-[9px] font-bold uppercase tracking-wider transition-all ${
|
|
|
|
|
@@ -1618,17 +1630,19 @@ export function AudioEditor() {
|
|
|
|
|
>
|
|
|
|
|
FFT
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => setAnalyzerView('spectrogram')}
|
|
|
|
|
className={`px-1 py-1 rounded text-[9px] font-bold uppercase tracking-wider transition-all ${
|
|
|
|
|
analyzerView === 'spectrogram'
|
|
|
|
|
? 'bg-accent text-accent-foreground shadow-sm'
|
|
|
|
|
: 'text-muted-foreground hover:text-foreground'
|
|
|
|
|
}`}
|
|
|
|
|
title="Spectrogram"
|
|
|
|
|
>
|
|
|
|
|
SPEC
|
|
|
|
|
</button>
|
|
|
|
|
{settings.performance.enableSpectrogram && (
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => setAnalyzerView('spectrogram')}
|
|
|
|
|
className={`px-1 py-1 rounded text-[9px] font-bold uppercase tracking-wider transition-all ${
|
|
|
|
|
analyzerView === 'spectrogram'
|
|
|
|
|
? 'bg-accent text-accent-foreground shadow-sm'
|
|
|
|
|
: 'text-muted-foreground hover:text-foreground'
|
|
|
|
|
}`}
|
|
|
|
|
title="Spectrogram"
|
|
|
|
|
>
|
|
|
|
|
SPEC
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => setAnalyzerView('phase')}
|
|
|
|
|
className={`px-1 py-1 rounded text-[9px] font-bold uppercase tracking-wider transition-all ${
|
|
|
|
|
@@ -1668,7 +1682,7 @@ export function AudioEditor() {
|
|
|
|
|
<div className="flex-1 min-h-[360px] flex items-start justify-center">
|
|
|
|
|
<div className="w-[178px]">
|
|
|
|
|
{analyzerView === 'frequency' && <FrequencyAnalyzer analyserNode={masterAnalyser} />}
|
|
|
|
|
{analyzerView === 'spectrogram' && <Spectrogram analyserNode={masterAnalyser} />}
|
|
|
|
|
{analyzerView === 'spectrogram' && settings.performance.enableSpectrogram && <Spectrogram analyserNode={masterAnalyser} />}
|
|
|
|
|
{analyzerView === 'phase' && <PhaseCorrelationMeter analyserNode={masterAnalyser} />}
|
|
|
|
|
{analyzerView === 'lufs' && <LUFSMeter analyserNode={masterAnalyser} />}
|
|
|
|
|
{analyzerView === 'stats' && <AudioStatistics tracks={tracks} />}
|
|
|
|
|
|