Frames of array data at whatever rate the source runs. LSL, MIDI and OSC in and out, spectra and band powers, complexity measures, and the arithmetic to select, reshape, combine and route whatever comes out of them.
A biofeedback framework for explorative experiments.
Rapidly prototype the entire biofeedback loop end-to-end. goofi handles signal processing, feature extraction, as well as audiovisual stimulus generation, all in one modular node patching editor. Don't know how? Your agent will know due to native AI agent integration.
Three engines, and your plugins.
Each node runs at its own pace: a kilohertz EEG stream and an audio chain share a patch without either holding the other up. Several people can work in it at once, each with their own undo. Building and exploring become the same act, and what you take away is intuition about your signal and the features you pull from it.
The same nodes at audio rate. Live in and out, MIDI, oscillators, filters, envelopes and feedback.
Frames of pixels on the GPU. A node is a WGSL fragment shader: noise, ramps and shapes to make them, blur, levels, displacement and compositing to work them, and feedback. A signal frame comes in through ArrayIn, as pixels, a line plot or a trajectory.
Any VST3 on the machine, as an audio node: goofi finds the plugins your system has installed. A plugin opens its own editor window, and its parameters are the node’s, so a band power can drive one.
Your agent, inside the patch.
goofi embeds the terminal agent you already use — claude, codex and opencode out of the box, and any other command you
name. It opens as a panel beside the canvas, starts in the patch's workspace, and reads the AGENTS.md there to orient itself. You talk to it the way you always do.
It reaches everything the editor does: add nodes, wire them, bind a parameter, lay out the panels, save the patch. Every edit it makes shows up as it happens, so you watch the patch build itself and take the mouse back whenever you like — its work lands on the same undo stack as yours.
The same patch, from a shell.
Everything the editor does is an op with a name, and the goofi binary speaks
them to a running goofi. Add a node, wire it, set a parameter, lay out a panel; the canvas
follows as you type. A file of ops is a script, and a script lands as one undo step.
$ goofi node add signal:Psd
$ goofi link add lslin.out psd.data
$ goofi layout panel edit p2 --type viewer
$ goofi - < ops.txt # a batch from stdin, one undo stepWrite a file, get a node.
Drop a Python, Rust or WGSL file in a folder and it is in the palette. goofi brings its own Python, so there is nothing to install first, and a parameter can be a Python expression.
import goofi
import numpy as np
class Smooth(goofi.Node):
"""Rolling mean over the last axis."""
INPUTS = {"data": goofi.InputSlot(goofi.DataType.ARRAY, required=True)}
OUTPUTS = {"out": goofi.DataType.ARRAY}
PARAMS = {"smoothing": {"window": goofi.IntParam(8, 1, 512, doc="Samples in the mean.")}}
def process(self, data):
w = self.params.smoothing.window
kernel = np.ones(w, dtype=np.float32) / w
return np.apply_along_axis(lambda v: np.convolve(v, kernel, mode="same"), -1, data.data)