Rockbox open source high quality audio player as a Music Player Daemon
mpris rockbox mpd libadwaita audio rust zig deno
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add real-time PCM loudness normalizer

Expose normalize_volume in settings and RPC, and add PCM normalizer
Add C implementation (pcm_normalizer.c/.h) and list it in
firmware/SOURCES
Add Rust FFI (crates/sys) and wire control into settings and PCM sinks
Update web UI snapshots to reflect new default volume

+226 -29
+1
crates/rpc/src/lib.rs
··· 950 950 chromecast_port: None, 951 951 snapcast_tcp_host: None, 952 952 snapcast_tcp_port: None, 953 + normalize_volume: None, 953 954 } 954 955 } 955 956 }
+8 -1
crates/settings/src/lib.rs
··· 1 1 use anyhow::Error; 2 - use rockbox_sys::{self as rb, sound::pcm, types::user_settings::NewGlobalSettings}; 2 + use rockbox_sys::{ 3 + self as rb, 4 + sound::{normalizer, pcm}, 5 + types::user_settings::NewGlobalSettings, 6 + }; 3 7 4 8 pub fn load_settings(new_settings: Option<NewGlobalSettings>) -> Result<(), Error> { 5 9 let settings: NewGlobalSettings = match new_settings.clone() { ··· 147 151 rockbox_upnp::start_renderer(port, name); 148 152 } 149 153 154 + normalizer::enable(settings.normalize_volume.unwrap_or(false)); 155 + 150 156 rb::settings::apply_audio_settings(); 151 157 152 158 let enabled = unsafe { rb::global_settings.eq_enabled }; ··· 199 205 settings.eq_band_settings = from_c.eq_band_settings; 200 206 settings.replaygain_settings = from_c.replaygain_settings; 201 207 settings.compressor_settings = from_c.compressor_settings; 208 + settings.normalize_volume = Some(normalizer::is_enabled()); 202 209 203 210 let content = toml::to_string(&settings)?; 204 211 let path = format!("{}/.config/rockbox.org/settings.toml", home);
+1
crates/sys/src/sound/mod.rs
··· 4 4 5 5 pub mod dsp; 6 6 pub mod mixer; 7 + pub mod normalizer; 7 8 pub mod pcm; 8 9 9 10 pub fn adjust_volume(steps: i32) {
+12
crates/sys/src/sound/normalizer.rs
··· 1 + unsafe extern "C" { 2 + fn pcm_normalizer_enable(enable: bool); 3 + fn pcm_normalizer_is_enabled() -> bool; 4 + } 5 + 6 + pub fn enable(enabled: bool) { 7 + unsafe { pcm_normalizer_enable(enabled) } 8 + } 9 + 10 + pub fn is_enabled() -> bool { 11 + unsafe { pcm_normalizer_is_enabled() } 12 + }
+4
crates/sys/src/types/user_settings.rs
··· 725 725 pub snapcast_tcp_host: Option<String>, 726 726 /// TCP port for the Snapcast source (default: 4953) 727 727 pub snapcast_tcp_port: Option<u16>, 728 + /// Enable real-time PCM loudness normalization (default: false). 729 + /// Equalises perceived volume across tracks, similar to Spotify's "Normalize Volume". 730 + pub normalize_volume: Option<bool>, 728 731 } 729 732 730 733 impl From<UserSettings> for NewGlobalSettings { ··· 777 780 chromecast_http_port: None, 778 781 snapcast_tcp_host: None, 779 782 snapcast_tcp_port: None, 783 + normalize_volume: None, 780 784 } 781 785 } 782 786 }
+1
firmware/SOURCES
··· 542 542 target/hosted/pcm-upnp.c 543 543 target/hosted/pcm-chromecast.c 544 544 target/hosted/pcm-tcp.c 545 + pcm_normalizer.c 545 546 #endif 546 547 #ifdef HAVE_SW_VOLUME_CONTROL 547 548 pcm_sw_volume.c
+20
firmware/export/pcm_normalizer.h
··· 1 + #ifndef PCM_NORMALIZER_H 2 + #define PCM_NORMALIZER_H 3 + 4 + #include <stdbool.h> 5 + #include <stddef.h> 6 + 7 + /* Real-time PCM loudness normalizer — Spotify-style "Normalize Volume". 8 + * 9 + * Tracks the RMS energy of the audio stream with asymmetric attack/release 10 + * smoothing and applies a per-chunk gain so all tracks play at roughly the 11 + * same perceived loudness (target: -23 dBFS RMS ≈ EBU R128). 12 + * 13 + * Called by each PCM sink after SW-volume scaling (pcm_copy_buffer). 14 + * Operates in-place on S16LE stereo PCM. */ 15 + 16 + void pcm_normalizer_enable(bool enable); 17 + bool pcm_normalizer_is_enabled(void); 18 + void pcm_normalizer_apply(void *buf, size_t bytes); 19 + 20 + #endif /* PCM_NORMALIZER_H */
+130
firmware/pcm_normalizer.c
··· 1 + /* 2 + * Real-time PCM loudness normalizer. 3 + * 4 + * Algorithm: RMS-based Automatic Gain Control with asymmetric attack/release 5 + * smoothing, similar to what Spotify's "Normalize Volume" does at the track 6 + * replay-gain level — but applied in real time at the PCM buffer level so it 7 + * works for any source (local files, streams, radio) without metadata. 8 + * 9 + * Data format: S16LE stereo, 44100 Hz (Rockbox hosted / sdlapp target). 10 + * 11 + * Copyright (C) 2026 Rockbox contributors 12 + * 13 + * This program is free software; you can redistribute it and/or 14 + * modify it under the terms of the GNU General Public License 15 + * as published by the Free Software Foundation; either version 2 16 + * of the License, or (at your option) any later version. 17 + */ 18 + 19 + #include "pcm_normalizer.h" 20 + 21 + #include <math.h> 22 + #include <stdint.h> 23 + 24 + /* ── Tuning knobs ───────────────────────────────────────────────────────────── 25 + * 26 + * TARGET_RMS Target output loudness, linear amplitude. 27 + * -23 dBFS = 10^(-23/20) ≈ 0.0708 (EBU R128 / broadcast). 28 + * 29 + * RMS_ATTACK Per-chunk IIR coefficient for the RMS estimator rising 30 + * toward a LOUDER signal. Smaller → faster attack. 31 + * 0.3 ≈ 2-3 chunks (< 150 ms) to track a loud transient. 32 + * 33 + * RMS_RELEASE Per-chunk IIR coefficient for the RMS estimator falling 34 + * toward a QUIETER signal. Larger → slower release. 35 + * 0.997 ≈ 330 chunks (≈ 15 s) to fully settle. 36 + * 37 + * GAIN_ATTACK How fast the applied gain is REDUCED (loud incoming signal). 38 + * Fast: prevents clipping when switching to a loud track. 39 + * 40 + * GAIN_RELEASE How slowly the applied gain is RAISED (quiet signal). 41 + * Very slow: prevents the "pumping" artefact during pauses 42 + * or quiet passages between tracks. 43 + * 44 + * MAX_GAIN Hard upper bound on the boost, +12 dB. 45 + * Prevents amplifying near-silence to noise. 46 + * 47 + * MIN_GAIN Hard lower bound, -12 dB. 48 + * Prevents crushing content that is louder than target. 49 + * 50 + * GATE_THRESH RMS below this level → chunk is silence; neither the RMS 51 + * estimate nor the gain are updated. -60 dBFS ≈ 0.001. 52 + * ──────────────────────────────────────────────────────────────────────────── */ 53 + #define TARGET_RMS 0.0708f 54 + #define RMS_ATTACK 0.3f 55 + #define RMS_RELEASE 0.997f 56 + #define GAIN_ATTACK 0.3f 57 + #define GAIN_RELEASE 0.9995f 58 + #define MAX_GAIN 4.0f 59 + #define MIN_GAIN 0.25f 60 + #define GATE_THRESH 0.001f 61 + 62 + static bool normalizer_enabled = false; 63 + static float gain = 1.0f; 64 + static float rms_estimate = TARGET_RMS; /* warm-start avoids over-correction on first chunk */ 65 + 66 + void pcm_normalizer_enable(bool enable) 67 + { 68 + if (enable && !normalizer_enabled) { 69 + /* Reset state on each fresh enable so a stale gain from a previous 70 + * session doesn't immediately blast or mute the first chunk. */ 71 + gain = 1.0f; 72 + rms_estimate = TARGET_RMS; 73 + } 74 + normalizer_enabled = enable; 75 + } 76 + 77 + bool pcm_normalizer_is_enabled(void) 78 + { 79 + return normalizer_enabled; 80 + } 81 + 82 + void pcm_normalizer_apply(void *buf, size_t bytes) 83 + { 84 + if (!normalizer_enabled || !buf || bytes < 2) 85 + return; 86 + 87 + int16_t *s = (int16_t *)buf; 88 + size_t n = bytes / sizeof(int16_t); 89 + 90 + /* ── Step 1: Compute RMS of this chunk ──────────────────────────────── */ 91 + double sum_sq = 0.0; 92 + for (size_t i = 0; i < n; i++) { 93 + double v = s[i] * (1.0 / 32768.0); 94 + sum_sq += v * v; 95 + } 96 + float chunk_rms = (n > 0) ? (float)sqrt(sum_sq / (double)n) : 0.0f; 97 + 98 + /* ── Step 2: Update RMS estimate (gate: skip silence) ───────────────── */ 99 + if (chunk_rms > GATE_THRESH) { 100 + float rms_coeff = (chunk_rms > rms_estimate) ? RMS_ATTACK : RMS_RELEASE; 101 + rms_estimate = rms_coeff * rms_estimate + (1.0f - rms_coeff) * chunk_rms; 102 + } 103 + 104 + /* ── Step 3: Compute desired gain and smooth toward it ──────────────── */ 105 + float desired_gain; 106 + if (rms_estimate > GATE_THRESH) { 107 + desired_gain = TARGET_RMS / rms_estimate; 108 + if (desired_gain > MAX_GAIN) desired_gain = MAX_GAIN; 109 + if (desired_gain < MIN_GAIN) desired_gain = MIN_GAIN; 110 + } else { 111 + desired_gain = gain; /* silence: hold current gain */ 112 + } 113 + 114 + float g_coeff = (desired_gain < gain) ? GAIN_ATTACK : GAIN_RELEASE; 115 + float g_start = gain; 116 + gain = g_coeff * gain + (1.0f - g_coeff) * desired_gain; 117 + float g_end = gain; 118 + 119 + /* ── Step 4: Apply linearly-interpolated gain across the chunk ────────── 120 + * Ramping from g_start → g_end avoids a click at chunk boundaries 121 + * when the gain changes rapidly (e.g., a loud track follows a quiet one). */ 122 + float n_inv = (n > 1) ? (1.0f / (float)(n - 1)) : 0.0f; 123 + for (size_t i = 0; i < n; i++) { 124 + float g = g_start + (g_end - g_start) * ((float)i * n_inv); 125 + float v = (float)s[i] * g; 126 + if (v > 32767.0f) v = 32767.0f; 127 + if (v < -32768.0f) v = -32768.0f; 128 + s[i] = (int16_t)v; 129 + } 130 + }
+3
firmware/target/hosted/pcm-airplay.c
··· 31 31 #include "pcm.h" 32 32 #include "pcm-internal.h" 33 33 #include "pcm_mixer.h" 34 + #include "pcm_normalizer.h" 34 35 #include "pcm_sampr.h" 35 36 #include "pcm_sink.h" 36 37 ··· 80 81 const void *data = (airplay_vol_buf && size > 0) 81 82 ? (pcm_copy_buffer(airplay_vol_buf, raw, size), airplay_vol_buf) 82 83 : raw; 84 + if (data == airplay_vol_buf) 85 + pcm_normalizer_apply(airplay_vol_buf, size); 83 86 84 87 if (data && size > 0) { 85 88 if (pcm_airplay_write((const uint8_t *)data, size) < 0) {
+3
firmware/target/hosted/pcm-chromecast.c
··· 35 35 #include "pcm.h" 36 36 #include "pcm-internal.h" 37 37 #include "pcm_mixer.h" 38 + #include "pcm_normalizer.h" 38 39 #include "pcm_sampr.h" 39 40 #include "pcm_sink.h" 40 41 ··· 92 93 const void *data = (chromecast_vol_buf && size > 0) 93 94 ? (pcm_copy_buffer(chromecast_vol_buf, raw, size), chromecast_vol_buf) 94 95 : raw; 96 + if (data == chromecast_vol_buf) 97 + pcm_normalizer_apply(chromecast_vol_buf, size); 95 98 96 99 if (data && size > 0) { 97 100 if (pcm_chromecast_write((const uint8_t *)data, size) < 0) {
+3
firmware/target/hosted/pcm-fifo.c
··· 43 43 #include "pcm.h" 44 44 #include "pcm-internal.h" 45 45 #include "pcm_mixer.h" 46 + #include "pcm_normalizer.h" 46 47 #include "pcm_sampr.h" 47 48 #include "pcm_sink.h" 48 49 ··· 137 138 const void *data = (fifo_vol_buf && size > 0) 138 139 ? (pcm_copy_buffer(fifo_vol_buf, raw, size), fifo_vol_buf) 139 140 : raw; 141 + if (data == fifo_vol_buf) 142 + pcm_normalizer_apply(fifo_vol_buf, size); 140 143 141 144 /* Write current chunk in pieces so stop() can interrupt promptly */ 142 145 while (size > 0 && !fifo_stop) {
+3
firmware/target/hosted/pcm-squeezelite.c
··· 34 34 #include "pcm.h" 35 35 #include "pcm-internal.h" 36 36 #include "pcm_mixer.h" 37 + #include "pcm_normalizer.h" 37 38 #include "pcm_sampr.h" 38 39 #include "pcm_sink.h" 39 40 ··· 89 90 const void *data = (squeezelite_vol_buf && size > 0) 90 91 ? (pcm_copy_buffer(squeezelite_vol_buf, raw, size), squeezelite_vol_buf) 91 92 : raw; 93 + if (data == squeezelite_vol_buf) 94 + pcm_normalizer_apply(squeezelite_vol_buf, size); 92 95 93 96 if (data && size > 0) { 94 97 if (pcm_squeezelite_write((const uint8_t *)data, size) < 0) {
+3
firmware/target/hosted/pcm-tcp.c
··· 49 49 #include "pcm.h" 50 50 #include "pcm-internal.h" 51 51 #include "pcm_mixer.h" 52 + #include "pcm_normalizer.h" 52 53 #include "pcm_sampr.h" 53 54 #include "pcm_sink.h" 54 55 ··· 144 145 const void *data = (tcp_vol_buf && size > 0) 145 146 ? (pcm_copy_buffer(tcp_vol_buf, raw, size), tcp_vol_buf) 146 147 : raw; 148 + if (data == tcp_vol_buf) 149 + pcm_normalizer_apply(tcp_vol_buf, size); 147 150 148 151 /* Write current chunk in pieces so stop() can interrupt promptly */ 149 152 while (size > 0 && !tcp_stop) {
+3
firmware/target/hosted/pcm-upnp.c
··· 33 33 #include "pcm.h" 34 34 #include "pcm-internal.h" 35 35 #include "pcm_mixer.h" 36 + #include "pcm_normalizer.h" 36 37 #include "pcm_sampr.h" 37 38 #include "pcm_sink.h" 38 39 ··· 89 90 const void *data = (upnp_vol_buf && size > 0) 90 91 ? (pcm_copy_buffer(upnp_vol_buf, raw, size), upnp_vol_buf) 91 92 : raw; 93 + if (data == upnp_vol_buf) 94 + pcm_normalizer_apply(upnp_vol_buf, size); 92 95 93 96 if (data && size > 0) { 94 97 if (pcm_upnp_write((const uint8_t *)data, size) < 0) {
+3
firmware/target/hosted/sdl/pcm-sdl.c
··· 42 42 43 43 #include "pcm.h" 44 44 #include "pcm-internal.h" 45 + #include "pcm_normalizer.h" 45 46 #include "pcm_sampr.h" 46 47 #include "pcm_mixer.h" 47 48 #include "pcm_sink.h" ··· 242 243 cvt.buf = (Uint8 *) malloc(cvt.len * cvt.len_mult); 243 244 244 245 pcm_copy_buffer(cvt.buf, pcm_data, cvt.len); 246 + pcm_normalizer_apply(cvt.buf, cvt.len); 245 247 246 248 SDL_ConvertAudio(&cvt); 247 249 memcpy(udata->stream, cvt.buf, cvt.len_cvt); ··· 288 290 udata->num_in = udata->num_out = MIN(udata->num_in, udata->num_out); 289 291 pcm_copy_buffer(udata->stream, pcm_data, 290 292 udata->num_out * pcm_sample_bytes); 293 + pcm_normalizer_apply(udata->stream, udata->num_out * pcm_sample_bytes); 291 294 #ifdef DEBUG 292 295 if (udata->debug != NULL) { 293 296 fwrite(pcm_data, sizeof(Uint8), udata->num_out * pcm_sample_bytes,
+4 -4
webui/rockbox/src/Components/AlbumDetails/__snapshots__/AlbumDetails.test.tsx.snap
··· 372 372 /> 373 373 <span 374 374 class="MuiSlider-track css-xvk2i-MuiSlider-track" 375 - style="left: 0%; width: 80%;" 375 + style="left: 0%; width: 0%;" 376 376 /> 377 377 <span 378 378 class="MuiSlider-thumb MuiSlider-thumbSizeMedium MuiSlider-thumbColorPrimary MuiSlider-thumb MuiSlider-thumbSizeMedium MuiSlider-thumbColorPrimary css-b1410s-MuiSlider-thumb" 379 379 data-index="0" 380 - style="left: 80%;" 380 + style="left: 0%;" 381 381 > 382 382 <input 383 383 aria-label="Volume" 384 384 aria-orientation="horizontal" 385 385 aria-valuemax="100" 386 386 aria-valuemin="0" 387 - aria-valuenow="80" 387 + aria-valuenow="0" 388 388 data-index="0" 389 389 max="100" 390 390 min="0" 391 391 step="1" 392 392 style="border: 0px; height: 100%; margin: -1px; overflow: hidden; padding: 0px; position: absolute; white-space: nowrap; width: 100%; direction: ltr;" 393 393 type="range" 394 - value="80" 394 + value="0" 395 395 /> 396 396 </span> 397 397 </span>
+4 -4
webui/rockbox/src/Components/Albums/__snapshots__/Albums.test.tsx.snap
··· 372 372 /> 373 373 <span 374 374 class="MuiSlider-track css-xvk2i-MuiSlider-track" 375 - style="left: 0%; width: 80%;" 375 + style="left: 0%; width: 0%;" 376 376 /> 377 377 <span 378 378 class="MuiSlider-thumb MuiSlider-thumbSizeMedium MuiSlider-thumbColorPrimary MuiSlider-thumb MuiSlider-thumbSizeMedium MuiSlider-thumbColorPrimary css-b1410s-MuiSlider-thumb" 379 379 data-index="0" 380 - style="left: 80%;" 380 + style="left: 0%;" 381 381 > 382 382 <input 383 383 aria-label="Volume" 384 384 aria-orientation="horizontal" 385 385 aria-valuemax="100" 386 386 aria-valuemin="0" 387 - aria-valuenow="80" 387 + aria-valuenow="0" 388 388 data-index="0" 389 389 max="100" 390 390 min="0" 391 391 step="1" 392 392 style="border: 0px; height: 100%; margin: -1px; overflow: hidden; padding: 0px; position: absolute; white-space: nowrap; width: 100%; direction: ltr;" 393 393 type="range" 394 - value="80" 394 + value="0" 395 395 /> 396 396 </span> 397 397 </span>
+4 -4
webui/rockbox/src/Components/ArtistDetails/__snapshots__/ArtistDetails.test.tsx.snap
··· 369 369 /> 370 370 <span 371 371 class="MuiSlider-track css-xvk2i-MuiSlider-track" 372 - style="left: 0%; width: 80%;" 372 + style="left: 0%; width: 0%;" 373 373 /> 374 374 <span 375 375 class="MuiSlider-thumb MuiSlider-thumbSizeMedium MuiSlider-thumbColorPrimary MuiSlider-thumb MuiSlider-thumbSizeMedium MuiSlider-thumbColorPrimary css-b1410s-MuiSlider-thumb" 376 376 data-index="0" 377 - style="left: 80%;" 377 + style="left: 0%;" 378 378 > 379 379 <input 380 380 aria-label="Volume" 381 381 aria-orientation="horizontal" 382 382 aria-valuemax="100" 383 383 aria-valuemin="0" 384 - aria-valuenow="80" 384 + aria-valuenow="0" 385 385 data-index="0" 386 386 max="100" 387 387 min="0" 388 388 step="1" 389 389 style="border: 0px; height: 100%; margin: -1px; overflow: hidden; padding: 0px; position: absolute; white-space: nowrap; width: 100%; direction: ltr;" 390 390 type="range" 391 - value="80" 391 + value="0" 392 392 /> 393 393 </span> 394 394 </span>
+4 -4
webui/rockbox/src/Components/Artists/__snapshots__/Artists.test.tsx.snap
··· 372 372 /> 373 373 <span 374 374 class="MuiSlider-track css-xvk2i-MuiSlider-track" 375 - style="left: 0%; width: 80%;" 375 + style="left: 0%; width: 0%;" 376 376 /> 377 377 <span 378 378 class="MuiSlider-thumb MuiSlider-thumbSizeMedium MuiSlider-thumbColorPrimary MuiSlider-thumb MuiSlider-thumbSizeMedium MuiSlider-thumbColorPrimary css-b1410s-MuiSlider-thumb" 379 379 data-index="0" 380 - style="left: 80%;" 380 + style="left: 0%;" 381 381 > 382 382 <input 383 383 aria-label="Volume" 384 384 aria-orientation="horizontal" 385 385 aria-valuemax="100" 386 386 aria-valuemin="0" 387 - aria-valuenow="80" 387 + aria-valuenow="0" 388 388 data-index="0" 389 389 max="100" 390 390 min="0" 391 391 step="1" 392 392 style="border: 0px; height: 100%; margin: -1px; overflow: hidden; padding: 0px; position: absolute; white-space: nowrap; width: 100%; direction: ltr;" 393 393 type="range" 394 - value="80" 394 + value="0" 395 395 /> 396 396 </span> 397 397 </span>
+4 -4
webui/rockbox/src/Components/ControlBar/__snapshots__/ControlBar.test.tsx.snap
··· 238 238 /> 239 239 <span 240 240 class="MuiSlider-track css-xvk2i-MuiSlider-track" 241 - style="left: 0%; width: 80%;" 241 + style="left: 0%; width: 0%;" 242 242 /> 243 243 <span 244 244 class="MuiSlider-thumb MuiSlider-thumbSizeMedium MuiSlider-thumbColorPrimary MuiSlider-thumb MuiSlider-thumbSizeMedium MuiSlider-thumbColorPrimary css-b1410s-MuiSlider-thumb" 245 245 data-index="0" 246 - style="left: 80%;" 246 + style="left: 0%;" 247 247 > 248 248 <input 249 249 aria-label="Volume" 250 250 aria-orientation="horizontal" 251 251 aria-valuemax="100" 252 252 aria-valuemin="0" 253 - aria-valuenow="80" 253 + aria-valuenow="0" 254 254 data-index="0" 255 255 max="100" 256 256 min="0" 257 257 step="1" 258 258 style="border: 0px; height: 100%; margin: -1px; overflow: hidden; padding: 0px; position: absolute; white-space: nowrap; width: 100%; direction: ltr;" 259 259 type="range" 260 - value="80" 260 + value="0" 261 261 /> 262 262 </span> 263 263 </span>
+4 -4
webui/rockbox/src/Components/Files/__snapshots__/Files.test.tsx.snap
··· 372 372 /> 373 373 <span 374 374 class="MuiSlider-track css-xvk2i-MuiSlider-track" 375 - style="left: 0%; width: 80%;" 375 + style="left: 0%; width: 0%;" 376 376 /> 377 377 <span 378 378 class="MuiSlider-thumb MuiSlider-thumbSizeMedium MuiSlider-thumbColorPrimary MuiSlider-thumb MuiSlider-thumbSizeMedium MuiSlider-thumbColorPrimary css-b1410s-MuiSlider-thumb" 379 379 data-index="0" 380 - style="left: 80%;" 380 + style="left: 0%;" 381 381 > 382 382 <input 383 383 aria-label="Volume" 384 384 aria-orientation="horizontal" 385 385 aria-valuemax="100" 386 386 aria-valuemin="0" 387 - aria-valuenow="80" 387 + aria-valuenow="0" 388 388 data-index="0" 389 389 max="100" 390 390 min="0" 391 391 step="1" 392 392 style="border: 0px; height: 100%; margin: -1px; overflow: hidden; padding: 0px; position: absolute; white-space: nowrap; width: 100%; direction: ltr;" 393 393 type="range" 394 - value="80" 394 + value="0" 395 395 /> 396 396 </span> 397 397 </span>
+4 -4
webui/rockbox/src/Components/Tracks/__snapshots__/Tracks.test.tsx.snap
··· 372 372 /> 373 373 <span 374 374 class="MuiSlider-track css-xvk2i-MuiSlider-track" 375 - style="left: 0%; width: 80%;" 375 + style="left: 0%; width: 0%;" 376 376 /> 377 377 <span 378 378 class="MuiSlider-thumb MuiSlider-thumbSizeMedium MuiSlider-thumbColorPrimary MuiSlider-thumb MuiSlider-thumbSizeMedium MuiSlider-thumbColorPrimary css-b1410s-MuiSlider-thumb" 379 379 data-index="0" 380 - style="left: 80%;" 380 + style="left: 0%;" 381 381 > 382 382 <input 383 383 aria-label="Volume" 384 384 aria-orientation="horizontal" 385 385 aria-valuemax="100" 386 386 aria-valuemin="0" 387 - aria-valuenow="80" 387 + aria-valuenow="0" 388 388 data-index="0" 389 389 max="100" 390 390 min="0" 391 391 step="1" 392 392 style="border: 0px; height: 100%; margin: -1px; overflow: hidden; padding: 0px; position: absolute; white-space: nowrap; width: 100%; direction: ltr;" 393 393 type="range" 394 - value="80" 394 + value="0" 395 395 /> 396 396 </span> 397 397 </span>