use super::command::Command;
use crate::traits::{NodeId, ParameterId, PortId};
use std::fmt;
use std::time::{SystemTime, UNIX_EPOCH};
pub const CLOCK_TICK: &str = "clock_tick";
pub const CLOCK_TEMPO: &str = "clock_tempo";
#[derive(Clone)]
pub struct TelemetryTx {
inner: Option<crossbeam_channel::Sender<Telemetry>>,
}
impl TelemetryTx {
pub const fn empty() -> Self {
Self { inner: None }
}
pub fn new(tx: crossbeam_channel::Sender<Telemetry>) -> Self {
Self { inner: Some(tx) }
}
pub fn try_send(&self, event: Telemetry) {
if let Some(ref tx) = self.inner {
let _ = tx.try_send(event);
}
}
pub fn sender(&self) -> Option<&crossbeam_channel::Sender<Telemetry>> {
self.inner.as_ref()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TelemetryKind {
Parameter,
Signal,
Peak,
Event,
Violation,
}
impl fmt::Display for TelemetryKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TelemetryKind::Parameter => write!(f, "parameter"),
TelemetryKind::Signal => write!(f, "signal"),
TelemetryKind::Peak => write!(f, "peak"),
TelemetryKind::Event => write!(f, "event"),
TelemetryKind::Violation => write!(f, "violation"),
}
}
}
#[derive(Debug, Clone)]
pub enum Telemetry {
ParameterValue {
port: PortId,
parameter: ParameterId,
value: f32,
timestamp: u64,
},
SignalData {
node_id: NodeId,
channel: usize,
data: Vec<f32>,
timestamp: u64,
sample_rate: f32,
},
Peak {
port: PortId,
value: f32,
timestamp: u64,
hold_time_ms: Option<u32>,
},
Event {
source: String,
kind: String,
data: Vec<f32>,
timestamp: u64,
description: Option<String>,
},
Violation {
component: String,
expected_ns: u64,
actual_ns: u64,
value: Option<f32>,
timestamp: u64,
},
}
impl Command for Telemetry {}
impl Telemetry {
pub fn now() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_micros() as u64
}
pub fn parameter(port: PortId, parameter: ParameterId, value: f32) -> Self {
Telemetry::ParameterValue {
port,
parameter,
value,
timestamp: Self::now(),
}
}
pub fn parameter_with_time(
port: PortId,
parameter: ParameterId,
value: f32,
timestamp: u64,
) -> Self {
Telemetry::ParameterValue {
port,
parameter,
value,
timestamp,
}
}
pub fn signal(node_id: NodeId, channel: usize, data: Vec<f32>) -> Self {
Telemetry::SignalData {
node_id,
channel,
data,
timestamp: Self::now(),
sample_rate: 44100.0,
}
}
pub fn signal_with_sample_rate(
node_id: NodeId,
channel: usize,
data: Vec<f32>,
sample_rate: f32,
) -> Self {
Telemetry::SignalData {
node_id,
channel,
data,
timestamp: Self::now(),
sample_rate,
}
}
pub fn peak(port: PortId, value: f32) -> Self {
Telemetry::Peak {
port,
value,
timestamp: Self::now(),
hold_time_ms: None,
}
}
pub fn peak_with_hold(port: PortId, value: f32, hold_time_ms: u32) -> Self {
Telemetry::Peak {
port,
value,
timestamp: Self::now(),
hold_time_ms: Some(hold_time_ms),
}
}
pub fn event(source: impl Into<String>, kind: impl Into<String>, data: Vec<f32>) -> Self {
Telemetry::Event {
source: source.into(),
kind: kind.into(),
data,
timestamp: Self::now(),
description: None,
}
}
pub fn event_with_description(
source: impl Into<String>,
kind: impl Into<String>,
data: Vec<f32>,
description: impl Into<String>,
) -> Self {
Telemetry::Event {
source: source.into(),
kind: kind.into(),
data,
timestamp: Self::now(),
description: Some(description.into()),
}
}
pub fn violation(
component: impl Into<String>,
expected_ns: u64,
actual_ns: u64,
value: Option<f32>,
) -> Self {
Telemetry::Violation {
component: component.into(),
expected_ns,
actual_ns,
value,
timestamp: Self::now(),
}
}
pub fn kind(&self) -> TelemetryKind {
match self {
Telemetry::ParameterValue { .. } => TelemetryKind::Parameter,
Telemetry::SignalData { .. } => TelemetryKind::Signal,
Telemetry::Peak { .. } => TelemetryKind::Peak,
Telemetry::Event { .. } => TelemetryKind::Event,
Telemetry::Violation { .. } => TelemetryKind::Violation,
}
}
pub fn timestamp(&self) -> u64 {
match self {
Telemetry::ParameterValue { timestamp, .. } => *timestamp,
Telemetry::SignalData { timestamp, .. } => *timestamp,
Telemetry::Peak { timestamp, .. } => *timestamp,
Telemetry::Event { timestamp, .. } => *timestamp,
Telemetry::Violation { timestamp, .. } => *timestamp,
}
}
}
impl fmt::Display for Telemetry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Telemetry::ParameterValue {
port,
parameter,
value,
timestamp,
} => {
write!(
f,
"[{}] 📊 {}::{} = {:.3}",
timestamp, port, parameter, value
)
}
Telemetry::SignalData {
node_id,
channel,
data,
timestamp,
sample_rate,
} => {
let duration_ms = data.len() as f32 / sample_rate * 1000.0;
write!(
f,
"[{}] 🎵 {}:{} ({} samples, {:.1}ms)",
timestamp,
node_id,
channel,
data.len(),
duration_ms
)
}
Telemetry::Peak {
port,
value,
timestamp,
hold_time_ms,
} => {
if let Some(hold) = hold_time_ms {
write!(
f,
"[{}] 📈 {} = {:.3} (hold {}ms)",
timestamp, port, value, hold
)
} else {
write!(f, "[{}] 📈 {} = {:.3}", timestamp, port, value)
}
}
Telemetry::Event {
source,
kind,
data,
timestamp,
description,
} => {
if let Some(desc) = description {
write!(
f,
"[{}] 📢 {}:{} ({}) {:?}",
timestamp, source, kind, desc, data
)
} else {
write!(f, "[{}] 📢 {}:{} {:?}", timestamp, source, kind, data)
}
}
Telemetry::Violation {
component,
expected_ns,
actual_ns,
value,
timestamp,
} => {
if let Some(v) = value {
write!(
f,
"[{}] ⚠️ {} violation: {}ns > {}ns, value={:.3}",
timestamp, component, actual_ns, expected_ns, v
)
} else {
write!(
f,
"[{}] ⚠️ {} violation: {}ns > {}ns",
timestamp, component, actual_ns, expected_ns
)
}
}
}
}
}
pub type TelemetryQueue = super::command::CommandQueue<Telemetry>;
pub trait TelemetryQueueExt {
fn send_parameter(
&self,
port: PortId,
parameter: ParameterId,
value: f32,
) -> Result<(), super::error::QueueError>;
fn send_signal(
&self,
node_id: NodeId,
channel: usize,
data: Vec<f32>,
) -> Result<(), super::error::QueueError>;
fn send_signal_with_sample_rate(
&self,
node_id: NodeId,
channel: usize,
data: Vec<f32>,
sample_rate: f32,
) -> Result<(), super::error::QueueError>;
fn send_peak(&self, port: PortId, value: f32) -> Result<(), super::error::QueueError>;
fn send_peak_with_hold(
&self,
port: PortId,
value: f32,
hold_time_ms: u32,
) -> Result<(), super::error::QueueError>;
fn send_event(
&self,
source: &str,
kind: &str,
data: Vec<f32>,
) -> Result<(), super::error::QueueError>;
fn send_event_with_description(
&self,
source: &str,
kind: &str,
data: Vec<f32>,
description: &str,
) -> Result<(), super::error::QueueError>;
fn send_violation(
&self,
component: &str,
expected_ns: u64,
actual_ns: u64,
value: Option<f32>,
) -> Result<(), super::error::QueueError>;
}
impl TelemetryQueueExt for super::command::CommandQueue<Telemetry> {
fn send_parameter(
&self,
port: PortId,
parameter: ParameterId,
value: f32,
) -> Result<(), super::error::QueueError> {
self.send(Telemetry::parameter(port, parameter, value))
}
fn send_signal(
&self,
node_id: NodeId,
channel: usize,
data: Vec<f32>,
) -> Result<(), super::error::QueueError> {
self.send(Telemetry::signal(node_id, channel, data))
}
fn send_signal_with_sample_rate(
&self,
node_id: NodeId,
channel: usize,
data: Vec<f32>,
sample_rate: f32,
) -> Result<(), super::error::QueueError> {
self.send(Telemetry::signal_with_sample_rate(
node_id,
channel,
data,
sample_rate,
))
}
fn send_peak(&self, port: PortId, value: f32) -> Result<(), super::error::QueueError> {
self.send(Telemetry::peak(port, value))
}
fn send_peak_with_hold(
&self,
port: PortId,
value: f32,
hold_time_ms: u32,
) -> Result<(), super::error::QueueError> {
self.send(Telemetry::peak_with_hold(port, value, hold_time_ms))
}
fn send_event(
&self,
source: &str,
kind: &str,
data: Vec<f32>,
) -> Result<(), super::error::QueueError> {
self.send(Telemetry::event(source, kind, data))
}
fn send_event_with_description(
&self,
source: &str,
kind: &str,
data: Vec<f32>,
description: &str,
) -> Result<(), super::error::QueueError> {
self.send(Telemetry::event_with_description(
source,
kind,
data,
description,
))
}
fn send_violation(
&self,
component: &str,
expected_ns: u64,
actual_ns: u64,
value: Option<f32>,
) -> Result<(), super::error::QueueError> {
self.send(Telemetry::violation(
component,
expected_ns,
actual_ns,
value,
))
}
}