Add synthetic array (waveform) DSP support + UX improvements
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>
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user