Audio & Focus
AMBIENT SYNTHESIZER
100% browser-synthesized focus soundscapes. Mix pink noise, rain, ocean waves, fireplace crackle, and binaural beats offline without loading external audio clips.
Pink Noise
40%
Brownian Deep Noise
50%
Gentle Rain
60%
Ocean Waves
40%
Fireplace Crackle
30%
Alpha Binaural Beat (10Hz)
35%
Developer Reference
Core Algorithm & Standalone Script
Standalone, zero-dependency JavaScript implementation powering this tool. Free to inspect, copy, and build upon.
let audioCtx = null;
let masterGain = null;
let isPlaying = false;
let animId = null;
const btnMaster = document.getElementById('btn-master');
const canvas = document.getElementById('audio-visualizer');
const ctx = canvas.getContext('2d');
const sounds = {
pink: { node: null, gain: null, chk: 'chk-pink', vol: 'vol-pink', card: 'card-pink', txt: 'txt-vol-pink' },
brown: { node: null, gain: null, chk: 'chk-brown', vol: 'vol-brown', card: 'card-brown', txt: 'txt-vol-brown' },
rain: { node: null, gain: null, chk: 'chk-rain', vol: 'vol-rain', card: 'card-rain', txt: 'txt-vol-rain' },
waves: { node: null, gain: null, chk: 'chk-waves', vol: 'vol-waves', card: 'card-waves', txt: 'txt-vol-waves' },
fire: { node: null, gain: null, chk: 'chk-fire', vol: 'vol-fire', card: 'card-fire', txt: 'txt-vol-fire' },
binaural: { nodeLeft: null, nodeRight: null, gain: null, chk: 'chk-binaural', vol: 'vol-binaural', card: 'card-binaural', txt: 'txt-vol-binaural' }
};
function initAudio() {
if (audioCtx) return;
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
masterGain = audioCtx.createGain();
masterGain.gain.value = 1.0;
masterGain.connect(audioCtx.destination);
}
function createPinkNoiseNode() {
const bufferSize = audioCtx.sampleRate * 2;
const buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate);
const data = buffer.getChannelData(0);
let b0 = 0, b1 = 0, b2 = 0, b3 = 0, b4 = 0, b5 = 0, b6 = 0;
for (let i = 0; i < bufferSize; i++) {
let white = Math.random() * 2 - 1;
b0 = 0.99886 * b0 + white * 0.0555179;
b1 = 0.99332 * b1 + white * 0.0750759;
b2 = 0.96900 * b2 + white * 0.1538520;
b3 = 0.86650 * b3 + white * 0.3104856;
b4 = 0.55000 * b4 + white * 0.5329522;
b5 = -0.7616 * b5 - white * 0.0168980;
data[i] = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362;
data[i] *= 0.11;
b6 = white * 0.115926;
}
const noise = audioCtx.createBufferSource();
noise.buffer = buffer;
noise.loop = true;
return noise;
}
function createBrownNoiseNode() {
const bufferSize = audioCtx.sampleRate * 2;
const buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate);
const data = buffer.getChannelData(0);
let lastOutput = 0.0;
for (let i = 0; i < bufferSize; i++) {
let white = Math.random() * 2 - 1;
data[i] = (lastOutput + (0.02 * white)) / 1.02;
lastOutput = data[i];
data[i] *= 3.5;
}
const noise = audioCtx.createBufferSource();
noise.buffer = buffer;
noise.loop = true;
return noise;
}
function toggleMaster() {
initAudio();
if (audioCtx.state === 'suspended') {
audioCtx.resume();
}
isPlaying = !isPlaying;
if (isPlaying) {
btnMaster.innerHTML = '⏸️ Pause Soundscape';
btnMaster.style.background = '#ff5555';
startActiveSounds();
drawVisualizer();
} else {
btnMaster.innerHTML = '▶ Play Soundscape';
btnMaster.style.background = 'var(--accent)';
stopAllSounds();
cancelAnimationFrame(animId);
clearCanvas();
}
}
function startActiveSounds() {
if (!isPlaying) return;
Object.keys(sounds).forEach(key => {
const s = sounds[key];
const isChecked = document.getElementById(s.chk).checked;
const volVal = parseInt(document.getElementById(s.vol).value) / 100;
document.getElementById(s.card).classList.toggle('active', isChecked);
if (isChecked) {
if (!s.gain) {
s.gain = audioCtx.createGain();
s.gain.connect(masterGain);
}
s.gain.gain.setValueAtTime(volVal, audioCtx.currentTime);
if (key === 'pink' && !s.node) {
s.node = createPinkNoiseNode();
s.node.connect(s.gain);
s.node.start();
} else if (key === 'brown' && !s.node) {
s.node = createBrownNoiseNode();
s.node.connect(s.gain);
s.node.start();
} else if (key === 'rain' && !s.node) {
s.node = createPinkNoiseNode();
const filter = audioCtx.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.value = 1200;
s.node.connect(filter);
filter.connect(s.gain);
s.node.start();
} else if (key === 'waves' && !s.node) {
s.node = createBrownNoiseNode();
const filter = audioCtx.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.value = 400;
s.node.connect(filter);
filter.connect(s.gain);
s.node.start();
} else if (key === 'binaural' && !s.nodeLeft) {
s.nodeLeft = audioCtx.createOscillator();
s.nodeRight = audioCtx.createOscillator();
s.nodeLeft.frequency.value = 200;
s.nodeRight.frequency.value = 210; // 10Hz Alpha difference
const merger = audioCtx.createChannelMerger(2);
s.nodeLeft.connect(merger, 0, 0);
s.nodeRight.connect(merger, 0, 1);
merger.connect(s.gain);
s.nodeLeft.start();
s.nodeRight.start();
}
} else {
stopSoundKey(key);
}
});
}
function stopSoundKey(key) {
const s = sounds[key];
if (s.node) {
try { s.node.stop(); s.node.disconnect(); } catch (e) {}
s.node = null;
}
if (s.nodeLeft) {
try { s.nodeLeft.stop(); s.nodeRight.stop(); s.nodeLeft.disconnect(); s.nodeRight.disconnect(); } catch (e) {}
s.nodeLeft = null; s.nodeRight = null;
}
}
function stopAllSounds() {
Object.keys(sounds).forEach(key => stopSoundKey(key));
}
function clearCanvas() {
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
ctx.fillStyle = '#08080a';
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
function drawVisualizer() {
if (!isPlaying) return;
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
const w = canvas.width;
const h = canvas.height;
ctx.fillStyle = '#08080a';
ctx.fillRect(0, 0, w, h);
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = '#ff2200';
const sliceWidth = w / 60;
let x = 0;
for (let i = 0; i < 60; i++) {
const v = Math.sin(Date.now() * 0.005 + i * 0.3) * (h * 0.3);
const y = (h / 2) + v;
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
x += sliceWidth;
}
ctx.stroke();
animId = requestAnimationFrame(drawVisualizer);
}
btnMaster.addEventListener('click', toggleMaster);
Object.keys(sounds).forEach(key => {
const s = sounds[key];
const chk = document.getElementById(s.chk);
const vol = document.getElementById(s.vol);
const txt = document.getElementById(s.txt);
chk.addEventListener('change', () => {
startActiveSounds();
});
vol.addEventListener('input', () => {
txt.textContent = vol.value + '%';
if (s.gain) {
s.gain.gain.setValueAtTime(parseInt(vol.value) / 100, audioCtx.currentTime);
}
});
});
document.querySelectorAll('.btn-preset').forEach(btn => {
btn.addEventListener('click', () => {
const p = btn.dataset.preset;
Object.keys(sounds).forEach(k => document.getElementById(sounds[k].chk).checked = false);
if (p === 'focus') {
document.getElementById('chk-pink').checked = true;
document.getElementById('chk-binaural').checked = true;
} else if (p === 'storm') {
document.getElementById('chk-rain').checked = true;
document.getElementById('chk-brown').checked = true;
} else if (p === 'ocean') {
document.getElementById('chk-waves').checked = true;
document.getElementById('chk-pink').checked = true;
} else if (p === 'zen') {
document.getElementById('chk-binaural').checked = true;
document.getElementById('chk-brown').checked = true;
}
if (isPlaying) startActiveSounds();
});
});
clearCanvas();