Sunradiance commited on
Commit
95a2ac2
·
verified ·
1 Parent(s): a0fc317

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +6 -4
  2. index.html +443 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Sun Projects
3
- emoji: 📈
4
- colorFrom: pink
5
  colorTo: blue
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: sun-projects
3
+ emoji: 🐳
4
+ colorFrom: blue
5
  colorTo: blue
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,443 @@
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
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Spectral Voice Analyzer</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <style>
10
+ @keyframes pulse {
11
+ 0% { opacity: 0.4; }
12
+ 50% { opacity: 1; }
13
+ 100% { opacity: 0.4; }
14
+ }
15
+
16
+ .pulse {
17
+ animation: pulse 1.5s infinite;
18
+ }
19
+
20
+ canvas {
21
+ background: linear-gradient(to bottom, #1e3a8a, #0f172a);
22
+ }
23
+
24
+ .bar-container {
25
+ height: 250px;
26
+ display: flex;
27
+ justify-content: center;
28
+ align-items: flex-end;
29
+ gap: 2px;
30
+ padding: 10px;
31
+ background: rgba(0,0,0,0.2);
32
+ border-radius: 8px;
33
+ }
34
+
35
+ .frequency-bar {
36
+ background: linear-gradient(to top, #3b82f6, #93c5fd);
37
+ width: 8px;
38
+ border-radius: 4px 4px 0 0;
39
+ transition: height 0.1s ease-out;
40
+ }
41
+ </style>
42
+ </head>
43
+ <body class="bg-gray-900 text-white min-h-screen">
44
+ <div class="container mx-auto px-4 py-8">
45
+ <header class="mb-8 text-center">
46
+ <h1 class="text-4xl font-bold mb-2 bg-gradient-to-r from-blue-400 to-purple-500 text-transparent bg-clip-text">
47
+ Spectral Voice Analyzer
48
+ </h1>
49
+ <p class="text-gray-400">Real-time frequency analysis of your voice</p>
50
+ </header>
51
+
52
+ <div class="flex flex-col lg:flex-row gap-8">
53
+ <div class="lg:w-1/3 bg-gray-800 rounded-lg p-6 shadow-lg">
54
+ <div class="flex items-center justify-between mb-6">
55
+ <h2 class="text-xl font-semibold">Controls</h2>
56
+ <div id="status" class="flex items-center">
57
+ <span class="h-3 w-3 rounded-full bg-red-500 mr-2"></span>
58
+ <span class="text-sm">Inactive</span>
59
+ </div>
60
+ </div>
61
+
62
+ <button id="toggleMic" class="w-full py-3 px-4 bg-blue-600 hover:bg-blue-700 rounded-lg font-medium mb-4 flex items-center justify-center">
63
+ <i class="fas fa-microphone mr-2"></i>
64
+ Start Analyzer
65
+ </button>
66
+
67
+ <div class="bg-gray-700 p-4 rounded-lg mb-4">
68
+ <div class="flex justify-between mb-2">
69
+ <span class="text-sm text-gray-300">Input Level</span>
70
+ <span id="inputLevel" class="text-sm font-mono">0%</span>
71
+ </div>
72
+ <div class="h-2.5 w-full bg-gray-600 rounded-full">
73
+ <div id="levelMeter" class="h-2.5 bg-green-500 rounded-full" style="width: 0%"></div>
74
+ </div>
75
+ </div>
76
+
77
+ <div class="grid grid-cols-2 gap-4">
78
+ <div class="bg-gray-700 p-4 rounded-lg">
79
+ <span class="text-sm text-gray-300 block mb-1">Frequency Range</span>
80
+ <select id="frequencyRange" class="w-full bg-gray-800 text-white rounded px-3 py-2 text-sm outline-none">
81
+ <option value="full">Full Spectrum</option>
82
+ <option value="voice">Voice Range (80-300Hz)</option>
83
+ <option value="high">High Frequencies</option>
84
+ <option value="low">Low Frequencies</option>
85
+ </select>
86
+ </div>
87
+ <div class="bg-gray-700 p-4 rounded-lg">
88
+ <span class="text-sm text-gray-300 block mb-1">Visual Style</span>
89
+ <select id="visualStyle" class="w-full bg-gray-800 text-white rounded px-3 py-2 text-sm outline-none">
90
+ <option value="bars">Bars</option>
91
+ <option value="wave">Waveform</option>
92
+ <option value="circle">Circular</option>
93
+ </select>
94
+ </div>
95
+ </div>
96
+
97
+ <div class="mt-6 p-4 bg-gray-700 rounded-lg">
98
+ <h3 class="text-sm font-medium mb-2 text-gray-300">Current Frequency</h3>
99
+ <div class="flex items-end">
100
+ <span id="dominantFreq" class="text-3xl font-bold mr-2">0</span>
101
+ <span class="text-lg">Hz</span>
102
+ </div>
103
+ </div>
104
+ </div>
105
+
106
+ <div class="lg:w-2/3">
107
+ <div class="bg-gray-800 rounded-lg overflow-hidden shadow-lg">
108
+ <div class="bg-gradient-to-b from-gray-700 to-gray-800">
109
+ <canvas id="visualizer" class="w-full h-64 lg:h-96"></canvas>
110
+ </div>
111
+ <div class="p-4 flex justify-between items-center border-t border-gray-700">
112
+ <div class="text-sm text-gray-400">
113
+ <span>FFT Size: </span>
114
+ <select id="fftSize" class="bg-gray-700 text-white rounded px-2 py-1 text-sm outline-none">
115
+ <option value="64">64</option>
116
+ <option value="128">128</option>
117
+ <option value="256" selected>256</option>
118
+ <option value="512">512</option>
119
+ <option value="1024">1024</option>
120
+ </select>
121
+ </div>
122
+ <div id="fpsCounter" class="text-sm text-gray-400">FPS: 0</div>
123
+ </div>
124
+ </div>
125
+
126
+ <div class="mt-4">
127
+ <h3 class="text-lg font-medium mb-2 text-gray-300">Frequency Distribution</h3>
128
+ <div id="barVisualizer" class="bar-container">
129
+ <!-- Bars will be generated by JavaScript -->
130
+ </div>
131
+ </div>
132
+ </div>
133
+ </div>
134
+
135
+ <footer class="mt-12 text-center text-sm text-gray-500">
136
+ <p>Speak into your microphone to see real-time spectral analysis</p>
137
+ </footer>
138
+ </div>
139
+
140
+ <script>
141
+ document.addEventListener('DOMContentLoaded', () => {
142
+ // Audio context and nodes
143
+ let audioContext;
144
+ let analyser;
145
+ let microphone;
146
+ let isActive = false;
147
+ let lastTime = 0;
148
+ let frames = 0;
149
+ let lastFpsUpdate = 0;
150
+ let fftSize = 256;
151
+ let barCount = 32;
152
+
153
+ // DOM elements
154
+ const toggleMicBtn = document.getElementById('toggleMic');
155
+ const statusElement = document.getElementById('status');
156
+ const inputLevelElement = document.getElementById('inputLevel');
157
+ const levelMeterElement = document.getElementById('levelMeter');
158
+ const visualizerCanvas = document.getElementById('visualizer');
159
+ const visualizerCtx = visualizerCanvas.getContext('2d');
160
+ const barVisualizer = document.getElementById('barVisualizer');
161
+ const dominantFreqElement = document.getElementById('dominantFreq');
162
+ const fftSizeSelect = document.getElementById('fftSize');
163
+ const frequencyRangeSelect = document.getElementById('frequencyRange');
164
+ const visualStyleSelect = document.getElementById('visualStyle');
165
+ const fpsCounter = document.getElementById('fpsCounter');
166
+
167
+ // Frequency data arrays
168
+ let frequencyData;
169
+ let timeDomainData;
170
+
171
+ // Initialize bars
172
+ const bars = [];
173
+ for (let i = 0; i < barCount; i++) {
174
+ const bar = document.createElement('div');
175
+ bar.className = 'frequency-bar';
176
+ bar.style.height = '0px';
177
+ barVisualizer.appendChild(bar);
178
+ bars.push(bar);
179
+ }
180
+
181
+ // Resize canvas
182
+ function resizeCanvas() {
183
+ visualizerCanvas.width = visualizerCanvas.offsetWidth;
184
+ visualizerCanvas.height = visualizerCanvas.offsetHeight;
185
+ }
186
+
187
+ window.addEventListener('resize', resizeCanvas);
188
+ resizeCanvas();
189
+
190
+ // FPS counter
191
+ function updateFps() {
192
+ const now = performance.now();
193
+ frames++;
194
+
195
+ if (now >= lastFpsUpdate + 1000) {
196
+ fpsCounter.textContent = `FPS: ${Math.round(frames * 1000 / (now - lastFpsUpdate))}`;
197
+ frames = 0;
198
+ lastFpsUpdate = now;
199
+ }
200
+ }
201
+
202
+ // Visualizer functions
203
+ function drawBars() {
204
+ const width = visualizerCanvas.width;
205
+ const height = visualizerCanvas.height;
206
+ const barWidth = width / barCount;
207
+ let maxFreq = 0;
208
+
209
+ visualizerCtx.clearRect(0, 0, width, height);
210
+
211
+ for (let i = 0; i < barCount; i++) {
212
+ const value = frequencyData[i] || 0;
213
+ const percent = value / 255;
214
+ const barHeight = percent * height;
215
+ maxFreq = Math.max(maxFreq, value);
216
+
217
+ // Set color based on frequency band
218
+ const hue = 240 - (i / barCount * 240);
219
+ visualizerCtx.fillStyle = `hsl(${hue}, 100%, 60%)`;
220
+
221
+ // Draw bar
222
+ visualizerCtx.fillRect(
223
+ i * barWidth,
224
+ height - barHeight,
225
+ barWidth * 0.8,
226
+ barHeight
227
+ );
228
+ }
229
+
230
+ // Update the bars in the secondary visualizer
231
+ for (let i = 0; i < barCount; i++) {
232
+ const value = frequencyData[i] || 0;
233
+ const percent = value / 255;
234
+ bars[i].style.height = `${percent * 100}%`;
235
+ }
236
+
237
+ // Find dominant frequency
238
+ let maxIndex = 0;
239
+ let maxValue = 0;
240
+ for (let i = 0; i < frequencyData.length; i++) {
241
+ if (frequencyData[i] > maxValue) {
242
+ maxValue = frequencyData[i];
243
+ maxIndex = i;
244
+ }
245
+ }
246
+
247
+ // Calculate dominant frequency in Hz
248
+ const sampleRate = audioContext.sampleRate;
249
+ const dominantFrequency = maxIndex * sampleRate / fftSize;
250
+
251
+ if (dominantFrequency > 50 && dominantFrequency < 4000) {
252
+ dominantFreqElement.textContent = Math.round(dominantFrequency);
253
+ }
254
+ }
255
+
256
+ function drawWaveform() {
257
+ const width = visualizerCanvas.width;
258
+ const height = visualizerCanvas.height;
259
+ const centerY = height / 2;
260
+
261
+ visualizerCtx.clearRect(0, 0, width, height);
262
+
263
+ visualizerCtx.beginPath();
264
+ visualizerCtx.strokeStyle = '#3b82f6';
265
+ visualizerCtx.lineWidth = 2;
266
+
267
+ for (let i = 0; i < timeDomainData.length; i++) {
268
+ const x = i / timeDomainData.length * width;
269
+ const y = (timeDomainData[i] / 255) * height;
270
+
271
+ if (i === 0) {
272
+ visualizerCtx.moveTo(x, y);
273
+ } else {
274
+ visualizerCtx.lineTo(x, y);
275
+ }
276
+ }
277
+
278
+ visualizerCtx.stroke();
279
+ }
280
+
281
+ function drawCircle() {
282
+ const width = visualizerCanvas.width;
283
+ const height = visualizerCanvas.height;
284
+ const centerX = width / 2;
285
+ const centerY = height / 2;
286
+ const radius = Math.min(width, height) * 0.4;
287
+
288
+ visualizerCtx.clearRect(0, 0, width, height);
289
+
290
+ // Draw frequency data as a circular spectrum
291
+ visualizerCtx.beginPath();
292
+
293
+ for (let i = 0; i < frequencyData.length; i++) {
294
+ const angle = (i / frequencyData.length) * Math.PI * 2;
295
+ const value = frequencyData[i] / 255;
296
+ const pointRadius = radius * (1 + value * 0.5);
297
+
298
+ const x = centerX + Math.cos(angle) * pointRadius;
299
+ const y = centerY + Math.sin(angle) * pointRadius;
300
+
301
+ if (i === 0) {
302
+ visualizerCtx.moveTo(x, y);
303
+ } else {
304
+ visualizerCtx.lineTo(x, y);
305
+ }
306
+ }
307
+
308
+ visualizerCtx.closePath();
309
+ visualizerCtx.fillStyle = 'rgba(59, 130, 246, 0.5)';
310
+ visualizerCtx.fill();
311
+ visualizerCtx.strokeStyle = '#3b82f6';
312
+ visualizerCtx.stroke();
313
+
314
+ // Draw center circle
315
+ visualizerCtx.beginPath();
316
+ visualizerCtx.arc(centerX, centerY, radius * 0.1, 0, Math.PI * 2);
317
+ visualizerCtx.fillStyle = 'rgba(59, 130, 246, 0.7)';
318
+ visualizerCtx.fill();
319
+ }
320
+
321
+ // Main visualization loop
322
+ function visualize() {
323
+ if (!isActive) return;
324
+
325
+ requestAnimationFrame(visualize);
326
+
327
+ analyser.getByteFrequencyData(frequencyData);
328
+ analyser.getByteTimeDomainData(timeDomainData);
329
+
330
+ // Calculate input level
331
+ let sum = 0;
332
+ for (let i = 0; i < timeDomainData.length; i++) {
333
+ const value = (timeDomainData[i] - 128) / 128;
334
+ sum += value * value;
335
+ }
336
+ const rms = Math.sqrt(sum / timeDomainData.length);
337
+ const level = Math.min(1, rms * 2);
338
+
339
+ levelMeterElement.style.width = `${level * 100}%`;
340
+ inputLevelElement.textContent = `${Math.round(level * 100)}%`;
341
+
342
+ // Update level meter color
343
+ levelMeterElement.className = level > 0.9 ?
344
+ 'h-2.5 bg-red-500 rounded-full' :
345
+ level > 0.7 ?
346
+ 'h-2.5 bg-yellow-500 rounded-full' :
347
+ 'h-2.5 bg-green-500 rounded-full';
348
+
349
+ // Draw based on selected visual style
350
+ const style = visualStyleSelect.value;
351
+ if (style === 'wave') {
352
+ drawWaveform();
353
+ } else if (style === 'circle') {
354
+ drawCircle();
355
+ } else {
356
+ drawBars();
357
+ }
358
+
359
+ updateFps();
360
+ }
361
+
362
+ // Toggle microphone
363
+ async function toggleMicrophone() {
364
+ try {
365
+ if (!isActive) {
366
+ // Start analysis
367
+ audioContext = new (window.AudioContext || window.webkitAudioContext)();
368
+ analyser = audioContext.createAnalyser();
369
+ analyser.fftSize = fftSize;
370
+
371
+ frequencyData = new Uint8Array(analyser.frequencyBinCount);
372
+ timeDomainData = new Uint8Array(analyser.frequencyBinCount);
373
+
374
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
375
+ microphone = audioContext.createMediaStreamSource(stream);
376
+ microphone.connect(analyser);
377
+
378
+ isActive = true;
379
+ toggleMicBtn.innerHTML = '<i class="fas fa-stop mr-2"></i> Stop Analyzer';
380
+ toggleMicBtn.className = 'w-full py-3 px-4 bg-red-600 hover:bg-red-700 rounded-lg font-medium mb-4 flex items-center justify-center';
381
+
382
+ statusElement.innerHTML = '<span class="h-3 w-3 rounded-full bg-green-500 mr-2 pulse"></span><span class="text-sm">Active</span>';
383
+
384
+ visualize();
385
+ } else {
386
+ // Stop analysis
387
+ if (microphone) {
388
+ microphone.disconnect();
389
+ }
390
+ if (audioContext) {
391
+ audioContext.close();
392
+ }
393
+
394
+ isActive = false;
395
+ toggleMicBtn.innerHTML = '<i class="fas fa-microphone mr-2"></i> Start Analyzer';
396
+ toggleMicBtn.className = 'w-full py-3 px-4 bg-blue-600 hover:bg-blue-700 rounded-lg font-medium mb-4 flex items-center justify-center';
397
+
398
+ statusElement.innerHTML = '<span class="h-3 w-3 rounded-full bg-red-500 mr-2"></span><span class="text-sm">Inactive</span>';
399
+
400
+ // Clear visualizer
401
+ visualizerCtx.clearRect(0, 0, visualizerCanvas.width, visualizerCanvas.height);
402
+ bars.forEach(bar => bar.style.height = '0px');
403
+
404
+ // Reset indicators
405
+ inputLevelElement.textContent = '0%';
406
+ levelMeterElement.style.width = '0%';
407
+ dominantFreqElement.textContent = '0';
408
+ fpsCounter.textContent = 'FPS: 0';
409
+ }
410
+ } catch (error) {
411
+ console.error('Error accessing microphone:', error);
412
+ alert('Could not access microphone. Please ensure you have granted microphone permissions.');
413
+
414
+ isActive = false;
415
+ toggleMicBtn.innerHTML = '<i class="fas fa-microphone mr-2"></i> Start Analyzer';
416
+ toggleMicBtn.className = 'w-full py-3 px-4 bg-blue-600 hover:bg-blue-700 rounded-lg font-medium mb-4 flex items-center justify-center';
417
+
418
+ statusElement.innerHTML = '<span class="h-3 w-3 rounded-full bg-red-500 mr-2"></span><span class="text-sm">Error</span>';
419
+ }
420
+ }
421
+
422
+ // Event listeners
423
+ toggleMicBtn.addEventListener('click', toggleMicrophone);
424
+
425
+ fftSizeSelect.addEventListener('change', () => {
426
+ fftSize = parseInt(fftSizeSelect.value);
427
+ if (analyser) {
428
+ analyser.fftSize = fftSize;
429
+ frequencyData = new Uint8Array(analyser.frequencyBinCount);
430
+ timeDomainData = new Uint8Array(analyser.frequencyBinCount);
431
+ }
432
+ });
433
+
434
+ // Inform users about microphone access
435
+ toggleMicBtn.addEventListener('mouseover', () => {
436
+ if (!isActive) {
437
+ toggleMicBtn.title = 'Click to start. You will be asked for microphone permission.';
438
+ }
439
+ });
440
+ });
441
+ </script>
442
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <a href="https://enzostvs-deepsite.hf.space" style="color: #fff;" target="_blank" >DeepSite</a> <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;"></p></body>
443
+ </html>