/ proof-of-concept / midiboot
Sign in
proof-of-concept / midiboot
midiboot / src / components / TrackList.tsx
78 lines · 3.2 KBCodeBlameHistory
0ad1ec1midiboot: in-browser MIDI workstation with pluggable instrumentsJeremy Magland 1import { store, useApp } from '../state/store.ts';
2import { listInstruments } from '../instruments/registry.ts';
3import { trackColor } from './colors.ts';
5export function TrackList() {
6 const app = useApp();
7 const song = app.rec?.song ?? null;
8 if (!song) return <div className="tracklist" />;
9 const instruments = listInstruments();
10 return (
11 <div className="tracklist">
12 {song.tracks.map((t, i) => {
13 const active = t.id === app.activeTrackId;
14 const armed = t.id === app.armedTrackId;
15 return (
16 <div key={t.id} className={`trackrow ${active ? 'active' : ''}`} onClick={() => store.setActiveTrack(t.id)}>
17 <div className="trackrow-top">
18 <span className="chip" style={{ background: trackColor(i) }} />
19 <input
20 className="trackname"
21 value={t.name}
22 onChange={(e) => store.updateTrack(t.id, { name: e.target.value }, false)}
23 onBlur={() => store.endEdit()}
24 onClick={(e) => e.stopPropagation()}
25 />
26 <button
27 className="mini danger"
28 title="Delete track"
29 onClick={(e) => { e.stopPropagation(); store.removeTrack(t.id); }}
30 >✕</button>
31 </div>
32 <div className="trackrow-bottom">
33 <select
34 value={t.instrumentId}
35 onChange={(e) => store.updateTrack(t.id, { instrumentId: e.target.value })}
36 onClick={(e) => e.stopPropagation()}
37 title="Instrument"
38 >
39 {instruments.map((ins) => (
40 <option key={ins.id} value={ins.id}>{ins.name}</option>
41 ))}
42 </select>
43 <button
44 className={`mini ${t.mute ? 'toggled' : ''}`}
45 title="Mute"
46 onClick={(e) => { e.stopPropagation(); store.updateTrack(t.id, { mute: !t.mute }, false); store.endEdit(); }}
47 >M</button>
48 <button
49 className={`mini ${t.solo ? 'toggled' : ''}`}
50 title="Solo"
51 onClick={(e) => { e.stopPropagation(); store.updateTrack(t.id, { solo: !t.solo }, false); store.endEdit(); }}
52 >S</button>
53 <button
54 className={`mini rec ${armed ? 'toggled' : ''}`}
55 title="Arm for recording"
56 onClick={(e) => { e.stopPropagation(); store.setArmedTrack(armed ? null : t.id); }}
57 >R</button>
58 </div>
59 <div className="trackrow-vol">
60 <input
61 type="range"
62 min={0}
63 max={1.5}
64 step={0.01}
65 value={t.volume}
66 title={`Volume ${(t.volume * 100).toFixed(0)}%`}
67 onChange={(e) => store.updateTrack(t.id, { volume: Number(e.target.value) }, false)}
68 onClick={(e) => e.stopPropagation()}
69 />
70 <span className="notecount">{t.notes.length} notes</span>
71 </div>
72 </div>
73 );
74 })}
75 <button className="addtrack" onClick={() => store.addTrack()}>+ Add track</button>
76 </div>
77 );
↑↓ move↵openescclose