Skip to content

Commit 28829f9

Browse files
committed
playground: appended additional lines.
1 parent 4cd79ad commit 28829f9

2 files changed

Lines changed: 80 additions & 3 deletions

File tree

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,11 @@ The core code related to the Starry✨ project will be organized and released in
99
## Abstract
1010

1111
We propose a new approach for a practical two-stage Optical Music Recognition (OMR) pipeline, with a particular focus on its second stage. Given symbol and event candidates from the visual pipeline, we decode them into an editable, verifiable, and exportable score structure. We focus on complex polyphonic staff notation, especially piano scores, where voice separation and intra-measure timing are the main bottlenecks. Our approach formulates second-stage decoding as a structure decoding problem and uses topology recognition with probability-guided search (BeadSolver) as its core method. We also describe a data strategy that combines procedural generation with recognition-feedback annotations. The result is a practical decoding component for real OMR systems and a path to accumulate structured score data for future end-to-end, multimodal, and RL-style methods.
12+
13+
14+
## Relative Repositories
15+
16+
* [Lotus](https://github.com/k-l-lambda/lotus) — an SVG/LilyPond geometry pipeline used by the paper to recover per-glyph positions from engraved scores, giving generated topology samples realistic spatial layouts.
17+
* [Paraff](https://github.com/FindLab-org/paraff) — a compact symbolic-music DSL used in the paper's data-generation strategy to sample structurally valid measure-level music for topology-recognition training; this project is now archived, with *Lilylet* as its successor.
18+
* [IMSLP-Mining](https://github.com/k-l-lambda/imslp-mining) — a related data-mining project for converting open sheet-music images into usable symbolic datasets.
19+
* [Lilylet](https://github.com/k-l-lambda/lilylet) — a symbolic-music language designed as a LilyPond variant; Starry OMR supports Lilylet as one of its export formats for structured recognition results.

assets/score-viewer/playground.js

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,76 @@
499499
}
500500
}
501501

502+
function positionedTokenIds(liveScore) {
503+
return new Set((liveScore?.playback?.positions || [])
504+
.map(position => position.id)
505+
.filter(id => id !== undefined && id !== null)
506+
.map(String));
507+
}
508+
509+
function noteheadLedgerLines(staff, positionIds) {
510+
const stacks = [];
511+
512+
for (const measure of staff.measures || []) {
513+
const noteheads = (measure.tokens || []).filter(token => {
514+
if (!isNoteheadToken(token) || !Number.isFinite(token.x) || !Number.isFinite(token.y)) return false;
515+
return !positionIds.size || positionIds.has(String(token.id));
516+
});
517+
const groups = new Map();
518+
519+
for (const token of noteheads) {
520+
const key = Math.round(token.x * 4) / 4;
521+
const group = groups.get(key) || { left: token.x - 1.2, right: token.x + 1.2, ys: [] };
522+
group.left = Math.min(group.left, token.x - 1.2);
523+
group.right = Math.max(group.right, token.x + 1.2);
524+
group.ys.push(token.y);
525+
groups.set(key, group);
526+
}
527+
528+
for (const group of groups.values()) {
529+
if (group.ys.some(y => y <= -3)) {
530+
stacks.push({ left: group.left, right: group.right, n: Math.ceil(Math.min(...group.ys)) + 2 });
531+
}
532+
if (group.ys.some(y => y >= 3)) {
533+
stacks.push({ left: group.left, right: group.right, n: Math.floor(Math.max(...group.ys)) - 2 });
534+
}
535+
}
536+
}
537+
538+
return stacks;
539+
}
540+
541+
function hasPositionedNoteheadForLedgerLine(staff, stack, positionIds) {
542+
if (!positionIds.size) return true;
543+
const center = (stack.left + stack.right) / 2;
544+
545+
for (const measure of staff.measures || []) {
546+
for (const token of measure.tokens || []) {
547+
if (!isNoteheadToken(token) || !positionIds.has(String(token.id)) || !Number.isFinite(token.x) || !Number.isFinite(token.y)) continue;
548+
if (Math.abs(token.x - center) > 1.6) continue;
549+
if (stack.n < 0 && token.y <= -3) return true;
550+
if (stack.n > 0 && token.y >= 3) return true;
551+
}
552+
}
553+
554+
return false;
555+
}
556+
557+
function appendLedgerLines(parent, staff, positionIds) {
558+
const sourceLines = staff.additionalLines?.length ? staff.additionalLines : noteheadLedgerLines(staff, positionIds);
559+
const stacks = sourceLines
560+
.map(line => ({ left: line.left, right: line.right, n: line.n }))
561+
.filter(line => Number.isFinite(line.left) && Number.isFinite(line.right) && Number.isFinite(line.n) && line.n !== 0)
562+
.filter(line => hasPositionedNoteheadForLedgerLine(staff, line, positionIds));
563+
564+
for (const stack of stacks) {
565+
for (let ii = 1; ii <= Math.abs(stack.n); ii += 1) {
566+
const y = staff.staffY + (stack.n > 0 ? 2 + ii : -2 - ii);
567+
parent.appendChild(createSvgNode("line", { class: "additional-line", x1: stack.left, x2: stack.right, y1: y, y2: y }));
568+
}
569+
}
570+
}
571+
502572
function renderTokens(parent, staff) {
503573
for (const measure of staff.measures || []) {
504574
for (const token of measure.tokens || []) {
@@ -526,6 +596,7 @@
526596
const svg = createSvgNode("svg", { class: "live-score-page", viewBox: `0 0 ${page.w} ${page.h}` });
527597
const originalMode = originalToggle.getAttribute("aria-pressed") === "true";
528598
const showSource = originalMode || state.hoverSourcePage === pageIndex;
599+
const positionIds = positionedTokenIds(liveScore);
529600

530601
if (showSource && page.source?.url) {
531602
svg.appendChild(createSvgNode("image", {
@@ -565,9 +636,7 @@
565636
[-2, -1, 0, 1, 2].forEach(line => {
566637
staffGroup.appendChild(createSvgNode("line", { x1: 0, x2: system.w, y1: staff.staffY + line, y2: staff.staffY + line }));
567638
});
568-
for (const line of staff.additionalLines || []) {
569-
staffGroup.appendChild(createSvgNode("line", { x1: line.left, x2: line.right, y1: staff.staffY + line.n, y2: staff.staffY + line.n }));
570-
}
639+
appendLedgerLines(staffGroup, staff, positionIds);
571640
}
572641
renderTokens(staffGroup, staff);
573642
systemGroup.appendChild(staffGroup);

0 commit comments

Comments
 (0)