Skip to main content

rill_patchbay/
module_def.rs

1//! Rack module type definitions.
2//!
3//! Always compiled. Serialisation derives are conditional on the
4//! `serde` feature.
5
6#![allow(missing_docs)]
7
8use std::collections::HashMap;
9
10use rill_core::traits::ParamValue;
11
12use crate::automaton::envelope::EnvelopeType;
13use crate::automaton::lfo::LfoWaveform;
14use crate::automaton::sequencer::PlayMode;
15use crate::engine::{ParameterMapping, Transform};
16use crate::strategy::{ConflictStrategy, ControlStrategy};
17
18// ============================================================================
19// AutomatonDef
20// ============================================================================
21
22/// Serializable description of a control automaton.
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24#[derive(Debug, Clone)]
25pub enum AutomatonDef {
26    Lfo {
27        id: String,
28        frequency: f64,
29        amplitude: f64,
30        offset: f64,
31        waveform: LfoWaveform,
32    },
33    Envelope {
34        id: String,
35        envelope_type: EnvelopeType,
36        attack: f64,
37        decay: f64,
38        sustain: f64,
39        release: f64,
40        curve: f64,
41    },
42    Sequencer {
43        id: String,
44        steps: Vec<StepDef>,
45        play_mode: PlayMode,
46        tempo: f64,
47    },
48    NamedFunction {
49        id: String,
50        function_name: String,
51        params: HashMap<String, f64>,
52    },
53    /// Custom automaton — dispatched via [`AutomatonFactory`].
54    Custom {
55        id: String,
56        type_name: String,
57        #[cfg_attr(feature = "serde", serde(default))]
58        params: HashMap<String, ParamValue>,
59    },
60}
61
62impl AutomatonDef {
63    pub fn id(&self) -> &str {
64        match self {
65            AutomatonDef::Lfo { id, .. } => id,
66            AutomatonDef::Envelope { id, .. } => id,
67            AutomatonDef::Sequencer { id, .. } => id,
68            AutomatonDef::NamedFunction { id, .. } => id,
69            AutomatonDef::Custom { id, .. } => id,
70        }
71    }
72}
73
74/// Serializable step for [`AutomatonDef::Sequencer`].
75#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
76#[derive(Debug, Clone)]
77pub struct StepDef {
78    /// Duration in beat fractions (1.0 = quarter note at the given tempo).
79    pub duration: f64,
80}
81
82// ============================================================================
83// ServoDef
84// ============================================================================
85
86/// Type of value mapping for a servo.
87#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
88#[derive(Debug, Clone, Copy, PartialEq)]
89pub enum MappingType {
90    Linear,
91    Exponential,
92    Logarithmic,
93    Inverted,
94}
95
96impl MappingType {
97    pub fn to_parameter_mapping(self) -> ParameterMapping {
98        match self {
99            MappingType::Linear => ParameterMapping::Linear,
100            MappingType::Exponential => ParameterMapping::Exponential,
101            MappingType::Logarithmic => ParameterMapping::Logarithmic,
102            MappingType::Inverted => ParameterMapping::Inverted,
103        }
104    }
105}
106
107/// Describes a servo: which automaton drives which node parameter.
108#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
109#[derive(Debug, Clone)]
110pub struct ServoDef {
111    pub automaton_id: String,
112    pub target_node: u32,
113    pub target_param: String,
114    pub mapping: MappingType,
115    pub min: f64,
116    pub max: f64,
117    pub enabled: bool,
118
119    /// Async mode: update interval in milliseconds.
120    /// When `Some`, the automaton runs as a green thread (tokio task)
121    /// with the given interval. When `None`, falls back to sync mode
122    /// (requires manual `Patchbay::update()` calls).
123    #[cfg_attr(feature = "serde", serde(default))]
124    pub async_interval_ms: Option<f64>,
125
126    /// Async mode: control strategy (defaults to `Absolute`).
127    #[cfg_attr(feature = "serde", serde(default))]
128    pub control_strategy: Option<ControlStrategy>,
129
130    /// Async mode: conflict resolution (defaults to `LastWriteWins`).
131    #[cfg_attr(feature = "serde", serde(default))]
132    pub conflict_strategy: Option<ConflictStrategy>,
133
134    /// Optional value table for index-based automata.
135    /// When set, the servo looks up `table[automaton_output]`.
136    #[cfg_attr(
137        feature = "serde",
138        serde(default, skip_serializing_if = "Option::is_none")
139    )]
140    pub table: Option<Vec<ParamValue>>,
141}
142
143// ============================================================================
144// MappingDef
145// ============================================================================
146
147/// Serializable transform — Linear, Exponential, Logarithmic, or Inverted.
148#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
149#[derive(Debug, Clone)]
150pub enum TransformDef {
151    Linear,
152    Exponential,
153    Logarithmic,
154    Inverted,
155}
156
157impl TransformDef {
158    pub fn to_transform(&self) -> Transform {
159        match self {
160            TransformDef::Linear => Transform::Linear,
161            TransformDef::Exponential => Transform::Exponential,
162            TransformDef::Logarithmic => Transform::Logarithmic,
163            TransformDef::Inverted => Transform::Inverted,
164        }
165    }
166}
167
168/// Describes a mapping from an external event to a node parameter.
169#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
170#[derive(Debug, Clone)]
171pub struct MappingDef {
172    pub event_pattern: crate::engine::EventPattern,
173    pub target_node: u32,
174    pub target_param: String,
175    pub transform: TransformDef,
176    pub min: f64,
177    pub max: f64,
178    pub enabled: bool,
179}
180
181impl MappingDef {
182    pub fn to_mapping(&self) -> crate::engine::Mapping {
183        use crate::engine::Target;
184        crate::engine::Mapping::new(
185            self.event_pattern.clone(),
186            Target {
187                node_id: rill_core::traits::NodeId(self.target_node),
188                param_name: self.target_param.clone(),
189                min: self.min as f32,
190                max: self.max as f32,
191            },
192            self.transform.to_transform(),
193        )
194    }
195}
196
197// ============================================================================
198// SensorDef
199// ============================================================================
200
201/// Serializable external input sensor.
202#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
203#[derive(Debug, Clone)]
204pub enum SensorDef {
205    /// MIDI input.
206    Midi {
207        /// Backend type — `"midir"` or `"alsa_seq"`.
208        backend: String,
209        /// Port name for the backend.
210        port_name: String,
211        /// Event-to-parameter mappings (CC → param, Note → param, etc.).
212        #[cfg_attr(feature = "serde", serde(default))]
213        mappings: Vec<MappingDef>,
214    },
215    /// OSC input over UDP.
216    Osc {
217        /// UDP port to listen on.
218        port: u16,
219        /// Event-to-parameter mappings (OSC address → param).
220        #[cfg_attr(feature = "serde", serde(default))]
221        mappings: Vec<MappingDef>,
222    },
223}
224
225impl SensorDef {
226    /// Returns the event-to-parameter mappings, if any.
227    pub fn get_mappings(&self) -> Vec<crate::engine::Mapping> {
228        match self {
229            SensorDef::Midi { mappings, .. } => mappings.iter().map(|m| m.to_mapping()).collect(),
230            SensorDef::Osc { mappings, .. } => mappings.iter().map(|m| m.to_mapping()).collect(),
231        }
232    }
233
234    #[cfg(any(feature = "midi", feature = "osc"))]
235    pub fn into_sensor(&self) -> Option<Box<dyn crate::sensor::Sensor>> {
236        match self {
237            #[cfg(feature = "midi")]
238            SensorDef::Midi {
239                backend,
240                port_name,
241                mappings: _,
242            } => {
243                use rill_io::midi_input::MidiInput;
244                let be: Box<dyn MidiInput> = match backend.as_str() {
245                    "midir" => Box::new(rill_io::backends::MidirBackend::new(port_name).ok()?),
246                    "alsa_seq" => {
247                        #[cfg(feature = "alsa")]
248                        {
249                            Box::new(
250                                rill_io::backends::AlsaSeqBackend::new(port_name)
251                                    .map_err(|e| log::warn!("AlsaSeqBackend: {e}"))
252                                    .ok()?,
253                            )
254                        }
255                        #[cfg(not(feature = "alsa"))]
256                        {
257                            log::warn!("ALSA seq backend requires 'alsa' feature");
258                            return None;
259                        }
260                    }
261                    _ => {
262                        log::warn!("unknown MIDI backend '{backend}'");
263                        return None;
264                    }
265                };
266                let hub = crate::midi::MidiHub::new(port_name.as_str(), be);
267                Some(Box::new(hub))
268            }
269            #[cfg(feature = "osc")]
270            SensorDef::Osc { port, mappings: _ } => {
271                let addr = std::net::SocketAddr::from(([0, 0, 0, 0], *port));
272                let sensor = crate::osc::OscSensor::new(format!("osc_{port}"), addr);
273                Some(Box::new(sensor))
274            }
275            #[allow(unreachable_patterns)]
276            _ => None,
277        }
278    }
279    #[cfg(not(any(feature = "midi", feature = "osc")))]
280    pub fn into_sensor(&self) -> Option<Box<dyn crate::sensor::Sensor>> {
281        None
282    }
283}
284
285// ============================================================================
286// ClockDef — MIDI clock output definition
287// ============================================================================
288
289/// Serializable MIDI clock output configuration.
290#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
291#[derive(Debug, Clone)]
292pub struct ClockDef {
293    /// Backend type — `"midir"`, `"alsa_seq"`, or `"jack"`.
294    pub backend: String,
295    /// Port name for the backend.
296    pub port_name: String,
297    /// Start clock automatically when the system launches.
298    #[cfg_attr(feature = "serde", serde(default))]
299    pub auto_start: bool,
300}
301
302// ============================================================================
303// ModuleDef — unified servo, sensor, and custom module serialization
304// ============================================================================
305
306/// A rack module — either a Servo (automaton → parameter), a Sensor (external input),
307/// or a Custom module dispatched through [`ModuleFactory`](crate::module_factory::ModuleFactory).
308#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
309#[derive(Debug, Clone)]
310pub enum ModuleDef {
311    /// MIDI clock output module.
312    Clock(ClockDef),
313    /// Servo: automaton → graph parameter bridge.
314    Servo(ServoDef),
315    /// Sensor: external input (MIDI, OSC, etc.).
316    Sensor(SensorDef),
317    /// Custom module — dispatched through the module factory.
318    Custom {
319        /// Module type name for factory lookup.
320        type_name: String,
321        /// Module-specific parameters.
322        #[cfg_attr(feature = "serde", serde(default))]
323        params: HashMap<String, ParamValue>,
324    },
325}
326
327impl ModuleDef {
328    /// Returns the factory registration key for this module.
329    pub fn type_name(&self) -> &str {
330        match self {
331            ModuleDef::Clock(_) => "clock",
332            ModuleDef::Servo(_) => "servo",
333            ModuleDef::Sensor(SensorDef::Midi { .. }) => "midi",
334            ModuleDef::Sensor(SensorDef::Osc { .. }) => "osc",
335            ModuleDef::Custom { type_name, .. } => type_name,
336        }
337    }
338}