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.

feat: add compressor settings for volume normalization

Wire compressor_settings through the full stack — proto, RPC, GraphQL,
sys settings save/load, and TOML persistence — enabling the Rockbox
dynamic range compressor to be configured as a volume normalizer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

+114 -2
+2
cli/src/api/rockbox.v1alpha1.rs
··· 5938 5938 pub eq_band_settings: ::prost::alloc::vec::Vec<EqBandSetting>, 5939 5939 #[prost(message, optional, tag = "27")] 5940 5940 pub replaygain_settings: ::core::option::Option<ReplaygainSettings>, 5941 + #[prost(message, optional, tag = "28")] 5942 + pub compressor_settings: ::core::option::Option<CompressorSettings>, 5941 5943 } 5942 5944 #[derive(Clone, Copy, PartialEq, ::prost::Message)] 5943 5945 pub struct SaveSettingsResponse {}
cli/src/api/rockbox_descriptor.bin

This is a binary file and will not be displayed.

+10
crates/graphql/src/schema/objects/compressor_settings.rs
··· 2 2 use rockbox_sys as rb; 3 3 use serde::{Deserialize, Serialize}; 4 4 5 + #[derive(Default, Clone, Serialize, Deserialize, InputObject)] 6 + pub struct CompressorSettingsInput { 7 + pub threshold: i32, 8 + pub makeup_gain: i32, 9 + pub ratio: i32, 10 + pub knee: i32, 11 + pub release_time: i32, 12 + pub attack_time: i32, 13 + } 14 + 5 15 #[derive(Default, Clone, Serialize, Deserialize)] 6 16 pub struct CompressorSettings { 7 17 pub threshold: i32,
+5 -1
crates/graphql/src/schema/objects/new_global_settings.rs
··· 1 1 use async_graphql::*; 2 2 use serde::{Deserialize, Serialize}; 3 3 4 - use super::{eq_band_setting::EqBandSettingInput, replaygain_settings::ReplaygainSettingsInput}; 4 + use super::{ 5 + compressor_settings::CompressorSettingsInput, eq_band_setting::EqBandSettingInput, 6 + replaygain_settings::ReplaygainSettingsInput, 7 + }; 5 8 6 9 #[derive(Default, Serialize, Deserialize, InputObject)] 7 10 pub struct NewGlobalSettings { ··· 32 35 pub eq_enabled: Option<bool>, 33 36 pub eq_band_settings: Option<Vec<EqBandSettingInput>>, 34 37 pub replaygain_settings: Option<ReplaygainSettingsInput>, 38 + pub compressor_settings: Option<CompressorSettingsInput>, 35 39 }
+2
crates/rocksky/src/api/rockbox.v1alpha1.rs
··· 5938 5938 pub eq_band_settings: ::prost::alloc::vec::Vec<EqBandSetting>, 5939 5939 #[prost(message, optional, tag = "27")] 5940 5940 pub replaygain_settings: ::core::option::Option<ReplaygainSettings>, 5941 + #[prost(message, optional, tag = "28")] 5942 + pub compressor_settings: ::core::option::Option<CompressorSettings>, 5941 5943 } 5942 5944 #[derive(Clone, Copy, PartialEq, ::prost::Message)] 5943 5945 pub struct SaveSettingsResponse {}
crates/rocksky/src/api/rockbox_descriptor.bin

This is a binary file and will not be displayed.

+1
crates/rpc/proto/rockbox/v1alpha1/settings.proto
··· 251 251 optional bool eq_enabled = 25; 252 252 repeated EqBandSetting eq_band_settings = 26; 253 253 optional ReplaygainSettings replaygain_settings = 27; 254 + optional CompressorSettings compressor_settings = 28; 254 255 } 255 256 256 257 message SaveSettingsResponse {}
+2
crates/rpc/src/api/rockbox.v1alpha1.rs
··· 6465 6465 pub eq_band_settings: ::prost::alloc::vec::Vec<EqBandSetting>, 6466 6466 #[prost(message, optional, tag = "27")] 6467 6467 pub replaygain_settings: ::core::option::Option<ReplaygainSettings>, 6468 + #[prost(message, optional, tag = "28")] 6469 + pub compressor_settings: ::core::option::Option<CompressorSettings>, 6468 6470 } 6469 6471 #[derive(Clone, Copy, PartialEq, ::prost::Message)] 6470 6472 pub struct SaveSettingsResponse {}
crates/rpc/src/api/rockbox_descriptor.bin

This is a binary file and will not be displayed.

