Skip to content

Add LRCLIB freestyle notes and linebreak support - #57

Merged
MrDix merged 2 commits into
mainfrom
feat/lrclib-freestyle-linebreaks
Mar 26, 2026
Merged

Add LRCLIB freestyle notes and linebreak support#57
MrDix merged 2 commits into
mainfrom
feat/lrclib-freestyle-linebreaks

Conversation

@MrDix

@MrDix MrDix commented Mar 26, 2026

Copy link
Copy Markdown
Owner

Summary

  • Parse parenthesized (backing vocals) and bracketed [ad-libs] text from LRCLIB lyrics as freestyle notes (F prefix in UltraStar — displayed but not scored)
  • Use LRCLIB line breaks for UltraStar linebreak placement instead of silence-based detection when reference lyrics are available
  • Propagate freestyle and linebreak flags through all post-processing stages (merge, split, absorb)

Changes

  • TranscribedData: is_freestyle, line_break_after fields
  • MidiSegment: note_type (:/F), line_break_after fields
  • lyrics_corrector: New _parse_reference_lyrics() with bracket parsing, _tokenize_with_parens() for (multi word) and [bracket] patterns
  • midi_creator: Transfer freestyle/linebreak from TranscribedData → MidiSegment
  • ultrastar_writer: Use note_type instead of hardcoded NORMAL, prefer LRCLIB linebreaks over silence-based detection
  • UltraSinger.py + pitch_change_splitter: Propagate flags through merge_syllable_segments, _absorb_tilde_segments, split_notes_at_pitch_changes

Test plan

  • 40 unit tests covering tokenizer, parser, and end-to-end correction
  • Manual test: convert song with LRCLIB lyrics containing (backing vocals) — verify F notes in output
  • Manual test: verify linebreaks match LRCLIB line structure
  • Verify fallback to silence-based linebreaks when no LRCLIB lyrics available

Summary by CodeRabbit

  • New Features

    • Added support for freestyle sections in lyrics (parentheses and brackets).
    • Improved line break detection and handling in lyrics for better output formatting.
    • Enhanced metadata preservation through the correction and MIDI generation pipeline.
  • Tests

    • Expanded test coverage for lyrics correction, including freestyle detection and line break handling.

Parse parenthesized text (backing vocals) and brackets [ad-libs] from
LRCLIB lyrics as freestyle notes (F prefix in UltraStar). Use LRCLIB
line breaks for UltraStar linebreak placement instead of silence-based
detection when available.

- TranscribedData: add is_freestyle, line_break_after fields
- MidiSegment: add note_type (:/F), line_break_after fields
- lyrics_corrector: parse (brackets) as freestyle, track line breaks
- midi_creator: transfer freestyle/linebreak from TranscribedData
- ultrastar_writer: use note_type, prefer LRCLIB linebreaks
- Propagate flags through merge, split, absorb post-processing
- 40 tests covering tokenizer, parser, and end-to-end correction

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Mar 26, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@MrDix has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 24 minutes and 46 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a0adf64e-7cc3-40c0-be9b-092e27c73296

📥 Commits

Reviewing files that changed from the base of the PR and between 8882f14 and 101c19c.

📒 Files selected for processing (3)
  • pytest/modules/Speech_Recognition/test_lyrics_corrector.py
  • src/modules/Midi/midi_creator.py
  • src/modules/Ultrastar/ultrastar_writer.py
📝 Walkthrough

Walkthrough

This change adds LRCLIB metadata propagation throughout the audio processing pipeline. New is_freestyle and line_break_after fields are added to TranscribedData and MidiSegment dataclasses. The lyrics corrector now parses parentheses/brackets as freestyle regions and tracks line boundaries. Metadata is propagated during MIDI segment creation, pitch splitting, syllable merging, and Ultrastar file output.

Changes

