Skip to content

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 → src
yield 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 form
  • v(type, opts) — generic form; the v_ prefix is added automatically.
  • v.bg(srcOrColor, opts?) — the first argument becomes src when it looks like a URL or path (/..., ./..., http(s)://, data:, or an image extension), otherwise color.
  • 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 form
yield {
type: 'v_bg',
color: 'linear-gradient(160deg, #0b1320, #2a1530)',
fade: 3,
once: true,
}
FieldTypeDefaultDescription
srcstringImage URL. Omit to use color
colorstringdark gradientAny CSS background (color / gradient)
opacitynumber1Layer opacity
fadenumber1Crossfade duration in seconds
fit'cover' | 'contain''cover'Image sizing
kenburnsboolean | objectSlow 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 })
FieldTypeDefaultDescription
srcstringrequiredVideo URL. Audio is always muted
opacitynumber1Layer opacity
fadenumber1Fade-in duration in seconds
loopbooleantrueLoop 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,
})
FieldTypeDefaultDescription
textstringrequiredText to display
idstringautoLayer id. Same id replaces the previous text
x, ynumber50Position in % of the viewport
sizenumber28Font size in px
colorstringtheme steelText color
fontstringtheme monoCSS font-family
animstring'fade''fade', 'slide-up', 'slide-left', 'pop', 'glitch', 'typewriter'
decostring'blocks' (▰▰ TEXT ▰▱), 'corners' (◢◤ / ◥◣), 'rule' (▬▬▬ rules above/below), 'brackets' (【 TEXT 】)
durnumberuntil replacedDisplay duration in seconds
alignstring'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 })
FieldTypeDefaultDescription
opacitynumberrequiredEditor opacity, 01 (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 })
FieldTypeDefaultDescription
target'bg' | stringrequired'bg' or a text id
x, ynumber0Offset in vw/vh
scalenumberScale factor
durnumber1Transition 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 })
PresetBehavior
riseGlowing dots drifting upward from the origin. Loudness increases spawn rate and brightness
sparksBursts of sparks on audio peaks (sudden rises), scattering and decaying
orbitA few orbs circling the center. Loudness pulses the orbit radius and glow
rainThin streaks falling from the top. Loudness increases fall speed and amount
pulseExpanding rings emitted on audio peaks. Loudness controls line thickness
FieldTypeDefaultDescription
presetstringrequiredOne of the presets above
idstringautoSame id replaces the existing layer
x, ynumber50Emission center in % of viewport
colorstringtheme cyanParticle color (glow uses the same hue)
opacitynumber1Opacity of the whole layer
densitynumber1Amount multiplier
speednumber1Velocity multiplier
sizenumber1Particle 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() // everything
yield v.clear('bg') // background + video
yield v.clear('text') // all texts
yield v.clear('particles') // all particle layers
yield v.clear('editor') // restore editor opacity
yield v.clear('lyric') // one text or particle layer by id

Example: 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.