sim-lib-stream-host 0.1.2

Host-device stream backend substrate for SIM streams.
Documentation
use std::sync::Arc;

use sim_kernel::{Cx, DefaultFactory, EagerPolicy, Expr, Symbol};
use sim_lib_stream_core::{BufferPolicy, ClockDomain, MidiPacket, MidiPacketEvent, StreamMedia};

use crate::{
    HostClockInfo, HostDirection, HostLatencyInfo, HostStreamConfig, HostStreamConfigRequest,
    fake_backend_symbol, rtp_midi_backend_symbol, stream_host_capability,
};

pub(super) fn test_cx() -> Cx {
    Cx::new(Arc::new(EagerPolicy), Arc::new(DefaultFactory))
}

pub(super) fn authorized_cx() -> Cx {
    let mut cx = test_cx();
    cx.grant(stream_host_capability());
    cx
}

pub(super) fn recorded_effect_kinds(cx: &Cx) -> Vec<Symbol> {
    cx.effect_ledger()
        .records()
        .iter()
        .filter_map(|record| cx.effect_ledger().effect(&record.effect))
        .map(|effect| effect.kind.clone())
        .collect()
}

pub(super) fn realtime_audio_config(
    media: StreamMedia,
    clock_domain: ClockDomain,
) -> HostStreamConfig {
    let clock = clock_domain.symbol();
    let request = HostStreamConfigRequest::new(
        fake_backend_symbol(),
        Symbol::new("fake/pcm"),
        media,
        HostDirection::Output,
        BufferPolicy::bounded(4).unwrap(),
    )
    .with_clock(clock.clone());
    HostStreamConfig::from_request(
        request,
        HostLatencyInfo::default(),
        HostClockInfo::new(clock, Some(48_000), true),
    )
}

pub(super) fn lan_midi_config(media: StreamMedia, clock_domain: ClockDomain) -> HostStreamConfig {
    let clock = clock_domain.symbol();
    let request = HostStreamConfigRequest::new(
        rtp_midi_backend_symbol(),
        Symbol::new("rtp-midi/config"),
        media,
        HostDirection::Input,
        BufferPolicy::bounded(4).unwrap(),
    )
    .with_clock(clock.clone());
    HostStreamConfig::from_request(
        request,
        HostLatencyInfo::default(),
        HostClockInfo::new(clock, None, true),
    )
}

pub(super) fn note_packet(ticks: i64) -> MidiPacket {
    MidiPacket::new(vec![
        MidiPacketEvent::new(ticks, 480, vec![0x90, 60, 100]).unwrap(),
    ])
    .unwrap()
}

pub(super) fn field<'a>(expr: &'a Expr, name: &str) -> Option<&'a Expr> {
    let Expr::Map(entries) = expr else {
        return None;
    };
    entries.iter().find_map(|(key, value)| match key {
        Expr::Symbol(symbol) if symbol.namespace.is_none() && symbol.name.as_ref() == name => {
            Some(value)
        }
        _ => None,
    })
}