yokoha commited on
Commit
a32ef17
·
verified ·
1 Parent(s): 58ba668

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +724 -19
index.html CHANGED
@@ -1,19 +1,724 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>3D Audio Spectrum Analyzer</title>
8
+ <style>
9
+ :root {
10
+ --primary-color: #1a1a2e;
11
+ --secondary-color: #16213e;
12
+ --accent-color: #0f3460;
13
+ --text-color: #e7e7e7;
14
+ --highlight-color: #4cc9f0;
15
+ --gradient-1: #4361ee;
16
+ --gradient-2: #3a0ca3;
17
+ --gradient-3: #7209b7;
18
+ --gradient-4: #f72585;
19
+ }
20
+
21
+ * {
22
+ margin: 0;
23
+ padding: 0;
24
+ box-sizing: border-box;
25
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
26
+ }
27
+
28
+ body {
29
+ background-color: var(--primary-color);
30
+ color: var(--text-color);
31
+ overflow: hidden;
32
+ display: flex;
33
+ flex-direction: column;
34
+ min-height: 100vh;
35
+ }
36
+
37
+ header {
38
+ background-color: var(--secondary-color);
39
+ padding: 1rem;
40
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
41
+ z-index: 10;
42
+ }
43
+
44
+ .title {
45
+ text-align: center;
46
+ font-size: 1.8rem;
47
+ font-weight: 600;
48
+ color: var(--highlight-color);
49
+ letter-spacing: 1px;
50
+ text-transform: uppercase;
51
+ }
52
+
53
+ .canvas-container {
54
+ flex: 1;
55
+ position: relative;
56
+ }
57
+
58
+ #visualizer {
59
+ position: absolute;
60
+ top: 0;
61
+ left: 0;
62
+ width: 100%;
63
+ height: 100%;
64
+ }
65
+
66
+ .control-panel {
67
+ position: absolute;
68
+ top: 1rem;
69
+ right: 1rem;
70
+ background-color: rgba(22, 33, 62, 0.8);
71
+ backdrop-filter: blur(10px);
72
+ border-radius: 12px;
73
+ padding: 1.2rem;
74
+ width: 280px;
75
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
76
+ z-index: 100;
77
+ transition: transform 0.3s ease;
78
+ }
79
+
80
+ .control-panel.collapsed {
81
+ transform: translateX(calc(100% - 50px));
82
+ }
83
+
84
+ .toggle-panel {
85
+ position: absolute;
86
+ left: 10px;
87
+ top: 50%;
88
+ transform: translateY(-50%);
89
+ background-color: var(--accent-color);
90
+ border: none;
91
+ color: var(--text-color);
92
+ width: 30px;
93
+ height: 30px;
94
+ border-radius: 50%;
95
+ cursor: pointer;
96
+ display: flex;
97
+ align-items: center;
98
+ justify-content: center;
99
+ font-size: 1.2rem;
100
+ transition: background-color 0.2s ease;
101
+ }
102
+
103
+ .toggle-panel:hover {
104
+ background-color: var(--highlight-color);
105
+ }
106
+
107
+ h2 {
108
+ margin-bottom: 1rem;
109
+ font-size: 1.3rem;
110
+ color: var(--highlight-color);
111
+ text-align: center;
112
+ }
113
+
114
+ .control-group {
115
+ margin-bottom: 1.2rem;
116
+ }
117
+
118
+ .control-label {
119
+ display: block;
120
+ margin-bottom: 0.5rem;
121
+ font-weight: 500;
122
+ font-size: 0.9rem;
123
+ color: var(--text-color);
124
+ }
125
+
126
+ .btn {
127
+ background-color: var(--accent-color);
128
+ color: var(--text-color);
129
+ border: none;
130
+ border-radius: 8px;
131
+ padding: 0.7rem 1rem;
132
+ font-size: 1rem;
133
+ cursor: pointer;
134
+ transition: all 0.2s ease;
135
+ display: inline-flex;
136
+ align-items: center;
137
+ justify-content: center;
138
+ gap: 0.5rem;
139
+ width: 100%;
140
+ }
141
+
142
+ .btn:hover {
143
+ background-color: var(--highlight-color);
144
+ color: var(--primary-color);
145
+ }
146
+
147
+ .btn:disabled {
148
+ opacity: 0.6;
149
+ cursor: not-allowed;
150
+ }
151
+
152
+ .btn-record {
153
+ background-color: #e63946;
154
+ }
155
+
156
+ .btn-record:hover {
157
+ background-color: #ff6b6b;
158
+ }
159
+
160
+ .btn-record.recording {
161
+ animation: pulse 1.5s infinite;
162
+ }
163
+
164
+ @keyframes pulse {
165
+ 0% {
166
+ background-color: #e63946;
167
+ }
168
+ 50% {
169
+ background-color: #ff6b6b;
170
+ }
171
+ 100% {
172
+ background-color: #e63946;
173
+ }
174
+ }
175
+
176
+ .range-slider {
177
+ width: 100%;
178
+ margin: 0.5rem 0;
179
+ }
180
+
181
+ .slider-container {
182
+ display: flex;
183
+ align-items: center;
184
+ gap: 1rem;
185
+ }
186
+
187
+ .slider-value {
188
+ width: 50px;
189
+ text-align: center;
190
+ font-size: 0.9rem;
191
+ color: var(--highlight-color);
192
+ }
193
+
194
+ .radio-group {
195
+ display: flex;
196
+ gap: 0.8rem;
197
+ margin-top: 0.5rem;
198
+ }
199
+
200
+ .radio-option {
201
+ display: flex;
202
+ align-items: center;
203
+ gap: 0.3rem;
204
+ cursor: pointer;
205
+ }
206
+
207
+ .radio-option input {
208
+ cursor: pointer;
209
+ }
210
+
211
+ .status-indicator {
212
+ display: flex;
213
+ align-items: center;
214
+ gap: 0.5rem;
215
+ margin-top: 1rem;
216
+ padding: 0.5rem;
217
+ border-radius: 8px;
218
+ background-color: rgba(15, 52, 96, 0.5);
219
+ }
220
+
221
+ .status-dot {
222
+ width: 10px;
223
+ height: 10px;
224
+ border-radius: 50%;
225
+ background-color: #6c757d;
226
+ }
227
+
228
+ .status-dot.active {
229
+ background-color: #4cc9f0;
230
+ box-shadow: 0 0 8px #4cc9f0;
231
+ animation: glow 1.5s infinite alternate;
232
+ }
233
+
234
+ @keyframes glow {
235
+ from {
236
+ box-shadow: 0 0 5px #4cc9f0;
237
+ }
238
+ to {
239
+ box-shadow: 0 0 12px #4cc9f0;
240
+ }
241
+ }
242
+
243
+ .status-text {
244
+ font-size: 0.9rem;
245
+ }
246
+
247
+ @media (max-width: 768px) {
248
+ .control-panel {
249
+ width: 250px;
250
+ }
251
+
252
+ .title {
253
+ font-size: 1.5rem;
254
+ }
255
+ }
256
+ </style>
257
+ </head>
258
+ <body>
259
+ <header>
260
+ <h1 class="title">3D Audio Spectrum Analyzer</h1>
261
+ </header>
262
+
263
+ <div class="canvas-container">
264
+ <canvas id="visualizer"></canvas>
265
+ </div>
266
+
267
+ <div class="control-panel">
268
+ <button class="toggle-panel">≡</button>
269
+ <h2>Control Panel</h2>
270
+
271
+ <div class="control-group">
272
+ <button id="startBtn" class="btn btn-record">
273
+ <span class="btn-icon">◉</span> Start Microphone
274
+ </button>
275
+ </div>
276
+
277
+ <div class="control-group">
278
+ <label class="control-label">Color Scheme</label>
279
+ <div class="radio-group">
280
+ <label class="radio-option">
281
+ <input type="radio" name="colorScheme" value="blue" checked>
282
+ <span>Blue</span>
283
+ </label>
284
+ <label class="radio-option">
285
+ <input type="radio" name="colorScheme" value="red">
286
+ <span>Red</span>
287
+ </label>
288
+ <label class="radio-option">
289
+ <input type="radio" name="colorScheme" value="rainbow">
290
+ <span>Rainbow</span>
291
+ </label>
292
+ </div>
293
+ </div>
294
+
295
+ <div class="control-group">
296
+ <label class="control-label">Frequency Range</label>
297
+ <div class="slider-container">
298
+ <input type="range" class="range-slider" id="frequencyRange" min="0" max="100" value="100">
299
+ <span class="slider-value" id="frequencyValue">100%</span>
300
+ </div>
301
+ </div>
302
+
303
+ <div class="control-group">
304
+ <label class="control-label">Sensitivity</label>
305
+ <div class="slider-container">
306
+ <input type="range" class="range-slider" id="sensitivityRange" min="1" max="10" value="5">
307
+ <span class="slider-value" id="sensitivityValue">5</span>
308
+ </div>
309
+ </div>
310
+
311
+ <div class="control-group">
312
+ <label class="control-label">3D Effect Depth</label>
313
+ <div class="slider-container">
314
+ <input type="range" class="range-slider" id="depthRange" min="1" max="10" value="5">
315
+ <span class="slider-value" id="depthValue">5</span>
316
+ </div>
317
+ </div>
318
+
319
+ <div class="status-indicator">
320
+ <div class="status-dot" id="statusDot"></div>
321
+ <div class="status-text" id="statusText">Waiting for microphone...</div>
322
+ </div>
323
+ </div>
324
+
325
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script>
326
+
327
+ <script>
328
+ class AudioVisualizer {
329
+ constructor() {
330
+ // DOM elements
331
+ this.canvas = document.getElementById('visualizer');
332
+ this.startBtn = document.getElementById('startBtn');
333
+ this.statusDot = document.getElementById('statusDot');
334
+ this.statusText = document.getElementById('statusText');
335
+ this.frequencyRange = document.getElementById('frequencyRange');
336
+ this.frequencyValue = document.getElementById('frequencyValue');
337
+ this.sensitivityRange = document.getElementById('sensitivityRange');
338
+ this.sensitivityValue = document.getElementById('sensitivityValue');
339
+ this.depthRange = document.getElementById('depthRange');
340
+ this.depthValue = document.getElementById('depthValue');
341
+ this.togglePanel = document.querySelector('.toggle-panel');
342
+ this.controlPanel = document.querySelector('.control-panel');
343
+ this.colorOptions = document.querySelectorAll('input[name="colorScheme"]');
344
+
345
+ // Audio context and analyzer
346
+ this.audioContext = null;
347
+ this.analyser = null;
348
+ this.dataArray = null;
349
+ this.source = null;
350
+ this.isRecording = false;
351
+
352
+ // Three.js variables
353
+ this.scene = null;
354
+ this.camera = null;
355
+ this.renderer = null;
356
+ this.terrain = null;
357
+ this.colorScheme = 'blue';
358
+ this.maxFrequencyPercent = 100;
359
+ this.sensitivity = 5;
360
+ this.depth = 5;
361
+
362
+ // Grid size for the 3D visualization
363
+ this.gridSize = 64;
364
+ this.vertices = [];
365
+ this.heights = [];
366
+
367
+ this.init();
368
+ }
369
+
370
+ init() {
371
+ // Initialize Three.js
372
+ this.initThree();
373
+
374
+ // Initialize UI controls
375
+ this.initControls();
376
+
377
+ // Start render loop
378
+ this.animate();
379
+
380
+ // Handle window resize
381
+ window.addEventListener('resize', () => this.onWindowResize());
382
+ }
383
+
384
+ initThree() {
385
+ // Create scene
386
+ this.scene = new THREE.Scene();
387
+
388
+ // Create camera
389
+ this.camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
390
+ this.camera.position.set(0, 25, 50);
391
+ this.camera.lookAt(0, 0, 0);
392
+
393
+ // Create renderer
394
+ this.renderer = new THREE.WebGLRenderer({ canvas: this.canvas, antialias: true });
395
+ this.renderer.setSize(window.innerWidth, window.innerHeight);
396
+ this.renderer.setClearColor(0x1a1a2e);
397
+
398
+ // Add ambient light
399
+ const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);
400
+ this.scene.add(ambientLight);
401
+
402
+ // Add directional light
403
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
404
+ directionalLight.position.set(1, 1, 1);
405
+ this.scene.add(directionalLight);
406
+
407
+ // Create the terrain mesh
408
+ this.createTerrain();
409
+ }
410
+
411
+ createTerrain() {
412
+ // Create grid geometry
413
+ const geometry = new THREE.PlaneGeometry(60, 60, this.gridSize - 1, this.gridSize - 1);
414
+ geometry.rotateX(-Math.PI / 2);
415
+
416
+ // Store original vertices
417
+ this.vertices = geometry.attributes.position.array;
418
+ this.heights = new Array(this.vertices.length / 3).fill(0);
419
+
420
+ // Create material
421
+ const material = new THREE.MeshStandardMaterial({
422
+ color: 0x4cc9f0,
423
+ wireframe: false,
424
+ flatShading: true,
425
+ metalness: 0.3,
426
+ roughness: 0.7,
427
+ vertexColors: true
428
+ });
429
+
430
+ // Create vertex colors
431
+ const count = geometry.attributes.position.count;
432
+ const colors = new Float32Array(count * 3);
433
+
434
+ for (let i = 0; i < count; i++) {
435
+ const x = geometry.attributes.position.array[i * 3];
436
+ const z = geometry.attributes.position.array[i * 3 + 2];
437
+ const distance = Math.sqrt(x * x + z * z);
438
+ const normalizedDistance = Math.min(1, distance / 30);
439
+
440
+ // Default blue color scheme
441
+ colors[i * 3] = 0.2 + normalizedDistance * 0.2; // R
442
+ colors[i * 3 + 1] = 0.5 + normalizedDistance * 0.3; // G
443
+ colors[i * 3 + 2] = 0.8; // B
444
+ }
445
+
446
+ geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
447
+
448
+ // Create and add mesh
449
+ this.terrain = new THREE.Mesh(geometry, material);
450
+ this.terrain.position.y = -10;
451
+ this.scene.add(this.terrain);
452
+ }
453
+
454
+ updateTerrainColors() {
455
+ const colors = this.terrain.geometry.attributes.color.array;
456
+ const count = this.terrain.geometry.attributes.position.count;
457
+
458
+ for (let i = 0; i < count; i++) {
459
+ const x = this.terrain.geometry.attributes.position.array[i * 3];
460
+ const z = this.terrain.geometry.attributes.position.array[i * 3 + 2];
461
+ const distance = Math.sqrt(x * x + z * z);
462
+ const normalizedDistance = Math.min(1, distance / 30);
463
+ const height = this.heights[i] / 10; // Normalized height
464
+
465
+ // Apply different color schemes
466
+ if (this.colorScheme === 'blue') {
467
+ colors[i * 3] = 0.2 + normalizedDistance * 0.2; // R
468
+ colors[i * 3 + 1] = 0.5 + normalizedDistance * 0.3; // G
469
+ colors[i * 3 + 2] = 0.8 - height * 0.3; // B
470
+ } else if (this.colorScheme === 'red') {
471
+ colors[i * 3] = 0.8 - height * 0.3; // R
472
+ colors[i * 3 + 1] = 0.2 + normalizedDistance * 0.2; // G
473
+ colors[i * 3 + 2] = 0.3 + normalizedDistance * 0.3; // B
474
+ } else if (this.colorScheme === 'rainbow') {
475
+ // Rainbow color scheme
476
+ const hue = (normalizedDistance + height) * 360;
477
+ const saturation = 0.8;
478
+ const lightness = 0.5 + height * 0.3;
479
+
480
+ // Convert HSL to RGB
481
+ const c = (1 - Math.abs(2 * lightness - 1)) * saturation;
482
+ const x = c * (1 - Math.abs((hue / 60) % 2 - 1));
483
+ const m = lightness - c / 2;
484
+ let r, g, b;
485
+
486
+ if (hue < 60) {
487
+ [r, g, b] = [c, x, 0];
488
+ } else if (hue < 120) {
489
+ [r, g, b] = [x, c, 0];
490
+ } else if (hue < 180) {
491
+ [r, g, b] = [0, c, x];
492
+ } else if (hue < 240) {
493
+ [r, g, b] = [0, x, c];
494
+ } else if (hue < 300) {
495
+ [r, g, b] = [x, 0, c];
496
+ } else {
497
+ [r, g, b] = [c, 0, x];
498
+ }
499
+
500
+ colors[i * 3] = r + m;
501
+ colors[i * 3 + 1] = g + m;
502
+ colors[i * 3 + 2] = b + m;
503
+ }
504
+ }
505
+
506
+ this.terrain.geometry.attributes.color.needsUpdate = true;
507
+ }
508
+
509
+ animate() {
510
+ requestAnimationFrame(() => this.animate());
511
+
512
+ // Update terrain based on audio data
513
+ if (this.isRecording && this.dataArray) {
514
+ this.updateTerrainGeometry();
515
+ } else {
516
+ // Idle animation when not recording
517
+ this.idleAnimation();
518
+ }
519
+
520
+ // Render scene
521
+ this.renderer.render(this.scene, this.camera);
522
+ }
523
+
524
+ updateTerrainGeometry() {
525
+ // Get frequency data
526
+ this.analyser.getByteFrequencyData(this.dataArray);
527
+
528
+ // Calculate the number of frequency bins to use based on the frequency range slider
529
+ const maxBinIndex = Math.floor(this.dataArray.length * (this.maxFrequencyPercent / 100));
530
+
531
+ // Update vertices based on frequency data
532
+ for (let i = 0; i < this.gridSize; i++) {
533
+ for (let j = 0; j < this.gridSize; j++) {
534
+ const index = i * this.gridSize + j;
535
+ const vertexIndex = index * 3 + 1; // Y component
536
+
537
+ // Map grid position to frequency bin
538
+ const binIndex = Math.floor((i * this.gridSize + j) * maxBinIndex / (this.gridSize * this.gridSize));
539
+
540
+ // Get amplitude from frequency data
541
+ const amplitude = this.dataArray[binIndex] / 255.0;
542
+
543
+ // Apply sensitivity multiplier
544
+ const heightValue = amplitude * (this.sensitivity * 2);
545
+
546
+ // Apply depth effect
547
+ const distanceFromCenter = Math.sqrt(
548
+ Math.pow((i - this.gridSize / 2) / (this.gridSize / 2), 2) +
549
+ Math.pow((j - this.gridSize / 2) / (this.gridSize / 2), 2)
550
+ );
551
+
552
+ // Apply distance falloff based on depth setting
553
+ const falloff = Math.max(0, 1 - distanceFromCenter * (1 - this.depth / 10));
554
+
555
+ // Calculate new height
556
+ this.heights[index] = heightValue * 10 * falloff;
557
+
558
+ // Update vertex position
559
+ this.vertices[vertexIndex] = this.heights[index];
560
+ }
561
+ }
562
+
563
+ // Update colors
564
+ this.updateTerrainColors();
565
+
566
+ // Update geometry
567
+ this.terrain.geometry.attributes.position.needsUpdate = true;
568
+ }
569
+
570
+ idleAnimation() {
571
+ // Simple idle wave animation
572
+ const time = Date.now() * 0.001;
573
+
574
+ for (let i = 0; i < this.gridSize; i++) {
575
+ for (let j = 0; j < this.gridSize; j++) {
576
+ const index = i * this.gridSize + j;
577
+ const vertexIndex = index * 3 + 1; // Y component
578
+
579
+ const x = (i - this.gridSize / 2) / 5;
580
+ const z = (j - this.gridSize / 2) / 5;
581
+
582
+ // Generate a gentle wave pattern
583
+ const height = Math.sin(x + time) * Math.cos(z + time) * 0.5;
584
+
585
+ // Store height for color calculations
586
+ this.heights[index] = height * 3;
587
+
588
+ // Update vertex position
589
+ this.vertices[vertexIndex] = height * 3;
590
+ }
591
+ }
592
+
593
+ // Update colors
594
+ this.updateTerrainColors();
595
+
596
+ // Update geometry
597
+ this.terrain.geometry.attributes.position.needsUpdate = true;
598
+ }
599
+
600
+ onWindowResize() {
601
+ // Update camera aspect ratio
602
+ this.camera.aspect = window.innerWidth / window.innerHeight;
603
+ this.camera.updateProjectionMatrix();
604
+
605
+ // Update renderer size
606
+ this.renderer.setSize(window.innerWidth, window.innerHeight);
607
+ }
608
+
609
+ async startMicrophone() {
610
+ try {
611
+ // Request microphone access
612
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
613
+
614
+ // Create audio context
615
+ this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
616
+
617
+ // Create analyzer
618
+ this.analyser = this.audioContext.createAnalyser();
619
+ this.analyser.fftSize = 2048;
620
+ this.analyser.smoothingTimeConstant = 0.85;
621
+
622
+ // Create buffer for frequency data
623
+ this.dataArray = new Uint8Array(this.analyser.frequencyBinCount);
624
+
625
+ // Connect microphone to analyzer
626
+ this.source = this.audioContext.createMediaStreamSource(stream);
627
+ this.source.connect(this.analyser);
628
+
629
+ // Update UI
630
+ this.isRecording = true;
631
+ this.updateUIState();
632
+
633
+ } catch (error) {
634
+ console.error('Error accessing microphone:', error);
635
+ this.statusText.textContent = 'Microphone access denied';
636
+ }
637
+ }
638
+
639
+ stopMicrophone() {
640
+ if (this.source) {
641
+ this.source.disconnect();
642
+ this.source = null;
643
+ }
644
+
645
+ if (this.audioContext) {
646
+ this.audioContext.close().then(() => {
647
+ this.audioContext = null;
648
+ this.analyser = null;
649
+ this.dataArray = null;
650
+
651
+ this.isRecording = false;
652
+ this.updateUIState();
653
+ });
654
+ } else {
655
+ this.isRecording = false;
656
+ this.updateUIState();
657
+ }
658
+ }
659
+
660
+ updateUIState() {
661
+ if (this.isRecording) {
662
+ this.startBtn.innerHTML = '<span class="btn-icon">■</span> Stop Microphone';
663
+ this.startBtn.classList.add('recording');
664
+ this.statusDot.classList.add('active');
665
+ this.statusText.textContent = 'Microphone active';
666
+ } else {
667
+ this.startBtn.innerHTML = '<span class="btn-icon">◉</span> Start Microphone';
668
+ this.startBtn.classList.remove('recording');
669
+ this.statusDot.classList.remove('active');
670
+ this.statusText.textContent = 'Microphone inactive';
671
+ }
672
+ }
673
+
674
+ initControls() {
675
+ // Start/stop button
676
+ this.startBtn.addEventListener('click', () => {
677
+ if (this.isRecording) {
678
+ this.stopMicrophone();
679
+ } else {
680
+ this.startMicrophone();
681
+ }
682
+ });
683
+
684
+ // Frequency range slider
685
+ this.frequencyRange.addEventListener('input', (e) => {
686
+ this.maxFrequencyPercent = parseInt(e.target.value);
687
+ this.frequencyValue.textContent = `${this.maxFrequencyPercent}%`;
688
+ });
689
+
690
+ // Sensitivity slider
691
+ this.sensitivityRange.addEventListener('input', (e) => {
692
+ this.sensitivity = parseInt(e.target.value);
693
+ this.sensitivityValue.textContent = this.sensitivity;
694
+ });
695
+
696
+ // Depth slider
697
+ this.depthRange.addEventListener('input', (e) => {
698
+ this.depth = parseInt(e.target.value);
699
+ this.depthValue.textContent = this.depth;
700
+ });
701
+
702
+ // Color scheme radio buttons
703
+ this.colorOptions.forEach(option => {
704
+ option.addEventListener('change', (e) => {
705
+ this.colorScheme = e.target.value;
706
+ this.updateTerrainColors();
707
+ });
708
+ });
709
+
710
+ // Toggle panel button
711
+ this.togglePanel.addEventListener('click', () => {
712
+ this.controlPanel.classList.toggle('collapsed');
713
+ this.togglePanel.textContent = this.controlPanel.classList.contains('collapsed') ? '≫' : '≡';
714
+ });
715
+ }
716
+ }
717
+
718
+ // Initialize the visualizer when the page loads
719
+ window.addEventListener('DOMContentLoaded', () => {
720
+ new AudioVisualizer();
721
+ });
722
+ </script>
723
+ </body>
724
+ </html>