Manual
- Generators are entities that generate sound
- Listeners are entities that can listen to sound
- Transporters are entities that move around in the space an can transport other entities
- Comments are text boxes for notes, they make no sound
- Manipulators are entities that change a property of nearby generators ( pitch, radius, cutoff or volume ), or open the gate of nearby triggered generators ( trigger )
- Rooms are generators that contain a whole scene, only their sound reaches the outside ( see ROOMS )
KEY OPS
KEYs marked with CLICK are held down while clicking with the mouse, KEYs marked with MOVE are
held down while moving the mouse.
| KEY | ACTION | COMMENT |
|---|---|---|
| // CREATE *) | ||
l + NUMBER |
create LISTENER | select 1 MONO, 2 STEREO |
g + NUMBER |
create GENERATOR | select 1 OSCILLATOR, 2 SAMPLE ( SAMPLE selected in GLOBAL ), 3 ROOM, 4 SAM ( speech ) |
m + NUMBER |
create MANIPULATOR | select 1 PITCH, 2 RADIUS, 3 CUTOFF, 4 VOLUME, 5 TRIGGER |
t + NUMBER |
create TRANSPORTER | select 1 CIRCLE, 2 AB ( A at the mouse, B to the right ) |
n + CLICK |
create ( TRANSPORTER ) PATH | hold n, click adds point, release n to finish ( over a path handle: extend that path ) |
? |
create COMMENT | type text and RETURN to finish |
| // SELECT + EDIT | ||
SPACE |
toggle selection mode | mouse / keyboard, UP/DOWN cycle the selection |
d + MOVE |
DRAG selected entity | |
c + MOVE |
COPY selected entity | copy follows the mouse while c is held |
a + MOVE |
ATTACH selected entity ( to TRANSPORTER ) | attaches to the transporter it is released on |
SHIFT + a |
DETACH selected entity ( from TRANSPORTER ) | |
x |
DELETE the selected entity | |
SHIFT + x |
DELETE all entities | of the current room only |
| // PARAMETERS | ||
p |
enter/leave PARAMETER MODE | on comment box type text and RETURN to finish |
SHIFT + p |
leave entity PARAMETER MODE | |
SHIFT + g |
enter/leave GLOBAL PARAMETER MODE | NAME, SAMPLE, SCALE, ROOT, TUNE, SNAP, GRID, RETRIGGER, MASTER, REVERB_WET, REVERB_ROOM, REVERB_DAMP |
| // SOUND | ||
SHIFT + SPACE |
PLAY / pause | start / pause all transporters |
SHIFT + m |
MUTE selected entity or bypass the selected manipulator | |
SHIFT + t |
AUDITION selected TRIGGERED generator ( HOLD ) | gate opens on press, closes on release of any key ( rooms too ). restarts a selected transporter with LOOP off |
SHIFT + l |
ALIGN transporters | every transporter in the current room ( and rooms inside ) jumps to its START phase. sound and gates are untouched, phases drift apart again if speeds differ |
| // VIEW | ||
v + MOVE |
change VIEW | hold and move the mouse to pan, wheel to zoom |
SHIFT + v |
reset VIEW | |
| // ROOMS | ||
e |
ENTER the selected room | |
SHIFT + e |
LEAVE to the parent room | no-op at the root |
s |
toggle SOLO of the current room | no-op at the root |
| // APPLICATION | ||
ESC |
ABORT | cancel CREATE, finish comment editing, leave PARAMETER and SELECTION MODE |
META + i |
show/hide INFO | OSD section, top left |
META + SHIFT + i |
show/hide OPS | OSD section, list of key operations |
META + f |
toggle FULLSCREEN | |
META + n |
NEW PROJECT | empty, untitled project, discards the running scene |
META + s |
SAVE PROJECT | overwrites the project, a new untitled-* project if never saved |
META + SHIFT + s |
SAVE PROJECT AS NEW | always a new untitled-* project, a full copy of the current one |
META + o |
OPEN PROJECT | replaces the whole tree and the samples, jumps to the root |
META + r |
RELOAD SAMPLES | rereads library and project samples, the scene keeps playing |
META + q |
QUIT application |
*) press KEY to enter CREATE entity operation, then press NUMBER ( 1 – 9 key ) to create entity or press KEY again to cancel CREATE entity operation.
CREATE
l, g, m and t arm a CREATE operation, the OPS row in the INFO section then shows e.g
CREATE GENERATOR followed by one row per option with its number ( 1 OSCILLATOR, 2 SAMPLE ). pressing a NUMBER ( 1 – 9 )
creates that entity at the mouse and ends the operation. a number without an option is ignored.
pressing the same key again ( or ESC ) cancels, another CREATE key switches to its menu, and any other key
cancels and runs its own operation. SHIFT alone changes nothing.
PLAY
SHIFT + SPACE pauses and restarts all transporters. while paused every transporter holds its
position and resumes from there, audio keeps running so the scene can still be heard and edited.
the scene starts playing. PLAY is saved with the project.
MUTE
SHIFT + m toggles MUTE ( also the MUTE parameter ) of the selected entity, a handle mutes its transporter.
muted entities are drawn dimmed.
- a muted generator is silent
- a muted listener adds silence to the mix ( it fades out and back in )
- a muted transporter holds its position until it is unmuted, its passengers stay attached. a transporter riding a muted one still moves along its own path, around a carrier that stands still
- a muted ( bypassed ) manipulator has no influence
PARAMETER MODE
p shows the parameters of the selected entity as a list in the INFO section and makes them
changeable with the arrow keys. with nothing selected p tweaks GLOBAL. p on a handle of an AB or
path transporter tweaks the transporter itself. p while already in parameter mode switches to
another selected entity, and leaves parameter mode when the tweaked entity ( or nothing ) is selected.
SHIFT + p always leaves parameter mode, deleting the tweaked entity leaves it as well. SHIFT + g toggles GLOBAL,
from an entity’s parameter mode it switches to GLOBAL.
| KEY | ACTION |
|---|---|
UP/DOWN |
select the previous / next parameter, wrapping around |
LEFT/RIGHT |
change the selected parameter by the fine step |
SHIFT + LEFT/RIGHT |
coarse step, for oscillator FREQUENCY the previous / next note of the scale |
on/off parameters ( MUTE, LOOP, CLOSED ) toggle with LEFT/RIGHT, list parameters ( WAVEFORM,
SAMPLE, SCALE, ROOT ) step through their options and wrap around at both ends. the selected row
is remembered by name: tweaking FREQUENCY on one oscillator and pressing p on another selects
FREQUENCY again. values that a manipulator currently changes show base and effective value, e.g
220.00->264.00Hz.
| ENTITY | PARAMETERS |
|---|---|
| Generator Oscillator | FREQUENCY, WAVEFORM, PLAYBACK, ( ATTACK, DECAY, SUSTAIN, RELEASE ), VOLUME, CUTOFF, Q, RADIUS, CURVE, MUTE |
| Generator Sampler | SPEED, SAMPLE, PLAYBACK, IN, OUT, CROSSFADE, VOLUME, CUTOFF, Q, RADIUS, CURVE, MUTE |
| Generator Room | NAME, PLAYBACK, CLAMP, VOLUME, CUTOFF, Q, RADIUS, CURVE, MUTE |
| Generator SAM | TEXT, PLAYBACK, SPEED, PITCH, MOUTH, THROAT, SING, PHONEMES, IN, OUT, CROSSFADE, VOLUME, CUTOFF, Q, RADIUS, CURVE, MUTE |
| Listener | VOLUME, MUTE |
| Listener Stereo | ORIENTATION, VOLUME, MUTE |
| Manipulator ( all kinds ) | MAX_OFFSET, RADIUS, CURVE, MUTE ( == bypass ) |
| Manipulator Trigger | RADIUS, MUTE |
| Transporter Circle | SPEED, LOOP, START, ROTATION, RADIUS, MUTE |
| Transporter AB | SPEED, LOOP, START, MUTE |
| Transporter Path | SPEED, LOOP, CLOSED, START, MUTE |
| GLOBAL | NAME, SAMPLE, SCALE, ROOT, TUNE, SNAP, GRID, RETRIGGER, MASTER, REVERB_WET, REVERB_ROOM, REVERB_DAMP |
| PARAMETER | FINE | COARSE ( SHIFT ) |
RANGE |
|---|---|---|---|
| FREQUENCY | 1Hz | next / previous scale note | 0 … 20000Hz |
| WAVEFORM | 9 waveforms | ||
| SPEED ( sampler ) | 0.01 | 0.1 | negative plays backwards |
| SAMPLE | every loaded sample | ||
| SAMPLE ( global ) | sample g + 2 creates |
||
| PLAYBACK | CONTINUOUS, TRIGGERED | ||
| IN / OUT ( sampler ) | 0.001 | 0.01 | 0 … 1 of the sample, IN < OUT |
| CROSSFADE | 0.01s | 0.1s | 0 ( OFF ) … 2s, loop wrap blend |
| ATTACK, DECAY | 0.01s | 0.1s | 0 … 60s |
| SUSTAIN | 0.01 | 0.1 | 0 … 1 |
| RELEASE | 0.01s | 0.1s | 0 … 60s |
| VOLUME | 0.01 | 0.1 | 0 … 4 ( shown with dB ) |
| CUTOFF | ×2^(1/12) | ×2 ( one octave ) | 10 … 22000Hz |
| Q | 0.1 | 1.0 | 0.5 … 20 ( 0.707 == no peak ) |
| RADIUS | 1 | 10 | ≥ 1 |
| CURVE | 0.1 | 1 | −10 … 10 ( 0 == linear ) |
| ORIENTATION | 1° | 45° | 0 … 359° ( wraps, 0° faces up, clockwise ) |
| MAX_OFFSET | 1% | 10% | −100% … +300% |
| MAX_OFFSET ( cutoff ) | 0.1oct | 1oct | ±10oct |
| SPEED ( transporter ) | 0.01 | 0.1 | negative reverses the direction |
| START | 0.01 | 0.1 | 0 … 1, phase a rewind jumps to |
| ROTATION ( circle ) | 1° | 45° | 0 … 359° ( wraps, 0° up, clockwise ), where phase 0 lies |
| SCALE | 19 scales | ||
| ROOT | C, C#, … B | ||
| TUNE | 0.1Hz | 1Hz | 400 … 480Hz ( frequency of A4 ) |
| GRID | 1 | 10 | 1 … 1000 |
| MASTER | 0.01 | 0.1 | 0 … 4 ( shown with dB ) |
| REVERB_WET | 0.01 | 0.1 | 0 ( dry ) … 1, default 0.33 |
| REVERB_ROOM | 0.01 | 0.1 | 0 … 1 ( room size ), default 0.5 |
| REVERB_DAMP | 0.01 | 0.1 | 0 … 1 ( damping ), default 0.5 |
NAME ( rooms, GLOBAL ) and TEXT ( SAM ) are text: select the row, press RETURN, type, RETURN finishes ( BACKSPACE deletes the last
character, ESC finishes and leaves parameter mode ).
changing SAMPLE swaps the sample of the generator. a generator whose sample is missing shows
( <name> missing ) until another sample is selected.
IN and OUT select the section of the sample that plays ( 0 … 1 of its length, shown with the position in
seconds ). CONTINUOUS loops the section, a TRIGGERED one-shot plays it from IN to OUT ( OUT to IN at a
negative speed ). a section shorter than the whole sample gets a short ramp ( ~2ms ) at both ends.
CROSSFADE blends the end of a CONTINUOUS loop into the start so the wrap does not click. the head of the section ( CROSSFADE long, at most a quarter of the section ) plays once, then the rest loops, i.e the loop is shorter by the CROSSFADE length. set it to OFF for samples that already loop seamlessly to keep their exact length. TRIGGERED one-shots are not affected.
a SAM generator speaks its TEXT with SAM ( Software Automatic Mouth ) and then plays the speech like a sample:
PLAYBACK, IN, OUT, CROSSFADE, triggers and manipulators work as for a sampler ( CONTINUOUS repeats the speech,
TRIGGERED speaks it once per trigger ). the voice is set by the SAM parameters, 0 … 255, step 1 ( coarse 10 ):
SPEED ( default 72, lower is faster, at least 1 ), PITCH ( default 64, lower is higher ), MOUTH and THROAT
( default 128 ). SING holds the pitch steady ( no speech intonation, for singing ), PHONEMES reads TEXT as SAM phonemes ( e.g /HEH4LOW ) instead
of english text. every change speaks the TEXT again ( TEXT once its editing is finished ) and restarts the
generator. the TEXT is shown below the generator. a pitch manipulator changes the playback speed of the speech.
GLOBAL SCALE
SCALE, ROOT and TUNE define the scale SHIFT + LEFT/RIGHT steps through on an oscillator’s
FREQUENCY ( 12-TET, A4 == TUNE ). SHIFT + RIGHT jumps to the next scale note strictly above the current
frequency, SHIFT + LEFT to the next one strictly below, so an off-scale frequency snaps to the nearest
scale note in that direction. new oscillators start on a random note of the scale. changing the
scale never retunes existing oscillators, it only affects future steps.
scales: CHROMATIC, FIFTH, OCTAVE, MAJOR, MINOR, HARMONIC_MINOR, DORIAN, PHRYGIAN, LYDIAN, MIXOLYDIAN, MAJOR_PENTATONIC, MINOR_PENTATONIC, BLUES, WHOLE_TONE, DIMINISHED, MAJOR_CHORD, MINOR_CHORD, MAJOR_CHORD_7 ( default ), MINOR_CHORD_7.
SNAP TO GRID
with SNAP on ( the SNAP parameter in GLOBAL, SHIFT + g ) every entity that is created, dragged,
attached or copied lands on the nearest grid point, as does every point of a new path. the grid
spacing is GRID ( default 25 ). the background dots always show this grid, with or without snapping
( every 2nd, 4th, … point when zoomed far out ). picking still follows the mouse freely. entities that are already
placed are not moved when snapping is switched on.
PROXIMITY
generators and manipulators emit: they have a RADIUS ( how far they reach ) and a CURVE ( how their effect
falls off ). listeners and the generators a manipulator acts on receive: they are points, only their position
counts. a listener hears a generator once it is inside the generator’s radius, a manipulator influences a
generator once the generator’s position is inside the manipulator’s radius ( see DESIGN-proximity.md ).
the effect is 0 at the border and grows to 1 when both centers coincide. CURVE shapes the way in between:
0… linear ( default ), half the effect halfway in> 0… bends up: close to full soon after entering, a zone< 0… bends down: quiet until close to the center, a point
3 mirrors -3. a dotted circle marks where the effect is half: at half the radius for 0, towards the border
for a zone, towards the center for a point.
a listener has no radius. it is drawn as its icon only, with a line to every generator it hears ( the brighter, the louder ). its VOLUME is its sensitivity.
ENTITIES
every entity is grabbed within the same distance of its center ( twice the size of its icon ), however large its radius. selection and parameter mode outline the icon. comments are grabbed inside their box.
attaching ( a ) snaps to a transporter within four times that distance of a drop position ( the center of a
circle, any handle of an AB or path ), the closest one wins. a dotted line previews where it will connect.
an entity that rides a transporter cannot be dragged ( d ) or attached elsewhere ( a ), the OPS
row shows DRAG ( ATTACHED ) instead. detach it first ( SHIFT + a ). this applies to handles of AB and path
transporters and to transporters riding other transporters as well.
a stereo listener faces the direction it moves in ( averaged over the last few units travelled ) and
pans generators between its left and right ear. it keeps its direction while it stands still. dragged
with SNAP on it turns in 45° steps. ORIENTATION sets the direction in degrees ( 0° faces up,
clockwise ), until the listener moves again.
transporters can ride other transporters: drop a circle transporter ( or a handle of an AB or path transporter ) onto a circle or onto any handle of an AB or path transporter. a transporter never carries itself, its own handles, or a transporter that already carries it.
a path transporter travels a polygon of n points at constant velocity, one round per 1 / speed
seconds ( backwards for a negative speed ). the polygon is closed ( the last point joins back to the first ) and the first point is
marked with a circle. a path with fewer than 2 points is discarded on release.
pressing n while hovering a handle of an existing path extends that path: every click adds a point after
the hovered handle ( before it, if it is the first point of an open path ), release n to finish.
while a comment is edited every key goes into its text ( BACKSPACE deletes the last character ),
no other operation is triggered until RETURN is pressed. only printable ASCII characters are accepted.
text is hard wrapped every 40 characters and the box grows with it from its top left corner.
comments can ride transporters like any other entity.
a pitch manipulator shifts the pitch of every generator in its radius by up to max_offset
percent of the generator’s own value: +20% moves an oscillator from 220Hz to 264Hz, or a sampler
from speed 1.00x to 1.20x, at full influence. influence starts once the generator’s position enters the
manipulator’s circle and grows ( shaped by CURVE, see PROXIMITY ) until the manipulator sits on top of the generator. manipulators stack additively; the result is clamped per generator to 0.25 … 4.0
times its base value ( adjustable via project.json only, pitch_min and pitch_max ). generators
show the manipulated value next to their base value, e.g 220->264Hz. manipulation never changes
the base value: a generator glides back to it once the manipulator moves away or is bypassed. in
parameter mode ( p ) MAX_OFFSET changes by 1% ( SHIFT: 10% ).
a radius manipulator works the same way on the radius of generators, i.e how far listeners hear
them: +20% grows a generator of radius 50 to 60 at full influence. the manipulated radius is drawn
as the generator’s outline, its base radius as a faint circle. clamped to 0.25 … 4.0 times the base
radius ( radius_min and radius_max in project.json ). it only changes how far a generator is heard:
manipulators and triggers measure the generator’s position, so a radius manipulator neither pulls a
generator further in nor opens or closes gates.
every generator ( oscillator and sampler ) runs through a 2-pole low-pass filter, open by default ( cutoff 22kHz, clamped just below
nyquist ). a cutoff manipulator moves that cutoff, measured in octaves rather than percent:
-4.0oct takes 22kHz down to ~1.4kHz at full influence, and each octave sounds like an equal step, so
the sweep is audible over the whole approach. stacked manipulators add their octaves; the result is
clamped to ±10 octaves. in parameter mode MAX_OFFSET changes by 0.1 octaves ( SHIFT: 1 octave ). a
generator shows its cutoff as LPF 22000->1375Hz while manipulated. the base cutoff and the filter’s
Q ( 0.707 == no resonance ) are the CUTOFF and Q parameters of every generator.
a volume manipulator raises or lowers the output volume of generators in percent, like the pitch
manipulator: -50% ( the default ) halves the volume at full influence, -100% silences it. clamped
to 0 … 4 times the base volume ( volume_min and volume_max in project.json ), so it can push a
generator above 1.0. a generator shows its volume as VOL 1.00->0.50 while manipulated and
VOL 0.50 while its base volume is not 1.0.
TRIGGERS
every generator plays in one of two PLAYBACK modes. CONTINUOUS ( the default ) sounds endlessly: the
sampler loops, the oscillator runs. TRIGGERED stays silent until a gate opens:
- a sampler plays its sample once from the start ( from the end at a negative SPEED ). closing the gate does nothing, the sample always plays to its end. retriggering while it plays fades out for ~5ms and starts again
- an oscillator runs through an ADSR envelope: ATTACK, DECAY, SUSTAIN and RELEASE ( defaults
0.01s,0.2s,0.6,0.5s). the envelope rows are only shown while PLAYBACK is TRIGGERED, but kept when it changes. a retrigger attacks from the current level, so it does not click
a trigger manipulator ( m + 5 ) opens the gate of every TRIGGERED generator whose position enters
its circle ( the generator’s radius is not involved ). the gate closes when the last trigger leaves: the time a
generator spends inside a trigger is the note length. a trigger has no CURVE, its gate is open or closed. a muted trigger counts as gone. a trigger draws a line to every generator
it holds open and flashes when it fires. CONTINUOUS generators ignore triggers.
RETRIGGER ( GLOBAL, default on ) decides what a second trigger entering an already open gate does: ON restarts envelope / sample, OFF does nothing ( the gate is open as long as any trigger is inside ).
switching a generator to TRIGGERED fades it out, a trigger that already overlaps it fires right away. switching back to CONTINUOUS sounds at once, a sampler loops from the start.
SHIFT + t held on a selected TRIGGERED generator opens its gate until a key is released ( releasing
SHIFT first closes it as well ).
transporters with LOOP off travel one run and then wait for a trigger: a circle or a closed path travels
one full lap and stops at START, an AB or an open path stops at the end it reached. a trigger manipulator
whose circle one of the transporter’s positions enters ( the moving point, or any handle of an AB or path ), SHIFT + t on the selected
transporter, or a triggered room opening around it restarts it from START. transporters with LOOP on ignore
triggers.
every generator and every listener has a VOLUME parameter, 0 … 4 ( default 1, values above 1 boost ), shown with its value in dB. a
listener’s volume scales everything it hears, before it is mixed with the other listeners. MASTER ( GLOBAL,
0 … 4, default 1 ) scales the mix of all listeners before the reverb, values above 1 boost it. the
output is still clamped to -1 … 1, so a boost that is too loud clips.
ROOMS
a room ( g + 3 ) is a generator that contains a whole scene. entities are placed inside it, the
room itself sits in its parent like any other generator. only its sound reaches the parent: manipulators,
transporters and triggers inside a room act inside only. rooms can contain rooms. the top-level scene is the
root.
- a new room is empty and silent until a listener is placed inside it
- its output is the sum of the listeners inside, folded to mono ( a stereo listener adds
left + right). no MASTER and no reverb inside, both are applied once at the root. the output is not clamped ( CLAMP on clamps it to-1 … 1), it runs through the low-pass filter and VOLUME like every other generator. a room whose output exceeds1.0draws its level ring red and showsPEAK >0dB( only while CLAMP is off ), trim it with its VOLUME - radius, cutoff, volume and trigger manipulators of the parent apply, pitch manipulators have no effect
- MUTE silences the output, the inside keeps running
- RADIUS is the audible radius in the parent, unrelated to the size of the inside, CURVE its falloff
- NAME is shown below the disc and in the
ROOMrow of the INFO section (ROOT > drums > hats). an empty NAME showsROOM[id]
PLAYBACK CONTINUOUS ( default ) runs the room whenever PLAY is on. TRIGGERED stops it until a gate
opens: the whole inside rewinds ( every transporter, also inside rooms in it, jumps to its START phase, gates and
envelopes close ) and runs. closing the gate pauses every transporter inside and fades the output out ( ~5ms ).
entities that ride nothing are not moved by a rewind. PLAY stays one global flag: a room runs only if PLAY is on and
no room above it is a stopped triggered room.
e enters the selected room, SHIFT + e leaves to the parent. inside a room the parent is not drawn, one frame
around the view per nesting level shows the depth. every room keeps its own view ( pan, zoom ). entering and leaving
reset parameter mode, CREATE operations and the selection. every operation ( picking, drag, copy, attach, create,
delete ) acts on the current room only, attaching never crosses a room boundary. x on a room deletes it with
everything inside, c copies it with everything inside ( the copy is independent ). GLOBAL settings are app-wide.
what is heard is always the root mix, entering a room never changes it. s toggles SOLO: the output is then the mix
of the current room ( through MASTER and reverb ). SOLO switches off when the room is entered or left, the ROOM row
shows SOLO while it is on. rooms always run, whether entered or not.
RESOURCE FOLDER
the resource folder, by default $HOME/Documents/back-rooms, holds the sample library and the projects:
~/Documents/back-rooms/
├── samples/ library, `.wav` + `.mp3`
└── projects/
├── untitled-2026-10-08_14-03-11.back-rooms/
└── my-project.back-rooms/
├── project.json the scene
└── samples/ project samples
samples/ and projects/ are created on startup if they do not exist.
command line options, in any order:
| OPTION | ACTION |
|---|---|
--help, -h |
print all options and exit |
--fullscreen |
start in fullscreen mode |
--window-size=WxH |
window size in pixels ( default 1920x1080 ) |
--resource-path <folder> |
use <folder> as resource folder instead of the default |
--project <path> |
open a project on startup ( its folder or its project.json ) |
--profile-frames |
print frame pacing stats to the console |
--swap-stereo |
swap left and right output channels |
--theme <name> |
colors of scene and icons from data/themes/<name>.json |
--background-color=#RRGGBB |
scene background color, overrides the theme |
--foreground-color=#RRGGBB |
scene foreground color, overrides the theme |
--second-window |
open a second window that mirrors the scene ( without OSD ) |
--second-window-size=WxH |
size of the second window ( default 960x540 ) |
--second-window-fullscreen [display] |
second window fullscreen on display ( default 1 ) |
./back-rooms --fullscreen --resource-path ~/Documents/my-back-rooms --project examples/dark-patterns.back-rooms
PROJECTS
the running scene belongs to a project, a folder <name>.back-rooms holding the scene ( project.json ) and the
samples it plays ( samples/ ). the project name is the folder name without .back-rooms, it is shown in the window
title and as PROJECT in INFO. a project is untitled until it is saved, a saved project whose name starts with
untitled- ( a sketch ) stays untitled. a project may live anywhere, e.g the examples in examples/ of the repository.
samples
the samples are the library ∪ the samples/ of the project ( an untitled, never saved project has the library
only ). both are scanned flat ( subfolders are ignored ), the file name without extension is the sample name. a
project sample shadows a library sample of the same name. of two files with the same name in one folder ( kick.wav
and kick.mp3 ) the first in sorted order wins ( .mp3 ), with a warning on the console.
saving copies every sample a sampler generator ( in any room ) plays from the library into the project’s
samples/, as is, so the project plays on another machine. a file already in the project is never overwritten and
nothing in samples/ is ever deleted, files can be put there by hand. SAM generators and the global SAMPLE are not
copied.
META + r RELOAD SAMPLES rereads every file of the library and of the project while the scene keeps playing,
e.g after a sample was edited elsewhere or added to samples/. every sampler generator restarts with its reloaded
sample. a generator whose sample is gone falls silent and keeps its name, it plays again after a reload once the
file is back. the console lists + <name> added, - <name> removed and ~ <name> reloaded samples.
save, open, new
NAME in GLOBAL holds the project name ( empty while untitled ) and decides where the next META + s writes to.
editing it changes nothing on disk.
META+sSAVE PROJECT:- untitled and never saved: a new project
projects/untitled-YYYY-MM-DD_HH-MM-SS.back-rooms( local time ) - NAME unchanged: overwrites
project.jsonof the project - NAME changed on an untitled project: the project is renamed to
<NAME>.back-rooms( next to it, inprojects/if never saved ) and saved, nountitled-*folder is left behind - NAME changed on a named project: save as, the folder is copied to
<NAME>.back-roomsnext to it and saved there, the old project stays /,\and:in NAME become-. if NAME is taken by another folder or the write fails ( e.g a read-only folder ), the scene is saved as a new project instead ( asMETA+SHIFT+s), nothing is overwritten
- untitled and never saved: a new project
META+SHIFT+sSAVE PROJECT AS NEW: always a newprojects/untitled-<timestamp>.back-roomsas a full copy of the current project folder, e.g to keep a snapshot. the new project is the current oneMETA+oOPEN PROJECT: pick a.back-roomsfolder ( or the folder holding aproject.json). opening is all-or-nothing: cancelling, picking something that is not a project or a failed load changes nothingMETA+nNEW PROJECT: an empty, untitled project with default global settings and the library only, as if the app was freshly started
there is no unsaved-changes tracking: META + n, META + o and META + q discard the running scene immediately.
the app always starts untitled ( unless --project is given ). the full path of every save, open, rename, copy and
reload is printed on the console.
project.json
what is stored:
- position, radius, id and pickability of every entity ( listeners: no radius, a
"radius"in an older file is ignored ) - global: sample, scale, root, tune, snap, grid, retrigger, master and reverb (
"global": { "sample": "kick", "scale": "MAJOR_CHORD_7", "root": "C", "tune": 440.0, "snap": false, "grid": 25.0, "retrigger": true, "master": 1.0, "reverb_wet": 0.33, "reverb_room": 0.5, "reverb_damp": 0.5 }), missing keys fall back to the defaults, so older files still load. an unknown sample falls back to the first loaded sample, but its name is kept and written back on the next save - view: pan, zoom and PLAY (
"view": { "pan": { "x": 0.0, "y": 0.0 }, "scale": 1.0, "play": true }), missing == centered, unzoomed and playing - generators: mute, volume, cutoff and
filter_q,curve( missing ==0),playback("CONTINUOUS"or"TRIGGERED", missing ==CONTINUOUS), oscillator frequency, waveform and envelope (attack,decay,sustain,release), sampler name, speed,inandout(0 … 1, missing == whole sample ), SAMtext,voice_speed,voice_pitch,mouth,throat,singandphonemes, pitch, radius, cutoff and volume clamp (pitch_min,pitch_max,radius_min,radius_max,cutoff_min,cutoff_max,volume_min,volume_max) - listeners: volume — always the base values, never the manipulated ones — and mute, for
listener_stereothe orientation - manipulators:
max_offset( as fraction,0.2 == +20%, unused bymanipulator_trigger), mute,curve( missing ==0, not onmanipulator_trigger) - comments: text
-
transporters: speed, mute,
phase(0 … 1, how far along its path, so transporters keep their timing relative to each other ),start(0 … 1, missing ==0),loop( missing ==true), the entities they carry, fortransporter_abboth end points, and fortransporter_pathevery point plus the closed flag - rooms (
generator_room): like a generator plusname,clamp( missing ==false) androom, which holds the contents in the same shape as the root file minusglobalandplay:"room": { "view": { "pan": { "x": 0.0, "y": 0.0 }, "scale": 1.0 }, "generators": [], "listeners": [], "transporters": [], "comments": [], "manipulators": [] }. rooms nest inline. ids are unique across all rooms. state version4, older files load without migration ( version 3 files sound different, the listener model changed, seeDESIGN-proximity.md)
what is not stored: app settings ( OSD, fullscreen, theme, colors, window size ) and runtime state that rebuilds itself ( smoothing, open gates, envelopes ).
entity ids are stable across a save and a load, which is what lets the file describe which entity
rides which transporter. a sample that cannot be found is reported on the console; its generator is
still loaded, keeps its name and stays silent, so putting the file back and META + r restores it.
opening is all-or-nothing: if project.json is missing or unreadable, the running scene is left
untouched.