/** * The note model behind the piano roll. * * Time is counted in *units*, where one unit is the tune's L: default note * length. That is the same currency ABC itself writes lengths in, so a note * written `A2` under `L:1/8` is two units long and needs no conversion in * either direction. Using seconds or beats here would mean rounding on every * round trip, and rounding is how an edited score slowly drifts out of time. */ export interface AbcNote { id: string; /** Index into AbcScore.voices. */ voice: number; /** Onset in units from the start of the piece. */ start: number; /** Length in units. Always at least 1. */ duration: number; /** MIDI note number. Rests are held separately, so this is always a pitch. */ midi: number; /** True when this note is tied into the one that follows it. */ tied: boolean; } /** * A quoted string in the music: a chord symbol like "G#m", or an annotation * like "^rit." with a placement prefix. YuE2 emits chord symbols routinely, so * these are carried through rather than refused. */ export interface AbcMark { id: string; voice: number; /** Onset in units, matching AbcNote.start. */ start: number; /** The text as written, without the quotes. */ text: string; /** True for a placement-prefixed annotation rather than a chord symbol. */ annotation: boolean; } export interface AbcVoice { /** The V: identifier as written, e.g. "Vocal". */ id: string; /** Anything after the identifier on the V: line, kept so it survives a rewrite. */ rest: string; } export interface AbcScore { /** Header lines above the first voice, verbatim and in order. */ header: string[]; voices: AbcVoice[]; notes: AbcNote[]; marks: AbcMark[]; /** Meter numerator and denominator from M:, defaulting to 4/4. */ meter: [number, number]; /** Unit note length from L:, defaulting to 1/8. */ unit: [number, number]; /** Key signature from K:, used by the grid to shade the scale. */ key: string; /** True when a repeat was written out so the grid could show it. */ repeatsExpanded: boolean; /** Beats per minute from Q:, defaulting to 120 quarter notes. */ tempo: number; } /** Seconds one unit lasts at this score's tempo. */ export function unitSeconds(score: Pick): number { const [un, ud] = score.unit; // A unit is un/ud of a whole note; a whole note is four quarters. const quarters = (un / ud) * 4; return quarters * (60 / Math.max(20, score.tempo)); } /** Why a score could not be opened in the grid. */ export interface AbcParseFailure { ok: false; reason: string; /** The construct that stopped us, so the message can name it. */ offending: string | null; } export interface AbcParseSuccess { ok: true; score: AbcScore; } export type AbcParseResult = AbcParseSuccess | AbcParseFailure; /** How many units fill one bar, given the meter and unit length. */ export function unitsPerBar(score: Pick): number { const [mn, md] = score.meter; const [un, ud] = score.unit; const bars = (mn / md) / (un / ud); // A meter that does not divide evenly into the unit length is legal ABC but // makes a ragged grid, so it is rounded rather than left fractional. return Math.max(1, Math.round(bars)); } export function noteName(midi: number): string { const names = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]; return `${names[((midi % 12) + 12) % 12]}${Math.floor(midi / 12) - 1}`; }