Audio Reactive Particles
Dynamic particle system that responds to audio frequency analysis with explosions, color changes, and movement based on bass, mid, and treble ranges.
What this code does
- Audio Analysis: Web Audio API analyser separates frequency spectrum into bass, mid, and treble ranges for different particle behaviors.
- Particle Explosion: bass frequencies trigger radial particle explosions from the center with configurable intensity.
- Frequency Color Mapping: particles change color based on audio content - red for bass, green for mid, blue for treble frequencies.
- Wave Motion: mid-range frequencies create wave-like motion across the particle field for organic movement.
- High Frequency Jitter: treble frequencies add subtle random movement to individual particles for texture.
- Explosion Particles: separate particle system spawns bright explosion effects on strong bass hits with physics-based decay.
- Audio-Reactive Lighting: four colored point lights move and change intensity based on different frequency ranges.
- Interactive Controls: adjust reactivity for each frequency range, explosion intensity, color modes, and particle sizes.
JavaScript (plain)
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000)
camera.position.set(0, 0, 30)
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(width, height)
document.querySelector('#app').appendChild(renderer.domElement)
// Setup audio context and analyser
const audioContext = new (window.AudioContext || window.webkitAudioContext)()
const analyser = audioContext.createAnalyser()
analyser.fftSize = 512
const bufferLength = analyser.frequencyBinCount
const dataArray = new Uint8Array(bufferLength)
// Create demo oscillators for audio
const oscillator1 = audioContext.createOscillator()
const oscillator2 = audioContext.createOscillator()
const gainNode = audioContext.createGain()
oscillator1.frequency.setValueAtTime(150, audioContext.currentTime)
oscillator2.frequency.setValueAtTime(220, audioContext.currentTime)
oscillator1.connect(gainNode)
oscillator2.connect(gainNode)
gainNode.connect(analyser)
analyser.connect(audioContext.destination)
gainNode.gain.setValueAtTime(0.1, audioContext.currentTime)
oscillator1.start()
oscillator2.start()
// Create particle system
const particleCount = 3000
const positions = new Float32Array(particleCount * 3)
const colors = new Float32Array(particleCount * 3)
const velocities = new Float32Array(particleCount * 3)
for (let i = 0; i < particleCount * 3; i += 3) {
positions[i] = (Math.random() - 0.5) * 40
positions[i + 1] = (Math.random() - 0.5) * 40
positions[i + 2] = (Math.random() - 0.5) * 40
velocities[i] = (Math.random() - 0.5) * 0.1
velocities[i + 1] = (Math.random() - 0.5) * 0.1
velocities[i + 2] = (Math.random() - 0.5) * 0.1
}
const geometry = new THREE.BufferGeometry()
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3))
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3))
const material = new THREE.PointsMaterial({
size: 0.5,
vertexColors: true,
transparent: true,
blending: THREE.AdditiveBlending
})
const particles = new THREE.Points(geometry, material)
scene.add(particles)
function animate() {
requestAnimationFrame(animate)
// Get audio data
analyser.getByteFrequencyData(dataArray)
const bassAvg = dataArray.slice(0, 32).reduce((a, b) => a + b) / 32 / 255
const midAvg = dataArray.slice(32, 128).reduce((a, b) => a + b) / 96 / 255
const trebleAvg = dataArray.slice(128, 256).reduce((a, b) => a + b) / 128 / 255
// Update particles based on audio
for (let i = 0; i < particleCount * 3; i += 3) {
const audioForce = bassAvg * 2 + midAvg * 1.5 + trebleAvg
positions[i] += velocities[i] + Math.sin(Date.now() * 0.001 + i) * audioForce * 0.1
positions[i + 1] += velocities[i + 1] + Math.cos(Date.now() * 0.001 + i) * audioForce * 0.1
positions[i + 2] += velocities[i + 2] + audioForce * 0.1
colors[i] = bassAvg
colors[i + 1] = midAvg
colors[i + 2] = trebleAvg
}
geometry.attributes.position.needsUpdate = true
geometry.attributes.color.needsUpdate = true
renderer.render(scene, camera)
}
animate()