| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>MS1-V</title> |
| <link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500&display=swap" rel="stylesheet"> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> |
| <style> |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| } |
| body { |
| overflow: hidden; |
| background: #000; |
| font-family: 'Orbitron', sans-serif; |
| } |
| canvas { |
| position: fixed; |
| top: 0; |
| left: 0; |
| } |
| .title-screen { |
| position: fixed; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| background: #000; |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| flex-direction: column; |
| color: #0ff; |
| text-align: center; |
| z-index: 20; |
| transition: opacity 1s ease-in-out; |
| } |
| .title-screen h1 { |
| font-size: 5em; |
| margin-bottom: 20px; |
| } |
| .title-screen button { |
| padding: 15px 30px; |
| background: rgba(0, 255, 255, 0.2); |
| border: 2px solid #0ff; |
| color: #0ff; |
| cursor: pointer; |
| font-size: 1.2em; |
| border-radius: 10px; |
| transition: all 0.3s; |
| } |
| .title-screen button:hover { |
| background: #0ff; |
| color: #000; |
| } |
| .interface { |
| position: fixed; |
| top: 20px; |
| left: 20px; |
| background: rgba(0, 0, 0, 0.8); |
| border: 2px solid #0ff; |
| padding: 20px; |
| color: #0ff; |
| z-index: 10; |
| display: none; |
| border-radius: 15px; |
| backdrop-filter: blur(5px); |
| } |
| .controls { |
| margin-top: 15px; |
| display: flex; |
| gap: 10px; |
| } |
| .controls button { |
| flex: 1; |
| padding: 12px; |
| background: rgba(0, 255, 255, 0.2); |
| border: 1px solid #0ff; |
| color: #0ff; |
| cursor: pointer; |
| transition: all 0.3s; |
| border-radius: 8px; |
| text-transform: uppercase; |
| letter-spacing: 1px; |
| } |
| .controls button:hover { |
| background: #0ff; |
| color: #000; |
| } |
| .toggle-button { |
| position: fixed; |
| top: 20px; |
| right: 20px; |
| padding: 12px 20px; |
| background: rgba(0, 255, 255, 0.2); |
| color: #0ff; |
| border: 1px solid #0ff; |
| cursor: pointer; |
| z-index: 10; |
| border-radius: 8px; |
| text-transform: uppercase; |
| letter-spacing: 1px; |
| } |
| .toggle-button:hover { |
| background: #0ff; |
| color: #000; |
| } |
| .track-list { |
| margin-top: 20px; |
| } |
| .track-list select { |
| padding: 10px; |
| background: rgba(0, 255, 255, 0.2); |
| border: 1px solid #0ff; |
| color: #0ff; |
| border-radius: 8px; |
| width: 100%; |
| } |
| </style> |
| </head> |
| <body> |
| <div class="title-screen" id="titleScreen"> |
| <h1>MS1-V</h1> |
| <button id="startButton">Start</button> |
| </div> |
| <button class="toggle-button">Controls</button> |
| <div class="interface"> |
| <input type="file" id="audioFile" accept="audio/*"> |
| <div class="track-list"> |
| <label for="trackSelect">Select Track:</label> |
| <select id="trackSelect"> |
| <option value="">Select a track</option> |
| <option value="track1.mp3">Track 1</option> |
| <option value="track2.mp3">Track 2</option> |
| <option value="track3.mp3">Track 3</option> |
| </select> |
| </div> |
| <div class="controls"> |
| <button id="play">Play</button> |
| <button id="pause">Pause</button> |
| <button id="stop">Stop</button> |
| </div> |
| </div> |
| <script> |
| const scene = new THREE.Scene(); |
| const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 2000); |
| const renderer = new THREE.WebGLRenderer({ antialias: true }); |
| renderer.setSize(window.innerWidth, window.innerHeight); |
| document.body.appendChild(renderer.domElement); |
| |
| let isDragging = false; |
| let previousMousePosition = { x: 0, y: 0 }; |
| let cameraAngle = { x: 0, y: 0 }; |
| const cameraDistance = 100; |
| |
| document.addEventListener('mousedown', (e) => { |
| isDragging = true; |
| previousMousePosition = { x: e.clientX, y: e.clientY }; |
| }); |
| |
| document.addEventListener('mousemove', (e) => { |
| if (!isDragging) return; |
| |
| const deltaMove = { |
| x: e.clientX - previousMousePosition.x, |
| y: e.clientY - previousMousePosition.y |
| }; |
| |
| cameraAngle.x += deltaMove.y * 0.005; |
| cameraAngle.y += deltaMove.x * 0.005; |
| |
| cameraAngle.x = Math.max(-Math.PI/2, Math.min(Math.PI/2, cameraAngle.x)); |
| |
| previousMousePosition = { x: e.clientX, y: e.clientY }; |
| }); |
| |
| document.addEventListener('mouseup', () => { |
| isDragging = false; |
| }); |
| |
| const lineMaterial = new THREE.LineBasicMaterial({ |
| color: 0x00ffff, |
| transparent: true, |
| opacity: 0.3, |
| blending: THREE.AdditiveBlending |
| }); |
| |
| function createBassPattern(frequency, intensity) { |
| const pattern = new THREE.Group(); |
| const geometry = new THREE.TorusKnotGeometry(10 * intensity, 2, 100, 16); |
| const material = new THREE.MeshBasicMaterial({ |
| color: 0x00ffff, |
| wireframe: true, |
| transparent: true, |
| opacity: intensity |
| }); |
| const torusKnot = new THREE.Mesh(geometry, material); |
| pattern.add(torusKnot); |
| |
| for(let i = 0; i < 3; i++) { |
| const sphereGeo = new THREE.SphereGeometry(5 * intensity, 32, 32); |
| const sphereMat = new THREE.MeshBasicMaterial({ |
| color: 0x00ffff, |
| wireframe: true, |
| transparent: true, |
| opacity: intensity * 0.5 |
| }); |
| const sphere = new THREE.Mesh(sphereGeo, sphereMat); |
| sphere.position.setFromSphericalCoords( |
| 20 * intensity, |
| i * Math.PI * 2/3, |
| frequency * Math.PI |
| ); |
| pattern.add(sphere); |
| } |
| return pattern; |
| } |
| |
| function createParticleSystem(count, size, spread) { |
| const geometry = new THREE.BufferGeometry(); |
| const positions = new Float32Array(count * 3); |
| const colors = new Float32Array(count * 3); |
| const scales = new Float32Array(count); |
| |
| for(let i = 0; i < count * 3; i += 3) { |
| positions[i] = (Math.random() - 0.5) * spread; |
| positions[i + 1] = (Math.random() - 0.5) * spread; |
| positions[i + 2] = (Math.random() - 0.5) * spread; |
| scales[i/3] = Math.random(); |
| } |
| |
| geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); |
| geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3)); |
| geometry.setAttribute('scale', new THREE.BufferAttribute(scales, 1)); |
| |
| const material = new THREE.PointsMaterial({ |
| size: size, |
| vertexColors: true, |
| blending: THREE.AdditiveBlending, |
| transparent: true, |
| opacity: 0.8, |
| }); |
| |
| const particles = new THREE.Points(geometry, material); |
| const lines = new THREE.LineSegments(new THREE.BufferGeometry(), lineMaterial); |
| |
| return { particles, lines }; |
| } |
| |
| const innerSystem = createParticleSystem(3000, 0.5, 80); |
| const midSystem = createParticleSystem(4000, 0.3, 150); |
| const outerSystem = createParticleSystem(5000, 0.2, 200); |
| const bassSystem = createParticleSystem(2000, 1.0, 100); |
| |
| let bassPattern = null; |
| |
| [innerSystem, midSystem, outerSystem, bassSystem].forEach(system => { |
| scene.add(system.particles); |
| scene.add(system.lines); |
| }); |
| |
| camera.position.z = cameraDistance; |
| |
| function updateParticleSystem(system, frequencyData, baseIndex, scale, bass, time) { |
| const positions = system.particles.geometry.attributes.position.array; |
| const colors = system.particles.geometry.attributes.color.array; |
| const scales = system.particles.geometry.attributes.scale.array; |
| |
| const evolutionFactor = Math.sin(time * 0.0005) * 0.5 + 0.5; |
| |
| const linePositions = []; |
| const lineIndices = []; |
| |
| for(let i = 0; i < positions.length; i += 3) { |
| const freqIndex = (baseIndex + Math.floor(i/30)) % frequencyData.length; |
| const frequency = frequencyData[freqIndex] / 255; |
| |
| const angle = (i/3) * 0.1 + time * 0.001; |
| const radius = scale * (0.5 + 0.5 * frequency); |
| |
| if(bass > 0.7) { |
| const bassImpact = Math.pow(bass - 0.7, 2); |
| positions[i] = Math.cos(angle) * radius * Math.sin(angle * 2 + evolutionFactor) * (1 + bassImpact); |
| positions[i + 1] = Math.sin(angle) * radius * Math.cos(angle * 3 + evolutionFactor) * (1 + bassImpact); |
| positions[i + 2] = Math.cos(angle * 2) * radius * Math.sin(angle + evolutionFactor) * (1 + bassImpact); |
| } else { |
| positions[i] *= 1 + frequency * 0.02 * (1 + evolutionFactor); |
| positions[i + 1] *= 1 + frequency * 0.02 * (1 + evolutionFactor); |
| positions[i + 2] *= 1 + frequency * 0.02 * (1 + evolutionFactor); |
| } |
| |
| const distance = Math.sqrt(positions[i]**2 + positions[i+1]**2 + positions[i+2]**2); |
| if(distance > scale) { |
| positions[i] *= scale/distance; |
| positions[i+1] *= scale/distance; |
| positions[i+2] *= scale/distance; |
| } |
| |
| colors[i] = Math.sin(frequency * Math.PI + bass + evolutionFactor) * 0.5 + 0.5; |
| colors[i + 1] = Math.cos(frequency * Math.PI * 0.5 + bass + evolutionFactor) * 0.5 + 0.5; |
| colors[i + 2] = frequency + bass * 0.5; |
| |
| scales[i/3] = frequency * (2 + bass * 2) * (1 + evolutionFactor); |
| |
| if(i % 9 === 0) { |
| linePositions.push(positions[i], positions[i+1], positions[i+2]); |
| if(linePositions.length > 6) { |
| lineIndices.push(linePositions.length/3 - 2, linePositions.length/3 - 1); |
| } |
| } |
| } |
| |
| const lineGeometry = new THREE.BufferGeometry(); |
| lineGeometry.setAttribute('position', new THREE.Float32BufferAttribute(linePositions, 3)); |
| lineGeometry.setIndex(lineIndices); |
| system.lines.geometry.dispose(); |
| system.lines.geometry = lineGeometry; |
| |
| system.particles.geometry.attributes.position.needsUpdate = true; |
| system.particles.geometry.attributes.color.needsUpdate = true; |
| system.particles.geometry.attributes.scale.needsUpdate = true; |
| } |
| |
| let audioContext, audioAnalyser, audioSource; |
| let audioElement = new Audio(); |
| |
| document.getElementById('audioFile').addEventListener('change', function(event) { |
| audioElement.src = URL.createObjectURL(event.target.files[0]); |
| }); |
| |
| document.getElementById('trackSelect').addEventListener('change', function(event) { |
| audioElement.src = event.target.value; |
| }); |
| |
| document.getElementById('startButton').addEventListener('click', () => { |
| document.getElementById('titleScreen').style.opacity = 0; |
| setTimeout(() => { |
| document.getElementById('titleScreen').style.display = 'none'; |
| }, 1000); |
| }); |
| |
| document.getElementById('play').addEventListener('click', () => audioElement.play()); |
| document.getElementById('pause').addEventListener('click', () => audioElement.pause()); |
| document.getElementById('stop').addEventListener('click', () => { |
| audioElement.pause(); |
| audioElement.currentTime = 0; |
| }); |
| |
| document.querySelector('.toggle-button').addEventListener('click', () => { |
| const interfaceDiv = document.querySelector('.interface'); |
| interfaceDiv.style.display = interfaceDiv.style.display === 'none' ? 'block' : 'none'; |
| }); |
| |
| audioElement.addEventListener('play', () => { |
| audioContext = new (window.AudioContext || window.webkitAudioContext)(); |
| audioSource = audioContext.createMediaElementSource(audioElement); |
| audioAnalyser = audioContext.createAnalyser(); |
| audioSource.connect(audioAnalyser); |
| audioAnalyser.connect(audioContext.destination); |
| audioAnalyser.fftSize = 2048; |
| const bufferLength = audioAnalyser.frequencyBinCount; |
| const dataArray = new Uint8Array(bufferLength); |
| |
| function animate() { |
| requestAnimationFrame(animate); |
| const time = Date.now(); |
| audioAnalyser.getByteFrequencyData(dataArray); |
| |
| const bass = Math.max(...dataArray.slice(0, 10)) / 255; |
| const mid = Math.max(...dataArray.slice(10, 100)) / 255; |
| const treble = Math.max(...dataArray.slice(100, 200)) / 255; |
| |
| camera.position.x = cameraDistance * Math.sin(cameraAngle.y) * Math.cos(cameraAngle.x); |
| camera.position.y = cameraDistance * Math.sin(cameraAngle.x); |
| camera.position.z = cameraDistance * Math.cos(cameraAngle.y) * Math.cos(cameraAngle.x); |
| camera.lookAt(scene.position); |
| |
| if(bass > 0.7) { |
| if(!bassPattern) { |
| bassPattern = createBassPattern(bass, bass - 0.7); |
| scene.add(bassPattern); |
| } |
| bassPattern.scale.set(1 + bass, 1 + bass, 1 + bass); |
| bassPattern.rotation.x += 0.02; |
| bassPattern.rotation.y += 0.03; |
| } else if(bassPattern) { |
| scene.remove(bassPattern); |
| bassPattern = null; |
| } |
| |
| updateParticleSystem(innerSystem, dataArray, 0, 80, bass, time); |
| updateParticleSystem(midSystem, dataArray, 100, 150, bass, time); |
| updateParticleSystem(outerSystem, dataArray, 200, 200, bass, time); |
| updateParticleSystem(bassSystem, dataArray.slice(0, 10), 0, 100 * bass, bass, time); |
| |
| innerSystem.particles.rotation.y += 0.003 * (1 + bass * 0.5); |
| midSystem.particles.rotation.x += 0.002 * (1 + mid * 0.5); |
| outerSystem.particles.rotation.z += 0.001 * (1 + treble * 0.5); |
| if(bassSystem.particles) { |
| bassSystem.particles.rotation.y -= 0.005 * (1 + bass); |
| } |
| |
| renderer.render(scene, camera); |
| } |
| animate(); |
| }); |
| |
| window.addEventListener('resize', () => { |
| camera.aspect = window.innerWidth / window.innerHeight; |
| camera.updateProjectionMatrix(); |
| renderer.setSize(window.innerWidth, window.innerHeight); |
| }); |
| </script> |
| </body> |
| </html> |
|
|