1use rill_core::prelude::*;
18use rill_core::queues::MpscQueue;
19use std::collections::HashMap;
20use std::sync::atomic::{AtomicBool, Ordering};
21use std::sync::Arc;
22use std::time::{Duration, Instant};
23
24use crate::automaton::{
25 EnvelopeAutomaton, FunctionAutomaton, LfoAutomaton, LfoWaveform, SequencerAutomaton, Step,
26};
27use crate::control::{
28 midi_cc, osc_address, AnyServo, Automaton, BoxedServo, ControlEvent, EventPattern, Mapping,
29 ParameterCommand, ParameterMapping, Transform,
30};
31
32#[derive(Debug, Clone)]
38pub enum PatchbayEvent {
39 AutomatonUpdated {
41 id: String,
43 value: f64,
45 time: f64,
47 },
48 MappingTriggered {
50 pattern: String,
52 target: String,
54 value: f32,
56 },
57 CommandSent(ParameterCommand),
59 Error(String),
61}
62
63#[derive(Debug, Clone, Default)]
69pub struct PatchbayStats {
70 pub automaton_count: usize,
72 pub mapping_count: usize,
74 pub commands_sent: u64,
76 pub last_update: Option<Duration>,
78 pub avg_update_time_us: f64,
80 pub max_update_time_us: f64,
82 pub error_count: u64,
84}
85
86impl PatchbayStats {
87 pub fn update(&mut self, update_duration: Duration) {
89 let us = update_duration.as_micros() as f64;
90 self.avg_update_time_us = self.avg_update_time_us * 0.9 + us * 0.1;
91 self.max_update_time_us = self.max_update_time_us.max(us);
92 self.last_update = Some(update_duration);
93 }
94}
95
96#[derive(Debug, Clone)]
102pub struct PatchbayConfig {
103 pub update_rate_hz: f64,
105 pub command_queue_size: usize,
107 pub collect_stats: bool,
109 pub log_events: bool,
111}
112
113impl Default for PatchbayConfig {
114 fn default() -> Self {
115 Self {
116 update_rate_hz: 1000.0,
117 command_queue_size: 1024,
118 collect_stats: true,
119 log_events: false,
120 }
121 }
122}
123
124pub struct PatchbayManager {
133 config: PatchbayConfig,
134 automata: HashMap<String, Box<dyn std::any::Any + Send>>,
135 automaton_states: HashMap<String, Box<dyn std::any::Any + Send>>,
136 servos: HashMap<String, BoxedServo>,
137 mappings: Vec<Mapping>,
138 command_queue: Arc<MpscQueue<ParameterCommand>>,
139 event_tx: Option<crossbeam_channel::Sender<PatchbayEvent>>,
140 time: f64,
141 stats: PatchbayStats,
142 running: Arc<AtomicBool>,
143 update_thread: Option<std::thread::JoinHandle<()>>,
144}
145
146impl PatchbayManager {
147 pub fn new(config: PatchbayConfig, command_queue: Arc<MpscQueue<ParameterCommand>>) -> Self {
149 Self {
150 config,
151 automata: HashMap::new(),
152 automaton_states: HashMap::new(),
153 servos: HashMap::new(),
154 mappings: Vec::new(),
155 command_queue,
156 event_tx: None,
157 time: 0.0,
158 stats: PatchbayStats::default(),
159 running: Arc::new(AtomicBool::new(false)),
160 update_thread: None,
161 }
162 }
163
164 pub fn with_event_channel(mut self, tx: crossbeam_channel::Sender<PatchbayEvent>) -> Self {
166 self.event_tx = Some(tx);
167 self
168 }
169
170 pub fn add_automaton<A: Automaton + 'static>(
180 &mut self,
181 id: impl Into<String>,
182 automaton: A,
183 ) -> Result<(), &'static str>
184 where
185 A::State: 'static,
186 {
187 let id = id.into();
188 if self.automata.contains_key(&id) {
189 return Err("Automaton with this ID already exists");
190 }
191
192 let state = automaton.initial_state();
193 self.automata.insert(
194 id.clone(),
195 Box::new(automaton) as Box<dyn std::any::Any + Send>,
196 );
197 self.automaton_states.insert(id, Box::new(state));
198
199 Ok(())
200 }
201
202 pub fn add_lfo(
208 &mut self,
209 id: impl Into<String>,
210 frequency: f64,
211 amplitude: f64,
212 offset: f64,
213 waveform: LfoWaveform,
214 ) -> Result<(), &'static str> {
215 let id_str = id.into();
216 let automaton = LfoAutomaton::new(&id_str, frequency, amplitude, offset, waveform);
217 self.add_automaton(id_str, automaton)
218 }
219
220 pub fn add_envelope(
226 &mut self,
227 id: impl Into<String>,
228 attack: f64,
229 decay: f64,
230 sustain: f64,
231 release: f64,
232 ) -> Result<(), &'static str> {
233 let id_str = id.into();
234 let automaton = EnvelopeAutomaton::adsr(&id_str, attack, decay, sustain, release);
235 self.add_automaton(id_str, automaton)
236 }
237
238 pub fn add_sequencer(
244 &mut self,
245 id: impl Into<String>,
246 steps: Vec<Step>,
247 ) -> Result<(), &'static str> {
248 let id_str = id.into();
249 let automaton = SequencerAutomaton::new(&id_str, steps);
250 self.add_automaton(id_str, automaton)
251 }
252
253 pub fn add_function<F>(
259 &mut self,
260 id: impl Into<String>,
261 generator: F,
262 ) -> Result<(), &'static str>
263 where
264 F: Fn(f64) -> f64 + Send + Sync + 'static,
265 {
266 let id_str = id.into();
267 let automaton = FunctionAutomaton::new(&id_str, generator);
268 self.add_automaton(id_str, automaton)
269 }
270
271 pub fn reset_automaton<A: Automaton + 'static>(
277 &mut self,
278 id: &str,
279 ) -> Result<(), &'static str> {
280 let automaton = self
281 .automata
282 .get(id)
283 .and_then(|a| a.downcast_ref::<A>())
284 .ok_or("Automaton not found or type mismatch")?;
285 let state = automaton.initial_state();
286 self.automaton_states
287 .insert(id.to_string(), Box::new(state));
288 Ok(())
289 }
290
291 pub fn remove_automaton(&mut self, id: &str) -> bool {
293 self.automata.remove(id).is_some() && self.automaton_states.remove(id).is_some()
294 }
295
296 pub fn add_servo(
306 &mut self,
307 id: impl Into<String>,
308 automaton_id: impl Into<String>,
309 target_node: NodeId,
310 target_param: impl Into<String>,
311 _mapping: ParameterMapping,
312 _min: f64,
313 _max: f64,
314 ) -> Result<(), &'static str> {
315 let id_str = id.into();
316 let automaton_id_str = automaton_id.into();
317 let target_param_str = target_param.into();
318 let _automaton = self
319 .automata
320 .get(&automaton_id_str)
321 .ok_or("Automaton not found")?;
322
323 let servo = Box::new(TestServo {
324 id: id_str.clone(),
325 target_node,
326 target_param: target_param_str,
327 last_value: 0.0,
328 });
329
330 self.servos.insert(id_str, servo);
331
332 Ok(())
333 }
334
335 pub fn add_lfo_servo(
341 &mut self,
342 id: impl Into<String>,
343 frequency: f64,
344 amplitude: f64,
345 offset: f64,
346 waveform: LfoWaveform,
347 target_node: NodeId,
348 target_param: impl Into<String>,
349 min: f64,
350 max: f64,
351 ) -> Result<(), &'static str> {
352 let id_str = id.into();
353 let automaton_id = format!("{}_auto", &id_str);
354 self.add_lfo(&automaton_id, frequency, amplitude, offset, waveform)?;
355 self.add_servo(
356 id_str,
357 automaton_id,
358 target_node,
359 target_param,
360 ParameterMapping::Linear,
361 min,
362 max,
363 )
364 }
365
366 pub fn get_servo(&self, id: &str) -> Option<&dyn AnyServo> {
368 self.servos.get(id).map(|b| b.as_ref())
369 }
370
371 pub fn get_servo_mut(&mut self, id: &str) -> Option<&mut BoxedServo> {
373 self.servos.get_mut(id)
374 }
375
376 pub fn remove_servo(&mut self, id: &str) -> bool {
378 self.servos.remove(id).is_some()
379 }
380
381 pub fn add_mapping(&mut self, mapping: Mapping) {
387 self.mappings.push(mapping);
388 }
389
390 pub fn add_midi_mapping(
392 &mut self,
393 controller: u8,
394 channel: Option<u8>,
395 target_node: NodeId,
396 target_param: impl Into<String>,
397 min: f32,
398 max: f32,
399 transform: Transform,
400 ) {
401 let mapping = midi_cc(
402 controller,
403 channel,
404 target_node,
405 &target_param.into(),
406 min,
407 max,
408 transform,
409 );
410 self.add_mapping(mapping);
411 }
412
413 pub fn add_osc_mapping(
415 &mut self,
416 address: &str,
417 target_node: NodeId,
418 target_param: impl Into<String>,
419 min: f32,
420 max: f32,
421 transform: Transform,
422 ) {
423 let mapping = osc_address(
424 address,
425 target_node,
426 &target_param.into(),
427 min,
428 max,
429 transform,
430 );
431 self.add_mapping(mapping);
432 }
433
434 pub fn remove_mappings(&mut self, pattern: &EventPattern) -> usize {
438 let before = self.mappings.len();
439 self.mappings.retain(|m| &m.pattern != pattern);
440 before - self.mappings.len()
441 }
442
443 pub fn clear_mappings(&mut self) {
445 self.mappings.clear();
446 }
447
448 pub fn handle_event(&mut self, event: ControlEvent) {
454 let mut commands = Vec::new();
455
456 for mapping in &self.mappings {
457 if let Some(cmd) = mapping.apply(&event) {
458 let value = cmd.value;
459 commands.push(cmd);
460
461 if self.config.log_events {
462 self.emit_event(PatchbayEvent::MappingTriggered {
463 pattern: format!("{:?}", mapping.pattern),
464 target: format!(
465 "{}:{}",
466 mapping.target.node_id.0, mapping.target.param_name
467 ),
468 value,
469 });
470 }
471 }
472 }
473
474 for cmd in commands {
475 let _ = self.command_queue.push(cmd.clone());
476 self.stats.commands_sent += 1;
477
478 if self.config.log_events {
479 self.emit_event(PatchbayEvent::CommandSent(cmd));
480 }
481 }
482 }
483
484 pub fn handle_midi(&mut self, channel: u8, controller: u8, value: u8) {
486 let event = ControlEvent::MidiControl {
487 channel,
488 controller,
489 value,
490 normalized: value as f32 / 127.0,
491 };
492 self.handle_event(event);
493 }
494
495 pub fn handle_osc(&mut self, address: &str, args: Vec<f32>) {
497 let event = ControlEvent::Osc {
498 address: address.to_string(),
499 args,
500 };
501 self.handle_event(event);
502 }
503
504 fn emit_event(&self, event: PatchbayEvent) {
505 if let Some(tx) = &self.event_tx {
506 let _ = tx.send(event);
507 }
508 }
509
510 pub fn start(&mut self) -> Result<(), &'static str> {
520 if self.running.load(Ordering::Relaxed) {
521 return Err("Already running");
522 }
523
524 self.running.store(true, Ordering::Relaxed);
525
526 let running = self.running.clone();
527 let update_interval = Duration::from_secs_f64(1.0 / self.config.update_rate_hz);
528 let collect_stats = self.config.collect_stats;
529
530 let automata = std::mem::take(&mut self.automata);
531 let mut automaton_states = std::mem::take(&mut self.automaton_states);
532 let mut servos = std::mem::take(&mut self.servos);
533 let command_queue = self.command_queue.clone();
534 let _event_tx = self.event_tx.clone();
535
536 self.update_thread = Some(std::thread::spawn(move || {
537 let mut last_time = Instant::now();
538 let mut stats = PatchbayStats::default();
539 let mut time = 0.0;
540
541 while running.load(Ordering::Relaxed) {
542 let frame_start = Instant::now();
543
544 let now = Instant::now();
545 let dt = now.duration_since(last_time).as_secs_f64();
546 last_time = now;
547 time += dt;
548
549 let mut commands = Vec::new();
550
551 for id in automata.keys() {
552 if let Some(_state) = automaton_states.get_mut(id) {
553 if let Some(servo) = servos.get_mut(id) {
554 if let Some(cmd) = servo.update(time) {
555 commands.push(cmd);
556 }
557 }
558 }
559 }
560
561 for cmd in commands {
562 let _ = command_queue.push(cmd.clone());
563 stats.commands_sent += 1;
564 }
565
566 if collect_stats {
567 stats.update(frame_start.elapsed());
568 }
569
570 let elapsed = frame_start.elapsed();
571 if elapsed < update_interval {
572 std::thread::sleep(update_interval - elapsed);
573 }
574 }
575 }));
576
577 Ok(())
578 }
579
580 pub fn stop(&mut self) {
582 self.running.store(false, Ordering::Relaxed);
583
584 if let Some(thread) = self.update_thread.take() {
585 let _ = thread.join();
586 }
587 }
588
589 pub fn stats(&self) -> &PatchbayStats {
591 &self.stats
592 }
593
594 pub fn reset_stats(&mut self) {
596 self.stats = PatchbayStats::default();
597 }
598
599 pub fn current_time(&self) -> f64 {
601 self.time
602 }
603
604 pub fn is_running(&self) -> bool {
606 self.running.load(Ordering::Relaxed)
607 }
608}
609
610impl Drop for PatchbayManager {
611 fn drop(&mut self) {
612 self.stop();
613 }
614}
615
616struct TestServo {
622 id: String,
623 target_node: NodeId,
624 target_param: String,
625 last_value: f64,
626}
627
628impl AnyServo for TestServo {
629 fn update(&mut self, time: f64) -> Option<ParameterCommand> {
630 let value = (time * 2.0).sin() * 0.5 + 0.5;
631
632 if (value - self.last_value).abs() > 0.01 {
633 self.last_value = value;
634 Some(ParameterCommand::new(
635 self.target_node,
636 &self.target_param,
637 value as f32,
638 ))
639 } else {
640 None
641 }
642 }
643
644 fn id(&self) -> &str {
645 &self.id
646 }
647
648 fn set_enabled(&mut self, _enabled: bool) {
649 }
650}
651
652pub struct PatchbayManagerBuilder {
658 config: PatchbayConfig,
659 command_queue: Option<Arc<MpscQueue<ParameterCommand>>>,
660 event_channel: Option<crossbeam_channel::Sender<PatchbayEvent>>,
661}
662
663impl PatchbayManagerBuilder {
664 pub fn new() -> Self {
666 Self {
667 config: PatchbayConfig::default(),
668 command_queue: None,
669 event_channel: None,
670 }
671 }
672
673 pub fn with_config(mut self, config: PatchbayConfig) -> Self {
675 self.config = config;
676 self
677 }
678
679 pub fn with_update_rate(mut self, hz: f64) -> Self {
681 self.config.update_rate_hz = hz;
682 self
683 }
684
685 pub fn with_command_queue(mut self, queue: Arc<MpscQueue<ParameterCommand>>) -> Self {
687 self.command_queue = Some(queue);
688 self
689 }
690
691 pub fn with_event_channel(mut self, tx: crossbeam_channel::Sender<PatchbayEvent>) -> Self {
693 self.event_channel = Some(tx);
694 self.config.log_events = true;
695 self
696 }
697
698 pub fn with_stats(mut self, enabled: bool) -> Self {
700 self.config.collect_stats = enabled;
701 self
702 }
703
704 pub fn build(self) -> PatchbayManager {
706 let queue = self
707 .command_queue
708 .unwrap_or_else(|| Arc::new(MpscQueue::with_capacity(self.config.command_queue_size)));
709
710 let mut manager = PatchbayManager::new(self.config, queue);
711
712 if let Some(tx) = self.event_channel {
713 manager = manager.with_event_channel(tx);
714 }
715
716 manager
717 }
718}
719
720impl Default for PatchbayManagerBuilder {
721 fn default() -> Self {
722 Self::new()
723 }
724}
725
726#[cfg(test)]
727mod tests {
728 use super::*;
729 use std::thread;
730 use std::time::Duration;
731
732 #[test]
733 fn test_manager_creation() {
734 let queue = Arc::new(MpscQueue::with_capacity(1024));
735 let manager = PatchbayManager::new(PatchbayConfig::default(), queue);
736
737 assert_eq!(manager.automata.len(), 0);
738 assert_eq!(manager.mappings.len(), 0);
739 assert!(!manager.is_running());
740 }
741
742 #[test]
743 fn test_add_automaton() {
744 let queue = Arc::new(MpscQueue::with_capacity(1024));
745 let mut manager = PatchbayManager::new(PatchbayConfig::default(), queue);
746
747 let result = manager.add_lfo("test_lfo", 1.0, 0.5, 0.0, LfoWaveform::Sine);
748 assert!(result.is_ok());
749 assert_eq!(manager.automata.len(), 1);
750 }
751
752 #[test]
753 fn test_add_mapping() {
754 let queue = Arc::new(MpscQueue::with_capacity(1024));
755 let mut manager = PatchbayManager::new(PatchbayConfig::default(), queue);
756
757 manager.add_midi_mapping(7, None, NodeId(1), "volume", 0.0, 1.0, Transform::Linear);
758 assert_eq!(manager.mappings.len(), 1);
759 }
760
761 #[test]
762 fn test_handle_event() {
763 let queue = Arc::new(MpscQueue::with_capacity(1024));
764 let mut manager = PatchbayManager::new(PatchbayConfig::default(), queue.clone());
765
766 manager.add_midi_mapping(7, None, NodeId(1), "volume", 0.0, 1.0, Transform::Linear);
767
768 let event = ControlEvent::MidiControl {
769 channel: 1,
770 controller: 7,
771 value: 64,
772 normalized: 0.5,
773 };
774
775 manager.handle_event(event);
776 }
777
778 #[test]
779 fn test_start_stop() {
780 let queue = Arc::new(MpscQueue::with_capacity(1024));
781 let mut manager = PatchbayManager::new(PatchbayConfig::default(), queue);
782
783 let result = manager.start();
784 assert!(result.is_ok());
785 assert!(manager.is_running());
786
787 thread::sleep(Duration::from_millis(100));
788
789 manager.stop();
790 assert!(!manager.is_running());
791 }
792
793 #[test]
794 fn test_builder() {
795 let queue = Arc::new(MpscQueue::with_capacity(1024));
796
797 let manager = PatchbayManagerBuilder::new()
798 .with_update_rate(500.0)
799 .with_command_queue(queue)
800 .with_stats(true)
801 .build();
802
803 assert_eq!(manager.config.update_rate_hz, 500.0);
804 assert!(manager.config.collect_stats);
805 }
806}