File size: 4,384 Bytes
44a2550 0dfd298 44a2550 0dfd298 44a2550 0dfd298 44a2550 6293e69 0dfd298 6293e69 0dfd298 44a2550 6293e69 0dfd298 6293e69 0dfd298 6293e69 44a2550 6293e69 44a2550 6293e69 44a2550 6293e69 44a2550 6293e69 44a2550 0dfd298 44a2550 6293e69 44a2550 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | /**
* Main score editor component integrating notation, playback, and export.
* Supports multi-instrument transcription.
*/
import { useState, useEffect } from 'react';
import { getMidiFile, getMetadata, getJobStatus } from '../api/client';
import { useNotationStore } from '../store/notation';
import { NotationCanvas } from './NotationCanvas';
import { PlaybackControls } from './PlaybackControls';
import { InstrumentTabs } from './InstrumentTabs';
import './ScoreEditor.css';
interface ScoreEditorProps {
jobId: string;
}
export function ScoreEditor({ jobId }: ScoreEditorProps) {
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [instruments, setInstruments] = useState<string[]>([]);
const loadFromMidi = useNotationStore((state) => state.loadFromMidi);
const activeInstrument = useNotationStore((state) => state.activeInstrument);
const setActiveInstrument = useNotationStore((state) => state.setActiveInstrument);
useEffect(() => {
loadScore();
}, [jobId]);
const loadScore = async () => {
try {
setLoading(true);
setError(null);
// Get job status to find which instruments were transcribed
const jobStatus = await getJobStatus(jobId);
// For now, assume piano is the default instrument (backend doesn't yet return instruments list)
// TODO: Update when backend API returns instruments list in job status
const transcribedInstruments = ['piano'];
setInstruments(transcribedInstruments);
// Fetch metadata once (shared across all instruments)
const metadata = await getMetadata(jobId);
// Load MIDI files for each instrument
for (const instrument of transcribedInstruments) {
// For MVP, backend only supports piano (single stem)
// In the future, this will fetch per-instrument MIDI: `/api/v1/scores/${jobId}/midi/${instrument}`
const midiData = await getMidiFile(jobId);
await loadFromMidi(instrument, midiData, {
tempo: metadata.tempo,
keySignature: metadata.key_signature,
timeSignature: metadata.time_signature,
});
}
// Set first instrument as active
if (transcribedInstruments.length > 0) {
setActiveInstrument(transcribedInstruments[0]);
}
setLoading(false);
} catch (err) {
console.error('Failed to load score:', err);
setError(err instanceof Error ? err.message : 'Failed to load score');
setLoading(false);
}
};
const handleExportMusicXML = () => {
// TODO: Generate MusicXML from edited score state
alert('MusicXML export coming soon - will generate from your edited notation');
};
const handleExportMIDI = async () => {
try {
// Download the original MIDI file
const midiData = await getMidiFile(jobId);
const blob = new Blob([midiData], { type: 'audio/midi' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `score_${jobId}.mid`;
a.click();
URL.revokeObjectURL(url);
} catch (err) {
console.error('Failed to export MIDI:', err);
alert('Failed to export MIDI file');
}
};
if (loading) {
return <div className="score-editor loading">Loading score...</div>;
}
if (error) {
return (
<div className="score-editor error">
<h2>Error Loading Score</h2>
<p>{error}</p>
<button onClick={loadScore}>Retry</button>
</div>
);
}
return (
<div className="score-editor">
<div className="editor-toolbar">
<h2>Score Editor</h2>
<div className="toolbar-actions">
<button onClick={handleExportMIDI}>Export MIDI</button>
</div>
</div>
<InstrumentTabs
instruments={instruments}
activeInstrument={activeInstrument}
onInstrumentChange={setActiveInstrument}
/>
<PlaybackControls />
<NotationCanvas />
<div className="editor-instructions">
<h3>Editing Instructions (MVP)</h3>
<ul>
<li>Click notes to select them</li>
<li>Press Delete to remove selected notes</li>
<li>Press 1-8 to change note duration</li>
<li>Full editing features coming soon...</li>
</ul>
</div>
</div>
);
}
|