use sim_kernel::{Error, Result, Symbol, Tick};
use sim_lib_stream_core::{
BufferPolicy, ClockDomain, LatencyClass, StreamCapability, StreamDirection, StreamEnvelope,
StreamMedia, StreamMetadata, StreamPacket, TransportProfile,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LiveStreamLane {
AudioInput,
AudioOutput,
Midi,
Parameter,
Diagnostic,
}
impl LiveStreamLane {
const ALL: [Self; 5] = [
Self::AudioInput,
Self::AudioOutput,
Self::Midi,
Self::Parameter,
Self::Diagnostic,
];
pub fn all() -> &'static [Self] {
&Self::ALL
}
pub fn wire_label(self) -> &'static str {
match self {
Self::AudioInput => "audio-input",
Self::AudioOutput => "audio-output",
Self::Midi => "midi",
Self::Parameter => "parameter",
Self::Diagnostic => "diagnostic",
}
}
pub fn stream_id(self) -> Symbol {
Symbol::qualified("stream/live", self.wire_label())
}
pub fn media(self) -> StreamMedia {
match self {
Self::AudioInput | Self::AudioOutput => StreamMedia::Pcm,
Self::Midi => StreamMedia::Midi,
Self::Parameter => StreamMedia::Data,
Self::Diagnostic => StreamMedia::Diagnostic,
}
}
pub fn direction(self) -> StreamDirection {
match self {
Self::AudioOutput => StreamDirection::Sink,
Self::AudioInput | Self::Midi | Self::Parameter | Self::Diagnostic => {
StreamDirection::Source
}
}
}
pub fn clock_domain(self) -> ClockDomain {
match self {
Self::AudioInput | Self::AudioOutput => ClockDomain::Sample,
Self::Midi => ClockDomain::MidiTick,
Self::Parameter => ClockDomain::Control,
Self::Diagnostic => ClockDomain::Block,
}
}
pub fn metadata(self, capacity: usize) -> Result<StreamMetadata> {
Ok(StreamMetadata::new(
self.stream_id(),
self.media(),
self.direction(),
self.clock_domain().symbol(),
BufferPolicy::bounded(capacity)?,
))
}
pub fn realtime_envelope(
self,
sequence: u64,
ticks: Vec<Tick>,
packet: StreamPacket,
) -> Result<StreamEnvelope> {
self.envelope(
sequence,
ticks,
realtime_local_audio_profile(),
Vec::new(),
packet,
)
}
pub fn buffered_preview_envelope(
self,
sequence: u64,
ticks: Vec<Tick>,
packet: StreamPacket,
) -> Result<StreamEnvelope> {
self.envelope(
sequence,
ticks,
buffered_pcm_preview_profile(),
Vec::new(),
packet,
)
}
pub fn lan_buffered_preview_envelope(
self,
sequence: u64,
ticks: Vec<Tick>,
packet: StreamPacket,
) -> Result<StreamEnvelope> {
self.envelope(
sequence,
ticks,
lan_buffered_audio_preview_profile(),
Vec::new(),
packet,
)
}
fn envelope(
self,
sequence: u64,
ticks: Vec<Tick>,
profile: TransportProfile,
diagnostics: Vec<Symbol>,
packet: StreamPacket,
) -> Result<StreamEnvelope> {
StreamEnvelope::new(
self.stream_id(),
packet_id(self, sequence),
self.media(),
self.direction(),
sequence,
ticks,
self.clock_domain(),
profile,
diagnostics,
packet,
)
}
}
pub fn realtime_local_audio_profile() -> TransportProfile {
TransportProfile::realtime_local_audio()
}
pub fn buffered_pcm_preview_profile() -> TransportProfile {
TransportProfile::buffered_pcm_preview()
}
pub fn lan_buffered_audio_preview_profile() -> TransportProfile {
TransportProfile::lan_buffered_audio_preview()
}
pub fn lan_render_return_profile() -> TransportProfile {
TransportProfile::lan_render_return()
}
pub fn validate_realtime_local_audio_profile(profile: &TransportProfile) -> Result<()> {
if profile.has_capability(StreamCapability::Remote)
|| profile.latency_class() == LatencyClass::RemoteCollaboration
{
return Err(Error::Eval(
"remote streams cannot enter the realtime local audio callback".to_owned(),
));
}
if !profile.has_capability(StreamCapability::Realtime) {
return Err(Error::Eval(
"realtime local audio requires realtime transport capability".to_owned(),
));
}
if !profile.has_capability(StreamCapability::Bounded) {
return Err(Error::Eval(
"realtime local audio requires bounded transport capability".to_owned(),
));
}
if profile.latency_class() != LatencyClass::SampleExact {
return Err(Error::Eval(
"realtime local audio requires sample-exact latency".to_owned(),
));
}
if profile.name() != &Symbol::qualified("stream/profile", "realtime-local-audio") {
return Err(Error::Eval(
"callback entry requires the realtime-local-audio profile".to_owned(),
));
}
Ok(())
}
pub fn refuse_unbuffered_audio_callback_tunnel(profile: &TransportProfile) -> Result<()> {
if profile.name() == &Symbol::qualified("stream/profile", "realtime-local-audio")
|| profile.has_capability(StreamCapability::Realtime)
{
return Err(Error::Eval(format!(
"unbuffered audio callback tunneling is refused by default for {}; use stream/profile/lan-buffered-audio-preview",
profile.name().as_qualified_str()
)));
}
Ok(())
}
fn packet_id(lane: LiveStreamLane, sequence: u64) -> Symbol {
Symbol::qualified(
"stream/live-packet",
format!("{}#{sequence}", lane.wire_label()),
)
}