Scaper-like Atmosphere Generator
by soulwire
HTML
<h1>Scaper-like Atmosphere Generator</h1>
<input type="file" id="fileInput" accept="audio/*">
<button id="startBtn">Start Atmosphere</button>
<label>Grain Size (ms): <input type="range" id="grainSize" min="20" max="500" value="200"></label>
<label>Density (grains/sec): <input type="range" id="density" min="1" max="30" value="10"></label>
<label>Filter Cutoff (Hz): <input type="range" id="cutoff" min="200" max="12000" value="1000"></label>
<canvas id="visualizer" width="800" height="200"></canvas>
CSS
body { font-family: sans-serif; background: #111; color: #eee; text-align: center; padding: 2rem; }
input, button, label { margin: 1rem; font-size: 1.2rem; }
canvas { display: block; margin: 2rem auto; background: #000; }
JavaScript
const ctx = new (window.AudioContext || window.webkitAudioContext)();
let buffer = null;
let isPlaying = false;
let intervalId = null;
const grainSizeSlider = document.getElementById('grainSize');
const densitySlider = document.getElementById('density');
const cutoffSlider = document.getElementById('cutoff');
const analyser = ctx.createAnalyser();
const canvas = document.getElementById('visualizer');
const canvasCtx = canvas.getContext('2d');
const reverb = ctx.createConvolver();
const delay = ctx.createDelay();
delay.delayTime.value = 0.4;
const feedback = ctx.createGain();
feedback.gain.value = 0.5;
delay.connect(feedback).connect(delay);
// Load simple impulse response for reverb
fetch('https://cdn.jsdelivr.net/gh/mdn/webaudio-examples/audio-buffer/concert-crowd.ogg')
.then(response => response.arrayBuffer())
.then(data => ctx.decodeAudioData(data))
.then(decoded => reverb.buffer = decoded);
document.getElementById('fileInput').addEventListener('change', async (e) => {
const file = e.target.files[0];
const arrayBuffer = await file.arrayBuffer();
buffer = await ctx.decodeAudioData(arrayBuffer);
});
document.getElementById('startBtn').addEventListener('click', () => {
if (!buffer || isPlaying) return;
isPlaying = true;
const interval = 1000 / parseInt(densitySlider.value);
intervalId = setInterval(() => playGrain(buffer), interval);
visualize();
});
function playGrain(buffer) {
const grainSize = parseInt(grainSizeSlider.value) / 1000;
const offset = Math.random() * (buffer.duration - grainSize);
const source = ctx.createBufferSource();
source.buffer = buffer;
const gain = ctx.createGain();
gain.gain.setValueAtTime(0, ctx.currentTime);
gain.gain.linearRampToValueAtTime(1, ctx.currentTime + 0.01);
...