1use std::sync::atomic::{AtomicU64, Ordering};
12
13use crate::midi_cc_config::{controller, MidiCcConfig, MAX_CC_CONTROLLER};
14use crate::parameter_groups::{GroupInfo, ParameterGroups, ROOT_GROUP_ID};
15use crate::parameter_info::{ParameterFlags, ParameterInfo, ParameterUnit};
16use crate::parameter_store::ParameterStore;
17use crate::types::{ParameterId, ParameterValue};
18
19pub const MIDI_CC_PARAM_BASE: u32 = 0x10000000; pub struct MidiCcState {
54 enabled: [bool; MAX_CC_CONTROLLER],
56 values: [AtomicU64; MAX_CC_CONTROLLER],
58 parameter_infos: Vec<CcParameterInfo>,
60 enabled_count: usize,
62}
63
64struct CcParameterInfo {
66 controller: u8,
67 info: ParameterInfo,
68}
69
70impl MidiCcState {
71 pub fn from_config(config: &MidiCcConfig) -> Self {
75 let values = std::array::from_fn(|i| {
77 let default: f64 = if i == controller::PITCH_BEND as usize {
78 0.5 } else {
80 0.0
81 };
82 AtomicU64::new(default.to_bits())
83 });
84
85 let enabled = *config.enabled_flags();
87 let mut parameter_infos = Vec::new();
88 let mut enabled_count = 0;
89
90 for (i, &is_enabled) in enabled.iter().enumerate() {
91 if is_enabled {
92 enabled_count += 1;
93 let controller = i as u8;
94 let info = Self::create_parameter_info(controller);
95 parameter_infos.push(CcParameterInfo { controller, info });
96 }
97 }
98
99 Self {
100 enabled,
101 values,
102 parameter_infos,
103 enabled_count,
104 }
105 }
106
107 #[inline]
115 pub fn pitch_bend(&self) -> f32 {
116 if self.enabled[controller::PITCH_BEND as usize] {
117 let normalized = self.get_normalized_internal(controller::PITCH_BEND);
118 (normalized * 2.0 - 1.0) as f32
119 } else {
120 0.0
121 }
122 }
123
124 #[inline]
128 pub fn aftertouch(&self) -> f32 {
129 if self.enabled[controller::AFTERTOUCH as usize] {
130 self.get_normalized_internal(controller::AFTERTOUCH) as f32
131 } else {
132 0.0
133 }
134 }
135
136 #[inline]
140 pub fn mod_wheel(&self) -> f32 {
141 self.cc(1)
142 }
143
144 #[inline]
148 pub fn cc(&self, cc: u8) -> f32 {
149 if (cc as usize) < MAX_CC_CONTROLLER && self.enabled[cc as usize] {
150 self.get_normalized_internal(cc) as f32
151 } else {
152 0.0
153 }
154 }
155
156 #[inline]
162 pub fn has_controller(&self, controller: u8) -> bool {
163 if (controller as usize) < MAX_CC_CONTROLLER {
164 self.enabled[controller as usize]
165 } else {
166 false
167 }
168 }
169
170 #[inline]
172 pub fn has_pitch_bend(&self) -> bool {
173 self.enabled[controller::PITCH_BEND as usize]
174 }
175
176 #[inline]
178 pub fn has_aftertouch(&self) -> bool {
179 self.enabled[controller::AFTERTOUCH as usize]
180 }
181
182 #[inline]
184 pub fn enabled_count(&self) -> usize {
185 self.enabled_count
186 }
187
188 pub fn enabled_controllers(&self) -> impl Iterator<Item = u8> + '_ {
190 self.parameter_infos.iter().map(|info| info.controller)
191 }
192
193 #[inline]
199 pub const fn parameter_id(controller: u8) -> u32 {
200 MIDI_CC_PARAM_BASE + controller as u32
201 }
202
203 #[inline]
205 pub const fn is_midi_cc_parameter(parameter_id: u32) -> bool {
206 parameter_id >= MIDI_CC_PARAM_BASE && parameter_id < MIDI_CC_PARAM_BASE + MAX_CC_CONTROLLER as u32
207 }
208
209 #[inline]
213 pub const fn parameter_id_to_controller(parameter_id: u32) -> Option<u8> {
214 if Self::is_midi_cc_parameter(parameter_id) {
215 Some((parameter_id - MIDI_CC_PARAM_BASE) as u8)
216 } else {
217 None
218 }
219 }
220
221 fn create_parameter_info(controller: u8) -> ParameterInfo {
226 let id = Self::parameter_id(controller);
227
228 let (name, short_name): (&'static str, &'static str) = match controller {
230 controller::PITCH_BEND => ("Pitch Bend", "PB"),
231 controller::AFTERTOUCH => ("Aftertouch", "AT"),
232 1 => ("Mod Wheel", "MW"),
233 2 => ("Breath Controller", "BC"),
234 7 => ("Volume", "Vol"),
235 10 => ("Pan", "Pan"),
236 11 => ("Expression", "Exp"),
237 64 => ("Sustain Pedal", "Sus"),
238 _ => ("MIDI CC", "CC"),
239 };
240
241 let default = if controller == controller::PITCH_BEND {
242 0.5
243 } else {
244 0.0
245 };
246
247 ParameterInfo {
248 id,
249 name,
250 short_name,
251 units: "",
252 unit: ParameterUnit::Generic,
253 default_normalized: default,
254 step_count: 0,
255 flags: ParameterFlags {
256 can_automate: true,
257 is_readonly: false,
258 is_bypass: false,
259 is_list: false,
260 is_hidden: true, },
262 group_id: ROOT_GROUP_ID,
263 }
264 }
265
266 #[inline]
267 fn get_normalized_internal(&self, controller: u8) -> f64 {
268 let idx = controller as usize;
269 if idx < MAX_CC_CONTROLLER {
270 f64::from_bits(self.values[idx].load(Ordering::Relaxed))
271 } else {
272 0.0
273 }
274 }
275
276 fn set_normalized_internal(&self, controller: u8, value: f64) {
277 let idx = controller as usize;
278 if idx < MAX_CC_CONTROLLER {
279 self.values[idx].store(value.clamp(0.0, 1.0).to_bits(), Ordering::Relaxed);
280 }
281 }
282}
283
284unsafe impl Send for MidiCcState {}
286unsafe impl Sync for MidiCcState {}
288
289impl core::fmt::Debug for MidiCcState {
290 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
291 let enabled: Vec<u8> = self
292 .enabled
293 .iter()
294 .enumerate()
295 .filter_map(|(i, &e)| if e { Some(i as u8) } else { None })
296 .collect();
297 f.debug_struct("MidiCcState")
298 .field("enabled_controllers", &enabled)
299 .field("enabled_count", &self.enabled_count)
300 .finish()
301 }
302}
303
304impl ParameterStore for MidiCcState {
309 fn count(&self) -> usize {
310 self.enabled_count
311 }
312
313 fn info(&self, index: usize) -> Option<&ParameterInfo> {
314 self.parameter_infos.get(index).map(|i| &i.info)
315 }
316
317 fn get_normalized(&self, id: ParameterId) -> ParameterValue {
318 if let Some(controller) = Self::parameter_id_to_controller(id) {
319 self.get_normalized_internal(controller)
320 } else {
321 0.0
322 }
323 }
324
325 fn set_normalized(&self, id: ParameterId, value: ParameterValue) {
326 if let Some(controller) = Self::parameter_id_to_controller(id) {
327 self.set_normalized_internal(controller, value);
328 }
329 }
330
331 fn normalized_to_string(&self, id: ParameterId, normalized: ParameterValue) -> String {
332 if let Some(controller) = Self::parameter_id_to_controller(id) {
333 if controller == controller::PITCH_BEND {
334 let semitones = (normalized * 2.0 - 1.0) * 2.0;
337 return format!("{:+.1} st", semitones);
338 }
339 }
340 format!("{:.0}", normalized * 127.0)
341 }
342
343 fn string_to_normalized(&self, _id: ParameterId, string: &str) -> Option<ParameterValue> {
344 if let Ok(v) = string.parse::<f64>() {
346 return Some((v / 127.0).clamp(0.0, 1.0));
347 }
348 if let Some(v) = string.strip_suffix('%') {
350 if let Ok(v) = v.trim().parse::<f64>() {
351 return Some((v / 100.0).clamp(0.0, 1.0));
352 }
353 }
354 None
355 }
356
357 fn normalized_to_plain(&self, _id: ParameterId, normalized: ParameterValue) -> ParameterValue {
358 normalized * 127.0
359 }
360
361 fn plain_to_normalized(&self, _id: ParameterId, plain: ParameterValue) -> ParameterValue {
362 (plain / 127.0).clamp(0.0, 1.0)
363 }
364}
365
366impl ParameterGroups for MidiCcState {
371 fn group_count(&self) -> usize {
372 1 }
374
375 fn group_info(&self, index: usize) -> Option<GroupInfo> {
376 if index == 0 {
377 Some(GroupInfo::root())
378 } else {
379 None
380 }
381 }
382}
383
384#[cfg(test)]
385mod tests {
386 use super::*;
387
388 #[test]
389 fn test_from_config() {
390 let config = MidiCcConfig::new()
391 .with_pitch_bend()
392 .with_mod_wheel()
393 .with_ccs(&[7, 64]);
394
395 let state = MidiCcState::from_config(&config);
396
397 assert!(state.has_pitch_bend());
398 assert!(state.has_controller(1)); assert!(state.has_controller(7)); assert!(state.has_controller(64)); assert!(!state.has_aftertouch());
402 assert_eq!(state.enabled_count(), 4);
403 }
404
405 #[test]
406 fn test_pitch_bend_default() {
407 let config = MidiCcConfig::new().with_pitch_bend();
408 let state = MidiCcState::from_config(&config);
409
410 assert!((state.pitch_bend() - 0.0).abs() < 0.01);
412 }
413
414 #[test]
415 fn test_set_and_get() {
416 let config = MidiCcConfig::new().with_pitch_bend().with_mod_wheel();
417 let state = MidiCcState::from_config(&config);
418
419 let pb_id = MidiCcState::parameter_id(controller::PITCH_BEND);
421 state.set_normalized(pb_id, 1.0);
422 assert!((state.pitch_bend() - 1.0).abs() < 0.01);
423
424 state.set_normalized(pb_id, 0.0);
426 assert!((state.pitch_bend() - (-1.0)).abs() < 0.01);
427
428 let mw_id = MidiCcState::parameter_id(1);
430 state.set_normalized(mw_id, 0.75);
431 assert!((state.mod_wheel() - 0.75).abs() < 0.01);
432 }
433
434 #[test]
435 fn test_parameter_id_helpers() {
436 assert_eq!(MidiCcState::parameter_id(1), MIDI_CC_PARAM_BASE + 1);
437 assert_eq!(MidiCcState::parameter_id(129), MIDI_CC_PARAM_BASE + 129);
438
439 assert!(MidiCcState::is_midi_cc_parameter(MIDI_CC_PARAM_BASE));
440 assert!(MidiCcState::is_midi_cc_parameter(MIDI_CC_PARAM_BASE + 129));
441 assert!(!MidiCcState::is_midi_cc_parameter(0));
442 assert!(!MidiCcState::is_midi_cc_parameter(MIDI_CC_PARAM_BASE + 200));
443
444 assert_eq!(
445 MidiCcState::parameter_id_to_controller(MIDI_CC_PARAM_BASE + 1),
446 Some(1)
447 );
448 assert_eq!(MidiCcState::parameter_id_to_controller(100), None);
449 }
450}