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()