1use std::{any::Any, sync::Arc};
4
5use sim_kernel::{
6 AbiVersion, Args, Callable, ClassRef, Cx, DefaultFactory, Dependency, Error, Export, Expr,
7 Factory, Lib, LibManifest, LibTarget, Linker, NumberLiteral, Object, RawArgs, Result, Symbol,
8 Value, Version, force_list_to_vec,
9};
10
11use crate::{
12 DctType, Direction, DstType, Normalization, SignalBuffer, SignalError, SignalView,
13 SpectrumPacking, TransformKind, TransformPlan,
14 runtime_convolution_callable::{
15 load_operations as load_convolution_operations,
16 operation_symbols as convolution_operation_symbols,
17 },
18 runtime_spectral_callable::{load_spectral_operations, spectral_symbols},
19 transform,
20};
21
22pub fn signal_transform_symbol() -> Symbol {
24 Symbol::qualified("signal", "transform")
25}
26
27#[derive(Clone)]
28struct SignalTransformFunction;
29
30impl Object for SignalTransformFunction {
31 fn display(&self, _cx: &mut Cx) -> Result<String> {
32 Ok(format!("#<function {}>", signal_transform_symbol()))
33 }
34
35 fn as_any(&self) -> &dyn Any {
36 self
37 }
38}
39
40impl sim_kernel::ObjectCompat for SignalTransformFunction {
41 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
42 if let Some(value) = cx
43 .registry()
44 .class_by_symbol(&Symbol::qualified("core", "Function"))
45 {
46 return Ok(value.clone());
47 }
48 DefaultFactory.class_stub(
49 sim_kernel::CORE_FUNCTION_CLASS_ID,
50 Symbol::qualified("core", "Function"),
51 )
52 }
53
54 fn as_expr(&self, _cx: &mut Cx) -> Result<Expr> {
55 Ok(Expr::Symbol(signal_transform_symbol()))
56 }
57
58 fn as_callable(&self) -> Option<&dyn Callable> {
59 Some(self)
60 }
61}
62
63impl Callable for SignalTransformFunction {
64 fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
65 call_signal_transform(cx, args)
66 }
67
68 fn call_exprs(&self, cx: &mut Cx, args: RawArgs) -> Result<Value> {
69 let values = args.into_exprs();
70 let [kind, direction, normalization, packing, len, input] = values.as_slice() else {
71 return Err(argument_error());
72 };
73 let options = ParsedOptions {
74 kind: option_expr(kind, "kind")?,
75 direction: option_expr(direction, "direction")?,
76 normalization: option_expr(normalization, "normalization")?,
77 packing: option_expr(packing, "packing")?,
78 };
79 let len = cx.eval_expr(len.clone())?;
80 let input = cx.eval_expr(input.clone())?;
81 execute(cx, options, &len, &input)
82 }
83}
84
85pub fn call_signal_transform(cx: &mut Cx, args: Args) -> Result<Value> {
88 let values = args.into_vec();
89 let [kind, direction, normalization, packing, len, input] = values.as_slice() else {
90 return Err(argument_error());
91 };
92 let options = ParsedOptions {
93 kind: option_value(cx, kind, "kind")?,
94 direction: option_value(cx, direction, "direction")?,
95 normalization: option_value(cx, normalization, "normalization")?,
96 packing: option_value(cx, packing, "packing")?,
97 };
98 execute(cx, options, len, input)
99}
100
101struct ParsedOptions {
102 kind: String,
103 direction: String,
104 normalization: String,
105 packing: String,
106}
107
108fn execute(cx: &mut Cx, options: ParsedOptions, len: &Value, input: &Value) -> Result<Value> {
109 let kind = parse_kind(&options.kind)?;
110 let direction = parse_direction(&options.direction)?;
111 let normalization = parse_normalization(&options.normalization)?;
112 let packing = parse_packing(&options.packing)?;
113 let len = value_to_usize(cx, len, "len")?;
114 let mut plan = TransformPlan::new(kind, len);
115 plan.direction = direction;
116 plan.normalization = normalization;
117 plan.packing = packing;
118
119 let output = match (kind, direction) {
120 (TransformKind::Dft | TransformKind::Fft, _)
121 | (TransformKind::RealFft, Direction::Inverse) => {
122 let input = value_to_complex_list(cx, input)?;
123 transform(&plan, SignalView::Complex(&input))
124 }
125 (TransformKind::RealFft | TransformKind::Dct(_) | TransformKind::Dst(_), _) => {
126 let input = value_to_real_list(cx, input)?;
127 transform(&plan, SignalView::Real(&input))
128 }
129 }
130 .map_err(signal_error_to_kernel)?;
131 buffer_to_value(cx, output)
132}
133
134pub struct SignalNumbersLib;
136
137impl SignalNumbersLib {
138 pub fn new() -> Self {
140 Self
141 }
142}
143
144impl Default for SignalNumbersLib {
145 fn default() -> Self {
146 Self::new()
147 }
148}
149
150impl Lib for SignalNumbersLib {
151 fn manifest(&self) -> LibManifest {
152 LibManifest {
153 id: Symbol::qualified("numbers", "signal"),
154 version: Version(env!("CARGO_PKG_VERSION").to_owned()),
155 abi: AbiVersion { major: 0, minor: 1 },
156 target: LibTarget::HostRegistered,
157 requires: Vec::<Dependency>::new(),
158 capabilities: Vec::new(),
159 exports: std::iter::once(signal_transform_symbol())
160 .chain(convolution_operation_symbols())
161 .chain(spectral_symbols())
162 .map(|symbol| Export::Function {
163 symbol,
164 function_id: None,
165 })
166 .collect(),
167 }
168 }
169
170 fn load(&self, _cx: &mut sim_kernel::LoadCx, linker: &mut Linker<'_>) -> Result<()> {
171 linker.function_value(
172 signal_transform_symbol(),
173 DefaultFactory.opaque(Arc::new(SignalTransformFunction))?,
174 )?;
175 load_convolution_operations(linker)?;
176 load_spectral_operations(linker)?;
177 Ok(())
178 }
179}
180
181fn parse_kind(name: &str) -> Result<TransformKind> {
182 match name {
183 "dft" => Ok(TransformKind::Dft),
184 "fft" => Ok(TransformKind::Fft),
185 "rfft" => Ok(TransformKind::RealFft),
186 "dct-i" => Ok(TransformKind::Dct(DctType::I)),
187 "dct-ii" => Ok(TransformKind::Dct(DctType::II)),
188 "dct-iii" => Ok(TransformKind::Dct(DctType::III)),
189 "dct-iv" => Ok(TransformKind::Dct(DctType::IV)),
190 "dst-i" => Ok(TransformKind::Dst(DstType::I)),
191 "dst-ii" => Ok(TransformKind::Dst(DstType::II)),
192 "dst-iii" => Ok(TransformKind::Dst(DstType::III)),
193 "dst-iv" => Ok(TransformKind::Dst(DstType::IV)),
194 _ => Err(Error::Eval(format!(
195 "signal/transform unsupported kind {name}"
196 ))),
197 }
198}
199
200fn parse_direction(name: &str) -> Result<Direction> {
201 match name {
202 "forward" => Ok(Direction::Forward),
203 "inverse" => Ok(Direction::Inverse),
204 _ => Err(Error::Eval(format!(
205 "signal/transform direction must be forward or inverse, got {name}"
206 ))),
207 }
208}
209
210fn parse_normalization(name: &str) -> Result<Normalization> {
211 match name {
212 "none" => Ok(Normalization::None),
213 "forward" => Ok(Normalization::Forward),
214 "inverse" => Ok(Normalization::Inverse),
215 "orthonormal" => Ok(Normalization::Orthonormal),
216 _ => Err(Error::Eval(format!(
217 "signal/transform unsupported normalization {name}"
218 ))),
219 }
220}
221
222fn parse_packing(name: &str) -> Result<SpectrumPacking> {
223 match name {
224 "full" => Ok(SpectrumPacking::Full),
225 "hermitian-half" => Ok(SpectrumPacking::HermitianHalf),
226 _ => Err(Error::Eval(format!(
227 "signal/transform packing must be full or hermitian-half, got {name}"
228 ))),
229 }
230}
231
232fn option_expr(expr: &Expr, name: &str) -> Result<String> {
233 let Expr::Symbol(symbol) = expr else {
234 return Err(Error::Eval(format!(
235 "signal/transform {name} must be an unquoted option symbol"
236 )));
237 };
238 Ok(symbol.as_qualified_str().to_owned())
239}
240
241fn option_value(cx: &mut Cx, value: &Value, name: &str) -> Result<String> {
242 let Expr::Symbol(symbol) = value.object().as_expr(cx)? else {
243 return Err(Error::Eval(format!(
244 "signal/transform {name} must be a symbol"
245 )));
246 };
247 Ok(symbol.as_qualified_str().to_owned())
248}
249
250fn value_to_usize(cx: &mut Cx, value: &Value, name: &str) -> Result<usize> {
251 let literal = value_to_number(cx, value, name)?;
252 literal.canonical.parse::<usize>().map_err(|_| {
253 Error::Eval(format!(
254 "signal/transform {name} must be a non-negative integer"
255 ))
256 })
257}
258
259fn value_to_real_list(cx: &mut Cx, value: &Value) -> Result<Vec<f64>> {
260 value_to_list(cx, value, "input")?
261 .iter()
262 .enumerate()
263 .map(|(index, value)| {
264 value_to_number(cx, value, &format!("input[{index}]"))?
265 .canonical
266 .parse::<f64>()
267 .map_err(|_| {
268 Error::Eval(format!(
269 "signal/transform input[{index}] must be an f64-compatible number"
270 ))
271 })
272 })
273 .collect()
274}
275
276fn value_to_complex_list(cx: &mut Cx, value: &Value) -> Result<Vec<(f64, f64)>> {
277 value_to_list(cx, value, "input")?
278 .iter()
279 .enumerate()
280 .map(|(index, value)| {
281 let pair = value_to_list(cx, value, &format!("input[{index}]"))?;
282 let [real, imag] = pair.as_slice() else {
283 return Err(Error::Eval(format!(
284 "signal/transform input[{index}] must contain real and imaginary components"
285 )));
286 };
287 Ok((
288 value_to_number(cx, real, &format!("input[{index}].real"))?
289 .canonical
290 .parse::<f64>()
291 .map_err(|_| Error::Eval("complex real component must be f64".to_owned()))?,
292 value_to_number(cx, imag, &format!("input[{index}].imag"))?
293 .canonical
294 .parse::<f64>()
295 .map_err(|_| {
296 Error::Eval("complex imaginary component must be f64".to_owned())
297 })?,
298 ))
299 })
300 .collect()
301}
302
303fn value_to_number(cx: &mut Cx, value: &Value, name: &str) -> Result<NumberLiteral> {
304 value
305 .object()
306 .as_number_value()
307 .ok_or(Error::TypeMismatch {
308 expected: "number",
309 found: "non-number",
310 })?
311 .number_literal(cx)?
312 .ok_or_else(|| Error::Eval(format!("signal/transform {name} has no numeric literal")))
313}
314
315fn value_to_list(cx: &mut Cx, value: &Value, name: &str) -> Result<Vec<Value>> {
316 let list = value.object().as_list().ok_or(Error::TypeMismatch {
317 expected: "list",
318 found: "non-list",
319 })?;
320 force_list_to_vec(cx, list, &format!("signal/transform {name}"))
321}
322
323fn buffer_to_value(cx: &mut Cx, output: SignalBuffer) -> Result<Value> {
324 match output {
325 SignalBuffer::Real(values) => {
326 let values = values
327 .as_slice()
328 .iter()
329 .map(|value| f64_value(cx, *value))
330 .collect::<Result<Vec<_>>>()?;
331 cx.factory().list(values)
332 }
333 SignalBuffer::Complex(values) => {
334 let values = values
335 .as_slice()
336 .iter()
337 .map(|(real, imag)| {
338 let real = f64_value(cx, *real)?;
339 let imag = f64_value(cx, *imag)?;
340 cx.factory().list(vec![real, imag])
341 })
342 .collect::<Result<Vec<_>>>()?;
343 cx.factory().list(values)
344 }
345 }
346}
347
348fn f64_value(cx: &mut Cx, value: f64) -> Result<Value> {
349 let canonical = if value == 0.0 {
350 "0".to_owned()
351 } else {
352 value.to_string()
353 };
354 cx.factory()
355 .number_literal(Symbol::qualified("numbers", "f64"), canonical)
356}
357
358fn signal_error_to_kernel(error: SignalError) -> Error {
359 Error::Eval(format!("signal/transform: {error}"))
360}
361
362fn argument_error() -> Error {
363 Error::Eval(
364 "signal/transform expects kind, direction, normalization, packing, len, and input"
365 .to_owned(),
366 )
367}