Skip to content

Add freestyle detection for unpitchable vocal passages - #62

Merged
MrDix merged 13 commits into
mainfrom
feat/growl-detection
Mar 27, 2026
Merged

Add freestyle detection for unpitchable vocal passages#62
MrDix merged 13 commits into
mainfrom
feat/growl-detection

Conversation

@MrDix

@MrDix MrDix commented Mar 27, 2026

Copy link
Copy Markdown
Owner

Summary

  • Freestyle detection: HPSS-based harmonicity analysis marks unpitchable vocal passages (growls, screams, rap, spoken word) as UltraStar freestyle notes — displayed but not scored
  • CLI flags: --detect_freestyle, --freestyle_harmonicity, --freestyle_energy, --freestyle_confidence, --freestyle_pitch_stdev, --freestyle_spectral_flatness, --freestyle_spectral (internal Settings fields unchanged for stability)
  • Detection tiers: Primary via harmonic-to-percussive energy ratio (HPSS), fallback via SwiftF0 confidence + pitch stability + optional spectral flatness
  • Syllable propagation: Freestyle marking propagates across tilde-connected syllables within the same word
  • SABR warning filter: Suppresses harmless yt-dlp SABR streaming experiment warnings across all YouTube download entry points
  • OGG cover art fix: Resizes embedded covers to ≤32 KB JPEG with zero mutagen padding, keeping Vorbis headers under Karedi's JOrbis 64 KB mark limit

Test plan

  • Convert a song with harsh/growled vocals using --detect_freestyle and verify freestyle notes appear as F in the UltraStar txt
  • Convert a clean vocal song and verify no false freestyle detections
  • Verify SABR yt-dlp warning no longer appears in console output
  • Open output in Karedi and confirm all OGG files (mix, vocals, instrumental) load successfully
  • Run pytest pytest/modules/Pitcher/test_growl_detector.py — all tests pass

Summary by CodeRabbit

  • New Features

    • Freestyle detection to mark unpitchable vocal passages as freestyle notes (displayed but not scored), with primary spectral analysis and pitch-based fallback, syllable propagation, and a GUI toggle.
  • Config & CLI

    • New CLI flags (including --detect_freestyle and --no_freestyle_spectral) and persistent settings with tunable thresholds; settings are reported in outputs when enabled.
  • Documentation

    • README section with usage examples and threshold-tuning commands.
  • Improvements

    • Reduced audio tag padding and smaller cover embedding; quieter yt-dlp logging.
  • Tests

    • Added comprehensive unit tests for freestyle detection.

Anthony and others added 12 commits March 27, 2026 09:32
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>
@MrDix

MrDix commented Mar 27, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Mar 27, 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 commented Mar 27, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

A 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

Cohort / File(s) Summary
Freestyle Detection Core
src/modules/Pitcher/growl_detector.py
New module implementing detect_growl_segments() with HPSS harmonicity primary path, SwiftF0-based fallback (confidence + pitch stdev), optional spectral-flatness gating, context expansion, syllable/word propagation, and error-resilient handling.
CLI & Pipeline Integration
src/UltraSinger.py, src/modules/common_print.py, src/gui/ultrasinger_runner.py
Added --detect_freestyle and threshold flags (--freestyle_harmonicity, --freestyle_energy, --freestyle_confidence, --freestyle_pitch_stdev, --freestyle_spectral_flatness, --no_freestyle_spectral); pipeline calls detector after syllable merge; help text and settings output extended.
Settings & GUI
src/Settings.py, src/gui/config.py, src/gui/settings_tab.py
Added detect_growl boolean, numeric thresholds (growl_harmonicity_threshold, growl_energy_threshold, growl_confidence_threshold, growl_pitch_stdev_threshold, growl_spectral_flatness_threshold) and growl_use_spectral flag; GUI toggle and config defaults wired and persisted.
Testing
pytest/modules/Pitcher/test_growl_detector.py
New comprehensive unit tests for growl detection covering HPSS harmonicity, SwiftF0 fallback, thresholds, propagation, edge cases, deterministic synthetic data, and API behavior.
Audio & Metadata
src/modules/Audio/metadata_writer.py, src/modules/Audio/youtube.py
Changed OGG save to audio.save(padding=lambda x: 0) (zero padding) and reduced cover max bytes from 48 KB to 32 KB; added a filtered logger to suppress benign yt-dlp warnings and standardize logging.
Documentation
README.md
Added "Freestyle Detection (--detect_freestyle)" section with usage examples, primary HPSS approach, SwiftF0 fallback, tunable thresholds, and example CLI tuning commands.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • PR #58: Implements the same freestyle/growl detection feature (overlapping module, settings, CLI/GUI, and tests) — directly related.
  • PR #57: Adds propagation/handling of note_type = "F" in merge/split/output logic — touches the same metadata and propagation behavior.
  • PR #14: Modifies UltraSinger.run() post-processing insertion points — relates to where this detector is invoked in the pipeline.

Poem

🐰 I sniffed the spectrogram at night,
Found growls that shone but missed the pitch light.
HPSS hummed, SwiftF0 chimed in,
I flagged the wild notes with a floppy-ear grin.
Freestyle now hops across the song — what a sight!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main feature added: a freestyle detection system for identifying unpitchable vocal passages, which is the central theme across all changed files (Settings, CLI, detector implementation, tests, and UI).
Docstring Coverage ✅ Passed Docstring coverage is 82.98% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/growl-detection

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.

@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.

🧹 Nitpick comments (2)
src/UltraSinger.py (1)

1689-1689: Minor: Error messages missing ULTRASINGER_HEAD prefix.

The error messages for freestyle threshold validation don't include the ULTRASINGER_HEAD prefix 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 = val

Also 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 using logger.info() for consistency.

The module uses logger for warnings elsewhere (lines 202, 334), but this status message uses print(). While the ULTRASINGER_HEAD prefix 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

📥 Commits

Reviewing files that changed from the base of the PR and between d0f6072 and 9547289.

📒 Files selected for processing (11)
  • README.md
  • pytest/modules/Pitcher/test_growl_detector.py
  • src/Settings.py
  • src/UltraSinger.py
  • src/gui/config.py
  • src/gui/settings_tab.py
  • src/gui/ultrasinger_runner.py
  • src/modules/Audio/metadata_writer.py
  • src/modules/Audio/youtube.py
  • src/modules/Pitcher/growl_detector.py
  • src/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>
@MrDix

MrDix commented Mar 27, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Mar 27, 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.

🧹 Nitpick comments (1)
src/UltraSinger.py (1)

1686-1715: Missing ValueError handling for float(arg) conversions.

If a user passes a non-numeric string (e.g., --freestyle_harmonicity abc), float(arg) will raise an unhandled ValueError with a cryptic traceback. The --bpm option (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

📥 Commits

Reviewing files that changed from the base of the PR and between 9547289 and ebf22cd.

📒 Files selected for processing (2)
  • src/UltraSinger.py
  • src/modules/Pitcher/growl_detector.py
✅ Files skipped from review due to trivial changes (1)
  • src/modules/Pitcher/growl_detector.py

@MrDix
MrDix merged commit 3c826b0 into main Mar 27, 2026
1 check passed
MrDix added a commit that referenced this pull request Jul 4, 2026
* 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>
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