VOLTAreference

Reference / Start here

Modulation recipes

Start here · playable examples

Open in the workbench →
On this page · 5 sections

Name a musical gesture once, then pass it different controls. These recipes are ordinary editable library functions: the compiler expands them into existing oscillators, envelopes, detectors and effects. They create no macros. Your macro, pattern, LFO or simulation remains the source of the movement.

Import "mod" as motion for controls and patch transforms, and "fx" as fx for audio processing. The Modules browser displays their authored modulator, transform and effect categories. A top-level binding cannot share a qualified module's underlying key: use vibratoDepth = macro "mod" 0.4 to retain a macro called mod while importing the mod library.

One filter, three placements#

filterMotion audio cutoff resonance accepts a Hz control. A voice supplies its own audio; audioFx supplies a persistent stereo track or return input. The shared sweep below evaluates once across all three placements. The Moving Parts preset (examples/songs/modlib.volta) introduces each in separate scenes.

Listen to this example · four bars, then the tail
import "fx" as fx
@shared "recipe-sweep"
sweep = lfoSine 2c |> scale [0, 1] |> syncTo "transport"
cutoff = 400Hz + sweep * 2500Hz
voice = patch {
  source = saw (freq pitch) |> fx.filterMotion cutoff 2,
  ampEnv = adsr 4ms 180ms 0.2 100ms, gain = vel * 0.12
}
dry = patch {
  source = saw (freq pitch),
  ampEnv = adsr 4ms 180ms 0.2 100ms, gain = vel * 0.12
}
a = play voice !"c3 ~ eb3 ~"
b = play dry !"~ g3 ~ bb3" |> audioFx (\audio -> audio |> fx.filterMotion cutoff 2)
c = play dry !"c4 ~ g4 ~"
bus colorReturn = audioFx (\audio -> audio |> fx.filterMotion cutoff 2)
mixer { c = { level = -90dB, presends = { colorReturn = 0.4 } } }

Auto-wah, tremolo, pan and velocity color#

autoWah measures its actual local input with follow; it does not use the legacy calibrated track tap. tremoloMotion attenuates by a supplied 0–1 control, and panMotion expects bipolar position. velocityColor takes an explicit velocity, opening the filter and increasing drive. In a persistent insert, use lastVelocity "track" initial instead of an implicit note velocity.

Listen to this example · four bars, then the tail
import "fx" as fx
movement = lfoSine 1b |> scale [0, 1]
p = patch {
  source = saw (freq pitch)
    |> fx.autoWah 300Hz 2200Hz 5ms 100ms
    |> fx.velocityColor vel 500Hz 1800Hz 0.25
    |> fx.tremoloMotion movement 0.35
    |> fx.panMotion (lfoSine 2c * 0.6),
  ampEnv = adsr 5ms 180ms 0.3 120ms, gain = vel * 0.15
}
t = play p !"c3 eb3 g3 bb3"

Pitch and brightness gestures#

vibrato rate depth delay delays the start of a pitch LFO. triggeredVibrato restarts one on an explicit trigger. scoop depth time is a linear attack drop; flutter amount scales a stable per-note random draw in cents. Their pitch units survive reuse. filterOpen envelope base range control lift includes note velocity; filterEnv envelope base range omits the extra control lift.

Listen to this example · four bars, then the tail
import "mod" as motion
intensity = macro 0.4
hits = patternTrig !"t*8"
p = patch {
  pitchMod = motion.vibrato 5.5Hz (intensity * 15Cents) 100ms
    + motion.triggeredVibrato hits 7Hz 4Cents
    + motion.scoop 8Cents 30ms + motion.flutter 2,
  source = triangle (freq pitch + pitchMod),
  filtEnv = adsr 4ms 220ms 0.2 90ms,
  cutoff = motion.filterOpen filtEnv 500Hz 1800Hz intensity 2400Hz,
  ampEnv = adsr 5ms 250ms 0.3 120ms,
  gain = vel * 0.2 * motion.tremoloGain 1b 0.2
}
played = p |> motion.humanizePatch 0.25
t = play played !"c4@2 eb4 g4@2 bb4"

humanizePatch patch amount adds pitch and gain variation to explicit pitchMod and gain fields. duckedPatch patch source depth attack release multiplies its gain by the legacy track-follower law. Both take the patch first, so patch |> motion.humanizePatch amount uses normal subject-first piping.

Rhythmic ducking and filtered sidechain#

duckGain control depth and tremoloGain rate depth return gain multipliers. followerGain source depth attack release supplies the legacy calibrated follower version. filteredDuck accepts detector audio, filters it, then follows it; transientColor samples a color at detected onsets and slews the result over 5 ms. Here the explicit current pre-fader tap has unity calibration. Inline onsets report on their 128-sample analysis grid.

Listen to this example · four bars, then the tail
import "mod" as motion
import "fx" as fx
kick = patch {
  source = sine (freq pitch + motion.scoop 36Semitones 40ms),
  ampEnv = adsr 1ms 130ms 0 30ms, gain = vel * 0.3
}
drums = play kick !"c2*4"
detector = tapLive "drums" { stage = "preFader" }
color = lfoSine 2c |> scale [0, 1]
p = patch {
  source = saw (freq pitch)
    |> fx.filteredDuck detector 80Hz 0.7 5ms 100ms
    |> fx.transientColor detector color 400Hz 2500Hz
    |> fx.delayMotion (60ms + color * 35ms) 0.3,
  ampEnv = adsr 5ms 220ms 0.3 100ms,
  gain = vel * 0.1 * motion.duckGain (holdFor 100ms (patternTrig !"t*4")) 0.3
}
q = p |> motion.duckedPatch "drums" 0.2 5ms 100ms
t = play q !"c3 eb3 g3 bb3"

delayMotion uses the existing retained delay. Moving its time deliberately changes pitch; its controls follow Bring a sound to life's delay policy.

psuSag track depth is power-supply sag: 1 − follower track 2ms 150ms · depth, meant to read the track's OWN name. Its summed voice level arrives one 128-sample block late, which keeps the self-read stable, so a chord that stacks up pulls the factor lower than a single note does (measured at depth 0.6: 0.92× for one saw, 0.83× for a four-note chord). Multiply it into gain, cutoff or frequency; see Analog character §14.

Listen to this example · four bars, then the tail
import "mod" as motion
pad = patch {
  source = saw (freq pitch),
  ampEnv = adsr 300ms 0ms 1 600ms,
  gain = vel * 0.08 * motion.psuSag "padTrack" 0.4
}
padTrack = play pad !"a2:min7"

Grain motion and one-control gestures#

A simulation can replace an LFO without changing the consuming recipe. colorGesture audio gesture maps one 0–1 control to cutoff, drive and pan. grainMotion sampleName position size rate uses the existing granular source and its declared per-grain latches; it does not continuously resize active grains.

Listen to this example · four bars, then the tail
import "fx" as fx
movement = orbit 0.3Hz |> scale [0, 1]
p = patch {
  source = fx.grainMotion "piano" movement (35ms + movement * 30ms) 24Hz
    |> fx.colorGesture movement,
  ampEnv = adsr 30ms 150ms 0.6 150ms, gain = 0.12
}
t = play p !"c3@4 eb3@4"

Keep destination units explicit: motion.wobble 2Hz 1200Hz 700Hz is a Hz control; motion.wobble 2Hz 0.5 0.4 is dimensionless. Envelopes and vel need a voice context. Global controls use explicit clock and note-source policies. Rack Routes preserves expression ownership: nonlinear combinations remain expressions, and imported coefficients cannot silently edit the shared library.