+8
crates/rpc/src/lib.rs
··· 921 921 preamp: settings.preamp, 922 922 } 923 923 }), 924 + compressor_settings: self.compressor_settings.map(|c| CompressorSettings { 925 + threshold: c.threshold, 926 + makeup_gain: c.makeup_gain, 927 + ratio: c.ratio, 928 + knee: c.knee, 929 + release_time: c.release_time, 930 + attack_time: c.attack_time, 931 + }), 924 932 } 925 933 } 926 934 }
+64
crates/settings/src/lib.rs
··· 66 66 let music_dir = std::env::var("ROCKBOX_LIBRARY").unwrap_or(format!("{}/Music", home)); 67 67 Ok(settings.music_dir.unwrap_or(music_dir)) 68 68 } 69 + 70 + #[cfg(test)] 71 + mod tests { 72 + use rockbox_sys::types::user_settings::{CompressorSettings, NewGlobalSettings}; 73 + 74 + #[test] 75 + fn compressor_settings_round_trip() { 76 + let original = NewGlobalSettings { 77 + compressor_settings: Some(CompressorSettings { 78 + threshold: -24, 79 + makeup_gain: 1, 80 + ratio: 4, 81 + knee: 1, 82 + release_time: 300, 83 + attack_time: 5, 84 + }), 85 + ..Default::default() 86 + }; 87 + 88 + let toml_str = toml::to_string(&original).expect("serialize"); 89 + assert!(toml_str.contains("[compressor_settings]")); 90 + 91 + let restored: NewGlobalSettings = toml::from_str(&toml_str).expect("deserialize"); 92 + let c = restored 93 + .compressor_settings 94 + .expect("compressor_settings present"); 95 + assert_eq!(c.threshold, -24); 96 + assert_eq!(c.makeup_gain, 1); 97 + assert_eq!(c.ratio, 4); 98 + assert_eq!(c.knee, 1); 99 + assert_eq!(c.release_time, 300); 100 + assert_eq!(c.attack_time, 5); 101 + } 102 + 103 + #[test] 104 + fn compressor_settings_absent_when_none() { 105 + let settings = NewGlobalSettings { 106 + compressor_settings: None, 107 + ..Default::default() 108 + }; 109 + let toml_str = toml::to_string(&settings).expect("serialize"); 110 + assert!(!toml_str.contains("compressor_settings")); 111 + 112 + let restored: NewGlobalSettings = toml::from_str(&toml_str).expect("deserialize"); 113 + assert!(restored.compressor_settings.is_none()); 114 + } 115 + 116 + #[test] 117 + fn existing_toml_without_compressor_deserializes_to_none() { 118 + let toml_str = r#" 119 + music_dir = "/home/user/Music" 120 + playlist_shuffle = false 121 + repeat_mode = 1 122 + 123 + [replaygain_settings] 124 + noclip = false 125 + type = 0 126 + preamp = -15 127 + "#; 128 + let settings: NewGlobalSettings = toml::from_str(toml_str).expect("deserialize"); 129 + assert!(settings.compressor_settings.is_none()); 130 + assert_eq!(settings.repeat_mode, Some(1)); 131 + } 132 + }
+4
crates/sys/src/settings.rs
··· 236 236 if let Some(replaygain_settings) = settings.replaygain_settings { 237 237 crate::global_settings.replaygain_settings = replaygain_settings.into(); 238 238 } 239 + 240 + if let Some(compressor_settings) = settings.compressor_settings { 241 + crate::global_settings.compressor_settings = compressor_settings.into(); 242 + } 239 243 } 240 244 } 241 245
+16 -1
crates/sys/src/types/user_settings.rs
··· 83 83 } 84 84 } 85 85 86 - #[derive(Serialize, Deserialize)] 86 + #[derive(Default, Debug, Clone, Serialize, Deserialize)] 87 87 pub struct CompressorSettings { 88 88 pub threshold: i32, 89 89 pub makeup_gain: i32, ··· 102 102 knee: settings.knee, 103 103 release_time: settings.release_time, 104 104 attack_time: settings.attack_time, 105 + } 106 + } 107 + } 108 + 109 + impl Into<crate::CompressorSettings> for CompressorSettings { 110 + fn into(self) -> crate::CompressorSettings { 111 + crate::CompressorSettings { 112 + threshold: self.threshold, 113 + makeup_gain: self.makeup_gain, 114 + ratio: self.ratio, 115 + knee: self.knee, 116 + release_time: self.release_time, 117 + attack_time: self.attack_time, 105 118 } 106 119 } 107 120 } ··· 663 676 pub eq_enabled: Option<bool>, 664 677 pub eq_band_settings: Option<Vec<EqBandSetting>>, 665 678 pub replaygain_settings: Option<ReplaygainSettings>, 679 + pub compressor_settings: Option<CompressorSettings>, 666 680 } 667 681 668 682 impl From<UserSettings> for NewGlobalSettings { ··· 695 709 eq_enabled: Some(settings.eq_enabled), 696 710 eq_band_settings: Some(settings.eq_band_settings), 697 711 replaygain_settings: Some(settings.replaygain_settings), 712 + compressor_settings: Some(settings.compressor_settings), 698 713 } 699 714 } 700 715 }