bibbler commited on
Commit
4d0a5da
·
verified ·
1 Parent(s): 5630e24

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +848 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Audio Reactive Visualizer
3
- emoji: 🦀
4
- colorFrom: blue
5
- colorTo: red
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: audio-reactive-visualizer
3
+ emoji: 🐳
4
+ colorFrom: purple
5
+ colorTo: purple
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,848 @@
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>Audio Reactive Visualizer</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/p5.min.js"></script>
9
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/RecordRTC/5.6.2/RecordRTC.min.js"></script>
10
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
11
+ <style>
12
+ .custom-scrollbar::-webkit-scrollbar {
13
+ width: 8px;
14
+ height: 8px;
15
+ }
16
+ .custom-scrollbar::-webkit-scrollbar-track {
17
+ background: #1e293b;
18
+ }
19
+ .custom-scrollbar::-webkit-scrollbar-thumb {
20
+ background: #475569;
21
+ border-radius: 4px;
22
+ }
23
+ .custom-scrollbar::-webkit-scrollbar-thumb:hover {
24
+ background: #64748b;
25
+ }
26
+ .gradient-bg {
27
+ background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
28
+ }
29
+ .glow {
30
+ box-shadow: 0 0 15px rgba(59, 130, 246, 0.5);
31
+ }
32
+ .glow:hover {
33
+ box-shadow: 0 0 20px rgba(59, 130, 246, 0.8);
34
+ }
35
+ canvas {
36
+ display: block;
37
+ border-radius: 8px;
38
+ }
39
+ .pattern-preview {
40
+ width: 100%;
41
+ height: 80px;
42
+ border-radius: 6px;
43
+ cursor: pointer;
44
+ transition: all 0.2s;
45
+ }
46
+ .pattern-preview:hover {
47
+ transform: scale(1.02);
48
+ }
49
+ .slider-thumb::-webkit-slider-thumb {
50
+ -webkit-appearance: none;
51
+ width: 18px;
52
+ height: 18px;
53
+ border-radius: 50%;
54
+ background: #3b82f6;
55
+ cursor: pointer;
56
+ }
57
+ .slider-thumb:focus::-webkit-slider-thumb {
58
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3);
59
+ }
60
+ </style>
61
+ </head>
62
+ <body class="gradient-bg text-slate-100 min-h-screen">
63
+ <div class="container mx-auto px-4 py-8">
64
+ <header class="mb-8 text-center">
65
+ <h1 class="text-4xl font-bold mb-2 bg-clip-text text-transparent bg-gradient-to-r from-blue-400 to-purple-500">Audio Reactive Visualizer</h1>
66
+ <p class="text-slate-300 max-w-2xl mx-auto">Create stunning audio-reactive visualizations with customizable patterns and real-time audio analysis. Import your music and export your creations.</p>
67
+ </header>
68
+
69
+ <div class="grid grid-cols-1 lg:grid-cols-4 gap-6">
70
+ <!-- Controls Panel -->
71
+ <div class="lg:col-span-1 bg-slate-800/50 rounded-xl p-6 shadow-lg border border-slate-700/50 custom-scrollbar overflow-y-auto max-h-screen">
72
+ <h2 class="text-xl font-semibold mb-4 flex items-center gap-2">
73
+ <i class="fas fa-sliders-h"></i> Controls
74
+ </h2>
75
+
76
+ <!-- Audio Input -->
77
+ <div class="mb-6">
78
+ <h3 class="text-sm font-medium mb-2 text-slate-300 flex items-center gap-2">
79
+ <i class="fas fa-music"></i> Audio Source
80
+ </h3>
81
+ <div class="flex flex-col gap-2">
82
+ <button id="fileInputBtn" class="bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-lg transition flex items-center justify-center gap-2 glow">
83
+ <i class="fas fa-file-audio"></i> Import Audio
84
+ </button>
85
+ <input type="file" id="fileInput" accept="audio/*" class="hidden">
86
+ <button id="micInputBtn" class="bg-purple-600 hover:bg-purple-700 text-white py-2 px-4 rounded-lg transition flex items-center justify-center gap-2 glow">
87
+ <i class="fas fa-microphone"></i> Use Microphone
88
+ </button>
89
+ </div>
90
+ <div class="mt-3">
91
+ <div class="flex justify-between text-xs text-slate-400">
92
+ <span>Volume</span>
93
+ <span id="volumeValue">50%</span>
94
+ </div>
95
+ <input type="range" id="volumeSlider" min="0" max="100" value="50" class="w-full slider-thumb">
96
+ </div>
97
+ </div>
98
+
99
+ <!-- Pattern Selection -->
100
+ <div class="mb-6">
101
+ <h3 class="text-sm font-medium mb-2 text-slate-300 flex items-center gap-2">
102
+ <i class="fas fa-shapes"></i> Visual Pattern
103
+ </h3>
104
+ <div class="grid grid-cols-2 gap-3">
105
+ <div class="pattern-option" data-pattern="waveform">
106
+ <div class="pattern-preview bg-gradient-to-r from-blue-500 to-purple-600"></div>
107
+ <p class="text-xs text-center mt-1">Waveform</p>
108
+ </div>
109
+ <div class="pattern-option" data-pattern="particles">
110
+ <div class="pattern-preview bg-gradient-to-r from-green-500 to-teal-600"></div>
111
+ <p class="text-xs text-center mt-1">Particles</p>
112
+ </div>
113
+ <div class="pattern-option" data-pattern="rings">
114
+ <div class="pattern-preview bg-gradient-to-r from-orange-500 to-pink-600"></div>
115
+ <p class="text-xs text-center mt-1">Concentric</p>
116
+ </div>
117
+ <div class="pattern-option" data-pattern="bars">
118
+ <div class="pattern-preview bg-gradient-to-r from-yellow-500 to-red-600"></div>
119
+ <p class="text-xs text-center mt-1">Frequency</p>
120
+ </div>
121
+ </div>
122
+ </div>
123
+
124
+ <!-- Color Customization -->
125
+ <div class="mb-6">
126
+ <h3 class="text-sm font-medium mb-2 text-slate-300 flex items-center gap-2">
127
+ <i class="fas fa-palette"></i> Colors
128
+ </h3>
129
+ <div class="grid grid-cols-2 gap-3">
130
+ <div>
131
+ <label class="text-xs text-slate-400 block mb-1">Primary</label>
132
+ <input type="color" id="color1" value="#3b82f6" class="w-full h-8 rounded cursor-pointer">
133
+ </div>
134
+ <div>
135
+ <label class="text-xs text-slate-400 block mb-1">Secondary</label>
136
+ <input type="color" id="color2" value="#8b5cf6" class="w-full h-8 rounded cursor-pointer">
137
+ </div>
138
+ </div>
139
+ <div class="mt-3">
140
+ <div class="flex justify-between text-xs text-slate-400">
141
+ <span>Background Opacity</span>
142
+ <span id="bgOpacityValue">20%</span>
143
+ </div>
144
+ <input type="range" id="bgOpacitySlider" min="0" max="100" value="20" class="w-full slider-thumb">
145
+ </div>
146
+ </div>
147
+
148
+ <!-- Pattern Settings -->
149
+ <div class="mb-6">
150
+ <h3 class="text-sm font-medium mb-2 text-slate-300 flex items-center gap-2">
151
+ <i class="fas fa-cog"></i> Pattern Settings
152
+ </h3>
153
+ <div id="waveformSettings">
154
+ <div class="mb-2">
155
+ <div class="flex justify-between text-xs text-slate-400">
156
+ <span>Line Thickness</span>
157
+ <span id="waveThicknessValue">2</span>
158
+ </div>
159
+ <input type="range" id="waveThicknessSlider" min="1" max="10" value="2" class="w-full slider-thumb">
160
+ </div>
161
+ <div class="mb-2">
162
+ <div class="flex justify-between text-xs text-slate-400">
163
+ <span>Smoothness</span>
164
+ <span id="waveSmoothnessValue">50%</span>
165
+ </div>
166
+ <input type="range" id="waveSmoothnessSlider" min="0" max="100" value="50" class="w-full slider-thumb">
167
+ </div>
168
+ </div>
169
+ <div id="particlesSettings" class="hidden">
170
+ <div class="mb-2">
171
+ <div class="flex justify-between text-xs text-slate-400">
172
+ <span>Particle Count</span>
173
+ <span id="particleCountValue">150</span>
174
+ </div>
175
+ <input type="range" id="particleCountSlider" min="50" max="500" value="150" class="w-full slider-thumb">
176
+ </div>
177
+ <div class="mb-2">
178
+ <div class="flex justify-between text-xs text-slate-400">
179
+ <span>Particle Size</span>
180
+ <span id="particleSizeValue">3</span>
181
+ </div>
182
+ <input type="range" id="particleSizeSlider" min="1" max="10" value="3" class="w-full slider-thumb">
183
+ </div>
184
+ </div>
185
+ <div id="ringsSettings" class="hidden">
186
+ <div class="mb-2">
187
+ <div class="flex justify-between text-xs text-slate-400">
188
+ <span>Ring Count</span>
189
+ <span id="ringCountValue">8</span>
190
+ </div>
191
+ <input type="range" id="ringCountSlider" min="3" max="20" value="8" class="w-full slider-thumb">
192
+ </div>
193
+ <div class="mb-2">
194
+ <div class="flex justify-between text-xs text-slate-400">
195
+ <span>Ring Spacing</span>
196
+ <span id="ringSpacingValue">30</span>
197
+ </div>
198
+ <input type="range" id="ringSpacingSlider" min="10" max="100" value="30" class="w-full slider-thumb">
199
+ </div>
200
+ </div>
201
+ <div id="barsSettings" class="hidden">
202
+ <div class="mb-2">
203
+ <div class="flex justify-between text-xs text-slate-400">
204
+ <span>Bar Count</span>
205
+ <span id="barCountValue">64</span>
206
+ </div>
207
+ <input type="range" id="barCountSlider" min="16" max="128" value="64" class="w-full slider-thumb">
208
+ </div>
209
+ <div class="mb-2">
210
+ <div class="flex justify-between text-xs text-slate-400">
211
+ <span>Bar Width</span>
212
+ <span id="barWidthValue">8</span>
213
+ </div>
214
+ <input type="range" id="barWidthSlider" min="2" max="20" value="8" class="w-full slider-thumb">
215
+ </div>
216
+ </div>
217
+ </div>
218
+
219
+ <!-- Export Options -->
220
+ <div>
221
+ <h3 class="text-sm font-medium mb-2 text-slate-300 flex items-center gap-2">
222
+ <i class="fas fa-download"></i> Export
223
+ </h3>
224
+ <div class="flex flex-col gap-2">
225
+ <button id="exportImageBtn" class="bg-emerald-600 hover:bg-emerald-700 text-white py-2 px-4 rounded-lg transition flex items-center justify-center gap-2 glow">
226
+ <i class="fas fa-image"></i> Save Image
227
+ </button>
228
+ <button id="exportVideoBtn" class="bg-rose-600 hover:bg-rose-700 text-white py-2 px-4 rounded-lg transition flex items-center justify-center gap-2 glow">
229
+ <i class="fas fa-video"></i> Record Video
230
+ </button>
231
+ </div>
232
+ <div id="recordingControls" class="hidden mt-3">
233
+ <div class="flex justify-center gap-2">
234
+ <button id="stopRecordingBtn" class="bg-rose-600 hover:bg-rose-700 text-white py-1 px-3 rounded-lg text-sm flex items-center gap-1">
235
+ <i class="fas fa-stop"></i> Stop
236
+ </button>
237
+ <div id="recordingTimer" class="text-sm text-slate-300 flex items-center">
238
+ <i class="fas fa-circle text-rose-500 mr-1 animate-pulse"></i> 00:00
239
+ </div>
240
+ </div>
241
+ </div>
242
+ </div>
243
+ </div>
244
+
245
+ <!-- Visualizer Canvas -->
246
+ <div class="lg:col-span-3">
247
+ <div id="visualizer-container" class="bg-slate-900/50 rounded-xl p-4 shadow-lg border border-slate-700/50">
248
+ <div id="canvas-container" class="flex items-center justify-center">
249
+ <!-- p5 canvas will be inserted here -->
250
+ </div>
251
+ <div class="mt-4 flex justify-between items-center">
252
+ <div class="flex items-center gap-2">
253
+ <button id="playBtn" class="bg-blue-600 hover:bg-blue-700 text-white p-2 rounded-full w-10 h-10 flex items-center justify-center glow">
254
+ <i class="fas fa-play"></i>
255
+ </button>
256
+ <button id="pauseBtn" class="bg-slate-700 hover:bg-slate-600 text-white p-2 rounded-full w-10 h-10 flex items-center justify-center">
257
+ <i class="fas fa-pause"></i>
258
+ </button>
259
+ <button id="stopBtn" class="bg-slate-700 hover:bg-slate-600 text-white p-2 rounded-full w-10 h-10 flex items-center justify-center">
260
+ <i class="fas fa-stop"></i>
261
+ </button>
262
+ </div>
263
+ <div id="songInfo" class="text-sm text-slate-400 italic">
264
+ No audio loaded
265
+ </div>
266
+ <div id="audioTime" class="text-sm text-slate-300">
267
+ 0:00 / 0:00
268
+ </div>
269
+ </div>
270
+ </div>
271
+
272
+ <!-- Audio Spectrum Display -->
273
+ <div class="mt-6 bg-slate-800/50 rounded-xl p-4 shadow-lg border border-slate-700/50">
274
+ <h3 class="text-sm font-medium mb-2 text-slate-300 flex items-center gap-2">
275
+ <i class="fas fa-chart-bar"></i> Audio Spectrum
276
+ </h3>
277
+ <div id="spectrum-container" class="h-24">
278
+ <!-- Spectrum analyzer canvas will be inserted here -->
279
+ </div>
280
+ </div>
281
+ </div>
282
+ </div>
283
+ </div>
284
+
285
+ <script>
286
+ // Global variables
287
+ let audioContext, analyser, dataArray, source, audioElement;
288
+ let isPlaying = false;
289
+ let currentPattern = 'waveform';
290
+ let color1 = '#3b82f6', color2 = '#8b5cf6';
291
+ let bgOpacity = 20;
292
+ let waveThickness = 2, waveSmoothness = 50;
293
+ let particleCount = 150, particleSize = 3;
294
+ let ringCount = 8, ringSpacing = 30;
295
+ let barCount = 64, barWidth = 8;
296
+ let mediaRecorder, recordedChunks = [], recordingStartTime;
297
+ let recordingInterval;
298
+
299
+ // Pattern settings
300
+ const patternSettings = {
301
+ waveform: {
302
+ thickness: 2,
303
+ smoothness: 50
304
+ },
305
+ particles: {
306
+ count: 150,
307
+ size: 3
308
+ },
309
+ rings: {
310
+ count: 8,
311
+ spacing: 30
312
+ },
313
+ bars: {
314
+ count: 64,
315
+ width: 8
316
+ }
317
+ };
318
+
319
+ // Initialize p5.js sketches
320
+ new p5(function(p) {
321
+ let canvas, spectrumCanvas;
322
+ let fft, waveform;
323
+ let particles = [];
324
+
325
+ p.setup = function() {
326
+ // Main visualizer canvas
327
+ canvas = p.createCanvas(800, 450);
328
+ canvas.parent('canvas-container');
329
+ p.colorMode(p.HSB, 360, 100, 100, 1);
330
+
331
+ // Spectrum analyzer canvas
332
+ spectrumCanvas = p.createCanvas(800, 100);
333
+ spectrumCanvas.parent('spectrum-container');
334
+
335
+ // Initialize FFT for frequency analysis
336
+ fft = new p5.FFT();
337
+
338
+ // Initialize audio context if not already done
339
+ if (!audioContext) {
340
+ audioContext = new (window.AudioContext || window.webkitAudioContext)();
341
+ analyser = audioContext.createAnalyser();
342
+ analyser.fftSize = 2048;
343
+ const bufferLength = analyser.frequencyBinCount;
344
+ dataArray = new Uint8Array(bufferLength);
345
+ }
346
+
347
+ // Initialize particles
348
+ initParticles();
349
+ };
350
+
351
+ p.draw = function() {
352
+ // Draw main visualizer
353
+ drawVisualizer();
354
+
355
+ // Draw spectrum analyzer
356
+ drawSpectrum();
357
+
358
+ // Update audio time display
359
+ updateAudioTime();
360
+ };
361
+
362
+ function drawVisualizer() {
363
+ // Get audio data
364
+ waveform = fft.waveform();
365
+
366
+ // Set background with opacity
367
+ p.background(10, bgOpacity);
368
+
369
+ // Draw selected pattern
370
+ switch(currentPattern) {
371
+ case 'waveform':
372
+ drawWaveform();
373
+ break;
374
+ case 'particles':
375
+ drawParticles();
376
+ break;
377
+ case 'rings':
378
+ drawRings();
379
+ break;
380
+ case 'bars':
381
+ drawBars();
382
+ break;
383
+ }
384
+ }
385
+
386
+ function drawWaveform() {
387
+ p.noFill();
388
+ p.strokeWeight(waveThickness);
389
+
390
+ // Create gradient for waveform
391
+ const gradient = p.drawingContext.createLinearGradient(0, 0, p.width, 0);
392
+ gradient.addColorStop(0, color1);
393
+ gradient.addColorStop(1, color2);
394
+ p.drawingContext.strokeStyle = gradient;
395
+
396
+ p.beginShape();
397
+ for (let i = 0; i < waveform.length; i++) {
398
+ // Apply smoothness
399
+ const x = p.map(i, 0, waveform.length, 0, p.width);
400
+ const y = p.map(waveform[i], -1, 1, p.height, 0);
401
+
402
+ // Smooth the line based on smoothness setting
403
+ if (i === 0) {
404
+ p.vertex(x, y);
405
+ } else {
406
+ const prevY = p.map(waveform[i-1], -1, 1, p.height, 0);
407
+ const smoothY = p.lerp(prevY, y, waveSmoothness / 100);
408
+ p.vertex(x, smoothY);
409
+ }
410
+ }
411
+ p.endShape();
412
+ }
413
+
414
+ function initParticles() {
415
+ particles = [];
416
+ for (let i = 0; i < particleCount; i++) {
417
+ particles.push({
418
+ x: p.random(p.width),
419
+ y: p.random(p.height),
420
+ vx: p.random(-1, 1),
421
+ vy: p.random(-1, 1),
422
+ size: particleSize,
423
+ color: p.lerpColor(p.color(color1), p.color(color2), p.random(1))
424
+ });
425
+ }
426
+ }
427
+
428
+ function drawParticles() {
429
+ const spectrum = fft.analyze();
430
+
431
+ for (let i = 0; i < particles.length; i++) {
432
+ const particle = particles[i];
433
+
434
+ // React to audio
435
+ const audioImpact = spectrum[Math.floor(p.map(i, 0, particles.length, 0, spectrum.length))] / 255;
436
+
437
+ // Update position with audio influence
438
+ particle.x += particle.vx * (1 + audioImpact * 2);
439
+ particle.y += particle.vy * (1 + audioImpact * 2);
440
+
441
+ // Wrap around edges
442
+ if (particle.x < 0) particle.x = p.width;
443
+ if (particle.x > p.width) particle.x = 0;
444
+ if (particle.y < 0) particle.y = p.height;
445
+ if (particle.y > p.height) particle.y = 0;
446
+
447
+ // Draw particle
448
+ p.noStroke();
449
+ p.fill(particle.color);
450
+ p.circle(particle.x, particle.y, particle.size * (1 + audioImpact * 2));
451
+ }
452
+ }
453
+
454
+ function drawRings() {
455
+ const spectrum = fft.analyze();
456
+ const centerX = p.width / 2;
457
+ const centerY = p.height / 2;
458
+
459
+ for (let i = 0; i < ringCount; i++) {
460
+ const radius = (i + 1) * ringSpacing;
461
+ const energy = spectrum[Math.floor(p.map(i, 0, ringCount, 0, spectrum.length))] / 255;
462
+
463
+ // Create gradient for each ring
464
+ const gradient = p.drawingContext.createRadialGradient(
465
+ centerX, centerY, radius - 5,
466
+ centerX, centerY, radius + 5
467
+ );
468
+ gradient.addColorStop(0, color1);
469
+ gradient.addColorStop(1, color2);
470
+ p.drawingContext.strokeStyle = gradient;
471
+
472
+ p.strokeWeight(5);
473
+ p.noFill();
474
+ p.circle(centerX, centerY, radius * (1 + energy * 0.5));
475
+ }
476
+ }
477
+
478
+ function drawBars() {
479
+ const spectrum = fft.analyze();
480
+ const binSize = Math.floor(spectrum.length / barCount);
481
+
482
+ for (let i = 0; i < barCount; i++) {
483
+ let sum = 0;
484
+ for (let j = 0; j < binSize; j++) {
485
+ sum += spectrum[i * binSize + j];
486
+ }
487
+ const avg = sum / binSize;
488
+ const energy = avg / 255;
489
+
490
+ const x = p.map(i, 0, barCount, 0, p.width);
491
+ const h = p.map(energy, 0, 1, 0, p.height * 0.8);
492
+
493
+ // Create gradient for each bar
494
+ const gradient = p.drawingContext.createLinearGradient(0, p.height - h, 0, p.height);
495
+ gradient.addColorStop(0, color1);
496
+ gradient.addColorStop(1, color2);
497
+ p.drawingContext.fillStyle = gradient;
498
+
499
+ p.noStroke();
500
+ p.rect(x, p.height - h, barWidth, h);
501
+ }
502
+ }
503
+
504
+ function drawSpectrum() {
505
+ const spectrum = fft.analyze();
506
+ p.background(15);
507
+
508
+ // Draw frequency spectrum
509
+ for (let i = 0; i < spectrum.length; i++) {
510
+ const x = p.map(i, 0, spectrum.length, 0, p.width);
511
+ const h = -p.height + p.map(spectrum[i], 0, 255, p.height, 0);
512
+
513
+ // Create gradient for spectrum
514
+ const gradient = p.drawingContext.createLinearGradient(0, 0, 0, p.height);
515
+ gradient.addColorStop(0, color1);
516
+ gradient.addColorStop(1, color2);
517
+ p.drawingContext.fillStyle = gradient;
518
+
519
+ p.noStroke();
520
+ p.rect(x, p.height, p.width / spectrum.length, h);
521
+ }
522
+ }
523
+
524
+ function updateAudioTime() {
525
+ if (audioElement && !isNaN(audioElement.duration)) {
526
+ const currentTime = audioElement.currentTime || 0;
527
+ const duration = audioElement.duration;
528
+
529
+ const currentMinutes = Math.floor(currentTime / 60);
530
+ const currentSeconds = Math.floor(currentTime % 60).toString().padStart(2, '0');
531
+ const durationMinutes = Math.floor(duration / 60);
532
+ const durationSeconds = Math.floor(duration % 60).toString().padStart(2, '0');
533
+
534
+ document.getElementById('audioTime').textContent =
535
+ `${currentMinutes}:${currentSeconds} / ${durationMinutes}:${durationSeconds}`;
536
+ } else {
537
+ document.getElementById('audioTime').textContent = '0:00 / 0:00';
538
+ }
539
+ }
540
+ });
541
+
542
+ // DOM event listeners
543
+ document.addEventListener('DOMContentLoaded', function() {
544
+ // Audio file input
545
+ document.getElementById('fileInputBtn').addEventListener('click', function() {
546
+ document.getElementById('fileInput').click();
547
+ });
548
+
549
+ document.getElementById('fileInput').addEventListener('change', function(e) {
550
+ if (e.target.files.length) {
551
+ loadAudioFile(e.target.files[0]);
552
+ }
553
+ });
554
+
555
+ // Microphone input
556
+ document.getElementById('micInputBtn').addEventListener('click', function() {
557
+ initMicrophone();
558
+ });
559
+
560
+ // Play/pause/stop controls
561
+ document.getElementById('playBtn').addEventListener('click', playAudio);
562
+ document.getElementById('pauseBtn').addEventListener('click', pauseAudio);
563
+ document.getElementById('stopBtn').addEventListener('click', stopAudio);
564
+
565
+ // Volume control
566
+ document.getElementById('volumeSlider').addEventListener('input', function(e) {
567
+ const volume = e.target.value / 100;
568
+ if (audioElement) audioElement.volume = volume;
569
+ document.getElementById('volumeValue').textContent = `${e.target.value}%`;
570
+ });
571
+
572
+ // Pattern selection
573
+ document.querySelectorAll('.pattern-option').forEach(option => {
574
+ option.addEventListener('click', function() {
575
+ currentPattern = this.getAttribute('data-pattern');
576
+
577
+ // Update UI
578
+ document.querySelectorAll('.pattern-option').forEach(opt => {
579
+ opt.querySelector('.pattern-preview').classList.remove('glow');
580
+ });
581
+ this.querySelector('.pattern-preview').classList.add('glow');
582
+
583
+ // Show relevant settings
584
+ document.querySelectorAll('[id$="Settings"]').forEach(el => {
585
+ el.classList.add('hidden');
586
+ });
587
+ document.getElementById(`${currentPattern}Settings`).classList.remove('hidden');
588
+ });
589
+ });
590
+
591
+ // Color controls
592
+ document.getElementById('color1').addEventListener('input', function(e) {
593
+ color1 = e.target.value;
594
+ });
595
+
596
+ document.getElementById('color2').addEventListener('input', function(e) {
597
+ color2 = e.target.value;
598
+ });
599
+
600
+ // Background opacity
601
+ document.getElementById('bgOpacitySlider').addEventListener('input', function(e) {
602
+ bgOpacity = e.target.value;
603
+ document.getElementById('bgOpacityValue').textContent = `${e.target.value}%`;
604
+ });
605
+
606
+ // Pattern-specific controls
607
+ // Waveform
608
+ document.getElementById('waveThicknessSlider').addEventListener('input', function(e) {
609
+ waveThickness = parseInt(e.target.value);
610
+ document.getElementById('waveThicknessValue').textContent = e.target.value;
611
+ });
612
+
613
+ document.getElementById('waveSmoothnessSlider').addEventListener('input', function(e) {
614
+ waveSmoothness = parseInt(e.target.value);
615
+ document.getElementById('waveSmoothnessValue').textContent = `${e.target.value}%`;
616
+ });
617
+
618
+ // Particles
619
+ document.getElementById('particleCountSlider').addEventListener('input', function(e) {
620
+ particleCount = parseInt(e.target.value);
621
+ document.getElementById('particleCountValue').textContent = e.target.value;
622
+ initParticles();
623
+ });
624
+
625
+ document.getElementById('particleSizeSlider').addEventListener('input', function(e) {
626
+ particleSize = parseInt(e.target.value);
627
+ document.getElementById('particleSizeValue').textContent = e.target.value;
628
+ });
629
+
630
+ // Rings
631
+ document.getElementById('ringCountSlider').addEventListener('input', function(e) {
632
+ ringCount = parseInt(e.target.value);
633
+ document.getElementById('ringCountValue').textContent = e.target.value;
634
+ });
635
+
636
+ document.getElementById('ringSpacingSlider').addEventListener('input', function(e) {
637
+ ringSpacing = parseInt(e.target.value);
638
+ document.getElementById('ringSpacingValue').textContent = e.target.value;
639
+ });
640
+
641
+ // Bars
642
+ document.getElementById('barCountSlider').addEventListener('input', function(e) {
643
+ barCount = parseInt(e.target.value);
644
+ document.getElementById('barCountValue').textContent = e.target.value;
645
+ });
646
+
647
+ document.getElementById('barWidthSlider').addEventListener('input', function(e) {
648
+ barWidth = parseInt(e.target.value);
649
+ document.getElementById('barWidthValue').textContent = e.target.value;
650
+ });
651
+
652
+ // Export buttons
653
+ document.getElementById('exportImageBtn').addEventListener('click', exportImage);
654
+ document.getElementById('exportVideoBtn').addEventListener('click', startRecording);
655
+ document.getElementById('stopRecordingBtn').addEventListener('click', stopRecording);
656
+
657
+ // Initialize with waveform pattern selected
658
+ document.querySelector('.pattern-option[data-pattern="waveform"]').click();
659
+ });
660
+
661
+ // Audio functions
662
+ function loadAudioFile(file) {
663
+ if (audioElement) {
664
+ audioElement.pause();
665
+ if (source) source.disconnect();
666
+ }
667
+
668
+ const fileURL = URL.createObjectURL(file);
669
+ audioElement = new Audio(fileURL);
670
+ audioElement.volume = document.getElementById('volumeSlider').value / 100;
671
+
672
+ // Set up audio context
673
+ if (!audioContext) {
674
+ audioContext = new (window.AudioContext || window.webkitAudioContext)();
675
+ }
676
+
677
+ if (analyser) {
678
+ source.disconnect();
679
+ }
680
+
681
+ source = audioContext.createMediaElementSource(audioElement);
682
+ analyser = audioContext.createAnalyser();
683
+ analyser.fftSize = 2048;
684
+
685
+ source.connect(analyser);
686
+ analyser.connect(audioContext.destination);
687
+
688
+ // Update UI
689
+ document.getElementById('songInfo').textContent = file.name;
690
+ document.getElementById('playBtn').classList.add('glow');
691
+ document.getElementById('pauseBtn').classList.remove('glow');
692
+ document.getElementById('stopBtn').classList.remove('glow');
693
+
694
+ // Set up event listeners
695
+ audioElement.addEventListener('play', function() {
696
+ isPlaying = true;
697
+ if (audioContext.state === 'suspended') {
698
+ audioContext.resume();
699
+ }
700
+ document.getElementById('playBtn').classList.add('glow');
701
+ document.getElementById('pauseBtn').classList.remove('glow');
702
+ document.getElementById('stopBtn').classList.remove('glow');
703
+ });
704
+
705
+ audioElement.addEventListener('pause', function() {
706
+ isPlaying = false;
707
+ document.getElementById('playBtn').classList.remove('glow');
708
+ document.getElementById('pauseBtn').classList.add('glow');
709
+ document.getElementById('stopBtn').classList.remove('glow');
710
+ });
711
+
712
+ audioElement.addEventListener('ended', function() {
713
+ isPlaying = false;
714
+ document.getElementById('playBtn').classList.remove('glow');
715
+ document.getElementById('pauseBtn').classList.remove('glow');
716
+ document.getElementById('stopBtn').classList.add('glow');
717
+ });
718
+ }
719
+
720
+ function initMicrophone() {
721
+ if (audioElement) {
722
+ audioElement.pause();
723
+ if (source) source.disconnect();
724
+ }
725
+
726
+ navigator.mediaDevices.getUserMedia({ audio: true, video: false })
727
+ .then(function(stream) {
728
+ if (!audioContext) {
729
+ audioContext = new (window.AudioContext || window.webkitAudioContext)();
730
+ }
731
+
732
+ if (source) {
733
+ source.disconnect();
734
+ }
735
+
736
+ source = audioContext.createMediaStreamSource(stream);
737
+ analyser = audioContext.createAnalyser();
738
+ analyser.fftSize = 2048;
739
+
740
+ source.connect(analyser);
741
+
742
+ // Update UI
743
+ document.getElementById('songInfo').textContent = "Microphone Input";
744
+ document.getElementById('playBtn').classList.remove('glow');
745
+ document.getElementById('pauseBtn').classList.remove('glow');
746
+ document.getElementById('stopBtn').classList.remove('glow');
747
+ isPlaying = true;
748
+
749
+ // For microphone, we're always "playing"
750
+ if (audioContext.state === 'suspended') {
751
+ audioContext.resume();
752
+ }
753
+ })
754
+ .catch(function(err) {
755
+ console.error('Error accessing microphone:', err);
756
+ alert('Could not access microphone. Please check permissions.');
757
+ });
758
+ }
759
+
760
+ function playAudio() {
761
+ if (audioElement && !isPlaying) {
762
+ audioElement.play();
763
+ } else if (!audioElement && !isPlaying) {
764
+ alert('Please load an audio file or enable microphone first.');
765
+ }
766
+ }
767
+
768
+ function pauseAudio() {
769
+ if (audioElement && isPlaying) {
770
+ audioElement.pause();
771
+ }
772
+ }
773
+
774
+ function stopAudio() {
775
+ if (audioElement) {
776
+ audioElement.pause();
777
+ audioElement.currentTime = 0;
778
+ isPlaying = false;
779
+ document.getElementById('playBtn').classList.remove('glow');
780
+ document.getElementById('pauseBtn').classList.remove('glow');
781
+ document.getElementById('stopBtn').classList.add('glow');
782
+ }
783
+ }
784
+
785
+ // Export functions
786
+ function exportImage() {
787
+ const canvas = document.querySelector('#canvas-container canvas');
788
+ const link = document.createElement('a');
789
+ link.download = 'visualizer-snapshot.png';
790
+ link.href = canvas.toDataURL('image/png');
791
+ link.click();
792
+ }
793
+
794
+ function startRecording() {
795
+ const canvas = document.querySelector('#canvas-container canvas');
796
+ const stream = canvas.captureStream(30); // 30 FPS
797
+
798
+ recordedChunks = [];
799
+ mediaRecorder = new MediaRecorder(stream, {
800
+ mimeType: 'video/webm;codecs=vp9'
801
+ });
802
+
803
+ mediaRecorder.ondataavailable = function(e) {
804
+ if (e.data.size > 0) {
805
+ recordedChunks.push(e.data);
806
+ }
807
+ };
808
+
809
+ mediaRecorder.onstop = function() {
810
+ const blob = new Blob(recordedChunks, { type: 'video/webm' });
811
+ const url = URL.createObjectURL(blob);
812
+ const link = document.createElement('a');
813
+ link.download = 'visualizer-recording.webm';
814
+ link.href = url;
815
+ link.click();
816
+
817
+ document.getElementById('recordingControls').classList.add('hidden');
818
+ clearInterval(recordingInterval);
819
+ };
820
+
821
+ mediaRecorder.start();
822
+ recordingStartTime = Date.now();
823
+
824
+ // Update timer
825
+ recordingInterval = setInterval(function() {
826
+ const elapsed = Math.floor((Date.now() - recordingStartTime) / 1000);
827
+ const minutes = Math.floor(elapsed / 60).toString().padStart(2, '0');
828
+ const seconds = (elapsed % 60).toString().padStart(2, '0');
829
+ document.getElementById('recordingTimer').innerHTML =
830
+ `<i class="fas fa-circle text-rose-500 mr-1 animate-pulse"></i> ${minutes}:${seconds}`;
831
+ }, 1000);
832
+
833
+ document.getElementById('recordingControls').classList.remove('hidden');
834
+ }
835
+
836
+ function stopRecording() {
837
+ if (mediaRecorder && mediaRecorder.state === 'recording') {
838
+ mediaRecorder.stop();
839
+ }
840
+ }
841
+
842
+ // Helper function to initialize particles (called from p5 sketch)
843
+ function initParticles() {
844
+ // This is just a placeholder - actual initialization happens in the p5 sketch
845
+ }
846
+ </script>
847
+ <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 <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - <a href="https://enzostvs-deepsite.hf.space?remix=bibbler/audio-reactive-visualizer" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body>
848
+ </html>