use sim_kernel::{
Claim, ClaimPattern, Cx, Error, Expr, Ref, Result, Symbol, stream_surface,
stream_surface::publish_stream_metadata_claims,
};
use crate::buffer::{BufferPolicy, expr_kind, field, string_field, symbol_field};
use crate::{ClockDomain, LatencyClass};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum StreamMedia {
Pcm,
Midi,
Diagnostic,
Data,
}
impl StreamMedia {
pub fn symbol(self) -> Symbol {
match self {
Self::Pcm => Symbol::qualified("stream/media", "pcm"),
Self::Midi => Symbol::qualified("stream/media", "midi"),
Self::Diagnostic => Symbol::qualified("stream/media", "diagnostic"),
Self::Data => Symbol::qualified("stream/media", "data"),
}
}
pub fn from_symbol(symbol: &Symbol) -> Result<Self> {
match symbol.as_qualified_str().as_str() {
"stream/media/pcm" => Ok(Self::Pcm),
"stream/media/midi" => Ok(Self::Midi),
"stream/media/diagnostic" => Ok(Self::Diagnostic),
"stream/media/data" => Ok(Self::Data),
other => Err(Error::Eval(format!("unknown stream media {other}"))),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum StreamDirection {
Source,
Sink,
Duplex,
}
impl StreamDirection {
pub fn symbol(self) -> Symbol {
match self {
Self::Source => Symbol::qualified("stream/direction", "source"),
Self::Sink => Symbol::qualified("stream/direction", "sink"),
Self::Duplex => Symbol::qualified("stream/direction", "duplex"),
}
}
pub fn from_symbol(symbol: &Symbol) -> Result<Self> {
match symbol.as_qualified_str().as_str() {
"stream/direction/source" => Ok(Self::Source),
"stream/direction/sink" => Ok(Self::Sink),
"stream/direction/duplex" => Ok(Self::Duplex),
other => Err(Error::Eval(format!("unknown stream direction {other}"))),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RateContract {
clock_domain: ClockDomain,
latency_class: LatencyClass,
nominal_rate_hz: Option<u32>,
}
impl RateContract {
pub fn new(
clock_domain: ClockDomain,
latency_class: LatencyClass,
nominal_rate_hz: Option<u32>,
) -> Self {
Self {
clock_domain,
latency_class,
nominal_rate_hz,
}
}
pub fn sample_exact(nominal_rate_hz: Option<u32>) -> Self {
Self::new(
ClockDomain::Sample,
LatencyClass::SampleExact,
nominal_rate_hz,
)
}
pub fn block_local() -> Self {
Self::new(ClockDomain::Block, LatencyClass::BlockLocal, None)
}
pub fn control() -> Self {
Self::new(ClockDomain::Control, LatencyClass::Interactive, None)
}
pub fn midi_tick() -> Self {
Self::new(ClockDomain::MidiTick, LatencyClass::Interactive, None)
}
pub fn trace_step() -> Self {
Self::new(ClockDomain::TraceStep, LatencyClass::OfflineRender, None)
}
pub fn clock_domain(self) -> ClockDomain {
self.clock_domain
}
pub fn latency_class(self) -> LatencyClass {
self.latency_class
}
pub fn nominal_rate_hz(self) -> Option<u32> {
self.nominal_rate_hz
}
pub fn is_compatible_with(self, other: Self) -> bool {
self.clock_domain == other.clock_domain
&& self.latency_class == other.latency_class
&& rates_are_compatible(self.nominal_rate_hz, other.nominal_rate_hz)
}
pub fn ensure_compatible(self, other: Self) -> Result<()> {
if self.is_compatible_with(other) {
return Ok(());
}
Err(Error::Eval(format!(
"incompatible port rate contracts: source {} {} {:?}, target {} {} {:?}",
self.clock_domain.wire_label(),
self.latency_class.wire_label(),
self.nominal_rate_hz,
other.clock_domain.wire_label(),
other.latency_class.wire_label(),
other.nominal_rate_hz
)))
}
}
fn rates_are_compatible(left: Option<u32>, right: Option<u32>) -> bool {
match (left, right) {
(Some(left), Some(right)) => left == right,
_ => true,
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StreamMetadata {
id: Symbol,
media: StreamMedia,
direction: StreamDirection,
clock: Symbol,
buffer: BufferPolicy,
}
impl StreamMetadata {
pub fn new(
id: Symbol,
media: StreamMedia,
direction: StreamDirection,
clock: Symbol,
buffer: BufferPolicy,
) -> Self {
Self {
id,
media,
direction,
clock,
buffer,
}
}
pub fn id(&self) -> &Symbol {
&self.id
}
pub fn media(&self) -> StreamMedia {
self.media
}
pub fn direction(&self) -> StreamDirection {
self.direction
}
pub fn clock(&self) -> &Symbol {
&self.clock
}
pub fn buffer(&self) -> &BufferPolicy {
&self.buffer
}
pub fn subject_ref(&self) -> Ref {
Ref::Symbol(self.id.clone())
}
pub fn to_constructor_args(&self) -> Vec<Expr> {
vec![
Expr::Symbol(self.id.clone()),
Expr::Symbol(self.media.symbol()),
Expr::Symbol(self.direction.symbol()),
Expr::Symbol(self.clock.clone()),
self.buffer.to_expr(),
]
}
pub fn from_constructor_args(args: Vec<Expr>) -> Result<Self> {
let [id, media, direction, clock, buffer] = args.as_slice() else {
return Err(Error::Eval(
"stream/Metadata expects five constructor arguments".to_owned(),
));
};
Ok(Self::new(
symbol_expr(id, "stream id")?,
StreamMedia::from_symbol(symbol_expr_ref(media, "stream media")?)?,
StreamDirection::from_symbol(symbol_expr_ref(direction, "stream direction")?)?,
symbol_expr(clock, "stream clock")?,
BufferPolicy::from_expr(buffer)?,
))
}
pub fn table_expr(&self) -> Expr {
Expr::Map(vec![
(
Expr::Symbol(Symbol::new("kind")),
Expr::Symbol(stream_surface::stream_kind()),
),
(
Expr::Symbol(Symbol::new("id")),
Expr::String(self.id.to_string()),
),
(
Expr::Symbol(Symbol::new("media")),
Expr::Symbol(self.media.symbol()),
),
(
Expr::Symbol(Symbol::new("direction")),
Expr::Symbol(self.direction.symbol()),
),
(
Expr::Symbol(Symbol::new("clock")),
Expr::Symbol(self.clock.clone()),
),
(Expr::Symbol(Symbol::new("buffer")), self.buffer.to_expr()),
])
}
pub fn from_table_expr(expr: &Expr) -> Result<Self> {
let Expr::Map(entries) = expr else {
return Err(Error::TypeMismatch {
expected: "stream metadata map",
found: expr_kind(expr),
});
};
Ok(Self::new(
Symbol::new(string_field(entries, "id")?.to_owned()),
StreamMedia::from_symbol(symbol_field(entries, "media")?)?,
StreamDirection::from_symbol(symbol_field(entries, "direction")?)?,
symbol_field(entries, "clock")?.clone(),
BufferPolicy::from_expr(field(entries, "buffer")?)?,
))
}
}
pub fn stream_id_predicate() -> Symbol {
Symbol::qualified("stream", "id")
}
pub fn stream_media_predicate() -> Symbol {
Symbol::qualified("stream", "media")
}
pub fn stream_direction_predicate() -> Symbol {
Symbol::qualified("stream", "direction")
}
pub fn stream_buffer_predicate() -> Symbol {
Symbol::qualified("stream", "buffer")
}
pub fn publish_metadata_claims(cx: &mut Cx, subject: Ref, metadata: &StreamMetadata) -> Result<()> {
publish_stream_metadata_claims(
cx,
subject.clone(),
[
(stream_id_predicate(), Ref::Symbol(metadata.id.clone())),
(
stream_media_predicate(),
Ref::Symbol(metadata.media.symbol()),
),
(
stream_direction_predicate(),
Ref::Symbol(metadata.direction.symbol()),
),
(
stream_surface::stream_clock_predicate(),
Ref::Symbol(metadata.clock.clone()),
),
(
stream_buffer_predicate(),
Ref::Symbol(metadata.buffer.symbol()),
),
],
)?;
insert_once(
cx,
subject,
stream_surface::stream_transport_predicate(),
Ref::Symbol(Symbol::qualified("stream", "memory")),
)
}
fn insert_once(cx: &mut Cx, subject: Ref, predicate: Symbol, object: Ref) -> Result<()> {
let exists = !cx
.query_facts(ClaimPattern::exact(
subject.clone(),
predicate.clone(),
object.clone(),
))?
.is_empty();
if !exists {
cx.insert_fact(Claim::public(subject, predicate, object))?;
}
Ok(())
}
fn symbol_expr(expr: &Expr, expected: &'static str) -> Result<Symbol> {
Ok(symbol_expr_ref(expr, expected)?.clone())
}
fn symbol_expr_ref<'a>(expr: &'a Expr, expected: &'static str) -> Result<&'a Symbol> {
match expr {
Expr::Symbol(symbol) => Ok(symbol),
other => Err(Error::TypeMismatch {
expected,
found: expr_kind(other),
}),
}
}