package synthetic import ( "fmt" "github.com/uopi/uopi/internal/dsp" ) // floatParam extracts a float64 from params; returns 0 if missing or wrong type. func floatParam(params map[string]any, key string) float64 { if params == nil { return 0 } v, ok := params[key] if !ok { return 0 } f, ok := v.(float64) if !ok { return 0 } return f } // stringParam extracts a string from params; returns "" if missing or wrong type. func stringParam(params map[string]any, key string) string { if params == nil { return "" } v, ok := params[key] if !ok { return "" } s, ok := v.(string) if !ok { return "" } return s } // stringSliceParam extracts a []string from params; JSON arrays decode to []any, // so each element is coerced via its string value. Returns nil if missing. func stringSliceParam(params map[string]any, key string) []string { if params == nil { return nil } v, ok := params[key] if !ok { return nil } arr, ok := v.([]any) if !ok { return nil } out := make([]string, 0, len(arr)) for _, e := range arr { if s, ok := e.(string); ok && s != "" { out = append(out, s) } } return out } // buildNode converts a single NodeDef (from JSON) into a dsp.Node ready for // execution. JSON numbers are float64, so all numeric params are handled as // float64 regardless of the final type needed. func buildNode(d NodeDef) (dsp.Node, error) { p := d.Params switch d.Type { case "gain": return &dsp.GainNode{Gain: floatParam(p, "gain")}, nil case "offset": return &dsp.OffsetNode{Offset: floatParam(p, "offset")}, nil case "add": return &dsp.AddNode{}, nil case "subtract": return &dsp.SubtractNode{}, nil case "multiply": return &dsp.MultiplyNode{}, nil case "divide": return &dsp.DivideNode{}, nil case "moving_average": return &dsp.MovingAverageNode{Window: max(1, int(floatParam(p, "window")))}, nil case "rms": return &dsp.RMSNode{Window: max(1, int(floatParam(p, "window")))}, nil case "derivative": return &dsp.DerivativeNode{}, nil case "integrate": return &dsp.IntegrateNode{}, nil case "clamp": return &dsp.ClampNode{ Min: floatParam(p, "min"), Max: floatParam(p, "max"), }, nil case "threshold": return &dsp.ThresholdNode{ Threshold: floatParam(p, "threshold"), High: floatParam(p, "high"), Low: floatParam(p, "low"), }, nil case "expr": return &dsp.ExprNode{Expr: stringParam(p, "expr"), Vars: stringSliceParam(p, "vars")}, nil case "lowpass": order := int(floatParam(p, "order")) if order < 1 { order = 1 } return &dsp.LowPassNode{ Freq: floatParam(p, "freq"), Order: order, }, nil case "lua": return &dsp.LuaNode{Script: stringParam(p, "script"), Vars: stringSliceParam(p, "vars")}, nil case "index": return &dsp.IndexNode{I: int(floatParam(p, "i"))}, nil case "slice": return &dsp.SliceNode{Start: int(floatParam(p, "start")), End: int(floatParam(p, "end"))}, nil case "sum": return &dsp.SumNode{}, nil case "mean": return &dsp.MeanNode{}, nil case "min": return &dsp.MinNode{}, nil case "max": return &dsp.MaxNode{}, nil case "length": return &dsp.LengthNode{}, nil case "fft": return &dsp.FFTNode{}, nil default: return nil, fmt.Errorf("unknown node type %q", d.Type) } }