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
17pub 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
45pub 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
51pub 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}