Grid Tutorials
Use these guided tours for layout, manipulation, mode rows, mode-specific gestures, and gesture recording.
A tutorial over the basics of oooo
oooo is a tool for creating and manipulating digital tape loops. These loops can be synchronous or asynchronous, linked or unlinked from the daw host (Ableton, FL Studio, Logic, Reaper, Bitwig, etc.), and can be manipulated in level, panning, and rate in addition to being played polyphonically like a mellotron with MIDI or with a grid. Each loop has its own effect sends, filters, and simple EQ. Each loop can record continouously or as a one shot, and can be routed to any other loop.
Use these guided tours for layout, manipulation, mode rows, mode-specific gestures, and gesture recording.
| Control | Action |
|---|---|
| H | toggle help |
| N | create new loop |
| CLICK INSIDE O | select loop (nearest center) |
| DRAG O | move selected loop (pan/volume) |
| CLICK PERIMETER O | jump cut on selected loop |
| DBL-CLICK OUTSIDE O | clear loop selection |
| CLICK+DRAG OUTSIDE O | adjust selected parameter |
| LEFT / RIGHT | select parameter |
| UP / DOWN | adjust value (or LFO period) |
| P | global play/stop all loops |
| SPACE / P | toggle selected loop playback |
| R | toggle selected loop recording |
| Z | tap: load preset, hold 1s: save slot |
| I / O | hold 1s: load / save session archive (.zip) |
| C / V | copy selected loop / paste duplicate |
| DELETE / BACKSPACE | hold 1s: delete selected loop |
| X | hold 1s: clear selected loop audio |
| F1..F12 | select loop 1..12 |
| 0..7 | cut: 0/8..7/8 (selected/all) |
| T | toggle dark/light theme |
| L | toggle LFO on selected parameter |
| Q / A | increase / decrease LFO min (x) |
| W / S | increase / decrease LFO max (y) |
The tape controls provide another way to control the loops when hotkeys are not an option.
Press Play () to play a loop (when
selected) or all loops (when not). Press Stop () to stop all loops, or the
selected loop if it is the only one playing. When stopped, press Stop () again or
key 0 to jump to the start of the loop(s).
Press Forward () or Reverse () to step the playback rate up or down, respectively. Press Play () twice to return to normal speed.
Press Record () to toggle recording on the selected loop. In Continuous Mode, user-stop truncation behavior is controlled by the global Record Truncation parameter.
Loop parameters are accessible by clicking a loop. Use ← / → to scroll through parameters and ↑ / ↓ to change values.
Values can also be adjusted at any time by clicking a non-loop area of the screen and dragging the mouse up and down.
Additionally, values can be set to change via an LFO by pressing L and then using hotkeys: Q/A increases/decreases LFO min (x) and W/S increases/decreases LFO max (y).
Volume sets loop output level and Pan places the loop in the stereo field.
Loop Start and Loop Length define the active playback window within the loop allocation.
Rate changes playback speed/pitch, Fade Time controls loop boundary crossfade and also sets post-roll duration when using Record Mode = Once, and DAW Sync links loop timing behavior to host transport sync state.
Reset Every sets an interval for periodic jump-cuts while playback is running (0 disables). In unquantized mode it is edited in seconds; in quantized mode it is edited in beats and snapped by Quantize Resolution at Quantize BPM.
Reset To sets the jump target as a percentage of the active loop window (0% start, 50% middle, 100% end boundary).
Record New controls incoming write level, Record Keep controls retained buffer level during overdub, Record Mode selects Continuous (default) or Once, and Reverb sets loop send amount to the reverb bus.
In Record Mode = Once, recording automatically disarms after one loop span and applies post-roll equal to the current Fade Time.
Record Keep Freq sets a low-pass filter cutoff on the retained buffer signal during overdub. When set to All (default), the existing loop content is kept unfiltered. When set to a frequency value, highs are rolled off from the kept material on each record pass — lower values cause faster high-frequency loss over successive overdubs. (added in v1.4.0)
Input Live sets the percentage of the main audio input fed into this loop during recording (0–100%). At 100% (default) the full live input signal is passed to the loop; at 0% the main input is cut entirely, which is useful when the loop receives its signal exclusively from loop-to-loop routing. (added in v1.4.0)
Tape Bias and Tape Gain shape tape drive behavior; Bass Freq, Bass Boost, and Bass Q tune the low-frequency tape contour.
Post LPF/Post LPF Q and Post HPF/Post HPF Q control post-stage low-pass and high-pass filter cutoff and resonance.
Note Attack, Note Decay, Note Sustain, and Note Release define the per-note ADSR envelope for note-triggered loop playback.
Note Start % randomizes note start offset, MIDI CH sets MIDI channel filtering per loop, and Note Mode chooses the note playback start behavior.
Global parameters are reached by double-clicking anywhere that is not a loop to open the global parameters menu. Navigate with ← / →, change values with ↑ / ↓, or click-drag in a non-loop area to adjust values directly.
Quantize Resolution controls loop-length/beat quantization behavior, and Quantize BPM sets the tempo used for beat-based calculations.
Record Chain is great for recording a series of loops in order. When enabled, starting recording on one loop can hand off recording to the next loop when the current loop reaches its boundary, continuing through the remaining loop order.
To use it, select the loop you want to start from and toggle recording with R (or the loop record gesture). Turning Record Chain off cancels future automatic handoffs; manually stopping the currently chained loop also stops the rest of the chain.
Record Truncation controls whether manually stopping recording resizes the loop window to the latest capture span.
Default is OFF. When OFF, stopping recording does not edit loop boundaries.
When ON, manual stop truncates to record start → punch-out only when that span is contiguous (no wrap crossing). If recording crossed the loop boundary (including full-loop captures), truncation is skipped.
Input sets global input trim, while Dry and Wet control global dry/wet output balance.
These parameters shape the global reverb path: Predelay, input level, LPF, HPF, damping, mod frequency and mod depth.
Reverb Shimmer adds a global parallel shimmer return fed by the same per-loop Reverb sends. At 0% (default) it is off; values up to 200% increase the shimmer layer without replacing the main reverb.
Using oooo with a MIDI keyboard lets you play the loops as if it were a Mellotron - each note will change the rate scaling of the playing loop based around middle C. MIDI note input is handled per loop through MIDI CH and Note Mode: channel mode decides which incoming channels are accepted, note mode decides where playback starts on note-on, and note-off (including note-on with velocity 0) releases active note heads while Note Attack, Note Decay, Note Sustain, Note Release, and Note Start % shape the loop’s note response.
A monome grid is automatically detected in oooo. When a monome grid is detected, an icon () appears automatically in the top-right corner. Each 8x8 section of the grid (a "tile") controls one loop. Column 1 is the tile menu and columns 2-8 are the active area.
Note: if serialosc is running, oooo will connect through serialosc automatically. If serialosc is not running, oooo falls back to its direct grid driver.
Each tile has four modes: Isomorphic (col1,row1), Slice (col1,row2), Volume/Pan (col1,row3), and Rate (col1,row4). In Slice mode, playhead and loop span are shown across columns 2-8 in recorded-normalized space.
Column 1 is the tile menu. Columns 2-8 show note names sent in Isomorphic mode (note + octave). C notes are dimly highlighted. Example below: held D4 is bright, matching D4 pads are ghost-highlighted.
Anchor used by current runtime mapping: local tile row=8, col=2 is C2 (MIDI 36).
Open Live Coding by clicking the terminal icon () in the top-right app bar. This opens the browser-based Lua editor at http://127.0.0.1:17321/ by default.
Run the whole file or selected lines to create loops, shape playback, and control engine globals from Lua.
Common commands include o.create, o.duplicate, o.set(loop_id, ...), o.get(), o.record, and o.play.
-- create and start a 4 second loop
loop_id, err = o.create({ seconds = 4.0, fade = 0.15 })
assert(loop_id, err)
o.record(loop_id, true)
o.play(loop_id, true)
o.set(loop_id, {
pan = -0.25,
db = -6.0,
pan_slew = 0.3,
db_slew = 0.3,
})
state = o.get(loop_id)
print('seconds', state.seconds, 'playing', state.playing)
o.jumpn(loop_id, 0.5)
o.set({
in_db = -3,
wet_db = -6,
rv_predelay = 12,
rv_mod_depth = 2,
})
engine = o.get()
print('input', engine.in_db, 'wet', engine.wet_db)
This reference reflects the current runtime Lua bindings. Mutating commands return true on success, or nil, err on failure.
Functions are listed first. Any opts/props object keys are defined in the props tables directly below.
| Function | Parameters | Returns | Notes |
|---|---|---|---|
o.create(opts) | opts table (optional) | loop_id | nil, err | opts keys come from Loop Props. |
o.duplicate(loop_id) | loop_id integer | loop_id | nil, err | Creates a new loop by duplicating the source loop. |
o.set(loop_id, props) | loop_id integer, props table | true | nil, err | props keys come from Loop Props. |
o.set(props) | props table | true | nil, err | props keys come from Engine Props. |
o.record(loop_id, on) | loop_id integer, on boolean | true | nil, err | Starts/stops recording on one loop. |
o.play(loop_id, on) | loop_id integer, on boolean | true | nil, err | Starts/stops playback on one loop. |
o.jump(loop_id, seconds) | loop_id integer, seconds number | true | nil, err | Seeks to loop-relative seconds; values wrap by loop length. |
o.jumpn(loop_id, normalized) | loop_id integer, normalized number | true | nil, err | normalized must be in [0, 1]. |
o.bpm(value) | value number | bpm | nil, err | value must be > 0. |
o.clear(loop_id) | loop_id integer | true | nil, err | Clears loop audio/buffer content. |
o.delete(loop_id) | loop_id integer | true | nil, err | Deletes a loop. |
For o.create(opts), length resolution order is: beats (if > 0), then seconds (if > 0), else defaults to 10.0 seconds.
o.create(opts) and o.set(loop_id, props))| Key (opts/props) | Meaning | Range / Behavior |
|---|---|---|
| beats | Loop length in beats. | Number; must be > 0 to apply. Converted using current o.bpm(). |
| seconds | Loop length in seconds. | Number; must be > 0 to apply. |
| fade | Post-roll / fade time. | Clamped to [0, 20] seconds. |
| pre | Buffer keep amount. | Clamped to [0, 1]. |
| pre_slew | Slew time for pre. | Seconds; negative values are treated as 0. |
| rec | Input write gain. | Clamped to [0, 1]. |
| rec_slew | Slew time for rec. | Seconds; negative values are treated as 0. |
| db | Loop output level in dB. | Upper-clamped by engine to about +12 dB. |
| db_slew | Slew time for db. | Seconds; negative values are treated as 0. |
| pan | Stereo pan position. | Clamped to [-1, 1]. |
| pan_slew | Slew time for pan. | Seconds; negative values are treated as 0. |
| rv_send | Per-loop reverb send amount. | Clamped to [0, 1]. |
| pan_lfo_period | Pan LFO period. | Seconds; values <= 0 are internally clamped by runtime LFO evaluation. |
| pan_lfo_min | Pan LFO minimum. | Number; may be lower or higher than pan_lfo_max (runtime normalizes low/high). |
| pan_lfo_max | Pan LFO maximum. | Number; may be lower or higher than pan_lfo_min (runtime normalizes low/high). |
| pan_lfo_active | Enables/disables pan LFO. | Boolean. |
| db_lfo_period | Loop dB LFO period. | Seconds; values <= 0 are internally clamped by runtime LFO evaluation. |
| db_lfo_min | Loop dB LFO minimum. | Number; may be lower or higher than db_lfo_max (runtime normalizes low/high). |
| db_lfo_max | Loop dB LFO maximum. | Number; may be lower or higher than db_lfo_min (runtime normalizes low/high). |
| db_lfo_active | Enables/disables loop dB LFO. | Boolean. |
| length_lfo_period | Loop-length LFO period. | Seconds; values <= 0 are internally clamped by runtime LFO evaluation. |
| length_lfo_min | Loop-length LFO minimum. | Number (seconds); may be lower or higher than length_lfo_max. |
| length_lfo_max | Loop-length LFO maximum. | Number (seconds); may be lower or higher than length_lfo_min. |
| length_lfo_active | Enables/disables loop-length LFO. | Boolean. |
| start_lfo_period | Loop-start LFO period. | Seconds; values <= 0 are internally clamped by runtime LFO evaluation. |
| start_lfo_min | Absolute loop-start LFO minimum. | Number (seconds); may be lower or higher than start_lfo_max; runtime clamps to recorded span constraints. |
| start_lfo_max | Absolute loop-start LFO maximum. | Number (seconds); may be lower or higher than start_lfo_min; runtime clamps to recorded span constraints. |
| start_lfo_active | Enables/disables loop-start LFO. | Boolean. |
| rate | Loop playback rate. | Clamped by engine to [-4, 4]. |
| rate_lfo_period | Playback-rate LFO period. | Seconds; values <= 0 are internally clamped by runtime LFO evaluation. |
| rate_lfo_min | Playback-rate LFO minimum. | Number; may be lower or higher than rate_lfo_max (runtime normalizes low/high). |
| rate_lfo_max | Playback-rate LFO maximum. | Number; may be lower or higher than rate_lfo_min (runtime normalizes low/high). |
| rate_lfo_active | Enables/disables playback-rate LFO. | Boolean. |
Aliases are also accepted for rate LFO keys: lfo_period, lfo_min, lfo_max, lfo_active.
o.set(props))| Parameter | Meaning | Range / Behavior |
|---|---|---|
| in_db | Global input trim (dB). | Upper-clamped to +24 dB; supports very low values including -inf. |
| dry_db | Global dry level (dB). | Upper-clamped to +24 dB; supports very low values including -inf. |
| wet_db | Global wet level (dB). | Upper-clamped to +24 dB; supports very low values including -inf. |
| in_db_slew | Slew time for in_db. | Seconds; must be >= 0, negative values return error. |
| dry_db_slew | Slew time for dry_db. | Seconds; must be >= 0, negative values return error. |
| wet_db_slew | Slew time for wet_db. | Seconds; must be >= 0, negative values return error. |
| rv_predelay | Reverb predelay (ms). | Clamped to [0, 300] ms. |
| rv_predelay_slew | Slew time for rv_predelay. | Seconds; must be >= 0, negative values return error. |
| rv_in | Reverb input amount (%). | Clamped to [0, 100] %. |
| rv_in_slew | Slew time for rv_in. | Seconds; must be >= 0, negative values return error. |
| rv_lpf | Reverb input low-pass cutoff (Hz). | Clamped to [1, 20000] Hz. |
| rv_lpf_slew | Slew time for rv_lpf. | Seconds; must be >= 0, negative values return error. |
| rv_hpf | Reverb input high-pass cutoff (Hz). | Clamped to [1, 1000] Hz. |
| rv_hpf_slew | Slew time for rv_hpf. | Seconds; must be >= 0, negative values return error. |
| rv_damp | Reverb damping frequency (Hz). | Clamped to [10, 20000] Hz. |
| rv_damp_slew | Slew time for rv_damp. | Seconds; must be >= 0, negative values return error. |
| rv_mod_freq | Reverb modulator frequency (Hz). | Clamped to [0.01, 4.0] Hz. |
| rv_mod_freq_slew | Slew time for rv_mod_freq. | Seconds; must be >= 0, negative values return error. |
| rv_mod_depth | Reverb modulator depth (ms). | Clamped to [0, 10] ms. |
| rv_mod_depth_slew | Slew time for rv_mod_depth. | Seconds; must be >= 0, negative values return error. |
| Function | Parameters | Returns | Notes |
|---|---|---|---|
o.get(loop_id) | loop_id integer | loop_state | nil, err | Returns one loop snapshot table. |
o.get() | none | engine_state | nil, err | Returns global engine snapshot table. |
o.list() | none | {loop_id, ...} | nil, err | Returns sorted loop IDs. |
o.bpm() | none | number | nil, err | Returns current BPM. |
o.getForm: o.get(loop_id) returns loop_state or nil, err.
| Field | Meaning | Range / Type |
|---|---|---|
| loop_id | Loop identifier. | Integer. |
| playing | Current playback state. | Boolean. |
| recording | Current recording state. | Boolean. |
| pos | Current loop-relative position (seconds). | Number in [0, seconds). |
| pos_norm | Current normalized position. | Number in [0, 1]. |
| seconds | Current loop length. | Number, expected > 0. |
| alloc_seconds | Allocated loop buffer length. | Number, expected > 0. |
| fade | Post-roll / fade time. | Number in [0, 20]. |
| pre | Buffer keep amount. | Number in [0, 1]. |
| pre_slew | Slew time for pre. | Number, >= 0. |
| rec | Input write gain. | Number in [0, 1]. |
| rec_slew | Slew time for rec. | Number, >= 0. |
| db | Loop output dB target. | Number, upper-clamped by engine. |
| db_slew | Slew time for db. | Number, >= 0. |
| pan | Stereo pan target. | Number in [-1, 1]. |
| pan_slew | Slew time for pan. | Number, >= 0. |
| rv_send | Per-loop reverb send amount. | Number in [0, 1]. |
| pan_lfo_period | Pan LFO period. | Number (seconds). |
| pan_lfo_min | Pan LFO minimum. | Number. |
| pan_lfo_max | Pan LFO maximum. | Number. |
| pan_lfo_active | Pan LFO enabled. | Boolean. |
| db_lfo_period | Loop dB LFO period. | Number (seconds). |
| db_lfo_min | Loop dB LFO minimum. | Number. |
| db_lfo_max | Loop dB LFO maximum. | Number. |
| db_lfo_active | Loop dB LFO enabled. | Boolean. |
| length_lfo_period | Loop-length LFO period. | Number (seconds). |
| length_lfo_min | Loop-length LFO minimum. | Number. |
| length_lfo_max | Loop-length LFO maximum. | Number. |
| length_lfo_active | Loop-length LFO enabled. | Boolean. |
| start_lfo_period | Loop-start LFO period. | Number (seconds). |
| start_lfo_min | Loop-start LFO minimum (absolute seconds). | Number. |
| start_lfo_max | Loop-start LFO maximum (absolute seconds). | Number. |
| start_lfo_active | Loop-start LFO enabled. | Boolean. |
| rate | Loop playback-rate target. | Number, engine-clamped to [-4, 4]. |
| rate_lfo_period | Playback-rate LFO period. | Number (seconds). |
| rate_lfo_min | Playback-rate LFO minimum. | Number. |
| rate_lfo_max | Playback-rate LFO maximum. | Number. |
| rate_lfo_active | Playback-rate LFO enabled. | Boolean. |
o.getForm: o.get() returns engine_state or nil, err.
| Engine Field | Meaning | Range / Type |
|---|---|---|
| in_db / dry_db / wet_db | Global mix dB values. | Number, upper-clamped to +24 dB. |
| in_db_slew / dry_db_slew / wet_db_slew | Lag time for global dB values. | Number, >= 0. |
| rv_predelay | Reverb predelay. | Number in [0, 300] ms. |
| rv_predelay_slew | Predelay lag time. | Number, >= 0. |
| rv_in | Reverb input amount. | Number in [0, 100] %. |
| rv_in_slew | Reverb input amount lag. | Number, >= 0. |
| rv_lpf / rv_hpf / rv_damp | Reverb filter/damping frequencies. | rv_lpf: [1, 20000] Hz, rv_hpf: [1, 1000] Hz, rv_damp: [10, 20000] Hz. |
| rv_lpf_slew / rv_hpf_slew / rv_damp_slew | Lag time for reverb filter/damping params. | Number, >= 0. |
| rv_mod_freq / rv_mod_depth | Reverb modulation params. | rv_mod_freq: [0.01, 4.0] Hz, rv_mod_depth: [0, 10] ms. |
| rv_mod_freq_slew / rv_mod_depth_slew | Lag time for reverb modulation params. | Number, >= 0. |
| bpm | Current BPM. | Number, > 0. |