Give individual events different strengths and let an instrument respond with changes in level or tone.
Velocity is one number per voice — NoteParams.vel — composed at trigger time from a fixed set of sources. Patches read it through the vel modulator (Spec §10.1); events, tracks, and shreds set it; native scheduled playback supplies the shared 0.85 base.
Hear it and change it#
Velocity is how strongly a note or hit is played. On a track, gain with a pattern gives each event a strength; it does not turn the mixer's volume fader. That difference matters when an instrument uses velocity to change its tone.
Listen for: strong hats alternating with soft ones.
tempo = 116bpm
timingVersion = 2
sound "bd*4" |> gain 0.6
sound "hh*8" |> gain "[0.4 0.12]*4"
Make it yours: reverse the strengths to "[0.12 0.4]*4". Now the offbeats stand out. The group repeats four times to give eight strengths at the same eight positions as the hats. Control patterns follow time through the bar: just "0.4 0.12" would hold each strength for half a bar. Use the mixer to balance whole parts; gainTrack scales their event strengths. See Velocity and accents and Control patterns.
1. Surfaces#
- Inline event mark — a
=Npostfix on any mini-notation atom,N ∈ [0, 1]:!"c4 d4=0.4 eb4",!"x x=0.5 x x=0.8",!"c4:maj7=0.6". A bare atom contributes no factor (IRvelocity: null); an explicit=1.0asserts full strength against a lower track baseline. Out-of-range values and=on a rest are compile errors (the mini-notation parse refuses them). A bracket voicing takes the mark once for the whole chord —!"[db3,f3,ab3]=0.9"≡!"[db3=0.9,f3=0.9,ab3=0.9]"— distributing it to every member that doesn't carry its own ([db3=0.5,f3]=0.9leaves db3 at 0.5). It composes with the slide marker in the atom's own order, slide first:[a2,a3]_=0.9(Glide between notes). - Track combinator —
|> velocity xwherexis a constant or aPattern Number:pluck |> play melody |> velocity !"1 0.6 0.8 0.4". The pattern runs on its own cycle and is sampled stepwise at each note onset (a long note keeps the velocity it triggered with, like MIDI). - Shred trigger — an optional keyword on
trigger:trigger pluck ~Eb4 1beat vel: 0.6. Constant only; absent means 0.85 (the historical default, now data-driven). - Default performance velocity — native scheduled playback and offline export use the shared 0.85 baseline. The former browser workbench exposed that baseline as a VEL slider; that slider is no longer shipped.
- External MIDI / keyboard —
EngineHandle::one_shotwrites the controller's velocity straight into the voice, overriding (not scaling) event/track factors — the performer's gesture trumps the song, same precedence as a shred trigger.
2. Composition rule#
For a pattern-scheduled note in the current native engine (and historically in the deleted web scheduler):
vel = clamp01( baseVelocity × trackVelocity × eventVelocity × gainEvent × gainTrack × gainCtrl )
baseVelocity—DEFAULT_VEL = 0.85(engine/src/lib.rs), including offline export; the deleted browser workbench exposed it as a slider.trackVelocity—|> velocityconstant or pattern sample; 1.0 absent.eventVelocity— the=Nmark; 1.0 absent.gainEvent/gainTrack— the Control patterns for individual events|> gainpattern and|> gainTrackscalar; velocity-shaped knobs that multiply into the same product.gainmay overshoot 1.0; the final clamp saturates it.gainTrackis macro-affine: it is read at each note-on, so a live macro ride on it is heard from the next event.gainCtrl— againbaked into the event's own control map (a# ctrl-style per-event control); 1.0 absent.
A shred trigger bypasses event/track factors entirely: its vel: (or the 0.85 default) is the voice's velocity as-is. A song using none of these plays every voice at the 0.85 base, as before the feature.
Determinism: velocity is event payload or a pure function of pattern time, and engine/src/offline.rs drives the same engine scheduling path — a render reproduces the source-declared dynamics exactly.
3. Where each piece lives#
compiler/src/mini.rs—split_velocity_suffix(the=Nresolver),MiniEvent.velocity, [0, 1] validation; rejection tests incompiler/src/lower/tests.rs.ir/src/ir.rs—TrackEvent.velocity: Option<f64>(mirrorspan);Track.velocity_const/Track.velocity_pattern(either-or, normalized by the lowerer);ScoreOp::Trigger.velocity: f64.compiler/src/builtins/track_modifiers.rs— thevelocitytrack modifier's descriptor (a constant or a numeric pattern, never both; a rest in the pattern holds the previous value);compiler/src/lower/track.rslowers it, trying the constant first.compiler/src/parse.rs+compiler/src/lower/score.rs— thevel:keyword ontrigger; default 0.85 folded at lower time.engine/src/lib.rs(DEFAULT_VEL,event_dynamics,track_velocity_at,EngineHandle::one_shot) andengine/src/shreds.rs— the one engine's composition, on every backend. (The browser's TypeScript scheduler and shred runner were deleted 2026-08-09; see git history.)- Voices consume
velonly throughNoteParam(vel)IR nodes — patches opt in (gain = vel * 0.6 + 0.3); the five-field desugar does not inject it.
4. Sampler velocity-sensitivity#
The fabricated $sampler synth (engine/src/graph.rs::build_sampler_synths; historically web samplerSynthFor) is Sample × NoteParam(vel) → out, so drums respond to =N, |> velocity, and |> gain like any vel-reading patch. At the default base (0.85), sample output sits ~1.4 dB below the old unconditional level — uniformly live and offline.
5. UI affordances#
- Timeline event glyphs (
ui/src/timeline.rs) — a pattern event's rect height scales with its resolved velocity (0.18 × velof the lane, floored at0.11so near-zero marks stay visible). - Removed with the TypeScript workbench (2026-08-09, see git history): the
|> velocityenvelope band along the ribbon's bottom edge, velocity-scaled shred rows, and thevel: Neditor hover popover.
6. Choosing a level#
- One note harder/softer within a phrase → inline
=N. - Whole phrase follows a dynamic shape →
|> velocity p(decoupled speed/euclid OK; sidechain-style ducking via a follower-derived pattern). - A shred drives the note →
trigger … vel: N. - Performance-level swell → a source-level control/macro or live MIDI; the old VEL slider is historical.
All compose by multiplication; none silently overrides another (the two exceptions — shred triggers and live MIDI — replace the base by design).