Improved UI
This commit is contained in:
@@ -99,6 +99,16 @@ func buildNode(d NodeDef) (dsp.Node, error) {
|
||||
case "expr":
|
||||
return &dsp.ExprNode{Expr: stringParam(p, "expr")}, 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")}, nil
|
||||
|
||||
|
||||
@@ -535,6 +535,68 @@ func (p *exprParser) parseFactor() (float64, error) {
|
||||
return 0, fmt.Errorf("unexpected character %q at position %d", ch, p.pos)
|
||||
}
|
||||
|
||||
// ── LowPassNode ───────────────────────────────────────────────────────────────
|
||||
|
||||
// LowPassNode implements a cascaded first-order IIR (RC) low-pass filter.
|
||||
// The filter coefficient α is recomputed each sample from the elapsed time so
|
||||
// it is correct for event-driven (non-fixed-rate) signals.
|
||||
// For Order > 1 the single-pole stage is cascaded Order times, approximating
|
||||
// a Butterworth response with -20·Order dB/decade roll-off.
|
||||
type LowPassNode struct {
|
||||
Freq float64 // cutoff frequency in Hz (–3 dB point)
|
||||
Order int // number of cascaded stages (1–8)
|
||||
}
|
||||
|
||||
func (n *LowPassNode) Type() string { return "lowpass" }
|
||||
|
||||
func (n *LowPassNode) Process(inputs []float64, state map[string]any) (float64, error) {
|
||||
if len(inputs) == 0 {
|
||||
return 0, errors.New("lowpass: no inputs")
|
||||
}
|
||||
x := inputs[0]
|
||||
now := time.Now()
|
||||
|
||||
order := n.Order
|
||||
if order < 1 {
|
||||
order = 1
|
||||
}
|
||||
if order > 8 {
|
||||
order = 8
|
||||
}
|
||||
fc := n.Freq
|
||||
if fc <= 0 {
|
||||
fc = 1.0
|
||||
}
|
||||
|
||||
prevT, hasT := state["t"].(time.Time)
|
||||
if !hasT {
|
||||
// First sample: prime all stage states with the input value.
|
||||
for i := range order {
|
||||
state[fmt.Sprintf("y%d", i)] = x
|
||||
}
|
||||
state["t"] = now
|
||||
return x, nil
|
||||
}
|
||||
|
||||
dt := now.Sub(prevT).Seconds()
|
||||
state["t"] = now
|
||||
if dt <= 0 {
|
||||
dt = 1e-9
|
||||
}
|
||||
|
||||
rc := 1.0 / (2.0 * math.Pi * fc)
|
||||
alpha := dt / (rc + dt)
|
||||
|
||||
y := x
|
||||
for i := range order {
|
||||
key := fmt.Sprintf("y%d", i)
|
||||
prev, _ := state[key].(float64)
|
||||
y = alpha*y + (1-alpha)*prev
|
||||
state[key] = y
|
||||
}
|
||||
return y, nil
|
||||
}
|
||||
|
||||
// ── LuaNode ───────────────────────────────────────────────────────────────────
|
||||
|
||||
// LuaNode runs a Lua script in a sandboxed gopher-lua VM.
|
||||
|
||||
Reference in New Issue
Block a user