sim_lib_stream_host/
rtp_midi.rs1use std::sync::Arc;
4
5use sim_kernel::{Cx, 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, cx: &mut Cx, 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 spec.open_plan().enforce(cx)?;
102 let stream = Arc::new(StreamValue::push(spec.metadata()));
103 Ok(RtpMidiPort {
104 spec,
105 queue: HostCallbackQueue::new(stream),
106 })
107 }
108
109 fn device_for_request(&self, request: &HostStreamConfigRequest) -> Result<HostDeviceSpec> {
110 <Self as HostBackend>::enumerate(self)?
111 .devices()
112 .iter()
113 .find(|device| device.id() == request.device())
114 .cloned()
115 .ok_or_else(|| Error::Eval(format!("unknown RTP-MIDI device {}", request.device())))
116 }
117}
118
119impl HostBackend for RtpMidiBackend {
120 fn info(&self) -> &HostBackendInfo {
121 &self.info
122 }
123
124 fn enumerate(&self) -> Result<HostDeviceInventory> {
125 let device = Self::source_spec("rtp-midi/default", 8)?;
126 let port = HostPortSpec::new(
127 Symbol::new("rtp-midi/default/in"),
128 device.id().clone(),
129 rtp_midi_backend_symbol(),
130 StreamMedia::Midi,
131 HostDirection::Input,
132 );
133 Ok(HostDeviceInventory::new(rtp_midi_backend_symbol())
134 .with_devices(vec![device])
135 .with_ports(vec![port]))
136 }
137
138 fn open(&self, request: HostStreamConfigRequest) -> Result<HostOpenStream> {
139 if request.backend() != self.info.id() {
140 return Err(Error::Eval(format!(
141 "RTP-MIDI backend cannot open {} requests",
142 request.backend()
143 )));
144 }
145 let device = self.device_for_request(&request)?;
146 if device.media() != request.media() {
147 return Err(Error::TypeMismatch {
148 expected: "MIDI host stream request",
149 found: "non-MIDI host stream request",
150 });
151 }
152 if device.direction() != request.direction() {
153 return Err(Error::TypeMismatch {
154 expected: "RTP-MIDI input or duplex stream request",
155 found: "request direction for another RTP-MIDI device",
156 });
157 }
158 let clock = HostClockInfo::new(request.clock().clone(), None, true);
159 let config = HostStreamConfig::from_request(request, HostLatencyInfo::default(), clock);
160 HostOpenStream::new_lan_midi_control(config)
161 }
162}
163
164impl RtpMidiPort {
165 pub fn spec(&self) -> &HostDeviceSpec {
167 &self.spec
168 }
169
170 pub fn queue(&self) -> &HostCallbackQueue {
172 &self.queue
173 }
174
175 pub fn stream(&self) -> Arc<StreamValue> {
177 self.queue.stream()
178 }
179
180 pub fn receive_packet_from_callback(&self, packet: MidiPacket) -> Result<()> {
182 self.queue
183 .callback_packet(StreamPacket::Midi(packet))
184 .map(|_| ())
185 }
186
187 pub fn receive_envelope_from_callback(
190 &self,
191 sequence: u64,
192 packet: MidiPacket,
193 ) -> Result<StreamEnvelope> {
194 let item = StreamItem::new(StreamPacket::Midi(packet));
195 let envelope = StreamEnvelope::from_item_with_profile(
196 &self.spec.metadata(),
197 sequence,
198 &item,
199 TransportProfile::lan_midi_control(),
200 )?;
201 self.queue.callback_item(item)?;
202 Ok(envelope)
203 }
204
205 pub fn close(&self) -> Result<()> {
207 self.queue.close()
208 }
209}