Last active
March 19, 2024 05:17
-
-
Save ShaneBrumback/f69841ea617cd0985fb32627ef42e913 to your computer and use it in GitHub Desktop.
Threejs Examples Interactive Audio Driven Particle Systems
This file contains 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>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