import { useRef } from 'react'; import { store, useApp } from '../state/store.ts'; import { getInstrument, listInstruments } from '../instruments/registry.ts'; import type { KnobSpec } from '../instruments/types.ts'; import { importInstrumentText, listImportedIds, removeImportedInstrument } from '../instruments/numbl/adapter.ts'; import { trackColor } from './colors.ts'; // Imported instrument bundles: a file picker that accepts exported // numbl-instrument-v1 JSON, and a chip per import so a stale one can be // removed (a shadowed catalog instrument of the same id comes back). function ImportedInstruments() { const fileRef = useRef(null); const imported = listImportedIds(); const onFiles = async (files: FileList | null) => { if (!files) return; for (const f of files) { try { const bundle = await importInstrumentText(await f.text()); store.setStatus(`imported instrument '${bundle.name}'`); } catch (err) { store.setStatus(err instanceof Error ? err.message : String(err)); } } store.bumpInstruments(); }; return (
{ void onFiles(e.target.files); e.target.value = ''; }} /> {imported.length > 0 && (
{imported.map((id) => ( {id} ))}
)}
); } function fmtKnob(v: number): string { const a = Math.abs(v); if (a >= 1e5 || (a > 0 && a < 0.01)) return v.toExponential(1); return String(Number(v.toPrecision(3))); } function KnobSlider({ knob, value, onChange }: { knob: KnobSpec; value: number | undefined; onChange: (v: number) => void }) { const cur = value ?? knob.default; const log = knob.scale === 'log'; const pos = log ? (1000 * Math.log(cur / knob.min)) / Math.log(knob.max / knob.min) : cur; return ( ); } export function TrackList() { const app = useApp(); const song = app.rec?.song ?? null; if (!song) return
; const instruments = listInstruments(); return (
{song.tracks.map((t, i) => { const active = t.id === app.activeTrackId; const armed = t.id === app.armedTrackId; return (
store.setActiveTrack(t.id)}>
store.updateTrack(t.id, { name: e.target.value }, false)} onBlur={() => store.endEdit()} onClick={(e) => e.stopPropagation()} />
{active && (getInstrument(t.instrumentId).knobs ?? []).length > 0 && (
{(getInstrument(t.instrumentId).knobs ?? []).map((k) => ( store.setTrackSetting(t.id, k.key, v)} /> ))}
)}
store.updateTrack(t.id, { volume: Number(e.target.value) }, false)} onClick={(e) => e.stopPropagation()} /> {t.notes.length} notes
); })}
); }