Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save ShaneBrumback/f69841ea617cd0985fb32627ef42e913 to your computer and use it in GitHub Desktop.
Save ShaneBrumback/f69841ea617cd0985fb32627ef42e913 to your computer and use it in GitHub Desktop.
Threejs Examples Interactive Audio Driven Particle Systems
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Blog - Interactive Audio Driven Particle Systems | www.ShaneBrumback.com</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!--Social Sharing-->
<link rel="stylesheet" href="/css/app.css?2">
<!--Font resources-->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://use.fontawesome.com/releases/v5.15.4/js/all.js" data-mutate-approach="sync"></script>
<link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&display=swap" rel="stylesheet">
</head>
<body>
<center>
<button id="startButton"><i class="fas fa-play"></i> Play Audio</button>
<button id="stopButton" style="display: none;"><i class="fas fa-stop"></i> Stop Audio</button>
<div id="divRange"></div>
<div id="divRange"></div>
<div id="divRangeFooter"></div>
</center>
<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 type="module">
// Declare global variables
let camera, scene, renderer, particles, controls, analyser, sound;
const particleCount = 10000;
const areaSize = 10;
// Function to initialize the scene and setup initial configurations
function init() {
// Create a scene
scene = new THREE.Scene();
// Create a camera with perspective projection
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.x = 2;
camera.position.y = 2;
camera.position.z = 2;
// Set up the WebGL renderer with antialiasing and alpha
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.toneMapping = THREE.ReinhardToneMapping;
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.domElement.id = 'renderer';
renderer.setClearColor(0x000000, 1); // Set background color to black
renderer.domElement.style.position = 'fixed';
renderer.domElement.style.zIndex = '-1';
renderer.domElement.style.left = '0';
renderer.domElement.style.top = '0';
document.body.appendChild(renderer.domElement);
// Create a grid to visualize the area size
const gridHelper = new THREE.GridHelper(areaSize, areaSize);
scene.add(gridHelper);
// Create particles as a group and add them to the scene
particles = new THREE.Group();
for (let i = 0; i < particleCount; i++) {
const particle = new THREE.Mesh(
new THREE.BoxGeometry(0.05, 0.05, 0.05),
new THREE.MeshBasicMaterial({ color: 0x0000ff })
);
particle.position.set(
Math.random() * areaSize - areaSize / 2,
0,
Math.random() * areaSize - areaSize / 2
);
particles.add(particle);
}
scene.add(particles);
// Create OrbitControls to enable camera movement
controls = new THREE.OrbitControls(camera, renderer.domElement);
// Set autoRotate for the controls to true for auto-rotation
controls.autoRotate = true;
// Add a new property to store the original y position for each particle
particles.children.forEach((particle) => {
particle.originalY = particle.position.y;
});
// Attach event listener to a button to start audio processing
var playButton = document.getElementById('startButton');
playButton.addEventListener('pointerdown', playNow);
// Attach event listener to handle window resize
window.addEventListener('resize', onWindowResize);
// Start the animation loop
animate();
// Hide/show buttons on click
const startBtn = document.getElementById('startButton');
const stopBtn = document.getElementById('stopButton');
startBtn.addEventListener('click', () => {
startBtn.style.display = 'none';
stopBtn.style.display = 'inline-block';
});
stopBtn.addEventListener('click', () => {
sound.stop();
stopBtn.style.display = 'none';
startBtn.style.display = 'inline-block';
});
}
// Animation loop to update the particle movement and render the scene
function animate() {
requestAnimationFrame(animate);
if (sound) {
// Get frequency data from the analyser
const frequencyData = analyser.getFrequencyData();
// Calculate the average frequency
let totalFrequency = 0;
for (let i = 0; i < frequencyData.length; i++) {
totalFrequency += frequencyData[i];
}
const averageFrequency = totalFrequency / frequencyData.length;
// Map the average frequency to a suitable range for particle movement
const frequency = mapRange(averageFrequency, 0, 255, 0, 1);
// Map the frequency to a color range (from blue to bright orange)
const color = new THREE.Color().setHSL(frequency * 0.17, 1, 0.7); // Adjust the saturation (0.7) for bright orange
// Move particles up on the y-axis when the frequency changes
particles.children.forEach((particle) => {
const threshold = frequency * Math.floor(Math.random() * 10); // Adjust the multiplier as needed to control the particle movement
if (particle.position.x <= threshold && particle.position.z <= threshold) {
// Lerp to the new y position (threshold)
particle.position.y = lerp(particle.position.y, threshold, 0.1); // Adjust the lerp factor (0.1) for desired speed
// Check if the particle has a material and create one if it doesn't
if (!particle.material) {
particle.material = new THREE.MeshBasicMaterial();
}
// Set the particle color based on the mapped frequency
particle.material.color.copy(color);
} else {
// Lerp back to the original y position (0)
particle.position.y = lerp(particle.position.y, particle.originalY, 0.1); // Adjust the lerp factor (0.1) for desired speed
particle.material.color.set(0x0000ff); // Set color to blue
}
// Check frequency = 0 set color back to blue
if (frequency == 0)
particle.material.color.set(0x0000ff); // Set color to blue
});
}
controls.update();
renderer.render(scene, camera);
}
// Helper function for linear interpolation
function lerp(a, b, t) {
return a + t * (b - a);
}
// Helper function to map a value from one range to another
function mapRange(value, low1, high1, low2, high2) {
return low2 + ((high2 - low2) * (value - low1)) / (high1 - low1);
}
// Event listener for window resize events
window.addEventListener('resize', function () {
const width = window.innerWidth;
const height = window.innerHeight;
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize(width, height);
});
// Initialize the scene
init();
// Function to handle starting the audio processing
function playNow() {
if (sound) {
sound.stop();
}
// Create an AudioListener and add it to the camera
const listener = new THREE.AudioListener();
camera.add(listener);
// Create the PositionalAudio object (passing in the listener)
sound = new THREE.PositionalAudio(listener);
// Load a sound and set it as the PositionalAudio object's buffer
const audioLoader = new THREE.AudioLoader();
audioLoader.load('../sounds/threejs-drum-particles-001.mp3', function (buffer) {
sound.setBuffer(buffer);
sound.setLoop(true);
sound.setRefDistance(100);
sound.play();
});
// Set up the analyser so we can get the audio frequency data
analyser = new THREE.AudioAnalyser(sound, 128);
// Add the sound to the particles group
particles.add(sound);
}
// Function to handle window resize events
function onWindowResize() {
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