Skip to content

Configurable volume floor and visualizer volume linking#237

Open
arro000 wants to merge 3 commits into
bjarneo:mainfrom
arro000:main
Open

Configurable volume floor and visualizer volume linking#237
arro000 wants to merge 3 commits into
bjarneo:mainfrom
arro000:main

Conversation

@arro000
Copy link
Copy Markdown

@arro000 arro000 commented May 20, 2026

Summary

Extends the minimum volume floor from -30 dB to -50 dB and makes it configurable via volume_min in config.toml. Also adds vis_volume_linked to control whether visualizer bar height follows the current volume level.

  • volume_min (default -50, range [-90, 0]): sets the dB floor for the volume control. The bar formula in the UI scales accordingly.
  • vis_volume_linked (default true): when true, visualizer bars reflect the current volume (classic behavior). Set to false to decouple the
    visualizer from volume — useful when listening at very low levels.

Internally, the audio tap is now placed before the volume streamer in the pipeline so the visualizer always captures pre-volume samples. When vis_volume_linked = true, the volume gain is re-applied to the samples in the FFT analysis path instead.

How to test

  1. Set volume_min = -50 (or omit — it's the default) and verify you can lower volume below -30 dB.
  2. Set volume_min = -70 in config.toml, restart, and confirm the floor is now -70 dB.
  3. Lower volume to -50 dB and confirm the volume bar renders correctly (no negative fraction).
  4. With vis_volume_linked = true (default), confirm visualizer bars shrink as volume decreases.
  5. Set vis_volume_linked = false, lower volume to -50 dB, and confirm bars remain at full height.
  6. Run make check.

Checklist

  • make check passes
  • docs/ and site/index.html updated for user-facing changes

Summary by CodeRabbit

  • New Features

    • Configurable minimum volume floor (volume_min) — playback volume now respects this user-set floor (default: -50 dB)
    • Visualizer volume linking (vis_volume_linked) — option to scale visualizer by current volume (default: enabled)
    • Volume UI now reflects the configured min/max when rendering the volume bar
  • Documentation

    • Updated configuration and CLI docs to describe volume_min and vis_volume_linked options

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 69763a64-6287-4ba5-9992-8151675ac28c

📥 Commits

Reviewing files that changed from the base of the PR and between d480f53 and 751c154.

📒 Files selected for processing (4)
  • config/config.go
  • config/config_test.go
  • player/player.go
  • player/player_test.go

📝 Walkthrough

Walkthrough

This PR adds a configurable minimum volume floor (VolumeMin) and a visualizer-link toggle (VisVolumeLinked); config parsing, defaults, and clamping are updated; Player and Engine APIs/implementation expose and enforce the floor; visualizer sample scaling and UI volume normalization use the new settings; tests and docs updated.

Changes

Configurable Volume Minimum and Visualizer Linking

Layer / File(s) Summary
Configuration schema, loading, and validation
config/config.go, config/config_test.go, docs/configuration.md, site/index.html
Config adds VolumeMin and VisVolumeLinked; defaultConfig() sets VolumeMin: -50 and VisVolumeLinked: true; Load() parses volume_min and vis_volume_linked; Config.clamp() constrains VolumeMin to [-90,0] and clamps Volume to [VolumeMin, 6]; tests added/updated to validate clamping and propagation; docs and CLI help updated.
Player engine interface and implementation
player/engine.go, player/player.go, player/player_test.go, player/tap.go
Engine adds SetVolumeMin(db float64) and VolumeMin() float64; Player gains atomic volMin initialized to -50; implements SetVolumeMin (clamping and reconciling current volume) and VolumeMin; SetVolume now clamps to [VolumeMin(), +6]; playback pipeline tap/volume wiring adjusted and tap comment clarified; player tests updated/extended.
Visualizer linking, sample scaling, and UI
ui/model/model.go, ui/model/init.go, ui/model/tick.go, ui/model/view.go, ui/model/playback_test.go, ui/model/stream_seek_keys_test.go, main.go
Model gains visVolumeLinked and SetVisVolumeLinked; main.go wires cfg.VisVolumeLinked into the model; tick.go conditionally scales FFT input samples by gain = 10^(volume/20) when linking is enabled; renderControls normalizes the volume bar using (vol - volMin) / (6 - volMin); test doubles updated to stub SetVolumeMin and VolumeMin.

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the two main changes: making the volume floor configurable and adding visualizer volume linking control.
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.

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


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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
config/config_test.go (1)

613-638: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Assert SetVolumeMin behavior in TestApplyPlayer.

The mock captures volumeMin, but the test never verifies it, so regressions in Config.ApplyPlayer can pass unnoticed.

✅ Suggested assertion
 	if p.volume != -10 {
 		t.Errorf("volume = %f, want -10", p.volume)
 	}
+	if p.volumeMin != cfg.VolumeMin {
+		t.Errorf("volumeMin = %f, want %f", p.volumeMin, cfg.VolumeMin)
+	}
 	if p.speed != 1.5 {
 		t.Errorf("speed = %f, want 1.5", p.speed)
 	}
🤖 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 `@config/config_test.go` around lines 613 - 638, TestApplyPlayer doesn't assert
the mockPlayer's captured volumeMin value, so regressions in
Config.ApplyPlayer's handling of SetVolumeMin can go unnoticed; update
TestApplyPlayer to verify the mockPlayer.volumeMin (the value set via
SetVolumeMin) matches the expected minimum for the configured Volume (or the
configured minimum logic), by adding an assertion after cfg.ApplyPlayer(p) that
checks p.volumeMin equals the expected value, referencing TestApplyPlayer,
mockPlayer, SetVolumeMin and Config.ApplyPlayer to locate the relevant code to
modify.
🤖 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 `@config/config_test.go`:
- Around line 103-111: Replace the single-case TestVolumeClampedToVolumeMin with
a table-driven test: create a slice of test cases (fields like name, initial
Volume, VolumeMin, expected after clamp), loop over them with t.Run(case.name,
func(t *testing.T) { cfg := defaultConfig(); cfg.VolumeMin = tc.VolumeMin;
cfg.Volume = tc.Volume; cfg.clamp(); if cfg.Volume != tc.expected {
t.Errorf(...) } }), and include at least the original scenario (Volume -40
clamped to -30) plus any additional edge cases you want; keep the test function
name TestVolumeClampedToVolumeMin and reference cfg.clamp() and
cfg.Volume/VolumeMin in the cases so the refactor is purely structural.

In `@config/config.go`:
- Around line 455-456: The case for "vis_volume_linked" currently coerces any
non-"true" token to false; change it to only update cfg.VisVolumeLinked when the
value is a valid boolean. In the "vis_volume_linked" branch, attempt to parse
val with strconv.ParseBool (or explicitly check for "true"/"false"), and only
assign to cfg.VisVolumeLinked when parsing succeeds; otherwise leave
cfg.VisVolumeLinked unchanged so the default is preserved.

In `@player/player_test.go`:
- Around line 62-69: Convert TestVolumeClampedToCustomMin into a table-driven
test: create a slice of test cases (struct with name, min, set, want) and
iterate with t.Run for each case; inside the loop call newTestPlayer(),
p.SetVolumeMin(tc.min), p.SetVolume(tc.set) and assert p.Volume() == tc.want.
Keep the original behavior for the existing values (min -30, set -40, want -30)
and reference the same functions/methods (TestVolumeClampedToCustomMin,
newTestPlayer, SetVolumeMin, SetVolume, Volume) so the test style matches the
rest of the suite.

In `@player/player.go`:
- Around line 569-571: The SetVolumeMin implementation must atomically clamp the
input (keep using max(min(db, 0), -90) and math.Float64bits) AND reconcile the
current volume so Volume() isn't left below the new floor: store the new min
into p.volMin, then load the current volume value from p.vol (use
math.Float64frombits on p.vol.Load()), and if the current volume is less than
the new min, perform an atomic compare-and-swap loop on p.vol to replace the old
bits with math.Float64bits(newMin) (retrying if CAS fails) so the current volume
is raised to the new floor immediately; reference SetVolumeMin, p.volMin, p.vol,
Volume(), and use math.Float64bits/Float64frombits and the atomic CAS on p.vol.

---

Outside diff comments:
In `@config/config_test.go`:
- Around line 613-638: TestApplyPlayer doesn't assert the mockPlayer's captured
volumeMin value, so regressions in Config.ApplyPlayer's handling of SetVolumeMin
can go unnoticed; update TestApplyPlayer to verify the mockPlayer.volumeMin (the
value set via SetVolumeMin) matches the expected minimum for the configured
Volume (or the configured minimum logic), by adding an assertion after
cfg.ApplyPlayer(p) that checks p.volumeMin equals the expected value,
referencing TestApplyPlayer, mockPlayer, SetVolumeMin and Config.ApplyPlayer to
locate the relevant code to modify.
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0968409b-f51e-408e-8b8e-e96c64d20ccb

📥 Commits

Reviewing files that changed from the base of the PR and between 94ba80c and d480f53.

📒 Files selected for processing (15)
  • config/config.go
  • config/config_test.go
  • docs/configuration.md
  • main.go
  • player/engine.go
  • player/player.go
  • player/player_test.go
  • player/tap.go
  • site/index.html
  • ui/model/init.go
  • ui/model/model.go
  • ui/model/playback_test.go
  • ui/model/stream_seek_keys_test.go
  • ui/model/tick.go
  • ui/model/view.go

Comment thread config/config_test.go
Comment thread config/config.go Outdated
Comment thread player/player_test.go Outdated
Comment thread player/player.go
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.

2 participants