Skip to content

Fix lost LRCLIB linebreaks and cap the visible line length - #135

Merged
MrDix merged 2 commits into
mainfrom
fix/linebreak-loss
Jul 20, 2026
Merged

Fix lost LRCLIB linebreaks and cap the visible line length#135
MrDix merged 2 commits into
mainfrom
fix/linebreak-loss

Conversation

@MrDix

@MrDix MrDix commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Problem

Converted songs ended up with almost no linebreaks: entire verses rendered as ONE screen line in the game. That compresses every note to an unreadable sliver (note width shrinks with the line's total duration) and makes the lyric line overflow the screen. A recently converted song had 10 breaks across 321 notes, with the longest screen line at 368 visible characters; the standard test song had zero LRC linebreaks surviving.

Root cause

_trim_word_to_voiced() (reference-lyrics pipeline, step 2b) rebuilt the word dict from scratch:

return {"word": word["word"], "start": new_start, "end": new_end}

This silently dropped the line_end (and backing) flags. CTC alignment stretches precisely the last word of each LRC line into the following pause - so almost every line-end word went through trimming, destroying nearly all LRCLIB linebreaks before they reached the writer. Trimming now preserves all extra keys ({**word, ...}), with a regression test.

Safety net: maximum visible line length

Even with correct flags, overlong lines can still happen (genuinely long LRC lines, or silence-fallback songs whose gaps are too uniform). The writer now enforces a cap of 45 visible characters per line:

  • Overlong lines are split at the largest pause on a word boundary - that is where a screen change feels natural.
  • Splits never land inside a melisma run (continuation notes carry no trailing space and are not candidates).
  • When no pause stands out, the split closest to the middle wins; no forced line ever gets shorter than 10 characters.
  • Nested overlong lines are handled via a worklist (each half is re-checked).

To make this possible, the linebreak decision moved out of the streaming writer loop into _compute_linebreak_indices(). The previous behavior (LRCLIB flags preferred, silence-percentile fallback including the separated-word workaround) is ported unchanged and covered by tests.

Verification

Re-converted a previously affected song end-to-end with the fix:

before after
Linebreaks 10 69 (48 from LRC + 21 safety net)
Longest screen line 368 chars 45 chars
LRC linebreaks surviving the pipeline ~0-10 48 (log now shows "48 LRC linebreaks")
  • 12 new unit tests (flag preservation on trim; LRC/silence-fallback break computation; max-length enforcement incl. melisma protection, middle-split fallback, nested splits); all pass.
  • Pre-existing failures in test_ultrastar_writer.py / test_reference_lyrics_aligner.py (librosa lazy-loader environment issue) are unrelated and unchanged.

Summary by CodeRabbit

  • Bug Fixes

    • Improved lyric alignment so trimming trailing silence preserves lyric metadata and line-ending information.
  • Improvements

    • Enhanced UltraStar lyric line breaking using explicit break markers and silence gaps.
    • Added safeguards to split overly long lyric lines at suitable word boundaries.
    • Improved handling of melisma notes and existing line breaks to prevent awkward splits.

Converted songs ended up with almost no linebreaks: entire verses were
rendered as one screen line, compressing every note to an unreadable
sliver and pushing the lyrics off-screen.

Root cause: _trim_word_to_voiced() rebuilt the word dict from scratch
and silently dropped the line_end (and backing) flags. CTC alignment
stretches precisely the LAST word of each LRC line into the following
pause, so almost every line-end word went through trimming - which
destroyed nearly all LRCLIB linebreaks (one affected song kept 10 of
~48; another kept none). Trimming now preserves all extra keys.

Safety net: the writer now enforces a maximum visible line length
(45 characters). Overlong lines - LRCLIB lines that are genuinely too
long, or silence-fallback songs without enough distinct gaps - are
split at the largest pause on a word boundary (never inside a melisma
run); when no pause stands out, the split closest to the middle wins.
The linebreak decision was refactored out of the streaming writer loop
into _compute_linebreak_indices() to make this possible; the previous
LRCLIB-flag and silence-percentile behavior is ported unchanged.

Verified on a previously affected song: 10 -> 69 breaks across 355
notes, longest screen line down from 368 to 45 visible characters,
with 48 LRC linebreaks now surviving the pipeline (12 new unit tests).
@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@MrDix, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 51 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 59bcc545-19dd-4462-be33-64c3af49c02a

📥 Commits

Reviewing files that changed from the base of the PR and between 21b7328 and 7f08483.

📒 Files selected for processing (2)
  • pytest/modules/UltraStar/test_ultrastar_writer.py
  • src/modules/Ultrastar/ultrastar_writer.py
📝 Walkthrough

Walkthrough

The changes preserve metadata when trimming reference lyric words and refactor UltraStar linebreak handling into precomputed helpers with silence, LRCLIB, and maximum-length rules. New tests cover metadata retention, split selection, melisma handling, nested splits, and existing break indices.

Changes

Reference lyrics metadata

Layer / File(s) Summary
Preserve metadata during voiced trimming
src/modules/Speech_Recognition/reference_lyrics_aligner.py, pytest/modules/Speech_Recognition/test_reference_lyrics_aligner.py
_trim_word_to_voiced now retains existing word fields while updating start and end; tests verify timing, text, and flags.

UltraStar linebreak calculation

Layer / File(s) Summary
Compute and enforce linebreak indices
src/modules/Ultrastar/ultrastar_writer.py
New helpers select LRCLIB or silence-based breaks and iteratively split overlong lines at valid word boundaries.
Use computed breaks during note writing
src/modules/Ultrastar/ultrastar_writer.py, pytest/modules/UltraStar/test_ultrastar_writer.py
Note writing emits breaks from the precomputed set, while tests cover fallback behavior, gap scoring, melisma boundaries, nested splits, and existing breaks.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant create_ultrastar_txt
  participant _compute_linebreak_indices
  participant _enforce_max_line_length
  participant UltraStarOutput
  create_ultrastar_txt->>_compute_linebreak_indices: Pass MIDI segments and silence threshold
  _compute_linebreak_indices->>_enforce_max_line_length: Enforce maximum lyric line length
  _enforce_max_line_length-->>_compute_linebreak_indices: Return additional break indices
  _compute_linebreak_indices-->>create_ultrastar_txt: Return complete break set
  create_ultrastar_txt->>UltraStarOutput: Emit notes and LINEBREAK tags
Loading

Possibly related PRs

  • MrDix/UltraSinger#55: Introduces the reference-lyrics alignment pipeline touched by the word-trimming metadata change.
  • MrDix/UltraSinger#57: Introduces LRCLIB-driven linebreak behavior refactored by the UltraStar writer changes.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main changes: preserving LRCLIB linebreaks and limiting visible lyric line length.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/linebreak-loss

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@MrDix

MrDix commented Jul 19, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/modules/Ultrastar/ultrastar_writer.py`:
- Around line 203-272: Update the visible character calculations in
_enforce_max_line_length, including visible_chars and the candidate-scan
left_chars accumulation, to count the rendered concatenated text rather than
adding a space for every segment; preserve spaces only where
midi_segments[k].word actually includes its trailing boundary space, so melisma
continuations are not overcounted. Add a regression test covering exact rendered
lengths for lines containing continuation segments.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5a4095cf-680a-44aa-8102-24184ebad6e2

📥 Commits

Reviewing files that changed from the base of the PR and between 091658e and 21b7328.

📒 Files selected for processing (4)
  • pytest/modules/Speech_Recognition/test_reference_lyrics_aligner.py
  • pytest/modules/UltraStar/test_ultrastar_writer.py
  • src/modules/Speech_Recognition/reference_lyrics_aligner.py
  • src/modules/Ultrastar/ultrastar_writer.py

Comment thread src/modules/Ultrastar/ultrastar_writer.py
Syllable and continuation notes carry no trailing space and attach
directly to the previous note in the rendered line - counting a
separator per note overcounted such lines and could force-split
lines whose true rendered length was within the limit.
@MrDix
MrDix merged commit ee0022d into main Jul 20, 2026
2 checks passed
@MrDix
MrDix deleted the fix/linebreak-loss branch July 20, 2026 00:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant