Merlintxu commited on
Commit
2a72ea4
verified
1 Parent(s): 8c86c22

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +247 -124
index.html CHANGED
@@ -3,118 +3,151 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Video Reactivo al Audio</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <style>
 
9
  .video-container {
10
  position: relative;
11
  width: 100%;
12
- max-width: 800px;
13
- margin: 0 auto;
14
  overflow: hidden;
15
- transition: filter 0.2s, transform 0.2s;
 
 
16
  }
17
-
18
  .glitch-effect {
19
  position: absolute;
20
  top: 0;
21
  left: 0;
22
  width: 100%;
23
  height: 100%;
24
- background: url('https://getsamplefiles.com/download/webm/sample-2.webm');
25
- mix-blend-mode: screen;
26
- opacity: 0;
 
 
 
27
  }
28
-
 
29
  .glitch-effect:nth-child(2) {
30
  transform: translate(2px, -2px);
31
- opacity: 0.8;
32
  }
33
-
34
  .glitch-effect:nth-child(3) {
35
  transform: translate(-2px, 2px);
36
- opacity: 0.8;
37
  }
38
-
39
  .visualizer {
40
- height: 100px;
41
  width: 100%;
42
- background: linear-gradient(to top, #4b5563, #1f2937);
43
  margin-top: 20px;
44
  border-radius: 8px;
45
  overflow: hidden;
 
 
 
 
46
  }
47
-
48
  .bar {
49
- height: 0;
50
- background: linear-gradient(to top, #3b82f6, #9333ea);
51
- width: 2px;
52
- margin-right: 1px;
53
- display: inline-block;
54
- transition: height 0.1s;
55
  }
56
-
 
 
 
 
57
  .controls {
58
- background-color: rgba(0, 0, 0, 0.7);
59
  padding: 20px;
60
  border-radius: 10px;
61
  margin-top: 20px;
62
  }
63
-
64
  .frequency-label {
65
  display: flex;
66
  justify-content: space-between;
67
  margin-bottom: 10px;
68
  color: white;
69
  font-weight: bold;
 
70
  }
71
-
 
72
  .pulse {
73
  animation: pulse 1s infinite;
74
  }
75
-
76
  @keyframes pulse {
77
  0% { transform: scale(1); }
78
- 50% { transform: scale(1.1); }
79
  100% { transform: scale(1); }
80
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  </style>
82
  </head>
83
- <body class="bg-gray-900 text-white min-h-screen flex flex-col items-center justify-center p-4">
84
  <div class="text-center mb-8">
85
- <h1 class="text-4xl font-bold mb-2">Video Reactivo al Audio</h1>
86
- <p class="text-gray-300">Conecta tu micr贸fono y observa c贸mo el video reacciona a las frecuencias de tu voz</p>
87
  </div>
88
-
89
  <div class="video-container" id="videoContainer">
90
- <video id="video" class="w-full h-auto" loop muted>
91
- <source src="https://getsamplefiles.com/download/webm/sample-2.webm" type="video/webm">
92
  Tu navegador no soporta el elemento de video.
93
  </video>
94
  <div class="glitch-effect"></div>
95
  <div class="glitch-effect"></div>
96
  <div class="glitch-effect"></div>
97
  </div>
98
-
99
- <div class="controls w-full max-w-md mt-8">
100
  <div class="frequency-label">
101
  <span>Graves</span>
102
  <span>Medios</span>
103
  <span>Agudos</span>
104
  </div>
105
  <div class="visualizer" id="visualizer"></div>
106
-
107
  <div class="flex justify-center mt-6">
108
- <button id="startButton" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-6 rounded-full pulse">
109
  Activar Micr贸fono
110
  </button>
111
  </div>
112
-
113
- <div class="mt-6 text-center text-sm text-gray-300">
114
  <p id="status">Esperando permiso para acceder al micr贸fono...</p>
115
  </div>
116
  </div>
117
-
118
  <script>
119
  document.addEventListener('DOMContentLoaded', () => {
120
  const video = document.getElementById('video');
@@ -122,150 +155,240 @@
122
  const startButton = document.getElementById('startButton');
123
  const visualizer = document.getElementById('visualizer');
124
  const statusText = document.getElementById('status');
125
-
126
- let audioContext;
127
- let analyser;
128
- let microphone;
129
- let dataArray;
130
  let isPlaying = false;
131
-
132
- // Crear barras del visualizador
133
- for (let i = 0; i < 64; i++) {
 
134
  const bar = document.createElement('div');
135
  bar.className = 'bar';
136
  visualizer.appendChild(bar);
137
  }
138
  const bars = document.querySelectorAll('.bar');
139
-
140
  // Inicializar el video
141
- video.play();
142
-
143
- // Configurar el audio
 
 
 
 
 
144
  startButton.addEventListener('click', async () => {
145
  try {
146
  if (!isPlaying) {
 
147
  await initAudio();
148
  startButton.textContent = 'Micr贸fono Activado';
149
- startButton.classList.remove('bg-blue-600', 'hover:bg-blue-700');
 
150
  startButton.classList.add('bg-green-600', 'hover:bg-green-700');
151
- startButton.classList.remove('pulse');
152
  statusText.textContent = 'Micr贸fono activado. Habla o reproduce m煤sica para ver los efectos.';
153
  isPlaying = true;
 
 
 
 
 
 
 
154
  }
155
  } catch (err) {
156
- console.error('Error al inicializar el audio:', err);
157
- statusText.textContent = 'Error al acceder al micr贸fono. Aseg煤rate de haber dado los permisos.';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  }
159
  });
160
-
 
161
  async function initAudio() {
162
- audioContext = new (window.AudioContext || window.webkitAudioContext)();
 
 
 
 
 
163
  analyser = audioContext.createAnalyser();
164
- analyser.fftSize = 128;
165
-
 
166
  const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
167
  microphone = audioContext.createMediaStreamSource(stream);
168
- microphone.connect(analyser);
169
-
170
- dataArray = new Uint8Array(analyser.frequencyBinCount);
171
-
 
 
172
  processAudio();
173
  }
174
-
 
175
  function processAudio() {
176
- if (!analyser) return;
177
-
 
 
 
 
 
178
  analyser.getByteFrequencyData(dataArray);
179
-
180
- // Dividir las frecuencias en graves, medios y agudos
181
- const lowFreq = getAverageVolume(dataArray, 0, 7); // 0-170Hz aprox
182
- const midFreq = getAverageVolume(dataArray, 8, 23); // 170-1000Hz aprox
183
- const highFreq = getAverageVolume(dataArray, 24, 63); // 1000-5000Hz aprox
184
-
185
- // Aplicar efectos basados en las frecuencias
 
186
  applyLowFrequencyEffect(lowFreq);
187
  applyMidFrequencyEffect(midFreq);
188
  applyHighFrequencyEffect(highFreq);
189
-
190
- // Actualizar visualizador
191
  updateVisualizer(dataArray);
192
-
 
193
  requestAnimationFrame(processAudio);
194
  }
195
-
 
196
  function getAverageVolume(data, start, end) {
197
  let sum = 0;
198
  for (let i = start; i <= end; i++) {
199
- sum += data[i];
200
  }
201
  return sum / (end - start + 1);
202
  }
203
-
 
204
  function applyLowFrequencyEffect(value) {
205
- // Los graves cambian el tono del video (sepia, brillo, etc.)
206
- const intensity = value / 255;
207
- video.style.filter = `hue-rotate(${intensity * 360}deg) brightness(${1 + intensity * 0.5})`;
 
 
 
208
  }
209
-
210
- let lastMidValue = 0;
 
211
  function applyMidFrequencyEffect(value) {
212
- // Los medios rotan el video
 
 
 
213
  const diff = value - lastMidValue;
214
  lastMidValue = value;
215
-
216
  let rotation = 0;
217
- if (diff > 5) {
218
- rotation = 20; // Rotaci贸n hacia la derecha si suben
219
- } else if (diff < -5) {
220
- rotation = -30; // Rotaci贸n hacia la izquierda si bajan
221
  }
222
-
 
223
  videoContainer.style.transform = `rotate(${rotation}deg)`;
224
  }
225
-
 
226
  function applyHighFrequencyEffect(value) {
227
- // Los agudos crean efecto de glitch/descomposici贸n
228
  const glitchEffects = document.querySelectorAll('.glitch-effect');
229
- const intensity = value / 255;
230
-
231
- if (intensity > 0.5) {
232
- // Efecto de descomposici贸n fuerte
233
- glitchEffects.forEach((effect, index) => {
234
- effect.style.opacity = intensity * 0.8;
235
- effect.style.transform = `translate(${(Math.random() - 0.5) * 10}px, ${(Math.random() - 0.5) * 10}px)`;
 
 
236
  });
237
-
238
- // Tambi茅n agregamos un peque帽o desplazamiento de color
239
- video.style.filter = `brightness(${1 + intensity * 0.3}) contrast(${1 + intensity * 0.5})`;
240
  } else {
241
- // Efecto m谩s suave o ninguno
242
- glitchEffects.forEach(effect => {
243
  effect.style.opacity = 0;
244
  });
 
245
  }
246
  }
247
-
 
248
  function updateVisualizer(data) {
249
- for (let i = 0; i < bars.length; i++) {
250
- const barIndex = Math.floor(i * (data.length / bars.length));
251
- const value = data[barIndex];
252
- const height = `${value / 2}px`;
253
- bars[i].style.height = height;
254
-
255
- // Colores basados en la frecuencia
256
- if (i < bars.length / 3) {
 
 
 
 
 
 
 
257
  // Graves - azul
258
- bars[i].style.background = `linear-gradient(to top, #3b82f6, #1e40af)`;
259
- } else if (i < (bars.length * 2) / 3) {
260
  // Medios - morado
261
- bars[i].style.background = `linear-gradient(to top, #9333ea, #6b21a8)`;
262
  } else {
263
- // Agudos - rosa
264
- bars[i].style.background = `linear-gradient(to top, #ec4899, #be185d)`;
265
  }
266
- }
267
  }
 
 
 
 
 
 
 
 
 
 
268
  });
269
  </script>
270
- <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=Merlintxu/video-audio" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
271
- </html>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Video Reactivo al Audio Mejorado</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <style>
9
+ /* Estilos personalizados que complementan Tailwind */
10
  .video-container {
11
  position: relative;
12
  width: 100%;
13
+ max-width: 800px; /* Ancho m谩ximo para el contenedor del video */
14
+ margin: 0 auto; /* Centrar el contenedor */
15
  overflow: hidden;
16
+ border-radius: 8px; /* Bordes redondeados */
17
+ /* Transiciones suaves para los efectos visuales */
18
+ transition: filter 0.3s ease-out, transform 0.3s ease-out;
19
  }
20
+
21
  .glitch-effect {
22
  position: absolute;
23
  top: 0;
24
  left: 0;
25
  width: 100%;
26
  height: 100%;
27
+ /* Usar una imagen o patr贸n para el efecto glitch */
28
+ background: url('https://getsamplefiles.com/download/webm/sample-2.webm'); /* Podr铆a ser un patr贸n o ruido */
29
+ background-size: cover;
30
+ mix-blend-mode: screen; /* Modo de mezcla para el efecto */
31
+ opacity: 0; /* Inicialmente invisible */
32
+ transition: opacity 0.1s ease-in-out; /* Transici贸n para la opacidad */
33
  }
34
+
35
+ /* Variaciones para las capas del efecto glitch */
36
  .glitch-effect:nth-child(2) {
37
  transform: translate(2px, -2px);
 
38
  }
39
+
40
  .glitch-effect:nth-child(3) {
41
  transform: translate(-2px, 2px);
 
42
  }
43
+
44
  .visualizer {
45
+ height: 100px; /* Altura fija para el visualizador */
46
  width: 100%;
47
+ background: linear-gradient(to top, #4b5563, #1f2937); /* Fondo degradado */
48
  margin-top: 20px;
49
  border-radius: 8px;
50
  overflow: hidden;
51
+ display: flex; /* Usar flexbox para las barras */
52
+ align-items: flex-end; /* Alinear barras en la parte inferior */
53
+ justify-content: space-between; /* Espacio entre barras */
54
+ padding: 0 2px; /* Peque帽o padding horizontal */
55
  }
56
+
57
  .bar {
58
+ height: 0; /* Altura inicial de las barras */
59
+ background: linear-gradient(to top, #3b82f6, #9333ea); /* Degradado de color para las barras */
60
+ width: 2px; /* Ancho fijo de las barras */
61
+ transition: height 0.1s ease-out; /* Transici贸n suave para la altura */
62
+ flex-grow: 1; /* Permitir que las barras crezcan para llenar el espacio */
63
+ margin-right: 1px; /* Espacio entre barras */
64
  }
65
+
66
+ .bar:last-child {
67
+ margin-right: 0; /* Eliminar margen de la 煤ltima barra */
68
+ }
69
+
70
  .controls {
71
+ background-color: rgba(0, 0, 0, 0.7); /* Fondo semitransparente */
72
  padding: 20px;
73
  border-radius: 10px;
74
  margin-top: 20px;
75
  }
76
+
77
  .frequency-label {
78
  display: flex;
79
  justify-content: space-between;
80
  margin-bottom: 10px;
81
  color: white;
82
  font-weight: bold;
83
+ padding: 0 10px; /* Padding para alinear con el visualizador */
84
  }
85
+
86
+ /* Animaci贸n de pulso para el bot贸n */
87
  .pulse {
88
  animation: pulse 1s infinite;
89
  }
90
+
91
  @keyframes pulse {
92
  0% { transform: scale(1); }
93
+ 50% { transform: scale(1.05); } /* Ligeramente m谩s sutil */
94
  100% { transform: scale(1); }
95
  }
96
+
97
+ /* Estilos responsivos con Media Queries personalizadas si es necesario */
98
+ @media (max-width: 640px) { /* Ejemplo de breakpoint para m贸viles */
99
+ .video-container {
100
+ border-radius: 4px; /* Bordes menos redondeados en pantallas peque帽as */
101
+ }
102
+
103
+ .visualizer {
104
+ height: 70px; /* Menor altura en pantallas peque帽as */
105
+ }
106
+
107
+ .bar {
108
+ width: 1px; /* Barras m谩s delgadas en pantallas peque帽as */
109
+ }
110
+
111
+ .controls {
112
+ padding: 15px;
113
+ }
114
+ }
115
  </style>
116
  </head>
117
+ <body class="bg-gray-900 text-white min-h-screen flex flex-col items-center justify-center p-4 sm:p-6 lg:p-8 font-sans">
118
  <div class="text-center mb-8">
119
+ <h1 class="text-3xl sm:text-4xl lg:text-5xl font-extrabold mb-2">Video Reactivo al Audio</h1>
120
+ <p class="text-gray-300 text-sm sm:text-base">Conecta tu micr贸fono y observa c贸mo el video reacciona a las frecuencias de tu voz</p>
121
  </div>
122
+
123
  <div class="video-container" id="videoContainer">
124
+ <video id="video" class="w-full h-auto block" loop muted playsinline> <source src="https://getsamplefiles.com/download/webm/sample-2.webm" type="video/webm">
 
125
  Tu navegador no soporta el elemento de video.
126
  </video>
127
  <div class="glitch-effect"></div>
128
  <div class="glitch-effect"></div>
129
  <div class="glitch-effect"></div>
130
  </div>
131
+
132
+ <div class="controls w-full max-w-sm sm:max-w-md lg:max-w-lg mt-8">
133
  <div class="frequency-label">
134
  <span>Graves</span>
135
  <span>Medios</span>
136
  <span>Agudos</span>
137
  </div>
138
  <div class="visualizer" id="visualizer"></div>
139
+
140
  <div class="flex justify-center mt-6">
141
+ <button id="startButton" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-6 rounded-full transition duration-300 ease-in-out pulse">
142
  Activar Micr贸fono
143
  </button>
144
  </div>
145
+
146
+ <div class="mt-6 text-center text-sm text-gray-400">
147
  <p id="status">Esperando permiso para acceder al micr贸fono...</p>
148
  </div>
149
  </div>
150
+
151
  <script>
152
  document.addEventListener('DOMContentLoaded', () => {
153
  const video = document.getElementById('video');
 
155
  const startButton = document.getElementById('startButton');
156
  const visualizer = document.getElementById('visualizer');
157
  const statusText = document.getElementById('status');
158
+
159
+ let audioContext = null; // Inicializado a null
160
+ let analyser = null;
161
+ let microphone = null;
162
+ let dataArray = null;
163
  let isPlaying = false;
164
+
165
+ // Crear barras del visualizador din谩micamente
166
+ const numberOfBars = 64; // N煤mero de barras para el visualizador
167
+ for (let i = 0; i < numberOfBars; i++) {
168
  const bar = document.createElement('div');
169
  bar.className = 'bar';
170
  visualizer.appendChild(bar);
171
  }
172
  const bars = document.querySelectorAll('.bar');
173
+
174
  // Inicializar el video
175
+ video.play().catch(error => {
176
+ console.error('Error al intentar reproducir el video autom谩ticamente:', error);
177
+ // Manejar el error si la reproducci贸n autom谩tica falla (ej. por pol铆ticas del navegador)
178
+ // No cambiamos el statusText aqu铆 para no sobrescribir el mensaje de "Esperando permiso..."
179
+ });
180
+
181
+
182
+ // Configurar el audio al hacer clic en el bot贸n
183
  startButton.addEventListener('click', async () => {
184
  try {
185
  if (!isPlaying) {
186
+ statusText.textContent = 'Solicitando acceso al micr贸fono...';
187
  await initAudio();
188
  startButton.textContent = 'Micr贸fono Activado';
189
+ // Cambiar colores del bot贸n usando clases de Tailwind
190
+ startButton.classList.remove('bg-blue-600', 'hover:bg-blue-700', 'pulse');
191
  startButton.classList.add('bg-green-600', 'hover:bg-green-700');
 
192
  statusText.textContent = 'Micr贸fono activado. Habla o reproduce m煤sica para ver los efectos.';
193
  isPlaying = true;
194
+
195
+ // Si el video no se reprodujo autom谩ticamente, intentar reproducirlo ahora
196
+ video.play().catch(error => {
197
+ console.error('Error al intentar reproducir el video tras activar micr贸fono:', error);
198
+ // No cambiamos el statusText aqu铆 para no sobrescribir el mensaje de 茅xito del micr贸fono
199
+ });
200
+
201
  }
202
  } catch (err) {
203
+ // --- Inicio de la secci贸n de manejo de errores mejorado ---
204
+ console.error('Error completo al inicializar el audio:', err); // Loguear el objeto de error completo
205
+
206
+ let userMessage = 'Error desconocido al acceder al micr贸fono.';
207
+
208
+ if (err.name === 'NotAllowedError' || err.name === 'PermissionDeniedError') {
209
+ userMessage = 'Permiso de micr贸fono denegado. Por favor, permite el acceso al micr贸fono en la configuraci贸n de tu navegador.';
210
+ } else if (err.name === 'NotFoundError' || err.name === 'DevicesNotFoundError') {
211
+ userMessage = 'No se encontr贸 ning煤n micr贸fono. Aseg煤rate de que uno est茅 conectado y configurado correctamente.';
212
+ } else if (err.name === 'NotReadableError' || err.name === 'TrackStartError') {
213
+ userMessage = 'El micr贸fono est谩 en uso por otra aplicaci贸n o hay un problema de hardware.';
214
+ } else if (err.name === 'OverconstrainedError' || err.name === 'ConstraintNotSatisfiedError') {
215
+ userMessage = 'Error de configuraci贸n del micr贸fono. Intenta con la configuraci贸n predeterminada.';
216
+ } else if (err.name === 'SecurityError') {
217
+ userMessage = 'Se requiere una conexi贸n segura (HTTPS) para acceder al micr贸fono.';
218
+ } else if (err.message) {
219
+ // Si hay un mensaje de error est谩ndar, lo usamos
220
+ userMessage = `Error al acceder al micr贸fono: ${err.message}.`;
221
+ } else {
222
+ // 脷ltimo recurso: intentar stringify el error si no tiene nombre o mensaje
223
+ userMessage = `Error al acceder al micr贸fono: ${typeof err === 'object' ? JSON.stringify(err) : err}.`;
224
+ }
225
+
226
+ statusText.textContent = userMessage; // Mostrar el mensaje espec铆fico al usuario
227
+ // --- Fin de la secci贸n de manejo de errores mejorado ---
228
+
229
+ startButton.classList.remove('bg-green-600', 'hover:bg-green-700');
230
+ startButton.classList.add('bg-red-600', 'hover:bg-red-700');
231
+ startButton.textContent = 'Error de Micr贸fono';
232
  }
233
  });
234
+
235
+ // Funci贸n para inicializar el AudioContext y conectar el micr贸fono
236
  async function initAudio() {
237
+ // Crear AudioContext si no existe
238
+ if (!audioContext) {
239
+ audioContext = new (window.AudioContext || window.webkitAudioContext)();
240
+ }
241
+
242
+ // Crear analizador
243
  analyser = audioContext.createAnalyser();
244
+ analyser.fftSize = 128; // Tama帽o del FFT, afecta la granularidad de las frecuencias
245
+
246
+ // Solicitar acceso al micr贸fono
247
  const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
248
  microphone = audioContext.createMediaStreamSource(stream);
249
+ microphone.connect(analyser); // Conectar el micr贸fono al analizador
250
+
251
+ // Preparar array para los datos de frecuencia
252
+ dataArray = new Uint8Array(analyser.frequencyBinCount); // analyser.frequencyBinCount es la mitad del fftSize
253
+
254
+ // Iniciar el procesamiento de audio
255
  processAudio();
256
  }
257
+
258
+ // Bucle principal para procesar el audio y actualizar la visualizaci贸n/efectos
259
  function processAudio() {
260
+ if (!analyser || !isPlaying) {
261
+ // Si el analizador no est谩 listo o no estamos reproduciendo, salir
262
+ requestAnimationFrame(processAudio); // Seguir intentando en el siguiente frame
263
+ return;
264
+ }
265
+
266
+ // Obtener los datos de frecuencia
267
  analyser.getByteFrequencyData(dataArray);
268
+
269
+ // Dividir las frecuencias en bandas (aproximadas)
270
+ // Los 铆ndices dependen del fftSize y sampleRate del AudioContext
271
+ const lowFreq = getAverageVolume(dataArray, 0, Math.floor(dataArray.length * 0.1)); // Graves (aprox. 0-200 Hz)
272
+ const midFreq = getAverageVolume(dataArray, Math.floor(dataArray.length * 0.1), Math.floor(dataArray.length * 0.4)); // Medios (aprox. 200-1000 Hz)
273
+ const highFreq = getAverageVolume(dataArray, Math.floor(dataArray.length * 0.4), dataArray.length - 1); // Agudos (aprox. 1000+ Hz)
274
+
275
+ // Aplicar efectos visuales basados en las frecuencias
276
  applyLowFrequencyEffect(lowFreq);
277
  applyMidFrequencyEffect(midFreq);
278
  applyHighFrequencyEffect(highFreq);
279
+
280
+ // Actualizar el visualizador de barras
281
  updateVisualizer(dataArray);
282
+
283
+ // Continuar el bucle en el siguiente frame de animaci贸n
284
  requestAnimationFrame(processAudio);
285
  }
286
+
287
+ // Calcula el volumen promedio en un rango de datos
288
  function getAverageVolume(data, start, end) {
289
  let sum = 0;
290
  for (let i = start; i <= end; i++) {
291
+ sum += data[i] || 0; // Sumar valores, usar 0 si el 铆ndice es inv谩lido
292
  }
293
  return sum / (end - start + 1);
294
  }
295
+
296
+ // Aplica efectos basados en las frecuencias graves
297
  function applyLowFrequencyEffect(value) {
298
+ // Los graves cambian el tono y el brillo general
299
+ const intensity = value / 255; // Normalizar el valor entre 0 y 1
300
+ // Ajustar la intensidad del filtro para que sea m谩s notable
301
+ const hue = intensity * 180; // Rotaci贸n de tono hasta 180 grados
302
+ const brightness = 1 + intensity * 0.5; // Brillo hasta 1.5
303
+ video.style.filter = `hue-rotate(${hue}deg) brightness(${brightness})`;
304
  }
305
+
306
+ let lastMidValue = 0; // Para detectar cambios en la frecuencia media
307
+ // Aplica efectos basados en las frecuencias medias
308
  function applyMidFrequencyEffect(value) {
309
+ // Los medios controlan la rotaci贸n
310
+ const threshold = 10; // Umbral para detectar un cambio significativo
311
+ const maxRotation = 10; // Rotaci贸n m谩xima en grados
312
+
313
  const diff = value - lastMidValue;
314
  lastMidValue = value;
315
+
316
  let rotation = 0;
317
+ if (diff > threshold) {
318
+ rotation = maxRotation; // Rotaci贸n positiva si sube
319
+ } else if (diff < -threshold) {
320
+ rotation = -maxRotation; // Rotaci贸n negativa si baja
321
  }
322
+
323
+ // Aplicar la rotaci贸n al contenedor del video
324
  videoContainer.style.transform = `rotate(${rotation}deg)`;
325
  }
326
+
327
+ // Aplica efectos basados en las frecuencias agudas
328
  function applyHighFrequencyEffect(value) {
329
+ // Los agudos activan el efecto glitch
330
  const glitchEffects = document.querySelectorAll('.glitch-effect');
331
+ const activationThreshold = 100; // Umbral para activar el glitch
332
+
333
+ if (value > activationThreshold) {
334
+ const intensity = (value - activationThreshold) / (255 - activationThreshold); // Normalizar por encima del umbral
335
+ glitchEffects.forEach((effect, index) => {
336
+ // Aumentar la opacidad y aplicar un desplazamiento aleatorio basado en la intensidad
337
+ effect.style.opacity = intensity * 0.6; // Opacidad m谩xima del 60%
338
+ const maxOffset = intensity * 20; // Desplazamiento m谩ximo basado en la intensidad
339
+ effect.style.transform = `translate(${(Math.random() - 0.5) * maxOffset}px, ${(Math.random() - 0.5) * maxOffset}px)`;
340
  });
341
+ // A帽adir un peque帽o efecto de contraste o saturaci贸n con los agudos
342
+ video.style.filter += ` contrast(${1 + intensity * 0.2}) saturate(${1 + intensity * 0.3})`;
343
+
344
  } else {
345
+ // Desactivar el glitch si los agudos est谩n por debajo del umbral
346
+ glitchEffects.forEach(effect => {
347
  effect.style.opacity = 0;
348
  });
349
+ // Asegurarse de que los filtros de contraste/saturaci贸n vuelvan a la normalidad (se maneja en applyLowFrequencyEffect)
350
  }
351
  }
352
+
353
+ // Actualiza la altura y color de las barras del visualizador
354
  function updateVisualizer(data) {
355
+ const barWidth = visualizer.offsetWidth / numberOfBars; // Calcular ancho de barra din谩micamente
356
+ bars.forEach((bar, i) => {
357
+ // Mapear los datos de frecuencia a las barras
358
+ const dataIndex = Math.floor(i * (data.length / numberOfBars));
359
+ const value = data[dataIndex] || 0; // Obtener valor, usar 0 si no hay datos
360
+
361
+ // Calcular la altura de la barra (escalada)
362
+ const maxHeight = visualizer.offsetHeight;
363
+ const height = `${(value / 255) * maxHeight}px`; // Escalar la altura al 100% de la altura del visualizador
364
+
365
+ bar.style.height = height;
366
+ bar.style.width = `${barWidth - 1}px`; // Ajustar ancho y restar margen
367
+
368
+ // Asignar colores basados en la posici贸n (frecuencia) de la barra
369
+ if (i < numberOfBars / 3) {
370
  // Graves - azul
371
+ bar.style.background = `linear-gradient(to top, #60a5fa, #3b82f6)`;
372
+ } else if (i < (numberOfBars * 2) / 3) {
373
  // Medios - morado
374
+ bar.style.background = `linear-gradient(to top, #c084fc, #9333ea)`;
375
  } else {
376
+ // Agudos - rosa/rojo
377
+ bar.style.background = `linear-gradient(to top, #f472b6, #ec4899)`;
378
  }
379
+ });
380
  }
381
+
382
+ // Limpieza al cerrar la p谩gina (opcional pero recomendado en aplicaciones m谩s grandes)
383
+ window.addEventListener('beforeunload', () => {
384
+ if (microphone) {
385
+ microphone.disconnect();
386
+ }
387
+ if (audioContext) {
388
+ audioContext.close();
389
+ }
390
+ });
391
  });
392
  </script>
393
+ </body>
394
+ </html>