use rill_core::prelude::*;
use rill_core::queues::{MpscQueue, SetParameter, SignalOrigin};
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};
use crate::automaton::{
EnvelopeAutomaton, FunctionAutomaton, LfoAutomaton, LfoWaveform, SequencerAutomaton, Step,
};
use crate::engine::{
midi_cc, osc_address, AnyServo, Automaton, BoxedServo, ControlEvent, EventPattern, Mapping,
ParameterMapping, Transform,
};
#[derive(Debug, Clone)]
pub enum Event {
AutomatonUpdated {
id: String,
value: f64,
time: f64,
},
MappingTriggered {
pattern: String,
target: String,
value: f32,
},
CommandSent(SetParameter),
Error(String),
}
#[derive(Debug, Clone, Default)]
pub struct Stats {
pub automaton_count: usize,
pub mapping_count: usize,
pub commands_sent: u64,
pub last_update: Option<Duration>,
pub avg_update_time_us: f64,
pub max_update_time_us: f64,
pub error_count: u64,
}
impl Stats {
pub fn update(&mut self, update_duration: Duration) {
let us = update_duration.as_micros() as f64;
self.avg_update_time_us = self.avg_update_time_us * 0.9 + us * 0.1;
self.max_update_time_us = self.max_update_time_us.max(us);
self.last_update = Some(update_duration);
}
}
#[derive(Debug, Clone)]
pub struct Config {
pub update_rate_hz: f64,
pub command_queue_size: usize,
pub collect_stats: bool,
pub log_events: bool,
}
impl Default for Config {
fn default() -> Self {
Self {
update_rate_hz: 1000.0,
command_queue_size: 1024,
collect_stats: true,
log_events: false,
}
}
}
pub struct Manager {
config: Config,
automata: HashMap<String, Box<dyn std::any::Any + Send>>,
automaton_states: HashMap<String, Box<dyn std::any::Any + Send>>,
servos: HashMap<String, BoxedServo>,
mappings: Vec<Mapping>,
command_queue: Arc<MpscQueue<SetParameter>>,
event_tx: Option<crossbeam_channel::Sender<Event>>,
time: f64,
stats: Stats,
running: Arc<AtomicBool>,
update_thread: Option<std::thread::JoinHandle<()>>,
}
impl Manager {
pub fn new(config: Config, command_queue: Arc<MpscQueue<SetParameter>>) -> Self {
Self {
config,
automata: HashMap::new(),
automaton_states: HashMap::new(),
servos: HashMap::new(),
mappings: Vec::new(),
command_queue,
event_tx: None,
time: 0.0,
stats: Stats::default(),
running: Arc::new(AtomicBool::new(false)),
update_thread: None,
}
}
pub fn with_event_channel(mut self, tx: crossbeam_channel::Sender<Event>) -> Self {
self.event_tx = Some(tx);
self
}
pub fn add_automaton<A: Automaton + 'static>(
&mut self,
id: impl Into<String>,
automaton: A,
) -> Result<(), &'static str>
where
A::State: 'static,
{
let id = id.into();
if self.automata.contains_key(&id) {
return Err("Automaton with this ID already exists");
}
let state = automaton.initial_state();
self.automata.insert(
id.clone(),
Box::new(automaton) as Box<dyn std::any::Any + Send>,
);
self.automaton_states.insert(id, Box::new(state));
Ok(())
}
pub fn add_lfo(
&mut self,
id: impl Into<String>,
frequency: f64,
amplitude: f64,
offset: f64,
waveform: LfoWaveform,
) -> Result<(), &'static str> {
let id_str = id.into();
let automaton = LfoAutomaton::new(&id_str, frequency, amplitude, offset, waveform);
self.add_automaton(id_str, automaton)
}
pub fn add_envelope(
&mut self,
id: impl Into<String>,
attack: f64,
decay: f64,
sustain: f64,
release: f64,
) -> Result<(), &'static str> {
let id_str = id.into();
let automaton = EnvelopeAutomaton::adsr(&id_str, attack, decay, sustain, release);
self.add_automaton(id_str, automaton)
}
pub fn add_sequencer(
&mut self,
id: impl Into<String>,
steps: Vec<Step>,
) -> Result<(), &'static str> {
let id_str = id.into();
let automaton = SequencerAutomaton::new(&id_str, steps);
self.add_automaton(id_str, automaton)
}
pub fn add_function<F>(
&mut self,
id: impl Into<String>,
generator: F,
) -> Result<(), &'static str>
where
F: Fn(f64) -> f64 + Send + Sync + 'static,
{
let id_str = id.into();
let automaton = FunctionAutomaton::new(&id_str, generator);
self.add_automaton(id_str, automaton)
}
pub fn reset_automaton<A: Automaton + 'static>(
&mut self,
id: &str,
) -> Result<(), &'static str> {
let automaton = self
.automata
.get(id)
.and_then(|a| a.downcast_ref::<A>())
.ok_or("Automaton not found or type mismatch")?;
let state = automaton.initial_state();
self.automaton_states
.insert(id.to_string(), Box::new(state));
Ok(())
}
pub fn remove_automaton(&mut self, id: &str) -> bool {
self.automata.remove(id).is_some() && self.automaton_states.remove(id).is_some()
}
pub fn add_servo(
&mut self,
id: impl Into<String>,
automaton_id: impl Into<String>,
target_node: NodeId,
target_param: impl Into<String>,
_mapping: ParameterMapping,
_min: f64,
_max: f64,
) -> Result<(), &'static str> {
let id_str = id.into();
let automaton_id_str = automaton_id.into();
let target_param_str = target_param.into();
let _automaton = self
.automata
.get(&automaton_id_str)
.ok_or("Automaton not found")?;
let servo = Box::new(TestServo {
id: id_str.clone(),
target_node,
target_param: target_param_str,
last_value: 0.0,
});
self.servos.insert(id_str, servo);
Ok(())
}
pub fn add_lfo_servo(
&mut self,
id: impl Into<String>,
frequency: f64,
amplitude: f64,
offset: f64,
waveform: LfoWaveform,
target_node: NodeId,
target_param: impl Into<String>,
min: f64,
max: f64,
) -> Result<(), &'static str> {
let id_str = id.into();
let automaton_id = format!("{}_auto", &id_str);
self.add_lfo(&automaton_id, frequency, amplitude, offset, waveform)?;
self.add_servo(
id_str,
automaton_id,
target_node,
target_param,
ParameterMapping::Linear,
min,
max,
)
}
pub fn get_servo(&self, id: &str) -> Option<&dyn AnyServo> {
self.servos.get(id).map(|b| b.as_ref())
}
pub fn get_servo_mut(&mut self, id: &str) -> Option<&mut BoxedServo> {
self.servos.get_mut(id)
}
pub fn remove_servo(&mut self, id: &str) -> bool {
self.servos.remove(id).is_some()
}
pub fn add_mapping(&mut self, mapping: Mapping) {
self.mappings.push(mapping);
}
pub fn add_midi_mapping(
&mut self,
controller: u8,
channel: Option<u8>,
target_node: NodeId,
target_param: impl Into<String>,
min: f32,
max: f32,
transform: Transform,
) {
let mapping = midi_cc(
controller,
channel,
target_node,
&target_param.into(),
min,
max,
transform,
);
self.add_mapping(mapping);
}
pub fn add_osc_mapping(
&mut self,
address: &str,
target_node: NodeId,
target_param: impl Into<String>,
min: f32,
max: f32,
transform: Transform,
) {
let mapping = osc_address(
address,
target_node,
&target_param.into(),
min,
max,
transform,
);
self.add_mapping(mapping);
}
pub fn remove_mappings(&mut self, pattern: &EventPattern) -> usize {
let before = self.mappings.len();
self.mappings.retain(|m| &m.pattern != pattern);
before - self.mappings.len()
}
pub fn clear_mappings(&mut self) {
self.mappings.clear();
}
pub fn handle_event(&mut self, event: ControlEvent) {
let mut commands = Vec::new();
for mapping in &self.mappings {
if let Some(cmd) = mapping.apply(&event) {
let value = cmd.value.clone();
commands.push(cmd);
if self.config.log_events {
self.emit_event(Event::MappingTriggered {
pattern: format!("{:?}", mapping.pattern),
target: format!(
"{}:{}",
mapping.target.node_id.0, mapping.target.param_name
),
value: value.as_f32().unwrap_or(0.0),
});
}
}
}
for cmd in commands {
let _ = self.command_queue.push(cmd.clone());
self.stats.commands_sent += 1;
if self.config.log_events {
self.emit_event(Event::CommandSent(cmd));
}
}
}
pub fn handle_midi(&mut self, channel: u8, controller: u8, value: u8) {
let event = ControlEvent::MidiControl {
channel,
controller,
value,
normalized: value as f32 / 127.0,
};
self.handle_event(event);
}
pub fn handle_osc(&mut self, address: &str, args: Vec<f32>) {
let event = ControlEvent::Osc {
address: address.to_string(),
args,
};
self.handle_event(event);
}
fn emit_event(&self, event: Event) {
if let Some(tx) = &self.event_tx {
let _ = tx.send(event);
}
}
pub fn start(&mut self) -> Result<(), &'static str> {
if self.running.load(Ordering::Relaxed) {
return Err("Already running");
}
self.running.store(true, Ordering::Relaxed);
let running = self.running.clone();
let update_interval = Duration::from_secs_f64(1.0 / self.config.update_rate_hz);
let collect_stats = self.config.collect_stats;
let automata = std::mem::take(&mut self.automata);
let mut automaton_states = std::mem::take(&mut self.automaton_states);
let mut servos = std::mem::take(&mut self.servos);
let command_queue = self.command_queue.clone();
let _event_tx = self.event_tx.clone();
self.update_thread = Some(std::thread::spawn(move || {
let mut last_time = Instant::now();
let mut stats = Stats::default();
let mut time = 0.0;
while running.load(Ordering::Relaxed) {
let frame_start = Instant::now();
let now = Instant::now();
let dt = now.duration_since(last_time).as_secs_f64();
last_time = now;
time += dt;
let mut commands = Vec::new();
for id in automata.keys() {
if let Some(_state) = automaton_states.get_mut(id) {
if let Some(servo) = servos.get_mut(id) {
if let Some(cmd) = servo.update(time) {
commands.push(cmd);
}
}
}
}
for cmd in commands {
let _ = command_queue.push(cmd.clone());
stats.commands_sent += 1;
}
if collect_stats {
stats.update(frame_start.elapsed());
}
let elapsed = frame_start.elapsed();
if elapsed < update_interval {
std::thread::sleep(update_interval - elapsed);
}
}
}));
Ok(())
}
pub fn stop(&mut self) {
self.running.store(false, Ordering::Relaxed);
if let Some(thread) = self.update_thread.take() {
let _ = thread.join();
}
}
pub fn stats(&self) -> &Stats {
&self.stats
}
pub fn reset_stats(&mut self) {
self.stats = Stats::default();
}
pub fn current_time(&self) -> f64 {
self.time
}
pub fn is_running(&self) -> bool {
self.running.load(Ordering::Relaxed)
}
}
impl Drop for Manager {
fn drop(&mut self) {
self.stop();
}
}
struct TestServo {
id: String,
target_node: NodeId,
target_param: String,
last_value: f64,
}
impl AnyServo for TestServo {
fn update(&mut self, time: f64) -> Option<SetParameter> {
let value = (time * 2.0).sin() * 0.5 + 0.5;
if (value - self.last_value).abs() > 0.01 {
self.last_value = value;
Some(SetParameter::new(
PortId::param(self.target_node, 0),
ParameterId::new(&self.target_param).unwrap(),
ParamValue::Float(value as f32),
SignalOrigin::Manual,
))
} else {
None
}
}
fn id(&self) -> &str {
&self.id
}
fn set_enabled(&mut self, _enabled: bool) {}
}
pub struct ManagerBuilder {
config: Config,
command_queue: Option<Arc<MpscQueue<SetParameter>>>,
event_channel: Option<crossbeam_channel::Sender<Event>>,
}
impl ManagerBuilder {
pub fn new() -> Self {
Self {
config: Config::default(),
command_queue: None,
event_channel: None,
}
}
pub fn with_config(mut self, config: Config) -> Self {
self.config = config;
self
}
pub fn with_update_rate(mut self, hz: f64) -> Self {
self.config.update_rate_hz = hz;
self
}
pub fn with_command_queue(mut self, queue: Arc<MpscQueue<SetParameter>>) -> Self {
self.command_queue = Some(queue);
self
}
pub fn with_event_channel(mut self, tx: crossbeam_channel::Sender<Event>) -> Self {
self.event_channel = Some(tx);
self.config.log_events = true;
self
}
pub fn with_stats(mut self, enabled: bool) -> Self {
self.config.collect_stats = enabled;
self
}
pub fn build(self) -> Manager {
let queue = self
.command_queue
.unwrap_or_else(|| Arc::new(MpscQueue::with_capacity(self.config.command_queue_size)));
let mut manager = Manager::new(self.config, queue);
if let Some(tx) = self.event_channel {
manager = manager.with_event_channel(tx);
}
manager
}
}
impl Default for ManagerBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
use std::time::Duration;
#[test]
fn test_manager_creation() {
let queue = Arc::new(MpscQueue::with_capacity(1024));
let manager = Manager::new(Config::default(), queue);
assert_eq!(manager.automata.len(), 0);
assert_eq!(manager.mappings.len(), 0);
assert!(!manager.is_running());
}
#[test]
fn test_add_automaton() {
let queue = Arc::new(MpscQueue::with_capacity(1024));
let mut manager = Manager::new(Config::default(), queue);
let result = manager.add_lfo("test_lfo", 1.0, 0.5, 0.0, LfoWaveform::Sine);
assert!(result.is_ok());
assert_eq!(manager.automata.len(), 1);
}
#[test]
fn test_add_mapping() {
let queue = Arc::new(MpscQueue::with_capacity(1024));
let mut manager = Manager::new(Config::default(), queue);
manager.add_midi_mapping(7, None, NodeId(1), "volume", 0.0, 1.0, Transform::Linear);
assert_eq!(manager.mappings.len(), 1);
}
#[test]
fn test_handle_event() {
let queue = Arc::new(MpscQueue::with_capacity(1024));
let mut manager = Manager::new(Config::default(), queue.clone());
manager.add_midi_mapping(7, None, NodeId(1), "volume", 0.0, 1.0, Transform::Linear);
let event = ControlEvent::MidiControl {
channel: 1,
controller: 7,
value: 64,
normalized: 0.5,
};
manager.handle_event(event);
}
#[test]
fn test_start_stop() {
let queue = Arc::new(MpscQueue::with_capacity(1024));
let mut manager = Manager::new(Config::default(), queue);
let result = manager.start();
assert!(result.is_ok());
assert!(manager.is_running());
thread::sleep(Duration::from_millis(100));
manager.stop();
assert!(!manager.is_running());
}
#[test]
fn test_builder() {
let queue = Arc::new(MpscQueue::with_capacity(1024));
let manager = ManagerBuilder::new()
.with_update_rate(500.0)
.with_command_queue(queue)
.with_stats(true)
.build();
assert_eq!(manager.config.update_rate_hz, 500.0);
assert!(manager.config.collect_stats);
}
}