| 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 | |
| 4 | use budctl_core::status::{Battery, Charge, EqBand, EqPreset, EqPresets, NoiseControl, Status, Switch}; |
| 5 | |
| 6 | /// The buds the window shows. |
| 7 | pub 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 | |
| 15 | impl 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`. |
| 49 | pub 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. |
| 114 | pub 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. |
| 125 | pub 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. |
| 134 | pub 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"). |
| 144 | pub 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". |
| 154 | fn 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)] |
| 164 | mod 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 | } |