// MasteringDropdown.tsx — Mastering config for the global param bar // // The on/off toggle is in the bar header (ToggleSwitch). // This dropdown shows reference track selection and options when mastering is enabled. import React, { useState, useEffect, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { Upload, Trash2, Music2 } from 'lucide-react'; import { useGlobalParams } from '../../context/GlobalParamsContext'; import { masteringApi } from '../../services/api'; import { useAuth } from '../../context/AuthContext'; import { ToggleSwitch } from './BarSection'; import { formatReferenceName } from './modelLabels'; interface ReferenceTrack { name: string; size: number; url: string; } const formatFileSize = (bytes: number): string => { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; }; export const MasteringDropdown: React.FC = () => { const gp = useGlobalParams(); const { t } = useTranslation(); const { token } = useAuth(); const [references, setReferences] = useState([]); const [uploading, setUploading] = useState(false); useEffect(() => { masteringApi.listReferences() .then(data => setReferences(data.references)) .catch(() => {}); }, []); const handleUpload = useCallback(async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file || !token) return; try { setUploading(true); const result = await masteringApi.uploadReference(file, token); gp.setMasteringReference(result.name); const data = await masteringApi.listReferences(); setReferences(data.references); } catch (err) { console.error('[Mastering] Upload failed:', err); } finally { setUploading(false); e.target.value = ''; } }, [token, gp]); const handleDelete = useCallback(async (name: string) => { if (!token) return; try { await masteringApi.deleteReference(name, token); if (gp.masteringReference === name) gp.setMasteringReference(''); const data = await masteringApi.listReferences(); setReferences(data.references); } catch (err) { console.error('[Mastering] Delete failed:', err); } }, [token, gp]); if (!gp.masteringEnabled) { return (
{t('mastering.disabled')}
); } return (
{/* Reference selector */}
{references.length > 0 ? ( ) : (
{t('mastering.noReferencesYet')}
)}
{/* Selected reference info + delete */} {gp.masteringReference && (
{gp.masteringReference}
)} {/* Upload button */}
{/* Timbre reference toggle */} {gp.masteringReference && ( gp.timbreAudioPath ? (
Timbre: using dedicated reference ({gp.timbreAudioPath.split(/[\\/]/).pop()})
) : (
{t('mastering.alsoTimbreRef')}
) )} {gp.timbreReference && gp.masteringReference && !gp.timbreAudioPath && (

The reference track will be VAE-encoded and fed into the timbre conditioning pipeline, guiding the generation's tone and texture to match the reference.

)} {/* Info */}

The generated audio will be mastered to match the RMS level, frequency spectrum, and dynamic characteristics of the reference track.

); }; /** Summary badge for the Mastering section */ export const MasteringBadge: React.FC = () => { const { masteringEnabled, masteringReference } = useGlobalParams(); if (!masteringEnabled) return null; const refName = formatReferenceName(masteringReference); return ( {refName || 'No ref'} ); };