A sample is a recording you can play as musical material: a drum hit, an instrument note, a loop, or a sound you recorded yourself. Volta can sequence it, change its speed, turn it into a pitched instrument, or scatter tiny pieces into a texture. These first examples use supplied samples.
Play a recorded rhythm#
sound triggers sample names directly. You can use the same rests, subdivisions and alternations that work with note patterns.
Listen for: a kick and snare sharing a bar, with a quick pair of hats between them.
tempo = 120bpm
timingVersion = 2
sound "bd [hh hh] sn hh" |> gain 0.5
Make it yours: replace the last hh with [sn hh], or use separate sound lines for independent levels and effects. See Rhythm and groove for a step-by-step beat.
Speed changes pitch and duration together#
Ordinary sample playback is like changing a record player's speed. At 0.5, the recording takes twice as long and sounds an octave lower. At 2, it takes half as long and sounds an octave higher. This changes the recording's playback, not the timing of the trigger pattern.
Listen for: the snare changing between its normal and slowed-down sound.
tempo = 120bpm
timingVersion = 2
sound "sn ~ sn ~" |> speed "1 0.5" |> gain 0.4
Make it yours: try "1 1.5" for an upward shift, or a constant 0.75 for a thicker hit. If you need to change duration while keeping pitch, continue to Time-stretching and pitch-shifting.
Turn a recording into an instrument#
A recording of one note needs a root pitch: the note that sounds at its original playback speed. The supplied piano sample has root C3. Declaring it with pitched ~C3 lets incoming notes choose the playback ratio.
Listen for: a recorded piano following a new melody.
tempo = 120bpm
timingVersion = 2
samplekeys = patch {
source = sample "piano" |> pitched ~C3,
ampEnv = adsr 3ms 300ms 0.6 180ms,
gain = vel * 0.4
}
play samplekeys "c4 eb4 g4 bb4"
A sample you dropped into the workbench usually already knows its root: the import detects it and writes it to the sample's manifest entry as rootPitch. Then a bare pitched uses it — sample "mycello" |> pitched — and you never type the note. A sample with no rootPitch is treated as C4, and the problems panel says so (UntaggedRoot); writing the root (pitched ~C3) always wins.
Make it yours: move the melody down an octave. With a single recording, high notes become shorter and brighter and low notes become longer and darker. A multisampled instrument uses several recordings across the keyboard to reduce that change; an existing SFZ instrument converts into one with SFZ import.
Turn the sample into a cloud#
Granular synthesis layers short windows of a recording. Grain size sets how much each little window contains; grain rate sets how often a new one begins. Together they move from scattered particles toward a sustained texture.
Listen for: a piano recording becoming a shimmering, overlapping texture.
tempo = 120bpm
timingVersion = 2
cloud = patch {
source = sample "piano" |> granular
|> grainSize 100ms |> grainRate 24Hz
|> grainPos 0.12 |> grainSpray 30ms |> grainPan 0.6,
ampEnv = adsr 200ms 0ms 1 400ms,
gain = 0.25,
voices = 1
}
play cloud ("c4" |> slow 4)
Make it yours: change 100ms to 35ms for finer grains, then try a higher grain rate to reconnect the texture. This example reads a fixed region of the sample; its note does not set sample pitch. See Granular synthesis for pitch and position controls.
Choose the right sample workflow#
| Goal | Tool | What to prepare |
|---|---|---|
| Trigger one-shots | sound, sample | A loaded sample name |
| Rearrange a loop | chop, slice, bite | A loop; detected slices for onset-based work |
| Follow project tempo | warp | The recording's source BPM |
| Change length independently of pitch | stretch | A recording to stretch |
| Reuse your own synthesis as audio | bounceOf, sampleOf | A source to render and a host that prepares it |
| Make a keyboard instrument | pitched, sample zones | Correct sounding root pitches |
| Capture an external instrument | Autosample | The instrument and a supported capture setup |
| Use an SFZ instrument | volta import sfz, or drop the .sfz on Studio | The .sfz file and its audio folder |
A loop with no tempo plays at its own speed. warp needs the loop's source tempo: warp { bpm = 138 } in the source, a bpm in its manifest entry (drop-import writes one when it finds a steady beat), or a recording made in Volta, which remembers the tempo it was recorded at. Without any of them the loop plays at native rate and drifts off the grid, and the problems panel shows an UntaggedWarp warning naming the sample. See Keep a loop in time.
Bring in your own sounds#
Drop audio files onto the workbench to add them to the open song. On the desktop they are copied into <song dir>/samples/; in the browser they are kept for that song in this browser profile. Either way the import listens to each file once: a loop with a steady beat gets a bpm, a pitched sound gets a rootPitch, a drum hit gets neither, and the toast names what to type (sound !"mybreak"). Then sample "mybreak" |> warp and sample "mycello" |> pitched just work.
Both hosts run the same detection (ui/src/sampleimport.rs::detect_meta: onsets → estimate_bpm, pitch tracker → fractional-MIDI root, each rounded to 0.1 and kept only when the detectors are confident; desktop app-native/src/samples.rs::import_files).
The browser song-sample store. eframe delivers a dropped file as bytes (no path), so the workbench hands them to EngineBridge::import_sample_bytes; the UI wasm analyses one file per frame and passes the batch to the page (importSamples → Studio.take_sample_import, answered through Studio.push_sample_import_result). The page stores each file in IndexedDB volta-song-samples (runtime/public/studio-sample-store.js), scoped to the document it was dropped on, as {scope, name, ext, bytes, bpm?, rootPitch?}. The bank name is the file stem with : and whitespace turned into -; a name the stock manifest already uses, or that this song imported with different bytes, gets -2, -3, … (imports never shadow a stock bank — unlike the desktop, whose song folder shadows the stock bank). Dropping the same file again is a no-op. The records form an overlay on the stock manifest: loadOneSample installs an overlay name from the stored bytes (with set_sample_root / set_sample_bpm), and analysis receives stock plus overlay through set_sample_manifest, so the names are known and UntaggedWarp / UntaggedRoot read their detected tags. The same database (version 2) also holds the song's pinned prints in a second object store, prints (runtime/public/studio-print-store.js, pinned-prints.md §7); both stores open through openSongDb, which upgrades a v1 profile in place.
Limits: browser imports stay in this browser profile — share links, rooms, publish and Save As carry the source only, so a recipient gets "not in the manifest" warnings and silent voices. Folders cannot be dropped in the browser. There is no delete for an imported sample yet. Analysis runs on the page's frame: a long file (minutes) stalls the browser for about a second.
For your own recordings, use the Samples workflow described in Recording and takes and The sampler. A code example naming an unavailable file can compile and still be silent; sample names refer to loaded assets, not arbitrary internet downloads.