1use rill_core::prelude::*;
13use rill_core::queues::MpscQueue;
14use std::collections::HashMap;
15use std::sync::atomic::{AtomicBool, Ordering};
16use std::sync::Arc;
17use std::time::{Duration, Instant};
18
19use crate::automaton::{
20 EnvelopeAutomaton, FunctionAutomaton, LfoAutomaton, LfoWaveform, SequencerAutomaton, Step,
21};
22use crate::control::{
23 midi_cc, osc_address, AnyServo, Automaton, BoxedServo, ControlEvent, EventPattern, Mapping,
24 ParameterCommand, ParameterMapping, Transform,
25};
26
27#[derive(Debug, Clone)]
31pub enum PatchbayEvent {
32 AutomatonUpdated { id: String, value: f64, time: f64 },
34 MappingTriggered {
36 pattern: String,
37 target: String,
38 value: f32,
39 },
40 CommandSent(ParameterCommand),
42 Error(String),
44}
45
46#[derive(Debug, Clone, Default)]
52pub struct PatchbayStats {
53 pub automaton_count: usize,
55 pub mapping_count: usize,
57 pub commands_sent: u64,
59 pub last_update: Option<Duration>,
61 pub avg_update_time_us: f64,
63 pub max_update_time_us: f64,
65 pub error_count: u64,
67}
68
69impl PatchbayStats {
70 pub fn update(&mut self, update_duration: Duration) {
72 let us = update_duration.as_micros() as f64;
73 self.avg_update_time_us = self.avg_update_time_us * 0.9 + us * 0.1;
74 self.max_update_time_us = self.max_update_time_us.max(us);
75 self.last_update = Some(update_duration);
76 }
77}
78
79#[derive(Debug, Clone)]
85pub struct PatchbayConfig {
86 pub update_rate_hz: f64,
88 pub command_queue_size: usize,
90 pub collect_stats: bool,
92 pub log_events: bool,
94}
95
96impl Default for PatchbayConfig {
97 fn default() -> Self {
98 Self {
99 update_rate_hz: 1000.0, command_queue_size: 1024,
101 collect_stats: true,
102 log_events: false,
103 }
104 }
105}
106
107pub struct PatchbayManager {
116 config: PatchbayConfig,
118
119 automata: HashMap<String, Box<dyn std::any::Any + Send>>,
121
122 automaton_states: HashMap<String, Box<dyn std::any::Any + Send>>,
124
125 servos: HashMap<String, BoxedServo>,
127
128 mappings: Vec<Mapping>,
130
131 command_queue: Arc<MpscQueue<ParameterCommand>>,
133
134 event_tx: Option<crossbeam_channel::Sender<PatchbayEvent>>,
136
137 time: f64,
139
140 stats: PatchbayStats,
142
143 running: Arc<AtomicBool>,
145
146 update_thread: Option<std::thread::JoinHandle<()>>,
148}
149
150impl PatchbayManager {
151 pub fn new(config: PatchbayConfig, command_queue: Arc<MpscQueue<ParameterCommand>>) -> Self {
153 Self {
154 config,
155 automata: HashMap::new(),
156 automaton_states: HashMap::new(),
157 servos: HashMap::new(),
158 mappings: Vec::new(),
159 command_queue,
160 event_tx: None,
161 time: 0.0,
162 stats: PatchbayStats::default(),
163 running: Arc::new(AtomicBool::new(false)),
164 update_thread: None,
165 }
166 }
167
168 pub fn with_event_channel(mut self, tx: crossbeam_channel::Sender<PatchbayEvent>) -> Self {
170 self.event_tx = Some(tx);
171 self
172 }
173
174 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(
204 &mut self,
205 id: impl Into<String>,
206 frequency: f64,
207 amplitude: f64,
208 offset: f64,
209 waveform: LfoWaveform,
210 ) -> Result<(), &'static str> {
211 let id_str = id.into();
212 let automaton = LfoAutomaton::new(&id_str, frequency, amplitude, offset, waveform);
213 self.add_automaton(id_str, automaton)
214 }
215
216 pub fn add_envelope(
218 &mut self,
219 id: impl Into<String>,
220 attack: f64,
221 decay: f64,
222 sustain: f64,
223 release: f64,
224 ) -> Result<(), &'static str> {
225 let id_str = id.into();
226 let automaton = EnvelopeAutomaton::adsr(&id_str, attack, decay, sustain, release);
227 self.add_automaton(id_str, automaton)
228 }
229
230 pub fn add_sequencer(
232 &mut self,
233 id: impl Into<String>,
234 steps: Vec<Step>,
235 ) -> Result<(), &'static str> {
236 let id_str = id.into();
237 let automaton = SequencerAutomaton::new(&id_str, steps);
238 self.add_automaton(id_str, automaton)
239 }
240
241 pub fn add_function<F>(
243 &mut self,
244 id: impl Into<String>,
245 generator: F,
246 ) -> Result<(), &'static str>
247 where
248 F: Fn(f64) -> f64 + Send + Sync + 'static,
249 {
250 let id_str = id.into();
251 let automaton = FunctionAutomaton::new(&id_str, generator);
252 self.add_automaton(id_str, automaton)
253 }
254
255 pub fn reset_automaton<A: Automaton + 'static>(
257 &mut self,
258 id: &str,
259 ) -> Result<(), &'static str> {
260 let automaton = self
261 .automata
262 .get(id)
263 .and_then(|a| a.downcast_ref::<A>())
264 .ok_or("Automaton not found or type mismatch")?;
265 let state = automaton.initial_state();
266 self.automaton_states
267 .insert(id.to_string(), Box::new(state));
268 Ok(())
269 }
270
271 pub fn remove_automaton(&mut self, id: &str) -> bool {
273 self.automata.remove(id).is_some() && self.automaton_states.remove(id).is_some()
274 }
275
276 pub fn add_servo(
282 &mut self,
283 id: impl Into<String>,
284 automaton_id: impl Into<String>,
285 target_node: NodeId,
286 target_param: impl Into<String>,
287 _mapping: ParameterMapping,
288 _min: f64,
289 _max: f64,
290 ) -> Result<(), &'static str> {
291 let id_str = id.into();
292 let automaton_id_str = automaton_id.into();
293 let target_param_str = target_param.into();
294 let _automaton = self
295 .automata
296 .get(&automaton_id_str)
297 .ok_or("Automaton not found")?;
298
299 let servo = Box::new(TestServo {
304 id: id_str.clone(),
305 target_node,
306 target_param: target_param_str,
307 last_value: 0.0,
308 });
309
310 self.servos.insert(id_str, servo);
311
312 Ok(())
313 }
314
315 pub fn add_lfo_servo(
317 &mut self,
318 id: impl Into<String>,
319 frequency: f64,
320 amplitude: f64,
321 offset: f64,
322 waveform: LfoWaveform,
323 target_node: NodeId,
324 target_param: impl Into<String>,
325 min: f64,
326 max: f64,
327 ) -> Result<(), &'static str> {
328 let id_str = id.into();
329 let automaton_id = format!("{}_auto", &id_str);
330 self.add_lfo(&automaton_id, frequency, amplitude, offset, waveform)?;
331 self.add_servo(
332 id_str,
333 automaton_id,
334 target_node,
335 target_param,
336 ParameterMapping::Linear,
337 min,
338 max,
339 )
340 }
341
342 pub fn get_servo(&self, id: &str) -> Option<&dyn AnyServo> {
344 self.servos.get(id).map(|b| b.as_ref())
345 }
346
347 pub fn get_servo_mut(&mut self, id: &str) -> Option<&mut BoxedServo> {
349 self.servos.get_mut(id)
350 }
351
352 pub fn remove_servo(&mut self, id: &str) -> bool {
354 self.servos.remove(id).is_some()
355 }
356
357 pub fn add_mapping(&mut self, mapping: Mapping) {
363 self.mappings.push(mapping);
364 }
365
366 pub fn add_midi_mapping(
368 &mut self,
369 controller: u8,
370 channel: Option<u8>,
371 target_node: NodeId,
372 target_param: impl Into<String>,
373 min: f32,
374 max: f32,
375 transform: Transform,
376 ) {
377 let mapping = midi_cc(
378 controller,
379 channel,
380 target_node,
381 &target_param.into(),
382 min,
383 max,
384 transform,
385 );
386 self.add_mapping(mapping);
387 }
388
389 pub fn add_osc_mapping(
391 &mut self,
392 address: &str,
393 target_node: NodeId,
394 target_param: impl Into<String>,
395 min: f32,
396 max: f32,
397 transform: Transform,
398 ) {
399 let mapping = osc_address(
400 address,
401 target_node,
402 &target_param.into(),
403 min,
404 max,
405 transform,
406 );
407 self.add_mapping(mapping);
408 }
409
410 pub fn remove_mappings(&mut self, pattern: &EventPattern) -> usize {
412 let before = self.mappings.len();
413 self.mappings.retain(|m| &m.pattern != pattern);
414 before - self.mappings.len()
415 }
416
417 pub fn clear_mappings(&mut self) {
419 self.mappings.clear();
420 }
421
422 pub fn handle_event(&mut self, event: ControlEvent) {
428 let mut commands = Vec::new();
429
430 for mapping in &self.mappings {
431 if let Some(cmd) = mapping.apply(&event) {
432 let value = cmd.value;
433 commands.push(cmd);
434
435 if self.config.log_events {
436 self.emit_event(PatchbayEvent::MappingTriggered {
437 pattern: format!("{:?}", mapping.pattern),
438 target: format!(
439 "{}:{}",
440 mapping.target.node_id.0, mapping.target.param_name
441 ),
442 value,
443 });
444 }
445 }
446 }
447
448 for cmd in commands {
450 let _ = self.command_queue.push(cmd.clone());
451 self.stats.commands_sent += 1;
452
453 if self.config.log_events {
454 self.emit_event(PatchbayEvent::CommandSent(cmd));
455 }
456 }
457 }
458
459 pub fn handle_midi(&mut self, channel: u8, controller: u8, value: u8) {
461 let event = ControlEvent::MidiControl {
462 channel,
463 controller,
464 value,
465 normalized: value as f32 / 127.0,
466 };
467 self.handle_event(event);
468 }
469
470 pub fn handle_osc(&mut self, address: &str, args: Vec<f32>) {
472 let event = ControlEvent::Osc {
473 address: address.to_string(),
474 args,
475 };
476 self.handle_event(event);
477 }
478
479 fn emit_event(&self, event: PatchbayEvent) {
481 if let Some(tx) = &self.event_tx {
482 let _ = tx.send(event);
483 }
484 }
485
486 pub fn start(&mut self) -> Result<(), &'static str> {
492 if self.running.load(Ordering::Relaxed) {
493 return Err("Already running");
494 }
495
496 self.running.store(true, Ordering::Relaxed);
497
498 let running = self.running.clone();
499 let update_interval = Duration::from_secs_f64(1.0 / self.config.update_rate_hz);
500 let collect_stats = self.config.collect_stats;
501
502 let automata = std::mem::replace(&mut self.automata, HashMap::new());
504 let mut automaton_states = std::mem::take(&mut self.automaton_states);
505 let mut servos = std::mem::take(&mut self.servos);
506 let command_queue = self.command_queue.clone();
507 let _event_tx = self.event_tx.clone();
508
509 self.update_thread = Some(std::thread::spawn(move || {
510 let mut last_time = Instant::now();
511 let mut stats = PatchbayStats::default();
512 let mut time = 0.0;
513
514 while running.load(Ordering::Relaxed) {
515 let frame_start = Instant::now();
516
517 let now = Instant::now();
519 let dt = now.duration_since(last_time).as_secs_f64();
520 last_time = now;
521 time += dt;
522
523 let mut commands = Vec::new();
525
526 for (id, _automaton) in &automata {
527 if let Some(_state) = automaton_states.get_mut(id) {
528 if let Some(servo) = servos.get_mut(id) {
532 if let Some(cmd) = servo.update(time) {
533 commands.push(cmd);
534 }
535 }
536 }
537 }
538
539 for cmd in commands {
541 let _ = command_queue.push(cmd.clone());
542 stats.commands_sent += 1;
543 }
544
545 if collect_stats {
547 stats.update(frame_start.elapsed());
548 }
549
550 let elapsed = frame_start.elapsed();
552 if elapsed < update_interval {
553 std::thread::sleep(update_interval - elapsed);
554 }
555 }
556 }));
557
558 Ok(())
559 }
560
561 pub fn stop(&mut self) {
563 self.running.store(false, Ordering::Relaxed);
564
565 if let Some(thread) = self.update_thread.take() {
566 let _ = thread.join();
567 }
568 }
569
570 pub fn stats(&self) -> &PatchbayStats {
572 &self.stats
573 }
574
575 pub fn reset_stats(&mut self) {
577 self.stats = PatchbayStats::default();
578 }
579
580 pub fn current_time(&self) -> f64 {
582 self.time
583 }
584
585 pub fn is_running(&self) -> bool {
587 self.running.load(Ordering::Relaxed)
588 }
589}
590
591impl Drop for PatchbayManager {
592 fn drop(&mut self) {
593 self.stop();
594 }
595}
596
597struct TestServo {
603 id: String,
604 target_node: NodeId,
605 target_param: String,
606 last_value: f64,
607}
608
609impl AnyServo for TestServo {
610 fn update(&mut self, time: f64) -> Option<ParameterCommand> {
611 let value = (time * 2.0).sin() * 0.5 + 0.5;
613
614 if (value - self.last_value).abs() > 0.01 {
615 self.last_value = value;
616 Some(ParameterCommand::new(
617 self.target_node,
618 &self.target_param,
619 value as f32,
620 ))
621 } else {
622 None
623 }
624 }
625
626 fn id(&self) -> &str {
627 &self.id
628 }
629
630 fn set_enabled(&mut self, _enabled: bool) {
631 }
633}
634
635pub struct PatchbayManagerBuilder {
641 config: PatchbayConfig,
642 command_queue: Option<Arc<MpscQueue<ParameterCommand>>>,
643 event_channel: Option<crossbeam_channel::Sender<PatchbayEvent>>,
644}
645
646impl PatchbayManagerBuilder {
647 pub fn new() -> Self {
649 Self {
650 config: PatchbayConfig::default(),
651 command_queue: None,
652 event_channel: None,
653 }
654 }
655
656 pub fn with_config(mut self, config: PatchbayConfig) -> Self {
658 self.config = config;
659 self
660 }
661
662 pub fn with_update_rate(mut self, hz: f64) -> Self {
664 self.config.update_rate_hz = hz;
665 self
666 }
667
668 pub fn with_command_queue(mut self, queue: Arc<MpscQueue<ParameterCommand>>) -> Self {
670 self.command_queue = Some(queue);
671 self
672 }
673
674 pub fn with_event_channel(mut self, tx: crossbeam_channel::Sender<PatchbayEvent>) -> Self {
676 self.event_channel = Some(tx);
677 self.config.log_events = true;
678 self
679 }
680
681 pub fn with_stats(mut self, enabled: bool) -> Self {
683 self.config.collect_stats = enabled;
684 self
685 }
686
687 pub fn build(self) -> PatchbayManager {
689 let queue = self
690 .command_queue
691 .unwrap_or_else(|| Arc::new(MpscQueue::with_capacity(self.config.command_queue_size)));
692
693 let mut manager = PatchbayManager::new(self.config, queue);
694
695 if let Some(tx) = self.event_channel {
696 manager = manager.with_event_channel(tx);
697 }
698
699 manager
700 }
701}
702
703impl Default for PatchbayManagerBuilder {
704 fn default() -> Self {
705 Self::new()
706 }
707}
708
709#[cfg(test)]
714mod tests {
715 use super::*;
716 use std::thread;
717 use std::time::Duration;
718
719 #[test]
720 fn test_manager_creation() {
721 let queue = Arc::new(MpscQueue::with_capacity(1024));
722 let manager = PatchbayManager::new(PatchbayConfig::default(), queue);
723
724 assert_eq!(manager.automata.len(), 0);
725 assert_eq!(manager.mappings.len(), 0);
726 assert!(!manager.is_running());
727 }
728
729 #[test]
730 fn test_add_automaton() {
731 let queue = Arc::new(MpscQueue::with_capacity(1024));
732 let mut manager = PatchbayManager::new(PatchbayConfig::default(), queue);
733
734 let result = manager.add_lfo("test_lfo", 1.0, 0.5, 0.0, LfoWaveform::Sine);
735 assert!(result.is_ok());
736 assert_eq!(manager.automata.len(), 1);
737 }
738
739 #[test]
740 fn test_add_mapping() {
741 let queue = Arc::new(MpscQueue::with_capacity(1024));
742 let mut manager = PatchbayManager::new(PatchbayConfig::default(), queue);
743
744 manager.add_midi_mapping(7, None, NodeId(1), "volume", 0.0, 1.0, Transform::Linear);
745 assert_eq!(manager.mappings.len(), 1);
746 }
747
748 #[test]
749 fn test_handle_event() {
750 let queue = Arc::new(MpscQueue::with_capacity(1024));
751 let mut manager = PatchbayManager::new(PatchbayConfig::default(), queue.clone());
752
753 manager.add_midi_mapping(7, None, NodeId(1), "volume", 0.0, 1.0, Transform::Linear);
754
755 let event = ControlEvent::MidiControl {
756 channel: 1,
757 controller: 7,
758 value: 64,
759 normalized: 0.5,
760 };
761
762 manager.handle_event(event);
763
764 }
767
768 #[test]
769 fn test_start_stop() {
770 let queue = Arc::new(MpscQueue::with_capacity(1024));
771 let mut manager = PatchbayManager::new(PatchbayConfig::default(), queue);
772
773 let result = manager.start();
774 assert!(result.is_ok());
775 assert!(manager.is_running());
776
777 thread::sleep(Duration::from_millis(100));
778
779 manager.stop();
780 assert!(!manager.is_running());
781 }
782
783 #[test]
784 fn test_builder() {
785 let queue = Arc::new(MpscQueue::with_capacity(1024));
786
787 let manager = PatchbayManagerBuilder::new()
788 .with_update_rate(500.0)
789 .with_command_queue(queue)
790 .with_stats(true)
791 .build();
792
793 assert_eq!(manager.config.update_rate_hz, 500.0);
794 assert!(manager.config.collect_stats);
795 }
796}