Apply a preset to a given entity. The preset is a special transaction.
Clone an existing entity, optionally overwriting some fields.
Note that ts by default doesn't correctly type the output. To get correctly typed output, do:
const tonematrix = t.create("tonematrix", {})
const tonematrix2 = t.clone<"tonematrix">(tonematrix)
Optionalargs: DeepPartial<EntityConstructorType<T>>Clone a list of entities, in such a way that pointers that
are both from to entities in this list are updated to point to
the cloned versions. The resulting creates command are ordered in such a way
that all pointers are always valid, and no transaction errors occur.
Each element in the past list can either be an entity itself, or an object
{
entity: NexusEntity<T>,
overwrites?: ConstructorTypes[T]
}
where overwrites work the same as the second parameter of t.clone() or t.create().
Pointers from and to entities not in the list remain unchanged, unless overwritten.
Returns the cloned version of the entities in order. Robust towards duplicates in the passed entities list.
Let's say we have entities a, b, c, d, e, with pointers between each
other like this:
a ──► b ──► c ──► d
▲
│
e
And we call cloneLinked(b, c). Then:
b and c is updated to duplicatesb or c to other entities remain untouchedb or c remain untouchedLeading to a graph like this:
b'──► c' ───┐
▼
a ──► b ──► c ──► d
▲
│
e
If overwrite arguments are given for a specific entity, they overwrite any value after the links have been adjusted.
Create a new entity with default values
Create a new device of the preset's target type and immediately apply the preset to it.
Equivalent to:
const device = t.create(preset.entityType, {})
t.applyPresetTo(device, preset)
Useful together with PresetsAPI.getInstrument / PresetsAPI.getDrums:
const frenchHorn = await client.presets.getInstrument("french-horn")
await nexus.modify((t) => {
const gakki = t.createDeviceFromPreset(frenchHorn)
// ...
})
Create a preset of a given entity. This doesn't modify the nexus document.
Insert a sample into the timeline, creating all required entities; AudioDevice AudioTrack,AutomationCollection, Sample and AudioRegion.
The sample to insert. Anything with name /
durationSeconds / optional bpm is accepted — typically:
{ name, durationSeconds, bpm? } when you already
know the duration locally (e.g. right after upload.uploaded
resolves but before processing completes — see the last example
below).Optionaloptions: InsertSampleOptionsInsertion options like where in time, how long, how to loop, which track to attach to, etc.
The created AudioRegion entity
You can use sample objects returned by SamplesAPI.get, SamplesAPI.list, or SamplesAPI.download:
// fetch some sample
const sample = await at.samples.get("samples/abc-123")
if (sample instanceof Error) throw sample
After the method completes, get the transaction builder to start modifying:
const t = await nexus.createTransaction()
Create track, region, cable, mixer channel, etc:
t.insertSample(sample)
const track: NexusEntity<"audioTrack"> = ...
t.insertSample(sample, { attachTo: track })
const device: NexusEntity<"audioDevice"> = ...
t.insertSample(sample, { attachTo: device })
To make it play in sync with the metronome, you have to tell audiotool how fast your sample should play relative to the metronome. You can do this by passing in either the sample's own BPM, or by the sample's duration in music time (bars).
t.insertSample(sample, {
sample: { bpm: 120 }
})
t.insertSample(sample, {
sample: { musicDurationTicks: Ticks.Bars(3) }
})
Position at bar 1, duration 3 bars, loop if sample is shorter:
t.insertSample(sample, {
sample: { bpm: 120 },
region: { positionTicks: Ticks.Bars(1), durationTicks: Ticks.Bars(3) },
loop: true,
})
t.insertSample(sample, {
sample: { bpm: 120 },
region: { positionTicks: Ticks.Bars(1), durationTicks: Ticks.Bars(9) },
loop: { startTicks: Ticks.Bars(2), durationTicks: Ticks.Bars(2) },
})
If you have a local sample, you have to upload it first. See the "Insert before processing completes" example below if you want to insert earlier, before the backend has finished transcoding.
const file: File = ...
const upload = await at.samples.upload({ file, displayName: "My Sample", bpm: 120 })
if (upload instanceof Error){
throw upload
}
// wait for the bytes to be on the server - after this point the user
// can safely close the tab.
const uploaded = await upload.uploaded
if (uploaded instanceof Error){
throw uploaded
}
// wait for backend processing (transcoding, duration, waveform).
const sample = await upload.ready
if (sample instanceof Error){
throw sample
}
// `sample` is a SampleMeta — pass it directly.
await nexus.modify(t => {
t.insertSample(sample)
})
const upload = await at.samples.upload({ file, displayName: "My Sample", bpm: 120 })
if (upload instanceof Error){
throw upload
}
// you should wait for this point - if the user leaves before upload completes, the project will contain a broken sample.
const uploaded = await upload.uploaded
if (uploaded instanceof Error){
throw uploaded
}
// The sample is uploaded, we can insert into the project, but we need the sample's duration.
// In the web, you can do this as follows. Node.js/Bun/Deno may differ:
const context = new AudioContext()
const decoded = await context.decodeAudioData(await file.arrayBuffer()) // throws if decoding fails!
await context.close()
await nexus.modify(t => {
t.insertSample(
{
name: upload.name,
durationSeconds: decoded.duration,
},
{
sample: { bpm: 120 }
}
)
})
Delete an entity with id id from the document.
Throws if another entity references it; use removeWithDependencies instead.
Delete an entity with id, after all entities with pointers to it, transitively,
are deleted. In other words, remove entity id, after all entities are deleted
that would result in dangling pointers if id was removed.
Let's say we have entities a, b, c, d, e, with pointers between each
other like this:
a ─► b ─┐
│ ├──► d ─► e
│ │
└──► c ─┘
Then calling removeWithDependencies(d.id) will remove all entities except e,
in an order that keeps all existing pointers valid after every modification.
Release the transaction lock and send the modifications to the backend. After this method
is called, this TransactionBuilder can't be used anymore.
Try to update a primitive field. Don't throw if it fails; return a string explaining the error
instead. If this returns undefined, the update was applied. Useful when e.g. a user enters a value
and it's not possible to know if the value is valid or not.
Update a primitive field value
A transaction builder can be used to make changes on a document.
All changes made using the same transaction builder will be part of the same transaction, and as such applied atomically to the backend.
While a transaction builder exists, the document is locked, and no other builders can be created, to avoid race conditions.
To finish a transaction, call send, which will unlock the document and let other builders be created. After send is called, all methods of the builder will throw.
Modifications to the document with a builder are applied to the local document immediately, and only sent to the backend when send is called.
Note that if receiving a builder through SyncedDocument.modify, then send method is called automatically once the function returns. See Overview for more information.