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}
216
217impl SensorDef {
218    /// Returns the event-to-parameter mappings, if any.
219    pub fn get_mappings(&self) -> Vec<crate::engine::Mapping> {
220        match self {
221            SensorDef::Midi { mappings, .. } => mappings.iter().map(|m| m.to_mapping()).collect(),
222        }
223    }
224
225    #[cfg(feature = "midi")]
226    pub fn into_sensor(&self) -> Option<Box<dyn crate::sensor::Sensor>> {
227        match self {
228            SensorDef::Midi {
229                backend,
230                port_name,
231                mappings: _,
232            } => {
233                use rill_io::midi_backend::MidiBackend;
234                let be: Box<dyn MidiBackend> = match backend.as_str() {
235                    "midir" => Box::new(rill_io::backends::MidirBackend::new(port_name).ok()?),
236                    "alsa_seq" => {
237                        #[cfg(feature = "alsa")]
238                        {
239                            Box::new(
240                                rill_io::backends::AlsaSeqBackend::new(port_name)
241                                    .map_err(|e| log::warn!("AlsaSeqBackend: {e}"))
242                                    .ok()?,
243                            )
244                        }
245                        #[cfg(not(feature = "alsa"))]
246                        {
247                            log::warn!("ALSA seq backend requires 'alsa' feature");
248                            return None;
249                        }
250                    }
251                    _ => {
252                        log::warn!("unknown MIDI backend '{backend}'");
253                        return None;
254                    }
255                };
256                let hub = crate::midi::MidiHub::new(port_name.as_str(), be);
257                Some(Box::new(hub))
258            }
259        }
260    }
261    #[cfg(not(feature = "midi"))]
262    pub fn into_sensor(&self) -> Option<Box<dyn crate::sensor::Sensor>> {
263        None
264    }
265}
266
267// ============================================================================
268// ModuleDef — unified servo, sensor, and custom module serialization
269// ============================================================================
270
271/// A rack module — either a Servo (automaton → parameter), a Sensor (external input),
272/// or a Custom module dispatched through [`ModuleFactory`](crate::module_factory::ModuleFactory).
273#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
274#[derive(Debug, Clone)]
275pub enum ModuleDef {
276    /// Servo: automaton → graph parameter bridge.
277    Servo(ServoDef),
278    /// Sensor: external input (MIDI, OSC, etc.).
279    Sensor(SensorDef),
280    /// Custom module — dispatched through the module factory.
281    Custom {
282        /// Module type name for factory lookup.
283        type_name: String,
284        /// Module-specific parameters.
285        #[cfg_attr(feature = "serde", serde(default))]
286        params: HashMap<String, ParamValue>,
287    },
288}
289
290impl ModuleDef {
291    /// Returns the factory registration key for this module.
292    pub fn type_name(&self) -> &str {
293        match self {
294            ModuleDef::Servo(_) => "servo",
295            ModuleDef::Sensor(_) => "midi",
296            ModuleDef::Custom { type_name, .. } => type_name,
297        }
298    }
299}