118 lines
4.7 KiB
TypeScript
118 lines
4.7 KiB
TypeScript
import React, { useMemo, useState, useEffect } from 'react';
|
|
import './InstrumentSelection.css';
|
|
import { useProjectStore } from '../stores/projectStore';
|
|
import { INSTRUMENT_GROUPS, FLUIDR3_INSTRUMENT_MAP } from '../constants/generalMidiConstants';
|
|
import { KGMidiTrack, type InstrumentType } from '../core/track/KGMidiTrack';
|
|
import { KGAudioTrack } from '../core/track/KGAudioTrack';
|
|
import { useI18n } from '../i18n/useI18n';
|
|
import { getInstrumentDisplayName, getInstrumentGroupLabel, type InstrumentGroupKey } from '../i18n/instruments';
|
|
|
|
const InstrumentSelection: React.FC = () => {
|
|
const { t } = useI18n();
|
|
const {
|
|
tracks,
|
|
selectedTrackId,
|
|
closeInstrumentSelection,
|
|
setTrackInstrument
|
|
} = useProjectStore();
|
|
|
|
const targetTrack = useMemo(() => {
|
|
return tracks.find(t => t.getId().toString() === selectedTrackId) || null;
|
|
}, [tracks, selectedTrackId]);
|
|
|
|
const currentInstrumentKey: InstrumentType = (targetTrack && targetTrack instanceof KGMidiTrack)
|
|
? (targetTrack.getInstrument() as InstrumentType)
|
|
: 'acoustic_grand_piano';
|
|
|
|
const currentInstrumentDef = FLUIDR3_INSTRUMENT_MAP[currentInstrumentKey] || FLUIDR3_INSTRUMENT_MAP['acoustic_grand_piano'];
|
|
|
|
// Maintain selected group in local state; selected instrument derives from model
|
|
const [selectedGroupKey, setSelectedGroupKey] = useState<string>(currentInstrumentDef?.group || 'PIANO_AND_KEYBOARDS');
|
|
|
|
useEffect(() => {
|
|
// Sync when the target track or its instrument changes
|
|
setSelectedGroupKey(currentInstrumentDef?.group || 'PIANO_AND_KEYBOARDS');
|
|
}, [selectedTrackId, currentInstrumentKey, currentInstrumentDef]);
|
|
|
|
const groups = useMemo(() => Object.keys(INSTRUMENT_GROUPS) as InstrumentGroupKey[], []);
|
|
|
|
const instrumentsInGroup = useMemo<Array<{ key: InstrumentType; label: string }>>(() => {
|
|
return Object.entries(FLUIDR3_INSTRUMENT_MAP)
|
|
.filter((entry) => entry[1].group === selectedGroupKey)
|
|
.map((entry) => ({ key: entry[0] as InstrumentType, label: getInstrumentDisplayName(entry[0] as InstrumentType, t) }));
|
|
}, [selectedGroupKey, t]);
|
|
|
|
const handleSelectGroup = (groupKey: string) => {
|
|
setSelectedGroupKey(groupKey);
|
|
};
|
|
|
|
const handleSelectInstrument = async (instrumentKey: InstrumentType) => {
|
|
// If no valid target track, ignore user interaction
|
|
if (!targetTrack || !(targetTrack instanceof KGMidiTrack)) return;
|
|
try {
|
|
await setTrackInstrument(targetTrack.getId(), instrumentKey);
|
|
} catch (err) {
|
|
console.error('Failed to change instrument from panel:', err);
|
|
}
|
|
};
|
|
|
|
const isAudioTrack = targetTrack instanceof KGAudioTrack;
|
|
const previewImage = isAudioTrack ? 'speaker.png' : (FLUIDR3_INSTRUMENT_MAP[currentInstrumentKey]?.image || 'piano.png');
|
|
const previewAlt = isAudioTrack ? 'Audio Track' : getInstrumentDisplayName(currentInstrumentKey, t);
|
|
const hasTargetTrack = !!targetTrack;
|
|
|
|
return (
|
|
<div className="instrument-selection">
|
|
<div className="instrument-selection-header">
|
|
<h3>{hasTargetTrack ? previewAlt : ''}</h3>
|
|
<button className="instrument-selection-close-btn" onClick={closeInstrumentSelection}>✕</button>
|
|
</div>
|
|
<div className="instrument-selection-top">
|
|
{hasTargetTrack && (
|
|
<div className="instrument-preview">
|
|
<img
|
|
src={`${import.meta.env.BASE_URL}resources/instruments/${previewImage}`}
|
|
alt={previewAlt.toString()}
|
|
width={256}
|
|
height={256}
|
|
/>
|
|
</div>
|
|
)}
|
|
<div className="instrument-name-overlay">{hasTargetTrack ? targetTrack.getName() : ''}</div>
|
|
</div>
|
|
{!isAudioTrack && (
|
|
<div className="instrument-selection-bottom">
|
|
<div className="instrument-groups">
|
|
<div className="instrument-groups-list">
|
|
{groups.map((key) => (
|
|
<div
|
|
key={key}
|
|
className={`instrument-group-item${selectedGroupKey === key ? ' active' : ''}`}
|
|
onClick={() => handleSelectGroup(key)}
|
|
>
|
|
{getInstrumentGroupLabel(key, t)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<div className="instrument-list">
|
|
<div className="instrument-instruments-list">
|
|
{instrumentsInGroup.map((inst) => (
|
|
<div
|
|
key={inst.key}
|
|
className={`instrument-instrument-item${currentInstrumentKey === inst.key ? ' active' : ''}`}
|
|
onClick={() => handleSelectInstrument(inst.key)}
|
|
>
|
|
{inst.label}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default InstrumentSelection;
|