Cohort / File(s) Summary
Data Models
src/modules/Speech_Recognition/TranscribedData.py, src/modules/Midi/MidiSegment.py
Added is_freestyle and line_break_after boolean fields to both dataclasses with default values of False.
Lyrics Correction
src/modules/Speech_Recognition/lyrics_corrector.py, pytest/modules/Speech_Recognition/test_lyrics_corrector.py
Replaced simple normalization with LRCLIB-aware parsing: tokenization detects parenthesized/bracketed "freestyle" spans; line boundaries converted to line_break_after flags. Updated matching to transfer reference metadata onto transcribed words. Added freestyle_words and linebreaks_applied tracking to LyricsLookupResult. Test suite expanded with dedicated tests for tokenization, reference parsing, and metadata propagation through correction.
MIDI Segment Generation & Splitting
src/modules/Midi/midi_creator.py, src/modules/Pitcher/pitch_change_splitter.py
MIDI creator now copies freestyle/linebreak metadata from TranscribedData to MidiSegment by index. Pitch splitter copies note_type across sub-segments and preserves line_break_after on final sub-segment and during merge operations.
Segment Merging
src/UltraSinger.py
Updated merge_syllable_segments() and _absorb_tilde_segments() to propagate is_freestyle (sets note_type = "F" on MidiSegment) and line_break_after metadata during tilde merging and same-pitch syllable consolidation.
Output Formatting
src/modules/Ultrastar/ultrastar_writer.py
Ultrastar writer now detects presence of LRCLIB linebreak metadata and conditionally emits linebreaks based on line_break_after flags instead of silence duration. Uses midi_segment.note_type field for per-note type output (supporting freestyle "F" marker).

Sequence Diagram(s)

sequenceDiagram
    participant LyricsParser as Lyrics Parser
    participant RefWords as Reference Words
    participant WhisperWords as Whisper Words (TranscribedData)
    participant MidiCreator as MIDI Creator
    participant MidiSegments as MIDI Segments
    participant MergeLogic as Merge/Split Logic
    participant UltrastarWriter as Ultrastar Writer

    LyricsParser->>RefWords: Parse lyrics with freestyle<br/>& linebreak metadata
    RefWords->>LyricsParser: [is_freestyle, line_break_after]
    
    WhisperWords->>MidiCreator: Transcribed words (plain)
    MidiCreator->>MidiCreator: Match & apply ref metadata
    MidiCreator->>MidiSegments: Copy is_freestyle<br/>& line_break_after
    
    MidiSegments->>MergeLogic: Segments with metadata
    MergeLogic->>MergeLogic: Merge/split preserves<br/>metadata flags
    MergeLogic->>MidiSegments: Updated segments
    
    MidiSegments->>UltrastarWriter: Final MIDI segments
    UltrastarWriter->>UltrastarWriter: Emit note_type (F/normal)<br/>& linebreaks per metadata
    UltrastarWriter->>UltrastarWriter: Generate Ultrastar output
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 Hops with glee through parsing lines,
Freestyle brackets, breaks align,
Metadata flows, no word left bare,
From lyrics to MIDI with utmost care!
The pipeline now remembers all~

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 39.58% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main changes: adding LRCLIB freestyle notes and linebreak support. It directly reflects the PR's primary objective.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/lrclib-freestyle-linebreaks

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 and usage tips.

@MrDix

MrDix commented Mar 26, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Mar 26, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

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

🧹 Nitpick comments (2)
pytest/modules/Speech_Recognition/test_lyrics_corrector.py (1)

335-340: Rename unused variable to silence linter warning.

The result variable is unpacked but never used. Prefix with underscore per Python convention.

