Skip to content

feat(midi): extract lyric meta-events and vocal melody into sidecar payloads#1038

Open
ChrisBeWithYou wants to merge 1 commit into
mainfrom
midi-lyrics-import
Open

feat(midi): extract lyric meta-events and vocal melody into sidecar payloads#1038
ChrisBeWithYou wants to merge 1 commit into
mainfrom
midi-lyrics-import

Conversation

@ChrisBeWithYou

Copy link
Copy Markdown
Contributor

What

lib/midi_import.py drops lyric meta-events entirely, so karaoke MIDI files lose both lyrics and vocal melody on import. This adds a third extraction flow (alongside keys and drums):

  • extract_midi_lyrics(midi_path, audio_offset=0.0) returns {"lyrics": [...§7.1...], "lyrics_source": "authored", "vocal_pitch": {...§7.2...} | None}, or None when the file has no usable lyrics (current behavior unchanged). Payloads only — the pak/manifest is assembled by the editor plugin, which gets a small follow-up PR to consume this (tracked on the org board).
  • Events: 0x05 Lyric everywhere; 0x01 Text only on vocal-ish / "Words" / "Karaoke" tracks (conservative by design — loosening it risks importing copyright-notice text events as lyrics).
  • .kar conventions: / and \ line-break prefixes fold onto the previous syllable as §7.1 +; @/% metadata dropped.
  • Word conventions per stream: hyphen-delimited streams pass - joins through untouched; space-delimited streams synthesize - joins; whole-word streams get none; line ends become trailing + (a - is stripped first — joins can't cross a line break).
  • Melody pairing: greedy time-ordered, one note per syllable within ±0.30 s; paired syllables snap t/d to the note so the two sidecars stay mirrored per §7.2; melisma extras skipped; unpaired syllables stay lyrics-only (spec-legal). Vocal-track selection mirrors the GP importer's idiom (name keywords + GM programs 52-54/85-87); SMF type-0 isolates vocal-program channels, type-1/2 trusts the lyric-carrying note track.
  • Unpaired durations: run to the next syllable, clamped 0.1–2.0 s.
  • Tempo mapping reuses _build_tick_to_seconds (correct type-0/1/2 scoping).

Tests

New tests/test_midi_lyrics.py (15 tests, in-memory mido fixtures matching the existing midi test style): both-sidecars from a vocal track; GM-program detection without a name; lyrics-only; no-lyrics → None; unpaired durations; .kar / and \+; hyphen pass-through; join synthesis; embedded newlines; Text fallback with @KMIDI dropped; Text rejected on non-vocal tracks; format-0 channel isolation; format-0 lyrics-only fallback; audio_offset on both payloads.

test_midi_lyrics.py: 15 passed. test_midi_import.py + test_midi_import_drums.py + test_midi_tempo_map.py: 44 passed.

Notes for review

  • The main judgment call open to veto: the conservative Text-event gate means a format-0 file using bare 0x01 events with no vocal program/name is missed.
  • lyrics_source: "authored" — lyric meta events are chart-author data, not transcription; no lyric_transcription/pitch_extraction provenance blocks are written (reserved for automated engines).

🤖 Generated with Claude Code

…ayloads

MIDI import previously ignored lyric meta-events entirely, so karaoke
MIDI files lost their lyrics and vocal melody on import.

- new extract_midi_lyrics(midi_path, audio_offset): returns spec 7.1
  lyrics + spec 7.2 vocal_pitch payloads, or None when the file has no
  usable lyrics (existing behavior unchanged)
- collects 0x05 Lyric events, with 0x01 Text as a conservative fallback
  on vocal-ish / Words / Karaoke tracks only (avoids importing copyright
  notices as lyrics)
- .kar conventions handled: '/' and '\' line-break prefixes fold onto
  the previous syllable as 7.1 '+'; '@'/'%' metadata dropped
- per-stream word conventions: hyphen-delimited streams pass '-' joins
  through; space-delimited streams synthesize joins; whole-word streams
  get none; line ends become trailing '+' (stripping a '-' first)
- melody pairing: greedy time-ordered, one note per syllable within
  0.30s; paired syllables snap t/d to the note so both sidecars stay
  mirrored per 7.2; unpaired syllables remain lyrics-only (spec-legal)
- vocal-track selection mirrors the GP importer's idiom (name keywords
  + GM programs 52-54/85-87); SMF type 0 isolates vocal-program
  channels, type 1/2 trusts the lyric-carrying note track
- unpaired lyric durations run to the next syllable, clamped 0.1-2.0s

Callers (the editor plugin's import flow) wire the payloads into the
pak in a follow-up; no manifest keys are written here.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: ChrisBeWithYou <chris@rifflarr.local>
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

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

Next review available in: 59 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 Plus

Run ID: 1a784331-64f8-411f-8c19-3315ff00f145

📥 Commits

Reviewing files that changed from the base of the PR and between 8297afc and c86b2c8.

📒 Files selected for processing (2)
  • lib/midi_import.py
  • tests/test_midi_lyrics.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch midi-lyrics-import

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.

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