natural-sounds / index.html
Ashfkhtk11's picture
Sound Options (10 sounds): White Noise β†’ sounds/white_noise.wav Pink Noise β†’ sounds/pink_noise.wav Brown Noise β†’ sounds/brown_noise.wav Rain β†’ sounds/rain.wav Ocean Waves β†’ sounds/ocean_waves.wav Thunderstorm β†’ sounds/thunderstorm.wav Fireplace β†’ sounds/fireplace.wav Wind β†’ sounds/wind.wav Night Crickets β†’ sounds/night_crickets.wav Forest Birds β†’ sounds/forest_birds.wav Each sound must have its own volume slider (0–100%), and multiple sounds should be able to play together (mixing). Sleep Timer: Dropdown with options: Off, 15 min, 30 min, 45 min, 60 min, 90 min. After the timer ends, fade out and stop all sounds. Presets (quick mixes): Rainy Night β†’ Rain + Thunderstorm + Night Crickets Ocean Breeze β†’ Ocean Waves + Wind + Forest Birds Deep Sleep β†’ Brown Noise + Fireplace UI Layout: A dropdown for sleep timer at the top-left. Preset buttons (Rainy Night, Ocean Breeze, Deep Sleep) at the top. Below that, show the 10 sound options in a grid layout (2 rows Γ— 5 columns). Each sound block should have: A round placeholder icon (or circle) The sound name A volume slider below Folder for Sounds: Create a folder named sounds in the Space. All sound files must be placed inside this folder with the exact filenames listed above. The app should automatically load these files when it runs. Technical details: Use Python + Gradio. Use pydub (or another library) to mix sounds based on slider volumes. Export the mixed audio as a temporary .wav and play it in a Gradio Audio player. Should run fully in browser on Hugging Face free tier. Design style: Clean, minimal, modern look. Light blue accent for sliders and buttons (like in the screenshot). Easy to use, calming interface. - Initial Deployment
44eb8f4 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Soundscape Mixer</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
/* Custom styles for sliders */
input[type="range"] {
-webkit-appearance: none;
height: 6px;
border-radius: 3px;
background: #e2e8f0;
outline: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 18px;
height: 18px;
border-radius: 50%;
background: #3b82f6;
cursor: pointer;
}
input[type="range"]::-moz-range-thumb {
width: 18px;
height: 18px;
border-radius: 50%;
background: #3b82f6;
cursor: pointer;
}
/* Fade animation */
@keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
.fade-out {
animation: fadeOut 2s ease-in-out forwards;
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
}
::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}
</style>
</head>
<body class="bg-gray-50 min-h-screen font-sans">
<div class="container mx-auto px-4 py-8 max-w-6xl">
<!-- Header -->
<header class="mb-8">
<h1 class="text-3xl font-bold text-gray-800 mb-2">Soundscape Mixer</h1>
<p class="text-gray-600">Create your perfect ambient sound mix</p>
</header>
<!-- Controls Section -->
<div class="bg-white rounded-xl shadow-md p-6 mb-8">
<div class="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 mb-6">
<!-- Timer Dropdown -->
<div class="w-full md:w-auto">
<label for="timer" class="block text-sm font-medium text-gray-700 mb-1">Sleep Timer</label>
<select id="timer" class="w-full md:w-48 px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<option value="0">Off</option>
<option value="15">15 minutes</option>
<option value="30">30 minutes</option>
<option value="45">45 minutes</option>
<option value="60">60 minutes</option>
<option value="90">90 minutes</option>
</select>
</div>
<!-- Preset Buttons -->
<div class="w-full md:w-auto flex flex-col md:flex-row gap-3">
<button onclick="loadPreset('rainyNight')" class="px-4 py-2 bg-blue-50 text-blue-600 rounded-lg hover:bg-blue-100 transition-colors flex items-center gap-2">
<i class="fas fa-cloud-rain"></i>
<span>Rainy Night</span>
</button>
<button onclick="loadPreset('oceanBreeze')" class="px-4 py-2 bg-blue-50 text-blue-600 rounded-lg hover:bg-blue-100 transition-colors flex items-center gap-2">
<i class="fas fa-water"></i>
<span>Ocean Breeze</span>
</button>
<button onclick="loadPreset('deepSleep')" class="px-4 py-2 bg-blue-50 text-blue-600 rounded-lg hover:bg-blue-100 transition-colors flex items-center gap-2">
<i class="fas fa-moon"></i>
<span>Deep Sleep</span>
</button>
</div>
</div>
<!-- Timer Display -->
<div id="timerDisplay" class="hidden text-center py-2 px-4 bg-blue-50 text-blue-600 rounded-lg mb-4">
<span id="timerText">Timer: 15:00</span>
</div>
<!-- Sound Grid -->
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4">
<!-- Sound 1: White Noise -->
<div class="sound-card bg-white p-4 rounded-lg border border-gray-200 hover:shadow-md transition-shadow">
<div class="flex flex-col items-center">
<div class="w-16 h-16 rounded-full bg-blue-100 flex items-center justify-center mb-3">
<i class="fas fa-wave-square text-blue-500 text-xl"></i>
</div>
<h3 class="font-medium text-gray-800 mb-2">White Noise</h3>
<input type="range" min="0" max="100" value="0" class="w-full sound-slider" data-sound="white_noise">
<div class="flex justify-between w-full mt-1 text-xs text-gray-500">
<span>0%</span>
<span id="white_noise-value">0%</span>
</div>
</div>
</div>
<!-- Sound 2: Pink Noise -->
<div class="sound-card bg-white p-4 rounded-lg border border-gray-200 hover:shadow-md transition-shadow">
<div class="flex flex-col items-center">
<div class="w-16 h-16 rounded-full bg-blue-100 flex items-center justify-center mb-3">
<i class="fas fa-wave-square text-blue-500 text-xl"></i>
</div>
<h3 class="font-medium text-gray-800 mb-2">Pink Noise</h3>
<input type="range" min="0" max="100" value="0" class="w-full sound-slider" data-sound="pink_noise">
<div class="flex justify-between w-full mt-1 text-xs text-gray-500">
<span>0%</span>
<span id="pink_noise-value">0%</span>
</div>
</div>
</div>
<!-- Sound 3: Brown Noise -->
<div class="sound-card bg-white p-4 rounded-lg border border-gray-200 hover:shadow-md transition-shadow">
<div class="flex flex-col items-center">
<div class="w-16 h-16 rounded-full bg-blue-100 flex items-center justify-center mb-3">
<i class="fas fa-wave-square text-blue-500 text-xl"></i>
</div>
<h3 class="font-medium text-gray-800 mb-2">Brown Noise</h3>
<input type="range" min="0" max="100" value="0" class="w-full sound-slider" data-sound="brown_noise">
<div class="flex justify-between w-full mt-1 text-xs text-gray-500">
<span>0%</span>
<span id="brown_noise-value">0%</span>
</div>
</div>
</div>
<!-- Sound 4: Rain -->
<div class="sound-card bg-white p-4 rounded-lg border border-gray-200 hover:shadow-md transition-shadow">
<div class="flex flex-col items-center">
<div class="w-16 h-16 rounded-full bg-blue-100 flex items-center justify-center mb-3">
<i class="fas fa-cloud-rain text-blue-500 text-xl"></i>
</div>
<h3 class="font-medium text-gray-800 mb-2">Rain</h3>
<input type="range" min="0" max="100" value="0" class="w-full sound-slider" data-sound="rain">
<div class="flex justify-between w-full mt-1 text-xs text-gray-500">
<span>0%</span>
<span id="rain-value">0%</span>
</div>
</div>
</div>
<!-- Sound 5: Ocean Waves -->
<div class="sound-card bg-white p-4 rounded-lg border border-gray-200 hover:shadow-md transition-shadow">
<div class="flex flex-col items-center">
<div class="w-16 h-16 rounded-full bg-blue-100 flex items-center justify-center mb-3">
<i class="fas fa-water text-blue-500 text-xl"></i>
</div>
<h3 class="font-medium text-gray-800 mb-2">Ocean Waves</h3>
<input type="range" min="0" max="100" value="0" class="w-full sound-slider" data-sound="ocean_waves">
<div class="flex justify-between w-full mt-1 text-xs text-gray-500">
<span>0%</span>
<span id="ocean_waves-value">0%</span>
</div>
</div>
</div>
<!-- Sound 6: Thunderstorm -->
<div class="sound-card bg-white p-4 rounded-lg border border-gray-200 hover:shadow-md transition-shadow">
<div class="flex flex-col items-center">
<div class="w-16 h-16 rounded-full bg-blue-100 flex items-center justify-center mb-3">
<i class="fas fa-bolt text-blue-500 text-xl"></i>
</div>
<h3 class="font-medium text-gray-800 mb-2">Thunderstorm</h3>
<input type="range" min="0" max="100" value="0" class="w-full sound-slider" data-sound="thunderstorm">
<div class="flex justify-between w-full mt-1 text-xs text-gray-500">
<span>0%</span>
<span id="thunderstorm-value">0%</span>
</div>
</div>
</div>
<!-- Sound 7: Fireplace -->
<div class="sound-card bg-white p-4 rounded-lg border border-gray-200 hover:shadow-md transition-shadow">
<div class="flex flex-col items-center">
<div class="w-16 h-16 rounded-full bg-blue-100 flex items-center justify-center mb-3">
<i class="fas fa-fire text-blue-500 text-xl"></i>
</div>
<h3 class="font-medium text-gray-800 mb-2">Fireplace</h3>
<input type="range" min="0" max="100" value="0" class="w-full sound-slider" data-sound="fireplace">
<div class="flex justify-between w-full mt-1 text-xs text-gray-500">
<span>0%</span>
<span id="fireplace-value">0%</span>
</div>
</div>
</div>
<!-- Sound 8: Wind -->
<div class="sound-card bg-white p-4 rounded-lg border border-gray-200 hover:shadow-md transition-shadow">
<div class="flex flex-col items-center">
<div class="w-16 h-16 rounded-full bg-blue-100 flex items-center justify-center mb-3">
<i class="fas fa-wind text-blue-500 text-xl"></i>
</div>
<h3 class="font-medium text-gray-800 mb-2">Wind</h3>
<input type="range" min="0" max="100" value="0" class="w-full sound-slider" data-sound="wind">
<div class="flex justify-between w-full mt-1 text-xs text-gray-500">
<span>0%</span>
<span id="wind-value">0%</span>
</div>
</div>
</div>
<!-- Sound 9: Night Crickets -->
<div class="sound-card bg-white p-4 rounded-lg border border-gray-200 hover:shadow-md transition-shadow">
<div class="flex flex-col items-center">
<div class="w-16 h-16 rounded-full bg-blue-100 flex items-center justify-center mb-3">
<i class="fas fa-bug text-blue-500 text-xl"></i>
</div>
<h3 class="font-medium text-gray-800 mb-2">Night Crickets</h3>
<input type="range" min="0" max="100" value="0" class="w-full sound-slider" data-sound="night_crickets">
<div class="flex justify-between w-full mt-1 text-xs text-gray-500">
<span>0%</span>
<span id="night_crickets-value">0%</span>
</div>
</div>
</div>
<!-- Sound 10: Forest Birds -->
<div class="sound-card bg-white p-4 rounded-lg border border-gray-200 hover:shadow-md transition-shadow">
<div class="flex flex-col items-center">
<div class="w-16 h-16 rounded-full bg-blue-100 flex items-center justify-center mb-3">
<i class="fas fa-dove text-blue-500 text-xl"></i>
</div>
<h3 class="font-medium text-gray-800 mb-2">Forest Birds</h3>
<input type="range" min="0" max="100" value="0" class="w-full sound-slider" data-sound="forest_birds">
<div class="flex justify-between w-full mt-1 text-xs text-gray-500">
<span>0%</span>
<span id="forest_birds-value">0%</span>
</div>
</div>
</div>
</div>
</div>
<!-- Global Controls -->
<div class="flex flex-col sm:flex-row gap-4 justify-center items-center">
<button id="playButton" onclick="togglePlayback()" class="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors flex items-center gap-2">
<i class="fas fa-play"></i>
<span>Play All</span>
</button>
<button onclick="stopAllSounds()" class="px-6 py-3 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 transition-colors flex items-center gap-2">
<i class="fas fa-stop"></i>
<span>Stop All</span>
</button>
<button onclick="resetAllSliders()" class="px-6 py-3 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 transition-colors flex items-center gap-2">
<i class="fas fa-undo"></i>
<span>Reset All</span>
</button>
</div>
</div>
<script>
// Audio context and elements
let audioContext;
let sounds = {};
let isPlaying = false;
let timerInterval;
let remainingTime = 0;
let gainNodes = {};
// Initialize audio context on first user interaction
function initAudioContext() {
if (!audioContext) {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
// Create gain nodes for each sound
const soundNames = [
'white_noise', 'pink_noise', 'brown_noise', 'rain', 'ocean_waves',
'thunderstorm', 'fireplace', 'wind', 'night_crickets', 'forest_birds'
];
soundNames.forEach(sound => {
gainNodes[sound] = audioContext.createGain();
gainNodes[sound].gain.value = 0;
gainNodes[sound].connect(audioContext.destination);
});
}
}
// Load audio files
function loadSounds() {
const soundFiles = {
white_noise: 'sounds/white_noise.wav',
pink_noise: 'sounds/pink_noise.wav',
brown_noise: 'sounds/brown_noise.wav',
rain: 'sounds/rain.wav',
ocean_waves: 'sounds/ocean_waves.wav',
thunderstorm: 'sounds/thunderstorm.wav',
fireplace: 'sounds/fireplace.wav',
wind: 'sounds/wind.wav',
night_crickets: 'sounds/night_crickets.wav',
forest_birds: 'sounds/forest_birds.wav'
};
Object.keys(soundFiles).forEach(sound => {
fetch(soundFiles[sound])
.then(response => response.arrayBuffer())
.then(arrayBuffer => audioContext.decodeAudioData(arrayBuffer))
.then(audioBuffer => {
sounds[sound] = audioBuffer;
})
.catch(error => {
console.error(`Error loading sound ${sound}:`, error);
});
});
}
// Play a sound with volume control
function playSound(soundName, volume) {
if (!sounds[soundName] || !audioContext) return;
const source = audioContext.createBufferSource();
source.buffer = sounds[soundName];
source.loop = true;
// Connect to existing gain node
source.connect(gainNodes[soundName]);
// Set volume (0-1 range)
gainNodes[soundName].gain.value = volume / 100;
source.start();
return source;
}
// Update volume for a sound
function updateVolume(soundName, volume) {
if (gainNodes[soundName]) {
gainNodes[soundName].gain.value = volume / 100;
}
document.getElementById(`${soundName}-value`).textContent = `${volume}%`;
}
// Toggle playback of all sounds
function togglePlayback() {
initAudioContext();
if (isPlaying) {
stopAllSounds();
} else {
startAllSounds();
}
}
// Start all sounds based on slider positions
function startAllSounds() {
const sliders = document.querySelectorAll('.sound-slider');
sliders.forEach(slider => {
const soundName = slider.dataset.sound;
const volume = parseInt(slider.value);
if (volume > 0) {
updateVolume(soundName, volume);
}
});
isPlaying = true;
document.getElementById('playButton').innerHTML = '<i class="fas fa-pause"></i><span>Pause All</span>';
}
// Stop all sounds
function stopAllSounds() {
Object.keys(gainNodes).forEach(sound => {
gainNodes[sound].gain.value = 0;
});
isPlaying = false;
document.getElementById('playButton').innerHTML = '<i class="fas fa-play"></i><span>Play All</span>';
// Also stop any timer
stopTimer();
}
// Reset all sliders to 0
function resetAllSliders() {
const sliders = document.querySelectorAll('.sound-slider');
sliders.forEach(slider => {
slider.value = 0;
const soundName = slider.dataset.sound;
document.getElementById(`${soundName}-value`).textContent = '0%';
updateVolume(soundName, 0);
});
}
// Load a preset
function loadPreset(presetName) {
const presets = {
rainyNight: {
rain: 70,
thunderstorm: 40,
night_crickets: 60
},
oceanBreeze: {
ocean_waves: 80,
wind: 50,
forest_birds: 40
},
deepSleep: {
brown_noise: 60,
fireplace: 50
}
};
// First reset all sliders
resetAllSliders();
// Then set the preset values
const preset = presets[presetName];
Object.keys(preset).forEach(sound => {
const slider = document.querySelector(`.sound-slider[data-sound="${sound}"]`);
if (slider) {
slider.value = preset[sound];
updateVolume(sound, preset[sound]);
}
});
// Auto-play if not already playing
if (!isPlaying) {
startAllSounds();
}
}
// Timer functions
function startTimer(minutes) {
stopTimer(); // Clear any existing timer
remainingTime = minutes * 60; // Convert to seconds
updateTimerDisplay();
document.getElementById('timerDisplay').classList.remove('hidden');
timerInterval = setInterval(() => {
remainingTime--;
updateTimerDisplay();
if (remainingTime <= 0) {
fadeOutAndStop();
stopTimer();
}
}, 1000);
}
function stopTimer() {
clearInterval(timerInterval);
document.getElementById('timerDisplay').classList.add('hidden');
}
function updateTimerDisplay() {
const minutes = Math.floor(remainingTime / 60);
const seconds = remainingTime % 60;
document.getElementById('timerText').textContent =
`Timer: ${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
}
function fadeOutAndStop() {
// Apply fade-out class to all sound cards
document.querySelectorAll('.sound-card').forEach(card => {
card.classList.add('fade-out');
});
// Gradually reduce volume over 2 seconds
const fadeDuration = 2;
const startTime = audioContext.currentTime;
Object.keys(gainNodes).forEach(sound => {
const gainNode = gainNodes[sound];
gainNode.gain.setValueAtTime(gainNode.gain.value, startTime);
gainNode.gain.linearRampToValueAtTime(0, startTime + fadeDuration);
});
// Stop completely after fade
setTimeout(() => {
stopAllSounds();
document.querySelectorAll('.sound-card').forEach(card => {
card.classList.remove('fade-out');
});
}, fadeDuration * 1000);
}
// Event listeners
document.addEventListener('DOMContentLoaded', () => {
// Slider change events
document.querySelectorAll('.sound-slider').forEach(slider => {
slider.addEventListener('input', function() {
const soundName = this.dataset.sound;
const volume = parseInt(this.value);
updateVolume(soundName, volume);
// Auto-play if not already playing and volume > 0
if (volume > 0 && !isPlaying) {
startAllSounds();
}
});
});
// Timer dropdown change
document.getElementById('timer').addEventListener('change', function() {
const minutes = parseInt(this.value);
if (minutes > 0) {
startTimer(minutes);
} else {
stopTimer();
}
});
// Initialize audio on first interaction
document.body.addEventListener('click', function initOnFirstInteraction() {
initAudioContext();
loadSounds();
document.body.removeEventListener('click', initOnFirstInteraction);
}, { once: true });
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Ashfkhtk11/natural-sounds" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>