♻️ Proposed fix
-        data, result = correct_transcription_from_lyrics(
+        data, _ = correct_transcription_from_lyrics(
             td, "Hello (ooh)\nworld"
         )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pytest/modules/Speech_Recognition/test_lyrics_corrector.py` around lines 335
- 340, The test unpacks two values from correct_transcription_from_lyrics into
variables data and result but never uses result; rename it to _result to follow
Python linter convention for unused variables. Locate the call to
correct_transcription_from_lyrics in test_lyrics_corrector.py (the line using
data, result = correct_transcription_from_lyrics(...)) and change the second
identifier to _result so the linter no longer flags an unused variable.
src/modules/Midi/midi_creator.py (1)

217-225: Consider adding a warning when list lengths mismatch.

The guard i < len(midi_segments) prevents index errors, but if lengths differ, metadata from trailing transcribed_data entries is silently dropped. While this shouldn't occur in normal operation (both lists derive from the same input), a warning would help diagnose edge-case bugs.

♻️ Optional: Add a debug warning for length mismatch
         # Transfer LRCLIB metadata (freestyle, linebreak) from TranscribedData
+        if len(transcribed_data) != len(midi_segments):
+            print(f"{ULTRASINGER_HEAD} Warning: TranscribedData ({len(transcribed_data)}) "
+                  f"and MidiSegments ({len(midi_segments)}) length mismatch during metadata transfer")
         for i, td in enumerate(transcribed_data):
             if i < len(midi_segments):
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modules/Midi/midi_creator.py` around lines 217 - 225, The loop that
transfers LRCLIB metadata from transcribed_data to midi_segments silently drops
trailing transcribed_data when len(transcribed_data) > len(midi_segments); add a
warning when the lengths mismatch so this situation is visible. Before or after
the for-loop that iterates over transcribed_data (the block referencing
midi_segments and td.is_freestyle/td.line_break_after), check if
len(transcribed_data) != len(midi_segments) and emit a warning via the module
logger (e.g., logger.warning or logging.warning) including both lengths and
contextual info; keep the existing i < len(midi_segments) guard so behavior is
unchanged but now logged for debugging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/modules/Ultrastar/ultrastar_writer.py`:
- Around line 164-166: The condition in the if statement uses mixed and/or
without parentheses so the any(...) branch can fire even for the final segment;
change the boolean grouping so the "not final segment" guard applies to both
checks. Specifically, in the if that references silence_split_duration, i,
len(midi_segments), silence, and separated_word_silence, rewrite the boolean to:
silence_split_duration is not None and i != len(midi_segments) - 1 and (silence
> silence_split_duration or any(s > silence_split_duration for s in
separated_word_silence)) so the final-segment guard is evaluated before the or.

---

Nitpick comments:
In `@pytest/modules/Speech_Recognition/test_lyrics_corrector.py`:
- Around line 335-340: The test unpacks two values from
correct_transcription_from_lyrics into variables data and result but never uses
result; rename it to _result to follow Python linter convention for unused
variables. Locate the call to correct_transcription_from_lyrics in
test_lyrics_corrector.py (the line using data, result =
correct_transcription_from_lyrics(...)) and change the second identifier to
_result so the linter no longer flags an unused variable.

In `@src/modules/Midi/midi_creator.py`:
- Around line 217-225: The loop that transfers LRCLIB metadata from
transcribed_data to midi_segments silently drops trailing transcribed_data when
len(transcribed_data) > len(midi_segments); add a warning when the lengths
mismatch so this situation is visible. Before or after the for-loop that
iterates over transcribed_data (the block referencing midi_segments and
td.is_freestyle/td.line_break_after), check if len(transcribed_data) !=
len(midi_segments) and emit a warning via the module logger (e.g.,
logger.warning or logging.warning) including both lengths and contextual info;
keep the existing i < len(midi_segments) guard so behavior is unchanged but now
logged for debugging.
🪄 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: 57c5c8f4-337c-40f0-8d4d-f1f87305f91a

📥 Commits

Reviewing files that changed from the base of the PR and between d4a23d6 and 8882f14.

📒 Files selected for processing (8)
  • pytest/modules/Speech_Recognition/test_lyrics_corrector.py
  • src/UltraSinger.py
  • src/modules/Midi/MidiSegment.py
  • src/modules/Midi/midi_creator.py
  • src/modules/Pitcher/pitch_change_splitter.py
  • src/modules/Speech_Recognition/TranscribedData.py
  • src/modules/Speech_Recognition/lyrics_corrector.py
  • src/modules/Ultrastar/ultrastar_writer.py

Comment thread src/modules/Ultrastar/ultrastar_writer.py Outdated
- Fix operator precedence bug in silence-based linebreak condition
  (any() could trigger on final segment due to missing parentheses)
- Rename unused `result` variable to `_` in test
- Add warning when TranscribedData/MidiSegments length mismatch

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@MrDix

MrDix commented Mar 26, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Mar 26, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

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.

@MrDix
MrDix merged commit 406ba58 into main Mar 26, 2026
1 check passed
MrDix pushed a commit that referenced this pull request Jul 4, 2026
- Fix operator precedence bug in silence-based linebreak condition
  (any() could trigger on final segment due to missing parentheses)
- Rename unused `result` variable to `_` in test
- Add warning when TranscribedData/MidiSegments length mismatch

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
MrDix added a commit that referenced this pull request Jul 4, 2026
Add LRCLIB freestyle notes and linebreak support
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