Add freestyle detection for unpitchable vocal passages - #62
Conversation
Detect unpitchable vocal passages (growl, scream, rap, spoken word)
and mark them as freestyle notes (F prefix in UltraStar format).
Tier 1: SwiftF0 confidence + pitch standard deviation (zero extra deps)
- Low median confidence (<0.35) + high pitch stdev (>4 semitones)
- Identifies aperiodic vocals where pitch detection returns garbage
Tier 2: Spectral flatness via librosa (already a dependency)
- Noise-like spectral profile (>0.25) as additional evidence
- Relaxes confidence threshold slightly when both tiers agree
CLI: --detect_growl, --growl_confidence, --growl_pitch_stdev,
--growl_spectral_flatness, --no_growl_spectral
GUI: Toggle in settings tab
Pipeline: runs after octave correction, before refinement pass
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the original SwiftF0-only approach with HPSS (Harmonic-Percussive Source Separation) as the primary detection method. Clean singing has a high harmonic-to-total energy ratio (0.7+), while growls/screams have a low ratio (< 0.40). This is genre- and gender-independent. The original pitch-confidence analysis becomes a fallback when no vocal audio is available. Short segments (< 1.0s) get expanded to a 1.0s context window for reliable HPSS analysis. New CLI args: --growl_harmonicity, --growl_energy Updated: README, help text, GUI description, info file output, tests (19) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove unused `import math` from growl_detector.py - Sort __slots__ alphabetically (RUF023) - Narrow broad `except Exception` to specific types (BLE001) - Add range validation for all growl threshold CLI arguments Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use RMS instead of raw energy sum for energy_threshold so the silence gate is independent of segment length (#4) - Allow HPSS to run even when pitched_data is missing, so vocal audio analysis is not skipped unnecessarily (#2) - Expand docstring to clearly document the two detection paths and mark fallback-only parameters (#3) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The feature detects any unpitchable vocal passage — not just metal growls/screams but also rap, spoken word, and other non-melodic styles. Updated all user-facing text (GUI label, tooltip, CLI help, README) to be genre-neutral and inclusive. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Rename all CLI flags from --detect_growl/--growl_* to --detect_freestyle/--freestyle_* for genre-neutral naming - Update help text, README CLI reference, info file labels - Add _FilteredLogger to suppress harmless yt-dlp SABR streaming experiment warning that confuses users Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The _FilteredLogger was only injected in __start_download() and get_youtube_title(), but not in __download_youtube_video_with_audio() and download_and_convert_thumbnail() which create their own YoutubeDL instances. Add the logger to all yt-dlp entry points. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Update _SUPPRESSED_WARNINGS with actual yt-dlp SABR warning texts (the previous string never matched current yt-dlp builds) - Add catch-all "SABR streaming" substring for future wording changes - Clarify in README and help text that fallback triggers when HPSS is unavailable, not only when no separated vocals exist Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The previous 48 KB JPEG limit produced ~71 KB total Vorbis headers after base64 encoding and Ogg page framing, exceeding Karedi's JOrbis 64,001-byte mark limit. A 32 KB JPEG yields ~50 KB total header size, safely under the limit. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Mutagen preserves old tag space as padding when rewriting comments. For OGG files that previously had large covers, this leftover padding can push the total Vorbis header well over Karedi's 64 KB JOrbis mark limit even after the cover is resized. Force zero padding to keep headers minimal. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When the first note of a syllable is marked as freestyle, propagate the freestyle marking to all continuation notes (~) of the same syllable. A sustained growl does not become pitched mid-syllable — partial harmonicity spikes within a sustained scream are HPSS measurement artefacts that should not break the freestyle chain. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
📝 WalkthroughWalkthroughA new freestyle/growl detection feature marks unpitchable vocal passages as UltraStar freestyle notes (displayed but not scored). It uses HPSS harmonicity when vocal audio is available, falls back to SwiftF0 confidence and pitch-stability metrics (with optional spectral-flatness), and integrates via CLI, GUI, settings, tests, and pipeline wiring. Changes
Sequence DiagramsequenceDiagram
participant UltraSinger as UltraSinger.run()
participant Detector as detect_growl_segments()
participant HPSS as HPSS Analysis
participant SwiftF0 as SwiftF0/PitchedData
participant Midi as MidiSegments
UltraSinger->>Detector: midi_segments, pitched_data, vocal_audio_path, thresholds
Detector->>HPSS: vocal_audio_path available? / attempt HPSS
alt HPSS available
HPSS->>Detector: per-frame harmonic & percussive energy
Detector->>Detector: compute harmonicity ratio, check energy_threshold
Detector->>Detector: classify vs harmonicity_threshold
else HPSS unavailable/failed
SwiftF0->>Detector: median confidence, voiced ratio, pitch stdev
Detector->>Detector: apply confidence & pitch_stdev thresholds
Detector->>Detector: optional spectral_flatness check
end
Detector->>Detector: mark segments (note_type="F") and propagate to continuations
Detector->>Midi: return updated midi_segments
Midi->>UltraSinger: updated segments consumed downstream
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
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 docstrings
🧪 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 |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
src/UltraSinger.py (1)
1689-1689: Minor: Error messages missingULTRASINGER_HEADprefix.The error messages for freestyle threshold validation don't include the
ULTRASINGER_HEADprefix that other error messages in this file use (e.g., lines 1554, 1557, 1574).🔧 Suggested consistency fix
elif opt in ("--freestyle_harmonicity"): val = float(arg) if not 0.0 <= val <= 1.0: - print(f"Error: --freestyle_harmonicity must be between 0.0 and 1.0, got {val}") + print(f"{ULTRASINGER_HEAD} {red_highlighted('Error:')} --freestyle_harmonicity must be between 0.0 and 1.0, got {val}") sys.exit(1) settings.growl_harmonicity_threshold = val elif opt in ("--freestyle_energy"): val = float(arg) if val < 0: - print(f"Error: --freestyle_energy must be non-negative, got {val}") + print(f"{ULTRASINGER_HEAD} {red_highlighted('Error:')} --freestyle_energy must be non-negative, got {val}") sys.exit(1) settings.growl_energy_threshold = val elif opt in ("--freestyle_confidence"): val = float(arg) if not 0.0 <= val <= 1.0: - print(f"Error: --freestyle_confidence must be between 0.0 and 1.0, got {val}") + print(f"{ULTRASINGER_HEAD} {red_highlighted('Error:')} --freestyle_confidence must be between 0.0 and 1.0, got {val}") sys.exit(1) settings.growl_confidence_threshold = val elif opt in ("--freestyle_pitch_stdev"): val = float(arg) if val <= 0: - print(f"Error: --freestyle_pitch_stdev must be positive, got {val}") + print(f"{ULTRASINGER_HEAD} {red_highlighted('Error:')} --freestyle_pitch_stdev must be positive, got {val}") sys.exit(1) settings.growl_pitch_stdev_threshold = val elif opt in ("--freestyle_spectral_flatness"): val = float(arg) if not 0.0 <= val <= 1.0: - print(f"Error: --freestyle_spectral_flatness must be between 0.0 and 1.0, got {val}") + print(f"{ULTRASINGER_HEAD} {red_highlighted('Error:')} --freestyle_spectral_flatness must be between 0.0 and 1.0, got {val}") sys.exit(1) settings.growl_spectral_flatness_threshold = valAlso applies to: 1695-1695, 1701-1701, 1707-1707, 1713-1713
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/UltraSinger.py` at line 1689, The freestyle threshold validation print statements (e.g., the one emitting "Error: --freestyle_harmonicity must be between 0.0 and 1.0, got {val}") should include the ULTRASINGER_HEAD prefix for consistency with other errors; update each affected print call (the occurrences at the shown diff and the ones around 1695, 1701, 1707, 1713) to prepend ULTRASINGER_HEAD (or format it into the f-string) so the error messages match the file's existing logging style.src/modules/Pitcher/growl_detector.py (1)
166-173: Consider usinglogger.info()for consistency.The module uses
loggerfor warnings elsewhere (lines 202, 334), but this status message usesprint(). While theULTRASINGER_HEADprefix suggests this may be intentional for console UI consistency, using the logger would enable filtering/redirection of these messages.♻️ Suggested change (optional)
if growl_count > 0: detail = "" if propagated > 0: detail = f" ({propagated} via syllable propagation)" - print( - f"{ULTRASINGER_HEAD} Freestyle detection: marked {growl_count}/{len(midi_segments)} " - f"segments as freestyle (unpitchable){detail}" - ) + logger.info( + "%s Freestyle detection: marked %d/%d segments as freestyle (unpitchable)%s", + ULTRASINGER_HEAD, growl_count, len(midi_segments), detail + )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/modules/Pitcher/growl_detector.py` around lines 166 - 173, Replace the print() status message with a logger.info call to keep logging consistent: in the block that checks growl_count (uses ULTRASINGER_HEAD, growl_count, midi_segments, and propagated) swap the print(...) for logger.info(...) and preserve the same message text and ULTRASINGER_HEAD prefix (and the propagated detail string) so the message goes through the module logger and can be filtered/redirected like other warnings.
🤖 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/growl_detector.py`:
- Around line 166-173: Replace the print() status message with a logger.info
call to keep logging consistent: in the block that checks growl_count (uses
ULTRASINGER_HEAD, growl_count, midi_segments, and propagated) swap the
print(...) for logger.info(...) and preserve the same message text and
ULTRASINGER_HEAD prefix (and the propagated detail string) so the message goes
through the module logger and can be filtered/redirected like other warnings.
In `@src/UltraSinger.py`:
- Line 1689: The freestyle threshold validation print statements (e.g., the one
emitting "Error: --freestyle_harmonicity must be between 0.0 and 1.0, got
{val}") should include the ULTRASINGER_HEAD prefix for consistency with other
errors; update each affected print call (the occurrences at the shown diff and
the ones around 1695, 1701, 1707, 1713) to prepend ULTRASINGER_HEAD (or format
it into the f-string) so the error messages match the file's existing logging
style.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 9bc51aee-976d-4385-8484-2f9b2b412b0a
📒 Files selected for processing (11)
README.mdpytest/modules/Pitcher/test_growl_detector.pysrc/Settings.pysrc/UltraSinger.pysrc/gui/config.pysrc/gui/settings_tab.pysrc/gui/ultrasinger_runner.pysrc/modules/Audio/metadata_writer.pysrc/modules/Audio/youtube.pysrc/modules/Pitcher/growl_detector.pysrc/modules/common_print.py
- Use ULTRASINGER_HEAD + red_highlighted prefix for freestyle threshold validation errors (consistency with rest of file) - Replace print() with logger.info() in growl_detector summary Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/UltraSinger.py (1)
1686-1715: MissingValueErrorhandling forfloat(arg)conversions.If a user passes a non-numeric string (e.g.,
--freestyle_harmonicity abc),float(arg)will raise an unhandledValueErrorwith a cryptic traceback. The--bpmoption (lines 1551-1558) demonstrates the preferred pattern with a try/except block.However, this matches the existing pattern used by
--vad_onset,--vad_offset,--no_speech_threshold, and--denoise_nr, which also lack this handling. Consider addressing this consistently across all float options in a follow-up refactor.🔧 Example fix for one option (apply pattern to others)
elif opt in ("--freestyle_harmonicity"): + try: + val = float(arg) + except ValueError: + print(f"{ULTRASINGER_HEAD} {red_highlighted('Error:')} --freestyle_harmonicity must be a number, got {arg}") + sys.exit(1) - val = float(arg) if not 0.0 <= val <= 1.0: print(f"{ULTRASINGER_HEAD} {red_highlighted('Error:')} --freestyle_harmonicity must be between 0.0 and 1.0, got {val}") sys.exit(1) settings.growl_harmonicity_threshold = val🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/UltraSinger.py` around lines 1686 - 1715, The float conversions for the freestyle CLI options (handlers for "--freestyle_harmonicity", "--freestyle_energy", "--freestyle_confidence", "--freestyle_pitch_stdev", "--freestyle_spectral_flatness") should be wrapped in try/except to catch ValueError from float(arg); on ValueError print the same ULTRASINGER_HEAD red_highlighted('Error:') message indicating the option requires a numeric value and call sys.exit(1), then proceed with the existing range checks and assignments to settings.growl_harmonicity_threshold, settings.growl_energy_threshold, settings.growl_confidence_threshold, settings.growl_pitch_stdev_threshold, and settings.growl_spectral_flatness_threshold respectively; apply the same pattern used by the --bpm handler for consistent error handling.
🤖 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/UltraSinger.py`:
- Around line 1686-1715: The float conversions for the freestyle CLI options
(handlers for "--freestyle_harmonicity", "--freestyle_energy",
"--freestyle_confidence", "--freestyle_pitch_stdev",
"--freestyle_spectral_flatness") should be wrapped in try/except to catch
ValueError from float(arg); on ValueError print the same ULTRASINGER_HEAD
red_highlighted('Error:') message indicating the option requires a numeric value
and call sys.exit(1), then proceed with the existing range checks and
assignments to settings.growl_harmonicity_threshold,
settings.growl_energy_threshold, settings.growl_confidence_threshold,
settings.growl_pitch_stdev_threshold, and
settings.growl_spectral_flatness_threshold respectively; apply the same pattern
used by the --bpm handler for consistent error handling.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: abfcdbc9-c4e2-46ce-98d6-e6e06c134db8
📒 Files selected for processing (2)
src/UltraSinger.pysrc/modules/Pitcher/growl_detector.py
✅ Files skipped from review due to trivial changes (1)
- src/modules/Pitcher/growl_detector.py
* Add growl/scream detection (Tier 1+2) for freestyle note marking
Detect unpitchable vocal passages (growl, scream, rap, spoken word)
and mark them as freestyle notes (F prefix in UltraStar format).
Tier 1: SwiftF0 confidence + pitch standard deviation (zero extra deps)
- Low median confidence (<0.35) + high pitch stdev (>4 semitones)
- Identifies aperiodic vocals where pitch detection returns garbage
Tier 2: Spectral flatness via librosa (already a dependency)
- Noise-like spectral profile (>0.25) as additional evidence
- Relaxes confidence threshold slightly when both tiers agree
CLI: --detect_growl, --growl_confidence, --growl_pitch_stdev,
--growl_spectral_flatness, --no_growl_spectral
GUI: Toggle in settings tab
Pipeline: runs after octave correction, before refinement pass
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add HPSS harmonicity as primary growl detection method
Replace the original SwiftF0-only approach with HPSS (Harmonic-Percussive
Source Separation) as the primary detection method. Clean singing has a
high harmonic-to-total energy ratio (0.7+), while growls/screams have a
low ratio (< 0.40). This is genre- and gender-independent.
The original pitch-confidence analysis becomes a fallback when no vocal
audio is available. Short segments (< 1.0s) get expanded to a 1.0s
context window for reliable HPSS analysis.
New CLI args: --growl_harmonicity, --growl_energy
Updated: README, help text, GUI description, info file output, tests (19)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix CodeRabbit review findings on PR #58
- Remove unused `import math` from growl_detector.py
- Sort __slots__ alphabetically (RUF023)
- Narrow broad `except Exception` to specific types (BLE001)
- Add range validation for all growl threshold CLI arguments
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix CodeRabbit major findings: RMS energy, early-return guard, docs
- Use RMS instead of raw energy sum for energy_threshold so the
silence gate is independent of segment length (#4)
- Allow HPSS to run even when pitched_data is missing, so vocal
audio analysis is not skipped unnecessarily (#2)
- Expand docstring to clearly document the two detection paths
and mark fallback-only parameters (#3)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Rename Growl/Scream Detection to Freestyle Detection
The feature detects any unpitchable vocal passage — not just metal
growls/screams but also rap, spoken word, and other non-melodic styles.
Updated all user-facing text (GUI label, tooltip, CLI help, README)
to be genre-neutral and inclusive.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Rename CLI flags --growl_* to --freestyle_*, filter SABR yt-dlp warning
- Rename all CLI flags from --detect_growl/--growl_* to
--detect_freestyle/--freestyle_* for genre-neutral naming
- Update help text, README CLI reference, info file labels
- Add _FilteredLogger to suppress harmless yt-dlp SABR streaming
experiment warning that confuses users
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix SABR warning filter not applied to all yt-dlp calls
The _FilteredLogger was only injected in __start_download() and
get_youtube_title(), but not in __download_youtube_video_with_audio()
and download_and_convert_thumbnail() which create their own the video platformDL
instances. Add the logger to all yt-dlp entry points.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix SABR warning filter strings, clarify fallback trigger in docs
- Update _SUPPRESSED_WARNINGS with actual yt-dlp SABR warning texts
(the previous string never matched current yt-dlp builds)
- Add catch-all "SABR streaming" substring for future wording changes
- Clarify in README and help text that fallback triggers when HPSS is
unavailable, not only when no separated vocals exist
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Reduce OGG cover art limit to 32 KB for Karedi compatibility
The previous 48 KB JPEG limit produced ~71 KB total Vorbis headers
after base64 encoding and Ogg page framing, exceeding Karedi's
JOrbis 64,001-byte mark limit. A 32 KB JPEG yields ~50 KB total
header size, safely under the limit.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use zero padding when saving OGG tags for Karedi compat
Mutagen preserves old tag space as padding when rewriting comments.
For OGG files that previously had large covers, this leftover padding
can push the total Vorbis header well over Karedi's 64 KB JOrbis
mark limit even after the cover is resized. Force zero padding to
keep headers minimal.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add syllable propagation to freestyle detection
When the first note of a syllable is marked as freestyle, propagate
the freestyle marking to all continuation notes (~) of the same
syllable. A sustained growl does not become pitched mid-syllable —
partial harmonicity spikes within a sustained scream are HPSS
measurement artefacts that should not break the freestyle chain.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix CodeRabbit nitpicks: consistent error prefix and logger usage
- Use ULTRASINGER_HEAD + red_highlighted prefix for freestyle
threshold validation errors (consistency with rest of file)
- Replace print() with logger.info() in growl_detector summary
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
--detect_freestyle,--freestyle_harmonicity,--freestyle_energy,--freestyle_confidence,--freestyle_pitch_stdev,--freestyle_spectral_flatness,--freestyle_spectral(internal Settings fields unchanged for stability)Test plan
--detect_freestyleand verify freestyle notes appear asFin the UltraStar txtpytest pytest/modules/Pitcher/test_growl_detector.py— all tests passSummary by CodeRabbit
New Features
Config & CLI
Documentation
Improvements
Tests