Skip to main content

rill_core/queues/
signal.rs

1//! Signal and command types for queues.
2//!
3//! This module defines all command types that can be sent through queues
4//! between Rill components. Each command type represents a specific
5//! action or event in the system.
6//!
7//! ## Command hierarchy
8//!
9//! - `CommandEnum` — top-level enum wrapping all command variants
10//! - `SetParameter` — parameter change for a signal graph node
11//! - `AutomatonCommand` — automaton control
12//! - `SensorCommand` — sensor control
13//! - `ServoCommand` — servo control
14//!
15//! ## Example
16//!
17//! ```rust
18//! use rill_core::queues::*;
19//! use rill_core::traits::*;
20//! #
21//! // Create a command queue
22//! let queue: CommandQueue<CommandEnum> = CommandQueue::new("signal-control", 1024);
23//!
24//! // Create identifiers
25//! let node = NodeId(1);
26//! let port = PortId::control_in(node, 0);
27//! let param = ParameterId::new("gain").unwrap();
28//!
29//! // Somewhere in the world of automatons
30//! let cmd = SetParameter::new(port, param, ParamValue::Float(0.5), SignalOrigin::Automaton("lfo".into()));
31//! queue.send(CommandEnum::SetParameter(cmd)).unwrap();
32//!
33//! // Somewhere in the sound world
34//! while let Ok(cmd_enum) = queue.try_recv() {
35//!     if let CommandEnum::SetParameter(cmd) = cmd_enum {
36//!         // apply_parameter(cmd); // function must be defined
37//!     }
38//! }
39//! # Ok::<(), Box<dyn std::error::Error>>(())
40//! ```
41
42use super::command::Command;
43use crate::traits::{ParamValue, ParameterId, PortId};
44use std::fmt;
45use std::time::{SystemTime, UNIX_EPOCH};
46
47//==============================================================================
48// SignalOrigin — signal source
49//==============================================================================
50
51/// Origin of a signal or command.
52///
53/// Used for tracking command provenance, feedback-loop prevention,
54/// and telemetry attribution.
55#[derive(Debug, Clone, PartialEq, Eq, Hash)]
56pub enum SignalOrigin {
57    /// Command from an automaton (LFO, envelope, sequencer).
58    Automaton(String),
59    /// Command from a sensor (physical input device).
60    Sensor(String),
61    /// Command from a servo (physical output device).
62    Servo(String),
63    /// Command from an external source (OSC, MIDI, etc.).
64    External(String),
65    /// Manual user interaction (UI slider, button, etc.).
66    Manual,
67    /// Command from a script.
68    Script,
69}
70
71impl SignalOrigin {
72    /// Return the human-readable name of this source.
73    pub fn name(&self) -> &str {
74        match self {
75            SignalOrigin::Automaton(name) => name,
76            SignalOrigin::Sensor(name) => name,
77            SignalOrigin::Servo(name) => name,
78            SignalOrigin::External(name) => name,
79            SignalOrigin::Manual => "manual",
80            SignalOrigin::Script => "script",
81        }
82    }
83
84    /// Return the type category of this source (e.g. "automaton", "sensor").
85    pub fn kind(&self) -> &'static str {
86        match self {
87            SignalOrigin::Automaton(_) => "automaton",
88            SignalOrigin::Sensor(_) => "sensor",
89            SignalOrigin::Servo(_) => "servo",
90            SignalOrigin::External(_) => "external",
91            SignalOrigin::Manual => "manual",
92            SignalOrigin::Script => "script",
93        }
94    }
95}
96
97impl fmt::Display for SignalOrigin {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        match self {
100            SignalOrigin::Automaton(name) => write!(f, "⚙️ {}", name),
101            SignalOrigin::Sensor(name) => write!(f, "👁️ {}", name),
102            SignalOrigin::Servo(name) => write!(f, "🦾 {}", name),
103            SignalOrigin::External(name) => write!(f, "🌍 {}", name),
104            SignalOrigin::Manual => write!(f, "👤 manual"),
105            SignalOrigin::Script => write!(f, "📜 script"),
106        }
107    }
108}
109
110// ===== SetParameter =====
111
112/// Command to change a parameter value on a signal graph node.
113#[derive(Debug, Clone)]
114pub struct SetParameter {
115    /// Target port.
116    pub port: PortId,
117    /// Target parameter identifier.
118    pub parameter: ParameterId,
119    /// New parameter value.
120    pub value: ParamValue,
121    /// Origin of this command.
122    pub source: SignalOrigin,
123    /// Unix timestamp (microseconds).
124    pub timestamp: u64,
125}
126
127impl SetParameter {
128    /// Create a new parameter-change command with the current timestamp.
129    pub fn new(
130        port: PortId,
131        parameter: ParameterId,
132        value: ParamValue,
133        source: SignalOrigin,
134    ) -> Self {
135        Self {
136            port,
137            parameter,
138            value,
139            source,
140            timestamp: Self::now(),
141        }
142    }
143
144    /// Create a new parameter-change command with an explicit timestamp.
145    pub fn with_timestamp(
146        port: PortId,
147        parameter: ParameterId,
148        value: ParamValue,
149        source: SignalOrigin,
150        timestamp: u64,
151    ) -> Self {
152        Self {
153            port,
154            parameter,
155            value,
156            source,
157            timestamp,
158        }
159    }
160
161    /// Return the current Unix time in microseconds.
162    pub fn now() -> u64 {
163        SystemTime::now()
164            .duration_since(UNIX_EPOCH)
165            .unwrap_or_default()
166            .as_micros() as u64
167    }
168}
169
170impl PartialEq for SetParameter {
171    fn eq(&self, other: &Self) -> bool {
172        self.port == other.port
173            && self.parameter == other.parameter
174            && self.value == other.value
175            && self.source == other.source
176    }
177}
178
179impl fmt::Display for SetParameter {
180    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181        write!(
182            f,
183            "[{}] {} → {}::{} = {:?}",
184            self.timestamp, self.source, self.port, self.parameter, self.value
185        )
186    }
187}
188
189// Implement the Command trait for SetParameter
190impl Command for SetParameter {}
191
192// ===== AutomatonCommand =====
193
194/// Commands for controlling automata (LFOs, envelopes, sequencers).
195#[derive(Debug, Clone)]
196pub enum AutomatonCommand {
197    /// Enable or disable an automaton by ID.
198    SetEnabled {
199        /// Automaton identifier.
200        id: String,
201        /// Whether the automaton should be enabled.
202        enabled: bool,
203    },
204    /// Set a named parameter on an automaton.
205    SetParameter {
206        /// Automaton identifier.
207        id: String,
208        /// Parameter name.
209        name: String,
210        /// Parameter value.
211        value: f32,
212    },
213    /// Reset an automaton to its initial state.
214    Reset {
215        /// Automaton identifier.
216        id: String,
217    },
218    /// Connect an automaton output to another automaton input.
219    Connect {
220        /// Source automaton identifier.
221        from: String,
222        /// Destination automaton identifier.
223        to: String,
224        /// Connection gain.
225        gain: f32,
226    },
227    /// Disconnect two automata.
228    Disconnect {
229        /// Source automaton identifier.
230        from: String,
231        /// Destination automaton identifier.
232        to: String,
233    },
234    /// Create a new automaton instance.
235    Create {
236        /// Automaton type (e.g. "lfo", "envelope").
237        kind: String,
238        /// New automaton identifier.
239        id: String,
240        /// Initial parameter values.
241        params: Vec<(String, f32)>,
242    },
243    /// Destroy an automaton by ID.
244    Destroy {
245        /// Automaton identifier to remove.
246        id: String,
247    },
248}
249
250impl AutomatonCommand {
251    /// Return the target automaton ID, if applicable.
252    pub fn automaton_id(&self) -> Option<&str> {
253        match self {
254            AutomatonCommand::SetEnabled { id, .. } => Some(id),
255            AutomatonCommand::SetParameter { id, .. } => Some(id),
256            AutomatonCommand::Reset { id } => Some(id),
257            AutomatonCommand::Connect { from, to: _to, .. } => Some(from),
258            AutomatonCommand::Disconnect { from, to: _to } => Some(from),
259            AutomatonCommand::Create { id, .. } => Some(id),
260            AutomatonCommand::Destroy { id } => Some(id),
261        }
262    }
263}
264
265impl fmt::Display for AutomatonCommand {
266    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
267        match self {
268            AutomatonCommand::SetEnabled { id, enabled } => {
269                write!(f, "Automaton[{}] set_enabled({})", id, enabled)
270            }
271            AutomatonCommand::SetParameter { id, name, value } => {
272                write!(f, "Automaton[{}] set_param({}={:.2})", id, name, value)
273            }
274            AutomatonCommand::Reset { id } => {
275                write!(f, "Automaton[{}] reset()", id)
276            }
277            AutomatonCommand::Connect { from, to, gain } => {
278                write!(f, "Automaton connect {} → {} gain={:.2}", from, to, gain)
279            }
280            AutomatonCommand::Disconnect { from, to } => {
281                write!(f, "Automaton disconnect {} → {}", from, to)
282            }
283            AutomatonCommand::Create { kind, id, params } => {
284                write!(
285                    f,
286                    "Automaton create {} as {} with {} params",
287                    kind,
288                    id,
289                    params.len()
290                )
291            }
292            AutomatonCommand::Destroy { id } => {
293                write!(f, "Automaton destroy {}", id)
294            }
295        }
296    }
297}
298
299impl Command for AutomatonCommand {}
300
301// ===== SensorCommand =====
302
303/// Type of sensor calibration to perform.
304#[derive(Debug, Clone)]
305pub enum CalibrationKind {
306    /// Automatically determine min/max from signal range.
307    Auto,
308    /// Set the current sensor reading as the minimum value.
309    SetCurrentAsMin,
310    /// Set the current sensor reading as the maximum value.
311    SetCurrentAsMax,
312    /// Reset calibration to factory defaults.
313    Reset,
314}
315
316impl fmt::Display for CalibrationKind {
317    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
318        match self {
319            CalibrationKind::Auto => write!(f, "auto"),
320            CalibrationKind::SetCurrentAsMin => write!(f, "set_min"),
321            CalibrationKind::SetCurrentAsMax => write!(f, "set_max"),
322            CalibrationKind::Reset => write!(f, "reset"),
323        }
324    }
325}
326
327/// Commands for controlling sensors (physical input devices).
328#[derive(Debug, Clone)]
329pub enum SensorCommand {
330    /// Start listening to a sensor data source.
331    StartListening {
332        /// Sensor identifier.
333        id: String,
334        /// Data source to listen to.
335        source: String,
336    },
337    /// Stop listening to a sensor.
338    StopListening {
339        /// Sensor identifier.
340        id: String,
341    },
342    /// Set sensor sensitivity.
343    SetSensitivity {
344        /// Sensor identifier.
345        id: String,
346        /// Sensitivity value.
347        value: f32,
348    },
349    /// Calibrate a sensor.
350    Calibrate {
351        /// Sensor identifier.
352        id: String,
353        /// Calibration type.
354        kind: CalibrationKind,
355    },
356    /// Enable or disable a sensor.
357    SetEnabled {
358        /// Sensor identifier.
359        id: String,
360        /// Whether the sensor should be enabled.
361        enabled: bool,
362    },
363}
364
365impl SensorCommand {
366    /// Return the target sensor ID.
367    pub fn sensor_id(&self) -> &str {
368        match self {
369            SensorCommand::StartListening { id, .. } => id,
370            SensorCommand::StopListening { id } => id,
371            SensorCommand::SetSensitivity { id, .. } => id,
372            SensorCommand::Calibrate { id, .. } => id,
373            SensorCommand::SetEnabled { id, .. } => id,
374        }
375    }
376}
377
378impl fmt::Display for SensorCommand {
379    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
380        match self {
381            SensorCommand::StartListening { id, source } => {
382                write!(f, "Sensor[{}] start listening to {}", id, source)
383            }
384            SensorCommand::StopListening { id } => {
385                write!(f, "Sensor[{}] stop listening", id)
386            }
387            SensorCommand::SetSensitivity { id, value } => {
388                write!(f, "Sensor[{}] set sensitivity to {:.2}", id, value)
389            }
390            SensorCommand::Calibrate { id, kind } => {
391                write!(f, "Sensor[{}] calibrate {}", id, kind)
392            }
393            SensorCommand::SetEnabled { id, enabled } => {
394                write!(f, "Sensor[{}] set enabled({})", id, enabled)
395            }
396        }
397    }
398}
399
400impl Command for SensorCommand {}
401
402// ===== ServoCommand =====
403
404/// Mapping function type for servo output value transformation.
405#[derive(Debug, Clone)]
406pub enum MappingType {
407    /// Linear mapping (identity).
408    Linear,
409    /// Exponential mapping.
410    Exponential,
411    /// Logarithmic mapping.
412    Logarithmic,
413    /// Inverted (reverse) mapping.
414    Inverted,
415    /// Custom named mapping function.
416    Custom(String),
417}
418
419impl fmt::Display for MappingType {
420    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
421        match self {
422            MappingType::Linear => write!(f, "linear"),
423            MappingType::Exponential => write!(f, "exponential"),
424            MappingType::Logarithmic => write!(f, "logarithmic"),
425            MappingType::Inverted => write!(f, "inverted"),
426            MappingType::Custom(s) => write!(f, "custom({})", s),
427        }
428    }
429}
430
431/// Commands for controlling servos (physical output devices).
432#[derive(Debug, Clone)]
433pub enum ServoCommand {
434    /// Bind a servo to follow an automaton output.
435    BindToAutomaton {
436        /// Servo identifier.
437        servo_id: String,
438        /// Automaton identifier to bind to.
439        automaton_id: String,
440    },
441    /// Bind a servo directly to a signal graph parameter.
442    BindToParameter {
443        /// Servo identifier.
444        servo_id: String,
445        /// Target port.
446        port: PortId,
447        /// Target parameter.
448        parameter: ParameterId,
449    },
450    /// Unbind a servo from all sources.
451    Unbind {
452        /// Servo identifier.
453        servo_id: String,
454    },
455    /// Set the output range of a servo.
456    SetRange {
457        /// Servo identifier.
458        servo_id: String,
459        /// Minimum output value.
460        min: f32,
461        /// Maximum output value.
462        max: f32,
463    },
464    /// Set the value mapping function for a servo.
465    SetMapping {
466        /// Servo identifier.
467        servo_id: String,
468        /// Mapping type.
469        mapping: MappingType,
470    },
471    /// Enable or disable a servo.
472    SetEnabled {
473        /// Servo identifier.
474        servo_id: String,
475        /// Whether the servo should be enabled.
476        enabled: bool,
477    },
478}
479
480impl ServoCommand {
481    /// Return the target servo ID.
482    pub fn servo_id(&self) -> &str {
483        match self {
484            ServoCommand::BindToAutomaton { servo_id, .. } => servo_id,
485            ServoCommand::BindToParameter { servo_id, .. } => servo_id,
486            ServoCommand::Unbind { servo_id } => servo_id,
487            ServoCommand::SetRange { servo_id, .. } => servo_id,
488            ServoCommand::SetMapping { servo_id, .. } => servo_id,
489            ServoCommand::SetEnabled { servo_id, .. } => servo_id,
490        }
491    }
492}
493
494impl fmt::Display for ServoCommand {
495    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
496        match self {
497            ServoCommand::BindToAutomaton {
498                servo_id,
499                automaton_id,
500            } => {
501                write!(f, "Servo[{}] bind to automaton {}", servo_id, automaton_id)
502            }
503            ServoCommand::BindToParameter {
504                servo_id,
505                port,
506                parameter,
507            } => {
508                write!(f, "Servo[{}] bind to {}::{}", servo_id, port, parameter)
509            }
510            ServoCommand::Unbind { servo_id } => {
511                write!(f, "Servo[{}] unbind", servo_id)
512            }
513            ServoCommand::SetRange { servo_id, min, max } => {
514                write!(f, "Servo[{}] set range [{}, {}]", servo_id, min, max)
515            }
516            ServoCommand::SetMapping { servo_id, mapping } => {
517                write!(f, "Servo[{}] set mapping {}", servo_id, mapping)
518            }
519            ServoCommand::SetEnabled { servo_id, enabled } => {
520                write!(f, "Servo[{}] set enabled({})", servo_id, enabled)
521            }
522        }
523    }
524}
525
526impl Command for ServoCommand {}
527
528// ===== CommandType (formerly Command) — common command type =====
529
530/// Runtime command type identifier.
531#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
532pub enum CommandType {
533    /// Parameter change command.
534    SetParameter,
535    /// Automaton control command.
536    Automaton,
537    /// Sensor control command.
538    Sensor,
539    /// Servo control command.
540    Servo,
541    /// System command.
542    System,
543}
544
545impl fmt::Display for CommandType {
546    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
547        match self {
548            CommandType::SetParameter => write!(f, "SetParameter"),
549            CommandType::Automaton => write!(f, "Automaton"),
550            CommandType::Sensor => write!(f, "Sensor"),
551            CommandType::Servo => write!(f, "Servo"),
552            CommandType::System => write!(f, "System"),
553        }
554    }
555}
556
557/// Universal command enum combining all possible command types.
558///
559/// Useful when a single queue must transport multiple command types,
560/// or when the command type is not known ahead of time.
561#[derive(Debug, Clone)]
562pub enum CommandEnum {
563    /// Parameter change command.
564    SetParameter(SetParameter),
565    /// Automaton control command.
566    Automaton(AutomatonCommand),
567    /// Sensor control command.
568    Sensor(SensorCommand),
569    /// Servo control command.
570    Servo(ServoCommand),
571    /// System-level command with opaque payload.
572    System {
573        /// System command kind.
574        kind: String,
575        /// Opaque command data.
576        data: Vec<u8>,
577    },
578}
579
580impl CommandEnum {
581    /// Return the runtime type tag of this command.
582    pub fn command_type(&self) -> CommandType {
583        match self {
584            CommandEnum::SetParameter(_) => CommandType::SetParameter,
585            CommandEnum::Automaton(_) => CommandType::Automaton,
586            CommandEnum::Sensor(_) => CommandType::Sensor,
587            CommandEnum::Servo(_) => CommandType::Servo,
588            CommandEnum::System { .. } => CommandType::System,
589        }
590    }
591
592    /// If this is a `SetParameter` command, return the target `NodeId`.
593    pub fn target_node_id(&self) -> Option<crate::traits::NodeId> {
594        match self {
595            CommandEnum::SetParameter(cmd) => Some(cmd.port.node_id()),
596            _ => None,
597        }
598    }
599
600    /// Return the timestamp if the command carries one.
601    pub fn timestamp(&self) -> Option<u64> {
602        match self {
603            CommandEnum::SetParameter(cmd) => Some(cmd.timestamp),
604            _ => None,
605        }
606    }
607
608    /// Try to downcast to `SetParameter`.
609    pub fn as_set_parameter(&self) -> Option<&SetParameter> {
610        match self {
611            CommandEnum::SetParameter(cmd) => Some(cmd),
612            _ => None,
613        }
614    }
615
616    /// Try to downcast to `AutomatonCommand`.
617    pub fn as_automaton(&self) -> Option<&AutomatonCommand> {
618        match self {
619            CommandEnum::Automaton(cmd) => Some(cmd),
620            _ => None,
621        }
622    }
623
624    /// Try to downcast to `SensorCommand`.
625    pub fn as_sensor(&self) -> Option<&SensorCommand> {
626        match self {
627            CommandEnum::Sensor(cmd) => Some(cmd),
628            _ => None,
629        }
630    }
631
632    /// Try to downcast to `ServoCommand`.
633    pub fn as_servo(&self) -> Option<&ServoCommand> {
634        match self {
635            CommandEnum::Servo(cmd) => Some(cmd),
636            _ => None,
637        }
638    }
639}
640
641impl fmt::Display for CommandEnum {
642    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
643        match self {
644            CommandEnum::SetParameter(cmd) => write!(f, "{}", cmd),
645            CommandEnum::Automaton(cmd) => write!(f, "{}", cmd),
646            CommandEnum::Sensor(cmd) => write!(f, "{}", cmd),
647            CommandEnum::Servo(cmd) => write!(f, "{}", cmd),
648            CommandEnum::System { kind, data } => {
649                write!(f, "System[{}] ({} bytes)", kind, data.len())
650            }
651        }
652    }
653}
654
655// Implement the Command trait for CommandEnum so it can be used in CommandQueue
656impl Command for CommandEnum {}
657
658// ===== Conversions =====
659
660/// Marker trait for types that can be converted into a command.
661pub trait ToCommand: Send + 'static {
662    /// The command type this type converts into.
663    type Command: Into<CommandEnum>;
664
665    /// Convert self into a command.
666    fn to_command(self) -> Self::Command;
667}
668
669/// Marker trait for types that can be constructed from a command.
670pub trait FromCommand: Sized {
671    /// The command type this type is constructed from.
672    type Command: TryInto<Self> + Clone;
673
674    /// Try to construct from a command.
675    fn from_command(cmd: Self::Command) -> Option<Self>;
676}
677
678impl From<SetParameter> for CommandEnum {
679    fn from(cmd: SetParameter) -> Self {
680        CommandEnum::SetParameter(cmd)
681    }
682}
683
684impl From<AutomatonCommand> for CommandEnum {
685    fn from(cmd: AutomatonCommand) -> Self {
686        CommandEnum::Automaton(cmd)
687    }
688}
689
690impl From<SensorCommand> for CommandEnum {
691    fn from(cmd: SensorCommand) -> Self {
692        CommandEnum::Sensor(cmd)
693    }
694}
695
696impl From<ServoCommand> for CommandEnum {
697    fn from(cmd: ServoCommand) -> Self {
698        CommandEnum::Servo(cmd)
699    }
700}
701
702impl TryFrom<CommandEnum> for SetParameter {
703    type Error = ();
704
705    fn try_from(cmd: CommandEnum) -> Result<Self, Self::Error> {
706        match cmd {
707            CommandEnum::SetParameter(cmd) => Ok(cmd),
708            _ => Err(()),
709        }
710    }
711}
712
713impl TryFrom<CommandEnum> for AutomatonCommand {
714    type Error = ();
715
716    fn try_from(cmd: CommandEnum) -> Result<Self, Self::Error> {
717        match cmd {
718            CommandEnum::Automaton(cmd) => Ok(cmd),
719            _ => Err(()),
720        }
721    }
722}
723
724impl TryFrom<CommandEnum> for SensorCommand {
725    type Error = ();
726
727    fn try_from(cmd: CommandEnum) -> Result<Self, Self::Error> {
728        match cmd {
729            CommandEnum::Sensor(cmd) => Ok(cmd),
730            _ => Err(()),
731        }
732    }
733}
734
735impl TryFrom<CommandEnum> for ServoCommand {
736    type Error = ();
737
738    fn try_from(cmd: CommandEnum) -> Result<Self, Self::Error> {
739        match cmd {
740            CommandEnum::Servo(cmd) => Ok(cmd),
741            _ => Err(()),
742        }
743    }
744}