signal: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.
50 nodesBrain, body, sound and text, read and answered while they happen. goofi is a node graph for building real-time experiences. Every node owns a thread and decides when to run, so a patch is a set of parts that are all alive at once.
cargo run -p goofi-init # once per clone
cargo run # builds the app if stale, prints the URLFive nodes goofi ships, turning a live EEG stream into sound: the stream, its spectrum, its
band powers, and the audio those powers drive. The cable into SignalIn is where
the patch crosses from the signal plane to the audio one.
A node runs on an engine, and its colour says which. They share one graph and one node interface, so a cable crosses between them: a band power off the signal engine can set a filter cutoff on the audio one.
signal: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.
50 nodesaudio:The same node interface at audio rate. Live in and out, MIDI, oscillators, filters, envelopes and feedback, plus any VST3 plugin on the machine hosted as a node.
17 nodesFrames of pixels, for camera in and generated visuals out. The viewers already draw bitmaps; the engine that fills them is the next one to land.
to comeReserved, and not yet named. Its colour is chosen so a patch can already tell it apart from the three that exist.
to comeWhat travels on the cable is a second axis: four data types, ARRAY, AUDIO, STRING and TABLE, each with its own ink on a node's pins. 6 viewers draw them, line, image, trajectory, topomap, string, table, and each binds to one output slot and redraws as frames arrive.
There is no tick. Every node owns a thread and schedules itself, and frames travel node to node over shared memory. Nothing waits on a global clock, so a kHz EEG stream and an audio chain sit in the same patch without either holding the other up.
A node you add is another inhabitant: it reads what reaches it and decides for itself how often to run. Several people can work inside one patch at the same time, and undo stays each person's own.
One programmatic interface
Everything goofi can do, it does through one vocabulary of 57 ops. The editor, the CLI, a test and an agent are all transports over that one entry point. A capability only the UI can reach is a defect.
So /mcp is not an integration built alongside the app. It is the same
vocabulary on a different wire. Describe the patch you want, let the agent build it, then
take the mouse back.
$ goofi node add signal:Psd
$ goofi link add lslin.out psd.data
$ goofi layout panel edit p2 --type viewerExtending it
Drop a Python or Rust file in a folder and it is in the palette. The file does not choose where it runs. A probe imports it and sends it to the tier its imports allow. Python is part of goofi rather than an add-on, and a parameter can be an expression it evaluates.
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)goofi is a single binary with the app compiled into it. It is the server, and it
is the CLI client of a running server. It serves the app and never opens one: it prints the
URL, and the opening is yours.
Every number on this page is read from goofi 3.1.0 describing itself. None of it is written by hand.