1use std::collections::HashMap;
12use std::fmt::Debug;
13use std::sync::Arc;
14
15use rill_core::prelude::*;
16use rill_core::queues::MpscQueue;
17
18pub use crate::automaton::Range;
19use crate::automaton::{EnvelopeAutomaton, LfoAutomaton, LfoWaveform};
20
21#[derive(Debug, Clone, PartialEq, Eq, Hash)]
27pub enum EventPattern {
28 AnyButton,
30 ButtonId(u32),
32
33 AnyKnob,
35 KnobId(u32),
37
38 AnyFader,
40 FaderId(u32),
42
43 AnyMidi,
45 MidiControl { channel: Option<u8>, controller: u8 },
47 MidiNote {
49 channel: Option<u8>,
50 note: Option<u8>,
51 },
52
53 OscAddress(String),
55
56 OscPattern(String),
58}
59
60impl EventPattern {
61 pub fn matches(&self, event: &ControlEvent) -> bool {
63 match (self, event) {
64 (EventPattern::AnyButton, ControlEvent::Button { .. }) => true,
65 (EventPattern::ButtonId(id), ControlEvent::Button { id: eid, .. }) => *id == *eid,
66
67 (EventPattern::AnyKnob, ControlEvent::Knob { .. }) => true,
68 (EventPattern::KnobId(id), ControlEvent::Knob { id: eid, .. }) => *id == *eid,
69
70 (EventPattern::AnyFader, ControlEvent::Fader { .. }) => true,
71 (EventPattern::FaderId(id), ControlEvent::Fader { id: eid, .. }) => *id == *eid,
72
73 (
74 EventPattern::MidiControl {
75 channel,
76 controller,
77 },
78 ControlEvent::MidiControl {
79 channel: ech,
80 controller: ectr,
81 ..
82 },
83 ) => (channel.is_none() || channel.unwrap() == *ech) && *controller == *ectr,
84
85 (EventPattern::OscAddress(addr), ControlEvent::Osc { address, .. }) => addr == address,
86
87 (EventPattern::OscPattern(pat), ControlEvent::Osc { address, .. }) => {
88 address.contains(pat)
89 }
90
91 _ => false,
92 }
93 }
94}
95
96#[derive(Debug, Clone, PartialEq)]
102pub enum ControlEvent {
103 Button { id: u32, pressed: bool },
105
106 Knob {
108 id: u32,
109 value: f32, normalized: f32, },
112
113 Fader {
115 id: u32,
116 value: f32, normalized: f32,
118 },
119
120 MidiControl {
122 channel: u8,
123 controller: u8,
124 value: u8, normalized: f32, },
127
128 MidiNote {
130 channel: u8,
131 note: u8,
132 velocity: u8,
133 on: bool,
134 },
135
136 Osc { address: String, args: Vec<f32> },
138}
139
140impl ControlEvent {
141 pub fn normalized_value(&self) -> Option<f32> {
143 match self {
144 ControlEvent::Knob { normalized, .. } => Some(*normalized),
145 ControlEvent::Fader { normalized, .. } => Some(*normalized),
146 ControlEvent::MidiControl { normalized, .. } => Some(*normalized),
147 ControlEvent::Button { pressed, .. } => Some(if *pressed { 1.0 } else { 0.0 }),
148 _ => None,
149 }
150 }
151
152 pub fn id(&self) -> Option<u32> {
154 match self {
155 ControlEvent::Button { id, .. } => Some(*id),
156 ControlEvent::Knob { id, .. } => Some(*id),
157 ControlEvent::Fader { id, .. } => Some(*id),
158 _ => None,
159 }
160 }
161}
162
163#[derive(Clone)]
169pub enum Transform {
170 Linear,
172
173 Exponential,
175
176 Logarithmic,
178
179 Inverted,
181
182 Custom(Arc<dyn Fn(f32) -> f32 + Send + Sync>),
184}
185
186impl Debug for Transform {
187 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
188 match self {
189 Transform::Linear => write!(f, "Linear"),
190 Transform::Exponential => write!(f, "Exponential"),
191 Transform::Logarithmic => write!(f, "Logarithmic"),
192 Transform::Inverted => write!(f, "Inverted"),
193 Transform::Custom(_) => write!(f, "Custom"),
194 }
195 }
196}
197
198impl Transform {
199 pub fn apply(&self, value: f32, min: f32, max: f32) -> f32 {
201 let range = max - min;
202 let normalized = value.clamp(0.0, 1.0);
203
204 let mapped = match self {
205 Transform::Linear => min + normalized * range,
206 Transform::Exponential => min + normalized * normalized * range,
207 Transform::Logarithmic => min + (1.0 + normalized * 9.0).log10() * range,
208 Transform::Inverted => max - normalized * range,
209 Transform::Custom(f) => min + f(normalized) * range,
210 };
211
212 mapped.clamp(min, max)
213 }
214}
215
216#[derive(Debug, Clone)]
222pub struct Target {
223 pub node_id: NodeId,
225 pub param_name: String,
227 pub min: f32,
229 pub max: f32,
231}
232
233#[derive(Debug, Clone)]
235pub struct Mapping {
236 pub pattern: EventPattern,
238 pub target: Target,
240 pub transform: Transform,
242 pub name: String,
244 pub enabled: bool,
246}
247
248impl Mapping {
249 pub fn new(pattern: EventPattern, target: Target, transform: Transform) -> Self {
251 let name = format!("{:?} -> {}", pattern, target.param_name);
252 Self {
253 pattern,
254 target,
255 transform,
256 name,
257 enabled: true,
258 }
259 }
260
261 pub fn matches(&self, event: &ControlEvent) -> bool {
263 self.enabled && self.pattern.matches(event)
264 }
265
266 pub fn apply(&self, event: &ControlEvent) -> Option<ParameterCommand> {
268 if !self.matches(event) {
269 return None;
270 }
271
272 event.normalized_value().map(|norm| {
273 let value = self.transform.apply(norm, self.target.min, self.target.max);
274 ParameterCommand {
275 node_id: self.target.node_id,
276 param: self.target.param_name.clone(),
277 value,
278 }
279 })
280 }
281}
282
283pub type Time = f64;
289
290#[derive(Debug, Clone, Default)]
292pub struct NoAction;
293
294pub trait Automaton: Send + Sync + Debug {
301 type State: Clone + Send + Sync + 'static + Debug;
303
304 type Action: Debug + Clone + Send + Sync + Default + 'static;
306
307 fn step(
316 &self,
317 time: Time,
318 action: &Self::Action,
319 state: &Self::State,
320 ) -> (Self::State, Option<f64>);
321
322 fn initial_state(&self) -> Self::State;
324
325 fn name(&self) -> &str;
327
328 fn extract_value(&self, state: &Self::State) -> f64;
330
331 fn reset(&self) -> Self::State {
333 self.initial_state()
334 }
335}
336
337#[derive(Clone)]
347pub enum ParameterMapping {
348 Linear,
349 Exponential,
350 Logarithmic,
351 Inverted,
352 Custom(Arc<dyn Fn(f64) -> f64 + Send + Sync>),
353}
354
355impl std::fmt::Debug for ParameterMapping {
356 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
357 match self {
358 ParameterMapping::Linear => write!(f, "Linear"),
359 ParameterMapping::Exponential => write!(f, "Exponential"),
360 ParameterMapping::Logarithmic => write!(f, "Logarithmic"),
361 ParameterMapping::Inverted => write!(f, "Inverted"),
362 ParameterMapping::Custom(_) => write!(f, "Custom(<fn>)"),
363 }
364 }
365}
366
367impl ParameterMapping {
368 pub fn apply(&self, raw: f64) -> f64 {
369 match self {
370 ParameterMapping::Linear => raw,
371 ParameterMapping::Exponential => raw * raw,
372 ParameterMapping::Logarithmic => (1.0 + raw * 9.0).log10(),
373 ParameterMapping::Inverted => 1.0 - raw,
374 ParameterMapping::Custom(f) => f(raw),
375 }
376 }
377}
378
379pub struct Servo<A: Automaton> {
381 id: String,
383 automaton: A,
385 state: A::State,
387 target_node: NodeId,
389 target_param: String,
391 mapping: ParameterMapping,
393 min: f64,
395 max: f64,
397 last_value: f64,
399 enabled: bool,
401 last_time: Time,
403}
404
405impl<A: Automaton> Servo<A> {
406 pub fn new(
407 id: impl Into<String>,
408 automaton: A,
409 target_node: NodeId,
410 target_param: impl Into<String>,
411 mapping: ParameterMapping,
412 min: f64,
413 max: f64,
414 ) -> Self {
415 let state = automaton.initial_state();
416 Self {
417 id: id.into(),
418 automaton,
419 state,
420 target_node,
421 target_param: target_param.into(),
422 mapping,
423 min,
424 max,
425 last_value: 0.0,
426 enabled: true,
427 last_time: 0.0,
428 }
429 }
430
431 pub fn update(&mut self, time: Time) -> Option<ParameterCommand> {
433 if !self.enabled {
434 return None;
435 }
436
437 let (new_state, value_opt) = self
438 .automaton
439 .step(time, &A::Action::default(), &self.state);
440 self.state = new_state;
441
442 if let Some(raw_value) = value_opt {
443 let mapped = self.mapping.apply(raw_value);
444 let clamped = mapped.clamp(self.min, self.max);
445
446 if (clamped - self.last_value).abs() > 1e-6 {
448 self.last_value = clamped;
449 self.last_time = time;
450
451 return Some(ParameterCommand {
452 node_id: self.target_node,
453 param: self.target_param.clone(),
454 value: clamped as f32,
455 });
456 }
457 }
458
459 None
460 }
461
462 pub fn set_enabled(&mut self, enabled: bool) {
464 self.enabled = enabled;
465 }
466
467 pub fn id(&self) -> &str {
469 &self.id
470 }
471}
472
473pub type BoxedServo = Box<dyn AnyServo>;
475
476pub trait AnyServo: Send + Sync {
477 fn update(&mut self, time: Time) -> Option<ParameterCommand>;
478 fn id(&self) -> &str;
479 fn set_enabled(&mut self, enabled: bool);
480}
481
482impl<A: Automaton + 'static> AnyServo for Servo<A> {
483 fn update(&mut self, time: Time) -> Option<ParameterCommand> {
484 Servo::update(self, time)
485 }
486
487 fn id(&self) -> &str {
488 &self.id
489 }
490
491 fn set_enabled(&mut self, enabled: bool) {
492 self.enabled = enabled;
493 }
494}
495
496#[derive(Debug, Clone)]
502pub struct ParameterCommand {
503 pub node_id: NodeId,
505 pub param: String,
507 pub value: f32,
509}
510
511impl ParameterCommand {
512 pub fn new(node_id: NodeId, param: impl Into<String>, value: f32) -> Self {
514 Self {
515 node_id,
516 param: param.into(),
517 value,
518 }
519 }
520}
521
522pub struct PatchbayControl {
531 mappings: Vec<Mapping>,
533
534 servos: HashMap<String, BoxedServo>,
536
537 command_queue: Arc<MpscQueue<ParameterCommand>>,
539
540 time: Time,
542}
543
544impl PatchbayControl {
545 pub fn new(command_queue: Arc<MpscQueue<ParameterCommand>>) -> Self {
547 Self {
548 mappings: Vec::new(),
549 servos: HashMap::new(),
550 command_queue,
551 time: 0.0,
552 }
553 }
554
555 pub fn add_mapping(&mut self, mapping: Mapping) {
557 self.mappings.push(mapping);
558 }
559
560 pub fn add_mapping_str(
562 &mut self,
563 pattern: &str,
564 target_node: NodeId,
565 target_param: &str,
566 min: f32,
567 max: f32,
568 transform: Transform,
569 ) -> Result<(), &'static str> {
570 let pattern = match pattern {
571 p if p.starts_with("button:") => {
572 let id = p[7..].parse().map_err(|_| "Invalid button ID")?;
573 EventPattern::ButtonId(id)
574 }
575 p if p.starts_with("knob:") => {
576 let id = p[5..].parse().map_err(|_| "Invalid knob ID")?;
577 EventPattern::KnobId(id)
578 }
579 p if p.starts_with("fader:") => {
580 let id = p[6..].parse().map_err(|_| "Invalid fader ID")?;
581 EventPattern::FaderId(id)
582 }
583 p if p.starts_with("midi:") => {
584 let parts: Vec<&str> = p[5..].split(':').collect();
585 if parts.len() == 2 {
586 let channel = parts[0].parse().ok();
587 let controller = parts[1].parse().map_err(|_| "Invalid controller")?;
588 EventPattern::MidiControl {
589 channel,
590 controller,
591 }
592 } else {
593 EventPattern::AnyMidi
594 }
595 }
596 p if p.starts_with("osc:") => EventPattern::OscAddress(p[4..].to_string()),
597 _ => return Err("Unknown pattern"),
598 };
599
600 let target = Target {
601 node_id: target_node,
602 param_name: target_param.to_string(),
603 min,
604 max,
605 };
606
607 self.add_mapping(Mapping::new(pattern, target, transform));
608 Ok(())
609 }
610
611 pub fn add_servo<A: Automaton + 'static>(&mut self, servo: Servo<A>) {
613 self.servos.insert(servo.id().to_string(), Box::new(servo));
614 }
615
616 pub fn add_lfo(
618 &mut self,
619 id: &str,
620 frequency: f64,
621 amplitude: f64,
622 offset: f64,
623 waveform: LfoWaveform,
624 target_node: NodeId,
625 target_param: &str,
626 min: f64,
627 max: f64,
628 ) {
629 let automaton = LfoAutomaton::new(id, frequency, amplitude, offset, waveform);
630 let servo = Servo::new(
631 id,
632 automaton,
633 target_node,
634 target_param,
635 ParameterMapping::Linear,
636 min,
637 max,
638 );
639 self.add_servo(servo);
640 }
641
642 pub fn add_envelope(
644 &mut self,
645 id: &str,
646 attack: f64,
647 decay: f64,
648 sustain: f64,
649 release: f64,
650 target_node: NodeId,
651 target_param: &str,
652 min: f64,
653 max: f64,
654 ) {
655 let automaton = EnvelopeAutomaton::adsr(id, attack, decay, sustain, release);
656 let servo = Servo::new(
657 id,
658 automaton,
659 target_node,
660 target_param,
661 ParameterMapping::Linear,
662 min,
663 max,
664 );
665 self.add_servo(servo);
666 }
667
668 pub fn handle_event(&mut self, event: ControlEvent) {
670 for mapping in &self.mappings {
671 if let Some(cmd) = mapping.apply(&event) {
672 let _ = self.command_queue.push(cmd);
673 }
674 }
675 }
676
677 pub fn update(&mut self, dt: f32) {
679 self.time += dt as f64;
680
681 for servo in self.servos.values_mut() {
683 if let Some(cmd) = servo.update(self.time) {
684 let _ = self.command_queue.push(cmd);
685 }
686 }
687 }
688
689 pub fn mappings(&self) -> &[Mapping] {
691 &self.mappings
692 }
693
694 pub fn get_servo(&self, id: &str) -> Option<&dyn AnyServo> {
696 self.servos.get(id).map(|b| b.as_ref())
697 }
698
699 pub fn get_servo_mut(&mut self, id: &str) -> Option<&mut BoxedServo> {
701 self.servos.get_mut(id)
702 }
703
704 pub fn remove_servo(&mut self, id: &str) -> bool {
706 self.servos.remove(id).is_some()
707 }
708
709 pub fn clear(&mut self) {
711 self.mappings.clear();
712 self.servos.clear();
713 }
714
715 pub fn reset_time(&mut self) {
717 self.time = 0.0;
718 }
719
720 pub fn current_time(&self) -> Time {
722 self.time
723 }
724}
725
726pub fn midi_cc(
732 controller: u8,
733 channel: Option<u8>,
734 target_node: NodeId,
735 target_param: &str,
736 min: f32,
737 max: f32,
738 transform: Transform,
739) -> Mapping {
740 let pattern = EventPattern::MidiControl {
741 channel,
742 controller,
743 };
744 let target = Target {
745 node_id: target_node,
746 param_name: target_param.to_string(),
747 min,
748 max,
749 };
750 Mapping::new(pattern, target, transform)
751}
752
753pub fn osc_address(
755 address: &str,
756 target_node: NodeId,
757 target_param: &str,
758 min: f32,
759 max: f32,
760 transform: Transform,
761) -> Mapping {
762 let pattern = EventPattern::OscAddress(address.to_string());
763 let target = Target {
764 node_id: target_node,
765 param_name: target_param.to_string(),
766 min,
767 max,
768 };
769 Mapping::new(pattern, target, transform)
770}
771
772#[cfg(test)]
777mod tests {
778 use super::*;
779 use rill_core::queues::MpscQueue;
780
781 #[test]
782 fn test_midi_mapping() {
783 let node = NodeId(1);
784 let mapping = midi_cc(7, Some(1), node, "volume", 0.0, 1.0, Transform::Linear);
785
786 let event = ControlEvent::MidiControl {
787 channel: 1,
788 controller: 7,
789 value: 64,
790 normalized: 0.5,
791 };
792
793 assert!(mapping.matches(&event));
794
795 let cmd = mapping.apply(&event).unwrap();
796 assert_eq!(cmd.node_id, node);
797 assert_eq!(cmd.param, "volume");
798 assert!((cmd.value - 0.5).abs() < 1e-6);
799 }
800
801 #[test]
802 fn test_lfo_servo() {
803 let node = NodeId(1);
804 let queue = Arc::new(MpscQueue::with_capacity(64));
805 let mut control = PatchbayControl::new(queue);
806
807 control.add_lfo(
808 "test_lfo",
809 1.0,
810 0.5,
811 0.0,
812 LfoWaveform::Sine,
813 node,
814 "cutoff",
815 100.0,
816 1000.0,
817 );
818
819 assert!(control.get_servo("test_lfo").is_some());
820
821 for _i in 0..10 {
823 control.update(0.1);
824 }
825 }
826
827 #[test]
828 fn test_envelope_servo() {
829 let node = NodeId(1);
830 let queue = Arc::new(MpscQueue::with_capacity(64));
831 let mut control = PatchbayControl::new(queue.clone());
832
833 control.add_envelope("test_env", 0.1, 0.2, 0.7, 0.3, node, "gain", 0.0, 1.0);
834
835 if let Some(_servo) = control.get_servo_mut("test_env") {
837 }
840
841 control.update(0.05);
842 control.update(0.05);
843
844 }
847}