Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -43,134 +43,170 @@ def audio_visualizer(audio_file, audio_record):
|
|
| 43 |
elif audio_record:
|
| 44 |
vis_data, audio_output = process_audio(audio_record)
|
| 45 |
else:
|
| 46 |
-
return "Please upload an audio file or record audio.", None
|
| 47 |
|
| 48 |
-
return vis_data, audio_output
|
| 49 |
|
| 50 |
# Custom CSS and JavaScript for the visualizer
|
| 51 |
visualizer_html = """
|
| 52 |
-
<
|
| 53 |
-
<div class="audio-wave" id="audioWave" style="position: absolute; bottom: 0; left: 0; width: 100%; height: 120px; background: linear-gradient(to top, rgba(0, 180, 219, 0.2), transparent);"></div>
|
| 54 |
-
<div class="loading-spinner" id="loadingSpinner" style="display: none; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); border: 4px solid rgba(255,255,255,0.3); border-top: 4px solid #00b4db; border-radius: 50%; width: 40px; height: 40px; animation: spin 1s linear infinite;"></div>
|
| 55 |
-
</div>
|
| 56 |
|
| 57 |
<style>
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
background: linear-gradient(to top, #00b4db, #0083b0);
|
| 63 |
-
border-radius: 6px 6px 0 0;
|
| 64 |
-
transition: height 0.1s cubic-bezier(0.4, 0, 0.2, 1);
|
| 65 |
-
}
|
| 66 |
-
@keyframes spin {
|
| 67 |
-
0% { transform: translate(-50%, -50%) rotate(0deg); }
|
| 68 |
-
100% { transform: translate(-50%, -50%) rotate(360deg); }
|
| 69 |
-
}
|
| 70 |
-
.bg-animated {
|
| 71 |
-
animation: gradient 15s ease infinite;
|
| 72 |
-
background-size: 400% 400%;
|
| 73 |
-
}
|
| 74 |
-
@keyframes gradient {
|
| 75 |
-
0% { background-position: 0% 50%; }
|
| 76 |
-
50% { background-position: 100% 50%; }
|
| 77 |
-
100% { background-position: 0% 50%; }
|
| 78 |
}
|
| 79 |
</style>
|
| 80 |
|
| 81 |
<script>
|
| 82 |
document.addEventListener('DOMContentLoaded', () => {
|
| 83 |
-
const
|
| 84 |
-
const
|
| 85 |
-
let
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
if (containerWidth === 0) {
|
| 95 |
-
console.warn('Visualizer container width is 0, retrying on resize');
|
| 96 |
-
return;
|
| 97 |
-
}
|
| 98 |
-
const barWidth = (containerWidth / barCount) - barSpacing;
|
| 99 |
-
for (let i = 0; i < barCount; i++) {
|
| 100 |
-
const bar = document.createElement('div');
|
| 101 |
-
bar.className = 'bar';
|
| 102 |
-
bar.style.left = `${i * (barWidth + barSpacing)}px`;
|
| 103 |
-
bar.style.width = `${barWidth}px`;
|
| 104 |
-
bar.style.height = '0px';
|
| 105 |
-
visualizer.appendChild(bar);
|
| 106 |
-
bars.push(bar);
|
| 107 |
-
}
|
| 108 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
|
| 110 |
-
|
| 111 |
-
|
|
|
|
|
|
|
|
|
|
| 112 |
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
}
|
| 120 |
|
| 121 |
-
//
|
| 122 |
-
function
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
|
|
|
| 132 |
}
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
}
|
| 138 |
-
const wavePoints = frequencies.map((f, i) => [
|
| 139 |
-
(i / frequencies.length) * visualizer.clientWidth,
|
| 140 |
-
visualizer.clientHeight * (1 - (f / maxFreq))
|
| 141 |
-
]);
|
| 142 |
-
let wavePath = `path('M0 ${visualizer.clientHeight / 2} `;
|
| 143 |
-
wavePoints.forEach(([x, y]) => {
|
| 144 |
-
wavePath += `L${x} ${y} `;
|
| 145 |
-
});
|
| 146 |
-
wavePath += `L${visualizer.clientWidth} ${visualizer.clientHeight / 2} Z')`;
|
| 147 |
-
audioWave.style.clipPath = wavePath;
|
| 148 |
}
|
| 149 |
|
| 150 |
-
//
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
}
|
| 155 |
-
});
|
| 156 |
|
| 157 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
|
| 159 |
-
//
|
|
|
|
|
|
|
|
|
|
| 160 |
setInterval(() => {
|
| 161 |
-
const visDataOutput = document.querySelector('
|
|
|
|
| 162 |
if (visDataOutput && visDataOutput.value) {
|
| 163 |
try {
|
| 164 |
-
|
| 165 |
-
updateVisualization(data);
|
| 166 |
} catch (e) {
|
| 167 |
console.error('Error parsing visualization data:', e);
|
| 168 |
-
|
| 169 |
}
|
| 170 |
} else {
|
| 171 |
-
|
|
|
|
| 172 |
}
|
| 173 |
-
},
|
| 174 |
});
|
| 175 |
</script>
|
| 176 |
"""
|
|
@@ -178,7 +214,7 @@ visualizer_html = """
|
|
| 178 |
# Gradio interface
|
| 179 |
with gr.Blocks() as demo:
|
| 180 |
gr.Markdown("# Advanced Audio Visualizer")
|
| 181 |
-
gr.Markdown("Upload an audio file or record audio to visualize frequencies and beats.")
|
| 182 |
|
| 183 |
with gr.Row():
|
| 184 |
audio_file = gr.Audio(sources=["upload"], type="filepath", label="Upload Audio")
|
|
@@ -188,9 +224,6 @@ with gr.Blocks() as demo:
|
|
| 188 |
vis_output = gr.JSON(label="Visualization Data")
|
| 189 |
audio_output = gr.Audio(label="Audio Playback", type="filepath")
|
| 190 |
|
| 191 |
-
# Hidden output to pass data to JavaScript
|
| 192 |
-
vis_data_output = gr.JSON(elem_id="vis_data_output", visible=False)
|
| 193 |
-
|
| 194 |
with gr.Row():
|
| 195 |
submit = gr.Button("Visualize")
|
| 196 |
clear = gr.Button("Clear")
|
|
@@ -201,12 +234,12 @@ with gr.Blocks() as demo:
|
|
| 201 |
submit.click(
|
| 202 |
fn=audio_visualizer,
|
| 203 |
inputs=[audio_file, audio_record],
|
| 204 |
-
outputs=[vis_output, audio_output
|
| 205 |
)
|
| 206 |
clear.click(
|
| 207 |
-
fn=lambda: (None, None
|
| 208 |
inputs=[],
|
| 209 |
-
outputs=[audio_file, audio_record
|
| 210 |
)
|
| 211 |
|
| 212 |
# Launch Gradio app
|
|
|
|
| 43 |
elif audio_record:
|
| 44 |
vis_data, audio_output = process_audio(audio_record)
|
| 45 |
else:
|
| 46 |
+
return "Please upload an audio file or record audio.", None
|
| 47 |
|
| 48 |
+
return vis_data, audio_output
|
| 49 |
|
| 50 |
# Custom CSS and JavaScript for the visualizer
|
| 51 |
visualizer_html = """
|
| 52 |
+
<canvas id="visualizerCanvas" style="width: 100%; height: 500px; background: #1a1a2e; border-radius: 16px; box-shadow: 0 15px 40px rgba(0, 0, 0, 0.4);"></canvas>
|
|
|
|
|
|
|
|
|
|
| 53 |
|
| 54 |
<style>
|
| 55 |
+
canvas {
|
| 56 |
+
display: block;
|
| 57 |
+
max-width: 800px;
|
| 58 |
+
margin: 0 auto;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
}
|
| 60 |
</style>
|
| 61 |
|
| 62 |
<script>
|
| 63 |
document.addEventListener('DOMContentLoaded', () => {
|
| 64 |
+
const canvas = document.getElementById('visualizerCanvas');
|
| 65 |
+
const ctx = canvas.getContext('2d');
|
| 66 |
+
let audioElement = null;
|
| 67 |
+
let data = { frequencies: [], beat_times: [], volume: 0 };
|
| 68 |
+
let particles = [];
|
| 69 |
+
let lastBeatIndex = 0;
|
| 70 |
+
|
| 71 |
+
// Set canvas size to match its CSS size
|
| 72 |
+
function resizeCanvas() {
|
| 73 |
+
canvas.width = canvas.offsetWidth;
|
| 74 |
+
canvas.height = canvas.offsetHeight;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
}
|
| 76 |
+
resizeCanvas();
|
| 77 |
+
window.addEventListener('resize', resizeCanvas);
|
| 78 |
+
|
| 79 |
+
// Particle class for beat effects
|
| 80 |
+
class Particle {
|
| 81 |
+
constructor(x, y, radius, speedX, speedY) {
|
| 82 |
+
this.x = x;
|
| 83 |
+
this.y = y;
|
| 84 |
+
this.radius = radius;
|
| 85 |
+
this.speedX = speedX;
|
| 86 |
+
this.speedY = speedY;
|
| 87 |
+
this.alpha = 1;
|
| 88 |
+
}
|
| 89 |
|
| 90 |
+
update() {
|
| 91 |
+
this.x += this.speedX;
|
| 92 |
+
this.y += this.speedY;
|
| 93 |
+
this.alpha -= 0.02;
|
| 94 |
+
}
|
| 95 |
|
| 96 |
+
draw() {
|
| 97 |
+
ctx.beginPath();
|
| 98 |
+
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
|
| 99 |
+
ctx.fillStyle = `rgba(0, 180, 219, ${this.alpha})`;
|
| 100 |
+
ctx.fill();
|
| 101 |
+
}
|
| 102 |
}
|
| 103 |
|
| 104 |
+
// Spawn particles on beats
|
| 105 |
+
function spawnParticles(volume) {
|
| 106 |
+
const centerX = canvas.width / 2;
|
| 107 |
+
const centerY = canvas.height / 2;
|
| 108 |
+
const particleCount = Math.floor(volume / 2) + 5; // More particles for higher volume
|
| 109 |
+
for (let i = 0; i < particleCount; i++) {
|
| 110 |
+
const angle = Math.random() * Math.PI * 2;
|
| 111 |
+
const speed = Math.random() * 5 + 2;
|
| 112 |
+
const speedX = Math.cos(angle) * speed;
|
| 113 |
+
const speedY = Math.sin(angle) * speed;
|
| 114 |
+
const radius = Math.random() * 5 + 2;
|
| 115 |
+
particles.push(new Particle(centerX, centerY, radius, speedX, speedY));
|
| 116 |
}
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
// Check for beats based on audio playback time
|
| 120 |
+
function checkBeats() {
|
| 121 |
+
if (!audioElement || !data.beat_times) return;
|
| 122 |
+
const currentTime = audioElement.currentTime;
|
| 123 |
+
for (let i = lastBeatIndex; i < data.beat_times.length; i++) {
|
| 124 |
+
if (currentTime >= data.beat_times[i]) {
|
| 125 |
+
spawnParticles(data.volume);
|
| 126 |
+
lastBeatIndex = i + 1;
|
| 127 |
+
} else {
|
| 128 |
+
break;
|
| 129 |
+
}
|
| 130 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
}
|
| 132 |
|
| 133 |
+
// Animation loop
|
| 134 |
+
function animate() {
|
| 135 |
+
requestAnimationFrame(animate);
|
| 136 |
+
|
| 137 |
+
// Clear canvas
|
| 138 |
+
ctx.fillStyle = 'rgba(26, 26, 46, 0.8)';
|
| 139 |
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
| 140 |
+
|
| 141 |
+
// Center of the canvas
|
| 142 |
+
const centerX = canvas.width / 2;
|
| 143 |
+
const centerY = canvas.height / 2;
|
| 144 |
+
const radius = Math.min(canvas.width, canvas.height) * 0.2;
|
| 145 |
+
|
| 146 |
+
// Draw glowing center circle (pulsing with volume)
|
| 147 |
+
const glowRadius = radius * (1 + data.volume / 100);
|
| 148 |
+
const gradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, glowRadius);
|
| 149 |
+
gradient.addColorStop(0, `rgba(0, 180, 219, ${0.5 + data.volume / 200})`);
|
| 150 |
+
gradient.addColorStop(1, 'rgba(0, 180, 219, 0)');
|
| 151 |
+
ctx.beginPath();
|
| 152 |
+
ctx.arc(centerX, centerY, glowRadius, 0, Math.PI * 2);
|
| 153 |
+
ctx.fillStyle = gradient;
|
| 154 |
+
ctx.fill();
|
| 155 |
+
|
| 156 |
+
// Draw circular spectrum
|
| 157 |
+
const freqCount = data.frequencies.length;
|
| 158 |
+
const barCount = 100; // Number of bars in the circle
|
| 159 |
+
const angleStep = (Math.PI * 2) / barCount;
|
| 160 |
+
for (let i = 0; i < barCount; i++) {
|
| 161 |
+
const freqIndex = Math.floor((i / barCount) * freqCount);
|
| 162 |
+
const freqValue = freqIndex < freqCount ? data.frequencies[freqIndex] : 0;
|
| 163 |
+
const maxFreq = Math.max(...data.frequencies) || 1;
|
| 164 |
+
const barLength = (freqValue / maxFreq) * 100 + 20; // Scale bar length
|
| 165 |
+
const angle = i * angleStep;
|
| 166 |
+
|
| 167 |
+
const x1 = centerX + Math.cos(angle) * radius;
|
| 168 |
+
const y1 = centerY + Math.sin(angle) * radius;
|
| 169 |
+
const x2 = centerX + Math.cos(angle) * (radius + barLength);
|
| 170 |
+
const y2 = centerY + Math.sin(angle) * (radius + barLength);
|
| 171 |
+
|
| 172 |
+
ctx.beginPath();
|
| 173 |
+
ctx.moveTo(x1, y1);
|
| 174 |
+
ctx.lineTo(x2, y2);
|
| 175 |
+
ctx.strokeStyle = `hsl(${i * (360 / barCount)}, 80%, 50%)`;
|
| 176 |
+
ctx.lineWidth = 2;
|
| 177 |
+
ctx.stroke();
|
| 178 |
}
|
|
|
|
| 179 |
|
| 180 |
+
// Update and draw particles
|
| 181 |
+
particles = particles.filter(p => p.alpha > 0);
|
| 182 |
+
particles.forEach(particle => {
|
| 183 |
+
particle.update();
|
| 184 |
+
particle.draw();
|
| 185 |
+
});
|
| 186 |
+
|
| 187 |
+
// Check for beats
|
| 188 |
+
checkBeats();
|
| 189 |
+
}
|
| 190 |
|
| 191 |
+
// Start animation
|
| 192 |
+
animate();
|
| 193 |
+
|
| 194 |
+
// Poll the visible JSON output for updates
|
| 195 |
setInterval(() => {
|
| 196 |
+
const visDataOutput = document.querySelector('div[label="Visualization Data"] textarea');
|
| 197 |
+
audioElement = document.querySelector('audio'); // Get the audio player
|
| 198 |
if (visDataOutput && visDataOutput.value) {
|
| 199 |
try {
|
| 200 |
+
data = JSON.parse(visDataOutput.value);
|
|
|
|
| 201 |
} catch (e) {
|
| 202 |
console.error('Error parsing visualization data:', e);
|
| 203 |
+
data = { frequencies: [], beat_times: [], volume: 0 };
|
| 204 |
}
|
| 205 |
} else {
|
| 206 |
+
data = { frequencies: [], beat_times: [], volume: 0 };
|
| 207 |
+
lastBeatIndex = 0; // Reset beat index
|
| 208 |
}
|
| 209 |
+
}, 100); // Poll more frequently for smoother animations
|
| 210 |
});
|
| 211 |
</script>
|
| 212 |
"""
|
|
|
|
| 214 |
# Gradio interface
|
| 215 |
with gr.Blocks() as demo:
|
| 216 |
gr.Markdown("# Advanced Audio Visualizer")
|
| 217 |
+
gr.Markdown("Upload an audio file or record audio to visualize frequencies and beats with dynamic effects.")
|
| 218 |
|
| 219 |
with gr.Row():
|
| 220 |
audio_file = gr.Audio(sources=["upload"], type="filepath", label="Upload Audio")
|
|
|
|
| 224 |
vis_output = gr.JSON(label="Visualization Data")
|
| 225 |
audio_output = gr.Audio(label="Audio Playback", type="filepath")
|
| 226 |
|
|
|
|
|
|
|
|
|
|
| 227 |
with gr.Row():
|
| 228 |
submit = gr.Button("Visualize")
|
| 229 |
clear = gr.Button("Clear")
|
|
|
|
| 234 |
submit.click(
|
| 235 |
fn=audio_visualizer,
|
| 236 |
inputs=[audio_file, audio_record],
|
| 237 |
+
outputs=[vis_output, audio_output]
|
| 238 |
)
|
| 239 |
clear.click(
|
| 240 |
+
fn=lambda: (None, None),
|
| 241 |
inputs=[],
|
| 242 |
+
outputs=[audio_file, audio_record]
|
| 243 |
)
|
| 244 |
|
| 245 |
# Launch Gradio app
|