Skip to content

Add chart_style toggle (singable default) for singable vs score-optimized charts - #128

Merged
MrDix merged 2 commits into
mainfrom
feat/chart-style-singable
Jul 6, 2026
Merged

Add chart_style toggle (singable default) for singable vs score-optimized charts#128
MrDix merged 2 commits into
mainfrom
feat/chart-style-singable

Conversation

@MrDix

@MrDix MrDix commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Problem

Generated charts scored very high on the in-game ptAKF metric (Medium ~92-98%) but were hard to actually sing: far too many short notes, vibrato traced note-for-note, and noise inserting spurious notes. An 8-song experiment against professional reference charts (SingStar) quantified and root-caused this.

Finding (8 reference songs, measured vs the original .txt)

The ptAKF refit is the cause. It's a score-maximiser that charts the vocal's exact per-beat tone, so every vibrato wiggle becomes its own note. Consistent across all 8 songs: default = 2.0-3.2x too many notes, 60-75% micro-notes (<120ms).

Scoring the professional reference charts themselves is the key context:

Chart Easy Medium Hard notes vs ref
Professional reference .txt 86.0% 76.8% 57.3% 1.0x
Default now (refit on) 95.8% 94.1% 87.2% 2.46x
Refit off () 84.8% 77.2% 59.1% 1.03x

The hand-made professional charts only score ~77% Medium against isolated vocals — that is the natural ceiling for a singable chart, because the scorer rewards tracing the vocal exactly. Refit-off matches it almost exactly (77.2 vs 76.8) and reproduces the professional note profile (1.03x note count). The default's 94% reflects over-fitting to the recording, not a better karaoke chart.

Change

Adds a user-facing chart_style setting (default singable):

  • singable: natural, held notes like a professional chart (refit off) - best to sing.
  • score: rebuild onto the game's exact beat tones (refit on) for a higher score number.

Surfaces (kept in sync):

  • CLI: --chart_style singable|score; resolves the internal ptAKF refit. The advanced --ptakf_refit/--disable_ptakf_refit flags still override it. Unknown values fall back to singable with a notice.
  • GUI: a "Chart Style" dropdown in Post-Processing (the old standalone "ptAKF Chart Refit" toggle is replaced by it; its min-note/fill sub-settings remain as advanced tuning, enabled only in score style).
  • Score output: in singable style, the console adds one line explaining the lower number is professional-level, not a defect.
  • README, CLI help, GUI tooltip, and the settings info file all document the trade-off.

Default change

The default output is now the singable chart. This lowers the reported game score for the default run (to professional level) - intended, and explained inline and in the docs. The score style reproduces the previous behaviour exactly.

Tests

6 new resolution tests (default→singable/refit-off, score→refit-on, explicit-flag overrides, unknown fallback) + fixed a singleton-state leak in init_settings. Full suite 857 passed / 4 skipped; GUI suite green; end-to-end verified (default 253 notes vs score 612 on the same song).

Summary by CodeRabbit

  • New Features

    • Added a new Chart Style setting in both the CLI and app settings, with singable as the default and score as an option.
    • The app now shows the selected chart style in its saved settings/info output.
    • Help text and documentation now explain the difference between chart styles and how they affect score results.
  • Bug Fixes

    • Improved chart-style handling so explicit ptAKF refit overrides are respected.
    • Invalid chart-style values now fall back safely to the default.

@MrDix

MrDix commented Jul 6, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

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

Run ID: a3434b1b-a947-4cd0-a0db-d6fac250adb5

📥 Commits

Reviewing files that changed from the base of the PR and between cff6ecb and 89ff39e.

📒 Files selected for processing (3)
  • pytest/test_UltraSinger.py
  • src/Settings.py
  • src/UltraSinger.py
📝 Walkthrough

Walkthrough

This PR introduces a chart_style setting (singable/score) that determines default ptAKF refit behavior, replacing the standalone ptakf_refit boolean as the primary control. CLI parsing, GUI settings, the CLI runner, help text, Settings dataclass, tests, and README are updated to reflect chart-style-driven resolution, with explicit --ptakf_refit/--disable_ptakf_refit overrides retained.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Changes

Chart Style Resolution

