Last active
May 23, 2026 00:36
-
-
Save ShaneBrumback/667daa075f76f571b2d9f06aa6d42c83 to your computer and use it in GitHub Desktop.
interactive-audio-sphere-particle-systems.html
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| interactive-audio-sphere-particle-systems.html |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!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">▶ Play Audio</button> | |
| <button id="stopButton" style="display:none;">■ 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