Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save ShaneBrumback/667daa075f76f571b2d9f06aa6d42c83 to your computer and use it in GitHub Desktop.

Select an option

Save ShaneBrumback/667daa075f76f571b2d9f06aa6d42c83 to your computer and use it in GitHub Desktop.
interactive-audio-sphere-particle-systems.html
interactive-audio-sphere-particle-systems.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Interactive Audio Driven Sphere Particle Systems - Three.js Example</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { margin: 0; overflow: hidden; background: #0a0b0f; }
#startButton, #stopButton {
position: fixed; top: 20px; left: 50%; transform: translateX(-50%);
z-index: 100; padding: 14px 32px; background: #2ad1d6; color: #000;
font-family: Arial, sans-serif; font-size: 16px; font-weight: bold;
border: none; border-radius: 50px; cursor: pointer;
box-shadow: 0 0 25px rgba(42,209,214,0.6);
}
</style>
</head>
<body>
<button id="startButton">&#9654; Play Audio</button>
<button id="stopButton" style="display:none;">&#9632; Stop Audio</button>
<script src="https://cdn.jsdelivr.net/npm/three@latest/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@latest/examples/js/controls/OrbitControls.js"></script>
<script>
let camera, scene, renderer, sphereGroup, controls, analyser, sound;
const SPHERE_COUNT = 500;
const AREA = 14;
let listenerAdded = false;
scene = new THREE.Scene();
scene.fog = new THREE.Fog(0x0a0b0f, 20, 50);
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x0a0b0f, 1);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
// Lighting
scene.add(new THREE.AmbientLight(0xffffff, 0.6));
const dirLight = new THREE.DirectionalLight(0xffffff, 1.5);
dirLight.position.set(10, 20, 10);
dirLight.castShadow = true;
dirLight.shadow.mapSize.width = 1024;
dirLight.shadow.mapSize.height = 1024;
scene.add(dirLight);
const pLight = new THREE.PointLight(0xffffff, 1.5, 100);
pLight.position.set(8, 20, 10);
scene.add(pLight);
// Grid
const grid = new THREE.GridHelper(AREA, AREA);
grid.material.opacity = 0.18;
grid.material.transparent = true;
scene.add(grid);
// Create colorful spheres
sphereGroup = new THREE.Group();
for (let i = 0; i < SPHERE_COUNT; i++) {
const radius = 0.1 + Math.random() * 0.15;
const hue = i / SPHERE_COUNT;
const mat = new THREE.MeshPhongMaterial({
color: new THREE.Color().setHSL(hue, 1, 0.55),
emissive: new THREE.Color().setHSL(hue, 1, 0.1),
shininess: 100
});
const mesh = new THREE.Mesh(new THREE.SphereGeometry(radius, 32, 32), mat);
mesh.position.set(
Math.random() * AREA - AREA / 2,
0,
Math.random() * AREA - AREA / 2
);
mesh.userData = {
baseHue: hue,
bounceHeight: 1 + Math.random() * 5,
bounceSpeed: 2 + Math.random() * 6,
bounceOffset: Math.random() * Math.PI * 2
};
sphereGroup.add(mesh);
}
scene.add(sphereGroup);
// Camera & Controls
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.12;
controls.autoRotate = true;
controls.autoRotateSpeed = 0.5;
camera.position.set(0, 5, 10);
controls.target.set(0, 2, 0);
controls.update();
// Audio
document.getElementById('startButton').addEventListener('click', function() {
document.getElementById('startButton').style.display = 'none';
document.getElementById('stopButton').style.display = 'inline-block';
if (!listenerAdded) {
var listener = new THREE.AudioListener();
camera.add(listener);
listenerAdded = true;
sound = new THREE.PositionalAudio(listener);
sphereGroup.add(sound);
}
var audioLoader = new THREE.AudioLoader();
audioLoader.load('https://www.shanebrumback.com/sounds/threejs-drum-particles-001.mp3', function(buf) {
sound.setBuffer(buf);
sound.setLoop(true);
sound.setVolume(0.9);
sound.play();
analyser = new THREE.AudioAnalyser(sound, 128);
});
});
document.getElementById('stopButton').addEventListener('click', function() {
if (sound) sound.stop();
analyser = null;
document.getElementById('stopButton').style.display = 'none';
document.getElementById('startButton').style.display = 'inline-block';
});
// Animation loop - spheres bounce on Y axis to music
function animate() {
requestAnimationFrame(animate);
var freq = 0;
if (analyser) {
var data = analyser.getFrequencyData();
var sum = 0;
for (var i = 0; i < data.length; i++) sum += data[i];
freq = Math.min(sum / (data.length * 255), 1);
}
var t = Date.now() * 0.001;
sphereGroup.children.forEach(function(s) {
if (!s.geometry) return;
// Bounce on Y axis based on audio frequency
s.position.y = Math.sin(t * s.userData.bounceSpeed + s.userData.bounceOffset) * s.userData.bounceHeight * freq;
// Color reacts to audio
s.material.color.setHSL(s.userData.baseHue, 1, 0.45 + freq * 0.35);
s.material.emissive.setHSL(s.userData.baseHue, 1, freq * 0.25);
});
controls.update();
renderer.render(scene, camera);
}
animate();
// Resize handler
window.addEventListener('resize', function() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment