Add pitch-change split and enable refinement by default - #49
Conversation
When a singer changes pitch within a single word (melismas, runs, ornaments), UltraSinger now splits the note at pitch change boundaries instead of averaging to a single flat note. - New module: pitch_change_splitter.py with split_notes_at_pitch_changes() - Detects sustained pitch changes >2 semitones within note segments - Splits into sub-notes, merges fragments <80ms with nearest neighbor - First sub-note keeps original text, continuations use "~" marker - Low-confidence frames (<0.3) excluded from change detection - CLI: --pitch_change_split (disabled by default) - GUI: Toggle in Experimental Features section - 20 unit tests covering edge cases Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Refinement consistently improves scores across all tested songs. Changed default from opt-in (--refine_from_vocal) to opt-out (--disable_refine). The old --refine_from_vocal flag still works as a legacy alias. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the 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 configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThis PR adds a new feature to split musical notes at pitch-change boundaries (melismas/runs). It introduces a Changes
Sequence DiagramsequenceDiagram
participant Main as Main Processor
participant Config as Settings/Config
participant Splitter as split_notes_at_pitch_changes
participant Analyzer as Pitch Analyzer
participant Segments as MIDI Segments
Main->>Config: Check settings.pitch_change_split & audio enabled
Config-->>Main: True/False
alt pitch_change_split enabled
Main->>Splitter: Call with segments + pitched_data
Splitter->>Analyzer: Extract per-frame pitch for each segment
Analyzer->>Analyzer: Detect pitch-change boundaries
Analyzer-->>Splitter: Split points identified
Splitter->>Segments: Create sub-segments at boundaries
Segments->>Segments: Merge short fragments & identical notes
Splitter-->>Main: Return expanded segment list
else disabled
Main-->>Main: Skip processing, use original segments
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (3)
src/modules/Pitcher/pitch_change_splitter.py (3)
8-8: Unused import:mathThe
mathmodule is imported but not used anywhere in this file.🔧 Proposed fix
-import math🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/modules/Pitcher/pitch_change_splitter.py` at line 8, Remove the unused import of the math module from pitch_change_splitter.py; locate the top-level import statement "import math" and delete it so there are no unused imports remaining in the module (no other code changes required).
177-177: Consider using list spread syntax for clarity (style preference).The static analysis suggests using spread syntax instead of list concatenation.
🔧 Proposed fix
- boundaries = [0] + change_points + [len(times)] + boundaries = [0, *change_points, len(times)]🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/modules/Pitcher/pitch_change_splitter.py` at line 177, Replace the list concatenation that builds boundaries with list spread syntax for clarity: in pitch_change_splitter.py where the variable boundaries is constructed from 0, change_points, and len(times), change the expression to use a single list literal that spreads change_points between 0 and len(times) (i.e., [0, *change_points, len(times)]), ensuring change_points is iterable and preserving the original order and types.
92-96: Consider addingstrict=Truetozip()for defensive programming.While
frequenciesandconfidencesshould always have the same length (both come from slicedPitchedData), addingstrict=Truewould catch potential data inconsistencies earlier.🔧 Proposed fix
- for freq, conf in zip(frequencies, confidences): + for freq, conf in zip(frequencies, confidences, strict=True):🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/modules/Pitcher/pitch_change_splitter.py` around lines 92 - 96, The loop using zip(frequencies, confidences) can silently mask length mismatches; update it to use zip(..., strict=True) so any unequal lengths raise an error early, e.g., replace zip(frequencies, confidences) with zip(frequencies, confidences, strict=True) in the function that builds midi_values (the block calling _freq_to_midi_safe and appending to midi_values) to defensively catch data inconsistencies between frequencies and confidences.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/modules/Pitcher/pitch_change_splitter.py`:
- Line 8: Remove the unused import of the math module from
pitch_change_splitter.py; locate the top-level import statement "import math"
and delete it so there are no unused imports remaining in the module (no other
code changes required).
- Line 177: Replace the list concatenation that builds boundaries with list
spread syntax for clarity: in pitch_change_splitter.py where the variable
boundaries is constructed from 0, change_points, and len(times), change the
expression to use a single list literal that spreads change_points between 0 and
len(times) (i.e., [0, *change_points, len(times)]), ensuring change_points is
iterable and preserving the original order and types.
- Around line 92-96: The loop using zip(frequencies, confidences) can silently
mask length mismatches; update it to use zip(..., strict=True) so any unequal
lengths raise an error early, e.g., replace zip(frequencies, confidences) with
zip(frequencies, confidences, strict=True) in the function that builds
midi_values (the block calling _freq_to_midi_safe and appending to midi_values)
to defensively catch data inconsistencies between frequencies and confidences.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: eebfb31a-f452-4368-afe2-182fe5a3ed84
📒 Files selected for processing (8)
pytest/modules/Pitcher/test_pitch_change_splitter.pysrc/Settings.pysrc/UltraSinger.pysrc/gui/config.pysrc/gui/settings_tab.pysrc/gui/ultrasinger_runner.pysrc/modules/Pitcher/pitch_change_splitter.pysrc/modules/common_print.py
… add strict zip Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add pitch-change split for multi-pitch notes within words When a singer changes pitch within a single word (melismas, runs, ornaments), UltraSinger now splits the note at pitch change boundaries instead of averaging to a single flat note. - New module: pitch_change_splitter.py with split_notes_at_pitch_changes() - Detects sustained pitch changes >2 semitones within note segments - Splits into sub-notes, merges fragments <80ms with nearest neighbor - First sub-note keeps original text, continuations use "~" marker - Low-confidence frames (<0.3) excluded from change detection - CLI: --pitch_change_split (disabled by default) - GUI: Toggle in Experimental Features section - 20 unit tests covering edge cases Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Enable refinement by default, add --disable_refine flag Refinement consistently improves scores across all tested songs. Changed default from opt-in (--refine_from_vocal) to opt-out (--disable_refine). The old --refine_from_vocal flag still works as a legacy alias. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Trigger CodeRabbit review Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Address CodeRabbit nitpicks: remove unused import, use spread syntax, add strict zip Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Anthony <anthony@baseattack.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Summary
--pitch_change_split): New experimental feature that splits notes at pitch change boundaries within word segments. Detects sustained pitch changes (>2 semitones, >80ms) and creates separate notes for each pitch region. First sub-note keeps the text, continuations use~markers. Disabled by default.--refine_from_vocalis now on by default (was opt-in). Added--disable_refineflag for opt-out. This uses the C++ ptAKF pitch detector to find and fix poorly-scoring notes.--youtube_urldocumentation: Detailed help text and README section explaining the browser-based download workflow where audio is pre-downloaded but the YouTube URL is still needed for metadata.Test Results
Tested with live performance song (high vocal variance — good stress test):
Test plan
--disable_refinecorrectly disables refinement--pitch_change_splitflag works from CLI and GUI settingsuv run pytest)Summary by CodeRabbit
Release Notes
New Features
--pitch_change_splitCLI flag or UI settings in Experimental Features.Changes
--disable_refineto disable.