/ proof-of-concept / midiboot
Sign in
proof-of-concept / midiboot
midiboot / src / input / recorder.ts
49 lines · 1.4 KBBlameHistoryRaw
1import type { NoteInputEvent } from './webmidi.ts';
3// Captures note input against the transport clock while recording. Times
4// are seconds on the song timeline; the store converts to ticks when the
5// take is committed.
7export interface CapturedNote {
8 startSec: number;
9 endSec: number;
10 pitch: number;
11 velocity: number; // 1..127
14export class Recorder {
15 private active = false;
16 private open = new Map<number, { startSec: number; velocity: number }>();
17 private captured: CapturedNote[] = [];
19 get isActive(): boolean { return this.active; }
21 start(): void {
22 this.active = true;
23 this.open.clear();
24 this.captured = [];
25 }
27 noteEvent(e: NoteInputEvent, songSec: number): void {
28 if (!this.active) return;
29 if (e.on) {
30 this.open.set(e.pitch, { startSec: songSec, velocity: e.velocity });
31 } else {
32 const o = this.open.get(e.pitch);
33 if (!o) return;
34 this.open.delete(e.pitch);
35 this.captured.push({ startSec: o.startSec, endSec: Math.max(o.startSec + 0.03, songSec), pitch: e.pitch, velocity: o.velocity });
36 }
37 }
39 stop(atSongSec: number): CapturedNote[] {
40 for (const [pitch, o] of this.open) {
41 this.captured.push({ startSec: o.startSec, endSec: Math.max(o.startSec + 0.03, atSongSec), pitch, velocity: o.velocity });
42 }
43 this.open.clear();
44 this.active = false;
45 const out = this.captured.sort((a, b) => a.startSec - b.startSec);
46 this.captured = [];
47 return out;
48 }
↑↓ move↵openescclose