Krolyxon
Commit e8c78b9

Branches

Refs →

budctlcratesbudctl-guisrcbuds.rs

190 lines · 5.9 KB · Rust

1//! The buds the window shows: made-up ones for `--demo` (roughly what the owner's T310 reported
2//! at the last checkpoints), or an empty pair until the real buds answer.
3
4use budctl_core::status::{Battery, Charge, EqBand, EqPreset, EqPresets, NoiseControl, Status, Switch};
5
6/// The buds the window shows.
7pub struct Buds {
8 pub name: String,
9 pub connected: bool,
10 pub status: Status,
11 /// The EQ presets, when the buds list them.
12 pub eq: Option<EqPresets>,
13}
14
15impl Buds {
16 /// The name to show for the EQ preset in use.
17 pub fn eq_name(&self) -> String {
18 eq_name(self.status.eq.as_deref(), self.eq.as_ref())
19 }
20
21 /// Records that `now` is the EQ preset in use.
22 pub fn set_eq(&mut self, now: String) {
23 if let Some(eq) = &mut self.eq {
24 eq.current = Some(now.clone());
25 }
26 self.status.eq = Some(now);
27 }
28
29 /// Takes in a custom preset as the buds report it after a write: saved, and in use.
30 pub fn set_custom(&mut self, preset: EqPreset) {
31 let now = key(&preset);
32 if let Some(shown) = self
33 .eq
34 .as_mut()
35 .and_then(|eq| eq.presets.iter_mut().find(|p| p.custom && p.id == preset.id))
36 {
37 *shown = preset;
38 }
39 self.set_eq(now);
40 }
41
42 /// The preset with this id.
43 pub fn preset(&self, id: u8) -> Option<&EqPreset> {
44 self.eq.as_ref()?.presets.iter().find(|p| p.id == id)
45 }
46}
47
48/// The made-up buds of `--demo`.
49pub fn demo() -> Buds {
50 let charge = |level| Some(Charge { level, charging: false });
51 let switch = |key, label, on| Switch { key, label, on };
52 let builtin = |id, name: &str| EqPreset {
53 name: name.to_owned(),
54 id,
55 custom: false,
56 bands: Vec::new(),
57 range: None,
58 };
59 let band = |hz, gain| EqBand { hz, gain };
60 Buds {
61 name: "realme Buds T310".to_owned(),
62 connected: true,
63 status: Status {
64 battery: Battery {
65 left: charge(80),
66 right: charge(80),
67 case: None,
68 },
69 noise_control: Some(NoiseControl {
70 mode: "anc".to_owned(),
71 level: Some("deep".to_owned()),
72 }),
73 eq: Some("custom-4".to_owned()),
74 bass_level: None,
75 switches: vec![
76 switch("game", "Game mode", false),
77 switch("dual", "Dual connection", true),
78 switch("wind", "Wind-noise reduction", true),
79 switch("spatial", "3D sound", true),
80 switch("bass-boost", "Bass boost", true),
81 ],
82 codec: Some("SBC".to_owned()),
83 firmware: Some("1.1.0.43".to_owned()),
84 warnings: Vec::new(),
85 },
86 eq: Some(EqPresets {
87 current: Some("custom-4".to_owned()),
88 presets: vec![
89 builtin(0, "pure-raw"),
90 builtin(1, "deep-bass"),
91 builtin(2, "pure-vocals"),
92 builtin(3, "pure-bass"),
93 EqPreset {
94 name: "Custom1".to_owned(),
95 id: 4,
96 custom: true,
97 bands: vec![
98 band(50, 3),
99 band(250, 1),
100 band(1000, 0),
101 band(4000, -1),
102 band(8000, 1),
103 band(16000, 2),
104 ],
105 range: Some((-6, 6)),
106 },
107 ],
108 warnings: Vec::new(),
109 }),
110 }
111}
112
113/// What the window shows before the real buds answer.
114pub fn unknown() -> Buds {
115 Buds {
116 name: "Your buds".to_owned(),
117 connected: false,
118 status: Status::default(),
119 eq: None,
120 }
121}
122
123/// The name a preset is selected by, as in [`Status::eq`]: a built-in one's own name,
124/// `custom-<id>` for a custom one.
125pub fn key(preset: &EqPreset) -> String {
126 if preset.custom {
127 format!("custom-{}", preset.id)
128 } else {
129 preset.name.clone()
130 }
131}
132
133/// A preset's name to show: a custom one's stored name, a built-in one's in words.
134pub fn shown_name(preset: &EqPreset) -> String {
135 if preset.custom {
136 preset.name.clone()
137 } else {
138 words(&preset.name)
139 }
140}
141
142/// The name to show for the EQ preset in use: a custom preset's own name (`custom-4` →
143/// "Custom1"), else the preset's name in words (`pure-bass` → "Pure bass").
144pub fn eq_name(current: Option<&str>, presets: Option<&EqPresets>) -> String {
145 let Some(current) = current else {
146 return "Unknown".to_owned();
147 };
148 presets
149 .and_then(|eq| eq.presets.iter().find(|p| key(p) == current))
150 .map_or_else(|| words(current), shown_name)
151}
152
153/// `pure-bass` → "Pure bass".
154fn words(name: &str) -> String {
155 let words = name.replace('-', " ");
156 let mut letters = words.chars();
157 letters
158 .next()
159 .map(|first| first.to_uppercase().chain(letters).collect())
160 .unwrap_or_default()
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn eq_names() {
169 let buds = demo();
170 assert_eq!(eq_name(Some("custom-4"), buds.eq.as_ref()), "Custom1");
171 assert_eq!(eq_name(Some("custom-4"), None), "Custom 4");
172 assert_eq!(eq_name(Some("pure-bass"), buds.eq.as_ref()), "Pure bass");
173 assert_eq!(eq_name(None, None), "Unknown");
174 assert_eq!(buds.eq_name(), "Custom1");
175 }
176
177 #[test]
178 fn eq_changes_show() {
179 let mut buds = demo();
180 buds.set_eq("deep-bass".to_owned());
181 assert_eq!(buds.eq_name(), "Deep bass");
182 assert_eq!(buds.eq.as_ref().unwrap().current.as_deref(), Some("deep-bass"));
183
184 let mut custom = buds.preset(4).unwrap().clone();
185 custom.bands[0].gain = -6;
186 buds.set_custom(custom);
187 assert_eq!(buds.status.eq.as_deref(), Some("custom-4"));
188 assert_eq!(buds.preset(4).unwrap().bands[0].gain, -6);
189 }
190}