UNPKG

rubberband-wasm

Version:

WebAssembly version of the Rubber Band Library (high quality software library for audio time-stretching and pitch-shifting)

195 lines (163 loc) 5.48 kB
const audioCtx = new window.AudioContext(); let inputAudioBuffer; let worker = null; let processing = false; function processOnWorker(msg) { processing = true; const workerInstance = new Worker(location.href + "/worker.js"); return new Promise((resolve) => { workerInstance.onmessage = function (e) { console.log("Message received from worker", e.data); if ("ready" in e.data) { worker = workerInstance; worker.postMessage(msg); } if ("status" in e.data) { setStatus(e.data.status); } if ("progress" in e.data) { const progress = e.data.progress; const str = `${lastStatusMsg} (${(progress * 100).toFixed(0)}%)`; document.getElementById("status").innerText = str; } if ("channelBuffers" in e.data) { const { channelBuffers } = e.data; const buffer = audioCtx.createBuffer(inputAudioBuffer.numberOfChannels, channelBuffers[0].length, inputAudioBuffer.sampleRate); channelBuffers.forEach((buf, i) => buffer.copyToChannel(buf, i)); processing = false; resolve(buffer); } if ("wavData" in e.data) { const { wavData } = e.data; processing = false; resolve(wavData); } }; }); } function setEnabledFields(fields) { const list = ["file", "pitch", "tempo", "processAndExport", "processAndPlay", "playOriginal", "stop"]; list.forEach((field) => { document.getElementById(field).disabled = !fields.includes(field); }); if (window.AudioEncoder === undefined) { document.getElementById("processAndExport").disabled = true; } } setEnabledFields(["file"]); let lastStatusMsg = ""; function setStatus(msg) { lastStatusMsg = msg; document.getElementById("status").innerText = msg; } function onNewFile(fileEl) { setEnabledFields([]); setStatus("Loading file..."); const file = fileEl.files[0]; const reader = new FileReader(); reader.onload = (event) => { const arrBuf = event.target.result; source = audioCtx.createBufferSource(); audioCtx.decodeAudioData( arrBuf, (audioBuffer) => { setEnabledFields(["pitch", "tempo", "processAndExport", "processAndPlay", "playOriginal"]); setStatus(""); inputAudioBuffer = audioBuffer; }, (e) => { setEnabledFields(["file"]); setStatus("Error with decoding audio data"); } ); }; reader.readAsArrayBuffer(file); } function onChangePitch(rangeEl) { document.getElementById("pitchLabel").innerText = Number(rangeEl.value); } function onChangeTempo(rangeEl) { document.getElementById("tempoLabel").innerText = `${(Number(rangeEl.value) * 100).toFixed(0)}%`; } let currentlyPlayingSource = null; function playBuffer(audioBuffer) { if (currentlyPlayingSource) { currentlyPlayingSource.stop(); currentlyPlayingSource = null; } if (audioBuffer) { currentlyPlayingSource = audioCtx.createBufferSource(); currentlyPlayingSource.buffer = audioBuffer; currentlyPlayingSource.connect(audioCtx.destination); currentlyPlayingSource.loop = true; currentlyPlayingSource.start(); document.getElementById("stop").disabled = false; } } function makeWorkerMessage({ askWav } = {}) { const tempo = Number(document.getElementById("tempo").value); const pitch = Number(document.getElementById("pitch").value); const pitchSemitones = Math.pow(2, pitch / 12); const channelBuffers = []; for (let channel = 0; channel < inputAudioBuffer.numberOfChannels; channel++) { channelBuffers.push(inputAudioBuffer.getChannelData(channel)); } return { channelBuffers, sampleRate: inputAudioBuffer.sampleRate, pitch: pitchSemitones, tempo: 1 / tempo, ...(askWav ? { wav: true } : {}), }; } async function processAndPlay() { setEnabledFields(["stop"]); setStatus("Initializing..."); const start = performance.now(); const msg = makeWorkerMessage(); const audioBuffer = await processOnWorker(msg); const timeMs = performance.now() - start; setStatus(`Finished processing in ${(timeMs / 1000).toFixed(3)} seconds.`); playBuffer(audioBuffer); } async function processAndExport() { setEnabledFields(["stop"]); setStatus("Initializing..."); const start = performance.now(); const msg = makeWorkerMessage({ askWav: true }); const wavBuffer = await processOnWorker(msg); const timeMs = performance.now() - start; const filename = "audio.wav"; const blob = new Blob([wavBuffer], { type: "audio/wav" }); const link = document.createElement("a"); link.download = filename; link.href = window.URL.createObjectURL(blob); document.body.appendChild(link); link.click(); setTimeout(() => { document.body.removeChild(link); window.URL.revokeObjectURL(link.href); }, 100); setStatus(`Finished processing in ${(timeMs / 1000).toFixed(3)} seconds.`); setEnabledFields(["pitch", "tempo", "processAndExport", "processAndPlay", "playOriginal"]); } function playOriginal() { setStatus("Playing..."); setEnabledFields(["stop"]); playBuffer(inputAudioBuffer); } async function stop() { if (processing) { setEnabledFields([]); processing = false; if (worker) { worker.terminate(); } } setStatus(""); setEnabledFields(["pitch", "tempo", "processAndExport", "processAndPlay", "playOriginal"]); if (currentlyPlayingSource) { currentlyPlayingSource.stop(); currentlyPlayingSource = null; } }