Scaper Web Audio Particle Engine
by soulwire
HTML
<div id="ui">
<label><input type="file" id="fileInput" multiple /> Load Audio</label>
<label
>Mode
<select id="mode">
<option value="ambient">Ambient</option>
<option value="drone">Drone</option>
<option value="pulse">Pulse</option>
<option value="cloud">Cloud</option>
</select>
</label>
<label
>Preset
<select id="presetSelect">
<option value="default">Default</option>
<option value="spaceEcho">Space Echo</option>
<option value="whispers">Whispers</option>
<option value="glimmer">Glimmer</option>
</select>
</label>
<label
>Pitch Shift
<input type="range" id="pitchShift" min="0.5" max="2" step="0.01" value="1"
/></label>
<label>Enable LFO <input type="checkbox" id="enableLfo" /></label>
<label
>Grain Size
<input
type="range"
id="grainSizeSlider"
min="0.05"
max="1"
step="0.01"
value="0.2"
/></label>
<label
>Variation
<input
type="range"
id="variationSlider"
min="0"
max="1"
step="0.01"
value="0.3"
/></label>
<label
>Density
<input
type="range"
id="densitySlider"
min="1"
max="100"
step="1"
value="40"
/></label>
<label
>Reverb Mix
<input
type="range"
id="reverbMixSlider"
min="0"
max="1"
step="0.01"
value="0.5"
/></label>
<label
>Delay Feedback
<input
type="range"
id="delayFeedbackSlider"
min="0"
max="1"
step="0.01"
value="0.4"
/></label>
<button id="startExport">Export WAV</button>
<a id="downloadLink" style="display: none" download="scaper.wav"
>Download Recording</a
>
</div>
<canvas id="canvas"></canvas>
CSS
body {
margin: 0;
font-family: sans-serif;
background: #000;
color: white;
overflow: hidden;
}
#ui {
position: absolute;
top: 10px;
left: 10px;
background: rgba(0, 0, 0, 0.6);
padding: 10px;
border-radius: 8px;
z-index: 10;
}
label {
display: block;
margin: 4px 0;
font-size: 12px;
}
input,
select {
width: 100%;
}
canvas {
display: block;
}
JavaScript
const canvas = document.getElementById("canvas");
const ctx2d = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const masterGain = audioCtx.createGain();
const convolver = audioCtx.createConvolver();
const globalDelay = audioCtx.createDelay();
const feedback = audioCtx.createGain();
const compressor = audioCtx.createDynamicsCompressor();
const analyser = audioCtx.createAnalyser();
analyser.fftSize = 2048;
analyser.smoothingTimeConstant = 0.9;
globalDelay.delayTime.value = 0.4;
feedback.gain.value = 0.4;
globalDelay.connect(feedback).connect(globalDelay);
convolver.connect(masterGain);
globalDelay.connect(masterGain);
masterGain.connect(compressor).connect(audioCtx.destination);
masterGain.connect(analyser);
analyser.connect(compressor);
let sources = [];
const particleSystem = [];
const colors = ["#77f", "#f77", "#7f7", "#ff7", "#7ff"];
const fileInput = document.getElementById("fileInput");
const pitchSlider = document.getElementById("pitchShift");
const enableLfo = document.getElementById("enableLfo");
const presetSelect = document.getElementById("presetSelect");
const modeSelect = document.getElementById("mode");
const grainSizeSlider = document.getElementById("grainSizeSlider");
const variationSlider = document.getElementById("variationSlider");
const densitySlider = document.getElementById("densitySlider");
const reverbMixSlider = document.getElementById("reverbMixSlider");
const delayFeedbackSlider = document.getElementById("delayFeedbackSlider");
const startExportBtn = document.getElementById("startExport");
const downloadLink = document.getElementById("downloadLink");
fileInput.addEventListener("change", async (e) => {
for (const file of e.target.files) {
const buffer = await file.arrayBuffer();
const audioBuffer = await audioCtx.decodeAudioData(buffer);
const...