Files
uopi/internal/dsp/types.go
T
2026-06-22 17:49:14 +02:00

87 lines
2.8 KiB
Go

package dsp
import "fmt"
// Op type categories for static (compile-time / editor) type propagation.
// These mirror the runtime dispatch in the synthetic graph evaluator and the
// frontend's inferNodeTypes (web/src/lib/synthTypes.ts) — keep the three in
// sync; a parity test guards the Go/TS pair.
var (
// reductionOps collapse an array (or scalar) to a single scalar.
reductionOps = map[string]bool{
"index": true, "length": true, "sum": true,
"mean": true, "min": true, "max": true,
}
// arrayProducerOps require an array input and yield an array.
arrayProducerOps = map[string]bool{
"fft": true, "slice": true,
}
// scalarOnlyOps reject array inputs and yield a scalar. Stateful filters
// plus lua (whose state/closure cannot be broadcast per array lane).
scalarOnlyOps = map[string]bool{
"moving_average": true, "rms": true, "lowpass": true,
"derivative": true, "integrate": true, "lua": true,
}
// statefulOps accumulate state across evaluations, so a single-shot trace
// (fresh state) only approximates their true running value. lua is included
// because a script may persist state in its state table.
statefulOps = map[string]bool{
"moving_average": true, "rms": true, "lowpass": true,
"derivative": true, "integrate": true, "lua": true,
}
)
// IsStateful reports whether an op accumulates state across evaluations. The
// editor's live/debug trace flags such nodes as "approx" since it evaluates a
// single tick with fresh state.
func IsStateful(op string) bool { return statefulOps[op] }
// OpOutputType reports the output ValType of an op given its input types, and
// an error if the inputs are definitely incompatible with the op. Inputs may be
// ValUnknown (a source whose real type is not yet known at compile time); such
// inputs never trigger an error — runtime Sample typing is authoritative.
func OpOutputType(op string, in []ValType) (ValType, error) {
switch {
case reductionOps[op]:
return ValScalar, nil
case arrayProducerOps[op]:
for _, t := range in {
if t == ValScalar {
return ValUnknown, fmt.Errorf("%s requires an array input", op)
}
}
return ValArray, nil
case scalarOnlyOps[op]:
for _, t := range in {
if t == ValArray {
return ValUnknown, fmt.Errorf("%s does not accept an array input", op)
}
}
return ValScalar, nil
default:
// Elementwise stateless ops (gain, offset, add, subtract, multiply,
// divide, clamp, threshold, expr): array if any input is an array,
// scalar if all inputs are definitely scalar, otherwise unknown.
anyArray, anyUnknown := false, false
for _, t := range in {
switch t {
case ValArray:
anyArray = true
case ValUnknown:
anyUnknown = true
}
}
switch {
case anyArray:
return ValArray, nil
case anyUnknown:
return ValUnknown, nil
default:
return ValScalar, nil
}
}
}