Score

Live Console

The live console lets you type and execute DSL cue expressions in real time during a performance. Any cue the score system supports can be triggered from the console -- synthesis, audio playback, animation, navigation, OSC messaging, and more.

The console can run in two modes: embedded inside the score view, or as a standalone window using the dedicated view system. In standalone mode, commands are sent over WebSocket to the score window and executed there.


Try It: the Live Coding Demo

The Live Coding demo (demo-livecode, under Sound & space) opens straight into perform mode on a dark, empty stage with no bars (double-click near the top or bottom edge for them), with a buffer of short snippets: sounds that change each time you run them, shapes, a turning triangle panning a saw, a dot on a line driving an organ's volume (and turned into a fader), scope views and palettes, a turning scope, audio drawn live. Ctrl+Enter on any line.

Live coding is meant for dark mode (the demo turns it on). Dark mode inverts the page, so a light score turns dark; what the console makes -- the code, shapes, sound boxes -- keeps the colours you give it, so fill:#111 is black and neon stays neon (very saturated colours come out a touch muted).

A project can do the same for itself:

The Projector: Visuals There, Code Here

With the projector as a second display, run

projector()

in the console. A second window of the same project opens (allow pop-ups for the page if the browser asks): drag it to the projector and press F11. It shows only the visuals -- no bars, no console -- and runs every line you run, as you run it. It is silent: its sounds play inside it, so its scopes draw them, but only your laptop window is heard. Random values (rand(), irand(), birand(), rand([...]), choose(a, b, ...)) are rolled once on the laptop and the same numbers are used in both windows, so the triangle is where it is on both screens and the scope shows the pitch you hear. (Inside a pattern -- Pseq, Prand ... -- randomness is drawn per step in each window.)

Ctrl+Shift+P on the laptop puts the code on the projector too -- the same text, caret, scroll and run flashes, sized to the projector -- and takes it off again.

If the projector window is reloaded or opened late, it asks the laptop for what has been run so far and catches up. The window is the project's URL with &follow=1, which you can also open by hand.

Other set-ups: one window in perform mode, mirrored to the projector, shows code over visuals on both; a ?view=live window gives a console with no visuals of its own, driving a score window elsewhere.

Opening the Console

In the score view: click the >_ button in the top bar, or open it programmatically.

As a standalone window: add ?view=live to the URL:

http://localhost:3000/?project=my-score&view=live

This opens a lightweight window with only the live console and WebSocket connection -- no score rendering, no audio context, no animation loop.


Perform Mode

