AI writes the code. Sialk Stage runs it.
Sialk Stage has no prompt box. It does not need one: an assistant you already pay for writes GLSL, p5 and Three.js well, and a sketch it writes is a folder you drag onto Sialk Stage like any other. Nothing here is a preview or a beta feature, this works today, with the build on the download page.
What this page gives you is the missing half. A model that has never heard of Sialk Stage will write you a beautiful shader that reacts to nothing, because it does not know how the room reaches your sketch. Paste the brief below into your assistant first, and it does.
Why this works better here than elsewhere
Models are materially better at the web stack than at node-graph environments, because the public training data for GLSL, p5 and Three.js dwarfs what exists for the others. That gap is unlikely to close.
The practical form of it: AI-written code is copy-paste in Sialk Stage and a rewrite in TouchDesigner. A shader arrives as a shader. There is no port step, no node graph to rebuild, no equivalent to find.
Honestly, though: that advantage belongs to the web platform, not to Sialk Stage. Anything hosting web content inherits it. What Sialk Stage adds is the half a model cannot write for you, the audio, the frame rate, and two hours without going black.
The brief
Copy everything in this block into your assistant, then ask for what you want.
You are writing a visual sketch for Sialk Stage, a macOS application that runs
web sketches as live, audio-reactive layers on stage.
Produce ONE file, complete and runnable. No build step, no package.json,
no index.html. Choose the format from what I ask for:
- A shader -> a single sketch.glsl
- A 3D scene, or anything that wants Three.js
-> a single scene.js, an ES module (the ONLY file that may
import, and only from 'three' and 'three/addons/...')
- Anything else -> a single sketch.js, p5.js in GLOBAL mode
(top-level `function setup()` / `function draw()`),
no imports
Sialk Stage supplies the page, the render loop, p5 itself and three itself. Do
not write any of them.
=== THE AUDIO, IN p5 (sketch.js) ===
A global `window.sialk` object exists. Every field is mutated in place once
per frame, so read it inside draw() and never cache it.
sialk.audio.level 0..1 broadband loudness, smoothed
sialk.audio.bass 0..1 ~20-250 Hz
sialk.audio.mid 0..1 ~250-2000 Hz
sialk.audio.high 0..1 ~2000-16000 Hz
sialk.audio.percussive 0..1 how drum-like the moment is
sialk.audio.harmonic 0..1 how tonal the moment is
sialk.audio.stems 8 entries {level,bass,mid,high}; zeros unless a stem input is set up
sialk.audio.stemCount how many of those are real, 0 when none
sialk.audio.spectrum Float32Array, 64 bins, log-spaced 20Hz-16kHz
sialk.audio.levelTrail Float32Array, 128 frames of level, [0] newest
sialk.audio.hits number, monotonic onset counter
sialk.audio.onBeat 1 on the beat, decaying before the next
sialk.audio.beatPhase 0..1 between beats; 0 when bpm is 0
sialk.audio.bpm 0 when unknown
sialk.audio.bpmConfidence 0..1, 0 when unknown
sialk.audio.silent boolean, true after 1s with no signal
sialk.transport.time seconds, show position - MAY BE SCRUBBED OR RESET
sialk.transport.elapsed seconds since this sketch loaded, monotonic
sialk.transport.delta seconds since the previous frame
sialk.transport.frame frames since load
sialk.output.width/height/fps
Anything not in that list does not exist. Do not invent fields.
Guard it so the file also runs in a plain browser tab:
const a = window.sialk?.audio ?? { level: 0, bass: 0, mid: 0, high: 0 };
=== THE AUDIO, IN GLSL (sketch.glsl) ===
The same values arrive as uniforms. Do not declare them; they are already
there. Write main() and nothing else - no #version line, no varyings.
vec2 sialkResolution the surface, in pixels
float sialkTime show transport position, seconds
float sialkDelta seconds since the previous frame
float sialkFrame frames since load
float sialkLevel broadband loudness, 0-1
float sialkBass sialkMid sialkHigh the three bands, 0-1
float sialkPercussive sialkHarmonic drum-like, tonal, 0-1
float sialkStemCount vec4 sialkStems[8] per-stem level/bass/mid/high, zeros when none
float sialkHits onset counter
float sialkOnBeat 1 on the beat, decaying
float sialkBeatPhase ramps 0 -> 1 between beats
float sialkBpm sialkBpmConfidence
float sialkSilent 1 when quiet for a second
float sialkBand(float t) t 0..1 across the spectrum
float sialkTrail(float t) t 0..1 back through the level trail
GLSL ES 3.00 (write to `fragColor`) and GLSL ES 1.00 (`gl_FragColor`,
`texture2D`) are both accepted. Shadertoy's iTime, iResolution and iFrame
are defined as aliases. iMouse is a constant zero.
=== A THREE.JS SCENE (scene.js) ===
One ES module. Import three the way its own docs do, and export ONE
function that builds the scene and returns what to draw:
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; // addons work too
export default function (stage) {
// stage: { THREE, renderer, canvas, width, height, sialk }
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(55, stage.width / stage.height, 0.1, 100);
camera.position.z = 9;
const mesh = new THREE.Mesh(new THREE.IcosahedronGeometry(2, 3), new THREE.MeshStandardMaterial());
scene.add(mesh, new THREE.PointLight(0xffffff, 60, 0, 1).translateY(6));
return {
scene,
camera,
update({ audio, transport, parameters, width, height }) {
// called every frame, before the render; the same fields as p5 above
mesh.scale.setScalar(1 + audio.bass * 0.6);
mesh.rotation.y = transport.elapsed * 0.3 * Number(parameters.speed ?? 1);
},
};
}
Sialk Stage owns the canvas, the WebGLRenderer, the loop and resizing. So:
NO `new WebGLRenderer`, NO `setAnimationLoop` or `requestAnimationFrame`,
NO `document.body.appendChild`, NO CDN import - `three` resolves to the
copy Sialk Stage ships, offline. The layer is transparent where the scene does
not paint; set `scene.background` if you want it opaque. A perspective
camera's aspect is kept for you; return `resize(width, height)` to do it
yourself, or `render(frame)` to take over drawing (post-processing with an
EffectComposer built on `stage.renderer`). The function may be `async` if
a model has to load first; the layer stays transparent until it resolves.
Controls for a scene are declared in a visual.json beside it (below), any
type, and arrive on `parameters` in update() under their own names.
=== CONTROLS THE PERFORMER CAN PLAY (p5 only) ===
Declare once at startup, then read live values every frame:
sialk.parameters.declare({
speed: { type: 'number', min: 0, max: 4, default: 1, label: 'Speed' },
mode: { type: 'enum', options: ['rings','bars'], default: 'rings' },
tint: { type: 'colour', default: '#ffbb66' },
burst: { type: 'trigger' },
});
Types: number (a fader - ALWAYS give min and max), boolean, enum, colour,
trigger. Read from `sialk.parameters.values`, which is one object mutated
in place - never destructure it at startup. Give every parameter a `label`;
it is read at a dark desk by someone who has never met the sketch.
=== CONTROLS FOR A SHADER OR A SCENE (visual.json beside the file) ===
A shader has no JavaScript, so its controls are declared in a visual.json
in the same folder, in the same shape, restricted to number, boolean and
colour (a number needs min and max; everything needs a label). A Three.js
scene declares its controls the same way, and may use every type:
{ "parameters": {
"height": { "type": "number", "min": 0.2, "max": 1, "default": 0.66, "label": "Bar height" },
"warm": { "type": "colour", "default": "#c2ab94", "label": "Low colour" } } }
In a shader they arrive as uniforms under their own names: number and
boolean as `uniform float height;`, colour as `uniform vec3 warm;`. Do NOT
declare them in the shader; the host does. Read them like sialkBass. In a
scene they arrive as `parameters.height` and `parameters.warm` ('#c2ab94')
in update().
=== RULES ===
1. Use createCanvas(windowWidth, windowHeight) in p5. Sialk Stage sizes the
surface.
2. Leave headroom. A value that saturates at 0.9 makes a loud room and a
very loud room look identical. Map bands into a visible range, do not
multiply them until they clip.
3. Prefer `beatPhase` over `onBeat` for anything that decays:
`1.0 - beatPhase` falls in time with the music.
4. Use `transport.elapsed` for anything that should simply keep going, and
`transport.time` only for something that should follow the show. `time`
can jump backwards.
5. Cost is asymmetric. GLSL cares about PIXELS - a loop that always runs to
the end costs its full length 8.3 million times at 4K. p5 and Three.js
care about COUNT, not resolution - a quarter of a million points is slow
at every size; prefer one InstancedMesh to a thousand meshes. This runs
at 4K/60 in front of a room. Write accordingly.
6. Skipping background() in p5 leaves the layer transparent, so what is
beneath shows through. That is often what you want.
That block is the whole interface, and it does not move. The contract is frozen: fields are only ever added, never removed, renamed, or given a new meaning. So a brief that works tonight still works after Sialk Stage has moved on, which is the only reason it is worth pasting into anything.
With an agent that reads tools
The same brief, the frozen contract, the kinds Sialk Stage plays, three worked
examples and a check for what was written are also packaged as
@sialk/agent: an MCP server over stdio (sialk-agent mcp), a command line
(sialk-agent check <folder>, which exits 1 on anything Sialk Stage would not play
as intended), and a skill an agent that reads one can follow. It is on npm,
MIT, beside the contract. Nothing to install: npx @sialk/agent brief prints
the brief, npx @sialk/agent check <folder> checks a sketch, and an MCP
client is pointed at npx @sialk/agent mcp over stdio. The skill is the
skill/sialk-sketch/ folder inside the package; copy it to wherever your
agent reads skills from. The library is made with it.
Worked example - a shader
Using the Sialk Stage brief above, write me a raymarched tunnel that pulses on the kick and shifts hue slowly. Warm palette. It has to hold 60 fps at 4K, so keep the march short and let the distance field converge early.
What comes back should look like this, one file, no scaffolding, the audio read straight out of the uniforms:
void main() {
vec2 uv = (gl_FragCoord.xy - 0.5 * sialkResolution) / sialkResolution.y;
float pulse = 1.0 - sialkBeatPhase; // falls in time with the music
float radius = 0.35 + sialkBass * 0.22; // headroom left deliberately
vec3 ro = vec3(0.0, 0.0, sialkTime * 0.6);
vec3 rd = normalize(vec3(uv, 1.0));
float t = 0.0;
for (int i = 0; i < 48; i++) {
vec3 p = ro + rd * t;
float d = radius - length(p.xy) + 0.05 * sin(p.z * 3.0 + sialkTime);
if (d < 0.001) break; // converges early, so it is cheap
t += d * 0.9;
}
float glow = 1.0 / (1.0 + t * t * 0.35);
vec3 warm = vec3(1.0, 0.55, 0.25) + 0.25 * sin(vec3(0.0, 2.1, 4.2) + sialkTime * 0.15);
fragColor = vec4(warm * glow * (0.7 + 0.6 * pulse), 1.0);
}
Put it in a folder as sketch.glsl and drag the folder onto Sialk Stage.
Worked example - p5, with controls
Using the Sialk Stage brief above, write me a p5 sketch: a ring of bars driven by the spectrum, leaving trails. Give the performer a fader for how long the trails last and a colour swatch.
function setup() {
createCanvas(windowWidth, windowHeight);
angleMode(RADIANS);
sialk.parameters.declare({
trails: { type: 'number', min: 0, max: 1, default: 0.6, label: 'Trail length' },
tint: { type: 'colour', default: '#ffbb66', label: 'Colour' },
});
}
function draw() {
const p = sialk.parameters.values; // read live, never cached
const a = window.sialk?.audio ?? { spectrum: new Float32Array(64), level: 0 };
background(0, 0, 0, map(p.trails, 0, 1, 90, 4));
translate(width / 2, height / 2);
stroke(p.tint);
strokeWeight(3);
const bins = a.spectrum;
for (let i = 0; i < bins.length; i++) {
const angle = (i / bins.length) * TWO_PI;
const inner = 140 + a.level * 60;
const outer = inner + bins[i] * min(width, height) * 0.28;
line(cos(angle) * inner, sin(angle) * inner, cos(angle) * outer, sin(angle) * outer);
}
}
sketch.js in a folder, dragged on. The two controls appear on the layer.
Check it before the room
A model is confident about things it has not run. Three things are worth a minute each:
- Play music into it. Not a sine sweep, the actual set. A sketch tuned on a hum is tuned on nothing. See the audio.
- Watch for saturation. If the picture looks the same at loud and very loud, a band was multiplied until it clipped. This is the single commonest thing to come back wrong.
- Run it at the size you will play it at. A shader that is fine at 720p and collapses at 4K is telling you the march is too long, and that is not something Sialk Stage can fix for you.
The shim is the fastest loop for this: the same
window.sialk in an ordinary browser tab, fed from your microphone. Refresh,
iterate, then drag the folder on.
What Sialk Stage does not do
Sialk Stage ships no generator. There is no prompt box in the application, no API key to paste, and nothing here talks to a model. You bring the assistant you already use; Sialk Stage runs what it writes.
A prompt box inside Sialk Stage is now on the pricing page as Sialk Stage AI, the one subscription, because it is the one thing with a running cost, and it arrives after v1. It is billed inside that subscription on a monthly allowance rather than asking you for a key, the way Figma and Spline do it.
No date and no version here, on purpose. That is not vagueness; it is the same rule the runtimes are held to. A date printed in a build that has already shipped cannot be corrected and will be quoted back. What can be said honestly is only this: it is committed to, and it is not today.
What is frozen either way is the contract. Whatever writes the sketch, you, a model, or something that does not exist yet, the surface it writes against is the same one, and it does not move.