Show length on a staff under each fingering chart.
AppImage release / appimage (push) Successful in 50s
AppImage release / appimage (push) Successful in 50s
Dragging no longer selects text. The line box resizes, rests and spaces can be added, and Save PNG writes the row. Each chart keeps the foot at the top, and the treble staff shows the pitch with a notehead, stem, flags, and dots. Version 0.2.1. Co-authored-by: catpersonisme <catpersonisme@gmail.com>
This commit is contained in:
11 files changed
+997
-177
No files matched your search
+41
-1
@@ -1,17 +1,48 @@
|
||||
import { DURATION_PRESETS, type DurationPreset } from "@/lib/duration";
|
||||
import { newId } from "@/lib/score";
|
||||
|
||||
export type LineKind = "note" | "rest" | "space";
|
||||
|
||||
export type LineNote = {
|
||||
id: string;
|
||||
kind: LineKind;
|
||||
pitch: string;
|
||||
quarters: number;
|
||||
label: string;
|
||||
};
|
||||
|
||||
export const LINE_BOX = {
|
||||
minWidth: 280,
|
||||
minHeight: 200,
|
||||
maxHeight: 880,
|
||||
};
|
||||
|
||||
const QUARTER = DURATION_PRESETS.find((preset) => preset.quarters === 1) ?? DURATION_PRESETS[3];
|
||||
|
||||
export function createNote(pitch: string, preset: DurationPreset = QUARTER, id = newId("n")): LineNote {
|
||||
return { id, pitch, quarters: preset.quarters, label: preset.label };
|
||||
return { id, kind: "note", pitch, quarters: preset.quarters, label: preset.label };
|
||||
}
|
||||
|
||||
export function createRest(preset: DurationPreset = QUARTER, id = newId("r")): LineNote {
|
||||
return { id, kind: "rest", pitch: "", quarters: preset.quarters, label: preset.label };
|
||||
}
|
||||
|
||||
export function createSpace(preset: DurationPreset = QUARTER, id = newId("s")): LineNote {
|
||||
return { id, kind: "space", pitch: "", quarters: preset.quarters, label: preset.label };
|
||||
}
|
||||
|
||||
/** Keep a resized line box on the page. `maxWidth` is the room the page actually has. */
|
||||
export function clampLineBox(
|
||||
width: number,
|
||||
height: number,
|
||||
maxWidth: number,
|
||||
): { width: number; height: number } {
|
||||
const limit = Math.max(160, maxWidth);
|
||||
const minWidth = Math.min(LINE_BOX.minWidth, limit);
|
||||
return {
|
||||
width: Math.round(Math.min(limit, Math.max(minWidth, width))),
|
||||
height: Math.round(Math.min(LINE_BOX.maxHeight, Math.max(LINE_BOX.minHeight, height))),
|
||||
};
|
||||
}
|
||||
|
||||
export function insertNote(notes: LineNote[], index: number, note: LineNote): LineNote[] {
|
||||
@@ -36,6 +67,15 @@ export function removeNote(notes: LineNote[], id: string): LineNote[] {
|
||||
return notes.filter((note) => note.id !== id);
|
||||
}
|
||||
|
||||
const DURATION_ORDER = [...DURATION_PRESETS].sort((a, b) => a.quarters - b.quarters);
|
||||
|
||||
/** The next longer or shorter preset. Null at either end of the list. */
|
||||
export function neighborDuration(quarters: number, direction: -1 | 1): DurationPreset | null {
|
||||
const index = DURATION_ORDER.findIndex((preset) => Math.abs(preset.quarters - quarters) < 0.001);
|
||||
if (index < 0) return null;
|
||||
return DURATION_ORDER[index + direction] ?? null;
|
||||
}
|
||||
|
||||
export function setDuration(notes: LineNote[], id: string, preset: DurationPreset): LineNote[] {
|
||||
return notes.map((note) =>
|
||||
note.id === id ? { ...note, quarters: preset.quarters, label: preset.label } : note,
|
||||
|
||||
Reference in new issue
Block a user