Skip to main content

sim_lib_stream_bridge/
runtime.rs

1use std::sync::Arc;
2
3use sim_kernel::{
4    AbiVersion, Args, Callable, ClassRef, Cx, Error, Export, Lib, LibManifest, LibTarget, Linker,
5    Object, ObjectCompat, RawArgs, Result, Symbol, Value, Version,
6};
7use sim_lib_stream_core::{StreamItem, StreamValue};
8use sim_lib_stream_prelude::{StreamHandle, install_stream_prelude_lib};
9
10use crate::{
11    StreamBridgeLiftMidiOptions, StreamBridgeRenderOptions, lift_pcm_items_to_midi,
12    render_midi_items_to_pcm, stream_bridge_symbol,
13};
14
15const STREAM_BRIDGE_LIB_ID: &str = "stream-bridge";
16
17/// Loadable library exposing the `stream/bridge` function to a runtime.
18pub struct StreamBridgeLib;
19
20impl Lib for StreamBridgeLib {
21    fn manifest(&self) -> LibManifest {
22        LibManifest {
23            id: manifest_name(),
24            version: Version(env!("CARGO_PKG_VERSION").to_owned()),
25            abi: AbiVersion { major: 0, minor: 1 },
26            target: LibTarget::HostRegistered,
27            requires: Vec::new(),
28            capabilities: Vec::new(),
29            exports: vec![Export::Function {
30                symbol: stream_bridge_symbol(),
31                function_id: None,
32            }],
33        }
34    }
35
36    fn load(&self, cx: &mut sim_kernel::LoadCx, linker: &mut Linker<'_>) -> Result<()> {
37        linker.function_value(
38            stream_bridge_symbol(),
39            cx.factory().opaque(Arc::new(StreamBridgeFunction))?,
40        )?;
41        Ok(())
42    }
43}
44
45/// Installs the stream prelude and registers [`StreamBridgeLib`] into `cx`.
46pub fn install_stream_bridge_lib(cx: &mut Cx) -> Result<()> {
47    install_stream_prelude_lib(cx)?;
48    sim_lib_core::install_once(cx, &StreamBridgeLib).map(|_| ())
49}
50
51/// Returns the manifest id symbol for [`StreamBridgeLib`].
52pub fn manifest_name() -> Symbol {
53    Symbol::new(STREAM_BRIDGE_LIB_ID)
54}
55
56struct StreamBridgeFunction;
57
58impl Object for StreamBridgeFunction {
59    fn display(&self, _cx: &mut Cx) -> Result<String> {
60        Ok("#<function stream/bridge>".to_owned())
61    }
62
63    fn as_any(&self) -> &dyn std::any::Any {
64        self
65    }
66}
67
68impl ObjectCompat for StreamBridgeFunction {
69    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
70        cx.factory().class_stub(
71            sim_kernel::CORE_FUNCTION_CLASS_ID,
72            Symbol::qualified("core", "Function"),
73        )
74    }
75
76    fn as_callable(&self) -> Option<&dyn Callable> {
77        Some(self)
78    }
79}
80
81impl Callable for StreamBridgeFunction {
82    fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
83        let exprs = args
84            .into_vec()
85            .into_iter()
86            .map(|value| value.object().as_expr(cx))
87            .collect::<Result<Vec<_>>>()?;
88        bridge_call(cx, &exprs)
89    }
90
91    fn call_exprs(&self, cx: &mut Cx, args: RawArgs) -> Result<Value> {
92        bridge_call(cx, args.exprs())
93    }
94}
95
96fn bridge_call(cx: &mut Cx, args: &[sim_kernel::Expr]) -> Result<Value> {
97    let [stream, options @ ..] = args else {
98        return Err(Error::Eval(
99            "stream/bridge expects a stream and option pairs".to_owned(),
100        ));
101    };
102    if !options.len().is_multiple_of(2) {
103        return Err(Error::Eval(
104            "stream/bridge options must be key/value pairs".to_owned(),
105        ));
106    }
107    let mut op = None;
108    let mut render = StreamBridgeRenderOptions::default();
109    let mut lift = StreamBridgeLiftMidiOptions::default();
110    for pair in options.chunks(2) {
111        let key = keyword(&pair[0])?;
112        match key.as_str() {
113            "op" => op = Some(symbolish(&pair[1])?),
114            "rate" | "sample-rate" => {
115                let value = usize_value(&pair[1])?;
116                render.sample_rate = value as u32;
117                lift.sample_rate = value as u32;
118            }
119            "channels" => render.channels = usize_value(&pair[1])? as u8,
120            "chunk-frames" => render.chunk_frames = usize_value(&pair[1])?,
121            "min-confidence" => lift.min_confidence = f64_value(&pair[1])?,
122            "window" => lift.window_size = usize_value(&pair[1])?,
123            "hop" => lift.hop_size = usize_value(&pair[1])?,
124            "tpq" => lift.tpq = usize_value(&pair[1])? as u16,
125            "max-events" => lift.max_events_per_packet = usize_value(&pair[1])?,
126            other => {
127                return Err(Error::Eval(format!(
128                    "unknown stream/bridge option :{other}"
129                )));
130            }
131        }
132    }
133    let value = cx.eval_expr(unquote(stream))?;
134    let items = items_from_value(value)?;
135    let output = match op.as_deref() {
136        Some("render") => render_midi_items_to_pcm(items, render)?,
137        Some("lift-midi") => lift_pcm_items_to_midi(items, lift)?,
138        Some(other) => return Err(Error::Eval(format!("unknown stream/bridge op {other}"))),
139        None => {
140            return Err(Error::Eval(
141                "stream/bridge requires :op render or :op lift-midi".to_owned(),
142            ));
143        }
144    };
145    let stream = Arc::new(output.stream);
146    let handle = StreamHandle::source(stream.metadata().clone(), stream);
147    cx.factory().opaque(Arc::new(handle))
148}
149
150fn items_from_value(value: Value) -> Result<Vec<StreamItem>> {
151    if let Some(handle) = value.object().downcast_ref::<StreamHandle>() {
152        return take_handle_items(handle);
153    }
154    if let Some(stream) = value.object().downcast_ref::<StreamValue>() {
155        return take_stream_items(stream);
156    }
157    Err(Error::TypeMismatch {
158        expected: "stream handle",
159        found: "non-stream",
160    })
161}
162
163fn take_handle_items(handle: &StreamHandle) -> Result<Vec<StreamItem>> {
164    let mut out = Vec::new();
165    while let Some(item) = handle.next_packet()? {
166        out.push(item);
167    }
168    Ok(out)
169}
170
171fn take_stream_items(stream: &StreamValue) -> Result<Vec<StreamItem>> {
172    let mut out = Vec::new();
173    while let Some(item) = stream.next_packet()? {
174        out.push(item);
175    }
176    Ok(out)
177}
178
179fn keyword(expr: &sim_kernel::Expr) -> Result<String> {
180    let sim_kernel::Expr::Symbol(symbol) = expr else {
181        return Err(Error::TypeMismatch {
182            expected: "keyword symbol",
183            found: "non-symbol",
184        });
185    };
186    Ok(symbol
187        .name
188        .strip_prefix(':')
189        .unwrap_or(symbol.name.as_ref())
190        .to_owned())
191}
192
193fn symbolish(expr: &sim_kernel::Expr) -> Result<String> {
194    match unquote_ref(expr) {
195        sim_kernel::Expr::Symbol(symbol) => Ok(symbol.name.to_string()),
196        sim_kernel::Expr::String(value) => Ok(value.clone()),
197        _ => Err(Error::Eval(
198            "stream/bridge :op expects a symbol or string".to_owned(),
199        )),
200    }
201}
202
203fn usize_value(expr: &sim_kernel::Expr) -> Result<usize> {
204    match unquote_ref(expr) {
205        sim_kernel::Expr::String(value) => value
206            .parse()
207            .map_err(|err| Error::Eval(format!("invalid stream/bridge integer option: {err}"))),
208        sim_kernel::Expr::Number(number) => number.canonical.parse().map_err(|err| {
209            Error::Eval(format!(
210                "invalid stream/bridge integer number option: {err}"
211            ))
212        }),
213        _ => Err(Error::Eval(
214            "stream/bridge integer option expects string or number".to_owned(),
215        )),
216    }
217}
218
219fn f64_value(expr: &sim_kernel::Expr) -> Result<f64> {
220    match unquote_ref(expr) {
221        sim_kernel::Expr::String(value) => value
222            .parse()
223            .map_err(|err| Error::Eval(format!("invalid stream/bridge float option: {err}"))),
224        sim_kernel::Expr::Number(number) => number.canonical.parse().map_err(|err| {
225            Error::Eval(format!("invalid stream/bridge float number option: {err}"))
226        }),
227        _ => Err(Error::Eval(
228            "stream/bridge float option expects string or number".to_owned(),
229        )),
230    }
231}
232
233fn unquote(expr: &sim_kernel::Expr) -> sim_kernel::Expr {
234    unquote_ref(expr).clone()
235}
236
237fn unquote_ref(expr: &sim_kernel::Expr) -> &sim_kernel::Expr {
238    match expr {
239        sim_kernel::Expr::Quote {
240            mode: sim_kernel::QuoteMode::Quote,
241            expr,
242        } => expr,
243        other => other,
244    }
245}