Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112// AC Audio Bridge for Max for Live// Receives audio samples from jweb and outputs them as signals// // This script processes Float32 audio samples sent from aesthetic.computer's// speaker worklet via the jweb message bridge.
const Max = require("max-api");
// Audio buffer settingsconst BUFFER_SIZE = 2048; // Ring buffer sizeconst CHUNK_SIZE = 128; // Output chunk size (matches Max signal vector)
// Ring buffers for left and right channelslet leftBuffer = new Float32Array(BUFFER_SIZE);let rightBuffer = new Float32Array(BUFFER_SIZE);let writeIndex = 0;let readIndex = 0;let samplesAvailable = 0;
// Sample rate conversion (browser might be 48kHz, Ableton might be 44.1kHz)let sourceSampleRate = 48000;let targetSampleRate = 44100;
// Statisticslet totalSamplesReceived = 0;let underruns = 0;
Max.post("๐๏ธ AC Audio Bridge loaded");
// Handle incoming samples from jweb// Format: [numSamples, ...leftSamples, ...rightSamples]Max.addHandler("samples", (...args) => { if (args.length < 1) return; const numSamples = args[0]; if (args.length < 1 + numSamples * 2) { Max.post("โ ๏ธ Invalid sample data received"); return; } // Extract left and right samples const leftSamples = args.slice(1, 1 + numSamples); const rightSamples = args.slice(1 + numSamples, 1 + numSamples * 2); // Write samples to ring buffer for (let i = 0; i < numSamples; i++) { leftBuffer[writeIndex] = leftSamples[i]; rightBuffer[writeIndex] = rightSamples[i]; writeIndex = (writeIndex + 1) % BUFFER_SIZE; // Track available samples (don't exceed buffer size) if (samplesAvailable < BUFFER_SIZE) { samplesAvailable++; } } totalSamplesReceived += numSamples;});
// Handle sample rate info from browserMax.addHandler("samplerate", (rate) => { sourceSampleRate = rate; Max.post(`๐๏ธ Source sample rate: ${rate}Hz`);});
// Output audio samples to Max signal outlets// This is called by a metro or qmetro in MaxMax.addHandler("tick", () => { // Read samples from ring buffer const leftOut = []; const rightOut = []; for (let i = 0; i < CHUNK_SIZE; i++) { if (samplesAvailable > 0) { leftOut.push(leftBuffer[readIndex]); rightOut.push(rightBuffer[readIndex]); readIndex = (readIndex + 1) % BUFFER_SIZE; samplesAvailable--; } else { // Underrun - output silence leftOut.push(0); rightOut.push(0); underruns++; } } // Output as lists to Max Max.outlet("left", leftOut); Max.outlet("right", rightOut);});
// Get buffer statusMax.addHandler("status", () => { Max.post(`๐ Buffer: ${samplesAvailable}/${BUFFER_SIZE} samples`); Max.post(`๐ Total received: ${totalSamplesReceived}`); Max.post(`๐ Underruns: ${underruns}`);});
// Clear bufferMax.addHandler("clear", () => { leftBuffer.fill(0); rightBuffer.fill(0); writeIndex = 0; readIndex = 0; samplesAvailable = 0; underruns = 0; Max.post("๐งน Buffer cleared");});
// Report readyMax.post("๐๏ธ AC Audio Bridge ready - waiting for samples from jweb");