Web apps
The npm package @lloydstellar/la2l pairs, checks, uploads and follows a send. It has no dependencies, and comes with types and its own docs.
npm install @lloydstellar/la2l
import { LA2L, wavFromAudioBuffer } from '@lloydstellar/la2l';
const la2l = new LA2L({ appName: 'Web808' });
if (!la2l.paired) await la2l.pair(codeFromUser);
const delivery = await la2l.send({
title: 'Treinbeat',
bpm: 92,
sounds: [
{ name: 'Beat', audio: wavFromAudioBuffer(loopBuffer), kind: 'loop', role: 'drums', lengthBeats: 4 },
{ name: 'Robot hallo', audio: voiceWav, kind: 'oneshot', role: 'vocal' },
],
onProgress: (share) => (bar.value = share), // 0 to 1
});
status.textContent = 'Waiting for Import in Live';
const result = await delivery.imported();
status.textContent = result.status === 'imported' ? 'In Live' : 'In Live, partly';
ESM for browsers, Node.js 22.12 and newer, Deno and Bun. A page without a build step loads the button from jsDelivr; that one file also carries LA2L, La2lError, wavFromAudioBuffer, wavFromSamples and toWav.
Pairing
Show a field for the code once, with this line above it: "In Live, click Pair app in LA2L and type the code it shows." Case, spaces and dashes do not matter. The SDK keeps the token in localStorage (in Node.js, in a file only the user can read) and sends it only in the Authorization header. Never put it in a URL, a log line or an error report.
Ask for a code again only when a call fails with the code not_paired: then the user unpaired your app in Live. la2l.me() tells you which computer you are paired with; la2l.unpair() ends the pairing.
Making WAV files
import { wavFromAudioBuffer, wavFromSamples, toWav, checkWav } from '@lloydstellar/la2l';
wavFromAudioBuffer(buffer); // an AudioBuffer, 16 bit; { bits: 24 } or { bits: 32 } (float)
wavFromSamples([left, right], 48000); // Float32Arrays from -1 to 1
await toWav(file); // mp3, m4a, ogg: decoded by the browser
await checkWav(file); // can LA2L use it? With LA2L's own text if not
A loop must be a whole number of beats long: one bar of 4/4 lasts 4 * 60 / bpm seconds, exactly 2 seconds at 120 BPM. Render it with an OfflineAudioContext of that length plus a tail, and add the tail to the start, so what rings past the end of the bar plays at the start of the next one:
// One bar of 4/4 at `bpm` as an AudioBuffer: schedule(ctx) starts your sources on the context.
async function renderLoop(bpm, schedule, tailSeconds = 1) {
const rate = 48000;
const bar = Math.round(rate * (60 / bpm) * 4);
const ctx = new OfflineAudioContext(2, bar + Math.round(rate * tailSeconds), rate);
schedule(ctx); // start your sources at step * 60 / bpm / 4
const rendered = await ctx.startRendering();
const loop = new AudioBuffer({ numberOfChannels: 2, length: bar, sampleRate: rate });
for (let c = 0; c < 2; c++) {
const from = rendered.getChannelData(c);
const to = loop.getChannelData(c);
to.set(from.subarray(0, bar));
for (let i = bar; i < from.length; i++) to[(i - bar) % bar] += from[i];
}
return loop;
}
const wav = wavFromAudioBuffer(await renderLoop(120, schedule));
Following a send
send() checks everything before the first byte goes out, with the texts LA2L itself uses, then uploads the files and commits them. It resolves to a Delivery:
delivery.imported()waits until the user clicked Import, and resolves to the status:imported, orpartialwith the result per sound. It rejects when the package failed or expired.delivery.status()asks once;delivery.cancel()takes the package back until it is in Live.delivery.warningsnames what LA2L will ignore, such as an unknown role. Show them to yourself, not to the user.
Errors
Every error is a La2lError with a code and a message that you can show as it is.
code | What to do |
|---|---|
not_paired | Show the code field again. |
bad_code | Show the message, and ask for the code again. |
bad_wav, bad_package | Show the message: it names the sound and what is wrong. |
rate_limited | Wait retryAfter seconds. |
server_full | Show the message, often "Click Import in Live first". |
network, server | Try again later. |
import_failed | Show the message. The package stays: the next Import in Live tries again, so do not send it again. |
expired | Show the message; send the sounds again. |
aborted | Your app stopped it, with an AbortSignal. |
Content-Security-Policy
A page with a CSP must allow the LA2L server: connect-src https://lloydstellar.nl. The button shows the LA2L logo as a data: image, so it also needs img-src data:.
Server-side rendering
@lloydstellar/la2l and @lloydstellar/la2l/button can be imported where a page is rendered on a server (Next.js, Nuxt, SvelteKit): there the button does nothing. Call send() in the browser.
An example
Web808, a drum machine of four sounds and 16 steps, sends its loop and every sound as a one-shot. Its source is in examples/web-drum-machine of the LA2L repository.
The full docs
The package carries its docs, for the version you installed, in node_modules/@lloydstellar/la2l/docs: getting started, the API, Web Audio, the button, testing and errors. npx @lloydstellar/la2l agents-md points AI coding agents to them (AI tools).