1use std::sync::atomic::{AtomicU64, Ordering};
12
13use crate::midi_cc_config::{controller, MidiCcConfig, MAX_CC_CONTROLLER};
14use crate::params::{ParamFlags, ParamInfo, Parameters, UnitInfo, Units, ROOT_UNIT_ID};
15use crate::types::{ParamId, ParamValue};
16
17pub const MIDI_CC_PARAM_BASE: u32 = 0x10000000; pub struct MidiCcState {
52 enabled: [bool; MAX_CC_CONTROLLER],
54 values: [AtomicU64; MAX_CC_CONTROLLER],
56 param_infos: Vec<CcParamInfo>,
58 enabled_count: usize,
60}
61
62struct CcParamInfo {
64 controller: u8,
65 info: ParamInfo,
66}
67
68impl MidiCcState {
69 pub fn from_config(config: &MidiCcConfig) -> Self {
73 let values = std::array::from_fn(|i| {
75 let default: f64 = if i == controller::PITCH_BEND as usize {
76 0.5 } else {
78 0.0
79 };
80 AtomicU64::new(default.to_bits())
81 });
82
83 let enabled = *config.enabled_flags();
85 let mut param_infos = Vec::new();
86 let mut enabled_count = 0;
87
88 for (i, &is_enabled) in enabled.iter().enumerate() {
89 if is_enabled {
90 enabled_count += 1;
91 let controller = i as u8;
92 let info = Self::create_param_info(controller);
93 param_infos.push(CcParamInfo { controller, info });
94 }
95 }
96
97 Self {
98 enabled,
99 values,
100 param_infos,
101 enabled_count,
102 }
103 }
104
105 #[inline]
113 pub fn pitch_bend(&self) -> f32 {
114 if self.enabled[controller::PITCH_BEND as usize] {
115 let normalized = self.get_normalized_internal(controller::PITCH_BEND);
116 (normalized * 2.0 - 1.0) as f32
117 } else {
118 0.0
119 }
120 }
121
122 #[inline]
126 pub fn aftertouch(&self) -> f32 {
127 if self.enabled[controller::AFTERTOUCH as usize] {
128 self.get_normalized_internal(controller::AFTERTOUCH) as f32
129 } else {
130 0.0
131 }
132 }
133
134 #[inline]
138 pub fn mod_wheel(&self) -> f32 {
139 self.cc(1)
140 }
141
142 #[inline]
146 pub fn cc(&self, cc: u8) -> f32 {
147 if (cc as usize) < MAX_CC_CONTROLLER && self.enabled[cc as usize] {
148 self.get_normalized_internal(cc) as f32
149 } else {
150 0.0
151 }
152 }
153
154 #[inline]
160 pub fn has_controller(&self, controller: u8) -> bool {
161 if (controller as usize) < MAX_CC_CONTROLLER {
162 self.enabled[controller as usize]
163 } else {
164 false
165 }
166 }
167
168 #[inline]
170 pub fn has_pitch_bend(&self) -> bool {
171 self.enabled[controller::PITCH_BEND as usize]
172 }
173
174 #[inline]
176 pub fn has_aftertouch(&self) -> bool {
177 self.enabled[controller::AFTERTOUCH as usize]
178 }
179
180 #[inline]
182 pub fn enabled_count(&self) -> usize {
183 self.enabled_count
184 }
185
186 pub fn enabled_controllers(&self) -> impl Iterator<Item = u8> + '_ {
188 self.param_infos.iter().map(|info| info.controller)
189 }
190
191 #[inline]
197 pub const fn param_id(controller: u8) -> u32 {
198 MIDI_CC_PARAM_BASE + controller as u32
199 }
200
201 #[inline]
203 pub const fn is_midi_cc_param(param_id: u32) -> bool {
204 param_id >= MIDI_CC_PARAM_BASE && param_id < MIDI_CC_PARAM_BASE + MAX_CC_CONTROLLER as u32
205 }
206
207 #[inline]
211 pub const fn param_id_to_controller(param_id: u32) -> Option<u8> {
212 if Self::is_midi_cc_param(param_id) {
213 Some((param_id - MIDI_CC_PARAM_BASE) as u8)
214 } else {
215 None
216 }
217 }
218
219 fn create_param_info(controller: u8) -> ParamInfo {
224 let id = Self::param_id(controller);
225
226 let (name, short_name): (&'static str, &'static str) = match controller {
228 controller::PITCH_BEND => ("Pitch Bend", "PB"),
229 controller::AFTERTOUCH => ("Aftertouch", "AT"),
230 1 => ("Mod Wheel", "MW"),
231 2 => ("Breath Controller", "BC"),
232 7 => ("Volume", "Vol"),
233 10 => ("Pan", "Pan"),
234 11 => ("Expression", "Exp"),
235 64 => ("Sustain Pedal", "Sus"),
236 _ => ("MIDI CC", "CC"),
237 };
238
239 let default = if controller == controller::PITCH_BEND {
240 0.5
241 } else {
242 0.0
243 };
244
245 ParamInfo {
246 id,
247 name,
248 short_name,
249 units: "",
250 default_normalized: default,
251 step_count: 0,
252 flags: ParamFlags {
253 can_automate: true,
254 is_readonly: false,
255 is_bypass: false,
256 is_list: false,
257 is_hidden: true, },
259 unit_id: ROOT_UNIT_ID,
260 }
261 }
262
263 #[inline]
264 fn get_normalized_internal(&self, controller: u8) -> f64 {
265 let idx = controller as usize;
266 if idx < MAX_CC_CONTROLLER {
267 f64::from_bits(self.values[idx].load(Ordering::Relaxed))
268 } else {
269 0.0
270 }
271 }
272
273 fn set_normalized_internal(&self, controller: u8, value: f64) {
274 let idx = controller as usize;
275 if idx < MAX_CC_CONTROLLER {
276 self.values[idx].store(value.clamp(0.0, 1.0).to_bits(), Ordering::Relaxed);
277 }
278 }
279}
280
281unsafe impl Send for MidiCcState {}
283unsafe impl Sync for MidiCcState {}
284
285impl core::fmt::Debug for MidiCcState {
286 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
287 let enabled: Vec<u8> = self
288 .enabled
289 .iter()
290 .enumerate()
291 .filter_map(|(i, &e)| if e { Some(i as u8) } else { None })
292 .collect();
293 f.debug_struct("MidiCcState")
294 .field("enabled_controllers", &enabled)
295 .field("enabled_count", &self.enabled_count)
296 .finish()
297 }
298}
299
300impl Parameters for MidiCcState {
305 fn count(&self) -> usize {
306 self.enabled_count
307 }
308
309 fn info(&self, index: usize) -> Option<&ParamInfo> {
310 self.param_infos.get(index).map(|i| &i.info)
311 }
312
313 fn get_normalized(&self, id: ParamId) -> ParamValue {
314 if let Some(controller) = Self::param_id_to_controller(id) {
315 self.get_normalized_internal(controller)
316 } else {
317 0.0
318 }
319 }
320
321 fn set_normalized(&self, id: ParamId, value: ParamValue) {
322 if let Some(controller) = Self::param_id_to_controller(id) {
323 self.set_normalized_internal(controller, value);
324 }
325 }
326
327 fn normalized_to_string(&self, id: ParamId, normalized: ParamValue) -> String {
328 if let Some(controller) = Self::param_id_to_controller(id) {
329 if controller == controller::PITCH_BEND {
330 let semitones = (normalized * 2.0 - 1.0) * 2.0;
333 return format!("{:+.1} st", semitones);
334 }
335 }
336 format!("{:.0}", normalized * 127.0)
337 }
338
339 fn string_to_normalized(&self, _id: ParamId, string: &str) -> Option<ParamValue> {
340 if let Ok(v) = string.parse::<f64>() {
342 return Some((v / 127.0).clamp(0.0, 1.0));
343 }
344 if let Some(v) = string.strip_suffix('%') {
346 if let Ok(v) = v.trim().parse::<f64>() {
347 return Some((v / 100.0).clamp(0.0, 1.0));
348 }
349 }
350 None
351 }
352
353 fn normalized_to_plain(&self, _id: ParamId, normalized: ParamValue) -> ParamValue {
354 normalized * 127.0
355 }
356
357 fn plain_to_normalized(&self, _id: ParamId, plain: ParamValue) -> ParamValue {
358 (plain / 127.0).clamp(0.0, 1.0)
359 }
360}
361
362impl Units for MidiCcState {
367 fn unit_count(&self) -> usize {
368 1 }
370
371 fn unit_info(&self, index: usize) -> Option<UnitInfo> {
372 if index == 0 {
373 Some(UnitInfo::root())
374 } else {
375 None
376 }
377 }
378}
379
380#[cfg(test)]
381mod tests {
382 use super::*;
383
384 #[test]
385 fn test_from_config() {
386 let config = MidiCcConfig::new()
387 .with_pitch_bend()
388 .with_mod_wheel()
389 .with_ccs(&[7, 64]);
390
391 let state = MidiCcState::from_config(&config);
392
393 assert!(state.has_pitch_bend());
394 assert!(state.has_controller(1)); assert!(state.has_controller(7)); assert!(state.has_controller(64)); assert!(!state.has_aftertouch());
398 assert_eq!(state.enabled_count(), 4);
399 }
400
401 #[test]
402 fn test_pitch_bend_default() {
403 let config = MidiCcConfig::new().with_pitch_bend();
404 let state = MidiCcState::from_config(&config);
405
406 assert!((state.pitch_bend() - 0.0).abs() < 0.01);
408 }
409
410 #[test]
411 fn test_set_and_get() {
412 let config = MidiCcConfig::new().with_pitch_bend().with_mod_wheel();
413 let state = MidiCcState::from_config(&config);
414
415 let pb_id = MidiCcState::param_id(controller::PITCH_BEND);
417 state.set_normalized(pb_id, 1.0);
418 assert!((state.pitch_bend() - 1.0).abs() < 0.01);
419
420 state.set_normalized(pb_id, 0.0);
422 assert!((state.pitch_bend() - (-1.0)).abs() < 0.01);
423
424 let mw_id = MidiCcState::param_id(1);
426 state.set_normalized(mw_id, 0.75);
427 assert!((state.mod_wheel() - 0.75).abs() < 0.01);
428 }
429
430 #[test]
431 fn test_param_id_helpers() {
432 assert_eq!(MidiCcState::param_id(1), MIDI_CC_PARAM_BASE + 1);
433 assert_eq!(MidiCcState::param_id(129), MIDI_CC_PARAM_BASE + 129);
434
435 assert!(MidiCcState::is_midi_cc_param(MIDI_CC_PARAM_BASE));
436 assert!(MidiCcState::is_midi_cc_param(MIDI_CC_PARAM_BASE + 129));
437 assert!(!MidiCcState::is_midi_cc_param(0));
438 assert!(!MidiCcState::is_midi_cc_param(MIDI_CC_PARAM_BASE + 200));
439
440 assert_eq!(
441 MidiCcState::param_id_to_controller(MIDI_CC_PARAM_BASE + 1),
442 Some(1)
443 );
444 assert_eq!(MidiCcState::param_id_to_controller(100), None);
445 }
446}