Layer / File(s) Summary
Settings schema
src/Settings.py
Adds chart_style (default "singable") and ptakf_refit_explicit fields; updates ptakf_refit documentation to describe derivation from chart_style.
CLI parsing and resolution
src/UltraSinger.py
Resets ptakf_refit_explicit per run, adds --chart_style option, marks explicit overrides on --ptakf_refit/--disable_ptakf_refit, validates and falls back invalid chart_style to singable, resolves ptakf_refit from chart_style unless explicitly overridden, registers chart_style= long option, prints a "Singable chart style" message, and records chart_style in the settings info file.
CLI help text
src/modules/common_print.py
Documents --chart_style and clarifies that --ptakf_refit/--disable_ptakf_refit override it.
GUI settings and defaults
src/gui/config.py, src/gui/settings_tab.py
Replaces the ptakf_refit default/toggle with a chart_style dropdown/default; drives ptAKF sub-setting enablement from chart_style == "score"; updates collect_config() to emit chart_style instead of ptakf_refit.
GUI CLI arg construction
src/gui/ultrasinger_runner.py
build_args always appends --chart_style and gates refit sub-setting flags on chart_style == "score" instead of the removed ptakf_refit flag.
Tests and documentation
pytest/test_UltraSinger.py, README.md
Adds TestChartStyleResolution covering default, override, and fallback scenarios; documents the --chart_style option and its behavioral trade-offs in README.

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant init_settings
  participant Settings

  CLI->>init_settings: pass --chart_style / --ptakf_refit / --disable_ptakf_refit
  init_settings->>Settings: reset ptakf_refit_explicit
  init_settings->>Settings: set chart_style
  init_settings->>Settings: mark ptakf_refit_explicit if override flag present
  init_settings->>init_settings: validate chart_style (default to singable if invalid)
  init_settings->>Settings: set ptakf_refit = (chart_style == score) unless explicit override
Loading

Possibly related PRs

  • MrDix/UltraSinger#75: Introduces the original --ptakf_refit flag and refit module that this PR's chart_style logic now resolves.
  • MrDix/UltraSinger#78: Adds ptAKF fill options gated by ptakf_refit enablement, which is now determined by chart_style in this PR.
  • MrDix/UltraSinger#81: Establishes the --disable_ptakf_refit control path in UltraSinger.py/Settings.py/common_print.py that this PR extends with chart_style resolution.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a chart_style toggle with singable as the default.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/chart-style-singable

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.

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/gui/config.py (1)

119-136: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Migrate legacy ptakf_refit into chart_style in src/gui/config.py::load_config(). Old GUI configs can still carry ptakf_refit, but this path never maps it to chart_style, so they fall back to "singable" and change chart generation on upgrade.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/gui/config.py` around lines 119 - 136, Legacy GUI configs still expose
ptakf_refit, but load_config() does not translate it into chart_style, so
upgraded configs silently keep the default singable behavior. Update
src/gui/config.py in load_config() to read the legacy ptakf_refit setting and
map it to the new chart_style value during config migration, preserving the
intended refit behavior for existing users while keeping the current
chart_style-based config structure.
README.md (1)

756-764: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Stale "Enabled by default" claim now contradicts the new chart_style default.

This section still states --ptakf_refit is "Enabled by default (disable with --disable_ptakf_refit)" (Line 760), but per this PR ptakf_refit is now only on by default when --chart_style score is used, and singable (the new default) leaves it off. This directly conflicts with the new "Chart style" section added right below it (Lines 804-812), which correctly explains the new default is singable (refit off).

📝 Proposed fix
-This maximizes the score an exact-match singer (or the extracted vocal track itself) can achieve. Benchmark over 10 songs: Medium score 72.8% → 90.0%, Easy 81.1% → 94.2%. The trade-off is a higher note count (roughly +40%, many short `~` notes). Enabled by default (disable with `--disable_ptakf_refit`).
+This maximizes the score an exact-match singer (or the extracted vocal track itself) can achieve. Benchmark over 10 songs: Medium score 72.8% → 90.0%, Easy 81.1% → 94.2%. The trade-off is a higher note count (roughly +40%, many short `~` notes). Enabled automatically when `--chart_style score` is used (see below), or force it on directly with `--ptakf_refit`.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 756 - 764, Update the README section for ptAKF Chart
Refit so the default behavior matches the new chart_style rules. In the ptAKF
Chart Refit paragraph, remove or qualify the blanket “Enabled by default” claim
and refer readers to the chart_style default instead; make it clear that ptAKF
refit is on by default only for the score style and off for singable. Use the
nearby chart_style documentation and the --ptakf_refit / --disable_ptakf_refit
wording to keep the description consistent.
🧹 Nitpick comments (2)
pytest/test_UltraSinger.py (1)

84-114: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Good coverage, but missing a regression test for the chart_style singleton-leak scenario.

Consider adding a test that calls init_settings with --chart_style score first, then calls it again without --chart_style and asserts settings.chart_style == "singable" and settings.ptakf_refit is False. This would catch the leak flagged in src/UltraSinger.py (missing settings.chart_style reset alongside settings.ptakf_refit_explicit).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pytest/test_UltraSinger.py` around lines 84 - 114, The test suite is missing
a regression case for the Settings singleton leak in init_settings. Add a test
in TestChartStyleResolution that first initializes with --chart_style score and
then calls init_settings again without chart_style, asserting the second
Settings instance resets chart_style to singable and ptakf_refit to False. Use
init_settings and Settings to verify the singleton state is cleared properly,
including the missing chart_style reset alongside ptakf_refit_explicit.
src/Settings.py (1)

62-66: 📐 Maintainability & Code Quality | 🔵 Trivial

ptakf_refit class default (True) contradicts the new chart_style="singable" default.

