use std::sync::Arc;
use sim_kernel::{Error, Result, Symbol};
use sim_lib_stream_core::{
BufferPolicy, ClockDomain, MidiPacket, StreamEnvelope, StreamItem, StreamMedia, StreamPacket,
StreamValue, TransportProfile,
};
use crate::{
HostBackend, HostBackendCapability, HostBackendInfo, HostCallbackQueue, HostClockInfo,
HostDeviceInventory, HostDeviceSpec, HostDirection, HostLatencyInfo, HostOpenStream,
HostPortSpec, HostStreamConfig, HostStreamConfigRequest,
};
#[derive(Clone, Debug)]
pub struct RtpMidiBackend {
info: HostBackendInfo,
}
pub struct RtpMidiPort {
spec: HostDeviceSpec,
queue: HostCallbackQueue,
}
pub fn rtp_midi_backend_symbol() -> Symbol {
Symbol::qualified("stream/host", "rtp-midi")
}
impl Default for RtpMidiBackend {
fn default() -> Self {
Self::new()
}
}
impl RtpMidiBackend {
pub fn new() -> Self {
Self {
info: HostBackendInfo::new(
rtp_midi_backend_symbol(),
Symbol::qualified("stream/transport", "rtp-midi"),
StreamMedia::Midi,
false,
)
.with_capabilities(vec![
HostBackendCapability::MidiInput,
HostBackendCapability::Offline,
]),
}
}
pub fn info(&self) -> &HostBackendInfo {
&self.info
}
pub fn source_spec(id: impl Into<String>, capacity: usize) -> Result<HostDeviceSpec> {
Ok(HostDeviceSpec::new(
Symbol::new(id.into()),
rtp_midi_backend_symbol(),
StreamMedia::Midi,
HostDirection::Input,
ClockDomain::MidiTick.symbol(),
BufferPolicy::bounded(capacity)?,
))
}
pub fn open_source(&self, spec: HostDeviceSpec) -> Result<RtpMidiPort> {
if spec.backend() != self.info.id() {
return Err(Error::Eval(format!(
"RTP-MIDI backend cannot open {} device specs",
spec.backend()
)));
}
if spec.media() != StreamMedia::Midi {
return Err(Error::TypeMismatch {
expected: "MIDI host device",
found: "non-MIDI host device",
});
}
if spec.direction() == HostDirection::Output {
return Err(Error::TypeMismatch {
expected: "RTP-MIDI input or duplex device",
found: "output-only host device",
});
}
let stream = Arc::new(StreamValue::push(spec.metadata()));
Ok(RtpMidiPort {
spec,
queue: HostCallbackQueue::new(stream),
})
}
}
impl HostBackend for RtpMidiBackend {
fn info(&self) -> &HostBackendInfo {
&self.info
}
fn enumerate(&self) -> Result<HostDeviceInventory> {
let device = Self::source_spec("rtp-midi/default", 8)?;
let port = HostPortSpec::new(
Symbol::new("rtp-midi/default/in"),
device.id().clone(),
rtp_midi_backend_symbol(),
StreamMedia::Midi,
HostDirection::Input,
);
Ok(HostDeviceInventory::new(rtp_midi_backend_symbol())
.with_devices(vec![device])
.with_ports(vec![port]))
}
fn open(&self, request: HostStreamConfigRequest) -> Result<HostOpenStream> {
if request.backend() != self.info.id() {
return Err(Error::Eval(format!(
"RTP-MIDI backend cannot open {} requests",
request.backend()
)));
}
if request.media() != StreamMedia::Midi {
return Err(Error::TypeMismatch {
expected: "MIDI host stream request",
found: "non-MIDI host stream request",
});
}
if request.direction() == HostDirection::Output {
return Err(Error::TypeMismatch {
expected: "RTP-MIDI input or duplex stream request",
found: "output-only host stream request",
});
}
let clock = HostClockInfo::new(request.clock().clone(), None, true);
let config = HostStreamConfig::from_request(request, HostLatencyInfo::default(), clock);
HostOpenStream::new_lan_midi_control(config)
}
}
impl RtpMidiPort {
pub fn spec(&self) -> &HostDeviceSpec {
&self.spec
}
pub fn queue(&self) -> &HostCallbackQueue {
&self.queue
}
pub fn stream(&self) -> Arc<StreamValue> {
self.queue.stream()
}
pub fn receive_packet_from_callback(&self, packet: MidiPacket) -> Result<()> {
self.queue
.callback_packet(StreamPacket::Midi(packet))
.map(|_| ())
}
pub fn receive_envelope_from_callback(
&self,
sequence: u64,
packet: MidiPacket,
) -> Result<StreamEnvelope> {
let item = StreamItem::new(StreamPacket::Midi(packet));
let envelope = StreamEnvelope::from_item_with_profile(
&self.spec.metadata(),
sequence,
&item,
TransportProfile::lan_midi_control(),
)?;
self.queue.callback_item(item)?;
Ok(envelope)
}
pub fn close(&self) -> Result<()> {
self.queue.close()
}
}