cube
cube · sources3D wavetable-navigator oscillator. Builds a 3D scalar field — "the cube," a space that is a mix of solid and filled — out of THREE e352 wavetables (FLOOR / WALL / CEILING, each independently chosen from WAVESCULPT's full preset set, defaults FLOOR=basic-shapes, WALL=pwm-sweep, CEILING=harmonic-sweep — ALL THREE must differ or one control dies by arithmetic: FLOOR=CEILING makes MORPH algebraically inert, and either connector slot matching WALL sends the occupancy function into its degenerate hard-step branch and kills CONNECT and CONNECT STRENGTH outright. The faceplate's table-stack panel draws the three heightfields and names the casualty when it happens), then flies an arbitrary planar SLICE through the field and reads it back as the played waveform via a SURFACE-HEIGHT SCAN: for each of 256 x-positions the sample = how far the solid extends along the slice (intersection depth), so the cube's shape literally becomes the wave. MORPH connects the wall to the floor (at min) or the ceiling (at max), weighted-averaged in between; CONNECT morphs the connecting curve from a circle arc to a sawtooth-V touching the floor, and CONNECT STRENGTH overshoots the connector's interior control point "out of the cube" for a dramatic swell of the solid base (0 = today's exact shape). The slice navigates with Y (up/down) + Rot X/Y/Z (Euler rotation on all 3 axes). MATERIAL switches SMOOTH (continuous density) ↔ HARD (binary solid in/out). CRUSH is a 3D bitcrush reducing spatial-grid + amplitude resolution (default 0 = transparent → blocky/steppy at max); SPACE CRUSH is an independent spatial voxelization of just the FIELD lookup coordinates (chunky voxels, 0 = transparent), and SPACE DIFFUSE applies a gravity that pulls the sampled cloud toward the cube's lowest-information (emptiest) wall — the target wall latches when the tables/morph change, not as the knob moves (0 = off). WRAP toggles whether a slice extending outside the cube is silent (default) or mirror-folds back to the opposite side. A V/oct pitched oscillator (pitch input + tune/fine) with stereo L/R SPREAD on SEPARATE L and R output ports (max ±18% of the cube depth between channels — the L slice is read below center, the R above, and the separate ports mean the offset survives patching into mono inputs; measured, the resulting image is NARROW, about -36 dB of side against mid at the stop with the two channels 0.99998 correlated, so treat SPREAD as a widener rather than a stereo field). View-only Zoom + Rot X/Y orbit the WebGL2 3D cube visualization (dragging the faceplate hero writes the same two angles) (the scalar field rendered as a translucent voxel slice-stack with the live selection slice shown as a square plane cutting through it) without affecting the sound or the selected slice. CV inputs cover the expressive params (slice Y/rotation, morph, connect, connect strength, crush, space crush, space diffuse, fold, tune). A POLY input (polyPitchGate) accepts the 5-voice chord bus from MIDI LANE (mode=poly) or POLYSEQZ: when any lane is gated CUBE runs one phase accumulator per gated lane through the SAME posted slice waves at that lane's pitch and sums them — polyphonic, with the slice/field timbre shared across voices; with nothing patched to poly the mono `pitch` path runs unchanged. A per-voice amplitude ADSR (Attack / Decay / Sustain / Release) plus a BASE VOL knob shape each voice. GATING is decided by what is PATCHED: when the POLY bus OR the mono TRIGGER is connected, CUBE is a GATED voice — a lane/voice sounds only while it is gated-or-releasing, and a never-gated lane is SILENT (patching poly never auto-drones). When NEITHER is patched, CUBE is a continuous raw VCO. BASE VOL is a per-voice VCA FLOOR the envelope rides on top of: gain = base + (1-base)·env per ACTIVE voice — base=0 (the default) is pure ADSR (silent between notes), 0.5 floors at 0.5 and rises to 1.0 at the env peak, base=1 means the env does nothing (full gain while active). It applies ONLY to gated voices: the raw-VCO case (nothing patched) runs at unity independently of BASE, so the legacy continuous drone is byte-identical at every setting. In poly mode each lane's gate edge drives its own envelope (one per voice, soft/click-safe retrigger), and the mix is normalized over ACTIVE voices (1/sqrt(N)) so a sustain=0 held note doesn't pump and a releasing tail doesn't pop. The ADSR + BASE VOL params read live (continuous k-rate) across all stages. Edge detection is block-rate (retrigger granularity floor ≈ one audio block); connectedness (poly/trigger patched) is read from the live patch edges, not bus presence. Pure deterministic field/slice DSP in cube-dsp.ts is reused verbatim by node-ART AND by the card's 3D render, and the surface-height scan runs OFF the audio thread (computed on the main thread, posted to the worklet) so sweeping params never drops the audio out. A VIDEO OUT port carries a live render of that same 3D view to any video module. SYNC is a pure sine off the playback phase that is NOT scaled by LEVEL and runs about 6 dB hotter than the L/R pair, so mixing it alongside them is a level surprise. Only PITCH is audio-rate: every other CV jack is summed into an AudioParam the worklet samples once per block (~375 Hz), quantised to 1/512 of range and 80 Hz-smoothed, so an audio-rate source into one of them aliases rather than modulates.
the faceplate
inputs
| id | cable | what it does |
|---|---|---|
pitch | cv | Mono V/oct pitch control voltage — the standard 1V-per-octave oscillator pitch input, read directly by the worklet, and the ONLY audio-rate input on the module: every other CV jack here is summed into an AudioParam the worklet samples once per block (~375 Hz), quantised to 1/512 of the param range and smoothed at an 80 Hz corner, so an audio-rate source into one of them aliases rather than modulates. This is the fallback voice when nothing is patched into POLY; summed with the TUNE and FINE offsets to set the playback fundamental. control voltage (CV) |
poly | polyPitchGate | Polyphonic chord bus (the 10-channel pitch+gate cable from MIDI LANE in poly mode or POLYSEQZ). Each gated lane renders its own phase accumulator through the same sliced waveform at that lane's pitch and they sum — so CUBE plays a whole chord. While any lane is gated this bus drives the voices; with nothing patched here the mono PITCH path runs instead (back-compat). poly pitch+gate bus; 10-channel poly bus (5 pitch + 5 gate lanes) |
trigger | gate | Mono gate for the per-voice amplitude envelope: while the level is high the ADSR holds open (attack→decay→sustain) and on the falling edge it releases. PATCHING it turns CUBE into a gated voice shaped by the lane-0 envelope: it is silent until the first gate and then follows every one. When this input (and POLY) is unpatched the amplitude env has nothing to shape and CUBE free-runs as a continuous drone. gate / trigger; gate — acts while the level is high (reacts to both edges) |
slice_y | cv | CV that offsets the Y param — raises or lowers the slicing plane's height through the cube, scanning it across the floor→ceiling stack. control voltage (CV); modulates slice_y (additive offset — ±1 CV sweeps the full range, centered on the knob) |
slice_rx | cv | CV that offsets the Rot X param — tilts the slicing plane about the X axis (±π), changing the surface contour it reads. control voltage (CV); modulates slice_rx (additive offset — ±1 CV sweeps the full range, centered on the knob) |
slice_ry | cv | CV that offsets the Rot Y param — tilts the slicing plane about the Y axis (±π). control voltage (CV); modulates slice_ry (additive offset — ±1 CV sweeps the full range, centered on the knob) |
slice_rz | cv | CV that offsets the Rot Z param — rotates the slicing plane about the Z axis (±π). control voltage (CV); modulates slice_rz (additive offset — ±1 CV sweeps the full range, centered on the knob) |
morph_fc | cv | CV that offsets the Morph param, cross-fading the floor↔ceiling wavetable layers of the field. control voltage (CV); modulates morph_fc (additive offset — ±1 CV sweeps the full range, centered on the knob) |
connect | cv | CV that offsets the Connect param, blending the floor and ceiling into a connected interior shape. control voltage (CV); modulates connect (additive offset — ±1 CV sweeps the full range, centered on the knob) |
connect_strength | cv | CV that offsets the Connect Strength param, pushing the connector's interior control point further out for a more dramatic swell. control voltage (CV); modulates connect_strength (additive offset — ±1 CV sweeps the full range, centered on the knob) |
crush | cv | CV that offsets the Crush param, driving the bit/sample reduction applied to the slice's read-out waveform. control voltage (CV); modulates crush (additive offset — ±1 CV sweeps the full range, centered on the knob) |
space_crush | cv | CV that offsets the Space Crush param, quantizing (voxelizing) the 3D field-lookup coordinates into chunky blocks. control voltage (CV); modulates space_crush (additive offset — ±1 CV sweeps the full range, centered on the knob) |
space_diffuse | cv | CV that offsets the Space Diffuse param, warping the lookup coordinates toward the cube's emptiest wall. control voltage (CV); modulates space_diffuse (additive offset — ±1 CV sweeps the full range, centered on the knob) |
fold_cv | cv | CV that offsets the Fold param, modulating the output wavefolder depth (added harmonics). control voltage (CV); modulates fold (additive offset — ±1 CV sweeps the full range, centered on the knob) |
tune | cv | CV that offsets the Tune param, shifting pitch in semitones around the base note (summed with the PITCH input and the FINE knob). control voltage (CV); modulates tune (additive offset — ±1 CV sweeps the full range, centered on the knob) |
outputs
| id | cable | what it does |
|---|---|---|
L | audio | Left audio channel of the sliced oscillator, including the −5% SPREAD plane offset, post-FOLD and post-LEVEL. Split out as its own mono port so the stereo width survives even when patched into a mono input. audio signal |
R | audio | Right audio channel, including the +5% SPREAD plane offset (the partner of L). Together L and R carry the spread stereo image; pan/mix them to keep the width. audio signal |
sync | audio | A pure SINE at the playback fundamental, phase-locked to the L/R slice read-out (it reads off the same phase accumulator). Use it to hard-sync another oscillator to CUBE, or as a clean reference / sub-oscillator tone. ⚠ It is NOT scaled by LEVEL and it is HOTTER than the audio: SYNC leaves at -3.01 dBFS against the L/R pair's -9.14 dBFS at the default knobs, so mixing SYNC alongside L and R is a +6.1 dB step, and turning LEVEL down does not touch it. audio signal |
video_out | mono-video | A mono-video output carrying a live render of the 3D cube view — the translucent field volume, the cut plane positioned by Y + the rotation knobs, and the output waveform. Patch it into VIDEOOUT or any video module; it keeps emitting frames even when the on-card SCREEN is switched off. mono video stream |
params
| id | label | range | default | curve |
|---|---|---|---|---|
tune | Tune | -36..36st | 0 | linear |
fine | Fine | -100..100¢ | 0 | linear |
morph_fc | Morph | 0..1 | 0 | linear |
connect | Connect | 0..1 | 0 | linear |
connect_strength | Cnct Str | 0..1 | 0 | linear |
crush | Crush | 0..1 | 0 | linear |
space_crush | Space Crush | 0..1 | 0 | linear |
space_diffuse | Space Diffuse | 0..1 | 0 | linear |
fold | Fold | 0..1 | 0 | linear |
spread | Spread | 0..1 | 0 | linear |
slice_y | Y | 0..1 | 0.5 | linear |
slice_rx | Rot X | -3.1416..3.1416 | 0 | linear |
slice_ry | Rot Y | -3.1416..3.1416 | 0 | linear |
slice_rz | Rot Z | -3.1416..3.1416 | 0 | linear |
level | Level | 0..2 | 1 | linear |
attack | A | 0.001..5s | 0.001 | log |
decay | D | 0.001..5s | 0.1 | log |
sustain | S | 0..1 | 1 | linear |
release | R | 0.001..5s | 0.005 | log |
base_vol | Base | 0..1 | 0 | linear |
wrap | Wrap | 0..1 | 0 | discrete |
material | Material | 0..1 | 0 | discrete |
view_zoom | Zoom | 0.3..3 | 1 | log |
view_rot_x | View X | -3.1416..3.1416 | 0.6 | linear |
view_rot_y | View Y | -3.1416..3.1416 | 0.7 | linear |
screen_on | Screen | 0..1 | 1 | discrete |
controls
| control | what it does |
|---|---|
| A | Per-voice amplitude envelope ATTACK time (0.001..5 s, log) — how long each note takes to rise to full from note-on. Drives both the poly lane envelopes and the mono TRIG voice. |
| Base | Per-voice VCA floor the ADSR rides on top of (gain = base + (1−base)·env): at 0 (default) the envelope has full control of every note and the voice is silent between notes; 0.5 floors each voice at half and the env swells it up to full on note-on; at 1 gain is always full while a voice is active, so the envelope does nothing. It only applies to GATED voices — with nothing patched into POLY or TRIG, CUBE free-runs at full level whatever this is set to. |
| Connect | Blends the floor and ceiling layers into a single connected interior shape (0 = today's separate shape, 1 = fully connected); CV via the CONNECT input. |
| Cnct Str | Overshoots the connector's interior control point 'out of the cube' for a dramatic base swell (0 = the exact CONNECT shape, max = pushed furthest out). Works alongside CONNECT. |
| Crush | Bit/sample reduction applied to the slice's read-out waveform (0 = clean, max = heavily crushed) for digital grit. |
| Cube table stack {n} | All three wavetable slots in one panel: FLOOR, WALL and CEILING drawn as heightfield strips in stack order, each with its roster of factory tables, the baked-preset list and a .WAV loader. It replaces nine controls on the legacy card that expressed three decisions — the per-slot preset dropdown there is a workaround for a Svelte binding that will not re-fire on an unchanged value, not a second choice. The panel also names the casualty of a slot COINCIDENCE, which is the one way this module can still be silenced by arithmetic rather than by a knob: the field is a morph between the floor-to-wall and ceiling-to-wall occupancies, so FLOOR = CEILING makes MORPH inert, and either connector slot matching WALL sends the occupancy function into its degenerate hard-step branch and kills CONNECT and CONNECT STRENGTH outright. The shipped defaults are three distinct tables and nothing is dead; you are one pick away from that not being true, so the panel says so while it is happening rather than afterwards. |
| Cube view {n} | The faceplate's hero picture, and the SAME renderer the card uses rather than a reduction of it. Three surfaces: the rotatable WebGL2 VOLUME (the density field as a stack of alpha-blended Z-slices, the cube wireframe, and the live slicing plane sitting exactly where Y and ROT X/Y/Z put it), the 2-D SLICE cross-section (the field the plane actually cuts, as a heatmap across all three tables), and the OUTPUT waveform (the 256 samples the scan read, which ARE one cycle of the sound). Drag the volume to orbit the camera — it writes the same VIEW X / VIEW Y params the knobs in the VIEW band write, and the printed angle beside it is how you can tell a live drag from a dead one. Two caption numbers exist because no knob on the module shows them: how much SOLID the plane cut through, and the wave's DC against its audio — CUBE's L and R carry more DC than signal at the defaults (|DC| is 1.06x the AC RMS), and WRAP is the only control that re-centres it. The third, the count of distinct sample values, is there because RMS is the wrong instrument for MATERIAL: SMOOTH to HARD moves RMS by 0.040, last of thirteen, while the distinct-value count falls 180 to 28. |
| D | Per-voice envelope DECAY time (0.001..5 s, log) — how long the level falls from the attack peak down to the SUSTAIN level. |
| Fine | Fine pitch trim in cents (−100..+100) for tuning between the semitone steps of TUNE. |
| Fold | West-coast wavefolder on the output (0 = pass-through, max = hard fold), applied after the slice is sampled and before LEVEL on both L and R, adding harmonics; CV via the FOLD input. |
| Level | Output gain on the sliced audio (0..2, applied after FOLD); 1 = unity, above 1 boosts. |
| Material | Field density model: SMOOTH (0) = continuous density gradients, HARD (1) = a binary solid (sharp inside/outside), which makes the sliced waveform edgier. |
| Morph | Cross-fades the FLOOR↔CEILING wavetable layers of the 3D field (0 = floor, 1 = ceiling), reshaping the terrain the plane slices through. |
| R | Per-voice envelope RELEASE time (0.001..5 s, log) — how long the level fades to silence after the note's gate falls. |
| Screen | Turns the on-card 3D viz screen on/off. When OFF and the VIDEO output is unpatched, the card skips all visual computation (the render loop and the field/slice/wave draws) to save GPU — audio keeps running untouched. A patched VIDEO output still receives live frames even with the screen off. |
| Rot X | Rotation of the slicing plane about the X axis (±π radians), tilting which surface contour it reads. CV via the ROT X input. ⚠ HALF THIS TRAVEL IS A DUPLICATE: at SPREAD 0 the waveform is bit-exactly π-periodic in this knob (rmsΔ between a and a−π is 0.000e+0 at every angle measured), because the scan offset is built from a vector with y = z = 0 and the X rotation is applied first, so ROT X only ever flips the plane normal — and the ray march window is symmetric about the origin. It is not EVEN, though: f(a) ≠ f(−a). Above SPREAD 0 the ±depth offset rides the normal, so a and a+π swap L and R instead of being identical. |
| Rot Y | Rotation of the slicing plane about the Y axis (±π radians). CV via the ROT Y input. |
| Rot Z | Rotation of the slicing plane about the Z axis (±π radians). CV via the ROT Z input. ⚠ IT DOES NOT TILT THE PLANE — it spins the SCAN LINE inside it. A Z rotation cannot move a vector lying on the Z axis, and the plane's normal at rest IS the Z axis, so this knob re-aims which line across the cut is read out while leaving the cut itself exactly where it was. That is why it is the module's second strongest control (0.877 rmsΔ over its travel) and yet leaves Y asleep: Y is still sliding along the plane's own normal. ROT X or ROT Y first, then Y. |
| Y | Height of the slicing plane through the cube (0..1) — scans the cut from the floor up to the ceiling. CV via the Y input. ⚠ ITS AUTHORITY DEPENDS ENTIRELY ON THE PLANE'S ORIENTATION, and it is at its WEAKEST in the state CUBE spawns in. The ray march integrates over a fixed ±√3/2 window CENTRED ON THE RAY ORIGIN, so sliding the plane along its own normal moves the window and its contents together — very nearly a no-op — and at spawn the normal IS the z axis, which is the direction this knob translates along. Measured max rmsΔ across the whole of Y: 0.115 flat, 0.317 at ROT X 0.4, 0.759 at ROT X 0.8. Tilt the plane first and Y becomes one of the strongest controls on the module; the faceplate prints 'asleep' or 'live' beside it for this reason. |
| Space Crush | Voxelizes the 3D field-lookup coordinates into chunky blocks (0 = transparent/smooth, max = blocky), aliasing the spatial sampling for a chunkier timbre. |
| Space Diffuse | Adds a 'gravity' that pulls the field sample cloud toward the cube's emptiest wall (0 = off), smearing the lookup coordinates. |
| Spread | Stereo width: at higher values the L and R taps read planes offset by up to ±18% of the ray-march depth (CUBE_SPREAD_DEPTH), so the two channels diverge (0 = mono, both channels identical). Measured, the resulting image is narrow — about -36 dB of side against mid at the stop, and the two channels stay 0.99998 correlated — so treat it as a widener, not a stereo field. |
| S | Per-voice envelope SUSTAIN level (0..1) — the level held while the note's gate stays high after the decay stage. |
| Tune | Coarse pitch in semitones (−36..+36), summed with the FINE offset and the PITCH/TUNE CV to set the oscillator fundamental. |
| View X | Visualization-only camera rotation about X — orbits the 3D view (no effect on audio). |
| View Y | Visualization-only camera rotation about Y — orbits the 3D view (no effect on audio). |
| Zoom | Visualization-only camera zoom for the 3D cube view (does not affect the sound or the selected slice). |
| Wrap | What happens when the slicing plane reads outside the cube: OFF = those regions are silent, ON = the coordinates mirror-fold back inside so the slice stays full. |
source
cube.ts on GitHub.