Under the hood
Codemods
A codemod is a small program that changes Live Sets. livesaver runs it over folders of sets, shows
what would change, and with --apply writes the changes the same way every livesaver command
does: the result must be well-formed XML, Live's backup scheme is followed
(Backup/<set> [date time].als), the run is journaled, and livesaver undo <run> puts every set
back.
livesaver run --list # built-in codemods
livesaver run set-tempo ~/Music/Projects --option bpm=124 # dry run: what would change
livesaver run set-tempo ~/Music/Projects --option bpm=124 --apply
livesaver run ./my-codemod.ts ~/Music/Projects # your own
Writing one
import type { Codemod } from 'livesaver'
export default {
name: 'rename-bass',
description: 'Call every track named "bass" "Bass"',
transform(set, ctx) {
for (const track of set.tracks()) {
if (track.name.toLowerCase() === 'bass' && track.name !== 'Bass') {
ctx.note(`${track.name} → Bass`)
track.rename('Bass')
}
}
},
} satisfies Codemod
- The import is type-only, so the file needs nothing at run time. Bun runs it as it is, and so does Node ≥ 22.18, as long as the file uses only TypeScript that type stripping removes (no enums, namespaces or parameter properties).
transformonly describes edits throughset(orset.patch). Nothing is written while it runs, and a set it does not touch is not written at all.ctx.optionsholds--option key=valuepairs;ctx.note(…)adds a line to the report.- Throwing an error skips that set and reports the error.
Views
set.tracks() | audio, MIDI, group and return tracks in order (TrackView) |
set.master() | the master (MainTrack, MasterTrack before Live 12) |
set.tempo, set.tempoAutomated, set.setTempo(bpm) | the master tempo; the start value of Live's tempo envelope is changed too, an automated tempo never |
set.locators() | { el, time, name } in time order |
set.rootAttr('Creator') | root attributes (Creator, MinorVersion) |
set.setValue(el, 'Name/UserName', 'x') | set the Value of a child element |
set.pointees() | ids that automation, modulation or mappings point to |
track.kind, track.name, track.rename(name), track.devices() | a track (DeviceViews of its chain) |
device.type, device.name, device.plugin, device.isRack, device.chains() | a device; plugin is { format, ident, name } for plug-ins |
device.on, device.onLinked, device.setOn(on) | the on/off switch (automated or mapped switches are left alone) |
Anything else goes through the element index and the patcher:
set.index:children,child,find(el, 'A/B'),findAll,descendants,all(name),attr,valueset.patch:setAttr(el, name, value),replace(el, xml),replaceInner(el, text),insertBefore(el, xml),insertAfter(el, xml),remove(el)
Edits are spliced into the original bytes; everything you do not touch stays byte-identical.
Checks before anything is written
- The patched document is scanned again, strictly; a malformed edit is an error for that set, and nothing is written.
- If the number of sample/device references or plug-ins changed, the report says so.
- The set must not have changed since it was read (Live saving it meanwhile): otherwise it is not written.
--applyrefuses to run while Live is open (--forceoverrides).
Built-ins
set-tempo --option bpm=<n>: set the master tempo (sets with tempo automation are left alone).mastering-off: switch off Limiter, Glue Compressor, Compressor and Multiband Dynamics on the master, racks included, e.g. before exporting stems for a mastering engineer.
More examples are in examples/codemods.