Skip to main content

sim_lib_stream_host/
catalog.rs

1//! Shared host-device catalog for modeled stream placements.
2
3use sim_kernel::{
4    Error, Result, Symbol,
5    library::{ExportKind, ExportRecord, ExportState, Registry},
6};
7
8use crate::audio_provider::ModeledAudioProvider;
9use crate::eval_site::{DeviceProvider, StreamEvalSite};
10use crate::midi_live_eval_site::MidiLiveEvalSite;
11use crate::midi_provider::ModeledMidiProvider;
12use crate::placement::{DeviceDirection, DeviceKind, DeviceRecord};
13use crate::{AudioRouter, Placement};
14
15/// Registry of stream-device providers.
16pub struct DeviceCatalog {
17    providers: Vec<Box<dyn DeviceProvider>>,
18}
19
20impl Default for DeviceCatalog {
21    fn default() -> Self {
22        Self::default_modeled()
23    }
24}
25
26impl DeviceCatalog {
27    /// Builds an empty device catalog.
28    pub fn new() -> Self {
29        Self {
30            providers: Vec::new(),
31        }
32    }
33
34    /// Builds a catalog with deterministic modeled MIDI and audio providers.
35    pub fn default_modeled() -> Self {
36        let mut catalog = Self::new();
37        catalog.register(Box::new(ModeledMidiProvider::default()));
38        catalog.register(Box::new(ModeledAudioProvider::default()));
39        catalog
40    }
41
42    /// Builds a modeled catalog plus audio devices exported by loaded libs.
43    pub fn with_registry_audio_devices(registry: &Registry) -> Self {
44        let mut catalog = Self::default_modeled();
45        catalog.register_registry_audio_devices(registry);
46        catalog
47    }
48
49    /// Builds a catalog with modeled providers plus one caller-supplied native MIDI provider.
50    #[cfg(any(feature = "rtmidi-hardware", feature = "ble-midi-hardware"))]
51    pub fn with_native_midi(provider: Box<dyn DeviceProvider>) -> Self {
52        let mut catalog = Self::default_modeled();
53        catalog.register(provider);
54        catalog
55    }
56
57    /// Builds a catalog with modeled providers plus a caller-supplied ALSA MIDI provider.
58    #[cfg(feature = "rtmidi-hardware")]
59    pub fn with_alsa_midi(provider: Box<dyn DeviceProvider>) -> Self {
60        Self::with_native_midi(provider)
61    }
62
63    /// Builds a catalog with modeled providers plus a caller-supplied BLE-MIDI provider.
64    #[cfg(feature = "ble-midi-hardware")]
65    pub fn with_ble_midi(provider: Box<dyn DeviceProvider>) -> Self {
66        Self::with_native_midi(provider)
67    }
68
69    /// Registers a device provider.
70    pub fn register(&mut self, provider: Box<dyn DeviceProvider>) {
71        self.providers.push(provider);
72    }
73
74    /// Adds a snapshot of the audio sites currently registered by a provider.
75    pub fn register_provider_sites(&mut self, router: &AudioRouter) {
76        let provider = ProviderAudioSites::from_router(router);
77        if !provider.is_empty() {
78            self.register(Box::new(provider));
79        }
80    }
81
82    /// Adds audio-device records currently owned by loaded registry libs.
83    pub fn register_registry_audio_devices(&mut self, registry: &Registry) {
84        let provider = RegistryAudioDevices::from_registry(registry);
85        if !provider.is_empty() {
86            self.register(Box::new(provider));
87        }
88    }
89
90    /// Enumerates every registered provider.
91    pub fn enumerate(&self) -> Result<Vec<DeviceRecord>> {
92        let mut records = Vec::new();
93        for provider in &self.providers {
94            records.extend(provider.enumerate()?);
95        }
96        Ok(records)
97    }
98
99    /// Opens a cataloged stream evaluation site by device id.
100    pub fn open(&self, id: &Symbol) -> Result<Box<dyn StreamEvalSite>> {
101        for provider in &self.providers {
102            let records = provider.enumerate()?;
103            if records.iter().any(|record| &record.id == id) {
104                return provider.open(id);
105            }
106        }
107        Err(Error::Eval(format!("DeviceCatalog: no device '{id}'")))
108    }
109
110    /// Opens a cataloged MIDI device as a live MIDI evaluation site.
111    pub fn open_live(&self, id: &Symbol) -> Result<MidiLiveEvalSite> {
112        MidiLiveEvalSite::from_eval_site(id, self.open(id)?)
113    }
114
115    /// Enumerates MIDI device rows.
116    pub fn enumerate_midi(&self) -> Result<Vec<DeviceRecord>> {
117        self.enumerate_kind(DeviceKind::Midi)
118    }
119
120    /// Enumerates audio device rows.
121    pub fn enumerate_audio(&self) -> Result<Vec<DeviceRecord>> {
122        self.enumerate_kind(DeviceKind::Audio)
123    }
124
125    fn enumerate_kind(&self, kind: DeviceKind) -> Result<Vec<DeviceRecord>> {
126        Ok(self
127            .enumerate()?
128            .into_iter()
129            .filter(|record| record.kind == kind)
130            .collect())
131    }
132}
133
134/// Stable export symbol for an audio device owned by a loaded lib.
135pub fn audio_device_export_symbol(name: &str) -> Symbol {
136    Symbol::qualified("audio/device", name)
137}
138
139struct ProviderAudioSites {
140    records: Vec<DeviceRecord>,
141}
142
143impl ProviderAudioSites {
144    fn from_router(router: &AudioRouter) -> Self {
145        let mut records = router
146            .site_keys()
147            .filter_map(|key| router.site(key))
148            .map(|site| DeviceRecord::modeled_audio_from_card(site.card()))
149            .collect::<Vec<_>>();
150        records.sort_by_key(|record| record.id.to_string());
151        Self { records }
152    }
153
154    fn is_empty(&self) -> bool {
155        self.records.is_empty()
156    }
157}
158
159impl DeviceProvider for ProviderAudioSites {
160    fn enumerate(&self) -> Result<Vec<DeviceRecord>> {
161        Ok(self.records.clone())
162    }
163
164    fn open(&self, id: &Symbol) -> Result<Box<dyn StreamEvalSite>> {
165        let record = self
166            .records
167            .iter()
168            .find(|record| &record.id == id)
169            .ok_or_else(|| Error::Eval(format!("ProviderAudioSites: unknown id '{id}'")))?
170            .clone();
171        Ok(Box::new(ProviderAudioSite { record }))
172    }
173}
174
175struct RegistryAudioDevices {
176    records: Vec<DeviceRecord>,
177}
178
179impl RegistryAudioDevices {
180    fn from_registry(registry: &Registry) -> Self {
181        let mut records = registry
182            .libs()
183            .iter()
184            .flat_map(|lib| lib.exports.iter())
185            .filter_map(registry_audio_device_record)
186            .collect::<Vec<_>>();
187        records.sort_by_key(|record| record.id.to_string());
188        records.dedup_by(|left, right| left.id == right.id);
189        Self { records }
190    }
191
192    fn is_empty(&self) -> bool {
193        self.records.is_empty()
194    }
195}
196
197impl DeviceProvider for RegistryAudioDevices {
198    fn enumerate(&self) -> Result<Vec<DeviceRecord>> {
199        Ok(self.records.clone())
200    }
201
202    fn open(&self, id: &Symbol) -> Result<Box<dyn StreamEvalSite>> {
203        let record = self
204            .records
205            .iter()
206            .find(|record| &record.id == id)
207            .ok_or_else(|| Error::Eval(format!("RegistryAudioDevices: unknown id '{id}'")))?
208            .clone();
209        Ok(Box::new(ProviderAudioSite { record }))
210    }
211}
212
213fn registry_audio_device_record(record: &ExportRecord) -> Option<DeviceRecord> {
214    if record.kind != ExportKind::named(ExportKind::VALUE) {
215        return None;
216    }
217    if record.symbol.namespace.as_deref() != Some("audio/device") {
218        return None;
219    }
220    if matches!(record.state, ExportState::Invalid { .. }) {
221        return None;
222    }
223    Some(DeviceRecord {
224        id: record.symbol.clone(),
225        display_name: format!("{} audio device", record.symbol.name),
226        kind: DeviceKind::Audio,
227        direction: DeviceDirection::Duplex,
228        placement: Placement::Modeled,
229    })
230}
231
232struct ProviderAudioSite {
233    record: DeviceRecord,
234}
235
236impl StreamEvalSite for ProviderAudioSite {
237    fn placement(&self) -> &Placement {
238        &self.record.placement
239    }
240
241    fn device_record(&self) -> &DeviceRecord {
242        &self.record
243    }
244
245    fn close(self: Box<Self>) -> Result<()> {
246        Ok(())
247    }
248}