Rain, fireplace, nature, ocean, night sounds while browsing – interaction‑aware atmosphere
// ==UserScript==
// @name Soundscape - Atmosphere Creator
// @namespace http://tampermonkey.net/
// @version 1.2
// @description Rain, fireplace, nature, ocean, night sounds while browsing – interaction‑aware atmosphere
// @author Mustafa Hakan
// @match *://*/*
// @grant none
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
const AC = window.AudioContext || window.webkitAudioContext;
let ctx, master, playing = false, currentProfile = 'rain', volume = 0.4;
let oscillators = [], gainNodes = [], noiseNodes = [];
let rainNodes = [], fireNodes = [], windNodes = [], birdNodes = [];
const PROFILES = {
rain: {
name: '🌧️ Rainstorm',
desc: 'Gentle rain and distant thunder',
color: '#4a90d9',
icon: '🌧️'
},
fireplace: {
name: '🔥 Fireplace',
desc: 'Crackling logs and warm crackle',
color: '#d94a1a',
icon: '🔥'
},
forest: {
name: '🌲 Deep Forest',
desc: 'Birdsong, wind and rustling leaves',
color: '#3d7a3d',
icon: '🌲'
},
ocean: {
name: '🌊 Ocean Waves',
desc: 'Waves on the shore and seagulls',
color: '#1a6b8a',
icon: '🌊'
},
night: {
name: '🦗 Night Symphony',
desc: 'Crickets, owl and distant wind',
color: '#2d1b69',
icon: '🦗'
},
thunder: {
name: '⛈️ Thunderstorm',
desc: 'Heavy rain and close lightning',
color: '#5a5a8a',
icon: '⛈️'
},
cafe: {
name: '☕ Café Ambience',
desc: 'Distant chatter, clinking cups, soft music',
color: '#8b6914',
icon: '☕'
}
};
function createNoise(duration) {
const size = ctx.sampleRate * duration;
const buffer = ctx.createBuffer(1, size, ctx.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < size; i++) {
data[i] = Math.random() * 2 - 1;
}
return buffer;
}
function createRain() {
const nodes = [];
for (let i = 0; i < 15; i++) {
const noise = createNoise(0.1);
const source = ctx.createBufferSource();
source.buffer = noise;
source.loop = true;
const filter = ctx.createBiquadFilter();
filter.type = 'bandpass';
filter.frequency.value = 2000 + Math.random() * 5000;
filter.Q.value = 0.5;
const gain = ctx.createGain();
gain.gain.value = 0.01 + Math.random() * 0.03;
source.connect(filter);
filter.connect(gain);
gain.connect(master);
source.start();
nodes.push({ source, filter, gain });
}
return nodes;
}
function createFire() {
const nodes = [];
for (let i = 0; i < 10; i++) {
const noise = createNoise(0.05);
const source = ctx.createBufferSource();
source.buffer = noise;
source.loop = true;
const filter = ctx.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.value = 200 + Math.random() * 400;
filter.Q.value = 1;
const gain = ctx.createGain();
gain.gain.value = 0.02 + Math.random() * 0.04;
gain.gain.setValueAtTime(gain.gain.value, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(gain.gain.value * (0.3 + Math.random() * 0.7), ctx.currentTime + 0.1 + Math.random() * 0.3);
source.connect(filter);
filter.connect(gain);
gain.connect(master);
source.start();
nodes.push({ source, filter, gain });
}
return nodes;
}
function createWind() {
const nodes = [];
for (let i = 0; i < 3; i++) {
const osc = ctx.createOscillator();
osc.type = 'sine';
osc.frequency.value = 100 + Math.random() * 300;
const gain = ctx.createGain();
gain.gain.value = 0.02;
gain.gain.setValueAtTime(0.01, ctx.currentTime);
gain.gain.linearRampToValueAtTime(0.04, ctx.currentTime + 2 + Math.random() * 3);
osc.connect(gain);
gain.connect(master);
osc.start();
nodes.push({ osc, gain });
}
return nodes;
}
function createBirds() {
const nodes = [];
for (let i = 0; i < 5; i++) {
const osc = ctx.createOscillator();
osc.type = 'sine';
osc.frequency.value = 2000 + Math.random() * 4000;
const gain = ctx.createGain();
gain.gain.value = 0;
const schedule = () => {
const now = ctx.currentTime;
const start = now + Math.random() * 5;
const dur = 0.05 + Math.random() * 0.2;
gain.gain.setValueAtTime(0, start);
gain.gain.linearRampToValueAtTime(0.015, start + dur * 0.3);
gain.gain.linearRampToValueAtTime(0, start + dur);
setTimeout(schedule, (start - now + dur) * 1000 + Math.random() * 3000);
};
schedule();
osc.connect(gain);
gain.connect(master);
osc.start();
nodes.push({ osc, gain });
}
return nodes;
}
function createThunder() {
const nodes = [];
const schedule = () => {
const now = ctx.currentTime;
const delay = 3 + Math.random() * 12;
const start = now + delay;
const noise = createNoise(1.5);
const source = ctx.createBufferSource();
source.buffer = noise;
const filter = ctx.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.setValueAtTime(80, start);
filter.frequency.linearRampToValueAtTime(3000, start + 0.1);
filter.frequency.linearRampToValueAtTime(60, start + 1.5);
const gain = ctx.createGain();
gain.gain.setValueAtTime(0, start);
gain.gain.linearRampToValueAtTime(0.15, start + 0.1);
gain.gain.exponentialRampToValueAtTime(0.001, start + 2);
source.connect(filter);
filter.connect(gain);
gain.connect(master);
source.start(start);
source.stop(start + 2);
setTimeout(schedule, delay * 1000 + 2000);
};
schedule();
return nodes;
}
function createCafe() {
const nodes = [];
const murmur = createNoise(2);
for (let i = 0; i < 3; i++) {
const source = ctx.createBufferSource();
source.buffer = murmur;
source.loop = true;
const filter = ctx.createBiquadFilter();
filter.type = 'bandpass';
filter.frequency.value = 300 + Math.random() * 700;
filter.Q.value = 0.3;
const gain = ctx.createGain();
gain.gain.value = 0.01;
source.connect(filter);
filter.connect(gain);
gain.connect(master);
source.start();
nodes.push({ source, filter, gain });
}
return nodes;
}
function clearAll() {
const allNodes = [...rainNodes, ...fireNodes, ...windNodes, ...birdNodes, ...noiseNodes];
allNodes.forEach(n => {
try { n.source?.stop(); } catch(e) {}
try { n.osc?.stop(); } catch(e) {}
try { n.gain?.disconnect(); } catch(e) {}
});
rainNodes = []; fireNodes = []; windNodes = []; birdNodes = []; noiseNodes = [];
oscillators.forEach(o => { try { o.stop(); } catch(e) {} });
oscillators = [];
gainNodes.forEach(g => { try { g.disconnect(); } catch(e) {} });
gainNodes = [];
}
function startProfile(profile) {
clearAll();
currentProfile = profile;
switch(profile) {
case 'rain':
rainNodes = createRain();
windNodes = createWind();
noiseNodes = createThunder();
break;
case 'fireplace':
fireNodes = createFire();
windNodes = createWind();
break;
case 'forest':
birdNodes = createBirds();
windNodes = createWind();
break;
case 'ocean':
noiseNodes = createThunder();
windNodes = createWind();
break;
case 'night':
birdNodes = createBirds();
windNodes = createWind();
break;
case 'thunder':
rainNodes = createRain();
noiseNodes = createThunder();
windNodes = createWind();
break;
case 'cafe':
noiseNodes = createCafe();
break;
}
}
function initAudio() {
if (ctx) return;
ctx = new AC();
master = ctx.createGain();
master.gain.value = volume;
master.connect(ctx.destination);
startProfile(currentProfile);
playing = true;
}
function setVolume(v) {
volume = v;
if (master) {
master.gain.setValueAtTime(v, ctx.currentTime);
master.gain.linearRampToValueAtTime(v, ctx.currentTime + 0.1);
}
}
// Scroll sound
let lastScroll = 0;
window.addEventListener('scroll', () => {
if (!playing) return;
const now = Date.now();
if (now - lastScroll < 50) return;
lastScroll = now;
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'sine';
osc.frequency.value = 200 + Math.random() * 600;
gain.gain.setValueAtTime(0.003, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + 0.15);
osc.connect(gain);
gain.connect(master);
osc.start();
osc.stop(ctx.currentTime + 0.15);
});
// Click sound
document.addEventListener('click', (e) => {
if (!playing || e.target.closest('#soundscape-panel')) return;
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'sine';
osc.frequency.value = 800 + Math.random() * 400;
gain.gain.setValueAtTime(0.01, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + 0.05);
osc.connect(gain);
gain.connect(master);
osc.start();
osc.stop(ctx.currentTime + 0.05);
});
// Typing sound
document.addEventListener('keydown', () => {
if (!playing) return;
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'sine';
osc.frequency.value = 400 + Math.random() * 300;
gain.gain.setValueAtTime(0.005, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + 0.03);
osc.connect(gain);
gain.connect(master);
osc.start();
osc.stop(ctx.currentTime + 0.03);
});
function createPanel() {
if (document.getElementById('soundscape-panel')) return;
const panel = document.createElement('div');
panel.id = 'soundscape-panel';
panel.style.cssText = `
position:fixed;bottom:30px;left:30px;background:rgba(15,15,15,0.95);
border-radius:20px;padding:20px;z-index:2147483646;min-width:260px;
border:1px solid rgba(255,255,255,0.1);font-family:system-ui;
box-shadow:0 20px 60px rgba(0,0,0,0.8);backdrop-filter:blur(20px);
transition:all 0.3s;animation:soundscapeIn 0.5s ease-out;
`;
panel.innerHTML = `
<style>
@keyframes soundscapeIn { from{opacity:0;transform:translateY(30px)} to{opacity:1;transform:translateY(0)} }
@keyframes soundscapePulse { 0%,100%{box-shadow:0 0 10px currentColor} 50%{box-shadow:0 0 25px currentColor} }
</style>
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:15px;">
<div style="color:#fff;font-weight:700;font-size:15px;">🎧 Soundscape</div>
<div style="display:flex;gap:5px;">
<button id="ss-min" style="background:none;border:none;color:#888;font-size:18px;cursor:pointer;">─</button>
<button id="ss-close" style="background:none;border:none;color:#888;font-size:18px;cursor:pointer;">✕</button>
</div>
</div>
<div id="ss-profiles" style="display:flex;flex-direction:column;gap:6px;"></div>
<div style="margin-top:12px;display:flex;align-items:center;gap:10px;">
<span style="color:#888;font-size:12px;">🔈</span>
<input type="range" id="ss-volume" min="0" max="100" value="${volume*100}" style="flex:1;accent-color:#4a90d9;">
<span style="color:#888;font-size:12px;">🔊</span>
</div>
<div id="ss-status" style="text-align:center;color:#666;font-size:10px;margin-top:8px;"></div>
`;
document.body.appendChild(panel);
const profileContainer = document.getElementById('ss-profiles');
Object.entries(PROFILES).forEach(([key, prof]) => {
const btn = document.createElement('div');
btn.style.cssText = `
padding:12px;border-radius:12px;cursor:pointer;display:flex;align-items:center;gap:10px;
background:${key===currentProfile?prof.color+'33':'rgba(255,255,255,0.03)'};
border:1px solid ${key===currentProfile?prof.color:'transparent'};
transition:all 0.2s;color:#ddd;
`;
btn.innerHTML = `
<span style="font-size:22px;">${prof.icon}</span>
<div>
<div style="font-weight:600;font-size:13px;">${prof.name}</div>
<div style="font-size:10px;color:#888;">${prof.desc}</div>
</div>
`;
btn.onmouseover = () => {
if (key !== currentProfile) btn.style.background = 'rgba(255,255,255,0.06)';
};
btn.onmouseout = () => {
if (key !== currentProfile) btn.style.background = 'rgba(255,255,255,0.03)';
};
btn.onclick = () => {
if (!ctx) initAudio();
startProfile(key);
document.querySelectorAll('#ss-profiles > div').forEach(b => {
b.style.background = 'rgba(255,255,255,0.03)';
b.style.borderColor = 'transparent';
});
btn.style.background = prof.color+'33';
btn.style.borderColor = prof.color;
document.getElementById('ss-status').textContent = '▶ ' + prof.name;
};
profileContainer.appendChild(btn);
});
document.getElementById('ss-volume').oninput = function() {
setVolume(this.value / 100);
};
document.getElementById('ss-close').onclick = () => {
panel.remove();
if (ctx) { ctx.close(); ctx = null; playing = false; clearAll(); }
};
document.getElementById('ss-min').onclick = () => {
const content = panel.querySelector('#ss-profiles');
const vol = panel.querySelector('div:last-child');
const status = document.getElementById('ss-status');
if (content.style.display === 'none') {
content.style.display = 'flex';
vol.style.display = 'flex';
status.style.display = 'block';
panel.style.minWidth = '260px';
} else {
content.style.display = 'none';
vol.style.display = 'none';
status.style.display = 'none';
panel.style.minWidth = 'auto';
panel.style.padding = '12px';
}
};
document.getElementById('ss-status').textContent = PROFILES[currentProfile] ? '▶ ' + PROFILES[currentProfile].name : '';
}
function addTrigger() {
const btn = document.createElement('div');
btn.id = 'soundscape-trigger';
btn.style.cssText = `
position:fixed;bottom:30px;left:30px;width:50px;height:50px;
background:rgba(15,15,15,0.9);border-radius:50%;display:flex;
align-items:center;justify-content:center;font-size:24px;
cursor:pointer;z-index:2147483645;border:1px solid rgba(255,255,255,0.2);
transition:all 0.3s;box-shadow:0 10px 30px rgba(0,0,0,0.5);
`;
btn.textContent = '🎧';
btn.title = 'Soundscape';
btn.onmouseover = () => { btn.style.transform = 'scale(1.1)'; btn.style.boxShadow = '0 15px 40px rgba(0,0,0,0.7)'; };
btn.onmouseout = () => { btn.style.transform = 'scale(1)'; btn.style.boxShadow = '0 10px 30px rgba(0,0,0,0.5)'; };
btn.onclick = () => {
if (document.getElementById('soundscape-panel')) {
document.getElementById('soundscape-panel').remove();
} else {
createPanel();
if (!ctx) initAudio();
}
};
document.body.appendChild(btn);
}
setTimeout(addTrigger, 1500);
console.log('🎧 Soundscape ready | Click the button at bottom left');
})();