import { Signal } from "@char/aftercare"; import { canonBeat, type Track } from "../project/model.ts"; import { project } from "../project/store.ts"; export const ROW_H = 10; export const STRIP_H = 48; export const HEADER_W = 190; /** where typed chords go and playback starts, in beats. on chord tracks, the chord starting * here is the one that keyboard edits apply to */ export const cursor = new (class extends Signal { // it's compared against chord boundaries, so it lives on the same grid as they do override set(beats: number) { super.set(canonBeat(beats)); } })(0); // "none" is null rather than undefined in signals: aftercare doesn't notify changes away from undefined export const activeTrackId = new Signal(project.get().tracks[0]?.id ?? null); /** selected notes, on note tracks */ export const selection = new Signal<{ trackId: string; ids: readonly string[] } | null>(null); export type ChordEdit = { trackId: string; initial: string; selectAll: boolean }; export const chordEdit = new Signal(null); export const pxPerBeat = new Signal(64); /** a signal kept in localStorage: for preferences about the app rather than a project */ function remembered(name: string, initial: T): Signal { const key = `chord-pad/ui/${name}`; let value = initial; try { value = JSON.parse(localStorage.getItem(key) ?? "null") ?? initial; } catch { // unreadable: start over } const signal = new Signal(value); signal.subscribe(v => localStorage.setItem(key, JSON.stringify(v))); return signal; } export const paletteSevenths = remembered("palette-sevenths", false); export const chordPanelOpen = remembered("chord-panel-open", true); export const mixtureOpen = remembered("mixture-open", false); export const rollHeight = (t: Track): number => (t.view.high - t.view.low + 1) * ROW_H; export const laneHeight = (t: Track): number => (t.kind === "chords" ? STRIP_H : 0) + rollHeight(t); export const selectedIds = (trackId: string): readonly string[] => { const sel = selection.get(); return sel?.trackId === trackId ? sel.ids : []; }; project.subscribe((p, old) => { if (p.id === old.id) return; cursor.set(0); selection.set(null); chordEdit.set(null); activeTrackId.set(p.tracks[0]?.id ?? null); });