diff --git a/mlx_audio/tts/audio_player.html b/mlx_audio/tts/audio_player.html
index 57111eaf..63c5b1c6 100644
--- a/mlx_audio/tts/audio_player.html
+++ b/mlx_audio/tts/audio_player.html
@@ -31,14 +31,22 @@
margin: 4px 2px;
cursor: pointer;
border-radius: 4px;
+ transition: all 0.3s ease;
+ }
+ button:hover {
+ background-color: #45a049;
}
button:disabled {
background-color: #cccccc;
+ color: #666;
cursor: not-allowed;
}
#stopBtn {
background-color: #f44336;
}
+ #stopBtn:hover {
+ background-color: #d32f2f;
+ }
#status {
margin-top: 10px;
}
@@ -113,6 +121,7 @@
margin: 0;
background: transparent;
-webkit-appearance: none;
+ appearance: none;
}
input[type="range"]:focus {
outline: none;
@@ -239,19 +248,17 @@
MLX-Audio Player
const audioUpload = document.getElementById('audioUpload');
const playBtn = document.getElementById('playBtn');
const stopBtn = document.getElementById('stopBtn');
- const statusElement = document.getElementById('status');
-
- // TTS elements
const textInput = document.getElementById('text');
const voiceSelect = document.getElementById('voice');
const modelSelect = document.getElementById('model');
const speedInput = document.getElementById('speed');
const speedValue = document.getElementById('speed-value');
+ const statusElement = document.getElementById('status');
const generateBtn = document.getElementById('generateBtn');
const openFolderBtn = document.getElementById('openFolderBtn');
- const ttsErrorElement = document.getElementById('ttsError');
const ttsStatusElement = document.getElementById('ttsStatus');
-
+ const ttsErrorElement = document.getElementById('ttsError');
+
// Audio variables
let audioContext;
let analyser;
@@ -259,90 +266,359 @@ MLX-Audio Player
let audioElement;
let audioSource;
- // Three.js setup
- const scene = new THREE.Scene();
- const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
- const renderer = new THREE.WebGLRenderer({ antialias: true });
- renderer.setSize(window.innerWidth, window.innerHeight);
- renderer.setClearColor(0x000000);
- document.body.appendChild(renderer.domElement);
-
- // Add orbit controls
- const controls = new THREE.OrbitControls(camera, renderer.domElement);
- controls.enableDamping = true;
- controls.dampingFactor = 0.05;
-
- // Position camera
- camera.position.set(0, 0, 100);
- camera.lookAt(0, 0, 0);
-
- // Add lights
- const ambientLight = new THREE.AmbientLight(0x404040);
- scene.add(ambientLight);
-
- const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
- directionalLight.position.set(1, 1, 1);
- scene.add(directionalLight);
-
- const pointLight = new THREE.PointLight(0xffffff, 1, 100);
- pointLight.position.set(0, 0, 0);
- scene.add(pointLight);
+ // Three.js variables
+ let scene, camera, renderer, controls;
+ let particleSystem, originalPositions;
+ let animationId;
+ let timeValue = 0;
+ let currentPlaybackRate = 1.0;
+
+ // Animation parameters
+ const particleCount = 8000;
+ let baseAmplitude = 0.05;
+ let baseFrequency = 0.3;
+ let basePull = new THREE.Vector3(0, 0, 1);
+
+ // Transition parameters
+ let targetAmplitude = 0.05;
+ let targetFrequency = 0.3;
+ let targetPull = new THREE.Vector3(0, 0, 1);
+ let transitionStartTime = 0;
+ let transitionDuration = 1000;
+
+ // Text visualization
+ let currentWords = [];
+ let wordParams = [];
+ let animationStartTime = 0;
+ let currentWordIndex = 0;
+ const wordDuration = 1000;
+
+ // Initialize Three.js scene
+ function init() {
+ scene = new THREE.Scene();
+
+ camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
+ camera.position.z = 5;
+
+ renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
+ renderer.setSize(window.innerWidth, window.innerHeight);
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
+ document.body.appendChild(renderer.domElement);
+
+ controls = new THREE.OrbitControls(camera, renderer.domElement);
+ controls.enableDamping = true;
+ controls.dampingFactor = 0.05;
+
+ createParticleSystem();
+
+ animate();
+ }
+
+ // Create particle system
+ function createParticleSystem() {
+ const positions = new Float32Array(particleCount * 3);
+ originalPositions = new Float32Array(particleCount * 3);
+ const colors = new Float32Array(particleCount * 3);
+ const sizes = new Float32Array(particleCount);
+
+ const radius = 1;
+
+ for (let i = 0; i < particleCount; i++) {
+ const theta = Math.random() * Math.PI * 2;
+ const phi = Math.acos(2 * Math.random() - 1);
+
+ const x = radius * Math.sin(phi) * Math.cos(theta);
+ const y = radius * Math.sin(phi) * Math.sin(theta);
+ const z = radius * Math.cos(phi);
+
+ positions[i * 3] = x;
+ positions[i * 3 + 1] = y;
+ positions[i * 3 + 2] = z;
+
+ originalPositions[i * 3] = x;
+ originalPositions[i * 3 + 1] = y;
+ originalPositions[i * 3 + 2] = z;
+
+ // Add variation to particle colors (blue/white hues)
+ colors[i * 3] = 0.7 + Math.random() * 0.3; // R: high amount for white
+ colors[i * 3 + 1] = 0.7 + Math.random() * 0.3; // G: high amount for white
+ colors[i * 3 + 2] = 0.9 + Math.random() * 0.1; // B: highest for blue tint
+
+ // Vary particle sizes slightly
+ sizes[i] = 0.02 + Math.random() * 0.03;
+ }
+
+ const geometry = new THREE.BufferGeometry();
+ geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
+ geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
+ geometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1));
+
+ const material = new THREE.PointsMaterial({
+ size: 0.03,
+ vertexColors: true,
+ transparent: true,
+ opacity: 0.8,
+ sizeAttenuation: true,
+ blending: THREE.AdditiveBlending // Add glow effect
+ });
+
+ particleSystem = new THREE.Points(geometry, material);
+ scene.add(particleSystem);
+ }
+
+ // Generate a unique sequence of numbers for each word or frequency band
+ function generateFingerprint(input) {
+ const length = 6 + Math.floor((input.length || 5) % 5);
+ const result = [];
+
+ for (let i = 0; i < length; i++) {
+ const charCode = typeof input === 'string'
+ ? input.charCodeAt(i % input.length)
+ : (input * 100 + i);
+ const position = i + 1;
+ const inputLength = typeof input === 'string' ? input.length : 5;
+
+ // Use a more varied calculation to increase uniqueness
+ const number = ((charCode * position * (i + 1) * inputLength) % 100) / 100;
+ result.push(number);
+ }
+
+ return result;
+ }
+
+ // Compute parameters for cosmic wave deformation based on the fingerprint
+ function computeParams(input) {
+ const fingerprint = generateFingerprint(input);
+ const avg = fingerprint.reduce((acc, n) => acc + n, 0) / fingerprint.length;
+
+ // Wider range for amplitude
+ const minAmplitude = 0.05, maxAmplitude = 0.7;
+ const amplitude = minAmplitude + avg * (maxAmplitude - minAmplitude);
+
+ // More varied frequency
+ const rawFrequency = fingerprint[0];
+ const minFrequency = 0.1, maxFrequency = 0.8;
+ const frequency = minFrequency + rawFrequency * (maxFrequency - minFrequency);
+
+ // More varied pull directions
+ const pullX = (fingerprint[0] - 0.5) * 2;
+ const pullY = (fingerprint[1] - 0.5) * 2;
+ const pullZ = (fingerprint[2] - 0.5) * 2;
+ const pull = new THREE.Vector3(pullX, pullY, pullZ).normalize();
+
+ return { amplitude, frequency, pull };
+ }
+
+ // Reset sphere to original state
+ function resetSphere() {
+ if (!particleSystem) return;
+
+ const positions = particleSystem.geometry.attributes.position.array;
+
+ for (let i = 0; i < particleCount; i++) {
+ positions[i * 3] = originalPositions[i * 3];
+ positions[i * 3 + 1] = originalPositions[i * 3 + 1];
+ positions[i * 3 + 2] = originalPositions[i * 3 + 2];
+ }
+
+ particleSystem.geometry.attributes.position.needsUpdate = true;
+
+ // Reset all animation parameters
+ timeValue = 0;
+ baseAmplitude = 0.05;
+ baseFrequency = 0.3;
+ basePull = new THREE.Vector3(0, 0, 1);
+ targetAmplitude = 0.05;
+ targetFrequency = 0.3;
+ targetPull = new THREE.Vector3(0, 0, 1);
+ transitionStartTime = 0;
+ }
+
+ // Process text into words and compute parameters for each word
+ function processText(text) {
+ if (!text || text.trim() === '') return;
+
+ const words = text.split(/\s+/).filter(word => word.length > 0);
+ if (words.length === 0) return;
+
+ currentWords = words;
+ wordParams = words.map(word => computeParams(word));
+
+ animationStartTime = performance.now();
+ currentWordIndex = 0;
+ }
+
+ // Animation loop
+ function animate() {
+ animationId = requestAnimationFrame(animate);
- // Create orb mesh
- const sphereGeometry = new THREE.IcosahedronGeometry(30, 4); // Higher detail icosahedron
- const sphereMaterial = new THREE.MeshPhongMaterial({
- color: 0x0088ff,
- emissive: 0x222222,
- shininess: 30,
- wireframe: false,
- flatShading: true
- });
- const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
- scene.add(sphere);
+ controls.update();
- // Store original vertex positions
- const originalVertices = [];
- for (let i = 0; i < sphereGeometry.attributes.position.count; i++) {
- originalVertices.push(
- new THREE.Vector3(
- sphereGeometry.attributes.position.getX(i),
- sphereGeometry.attributes.position.getY(i),
- sphereGeometry.attributes.position.getZ(i)
- )
- );
+ // Adjust animation speed based on playback rate
+ const speedFactor = audioElement && audioElement.playbackRate ? audioElement.playbackRate : 1.0;
+ timeValue += 0.01 * speedFactor;
+
+ let effectiveAmplitude = baseAmplitude;
+ let effectiveFrequency = baseFrequency;
+ let effectivePull = basePull.clone();
+
+ if (audioContext && analyser) {
+ analyser.getByteFrequencyData(dataArray);
+
+ // More granular frequency analysis
+ const lowBass = getAverageFrequency(dataArray, 0, 5) / 256;
+ const highBass = getAverageFrequency(dataArray, 6, 15) / 256;
+ const lowMid = getAverageFrequency(dataArray, 16, 50) / 256;
+ const highMid = getAverageFrequency(dataArray, 51, 100) / 256;
+ const lowTreble = getAverageFrequency(dataArray, 101, 175) / 256;
+ const highTreble = getAverageFrequency(dataArray, 176, 255) / 256;
+
+ // Create more dynamic amplitude response
+ targetAmplitude = 0.05 + (lowBass * 0.3 + highBass * 0.4);
+
+ // Create more varied frequency response
+ targetFrequency = 0.1 + (lowMid * 0.3 + highMid * 0.5);
+
+ // Create more varied pull directions
+ const pullX = (lowBass + highMid) - 0.5;
+ const pullY = (highBass + lowTreble) - 0.5;
+ const pullZ = (lowMid + highTreble) - 0.5;
+ targetPull = new THREE.Vector3(pullX, pullY, pullZ).normalize();
+
+ if (transitionStartTime === 0) {
+ transitionStartTime = performance.now();
+ }
+ }
+ else if (currentWords.length > 0 && animationStartTime > 0) {
+ const elapsed = performance.now() - animationStartTime;
+ const totalWords = currentWords.length;
+ const totalDuration = totalWords * wordDuration;
+
+ if (elapsed >= totalDuration) {
+ currentWords = [];
+ animationStartTime = 0;
+
+ targetAmplitude = 0.05;
+ targetFrequency = 0.3;
+ targetPull = new THREE.Vector3(0, 0, 1);
+
+ transitionStartTime = performance.now();
+ } else {
+ let wordIndex = Math.floor(elapsed / wordDuration);
+ let phaseTime = elapsed % wordDuration;
+ let currentParams, nextParams;
+
+ if (wordIndex < totalWords - 1) {
+ currentParams = wordParams[wordIndex];
+ nextParams = wordParams[wordIndex + 1];
+ } else {
+ currentParams = wordParams[wordIndex];
+ nextParams = {
+ amplitude: 0.05,
+ frequency: 0.3,
+ pull: new THREE.Vector3(0, 0, 1)
+ };
+ }
+
+ const t = phaseTime / wordDuration;
+ const easeInOutQuad = v => v < 0.5 ? 2 * v * v : -1 + (4 - 2 * v) * v;
+ const easedT = easeInOutQuad(t);
+
+ targetAmplitude = currentParams.amplitude + (nextParams.amplitude - currentParams.amplitude) * easedT;
+ targetFrequency = currentParams.frequency + (nextParams.frequency - currentParams.frequency) * easedT;
+ targetPull = currentParams.pull.clone().lerp(nextParams.pull, easedT).normalize();
+
+ if (wordIndex !== currentWordIndex) {
+ currentWordIndex = wordIndex;
+ }
+ }
+ }
+ else {
+ particleSystem.rotation.y += 0.001;
+ particleSystem.rotation.x += 0.0005;
+ }
+
+ if (audioContext) {
+ const elapsed = performance.now() - transitionStartTime;
+ const t = Math.min(elapsed / transitionDuration, 1.0);
+ const easeOutQuad = t => t * (2 - t);
+ const easedT = easeOutQuad(t);
+
+ effectiveAmplitude = baseAmplitude + (targetAmplitude - baseAmplitude) * easedT;
+ effectiveFrequency = baseFrequency + (targetFrequency - baseFrequency) * easedT;
+ effectivePull = basePull.clone().lerp(targetPull, easedT);
+
+ if (elapsed >= transitionDuration) {
+ baseAmplitude = targetAmplitude;
+ baseFrequency = targetFrequency;
+ basePull = targetPull.clone();
+ transitionStartTime = 0;
+ }
+ }
+
+ if (particleSystem) {
+ const positions = particleSystem.geometry.attributes.position.array;
+
+ for (let i = 0; i < particleCount; i++) {
+ const ox = originalPositions[i * 3];
+ const oy = originalPositions[i * 3 + 1];
+ const oz = originalPositions[i * 3 + 2];
+
+ const base = new THREE.Vector3(ox, oy, oz);
+
+ const dotVal = base.dot(effectivePull);
+ const parallel = effectivePull.clone().multiplyScalar(dotVal);
+ const perpendicular = base.clone().sub(parallel);
+
+ const phase = timeValue * effectiveFrequency + (ox + oy + oz) * 2.0;
+ const deformation = effectiveAmplitude * Math.sin(phase);
+
+ const newParallel = parallel.multiplyScalar(1 + 2 * deformation);
+ const newPerp = perpendicular.multiplyScalar(1 - deformation);
+ const newPos = newParallel.add(newPerp);
+
+ positions[i * 3] = newPos.x;
+ positions[i * 3 + 1] = newPos.y;
+ positions[i * 3 + 2] = newPos.z;
+ }
+
+ particleSystem.geometry.attributes.position.needsUpdate = true;
+ }
+
+ renderer.render(scene, camera);
}
-
- // Create a glow effect
- const glowGeometry = new THREE.SphereGeometry(32, 32, 32);
- const glowMaterial = new THREE.MeshBasicMaterial({
- color: 0x0088ff,
- transparent: true,
- opacity: 0.15,
- side: THREE.BackSide
+
+ // Helper function to get average frequency in a range
+ function getAverageFrequency(dataArray, startIndex, endIndex) {
+ let sum = 0;
+ for (let i = startIndex; i <= endIndex; i++) {
+ sum += dataArray[i];
+ }
+ return sum / (endIndex - startIndex + 1);
+ }
+
+ // Handle window resize
+ window.addEventListener('resize', () => {
+ camera.aspect = window.innerWidth / window.innerHeight;
+ camera.updateProjectionMatrix();
+ renderer.setSize(window.innerWidth, window.innerHeight);
});
- const glowMesh = new THREE.Mesh(glowGeometry, glowMaterial);
- scene.add(glowMesh);
- // Define rotation speed variables
- let rotationSpeedY = 0.002;
- let rotationSpeedX = 0.001;
- let isGenerating = false;
+ // Initialize the scene
+ init();
// Tab functionality
function openTab(evt, tabName) {
- // Hide all tabcontent
const tabcontent = document.getElementsByClassName("tabcontent");
for (let i = 0; i < tabcontent.length; i++) {
tabcontent[i].style.display = "none";
}
- // Remove active class from all tablinks
const tablinks = document.getElementsByClassName("tablinks");
for (let i = 0; i < tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" active", "");
}
- // Show the current tab and add active class to the button
document.getElementById(tabName).style.display = "block";
evt.currentTarget.className += " active";
}
@@ -364,23 +640,15 @@ MLX-Audio Player
return;
}
- // Hide previous error
ttsErrorElement.style.display = 'none';
ttsStatusElement.textContent = 'Generating speech...';
- // Increase rotation speed to indicate processing
- isGenerating = true;
- rotationSpeedY = 0.01;
- rotationSpeedX = 0.005;
-
- // Create form data
const formData = new FormData();
formData.append('text', text);
formData.append('voice', voice);
formData.append('model', model);
formData.append('speed', speed);
- // Send request to server
fetch('/tts', {
method: 'POST',
body: formData
@@ -396,35 +664,13 @@ MLX-Audio Player
.then(data => {
ttsStatusElement.textContent = 'Speech generated successfully!';
- // Reset rotation speed
- isGenerating = false;
- rotationSpeedY = 0.002;
- rotationSpeedX = 0.001;
-
- // Clean up previous audio resources
- if (audioElement) {
- audioElement.pause();
- audioElement.removeAttribute('src');
- audioElement.load();
- }
-
- if (audioSource) {
- audioSource.disconnect();
- audioSource = null;
- }
-
- // Create new audio element to avoid source node issues
audioElement = new Audio();
-
- // Set audio source with absolute path
audioElement.src = `/audio/${data.filename}`;
audioElement.loop = false;
- // Enable play button
playBtn.disabled = false;
stopBtn.disabled = true;
- // Add ended event listener
audioElement.addEventListener('ended', function() {
statusElement.textContent = "Audio finished playing.";
playBtn.disabled = false;
@@ -432,16 +678,10 @@ MLX-Audio Player
resetSphere();
});
- // Auto-play the generated audio
playAudio();
})
.catch(error => {
showTtsError(error.message);
-
- // Reset rotation speed on error too
- isGenerating = false;
- rotationSpeedY = 0.002;
- rotationSpeedX = 0.001;
});
});
@@ -473,52 +713,40 @@ MLX-Audio Player
// Function to play audio (reused for both upload and TTS)
function playAudio() {
- if (!audioElement || !audioElement.src) {
- statusElement.textContent = "No audio available to play.";
- return;
+ if (audioContext) {
+ audioContext.close();
}
- statusElement.textContent = "Playing audio...";
+ audioContext = new (window.AudioContext || window.webkitAudioContext)();
+ analyser = audioContext.createAnalyser();
+ analyser.fftSize = 512;
+ dataArray = new Uint8Array(analyser.frequencyBinCount);
- // Initialize audio context if needed
- if (!audioContext) {
- audioContext = new (window.AudioContext || window.webkitAudioContext)();
- }
+ const source = audioContext.createMediaElementSource(audioElement);
+ source.connect(analyser);
+ analyser.connect(audioContext.destination);
- // Create analyser if needed
- if (!analyser) {
- analyser = audioContext.createAnalyser();
- analyser.fftSize = 256;
- dataArray = new Uint8Array(analyser.frequencyBinCount);
- }
+ const newPlaybackRate = parseFloat(speedInput.value);
+ // Always reset timeValue when starting playback to ensure consistent animation speed
+ timeValue = 0;
+ currentPlaybackRate = newPlaybackRate;
+ audioElement.playbackRate = newPlaybackRate;
+ audioElement.play();
- // Connect audio element to analyser if not already connected
- if (!audioSource) {
- try {
- audioSource = audioContext.createMediaElementSource(audioElement);
- audioSource.connect(analyser);
- analyser.connect(audioContext.destination);
- } catch (error) {
- console.error("Error connecting audio source:", error);
- statusElement.textContent = "Error setting up audio visualization. Try refreshing the page.";
-
- // Still try to play the audio even if visualization fails
- audioElement.play().catch(playError => {
- statusElement.textContent = "Error playing audio: " + playError.message;
- });
- return;
- }
+ // Reset the sphere first
+ resetSphere();
+
+ // Process text for word animation if we have text input
+ if (textInput.value && document.getElementById('textToSpeech').style.display === 'block') {
+ processText(textInput.value);
}
- // Play audio
- audioElement.play().then(() => {
- playBtn.disabled = true;
- stopBtn.disabled = false;
- }).catch(error => {
- statusElement.textContent = "Error playing audio: " + error.message;
- });
+ // Update UI state
+ playBtn.disabled = true;
+ stopBtn.disabled = false;
+ statusElement.textContent = 'Playing...';
}
-
+
// Handle audio upload
audioUpload.addEventListener('change', function(e) {
const file = e.target.files[0];
@@ -526,7 +754,6 @@ MLX-Audio Player
statusElement.textContent = "Audio file loaded. Press Play to start.";
- // Clean up previous audio resources
if (audioElement) {
audioElement.pause();
audioElement.removeAttribute('src');
@@ -538,16 +765,13 @@ MLX-Audio Player
audioSource = null;
}
- // Create new audio element
audioElement = new Audio();
audioElement.src = URL.createObjectURL(file);
audioElement.loop = false;
- // Enable play button
playBtn.disabled = false;
stopBtn.disabled = true;
- // Add ended event listener
audioElement.addEventListener('ended', function() {
statusElement.textContent = "Audio finished playing.";
playBtn.disabled = false;
@@ -570,179 +794,20 @@ MLX-Audio Player
playBtn.disabled = false;
stopBtn.disabled = true;
- // Reset sphere to original state
+ // Ensure animation state is completely reset
resetSphere();
- }
- });
-
- // Reset sphere to original state
- function resetSphere() {
- const positionAttribute = sphereGeometry.attributes.position;
-
- for (let i = 0; i < positionAttribute.count; i++) {
- const originalVertex = originalVertices[i];
- positionAttribute.setXYZ(i, originalVertex.x, originalVertex.y, originalVertex.z);
- }
-
- positionAttribute.needsUpdate = true;
- sphereGeometry.computeVertexNormals();
-
- // Reset colors
- sphere.material.color.set(0x0088ff);
- sphere.material.emissive.set(0x222222);
- glowMesh.material.color.set(0x0088ff);
- }
-
- // Animation loop
- function animate() {
- requestAnimationFrame(animate);
-
- // Update controls
- controls.update();
-
- // Get current time for pulsating effect
- const time = performance.now() * 0.001; // Convert to seconds
-
- // Rotate sphere with current speed
- sphere.rotation.y += rotationSpeedY;
- sphere.rotation.x += rotationSpeedX;
- glowMesh.rotation.copy(sphere.rotation);
-
- // Update visualization if audio is playing
- if (analyser && dataArray && !audioElement.paused) {
- analyser.getByteFrequencyData(dataArray);
-
- // Calculate average frequency values for different ranges
- const bassAvg = getAverageFrequency(dataArray, 0, 5);
- const midAvg = getAverageFrequency(dataArray, 6, 20);
- const trebleAvg = getAverageFrequency(dataArray, 21, 40);
-
- // Calculate base pulsating factor (same as when no audio is playing)
- const pulseFactor = Math.sin(time * 1.5) * 0.03 + 1; // Subtle pulsation (±3%)
-
- // Update sphere vertices based on frequency data
- const positionAttribute = sphereGeometry.attributes.position;
-
- for (let i = 0; i < positionAttribute.count; i++) {
- const originalVertex = originalVertices[i];
-
- // Calculate normalized distance from center (0-1)
- const vertexLength = originalVertex.length();
-
- // Get frequency value based on vertex position
- let frequencyFactor;
-
- // Use different frequency ranges based on vertex position
- if (Math.abs(originalVertex.y) > vertexLength * 0.7) {
- // Top/bottom vertices - use treble
- frequencyFactor = trebleAvg / 255;
- } else if (Math.abs(originalVertex.x) > vertexLength * 0.7) {
- // Left/right vertices - use mids
- frequencyFactor = midAvg / 255;
- } else {
- // Other vertices - use bass
- frequencyFactor = bassAvg / 255;
- }
-
- // Scale vertex based on both pulsation and frequency
- // First apply the pulsating effect, then add audio reactivity
- const scaleFactor = pulseFactor * (1 + frequencyFactor * 0.5);
-
- positionAttribute.setXYZ(
- i,
- originalVertex.x * scaleFactor,
- originalVertex.y * scaleFactor,
- originalVertex.z * scaleFactor
- );
- }
-
- positionAttribute.needsUpdate = true;
- sphereGeometry.computeVertexNormals();
-
- // Update colors based on frequency
- const hue = (bassAvg / 255) * 0.3;
- const saturation = 0.8;
- const lightness = 0.4 + (midAvg / 255) * 0.2;
-
- sphere.material.color.setHSL(hue, saturation, lightness);
- sphere.material.emissive.setHSL(hue, saturation, lightness * 0.5);
-
- // Update glow with both pulsation and audio reactivity
- glowMesh.material.color.setHSL(hue, saturation, lightness);
- const glowPulseFactor = 1 + Math.sin(time * 1.2) * 0.04;
- glowMesh.scale.set(
- glowPulseFactor * (1 + (bassAvg / 255) * 0.1),
- glowPulseFactor * (1 + (bassAvg / 255) * 0.1),
- glowPulseFactor * (1 + (bassAvg / 255) * 0.1)
- );
-
- // Update point light with both pulsation and audio reactivity
- const lightPulseFactor = 0.5 + Math.sin(time * 1.8) * 0.2;
- pointLight.intensity = lightPulseFactor + (bassAvg / 255) * 1.5;
- pointLight.color.setHSL(hue, saturation, lightness);
- } else {
- // Apply subtle pulsating effect when no audio is playing
- const pulseFactor = Math.sin(time * 1.5) * 0.03 + 1; // Subtle pulsation (±3%)
-
- // Update sphere vertices for pulsating effect
- const positionAttribute = sphereGeometry.attributes.position;
+ timeValue = 0;
- for (let i = 0; i < positionAttribute.count; i++) {
- const originalVertex = originalVertices[i];
-
- positionAttribute.setXYZ(
- i,
- originalVertex.x * pulseFactor,
- originalVertex.y * pulseFactor,
- originalVertex.z * pulseFactor
- );
- }
-
- positionAttribute.needsUpdate = true;
- sphereGeometry.computeVertexNormals();
-
- // Subtle color pulsation
- const hue = 0.6; // Blue hue
- const saturation = 0.8;
- const lightness = 0.4 + Math.sin(time * 2) * 0.05; // Subtle brightness pulsation
-
- sphere.material.color.setHSL(hue, saturation, lightness);
- sphere.material.emissive.setHSL(hue, saturation, lightness * 0.5);
-
- // Update glow with subtle pulsation
- glowMesh.material.color.setHSL(hue, saturation, lightness);
- glowMesh.scale.set(
- 1 + Math.sin(time * 1.2) * 0.04, // Slightly different frequency for interesting effect
- 1 + Math.sin(time * 1.2) * 0.04,
- 1 + Math.sin(time * 1.2) * 0.04
- );
-
- // Subtle point light pulsation
- pointLight.intensity = 0.5 + Math.sin(time * 1.8) * 0.2;
- pointLight.color.setHSL(hue, saturation, lightness);
+ // Reset all animation parameters
+ baseAmplitude = 0.05;
+ baseFrequency = 0.3;
+ basePull = new THREE.Vector3(0, 0, 1);
+ targetAmplitude = 0.05;
+ targetFrequency = 0.3;
+ targetPull = new THREE.Vector3(0, 0, 1);
+ transitionStartTime = 0;
}
-
- renderer.render(scene, camera);
- }
-
- // Helper function to get average frequency in a range
- function getAverageFrequency(dataArray, startIndex, endIndex) {
- let sum = 0;
- for (let i = startIndex; i <= endIndex; i++) {
- sum += dataArray[i];
- }
- return sum / (endIndex - startIndex + 1);
- }
-
- // Handle window resize
- window.addEventListener('resize', () => {
- camera.aspect = window.innerWidth / window.innerHeight;
- camera.updateProjectionMatrix();
- renderer.setSize(window.innerWidth, window.innerHeight);
});
-
- // Start animation loop
- animate();