Files
2026-05-06 17:56:38 +02:00

90 lines
3.6 KiB
TypeScript

// ─── EventBus typed event map ─────────────────────────────────────────────────
// All inter-component communication flows through these events.
// Views subscribe; only SyncEngine emits (except input:note from MIDI/WebMidi).
import type { NoteEvent, PlaybackStatus } from "./MusicData";
export interface SyncEventMap {
// ── Playback events ──────────────────────────────────────────────────────────
/**
* Emitted LOOKAHEAD_SECONDS before a note plays.
* FallingNotes pre-renders the bar so it's ready when it enters the viewport.
*/
"note:upcoming": NoteEvent;
/**
* Emitted at the exact moment a note begins playing.
* PianoKeyboard highlights the key; Tone.js triggers the sample.
*/
"note:on": NoteEvent;
/**
* Emitted at the exact moment a note stops playing.
* PianoKeyboard un-highlights the key.
*/
"note:off": NoteEvent;
/**
* Emitted every animation frame (~16ms) with the current playback position.
* SheetMusicView drives the AlphaTab cursor; FallingNotesView moves bars.
*/
"transport:tick": { seconds: number; tick: number };
/**
* Emitted when transport status changes (play/pause/stop/loop/waiting).
*/
"transport:stateChange": { status: PlaybackStatus; seconds: number };
// ── User input events ────────────────────────────────────────────────────────
/**
* Emitted when the user plays a note on the physical piano or virtual keyboard.
* SyncEngine uses this for wait-mode comparison.
* PianoKeyboard echoes the key highlight with a different color.
*/
"input:note:on": { midi: number; velocity: number };
/**
* Emitted when the user releases a note.
*/
"input:note:off": { midi: number };
// ── Document events ──────────────────────────────────────────────────────────
/**
* Emitted when a new MusicDocument is loaded and all views should reset.
*/
"document:loaded": { id: string; title: string; totalDuration: number };
/**
* Emitted when settings change that affect all views (e.g., toggle labels,
* change hand colors, toggle fingering).
*/
"settings:changed": { key: string; value: unknown };
// ── Transport ────────────────────────────────────────────────────────────────
/**
* Emitted at the start of every seek/skip so UI can release pressed keys
* before Tone cancels pending note:off callbacks.
*/
"transport:seek": { seconds: number };
// ── Practice features ────────────────────────────────────────────────────────
/**
* Emitted by SyncEngine when the speed trainer advances one step.
* The store updates settings.speedTrainer.currentPct to keep UI in sync.
*/
"speedTrainer:stepped": { currentPct: number };
/**
* Emitted by AccuracyTracker after each note event with running totals.
*/
"accuracy:update": { correct: number; wrong: number; missed: number; score: number };
}
export type SyncEventName = keyof SyncEventMap;
export type SyncEventPayload<K extends SyncEventName> = SyncEventMap[K];