Bundles

A directory of nodes and the packages they import. Everything goofi ships is one, but two of them are the engines' own and the rest are optional packs.

what a bundle is on disk
node-bundles/eeg/
  eeg_playback.py  eeg_power_bands.py  fooof.py  lsl_in.py
  requirements.txt

Every shipped bundle is built at goofi's build time and embedded, so cargo run carries them all without a flag. A type is namespaced by the plane it runs on, as signal:Psd or audio:Osc, which is why two bundles can both hold a node called Gain without either shadowing the other.

--extra-nodes adds a root outside the repo the same way. It is a folder of node files, .py and .rs, scanned after the shipped bundles and before the open patch's own workspace:

cargo run -- --extra-nodes ~/my-nodes

A bundle with Python nodes names the packages they import in a requirements.txt. goofi-init installs every shipped bundle's requirements, and at startup goofi checks every scanned root's against both interpreters. A terminal is asked before anything is installed, and without one the nodes are unavailable: greyed in the palette and naming what is missing.

A library that installs a bundle by name is designed but unbuilt; it lives in the project's backlog as roadmap/library.md, one file per unbuilt feature.

Core, and the rest

A bundle named after an engine is that engine's own node set: signal and audio today. Those are the core. They are Rust for the most part, they carry no dependency you have to install, and every clone has them.

The others are packs: Python nodes for one domain, depending on packages named in their requirements.txt. They ship too, but a node in one goes unavailable when its packages are missing, and says which. A core node always works; a pack node works once its imports do.

BundleKindNodesWritten in
audiocore14rust
signalcore38python + rust
complexitypack9python
eegpack3python

node-bundles/audio core · 14 nodes

NodeWhat it does
DelayAn echo, one line per channel, at a delay that may move while it sounds.
EnvAn ADSR in [0, 1] over `gate`, one voice per channel of it.
FeedbackIts input one block late: the one way a loop closes. It runs first each block, reading what its producer left in the last one.
FilterA resonant state-variable filter, low, band or high, at audio-rate cutoff.
FreqShiftEvery partial moved by the same number of hertz, which no interval survives.
GainIts input times `gain`; the wires into `input` sum.
LimiterOne gain across every channel, so nothing leaves above the ceiling and the picture holds.
MixdownMany channels become few, each one placed across the field it lands in.
NoiseNoise in [-1, 1], white or pink, on as many channels as are asked for.
OscAn oscillator in [-1, 1], one channel per channel of `pitch`.
QuantizeA pitch in volts pulled to the nearest note of a scale.
ReverbA room around the sound: eight delay lines fed back through each other, out in stereo.
SignalInThe in-order crossing: a `[C, T]` signal frame with `sfreq` enters as `C` audio channels, resampled to the rate. A frame with no `sfreq` enters one sample per sample, so a control value is held until the next.
SlewIts input with a rate limit: a step becomes a ramp, a gate an envelope, a knob a glide.

node-bundles/signal core · 38 nodes

NodeWhat it does
BufferRolling window along one axis: keeps the most recent `size` entries, rank unchanged.
ConstantA number, given a shape: the way a scalar becomes an array of the size a node downstream wants.
DelayThe stream as it was a moment ago, counted in samples, seconds or updates.
EigenThe eigenvalues and eigenvectors of a square symmetric matrix, or of its Laplacian.
EmdPull a frame apart into the oscillations it is made of, fastest first.
FftThe spectrum both ways: samples into one-sided bins, and those bins back into samples.
FilterKeep one band of the spectrum, run both ways so nothing comes out shifted in time.
FormatSeveral strings become one, joined in order or placed into a template.
FreqShiftMove a signal up or down the spectrum by a fixed number of hertz, without changing its speed.
FromJsonThe first object in a piece of text becomes a table.
FunctionOne elementwise function over the frame, chosen by name. Shape and metadata are untouched.
HilbertHow big a signal's swing is, where in its cycle it stands, and how fast that is turning.
JoinLay several frames end to end along one axis, or stack them onto a new axis named after their senders.
LFOA low-frequency oscillator: one sample per update to modulate a param, or a block of samples to feed a signal.
LslInReceive an LSL stream: [channels, samples] per tick, with its labels and rate.
LslOutPublish a frame as an LSL stream, with the labels and rate it arrived with.
MathScale and shift every value, then map one range onto another. Shape and metadata are untouched.
MetaWrite the sample rate or the channel names onto a frame, for a source that does not carry its own.
MidiInReceive MIDI: the velocity of every held note, and the value of every controller.
MidiOutSend MIDI: note-on for a number that appears, note-off for one that goes.
NoiseUniform, normal or pink noise, one independent stream per channel.
NormalizePut a signal on a common scale, from statistics over a window of its own past.
OperationCombine several frames: arithmetic folded left with numpy broadcasting, a matrix product, or a correlation.
OscInReceive OSC messages: one table entry per address, the latest value each.
OscOutSend a table as OSC messages, one address per entry.
PsdHow much power sits at each frequency, in Hz, over the last axis.
ReduceCollapse one axis to a single value per position on the others: a mean, a spread, an extreme or a total.
ResampleChange a signal's sample rate, filtering as it goes so nothing folds back.
ReshapeReorder the axes of a frame, then re-cut the entries into a new shape.
SelectKeep part of one axis, named by label, by position, or by the coordinate range it carries.
SmoothTake the edge off a signal, as a running mean or as an exponential decay.
SwitchLet one of several wires through, chosen by number, so a signal can pick the route.
TableSeveral wires become one table, each under a key.
TableSelectOne field out of a table, on the output that matches what the field holds.
TextA string, which an expression can build out of anything else in the patch.
ThresholdTurn a signal into a decision: one where it is past the level, zero where it is not.
ToJsonA table becomes JSON text: an object, with arrays as lists and strings as strings.
WaveletHow a signal's frequencies come and go across the frame, which one spectrum cannot show.

node-bundles/complexity pack · 9 nodes

NodeWhat it does
DetrendedFluctuationDFA exponent: how far a signal remembers its own past.
FractalDimensionFractal dimension: how much the trace fills the plane, 1 (smooth) to 2 (filling).
HjorthHjorth mobility and complexity: mean frequency, and distance from a sine.
LempelZivLZ76 complexity: how much a signal repeats itself.
PermutationEntropyPermutation entropy: disorder in the ORDER of neighbouring samples.
SampleEntropySample entropy: how rarely a pattern that recurred keeps recurring.
SpectralEntropySpectral entropy: how flat the power spectrum is, tone to noise.
SvdEntropySVD entropy: how many independent components the signal's history spans.
ZeroCrossingsZero crossings: how often the signal changes sign over the window.

node-bundles/eeg pack · 3 nodes

NodeWhat it does
EegPlaybackReplay an EEG recording at its own rate, as if it were live.
EegPowerBandsBand power per channel: delta to gamma, absolute or as a share of the whole spectrum.
FooofFOOOF: the aperiodic 1/f slope and the peaks that rise above it.

Your own

--extra-nodes is repeatable, and a later root wins a type name it shares with an earlier one. A patch's own workspace is scanned last of all, so a patch can always override what it was given.

This reference describes goofi 3.1.0(537cd394), generated from a running instance on 2026-09-06.