LA2L: Send to Live
This app sends sounds to Ableton Live with LA2L (Lloyd's App2Live), through the npm package @lloydstellar/la2l. Follow these notes whenever you build or change that part. Full docs: https://lloydstellar.nl/la2l/llms-full.txt
How it works
The user has LA2L, a Max for Live device, in Ableton Live. They click Pair app in it once and type the six-character code it shows into this app. After that the app sends WAV files with a description of each sound. When the user clicks Import in LA2L, each sound becomes a track in Live, warped to the tempo the app gave. No API key, no account.
Install
npm install @lloydstellar/la2l
The button
<la2l-send-button> asks for the code the first time, sends, and shows the status. Give it a function that returns the sounds at the moment of the click. In React with TypeScript:
import { useEffect, useRef } from 'react';
import '@lloydstellar/la2l/button';
import type { La2lSendButton } from '@lloydstellar/la2l/button';
import { wavFromAudioBuffer } from '@lloydstellar/la2l';
declare module 'react' {
namespace JSX {
interface IntrinsicElements {
'la2l-send-button': React.DetailedHTMLProps<React.HTMLAttributes<La2lSendButton>, La2lSendButton> & { 'app-name'?: string; server?: string };
}
}
}
export function SendToLive({ bpm, render }: { bpm: number; render: () => Promise<AudioBuffer> }) {
const ref = useRef<La2lSendButton>(null);
useEffect(() => {
if (!ref.current) return;
ref.current.sounds = async () => ({
title: `Beat at ${bpm} BPM`,
bpm,
sounds: [{ name: 'Beat', audio: wavFromAudioBuffer(await render()), kind: 'loop', role: 'drums', lengthBeats: 4 }],
});
}, [bpm, render]);
return <la2l-send-button ref={ref} app-name="My app" />;
}
Set sounds through a ref as above; React 18 cannot pass a function to a custom element as a prop. In Vue, tell the compiler about the element: isCustomElement: (tag) => tag.startsWith('la2l-').
Without the button
import { LA2L, La2lError, wavFromAudioBuffer } from '@lloydstellar/la2l';
const la2l = new LA2L({ appName: 'My app' }); // Live shows "Paired with My app"
// Pair once, from a field with this line above it:
// "In Live, click Pair app in LA2L and type the code it shows."
await la2l.pair(code);
const delivery = await la2l.send({ title: 'Beat', bpm: 120, sounds: [{ name: 'Beat', audio: wav, kind: 'loop', role: 'drums' }] });
// show "Waiting for Import in Live", then:
await delivery.imported(); // show "In Live"
Every error is a La2lError with a code and a message to show as it is. On the code not_paired, show the code field again.
Rendering a loop
Render with an OfflineAudioContext exactly a whole number of beats: one bar of 4/4 is 4 * 60 / bpm seconds, so Math.round(48000 * 240 / bpm) frames at 48 kHz. Render a second more, and add what rings past the end of the bar to its start, so the loop has no click and no gap:
const rate = 48000;
const bar = Math.round(rate * 240 / bpm);
const ctx = new OfflineAudioContext(2, bar + rate, rate);
// schedule every hit at step * 60 / bpm / 4 seconds, connected to ctx.destination
const out = await ctx.startRendering();
const loop = new AudioBuffer({ numberOfChannels: 2, length: bar, sampleRate: rate });
for (let c = 0; c < 2; c++) {
const from = out.getChannelData(c), to = loop.getChannelData(c);
to.set(from.subarray(0, bar));
for (let i = bar; i < from.length; i++) to[(i - bar) % bar] += from[i];
}
const wav = wavFromAudioBuffer(loop);
Rules
- Every sound needs
kind:'loop'is warped to the tempo and loops,'oneshot'plays as it is. A loop withoutkind: 'loop'arrives as a one-shot, out of time. - Give loops a
bpm(for the whole package, or per sound) andlengthBeatswhen you know it. rolepicks how Live warps a loop:drums,bass,melodic,vocal,textureorfx.- WAV only:
wavFromAudioBuffer,wavFromSamples, ortoWavfor an mp3 or m4a. Never send an mp3 as if it were WAV. - Names have at most 40 characters. At most 32 sounds and 200 MB per package.
- Pair once. Never put the token in a URL, a log line or an error report.
- A page with a Content-Security-Policy needs
connect-src https://lloydstellar.nlandimg-src data:. - Call it "Send to Live", with the LA2L logo the button has. Never use the Ableton logo.
Testing it
Open https://lloydstellar.nl/la2l/receiver in another tab: it stands in for Live. Pair the app with the code it shows and send. The report must say "Loop, warped in Beats mode at 120 BPM, 4 beats" for a loop of one bar at 120 BPM, not "One-shot".