sim_lib_stream_host/
rtp_midi.rs1use std::sync::Arc;
4
5use sim_kernel::{Error, Result, Symbol};
6use sim_lib_stream_core::{
7 BufferPolicy, ClockDomain, MidiPacket, StreamEnvelope, StreamItem, StreamMedia, StreamPacket,
8 StreamValue, TransportProfile,
9};
10
11use crate::{
12 HostBackend, HostBackendCapability, HostBackendInfo, HostCallbackQueue, HostClockInfo,
13 HostDeviceInventory, HostDeviceSpec, HostDirection, HostLatencyInfo, HostOpenStream,
14 HostPortSpec, HostStreamConfig, HostStreamConfigRequest,
15};
16
17#[derive(Clone, Debug)]
22pub struct RtpMidiBackend {
23 info: HostBackendInfo,
24}
25
26pub struct RtpMidiPort {
28 spec: HostDeviceSpec,
29 queue: HostCallbackQueue,
30}
31
32pub fn rtp_midi_backend_symbol() -> Symbol {
34 Symbol::qualified("stream/host", "rtp-midi")
35}
36
37impl Default for RtpMidiBackend {
38 fn default() -> Self {
39 Self::new()
40 }
41}
42
43impl RtpMidiBackend {
44 pub fn new() -> Self {
46 Self {
47 info: HostBackendInfo::new(
48 rtp_midi_backend_symbol(),
49 Symbol::qualified("stream/transport", "rtp-midi"),
50 StreamMedia::Midi,
51 false,
52 )
53 .with_capabilities(vec![
54 HostBackendCapability::MidiInput,
55 HostBackendCapability::Offline,
56 ]),
57 }
58 }
59
60 pub fn info(&self) -> &HostBackendInfo {
62 &self.info
63 }
64
65 pub fn source_spec(id: impl Into<String>, capacity: usize) -> Result<HostDeviceSpec> {
68 Ok(HostDeviceSpec::new(
69 Symbol::new(id.into()),
70 rtp_midi_backend_symbol(),
71 StreamMedia::Midi,
72 HostDirection::Input,
73 ClockDomain::MidiTick.symbol(),
74 BufferPolicy::bounded(capacity)?,
75 ))
76 }
77
78 pub fn open_source(&self, spec: HostDeviceSpec) -> Result<RtpMidiPort> {
83 if spec.backend() != self.info.id() {
84 return Err(Error::Eval(format!(
85 "RTP-MIDI backend cannot open {} device specs",
86 spec.backend()
87 )));
88 }
89 if spec.media() != StreamMedia::Midi {
90 return Err(Error::TypeMismatch {
91 expected: "MIDI host device",
92 found: "non-MIDI host device",
93 });
94 }
95 if spec.direction() == HostDirection::Output {
96 return Err(Error::TypeMismatch {
97 expected: "RTP-MIDI input or duplex device",
98 found: "output-only host device",
99 });
100 }
101 let stream = Arc::new(StreamValue::push(spec.metadata()));
102 Ok(RtpMidiPort {
103 spec,
104 queue: HostCallbackQueue::new(stream),
105 })
106 }
107}
108
109impl HostBackend for RtpMidiBackend {
110 fn info(&self) -> &HostBackendInfo {
111 &self.info
112 }
113
114 fn enumerate(&self) -> Result<HostDeviceInventory> {
115 let device = Self::source_spec("rtp-midi/default", 8)?;
116 let port = HostPortSpec::new(
117 Symbol::new("rtp-midi/default/in"),
118 device.id().clone(),
119 rtp_midi_backend_symbol(),
120 StreamMedia::Midi,
121 HostDirection::Input,
122 );
123 Ok(HostDeviceInventory::new(rtp_midi_backend_symbol())
124 .with_devices(vec![device])
125 .with_ports(vec![port]))
126 }
127
128 fn open(&self, request: HostStreamConfigRequest) -> Result<HostOpenStream> {
129 if request.backend() != self.info.id() {
130 return Err(Error::Eval(format!(
131 "RTP-MIDI backend cannot open {} requests",
132 request.backend()
133 )));
134 }
135 if request.media() != StreamMedia::Midi {
136 return Err(Error::TypeMismatch {
137 expected: "MIDI host stream request",
138 found: "non-MIDI host stream request",
139 });
140 }
141 if request.direction() == HostDirection::Output {
142 return Err(Error::TypeMismatch {
143 expected: "RTP-MIDI input or duplex stream request",
144 found: "output-only host stream request",
145 });
146 }
147 let clock = HostClockInfo::new(request.clock().clone(), None, true);
148 let config = HostStreamConfig::from_request(request, HostLatencyInfo::default(), clock);
149 HostOpenStream::new_lan_midi_control(config)
150 }
151}
152
153impl RtpMidiPort {
154 pub fn spec(&self) -> &HostDeviceSpec {
156 &self.spec
157 }
158
159 pub fn queue(&self) -> &HostCallbackQueue {
161 &self.queue
162 }
163
164 pub fn stream(&self) -> Arc<StreamValue> {
166 self.queue.stream()
167 }
168
169 pub fn receive_packet_from_callback(&self, packet: MidiPacket) -> Result<()> {
171 self.queue
172 .callback_packet(StreamPacket::Midi(packet))
173 .map(|_| ())
174 }
175
176 pub fn receive_envelope_from_callback(
179 &self,
180 sequence: u64,
181 packet: MidiPacket,
182 ) -> Result<StreamEnvelope> {
183 let item = StreamItem::new(StreamPacket::Midi(packet));
184 let envelope = StreamEnvelope::from_item_with_profile(
185 &self.spec.metadata(),
186 sequence,
187 &item,
188 TransportProfile::lan_midi_control(),
189 )?;
190 self.queue.callback_item(item)?;
191 Ok(envelope)
192 }
193
194 pub fn close(&self) -> Result<()> {
196 self.queue.close()
197 }
198}