Skip to main content

rill_patchbay/
manager.rs

1//! # Менеджер патчбэя — центральный координатор
2//!
3//! `PatchbayManager` объединяет все компоненты патчбэя:
4//! - Автоматы (LFO, огибающие, секвенсоры)
5//! - Маппинги событий (MIDI/OSC)
6//! - Сервоприводы (связь с параметрами)
7//! - Очередь команд для аудиопотока
8//!
9//! Работает в **потоке управления** (soft RT) и отправляет
10//! команды в аудиопоток через `RtQueue<ParameterCommand>`.
11
12use 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// =============================================================================
28// Событие для логирования и отладки
29// =============================================================================
30#[derive(Debug, Clone)]
31pub enum PatchbayEvent {
32    /// Автомат обновлён
33    AutomatonUpdated { id: String, value: f64, time: f64 },
34    /// Маппинг сработал
35    MappingTriggered {
36        pattern: String,
37        target: String,
38        value: f32,
39    },
40    /// Команда отправлена в аудиопоток
41    CommandSent(ParameterCommand),
42    /// Ошибка
43    Error(String),
44}
45
46// =============================================================================
47// Статистика работы патчбэя
48// =============================================================================
49
50/// Статистика работы патчбэя
51#[derive(Debug, Clone, Default)]
52pub struct PatchbayStats {
53    /// Количество активных автоматов
54    pub automaton_count: usize,
55    /// Количество активных маппингов
56    pub mapping_count: usize,
57    /// Количество отправленных команд
58    pub commands_sent: u64,
59    /// Время последнего обновления
60    pub last_update: Option<Duration>,
61    /// Среднее время обновления (мкс)
62    pub avg_update_time_us: f64,
63    /// Максимальное время обновления (мкс)
64    pub max_update_time_us: f64,
65    /// Количество ошибок
66    pub error_count: u64,
67}
68
69impl PatchbayStats {
70    /// Обновить статистику
71    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// =============================================================================
80// Конфигурация патчбэя
81// =============================================================================
82
83/// Конфигурация патчбэя
84#[derive(Debug, Clone)]
85pub struct PatchbayConfig {
86    /// Частота обновления автоматов (Гц)
87    pub update_rate_hz: f64,
88    /// Размер очереди команд
89    pub command_queue_size: usize,
90    /// Собирать ли статистику
91    pub collect_stats: bool,
92    /// Логировать ли события
93    pub log_events: bool,
94}
95
96impl Default for PatchbayConfig {
97    fn default() -> Self {
98        Self {
99            update_rate_hz: 1000.0, // 1 кГц
100            command_queue_size: 1024,
101            collect_stats: true,
102            log_events: false,
103        }
104    }
105}
106
107// =============================================================================
108// Основной менеджер патчбэя
109// =============================================================================
110
111/// Главный менеджер патчбэя
112///
113/// Координирует все компоненты управления и автоматизации.
114/// Работает в отдельном потоке с настраиваемой частотой обновления.
115pub struct PatchbayManager {
116    /// Конфигурация
117    config: PatchbayConfig,
118
119    /// Автоматы (ключ — ID, type-erased)
120    automata: HashMap<String, Box<dyn std::any::Any + Send>>,
121
122    /// Состояния автоматов
123    automaton_states: HashMap<String, Box<dyn std::any::Any + Send>>,
124
125    /// Сервоприводы (связь автоматов с параметрами)
126    servos: HashMap<String, BoxedServo>,
127
128    /// Маппинги событий
129    mappings: Vec<Mapping>,
130
131    /// Очередь для отправки команд в аудиопоток
132    command_queue: Arc<MpscQueue<ParameterCommand>>,
133
134    /// Канал для событий (опционально)
135    event_tx: Option<crossbeam_channel::Sender<PatchbayEvent>>,
136
137    /// Текущее время (секунды)
138    time: f64,
139
140    /// Статистика
141    stats: PatchbayStats,
142
143    /// Флаг работы
144    running: Arc<AtomicBool>,
145
146    /// Поток обновления
147    update_thread: Option<std::thread::JoinHandle<()>>,
148}
149
150impl PatchbayManager {
151    /// Создать новый менеджер
152    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    /// Установить канал для событий
169    pub fn with_event_channel(mut self, tx: crossbeam_channel::Sender<PatchbayEvent>) -> Self {
170        self.event_tx = Some(tx);
171        self
172    }
173
174    // =========================================================================
175    // Управление автоматами
176    // =========================================================================
177
178    /// Добавить автомат
179    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    /// Добавить LFO как автомат
203    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    /// Добавить огибающую как автомат
217    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    /// Добавить секвенсор как автомат
231    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    /// Добавить функциональный автомат
242    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    /// Сбросить автомат (generic, caller must know the type)
256    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    /// Удалить автомат
272    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    // =========================================================================
277    // Управление сервоприводами
278    // =========================================================================
279
280    /// Добавить сервопривод (связь автомата с параметром)
281    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        // Создаём сервопривод
300        // В реальном коде нужно клонировать автомат
301        // Здесь упрощённо
302
303        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    /// Добавить LFO как сервопривод (упрощённый метод)
316    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    /// Получить сервопривод
343    pub fn get_servo(&self, id: &str) -> Option<&dyn AnyServo> {
344        self.servos.get(id).map(|b| b.as_ref())
345    }
346
347    /// Получить мутабельный сервопривод
348    pub fn get_servo_mut(&mut self, id: &str) -> Option<&mut BoxedServo> {
349        self.servos.get_mut(id)
350    }
351
352    /// Удалить сервопривод
353    pub fn remove_servo(&mut self, id: &str) -> bool {
354        self.servos.remove(id).is_some()
355    }
356
357    // =========================================================================
358    // Управление маппингами
359    // =========================================================================
360
361    /// Добавить маппинг события
362    pub fn add_mapping(&mut self, mapping: Mapping) {
363        self.mappings.push(mapping);
364    }
365
366    /// Добавить MIDI маппинг
367    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    /// Добавить OSC маппинг
390    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    /// Удалить маппинги по паттерну
411    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    /// Очистить все маппинги
418    pub fn clear_mappings(&mut self) {
419        self.mappings.clear();
420    }
421
422    // =========================================================================
423    // Обработка событий
424    // =========================================================================
425
426    /// Обработать внешнее событие (MIDI/OSC)
427    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        // Отправляем команды в аудиопоток
449        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    /// Обработать MIDI сообщение (упрощённый метод)
460    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    /// Обработать OSC сообщение (упрощённый метод)
471    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    /// Отправить событие (если есть канал)
480    fn emit_event(&self, event: PatchbayEvent) {
481        if let Some(tx) = &self.event_tx {
482            let _ = tx.send(event);
483        }
484    }
485
486    // =========================================================================
487    // Запуск и остановка
488    // =========================================================================
489
490    /// Запустить менеджер в отдельном потоке
491    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        // Перемещаем данные в поток
503        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                // Вычисляем dt
518                let now = Instant::now();
519                let dt = now.duration_since(last_time).as_secs_f64();
520                last_time = now;
521                time += dt;
522
523                // Обновляем все автоматы
524                let mut commands = Vec::new();
525
526                for (id, _automaton) in &automata {
527                    if let Some(_state) = automaton_states.get_mut(id) {
528                        // В реальном коде здесь нужно применить шаг автомата
529                        // и получить команды от сервоприводов
530
531                        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                // Отправляем команды
540                for cmd in commands {
541                    let _ = command_queue.push(cmd.clone());
542                    stats.commands_sent += 1;
543                }
544
545                // Обновляем статистику
546                if collect_stats {
547                    stats.update(frame_start.elapsed());
548                }
549
550                // Спим до следующего обновления
551                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    /// Остановить менеджер
562    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    /// Получить статистику
571    pub fn stats(&self) -> &PatchbayStats {
572        &self.stats
573    }
574
575    /// Сбросить статистику
576    pub fn reset_stats(&mut self) {
577        self.stats = PatchbayStats::default();
578    }
579
580    /// Получить текущее время
581    pub fn current_time(&self) -> f64 {
582        self.time
583    }
584
585    /// Проверить, запущен ли менеджер
586    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
597// =============================================================================
598// Вспомогательные типы для тестирования
599// =============================================================================
600
601/// Тестовый сервопривод (заглушка)
602struct 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        // Генерируем тестовое значение
612        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        // Игнорируем
632    }
633}
634
635// =============================================================================
636// Строитель для удобного создания менеджера
637// =============================================================================
638
639/// Строитель для PatchbayManager
640pub 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    /// Создать нового строителя
648    pub fn new() -> Self {
649        Self {
650            config: PatchbayConfig::default(),
651            command_queue: None,
652            event_channel: None,
653        }
654    }
655
656    /// Установить конфигурацию
657    pub fn with_config(mut self, config: PatchbayConfig) -> Self {
658        self.config = config;
659        self
660    }
661
662    /// Установить частоту обновления
663    pub fn with_update_rate(mut self, hz: f64) -> Self {
664        self.config.update_rate_hz = hz;
665        self
666    }
667
668    /// Установить очередь команд
669    pub fn with_command_queue(mut self, queue: Arc<MpscQueue<ParameterCommand>>) -> Self {
670        self.command_queue = Some(queue);
671        self
672    }
673
674    /// Установить канал событий
675    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    /// Включить сбор статистики
682    pub fn with_stats(mut self, enabled: bool) -> Self {
683        self.config.collect_stats = enabled;
684        self
685    }
686
687    /// Собрать менеджер
688    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// =============================================================================
710// Тесты
711// =============================================================================
712
713#[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        // Должна быть команда в очереди
765        // assert!(queue.len() > 0); // В реальном тесте
766    }
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}