Support low-power mode in config (#250)
* Suspend ALSA speaker on stop/pause to save ~2% CPU * Reduce renderer wakeups in low-power mode * Support low-power mode in config
This commit is contained in:
committed by
GitHub
parent
139b2c02c2
commit
c9c6faf655
+1
-1
@@ -43,7 +43,7 @@ func buildApp() *cli.Command {
|
||||
&cli.StringFlag{Name: "audio-device", Usage: "audio output device (use 'list' to show)"},
|
||||
&cli.StringFlag{Name: "playlist", Usage: "load a local TOML playlist by name and start playing"},
|
||||
&cli.StringFlag{Name: "log-level", Usage: "log level: debug, info, warn, error"},
|
||||
&cli.BoolFlag{Name: "low-power", Usage: "low-power mode: disable visualizer to minimize CPU"},
|
||||
&cli.BoolFlag{Name: "low-power", Usage: "low-power mode: reduce CPU by lowering UI cadence and disabling visualization"},
|
||||
&cli.BoolFlag{Name: "daemon", Aliases: []string{"d"}, Usage: "run headless (no TUI), serving IPC for scripts/Waybar"},
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,9 @@ eq = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
# Visualizer mode: Bars, BarsDot, Rain, BarsOutline, Bricks, Columns, ClassicPeak, Wave, Scatter, Flame, Retro, Pulse, Matrix, Binary, Sakura, Firework, Bubbles, Logo, Terrain, Scope, Heartbeat, Butterfly, Ascii, Firefly, Mosaic, Sand, or None
|
||||
# visualizer = "Bars"
|
||||
|
||||
# Reduce CPU usage by lowering UI cadence and disabling visualization.
|
||||
# low_power = false
|
||||
|
||||
# Log level: "debug", "info", "warn", or "error" (default "info")
|
||||
# Logs are written to ~/.config/cliamp/cliamp.log
|
||||
# log_level = "info"
|
||||
|
||||
+6
-1
@@ -252,7 +252,7 @@ type Config struct {
|
||||
NetEase NetEaseConfig // NetEase Cloud Music provider (opt-in via enabled = true)
|
||||
Plugins map[string]map[string]string // per-plugin config from [plugins.*] sections
|
||||
LogLevel string // log level: debug, info, warn, error (default "info")
|
||||
LowPower bool // runtime-only: set by --low-power, not loaded from config
|
||||
LowPower bool // reduce CPU by lowering UI cadence and disabling visualization
|
||||
}
|
||||
|
||||
// defaultConfig returns a Config with sensible defaults.
|
||||
@@ -523,6 +523,8 @@ func Load() (Config, error) {
|
||||
case "debug", "info", "warn", "warning", "error":
|
||||
cfg.LogLevel = lvl
|
||||
}
|
||||
case "low_power":
|
||||
cfg.LowPower = strings.ToLower(val) == "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -733,6 +735,9 @@ func (c *Config) clamp() {
|
||||
c.Spotify.Bitrate = clampSpotifyBitrate(c.Spotify.Bitrate)
|
||||
c.PaddingH = max(min(c.PaddingH, 10), 0)
|
||||
c.PaddingV = max(min(c.PaddingV, 5), 0)
|
||||
if c.LowPower {
|
||||
c.Visualizer = "none"
|
||||
}
|
||||
}
|
||||
|
||||
// nearestAllowed returns the value in allowed closest to v.
|
||||
|
||||
@@ -69,3 +69,27 @@ func TestSeekStepLargeDuration(t *testing.T) {
|
||||
t.Fatalf("SeekStepLargeDuration = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadLowPower(t *testing.T) {
|
||||
t.Setenv("HOME", t.TempDir())
|
||||
|
||||
path := filepath.Join(os.Getenv("HOME"), ".config", "cliamp", "config.toml")
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
t.Fatalf("MkdirAll: %v", err)
|
||||
}
|
||||
data := []byte("visualizer = \"Bars\"\nlow_power = true\n")
|
||||
if err := os.WriteFile(path, data, 0o644); err != nil {
|
||||
t.Fatalf("WriteFile: %v", err)
|
||||
}
|
||||
|
||||
cfg, err := Load()
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if !cfg.LowPower {
|
||||
t.Fatal("LowPower = false, want true")
|
||||
}
|
||||
if cfg.Visualizer != "none" {
|
||||
t.Fatalf("Visualizer = %q, want none", cfg.Visualizer)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -610,6 +610,21 @@ func TestOverridesApplyClamps(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestOverridesApplyLowPower(t *testing.T) {
|
||||
cfg := defaultConfig()
|
||||
cfg.Visualizer = "Bars"
|
||||
lowPower := true
|
||||
|
||||
Overrides{LowPower: &lowPower}.Apply(&cfg)
|
||||
|
||||
if !cfg.LowPower {
|
||||
t.Fatal("LowPower = false, want true")
|
||||
}
|
||||
if cfg.Visualizer != "none" {
|
||||
t.Fatalf("Visualizer = %q, want none", cfg.Visualizer)
|
||||
}
|
||||
}
|
||||
|
||||
// Mock player for ApplyPlayer tests
|
||||
type mockPlayer struct {
|
||||
volumeMin float64
|
||||
|
||||
@@ -77,9 +77,6 @@ func (o Overrides) Apply(cfg *Config) {
|
||||
}
|
||||
if o.LowPower != nil && *o.LowPower {
|
||||
cfg.LowPower = true
|
||||
// Low-power mode disables the visualizer entirely. With Mode = None,
|
||||
// the model also uses lower UI cadences to reduce idle wakeups.
|
||||
cfg.Visualizer = "none"
|
||||
}
|
||||
cfg.clamp()
|
||||
}
|
||||
|
||||
+9
-3
@@ -41,10 +41,16 @@ Logs are written to `~/.config/cliamp/cliamp.log`. Levels: `debug`, `info` (defa
|
||||
## Low-power mode
|
||||
|
||||
```sh
|
||||
cliamp --low-power track.mp3 # minimize CPU: visualizer off
|
||||
cliamp --low-power track.mp3 # reduce CPU load for this session
|
||||
```
|
||||
|
||||
Forces the visualizer to `none` so the TUI ticks at 5 FPS for the time/seek display only. Useful on battery, slow terminals, or SSH sessions. Press `v` in the player to cycle visualizers back on at any time.
|
||||
Reduces CPU load by lowering UI/render cadence and forcing the visualizer to `none`. Useful on battery, slow terminals, or SSH sessions. Press `v` in the player to cycle visualizers back on at any time.
|
||||
|
||||
To make this persistent, set it in `~/.config/cliamp/config.toml`:
|
||||
|
||||
```toml
|
||||
low_power = true
|
||||
```
|
||||
|
||||
## Headless daemon mode
|
||||
|
||||
@@ -102,7 +108,7 @@ cliamp track.mp3 --repeat all --mono ~/Music
|
||||
| `--bit-depth` | int | 16 | 16, 32 |
|
||||
| `--playlist` | string | | local TOML playlist name |
|
||||
| `--log-level` | string | info | debug, info, warn, error |
|
||||
| `--low-power` | bool | false | disables visualizer; cuts redraw + FFT cost |
|
||||
| `--low-power` | bool | false | lowers UI cadence; disables visualization |
|
||||
| `--daemon` / `-d` | bool | false | run headless; IPC only, no TUI |
|
||||
|
||||
CLI flags override config file values for the current session only. They are not persisted.
|
||||
|
||||
@@ -60,6 +60,10 @@ visualizer = "Bars"
|
||||
# even at very low volume levels.
|
||||
vis_volume_linked = true
|
||||
|
||||
# Reduce CPU usage by lowering UI cadence and disabling visualization.
|
||||
# This has the same effect as starting with --low-power.
|
||||
low_power = false
|
||||
|
||||
# Compact mode: cap UI width at 80 columns (default: fluid/full-width)
|
||||
compact = false
|
||||
|
||||
|
||||
+1
-1
@@ -2090,7 +2090,7 @@ user_id = "your-account-user-id"</code></pre>
|
||||
<div class="key-row"><kbd>--resample-quality <n></kbd><span>1–4</span></div>
|
||||
<div class="key-row"><kbd>--bit-depth <n></kbd><span>PCM: 16 or 32</span></div>
|
||||
<div class="key-row"><kbd>--compact</kbd><span>Cap width at 80 cols</span></div>
|
||||
<div class="key-row"><kbd>--low-power</kbd><span>Disable visualizer to save CPU</span></div>
|
||||
<div class="key-row"><kbd>--low-power</kbd><span>Lower UI cadence and disable visualization</span></div>
|
||||
<div class="key-row"><kbd>--daemon / -d</kbd><span>Run headless (IPC only, no TUI)</span></div>
|
||||
<div class="key-row"><kbd>--playlist <name></kbd><span>Load TOML playlist</span></div>
|
||||
<div class="key-row"><kbd>--provider <name></kbd><span>Default provider</span></div>
|
||||
|
||||
+2
-2
@@ -84,7 +84,7 @@ func (m *Model) findProviderWith(check func(playlist.Provider) bool) playlist.Pr
|
||||
// SetAutoPlay makes the player start playback immediately on Init.
|
||||
func (m *Model) SetAutoPlay(v bool) { m.autoPlay = v }
|
||||
|
||||
// SetLowPower lowers UI cadences for --low-power without affecting normal mode.
|
||||
// SetLowPower lowers UI cadences without affecting normal mode.
|
||||
func (m *Model) SetLowPower(v bool) { m.lowPower = v }
|
||||
|
||||
// SetCompact enables compact mode which caps the frame width at 80 columns.
|
||||
@@ -137,7 +137,7 @@ func (m *Model) SetVisualizer(name string) bool {
|
||||
m.vis.RequestRefresh()
|
||||
m.refreshChrome()
|
||||
// Skip the terminal-title intro animation when the visualizer is disabled
|
||||
// (e.g. --low-power); the user opted out of visual flair, so the 3-second
|
||||
// (e.g. low-power mode); the user opted out of visual flair, so the 3-second
|
||||
// TickFast intro burn (~20 FPS UI rendering) is just wasted CPU.
|
||||
if mode == ui.VisNone {
|
||||
m.termTitle.introActive = false
|
||||
|
||||
+1
-1
@@ -271,7 +271,7 @@ type Model struct {
|
||||
fullVis bool
|
||||
|
||||
autoPlay bool // start playing immediately on launch
|
||||
lowPower bool // lower UI/render cadences; set only by --low-power
|
||||
lowPower bool // lower UI/render cadences in low-power mode
|
||||
compact bool // compact mode: cap frame width at 80 columns
|
||||
heightExpanded bool // tracks whether manual 'x' expansion is active
|
||||
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ const (
|
||||
TickAnalyze = 33 * time.Millisecond // ~30 Hz — FFT analysis cadence (independent of animation)
|
||||
TickSlow = 200 * time.Millisecond // 5 FPS — visualizer off or overlay
|
||||
// TickLowPowerPlaying keeps playback bookkeeping responsive while avoiding
|
||||
// the 20 FPS time/seek refresh cost when --low-power is explicitly enabled.
|
||||
// the 20 FPS time/seek refresh cost when low-power mode is enabled.
|
||||
TickLowPowerPlaying = 500 * time.Millisecond // 2 FPS — low-power playback UI cadence
|
||||
// TickIdle is used when the player is stopped or paused with nothing
|
||||
// animating (no overlay, no buffering, no pending status / reconnect).
|
||||
|
||||
Reference in New Issue
Block a user