chart_style defaults to "singable", which per the resolution logic in init_settings should yield ptakf_refit=False. But the raw class attribute here still defaults to True. Any code path that reads settings.ptakf_refit (or instantiates a fresh Settings()) before init_settings() resolves it will see the old, now-incorrect default. Consider aligning the literal default with the new intended behavior to avoid confusion for future consumers.

♻️ Proposed fix
-    ptakf_refit = True  # Resolved from chart_style unless --ptakf_refit/--disable_ptakf_refit is passed
+    ptakf_refit = False  # Resolved from chart_style unless --ptakf_refit/--disable_ptakf_refit is passed
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Settings.py` around lines 62 - 66, Align the default for ptakf_refit in
Settings with the new chart_style="singable" behavior so a fresh Settings()
instance does not expose the old value. Update the class-level default alongside
chart_style and keep init_settings as the source of any explicit override
resolution; check the Settings attributes ptakf_refit and ptakf_refit_explicit
when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/UltraSinger.py`:
- Around line 798-806: The “Singable chart style” message is gated only by
settings.ptakf_refit, so it can print for score-style runs when refit was
explicitly disabled. Update the conditional around the print in UltraSinger.py
to check the actual chart_style selection as well, using the chart_style logic
that distinguishes "singable" from "score", so the note only appears when the
run is truly in singable mode. Keep the fix local to the block near the existing
settings.ptakf_refit check and preserve the current message text for genuine
singable runs.
- Around line 1936-1937: init_settings() is reusing the settings singleton, and
chart_style is not being reset with the other per-run state, which can leak a
previous run’s value into the next one and affect the ptakf_refit default.
Update the reset block in init_settings() near ptakf_refit_explicit to also
restore settings.chart_style to "singable" so each run starts from the same
baseline.

---

Outside diff comments:
In `@README.md`:
- Around line 756-764: Update the README section for ptAKF Chart Refit so the
default behavior matches the new chart_style rules. In the ptAKF Chart Refit
paragraph, remove or qualify the blanket “Enabled by default” claim and refer
readers to the chart_style default instead; make it clear that ptAKF refit is on
by default only for the score style and off for singable. Use the nearby
chart_style documentation and the --ptakf_refit / --disable_ptakf_refit wording
to keep the description consistent.

In `@src/gui/config.py`:
- Around line 119-136: Legacy GUI configs still expose ptakf_refit, but
load_config() does not translate it into chart_style, so upgraded configs
silently keep the default singable behavior. Update src/gui/config.py in
load_config() to read the legacy ptakf_refit setting and map it to the new
chart_style value during config migration, preserving the intended refit
behavior for existing users while keeping the current chart_style-based config
structure.

---

Nitpick comments:
In `@pytest/test_UltraSinger.py`:
- Around line 84-114: The test suite is missing a regression case for the
Settings singleton leak in init_settings. Add a test in TestChartStyleResolution
that first initializes with --chart_style score and then calls init_settings
again without chart_style, asserting the second Settings instance resets
chart_style to singable and ptakf_refit to False. Use init_settings and Settings
to verify the singleton state is cleared properly, including the missing
chart_style reset alongside ptakf_refit_explicit.

In `@src/Settings.py`:
- Around line 62-66: Align the default for ptakf_refit in Settings with the new
chart_style="singable" behavior so a fresh Settings() instance does not expose
the old value. Update the class-level default alongside chart_style and keep
init_settings as the source of any explicit override resolution; check the
Settings attributes ptakf_refit and ptakf_refit_explicit when making the change.
🪄 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: 389a1b72-c8c2-416e-a965-e95823145b06

📥 Commits

Reviewing files that changed from the base of the PR and between 0ed103d and cff6ecb.

📒 Files selected for processing (8)
  • README.md
  • pytest/test_UltraSinger.py
  • src/Settings.py
  • src/UltraSinger.py
  • src/gui/config.py
  • src/gui/settings_tab.py
  • src/gui/ultrasinger_runner.py
  • src/modules/common_print.py

Comment thread src/UltraSinger.py Outdated
Comment thread src/UltraSinger.py
@MrDix

MrDix commented Jul 6, 2026

Copy link
Copy Markdown
Owner Author

All three addressed:

  1. Singable note under score style: now gated on chart_style == "singable" and not settings.ptakf_refit, so --chart_style score --disable_ptakf_refit no longer mislabels the lower score as "singable style".
  2. Singleton leak (good catch — second half was still leaking): init_settings now resets settings.chart_style = "singable" too (it only reset ptakf_refit_explicit before), so a prior --chart_style score run cannot leak into a later default run. Added test_no_leak_across_calls.
  3. Class default alignment: Settings.ptakf_refit default is now False, matching the chart_style="singable" default, so any consumer reading it before resolution sees the correct value. Added test_class_default_ptakf_refit_matches_singable.

@MrDix

MrDix commented Jul 6, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

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 commented Jul 6, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

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 fa97aec into main Jul 6, 2026
2 checks passed
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