1use sim_kernel::{Error, Result, Symbol};
4use sim_lib_stream_core::{BufferPolicy, ClockDomain, StreamMedia};
5
6use crate::{
7 HostBackend, HostBackendCapability, HostBackendInfo, HostClockInfo, HostDeviceInventory,
8 HostDeviceSpec, HostDirection, HostLatencyInfo, HostOpenStream, HostPortSpec, HostStreamConfig,
9 HostStreamConfigRequest,
10};
11
12pub fn fake_backend_symbol() -> Symbol {
14 Symbol::qualified("stream/host", "fake")
15}
16
17#[derive(Clone, Debug)]
19pub struct FakeBackend {
20 info: HostBackendInfo,
21 inventory: HostDeviceInventory,
22}
23
24impl Default for FakeBackend {
25 fn default() -> Self {
26 Self::new()
27 }
28}
29
30impl FakeBackend {
31 pub fn new() -> Self {
33 let backend = fake_backend_symbol();
34 let data = fake_device("fake/data", StreamMedia::Data, HostDirection::Input, 8);
35 let data_duplex = fake_device(
36 "fake/data-duplex",
37 StreamMedia::Data,
38 HostDirection::Duplex,
39 8,
40 );
41 let midi = fake_device("fake/midi", StreamMedia::Midi, HostDirection::Input, 8);
42 let pcm = fake_device("fake/pcm", StreamMedia::Pcm, HostDirection::Output, 8);
43 let ports = vec![
44 fake_port(
45 "fake/data/in",
46 data.id(),
47 StreamMedia::Data,
48 HostDirection::Input,
49 ),
50 fake_port(
51 "fake/data-duplex/io",
52 data_duplex.id(),
53 StreamMedia::Data,
54 HostDirection::Duplex,
55 ),
56 fake_port(
57 "fake/midi/in",
58 midi.id(),
59 StreamMedia::Midi,
60 HostDirection::Input,
61 ),
62 fake_port(
63 "fake/pcm/out",
64 pcm.id(),
65 StreamMedia::Pcm,
66 HostDirection::Output,
67 ),
68 ];
69 Self {
70 info: HostBackendInfo::new(
71 backend.clone(),
72 Symbol::qualified("stream/transport", "fake"),
73 StreamMedia::Data,
74 false,
75 )
76 .with_capabilities(vec![
77 HostBackendCapability::Fake,
78 HostBackendCapability::Offline,
79 HostBackendCapability::MidiInput,
80 HostBackendCapability::AudioOutput,
81 HostBackendCapability::Duplex,
82 ]),
83 inventory: HostDeviceInventory::new(backend)
84 .with_devices(vec![data, data_duplex, midi, pcm])
85 .with_ports(ports),
86 }
87 }
88
89 pub fn data_request(capacity: usize) -> Result<HostStreamConfigRequest> {
91 Ok(HostStreamConfigRequest::new(
92 fake_backend_symbol(),
93 Symbol::new("fake/data"),
94 StreamMedia::Data,
95 HostDirection::Input,
96 BufferPolicy::bounded(capacity)?,
97 ))
98 }
99
100 pub fn duplex_data_request(capacity: usize) -> Result<HostStreamConfigRequest> {
102 Ok(HostStreamConfigRequest::new(
103 fake_backend_symbol(),
104 Symbol::new("fake/data-duplex"),
105 StreamMedia::Data,
106 HostDirection::Duplex,
107 BufferPolicy::bounded(capacity)?,
108 ))
109 }
110}
111
112impl HostBackend for FakeBackend {
113 fn info(&self) -> &HostBackendInfo {
114 &self.info
115 }
116
117 fn enumerate(&self) -> Result<HostDeviceInventory> {
118 Ok(self.inventory.clone())
119 }
120
121 fn open(&self, request: HostStreamConfigRequest) -> Result<HostOpenStream> {
122 if request.backend() != self.info.id() {
123 return Err(Error::Eval(format!(
124 "fake backend cannot open {} requests",
125 request.backend()
126 )));
127 }
128 let Some(device) = self
129 .inventory
130 .devices()
131 .iter()
132 .find(|device| device.id() == request.device())
133 else {
134 return Err(Error::Eval(format!(
135 "fake backend has no device {}",
136 request.device()
137 )));
138 };
139 if device.media() != request.media() {
140 return Err(Error::TypeMismatch {
141 expected: "request media matching fake device",
142 found: "request media for another fake device",
143 });
144 }
145 if device.direction() != request.direction() {
146 return Err(Error::TypeMismatch {
147 expected: "request direction matching fake device",
148 found: "request direction for another fake device",
149 });
150 }
151 let config = HostStreamConfig::from_request(
152 request,
153 HostLatencyInfo::default(),
154 HostClockInfo::new(fake_clock(device.media()), None, true),
155 );
156 Ok(HostOpenStream::new(config))
157 }
158}
159
160fn fake_device(
161 id: &str,
162 media: StreamMedia,
163 direction: HostDirection,
164 capacity: usize,
165) -> HostDeviceSpec {
166 HostDeviceSpec::new(
167 Symbol::new(id),
168 fake_backend_symbol(),
169 media,
170 direction,
171 fake_clock(media),
172 BufferPolicy::bounded(capacity).expect("valid fake buffer"),
173 )
174}
175
176fn fake_clock(media: StreamMedia) -> Symbol {
177 match media {
178 StreamMedia::Pcm => ClockDomain::Sample.symbol(),
179 StreamMedia::Midi => ClockDomain::MidiTick.symbol(),
180 StreamMedia::Diagnostic | StreamMedia::Data => ClockDomain::ServerFrame.symbol(),
181 }
182}
183
184fn fake_port(
185 id: &str,
186 device: &Symbol,
187 media: StreamMedia,
188 direction: HostDirection,
189) -> HostPortSpec {
190 HostPortSpec::new(
191 Symbol::new(id),
192 device.clone(),
193 fake_backend_symbol(),
194 media,
195 direction,
196 )
197}