1use std::sync::atomic::{AtomicU64, Ordering};
44
45use crate::params::{ParamFlags, ParamInfo, Parameters, Units, ROOT_UNIT_ID};
46use crate::types::{ParamId, ParamValue};
47
48pub const MIDI_CC_PARAM_BASE: u32 = 0x10000000; const MAX_CONTROLLER: usize = 130;
64
65pub mod controller {
67 pub const AFTERTOUCH: u8 = 128;
69 pub const PITCH_BEND: u8 = 129;
71}
72
73pub struct MidiCcParams {
93 enabled: [bool; MAX_CONTROLLER],
95 values: [AtomicU64; MAX_CONTROLLER],
97 param_infos: Vec<CcParamInfo>,
99 enabled_count: usize,
101}
102
103struct CcParamInfo {
105 controller: u8,
106 info: ParamInfo,
107}
108
109impl MidiCcParams {
110 pub fn new() -> Self {
114 let values = std::array::from_fn(|_| AtomicU64::new(0));
116
117 Self {
118 enabled: [false; MAX_CONTROLLER],
119 values,
120 param_infos: Vec::new(),
121 enabled_count: 0,
122 }
123 }
124
125 pub fn with_pitch_bend(mut self) -> Self {
134 self.enable_controller(controller::PITCH_BEND);
135 self.values[controller::PITCH_BEND as usize]
137 .store(0.5f64.to_bits(), Ordering::Relaxed);
138 self
139 }
140
141 pub fn with_aftertouch(mut self) -> Self {
143 self.enable_controller(controller::AFTERTOUCH);
144 self
145 }
146
147 pub fn with_mod_wheel(mut self) -> Self {
149 self.enable_controller(1);
150 self
151 }
152
153 pub fn with_cc(mut self, cc: u8) -> Self {
155 if cc < 128 {
156 self.enable_controller(cc);
157 }
158 self
159 }
160
161 pub fn with_ccs(mut self, ccs: &[u8]) -> Self {
163 for &cc in ccs {
164 if cc < 128 {
165 self.enable_controller(cc);
166 }
167 }
168 self
169 }
170
171 pub fn with_all_ccs(mut self) -> Self {
175 for cc in 0..128 {
176 self.enable_controller(cc);
177 }
178 self
179 }
180
181 pub fn has_controller(&self, controller: u8) -> bool {
187 if (controller as usize) < MAX_CONTROLLER {
188 self.enabled[controller as usize]
189 } else {
190 false
191 }
192 }
193
194 pub fn has_pitch_bend(&self) -> bool {
196 self.has_controller(controller::PITCH_BEND)
197 }
198
199 pub fn has_aftertouch(&self) -> bool {
201 self.has_controller(controller::AFTERTOUCH)
202 }
203
204 pub fn pitch_bend(&self) -> f32 {
208 if self.has_pitch_bend() {
209 let normalized = self.get_normalized_internal(controller::PITCH_BEND);
210 (normalized * 2.0 - 1.0) as f32
211 } else {
212 0.0
213 }
214 }
215
216 pub fn aftertouch(&self) -> f32 {
220 if self.has_aftertouch() {
221 self.get_normalized_internal(controller::AFTERTOUCH) as f32
222 } else {
223 0.0
224 }
225 }
226
227 pub fn cc(&self, cc: u8) -> f32 {
231 if cc < 128 && self.has_controller(cc) {
232 self.get_normalized_internal(cc) as f32
233 } else {
234 0.0
235 }
236 }
237
238 pub fn mod_wheel(&self) -> f32 {
240 self.cc(1)
241 }
242
243 pub fn enabled_count(&self) -> usize {
245 self.enabled_count
246 }
247
248 pub fn enabled_controllers(&self) -> impl Iterator<Item = u8> + '_ {
250 self.param_infos.iter().map(|info| info.controller)
251 }
252
253 #[inline]
259 pub const fn param_id(controller: u8) -> u32 {
260 MIDI_CC_PARAM_BASE + controller as u32
261 }
262
263 #[inline]
265 pub const fn is_midi_cc_param(param_id: u32) -> bool {
266 param_id >= MIDI_CC_PARAM_BASE && param_id < MIDI_CC_PARAM_BASE + MAX_CONTROLLER as u32
267 }
268
269 #[inline]
273 pub const fn param_id_to_controller(param_id: u32) -> Option<u8> {
274 if Self::is_midi_cc_param(param_id) {
275 Some((param_id - MIDI_CC_PARAM_BASE) as u8)
276 } else {
277 None
278 }
279 }
280
281 fn enable_controller(&mut self, controller: u8) {
286 let idx = controller as usize;
287 if idx < MAX_CONTROLLER && !self.enabled[idx] {
288 self.enabled[idx] = true;
289 self.enabled_count += 1;
290
291 let info = self.create_param_info(controller);
293 self.param_infos.push(CcParamInfo { controller, info });
294 }
295 }
296
297 fn create_param_info(&self, controller: u8) -> ParamInfo {
298 let id = Self::param_id(controller);
299
300 let (name, short_name): (&'static str, &'static str) = match controller {
302 controller::PITCH_BEND => ("Pitch Bend", "PB"),
303 controller::AFTERTOUCH => ("Aftertouch", "AT"),
304 1 => ("Mod Wheel", "MW"),
305 2 => ("Breath Controller", "BC"),
306 7 => ("Volume", "Vol"),
307 10 => ("Pan", "Pan"),
308 11 => ("Expression", "Exp"),
309 64 => ("Sustain Pedal", "Sus"),
310 _ => ("MIDI CC", "CC"),
311 };
312
313 let default = if controller == controller::PITCH_BEND { 0.5 } else { 0.0 };
314
315 ParamInfo {
316 id,
317 name,
318 short_name,
319 units: "",
320 default_normalized: default,
321 step_count: 0,
322 flags: ParamFlags {
323 can_automate: true,
324 is_readonly: false,
325 is_bypass: false,
326 is_list: false,
327 is_hidden: true, },
329 unit_id: ROOT_UNIT_ID,
330 }
331 }
332
333 fn get_normalized_internal(&self, controller: u8) -> f64 {
334 let idx = controller as usize;
335 if idx < MAX_CONTROLLER {
336 f64::from_bits(self.values[idx].load(Ordering::Relaxed))
337 } else {
338 0.0
339 }
340 }
341
342 fn set_normalized_internal(&self, controller: u8, value: f64) {
343 let idx = controller as usize;
344 if idx < MAX_CONTROLLER {
345 self.values[idx].store(value.clamp(0.0, 1.0).to_bits(), Ordering::Relaxed);
346 }
347 }
348}
349
350impl Default for MidiCcParams {
351 fn default() -> Self {
352 Self::new()
353 }
354}
355
356unsafe impl Send for MidiCcParams {}
358unsafe impl Sync for MidiCcParams {}
359
360impl Parameters for MidiCcParams {
365 fn count(&self) -> usize {
366 self.enabled_count
367 }
368
369 fn info(&self, index: usize) -> Option<&ParamInfo> {
370 self.param_infos.get(index).map(|i| &i.info)
371 }
372
373 fn get_normalized(&self, id: ParamId) -> ParamValue {
374 if let Some(controller) = Self::param_id_to_controller(id) {
375 self.get_normalized_internal(controller)
376 } else {
377 0.0
378 }
379 }
380
381 fn set_normalized(&self, id: ParamId, value: ParamValue) {
382 if let Some(controller) = Self::param_id_to_controller(id) {
383 self.set_normalized_internal(controller, value);
384 }
385 }
386
387 fn normalized_to_string(&self, id: ParamId, normalized: ParamValue) -> String {
388 if let Some(controller) = Self::param_id_to_controller(id) {
389 if controller == controller::PITCH_BEND {
390 let bend = (normalized * 2.0 - 1.0) * 100.0;
391 return format!("{:+.0}%", bend);
392 }
393 }
394 format!("{:.0}", normalized * 127.0)
395 }
396
397 fn string_to_normalized(&self, _id: ParamId, string: &str) -> Option<ParamValue> {
398 if let Ok(v) = string.parse::<f64>() {
400 return Some((v / 127.0).clamp(0.0, 1.0));
401 }
402 if let Some(v) = string.strip_suffix('%') {
404 if let Ok(v) = v.trim().parse::<f64>() {
405 return Some((v / 100.0).clamp(0.0, 1.0));
406 }
407 }
408 None
409 }
410
411 fn normalized_to_plain(&self, _id: ParamId, normalized: ParamValue) -> ParamValue {
412 normalized * 127.0
413 }
414
415 fn plain_to_normalized(&self, _id: ParamId, plain: ParamValue) -> ParamValue {
416 (plain / 127.0).clamp(0.0, 1.0)
417 }
418}
419
420impl Units for MidiCcParams {
425 fn unit_count(&self) -> usize {
426 1 }
428
429 fn unit_info(&self, index: usize) -> Option<crate::params::UnitInfo> {
430 if index == 0 {
431 Some(crate::params::UnitInfo::root())
432 } else {
433 None
434 }
435 }
436}
437
438#[cfg(test)]
439mod tests {
440 use super::*;
441
442 #[test]
443 fn test_builder() {
444 let params = MidiCcParams::new()
445 .with_pitch_bend()
446 .with_mod_wheel()
447 .with_ccs(&[7, 64]);
448
449 assert!(params.has_pitch_bend());
450 assert!(!params.has_aftertouch());
451 assert!(params.has_controller(1)); assert!(params.has_controller(7)); assert!(params.has_controller(64)); assert!(!params.has_controller(10)); assert_eq!(params.enabled_count(), 4);
456 }
457
458 #[test]
459 fn test_param_id() {
460 assert_eq!(MidiCcParams::param_id(1), MIDI_CC_PARAM_BASE + 1);
461 assert_eq!(MidiCcParams::param_id(129), MIDI_CC_PARAM_BASE + 129);
462
463 assert!(MidiCcParams::is_midi_cc_param(MIDI_CC_PARAM_BASE));
464 assert!(MidiCcParams::is_midi_cc_param(MIDI_CC_PARAM_BASE + 129));
465 assert!(!MidiCcParams::is_midi_cc_param(0));
466 assert!(!MidiCcParams::is_midi_cc_param(MIDI_CC_PARAM_BASE + 200));
467
468 assert_eq!(MidiCcParams::param_id_to_controller(MIDI_CC_PARAM_BASE + 1), Some(1));
469 assert_eq!(MidiCcParams::param_id_to_controller(100), None);
470 }
471
472 #[test]
473 fn test_values() {
474 let params = MidiCcParams::new()
475 .with_pitch_bend()
476 .with_mod_wheel();
477
478 assert!((params.pitch_bend() - 0.0).abs() < 0.01);
480
481 let pb_id = MidiCcParams::param_id(controller::PITCH_BEND);
483 Parameters::set_normalized(¶ms, pb_id, 1.0);
484 assert!((params.pitch_bend() - 1.0).abs() < 0.01);
485
486 Parameters::set_normalized(¶ms, pb_id, 0.0);
487 assert!((params.pitch_bend() - (-1.0)).abs() < 0.01);
488 }
489}