For a concert, Ctrl+Shift+F (or the ⛶ button in the header) turns the console into the top layer of the score: the code fills the screen in large type, each line on a dark band so it reads over any score, and the line you run flashes green, or red if it was refused. The last few lines of output run along the bottom. The line you are typing stays on the middle of the screen, moving down or up: the code scrolls under it, so the typing stays in the top half and the bottom is left to the visuals. The first lines too: there is blank room above the code to scroll into. Tweaking one sound with the visuals up, Ctrl+Shift+B leaves only the block you are in on the screen (the #$ block, or the lines round the cursor up to a blank line); move into another block and it shows that one instead. Ctrl+Shift+B again leaves only the line at the cursor (the whole expression, if it runs over several lines), and once more brings back all the code.

The code takes the mouse -- the column from its left edge to the end of its longest line, first line to last, blank lines included: click to put the cursor there, drag to select, double click for a word, scroll with the wheel or a two-finger swipe (a swipe started on the code keeps scrolling it to the end), pinch to size the text. Alt+drag moves the whole block; Alt+double click puts it back. Right of the code and below it the mouse goes through to the score, so you can still drag a scope, press a button or move a fader. The keyboard stays in the code: after a click on the score, typing carries on where the cursor was.

A− / A+ change the size of the code. Perform mode keeps its own size, so the panel stays small when you switch back.

Ctrl+. stops every sound, like the stop button, in either mode -- after a small warning: Enter to stop, Esc to leave it playing (pressing Ctrl+. twice stops at once). Ctrl+, stops everything that moves on the screen instead, and leaves the sound playing.

A reload comes back as you left it: the console open or not, perform mode on or off, the panel where you put it, and the cursor and scroll in the code. This is remembered in this browser only.

Panel Sections

The console has three vertically stacked sections. Drag the bars between them to resize.

Editor -- a text area for writing DSL expressions. Type a cue expression and press Ctrl+Enter to execute the current line, or Ctrl+Shift+Enter to execute all lines.

Output -- shows execution results, errors, and (in standalone mode) a browsable list of all cue expressions found in the score.

Signals -- a live monitor of all ParamBus signals. Shows the current value of every active signal path. Use the filter field to narrow the display.

The panel is draggable (by its header bar) and resizable (all edges and corners).


Keyboard Shortcuts

Key Action
Ctrl+Enter Execute the current line
Ctrl+Alt+Enter Execute the line on every voice of a poly: sound (Ctrl+Enter adds a voice)
Ctrl+_ Freeze the line: its synths on their last note, its animations where they are (hold()); again to let them go on
Ctrl+Shift+Enter Execute all lines
Ctrl+Shift+E Enter cue browser (standalone mode). Not Ctrl+J: that is the browser's downloads
Ctrl+Shift+L Show / hide the console (perform mode is kept while hidden)
Ctrl+Shift+P Show / hide the code on the projector window (projector())
Tab Take the suggestion shown faintly after the cursor (two spaces when there is none)
Ctrl+Shift+H The cheatsheet: the console's keys and a reminder of the language, faint beside the console (over the score's right edge in perform mode). It takes no clicks and you go on typing; Ctrl+Shift+H or Esc puts it away
Tab on a #$ line Fold its block away, or bring it back (Folding)
Enter A new line. Inside an unclosed bracket -- synth(uid:a, -- the new line carries the expression on, indented two spaces past where it began
Ctrl+Space List the suggestions to pick from: arrows to choose, Tab / Enter to take, Esc to close. (It never opens by itself: what can go where shows as a faint reminder in the line)
Ctrl+= / Ctrl+- Bigger / smaller code (the panel and perform mode keep a size each); the browser's zoom while the console is closed
Ctrl+Shift+D Help: the page of the cue under the cursor (or the call it is inside), in a new tab
Ctrl+. Hush: asks first -- Enter stops every sound, Esc leaves it; Ctrl+. twice stops at once
Ctrl+Shift+, still(all): asks first, Enter stops every animation and text and puts the screen back to plain -- screen(off), every gui()'s effects off, trails wiped; the sound plays on
Ctrl+, Still: asks first, Enter stops every animation and text (still()) and what the texts were saying; the sound plays on. Esc leaves it, Ctrl+, twice stops at once
Ctrl+Shift+. Stop what the line under the cursor started, and leave its text alone (Ctrl+Enter brings it back)
Ctrl+C / Ctrl+X (nothing selected) Copy / cut the whole line
Ctrl+Shift+K Delete the line, or every line the selection touches (the clipboard is left alone)
Ctrl+V (after a line copy) Paste it as a line above the cursor's: copy + paste duplicates a line
Ctrl+Shift+F Perform mode on/off
Ctrl+Shift+B Focus, in turn: only the block at the cursor shows -- the #$ block it is in, or the lines round it up to a blank line --, then only the line (the expression) at the cursor, then all the code; the rest out of sight, in its place; the projector shows the same
Ctrl+Shift+S Copy the line, or every line the selection touches, to the project's stash (see The Stash)
Ctrl+Alt+Shift+S The same, to the global stash beside your projects
Escape Exit cue browser

Target Selection

Some cue types operate on a specific SVG element -- rotate, scale, o2p, color, fade. Before executing these, you need to select a target.

In the score view: click pick and click on an element in the score. Or type a uid or element ID into the target input and press Enter.

In standalone mode: the pick button is unavailable (no local score). Instead, type into the target input -- it autocompletes from all element IDs in the score. When you insert a cue expression from the browser, the element is targeted automatically since the expression IS the element's ID.

Cue types that do not need an element (synth, audio, speed, nav, osc, stop, etc.) execute without a target.


Cue Browser

In standalone mode, the console fetches the project SVG on open and lists all DSL cue expressions in the output panel. You can interact with this list to select and insert cues into the editor.

Keyboard workflow

  1. Press Ctrl+Shift+E to enter browse mode
  2. Navigate with arrow keys (or j/k)
  3. Press Enter to insert the selected expression at the cursor
  4. Focus returns to the editor automatically
  5. Press Escape to exit without inserting

You can also click any entry to insert it.


Multi-line Expressions

The editor supports multi-line DSL expressions. Lines with unbalanced parentheses are joined to the next line, so long expressions can be split for readability:

synth(
  wave:sin,
  freq:440,
  dur:2,
  amp:0.6
)

Ctrl+Enter detects which expression the cursor is inside and executes the whole thing.

A blank line always ends an expression, so keep a multi-line block together. // comments can sit on their own line or at the end of one.

Folding Blocks Away

A line starting with #$ heads a block: everything below it, up to the next #$ line. Tab on that line folds the block away; the line stays, with what is written on it, and shows how much is hidden. Tab again brings it back.

#$ -- drums --                ▸ 3 lines
#$ -- pads, for the second half --
synth(uid:pad,
  wave:saw, freq:110)

A folded block is still there:

The #$ line is a comment (#), so it never runs as code itself.

Blocks inside blocks

More $ is a bigger block: a block runs to the next head with as many $ or more, so a #$$ line holds every #$ block under it. Ten synths in their own #$ blocks are wrapped by one line above them:

#$$ synths                    ▸ 14 lines   (kick ... pad in it)
#$$ visuals
#$ shapes

opened:

#$$ synths
#$ kick                       ▸ 2 lines   (still folded)
#$ snare
synth(uid:sn, …)
…
#$ pad
#$$ visuals

Running a Line Again

A sound you run from the console belongs to the line that made it. The first time you run a sound cue without a uid, the console writes one into the line:

synth(freq:rand(100, 400))

becomes

synth(uid:s1, freq:rand(100, 400))

From then on, running the line again updates s1, here with a new random frequency, instead of starting another voice you could never reach. The name moves with the line through any edit, Ctrl+Z takes it out again, and you can rename it to something meaningful. Synths are named s1, s2…, audio a1…, audioPool p1… and audioImpulse i1…. A uid you write yourself is left alone. Use poly: when you want new voices to stack.

Patterns Come In on the Beat

A synth with a pattern, run from the console, starts on the next beat (quant:1), so lines run at any moment come in together and stay in step; quant:4 waits for the bar, quant:0 starts at once. A plain synth with no pattern starts at once. See synth: On the beat.

Building a Score Live

shape() draws a shape into the score where you are looking: sides:0 is a circle, 1 a line, 2 a lens, 3 a triangle and so on. Animation cues after it on the same line act on it, and its signals can drive sound:

shape(sides:3, size:0.15) rotate(dur:4, loop:0)
synth(uid:s2, freq:rand(100, 400), pan:follow(sh1.sin, -1, 1))

The console names the shape sh1 on the first run, as it names sounds. Run the line again to change the shape in place; comment it out and run it to take it away.

Changing how it looks

gui() reaches whatever a uid draws, a sound's box or a shape, by name:

gui(s3, size:1)                              // the scope fills the screen
gui(s3, size:[0.5, 0.3], at:[0.75, 0.2])     // half wide, near the top right
gui(s3, stroke:#ff2a6d, width:3, fill:#000)  // trace colour and width, background
gui(sh1, at:[0.7, 0.4], fill:#f60)

hush(s3) silences a sound the same way, by name, and an animation can take a sound's box as its target: s3 > rotate(dur:6, loop:0) turns the box with its scope in it.

In perform mode the keyboard belongs to the code: after a click on a score button, the next key you type goes into the code, not to the transport.

A fader with a life of its own

A made line is a path, so a dot can travel it with o2p(), and anything can follow the dot's t (0..1 along the line):

shape(sides:1, size:0.5, at:[0.5, 0.8])
shape(sides:0, size:0.04, fill:#f60) o2p(path:sh1, mode:alt, dur:1, loop:0)
synth(uid:s1, freq:220, amp:follow(sh2.t, 0, 0.1))

Running an o2p() line again from the console carries on from where the dot is, with the new settings, instead of starting over:

Suggestions

As you type, the best completion shows faintly after the cursor -- syn → synth(, wa inside a synth → wave:, sa after wave: → sawtooth, gui(or → organ, , follow(self.s → sin -- and Tab takes it. Ctrl+Space lists the others to pick from.

What can go where shows as a faint reminder at the end of the line -- to read, not to pick from:

organ > rotate(                dur · dir · ease · loop · pivot · trig
organ > rotate(dur:2,          dir · ease · loop · pivot · trig
synth(uid:b, wave:             sine · square · sawtooth · triangle · noise …
synth(uid:b, env               env · envPerStep
synth(freq:follow(             organ · me · sq · /pedal/1
synth(freq:follow(sq.          angle · cos · norm · rad · sin · tvel · vel · x · y

In follow( it shows what there is to follow: the names you have made, what is publishing signals right now, and the OSC addresses heard so far (type / and they complete). After a name and a dot, that name's own channels, as it publishes them now -- self. is the line's own shape; something not running yet shows the usual channels.

After a cue's bracket, and after each comma, it shows the parameters most lines use and that are not written yet; after a parameter with values of its own (wave:, view:, ease:, trig: …) its values; on a whole name that begins others too (env / envPerStep, audio / audioPool) all of them. Type a letter and it goes: the faint completion carries on a word at a time. An expression laid out over several lines is one expression: on its third line, the suggestions are still the synth's. With the cursor at a bracket, the bracket it pairs with is marked. It knows the cues, each cue's parameters (leaving out the ones already written), the choices of a parameter that has them (ease: on any animation: the easing names; inside choose(, Prand([ and the other patterns too: wave:choose(sq → square), the names you have made (written in the buffer or alive: sounds, shapes, texts) and the signals after a name and a dot.

Object parameters come whole: env: offers {a:0.02, d:0, s:1, r:0.1} (the defaults, to change), and filter:, delay:, reverb: likewise; inside the braces, the keys you have not written yet.

You need not know how a value begins. Type the colon and the choices are listed (view: → wave, polar, phase, xy, spectrum, ridge, waterfall; wave: → sine, square, sawtooth … kick, snare, hat; scale: → major, minor, dorian …), or type any part of one: view:fall lists waterfall, and Tab puts the whole word in place of what you typed. Those that begin with what you typed come first.

The Stash

Lines you mean to use in a set but not to show before you do. Write a line in the console (or select several), press Ctrl+Shift+S, and it is copied to the project's stash -- the console keeps its copy, so delete it if the screen should start blank. From then on the suggestions offer it back:

The stash is a plain text file in the project folder, to edit by hand as well: a phrase is a run of lines, a blank line ends it, and lines starting with # are notes. Lines selected together with blank lines between them are stashed as separate phrases, and a phrase already there is not written twice.

The global stash. Beside your projects, in the projects folder, one more vocab.live holds the lines common to every set: Ctrl+Alt+Shift+S copies to it. The suggestions offer the project's stashed lines first, then the global ones. Only the computer running Oscilla has one; no other laptop is sent it.

Whose stash. The computer running Oscilla keeps vocab.live. In a shared session every other laptop keeps its own, vocab-<name>.live beside it, named by what that laptop calls itself in the name dialog -- so set your name before you stash, and keep it (a new name starts a new, empty stash; the old file stays). The console's output says which file a line went to.

Who can see it. No stash is ever shown on the score or the projector until you type it, the server never hands a .live file out as a download, and stashes are left out of a project export. The host's vocab.live cannot be reached from any other laptop. A guest's stash is kept by name only: someone on the same network who knows your name, and knows how, could ask the server for it.

When Something Is Wrong

Every expression is checked before it runs, and nothing on the line plays if any part of it fails. The output says why, with a caret under the word it could not read:

expected ) before "freq"
synth(uid:a freq:440)
            ^

Problems found after a cue starts, such as an audio file that is not there, also appear in the output for a couple of seconds after you run something.


Signal Monitor

The bottom section shows all active ParamBus signals in real time. Each row displays a signal path and its current value, updated at 5 fps.

Use the filter field to narrow the display. For example, typing freq shows only signal paths containing "freq".

This is useful for verifying that control bindings are working -- you can see whether a fader's t value is changing, what frequency a synth is receiving, etc.


Standalone Mode Details

The standalone ?view=live window is a lightweight client. It connects to the same WebSocket server as the score window but does not load the score, initialise audio, or run the animation loop.

What happens when you execute a command:

  1. The console sends a livecode_exec message via WebSocket
  2. The server relays it to all other connected clients
  3. The score window receives it, resolves the target element, and calls handleCueTrigger

This means multiple performers can have their own live console windows open, all sending commands to the same score.


Live Rects: Audio Waveforms and Synth Scopes

When you trigger audio or synth cues from the console, there is no score element to anchor the visual display. The system creates ephemeral SVG rects in the viewport automatically. Audio cues get waveform contours with playback cursors. Synth cues get real-time oscilloscope traces. Both use the same rect system.

A synth's rect shows only its scope; add overlay:1 or overlay:2 to the synth to get the line of parameters across its top. Each unique uid gets its own rect. Rects stack vertically from the center of the viewport. They can be dragged by their body and resized by their edges. They persist until the voice stops or the global stop button clears them.

The waveform parameter controls where the display goes:

Value Behaviour
self (default) Voice gets its own rect
none No visual display
<uid> Display renders into another voice's rect

By varying uid, poly, and waveform, you can build anything from a single combined display to a multi-rect spatial layout.


Audio Examples

Single file with its own waveform

audio(src:drone.wav, loop:0, fade:2, uid:pad1)

A blue dashed rect appears with the waveform rendered inside and a cursor tracking playback.

Multiple independent layers

Each uid gets a separate rect. Use this to visually separate different sound roles:

audio(src:"noise/rain.wav", amp:0.6, loop:0, uid:rain)
audio(src:"pads/drone.wav", amp:0.3, speed:0.5, uid:drone)
audio(src:"perc/click.wav", amp:0.8, loop:0, uid:clicks)

Three separate rects, each with its own waveform and cursor. Drag them apart to organise the display.

Layering voices on a shared waveform

Use the same uid with poly:0 to stack multiple voices onto one rect. Each voice gets its own coloured peak layer and independent cursor:

audio(src:"pads/drone.wav", loop:0, speed:1, uid:mix, poly:0)
audio(src:"pads/drone.wav", loop:0, speed:0.5, uid:mix, poly:0)
audio(src:"pads/drone.wav", loop:0, speed:0.25, uid:mix, poly:0)

One rect, three cursors sweeping at different speeds, three peak contours in different colours from the 12-colour palette. Useful for building chordal textures from a single source.

Hot-updating a running voice

In mono mode (default poly:1), retriggering the same uid updates the running voice without restarting it. Execute these one at a time:

audio(src:drone.wav, loop:0, uid:myDrone)
audio(src:drone.wav, speed:0.5, uid:myDrone)
audio(src:drone.wav, amp:0.3, pan:-0.5, uid:myDrone)
audio(src:drone.wav, in:2, out:8, uid:myDrone)
audio(src:other.wav, uid:myDrone)
audio(src:drone.wav, loop:1, uid:myDrone)

Speed and gain change immediately via smooth ramps. In/out points and src changes take effect on the next loop iteration. Setting loop:1 finishes after the current iteration.

Reverse playback

Negative speed plays the file backwards. The waveform mirrors horizontally and the cursor sweeps right-to-left:

audio(src:texture.wav, speed:-1, loop:0, fade:2, uid:rev1)

Polyphony with voice cap

Cap overlapping voices. When the limit is reached, the oldest voice stops:

audio(src:grain.wav, loop:0, speed:rand(0.3, 2), uid:grains, poly:4)

Execute this repeatedly. Each trigger creates a new voice at a random speed. After four voices, the fifth stops the oldest.


AudioPool Examples

One-shot from a folder

Each execution selects and plays one file from the pool:

audioPool(path:"sfx/birds", mode:shuffle, pan:rand(-1, 1), uid:birds)

The waveform redraws on each trigger to show whichever file was selected. Execute repeatedly to walk through the pool.

Polyphonic pool

Allow multiple pool hits to overlap:

audioPool(path:"sfx/wood", mode:rand, amp:rand(0.3, 1), pan:rand(-0.8, 0.8), poly:5, uid:wood)

Execute rapidly to build up a dense layered texture with random pan positions.


AudioImpulse Examples

Start a texture process

Impulse runs autonomously once triggered -- no need to re-execute:

audioImpulse(
  path:"sfx/rain",
  rate:30,
  jitter:0.5,
  amp:rand(0.2, 0.6),
  pan:rand(-1, 1),
  speed:rand(0.8, 1.3),
  fadeout:"40%",
  poly:6,
  lifetime:process,
  uid:rain1
)

A rect appears with a base waveform. Coloured peak layers and cursors appear and disappear as hits fire and complete.

Update a running impulse

Retrigger with the same uid to change parameters on the fly:

audioImpulse(path:"sfx/rain", rate:60, jitter:0.8, poly:8, uid:rain1)

Rate and jitter update immediately. Subsequent hits use the new values.

Granular cloud

Push the rate high and the speed range wide for granular-style textures:

audioImpulse(
  path:"grains/voice",
  rate:200,
  jitter:0.9,
  amp:rand(0.05, 0.2),
  pan:rand(-1, 1),
  speed:rand(0.1, 4),
  fadein:0.01,
  fadeout:rand(0.01, 0.1),
  poly:12,
  lifetime:process,
  uid:voiceGrain
)

Synth Examples

Simple tone with scope

A live oscilloscope trace appears in an ephemeral rect:

synth(uid:tone, wave:sine, freq:440, amp:0.1)

Chord with envelope

Multiple oscillators rendered as a single scope:

synth(uid:pad, wave:sine, freq:[440, 550, 660], env:{a:2, r:3}, amp:0.12)

Hot-updating a synth

In mono mode (default), retriggering the same uid updates the running voice. Wave type, frequency, filter, amp, and pan update smoothly:

synth(uid:pad, wave:sine, freq:440, amp:0.1)
synth(uid:pad, wave:triangle, freq:330, amp:0.15)
synth(uid:pad, freq:[220, 330, 440], amp:0.12)
synth(uid:pad, filter:{type:lp, freq:800, q:4})

Random chords

rand() expressions evaluate fresh on each execution. Run the same line repeatedly to get different voicings:

synth(
  uid:cluster,
  wave:triangle,
  freq:[rand(200, 800), rand(300, 1000), rand(400, 1200)],
  env:{a:2, r:3},
  amp:0.1
)

Separate scopes for separate roles

Give each synth its own uid to get independent rects. Drag them apart to arrange by function:

synth(uid:bass, wave:saw, freq:55, amp:0.1)
synth(uid:mid, wave:triangle, freq:440, amp:0.08)
synth(uid:high, wave:sine, freq:2200, amp:0.05)

Three rects appear, each showing the waveform shape of its oscillator.

Stacking scopes on a shared display

Use waveform:uid to direct one synth's scope into another synth's rect. Both scopes render in the same element with distinct colours:

synth(uid:pad1, wave:sine, freq:[440, 550, 660], amp:0.12)
synth(uid:pad2, wave:square, freq:880, waveform:pad1, amp:0.08)

pad1 gets a rect with its scope. pad2's scope stacks into pad1's rect in a different colour. Useful for visually grouping related voices.

Poly overlapping voices

With poly:N, each execution creates a new overlapping voice. All poly voices share a single scope rect:

synth(
  uid:swell,
  wave:sine,
  freq:[rand(100, 800), rand(200, 1200)],
  env:{a:4, r:6},
  dur:8,
  amp:0.1,
  poly:12
)

Execute repeatedly. Each trigger creates a two-oscillator chord with random frequencies, fading in over 4 beats, auto-stopping at 8 beats with a 6-beat release tail. Up to 12 voices overlap. All scope traces stack in one rect with distinct colours. The 13th voice steals the oldest.

Poly cloud building

Combine rand(), dur, env, and poly to build evolving textures by repeated execution:

synth(
  uid:cloud,
  wave:triangle,
  freq:[rand(33, 333), rand(33, 533), rand(33, 1333)],
  env:{a:6, r:4},
  dur:12,
  amp:0.08,
  poly:20
)

Each execution adds a new three-oscillator chord with random frequencies. Voices fade in over 6 beats, sustain, then auto-stop at 12 beats with a 4-beat release. One rect holds all scopes.

Signal-bound synth

Bind synth parameters to controlXY faders or o2p animations. Move the fader to modulate the synth in real time:

synth(uid:ctrlDrone, wave:saw, freq: follow(fader1.t, 80, 800), amp: follow(fader1.y, 0, 0.2))

Check the signal monitor panel to verify values are flowing.


Mixing Audio and Synth

Shared display for different source types

Audio waveforms and synth scopes use the same live rect system. Direct a synth scope into an audio rect using waveform:uid:

audio(src:drone.wav, loop:0, amp:0.3, uid:layer1)
synth(uid:synPad, wave:sine, freq:[220, 330], waveform:layer1, amp:0.1)

The audio waveform and synth scope share the same rect. The synth scope renders on top in a distinct colour.

Separate rects for separate roles

For clarity during performance, keep different sound roles on separate rects:

audio(src:"pads/warm.wav", loop:0, fade:3, uid:backing)
audioImpulse(path:"sfx/rain", rate:20, poly:4, uid:texture)
synth(uid:lead, wave:saw, freq:440, filter:{type:lp, freq:1200}, amp:0.08)

Three rects, each with its own visual identity. Drag them to different screen positions to create a spatial performance layout.

Layered drone setup

Build a complex layered drone from the console, mixing file playback with synthesis:

audio(src:"pads/drone.wav", loop:0, speed:0.5, fade:4, uid:droneFile)
audio(src:"pads/drone.wav", loop:0, speed:0.25, fade:4, uid:droneFile, poly:0)
synth(uid:droneSyn, wave:saw, freq:55, filter:{type:lp, freq:400, q:2}, amp:0.06)
synth(uid:droneOvertone, wave:sine, freq:[110, 165, 220], waveform:droneSyn, amp:0.04)

The two audio voices share one rect (same uid with poly:0, two peak layers, two cursors). The two synths share another rect (droneOvertone's scope targets droneSyn). Two rects total, four sound sources, each pair visually grouped.


External Audio Search Path

By default, audio cues resolve files from the project's audio/ directory, falling back to the shared shared/audio/ directory. When working from a blank template or using samples stored elsewhere on the system, you can point the server at an external directory.

Set a path

audiopath(/home/rob/samples)
audiopath("/mnt/audio library/field recordings")

The server validates that the path exists and is a directory. On success, the output shows what's available at the root (subdirectories and audio file count). All audio resolution -- single file, pool, and impulse -- now falls back to this directory when files are not found in the project.

Query the current path

audiopath()
audiopath(status)

Reports the currently active path, or "not set".

Clear the path

audiopath(clear)

Removes the external path. Audio resolution reverts to project + shared only.

How it works

The server mounts the external directory at /ext-audio/. The fetch chain for all audio cues becomes: project directory, then external path, then shared directory. Pool and impulse file listings also scan the external path when the project directory is empty.

The path is per-server-session (not persisted across restarts). All connected clients benefit from the mount -- if one performer sets the path from a standalone live console, the score window's audio resolution picks it up immediately.

Example workflow

// Point at a sample library
audiopath(/home/rob/freesound-pack)

// Now use folders from that library
audioPool(path:rain, mode:shuffle, uid:rain1)
audioImpulse(path:birds, rate:20, jitter:0.6, poly:6, uid:birds1)
audio(src:"drone/low-hum.wav", loop:0, uid:hum)

// Done with external samples -- revert
audiopath(clear)

Spatial Control

Spatial sources (Spatialisation) are ordinary o2p and controlXY cues, so the console drives them like any other.

Move the hand-placed sources: controlXY presets

ui(action:'controlXYRecall', preset:'front_left')
ui(action:'controlXYRecall', preset:'overhead', dur:3, ease:'easeInOutSine')

A preset stores where each handle of a controlXY pad is; recalling it with dur: moves them there, and a spatial pad sends the positions in space all the way. Sequences of presets:

ui(action:'controlXYDefineSequence', name:'orbit_snap', steps:'front,right,rear,left,front')
ui(action:'controlXYSequence', seq:'orbit_snap', dur:2, ease:'easeInOutSine', loop:true)
ui(action:'controlXYSequenceStop')

Presets apply to controlXY handles, not to o2p sources.

Stop and restart a source on a path

window.runningAnimations.get("src1").pause()     // with drag:1 it can now be dragged
window.runningAnimations.get("src1").resume()    // carries on from the nearest point of its path

Change a source while it runs

Run its o2p again with new values — it carries on from where it is:

o2p(path:traj_spiral, uid:src1, dur:8, spatial:dome, bounds:dome_bounds, osc:1)

Watch it

The signal monitor shows each source's src1.azi, src1.elev and src1.dist (0..1), and a pad's mixer.src4.azi and so on.


Stopping

Stop one sound, or several

hush(uid:rain1)
hush(uid:[pad, rain1], rel:3)

hush(uid:X) silences whatever is called X: a synth (with all its poly voices), an audio file, an audioPool or an audioImpulse. rel: is the release in beats.

Stop one animation

off:1 stops an animation where it has got to: a turning box stays at its angle, a moving shape where it is. Run the line again to start it from there.

organ > rotate(off:1)
sh1 > trans(off:1)

It works for rotate, scale, trans, color and o2p. An o2p is taken down, not made a fader: for a fader, run it again with trig:touch. Other animations on the same element carry on.

Stop the line under the cursor

Ctrl+Shift+. takes back what the line under the cursor started, and leaves the line as it is:

The line What stops
synth(uid:s1, …), audio(uid:a1, …) the sound: hush(uid:[s1])
shape(uid:sh1, …) rotate(…) the shape goes, with the sounds and turns it carried
text(uid:sz, …) the text: text(uid:sz, stop:1)
organ > rotate(…) the turn: organ > rotate(off:1)

The line it runs instead shows in the output. Ctrl+Enter on the line starts it again.

Comment it out and run it

Put // in front of a block and run it with Ctrl+Enter: it is the same as Ctrl+Shift+. on it.

// synth(uid:s1, freq:rand(100, 400))
// organ > rotate(dur:1.6, loop:0)

Stop everything

Ctrl+., or:

hush()

This is the same as the stop button: every sound stops, and the scopes and waveforms go.

For the screen, Ctrl+,, or:

still()

Every animation stops where it has got to, every text() goes (and what stay:1 left), and the trig: lines that draw stop watching, so nothing new comes in on the beat. The sound plays on.

stop() is not for sound. It stops the transport; play(), rewind() and seek() work it too (see the transport cues).


Tip: use ← → or ↑ ↓ to navigate the docs