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 automatons.
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    /// String anchor name for rill-lang graph nodes.
143    /// When set, the servo sends `SetParameter` to the
144    /// RillGraphEngine using this anchor instead of a `PortId`.
145    #[cfg_attr(feature = "serde", serde(default))]
146    pub target_anchor: Option<String>,
147}
148
149// ============================================================================
150// MappingDef
151// ============================================================================
152
153/// Serializable transform — Linear, Exponential, Logarithmic, or Inverted.
154#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
155#[derive(Debug, Clone)]
156pub enum TransformDef {
157    Linear,
158    Exponential,
159    Logarithmic,
160    Inverted,
161}
162
163impl TransformDef {
164    pub fn to_transform(&self) -> Transform {
165        match self {
166            TransformDef::Linear => Transform::Linear,
167            TransformDef::Exponential => Transform::Exponential,
168            TransformDef::Logarithmic => Transform::Logarithmic,
169            TransformDef::Inverted => Transform::Inverted,
170        }
171    }
172}
173
174/// Describes a mapping from an external event to a node parameter.
175#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
176#[derive(Debug, Clone)]
177pub struct MappingDef {
178    pub event_pattern: crate::engine::EventPattern,
179    pub target_node: u32,
180    pub target_param: String,
181    pub transform: TransformDef,
182    pub min: f64,
183    pub max: f64,
184    pub enabled: bool,
185}
186
187impl MappingDef {
188    pub fn to_mapping(&self) -> crate::engine::Mapping {
189        use crate::engine::Target;
190        crate::engine::Mapping::new(
191            self.event_pattern.clone(),
192            Target {
193                node_id: self.target_node,
194                param_name: self.target_param.clone(),
195                min: self.min as f32,
196                max: self.max as f32,
197            },
198            self.transform.to_transform(),
199        )
200    }
201}
202
203// ============================================================================
204// SensorDef
205// ============================================================================
206
207/// Serializable external input sensor.
208#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
209#[derive(Debug, Clone)]
210pub enum SensorDef {
211    /// MIDI input.
212    Midi {
213        /// Backend type — `"midir"` or `"alsa_seq"`.
214        backend: String,
215        /// Port name for the backend.
216        port_name: String,
217        /// Event-to-parameter mappings (CC → param, Note → param, etc.).
218        #[cfg_attr(feature = "serde", serde(default))]
219        mappings: Vec<MappingDef>,
220    },
221    /// OSC input over UDP.
222    Osc {
223        /// UDP port to listen on.
224        port: u16,
225        /// Event-to-parameter mappings (OSC address → param).
226        #[cfg_attr(feature = "serde", serde(default))]
227        mappings: Vec<MappingDef>,
228    },
229}
230
231impl SensorDef {
232    /// Returns the event-to-parameter mappings, if any.
233    pub fn get_mappings(&self) -> Vec<crate::engine::Mapping> {
234        match self {
235            SensorDef::Midi { mappings, .. } => mappings.iter().map(|m| m.to_mapping()).collect(),
236            SensorDef::Osc { mappings, .. } => mappings.iter().map(|m| m.to_mapping()).collect(),
237        }
238    }
239
240    #[cfg(any(feature = "midi", feature = "osc"))]
241    pub fn into_sensor(&self) -> Option<Box<dyn crate::sensor::Sensor>> {
242        match self {
243            #[cfg(feature = "midi")]
244            SensorDef::Midi {
245                backend,
246                port_name,
247                mappings: _,
248            } => {
249                use rill_io::midi_input::MidiInput;
250                let be: Box<dyn MidiInput> = match backend.as_str() {
251                    "midir" => Box::new(rill_io::backends::MidirBackend::new(port_name).ok()?),
252                    "alsa_seq" => {
253                        #[cfg(feature = "alsa")]
254                        {
255                            Box::new(
256                                rill_io::backends::AlsaSeqBackend::new(port_name)
257                                    .map_err(|e| log::warn!("AlsaSeqBackend: {e}"))
258                                    .ok()?,
259                            )
260                        }
261                        #[cfg(not(feature = "alsa"))]
262                        {
263                            log::warn!("ALSA seq backend requires 'alsa' feature");
264                            return None;
265                        }
266                    }
267                    _ => {
268                        log::warn!("unknown MIDI backend '{backend}'");
269                        return None;
270                    }
271                };
272                let hub = crate::midi::MidiHub::new(port_name.as_str(), be);
273                Some(Box::new(hub))
274            }
275            #[cfg(feature = "osc")]
276            SensorDef::Osc { port, mappings: _ } => {
277                let addr = std::net::SocketAddr::from(([0, 0, 0, 0], *port));
278                let sensor = crate::osc::OscSensor::new(format!("osc_{port}"), addr);
279                Some(Box::new(sensor))
280            }
281            #[allow(unreachable_patterns)]
282            _ => None,
283        }
284    }
285    #[cfg(not(any(feature = "midi", feature = "osc")))]
286    pub fn into_sensor(&self) -> Option<Box<dyn crate::sensor::Sensor>> {
287        None
288    }
289}
290
291// ============================================================================
292// ClockDef — MIDI clock output definition
293// ============================================================================
294
295/// Serializable MIDI clock output configuration.
296#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
297#[derive(Debug, Clone)]
298pub struct ClockDef {
299    /// Backend type — `"midir"`, `"alsa_seq"`, or `"jack"`.
300    pub backend: String,
301    /// Port name for the backend.
302    pub port_name: String,
303    /// Start clock automatically when the system launches.
304    #[cfg_attr(feature = "serde", serde(default))]
305    pub auto_start: bool,
306}
307
308// ============================================================================
309// ModuleDef — unified servo, sensor, and custom module serialization
310// ============================================================================
311
312/// A rack module — either a Servo (automaton → parameter), a Sensor (external input),
313/// or a Custom module dispatched through [`ModuleFactory`](crate::module_factory::ModuleFactory).
314#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
315#[derive(Debug, Clone)]
316pub enum ModuleDef {
317    /// MIDI clock output module.
318    Clock(ClockDef),
319    /// Servo: automaton → graph parameter bridge.
320    Servo(ServoDef),
321    /// Sensor: external input (MIDI, OSC, etc.).
322    Sensor(SensorDef),
323    /// Custom module — dispatched through the module factory.
324    Custom {
325        /// Module type name for factory lookup.
326        type_name: String,
327        /// Module-specific parameters.
328        #[cfg_attr(feature = "serde", serde(default))]
329        params: HashMap<String, ParamValue>,
330    },
331}
332
333impl ModuleDef {
334    /// Returns the factory registration key for this module.
335    pub fn type_name(&self) -> &str {
336        match self {
337            ModuleDef::Clock(_) => "clock",
338            ModuleDef::Servo(_) => "servo",
339            ModuleDef::Sensor(SensorDef::Midi { .. }) => "midi",
340            ModuleDef::Sensor(SensorDef::Osc { .. }) => "osc",
341            ModuleDef::Custom { type_name, .. } => type_name,
342        }
343    }
344}