f7f297c3df
Adds full array/waveform support through the synthetic DSP engine: a dsp.Sample value model (scalar or []float64), array ops (index, slice, sum, mean, min, max, length, fft) with an in-tree radix-2 FFT, and static type propagation (OpOutputType) that the editor mirrors to colour wires by data type and flag invalid wirings. Stateful filters and lua stay scalar-only. Adds a waveform plot mode (x-vs-index trace). Also: errored-node hover reasons, S/N add-signal/add-node HUD shortcuts in the synthetic editor, and view-mode widgets that blend with the canvas background (chrome kept in edit mode). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
75 lines
2.2 KiB
Go
75 lines
2.2 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,
|
|
}
|
|
)
|
|
|
|
// 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
|
|
}
|
|
}
|
|
}
|