Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>3D Visualization - Quantum Music</title> | |
| <link rel="icon" type="image/x-icon" href="/static/favicon.ico"> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/animejs/lib/anime.iife.min.js"></script> | |
| <script src="https://unpkg.com/feather-icons"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> | |
| <style> | |
| :root { | |
| --quantum-purple: #6e45e2; | |
| --quantum-teal: #88d3ce; | |
| --quantum-pink: #ff2a6d; | |
| --quantum-blue: #05d9e8; | |
| --quantum-dark: #0c0f1f; | |
| --quantum-darker: #080a1a; | |
| } | |
| .quantum-bg { | |
| background: linear-gradient(135deg, var(--quantum-dark), var(--quantum-darker)); | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .quantum-bg::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background: | |
| radial-gradient(circle at 10% 20%, rgba(110, 69, 226, 0.1) 0%, transparent 20%), | |
| radial-gradient(circle at 90% 80%, rgba(136, 211, 206, 0.1) 0%, transparent 20%), | |
| radial-gradient(circle at 50% 50%, rgba(5, 217, 232, 0.05) 0%, transparent 30%); | |
| z-index: 0; | |
| } | |
| .quantum-card { | |
| background: rgba(255, 255, 255, 0.05); | |
| backdrop-filter: blur(10px); | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| border-radius: 16px; | |
| transition: all 0.3s ease; | |
| } | |
| .quantum-card:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 10px 30px rgba(110, 69, 226, 0.2); | |
| border-color: rgba(110, 69, 226, 0.3); | |
| } | |
| .quantum-header { | |
| background: rgba(12, 15, 31, 0.8); | |
| backdrop-filter: blur(10px); | |
| position: sticky; | |
| top: 0; | |
| z-index: 100; | |
| border-bottom: 1px solid rgba(255, 255, 255, 0.05); | |
| } | |
| .quantum-nav a { | |
| position: relative; | |
| padding: 8px 0; | |
| } | |
| .quantum-nav a::after { | |
| content: ''; | |
| position: absolute; | |
| bottom: 0; | |
| left: 0; | |
| width: 0; | |
| height: 2px; | |
| background: var(--quantum-blue); | |
| transition: width 0.3s ease; | |
| } | |
| .quantum-nav a:hover::after { | |
| width: 100%; | |
| } | |
| #visualization-container { | |
| width: 100%; | |
| height: 500px; | |
| background: rgba(10, 12, 25, 0.7); | |
| border-radius: 12px; | |
| position: relative; | |
| overflow: hidden; | |
| border: 1px solid rgba(110, 69, 226, 0.2); | |
| } | |
| .control-panel { | |
| background: rgba(255, 255, 255, 0.05); | |
| backdrop-filter: blur(10px); | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| border-radius: 12px; | |
| padding: 1.5rem; | |
| } | |
| .legend-item { | |
| display: flex; | |
| align-items: center; | |
| margin-bottom: 1rem; | |
| } | |
| .legend-color { | |
| width: 20px; | |
| height: 20px; | |
| border-radius: 4px; | |
| margin-right: 10px; | |
| } | |
| .slider { | |
| width: 100%; | |
| height: 6px; | |
| background: rgba(255, 255, 255, 0.1); | |
| border-radius: 3px; | |
| outline: none; | |
| -webkit-appearance: none; | |
| } | |
| .slider::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| width: 18px; | |
| height: 18px; | |
| border-radius: 50%; | |
| background: var(--quantum-purple); | |
| cursor: pointer; | |
| box-shadow: 0 0 10px rgba(110, 69, 226, 0.5); | |
| } | |
| .quantum-btn { | |
| background: linear-gradient(45deg, var(--quantum-purple), var(--quantum-teal)); | |
| border: none; | |
| border-radius: 30px; | |
| padding: 10px 20px; | |
| font-weight: 600; | |
| transition: all 0.3s ease; | |
| color: white; | |
| width: 100%; | |
| } | |
| .quantum-btn:hover { | |
| transform: translateY(-3px); | |
| box-shadow: 0 5px 20px rgba(110, 69, 226, 0.4); | |
| } | |
| .note-detail { | |
| background: rgba(110, 69, 226, 0.1); | |
| border-left: 3px solid var(--quantum-purple); | |
| padding: 12px; | |
| border-radius: 0 8px 8px 0; | |
| margin-bottom: 10px; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-900 text-white min-h-screen"> | |
| <div class="quantum-bg min-h-screen"> | |
| <!-- Header --> | |
| <header class="quantum-header container mx-auto py-4 px-4"> | |
| <div class="flex justify-between items-center"> | |
| <h1 class="text-3xl font-bold">Quantum Music Synthesizer</h1> | |
| <nav class="quantum-nav"> | |
| <ul class="flex space-x-8"> | |
| <li><a href="index.html" class="hover:text-purple-300 font-medium">Home</a></li> | |
| <li><a href="synthesizer.html" class="hover:text-purple-300 font-medium">Synthesizer</a></li> | |
| <li><a href="quantum.html" class="hover:text-purple-300 font-medium">Quantum</a></li> | |
| <li><a href="visualization.html" class="hover:text-purple-300 font-medium">Visualization</a></li> | |
| <li><a href="about.html" class="hover:text-purple-300 font-medium">About</a></li> | |
| <li><a href="api.html" class="hover:text-purple-300 font-medium">API</a></li> | |
| <li><a href="agent.html" class="hover:text-purple-300 font-medium">Agent</a></li> | |
| </ul> | |
| </nav> | |
| </div> | |
| </header> | |
| <!-- Visualization Section --> | |
| <section class="container mx-auto py-16 px-4"> | |
| <h2 class="text-4xl font-bold text-center mb-12">3D Note Visualization</h2> | |
| <div class="max-w-7xl mx-auto quantum-card p-8"> | |
| <div class="grid grid-cols-1 lg:grid-cols-4 gap-8 mb-8"> | |
| <div class="lg:col-span-3"> | |
| <div id="visualization-container"></div> | |
| </div> | |
| <div class="space-y-6"> | |
| <div class="control-panel"> | |
| <h3 class="text-xl font-bold mb-4">Controls</h3> | |
| <div class="space-y-5"> | |
| <div> | |
| <label class="block mb-2">Rotation Speed</label> | |
| <input type="range" id="rotation-speed" min="0" max="2" step="0.1" value="0.5" class="slider"> | |
| </div> | |
| <div> | |
| <label class="block mb-2">Point Size</label> | |
| <input type="range" id="point-size" min="1" max="10" step="0.5" value="3" class="slider"> | |
| </div> | |
| <div> | |
| <label class="block mb-2">Animation Speed</label> | |
| <input type="range" id="animation-speed" min="0.1" max="2" step="0.1" value="1" class="slider"> | |
| </div> | |
| <div> | |
| <button id="reset-view" class="quantum-btn"> | |
| Reset View | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="control-panel"> | |
| <h3 class="text-xl font-bold mb-4">Selected Note</h3> | |
| <div id="note-details"> | |
| <p class="text-gray-400">Click on a note to see details</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-8"> | |
| <h3 class="text-2xl font-bold mb-6">Visualization Legend</h3> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-4"> | |
| <div class="legend-item"> | |
| <div class="legend-color" style="background: linear-gradient(to right, #6e45e2, #88d3ce);"></div> | |
| <div> | |
| <h4 class="font-bold">X-Axis: Frequency</h4> | |
| <p class="text-sm text-gray-400">Position represents note frequency (Hz)</p> | |
| </div> | |
| </div> | |
| <div class="legend-item"> | |
| <div class="legend-color" style="background: linear-gradient(to right, #ff2a6d, #d10068);"></div> | |
| <div> | |
| <h4 class="font-bold">Y-Axis: Amplitude</h4> | |
| <p class="text-sm text-gray-400">Height represents note volume</p> | |
| </div> | |
| </div> | |
| <div class="legend-item"> | |
| <div class="legend-color" style="background: linear-gradient(to right, #05d9e8, #00a8b5);"></div> | |
| <div> | |
| <h4 class="font-bold">Z-Axis: Time</h4> | |
| <p class="text-sm text-gray-400">Depth represents note sequence timing</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- Footer --> | |
| <footer class="container mx-auto py-8 px-4 text-center border-t border-gray-800"> | |
| <p class="text-gray-400">Powered by QuantumToolbox.jl, Amazon Braket, and Lindblad Solvers</p> | |
| </footer> | |
| </div> | |
| <script> | |
| feather.replace(); | |
| // Initialize Three.js scene | |
| let scene, camera, renderer, points; | |
| let rotationSpeed = 0.5; | |
| let pointSize = 3; | |
| let animationSpeed = 1; | |
| let noteData = []; | |
| function init() { | |
| // Create scene | |
| scene = new THREE.Scene(); | |
| scene.background = new THREE.Color(0x0c0f1f); | |
| // Create camera | |
| camera = new THREE.PerspectiveCamera(75, | |
| document.getElementById('visualization-container').clientWidth / | |
| document.getElementById('visualization-container').clientHeight, | |
| 0.1, 1000); | |
| camera.position.set(0, 15, 30); | |
| // Create renderer | |
| renderer = new THREE.WebGLRenderer({ antialias: true }); | |
| renderer.setSize( | |
| document.getElementById('visualization-container').clientWidth, | |
| document.getElementById('visualization-container').clientHeight | |
| ); | |
| document.getElementById('visualization-container').appendChild(renderer.domElement); | |
| // Add lighting | |
| const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); | |
| scene.add(ambientLight); | |
| const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); | |
| directionalLight.position.set(20, 20, 20); | |
| scene.add(directionalLight); | |
| // Add subtle fog | |
| scene.fog = new THREE.Fog(0x0c0f1f, 20, 100); | |
| // Generate sample note data | |
| generateNoteData(); | |
| // Create 3D points | |
| createPoints(); | |
| // Add grid helper | |
| const gridHelper = new THREE.GridHelper(40, 20, 0x444444, 0x222222); | |
| gridHelper.position.y = -5; | |
| scene.add(gridHelper); | |
| // Add axes helper | |
| const axesHelper = new THREE.AxesHelper(20); | |
| scene.add(axesHelper); | |
| // Add event listeners | |
| setupEventListeners(); | |
| // Start animation | |
| animate(); | |
| } | |
| function generateNoteData() { | |
| // Generate sample musical sequence data | |
| const baseNotes = [261.63, 293.66, 329.63, 349.23, 392.00, 440.00, 493.88, 523.25]; // C4 to C5 | |
| const durations = [0.5, 1.0, 1.5, 2.0]; | |
| for (let i = 0; i < 100; i++) { | |
| noteData.push({ | |
| frequency: baseNotes[Math.floor(Math.random() * baseNotes.length)] + (Math.random() * 200), | |
| amplitude: Math.random(), | |
| duration: durations[Math.floor(Math.random() * durations.length)], | |
| time: i * 0.3, | |
| color: new THREE.Color( | |
| 0.5 + 0.5 * Math.sin(i * 0.1), | |
| 0.3 + 0.3 * Math.cos(i * 0.15), | |
| 0.7 + 0.3 * Math.sin(i * 0.2) | |
| ) | |
| }); | |
| } | |
| } | |
| function createPoints() { | |
| const positions = new Float32Array(noteData.length * 3); | |
| const colors = new Float32Array(noteData.length * 3); | |
| // Normalize data for visualization | |
| const maxFreq = Math.max(...noteData.map(n => n.frequency)); | |
| const minFreq = Math.min(...noteData.map(n => n.frequency)); | |
| const maxTime = Math.max(...noteData.map(n => n.time)); | |
| const maxAmplitude = Math.max(...noteData.map(n => n.amplitude)); | |
| for (let i = 0; i < noteData.length; i++) { | |
| // Map frequency to X-axis (-20 to 20) | |
| positions[i * 3] = ((noteData[i].frequency - minFreq) / (maxFreq - minFreq)) * 40 - 20; | |
| // Map amplitude to Y-axis (0 to 30) | |
| positions[i * 3 + 1] = (noteData[i].amplitude / maxAmplitude) * 30; | |
| // Map time to Z-axis (-20 to 20) | |
| positions[i * 3 + 2] = (noteData[i].time / maxTime) * 40 - 20; | |
| // Set colors | |
| colors[i * 3] = noteData[i].color.r; | |
| colors[i * 3 + 1] = noteData[i].color.g; | |
| colors[i * 3 + 2] = noteData[i].color.b; | |
| } | |
| // Create geometry | |
| const geometry = new THREE.BufferGeometry(); | |
| geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); | |
| geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3)); | |
| // Create material | |
| const material = new THREE.PointsMaterial({ | |
| size: pointSize, | |
| vertexColors: true, | |
| transparent: true, | |
| opacity: 0.9, | |
| sizeAttenuation: true | |
| }); | |
| // Create points | |
| points = new THREE.Points(geometry, material); | |
| scene.add(points); | |
| } | |
| function setupEventListeners() { | |
| // Rotation speed control | |
| document.getElementById('rotation-speed').addEventListener('input', function() { | |
| rotationSpeed = parseFloat(this.value); | |
| }); | |
| // Point size control | |
| document.getElementById('point-size').addEventListener('input', function() { | |
| pointSize = parseFloat(this.value); | |
| points.material.size = pointSize; | |
| }); | |
| // Animation speed control | |
| document.getElementById('animation-speed').addEventListener('input', function() { | |
| animationSpeed = parseFloat(this.value); | |
| }); | |
| // Reset view button | |
| document.getElementById('reset-view').addEventListener('click', function() { | |
| camera.position.set(0, 15, 30); | |
| camera.lookAt(0, 0, 0); | |
| }); | |
| // Handle window resize | |
| window.addEventListener('resize', function() { | |
| camera.aspect = document.getElementById('visualization-container').clientWidth / | |
| document.getElementById('visualization-container').clientHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize( | |
| document.getElementById('visualization-container').clientWidth, | |
| document.getElementById('visualization-container').clientHeight | |
| ); | |
| }); | |
| // Mouse interaction for note selection | |
| const raycaster = new THREE.Raycaster(); | |
| const mouse = new THREE.Vector2(); | |
| document.getElementById('visualization-container').addEventListener('click', function(event) { | |
| // Calculate mouse position in normalized device coordinates | |
| const rect = renderer.domElement.getBoundingClientRect(); | |
| mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1; | |
| mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1; | |
| // Update the picking ray with the camera and mouse position | |
| raycaster.setFromCamera(mouse, camera); | |
| // Calculate objects intersecting the picking ray | |
| const intersects = raycaster.intersectObject(points); | |
| if (intersects.length > 0) { | |
| const index = intersects[0].index; | |
| const note = noteData[index]; | |
| document.getElementById('note-details').innerHTML = ` | |
| <div class="note-detail"> | |
| <p><strong>Frequency:</strong> ${note.frequency.toFixed(2)} Hz</p> | |
| </div> | |
| <div class="note-detail"> | |
| <p><strong>Amplitude:</strong> ${(note.amplitude * 100).toFixed(0)}%</p> | |
| </div> | |
| <div class="note-detail"> | |
| <p><strong>Duration:</strong> ${note.duration} seconds</p> | |
| </div> | |
| <div class="note-detail"> | |
| <p><strong>Time:</strong> ${note.time.toFixed(1)} seconds</p> | |
| </div> | |
| `; | |
| } | |
| }); | |
| } | |
| function animate() { | |
| requestAnimationFrame(animate); | |
| // Rotate the entire scene | |
| if (points) { | |
| points.rotation.y += 0.005 * rotationSpeed * animationSpeed; | |
| } | |
| // Add subtle floating animation to points | |
| if (points) { | |
| const positions = points.geometry.attributes.position.array; | |
| for (let i = 1; i < positions.length; i += 3) { | |
| positions[i] += Math.sin(Date.now() * 0.001 + i) * 0.01 * animationSpeed; | |
| } | |
| points.geometry.attributes.position.needsUpdate = true; | |
| } | |
| renderer.render(scene, camera); | |
| } | |
| // Initialize when page loads | |
| window.addEventListener('load', init); | |
| </script> | |
| </body> | |
| </html> | |