Visuals
Nimbus can render visuals behind the editor, driven by meta events yielded from
your song generator. Yield any object whose type starts with v_ and the
editor’s visual stage (powered by the @scriptedcity/lumos package) picks it up. Use
v_editor to make the editor itself translucent so the visuals show through —
enough to build a simple lyric video alongside your track.
All visual events support the standard meta-event fields beat (fire at a
beat within the cycle, default 0) and once (fire only on the first cycle
after start/swap).
The v() helper
The v global builds visual events for you — it is to visuals what cast()
is to sound. Each helper returns a plain event object, so yielding raw objects
(the “raw form” shown throughout this page) remains equally valid.
yield v.bg('linear-gradient(160deg, #0b1320, #2a1530)', { fade: 3, once: true })yield v.bg('/img/cover.png', { kenburns: true }) // URL/path → srcyield v.video('/clips/rain.mp4', { opacity: 0.6 })yield v.text('NEON LETTER', { id: 'title', anim: 'typewriter' })yield v.editor(0.55, { once: true })yield v.move('bg', { scale: 1.1, dur: 8 })yield v.clear('text')yield v('bg', { color: '#0b1320' }) // generic formv(type, opts)— generic form; thev_prefix is added automatically.v.bg(srcOrColor, opts?)— the first argument becomessrcwhen it looks like a URL or path (/...,./...,http(s)://,data:, or an image extension), otherwisecolor.v.video(src, opts?),v.text(text, opts?),v.editor(opacity, opts?),v.move(target, opts?),v.clear(target?).
v_bg — background
Sets the background layer. New backgrounds crossfade over the previous one.
yield v.bg('linear-gradient(160deg, #0b1320, #2a1530)', { fade: 3, once: true })
// raw formyield { type: 'v_bg', color: 'linear-gradient(160deg, #0b1320, #2a1530)', fade: 3, once: true,}| Field | Type | Default | Description |
|---|---|---|---|
src | string | — | Image URL. Omit to use color |
color | string | dark gradient | Any CSS background (color / gradient) |
opacity | number | 1 | Layer opacity |
fade | number | 1 | Crossfade duration in seconds |
fit | 'cover' | 'contain' | 'cover' | Image sizing |
kenburns | boolean | object | — | Slow pan/zoom. true for a default drift, or { from: {x,y,scale}, to: {x,y,scale}, dur } (x/y in %, dur in seconds) |
v_video — background video
yield v.video('/clips/rain.mp4', { opacity: 0.6, loop: true })| Field | Type | Default | Description |
|---|---|---|---|
src | string | required | Video URL. Audio is always muted |
opacity | number | 1 | Layer opacity |
fade | number | 1 | Fade-in duration in seconds |
loop | boolean | true | Loop playback |
v_text — animated text
Displays a text layer (lyrics, titles, captions). Texts are keyed by id:
yielding the same id replaces the previous text and replays the animation.
Without an id, each event creates a new auto-numbered layer.
yield v.text('streetlight hum, a tape rewinds', { id: 'lyric', anim: 'slide-up', deco: 'rule', y: 68, beat: 2,})| Field | Type | Default | Description |
|---|---|---|---|
text | string | required | Text to display |
id | string | auto | Layer id. Same id replaces the previous text |
x, y | number | 50 | Position in % of the viewport |
size | number | 28 | Font size in px |
color | string | theme steel | Text color |
font | string | theme mono | CSS font-family |
anim | string | 'fade' | 'fade', 'slide-up', 'slide-left', 'pop', 'glitch', 'typewriter' |
deco | string | — | 'blocks' (▰▰ TEXT ▰▱), 'corners' (◢◤ / ◥◣), 'rule' (▬▬▬ rules above/below), 'brackets' (【 TEXT 】) |
dur | number | until replaced | Display duration in seconds |
align | string | 'center' | 'left', 'center', 'right' |
v_editor — editor opacity
Sets the opacity of the editor pane itself (text included). The editor stays fully opaque until you yield this event.
yield v.editor(0.55, { once: true })| Field | Type | Default | Description |
|---|---|---|---|
opacity | number | required | Editor opacity, 0–1 (1 = opaque) |
yield v.clear('editor') (or a full v.clear()) restores full opacity.
v_move — pan / zoom
Moves the background or a text layer by id.
yield v.move('lyric', { y: -10, scale: 1.2, dur: 4 })yield v.move('bg', { scale: 1.1, dur: 8 })| Field | Type | Default | Description |
|---|---|---|---|
target | 'bg' | string | required | 'bg' or a text id |
x, y | number | 0 | Offset in vw/vh |
scale | number | — | Scale factor |
dur | number | 1 | Transition duration in seconds |
v_particles — audio-reactive particles
Layered above the background (and video) but below texts, particle layers
react to the main audio output in real time. Layers with the same id
replace each other; omitting id stacks new layers.
yield v.particles('rise', { y: 80, color: '#56c9d8', opacity: 0.6, once: true })yield v.particles('sparks', { id: 'hit', react: 'bass', density: 1.5 })| Preset | Behavior |
|---|---|
rise | Glowing dots drifting upward from the origin. Loudness increases spawn rate and brightness |
sparks | Bursts of sparks on audio peaks (sudden rises), scattering and decaying |
orbit | A few orbs circling the center. Loudness pulses the orbit radius and glow |
rain | Thin streaks falling from the top. Loudness increases fall speed and amount |
pulse | Expanding rings emitted on audio peaks. Loudness controls line thickness |
| Field | Type | Default | Description |
|---|---|---|---|
preset | string | required | One of the presets above |
id | string | auto | Same id replaces the existing layer |
x, y | number | 50 | Emission center in % of viewport |
color | string | theme cyan | Particle color (glow uses the same hue) |
opacity | number | 1 | Opacity of the whole layer |
density | number | 1 | Amount multiplier |
speed | number | 1 | Velocity multiplier |
size | number | 1 | Particle size multiplier |
react | 'level' | 'bass' | 'treble' | 'level' | Frequency band the layer reacts to |
Without audio playing, layers stay in a calm idle animation.
v_clear — remove visuals
yield v.clear() // everythingyield v.clear('bg') // background + videoyield v.clear('text') // all textsyield v.clear('particles') // all particle layersyield v.clear('editor') // restore editor opacityyield v.clear('lyric') // one text or particle layer by idExample: lyric video
function* song(ctx) { yield { type: 'tempo', bpm: 84, once: true }
yield v.bg('linear-gradient(165deg, #0b1320, #2a1530)', { fade: 3, once: true }) yield v.editor(0.55, { once: true }) yield v.text('NEON LETTER', { id: 'title', anim: 'typewriter', deco: 'corners', y: 26, size: 40, dur: 14, once: true, })
const LYRICS = [ 'streetlight hum, a tape rewinds', 'your name in vapor on the glass', ] yield v.text(LYRICS[ctx.cycle % LYRICS.length], { id: 'lyric', anim: 'slide-up', deco: 'rule', y: 68, })
const pn = piano({ gain: 0.3 }) yield cast(pn, scales(4, 'F4:dorian'), seq(4, '0,_,2,_,4,_,_,_'), ctx)}The Neon Letter (MV) preset in the editor is a complete version of this
example.