dev2008/audio-separation
0
1// Frontend JavaScript for audio separation app2 3let currentFiles = [];4let currentFileId = null;5let isProcessing = false;6let uploadedFile = null;7 8// Initialize on page load9document.addEventListener('DOMContentLoaded', () => {10 loadFiles();11 setupEventListeners();12});13 14// Setup event listeners15function setupEventListeners() {16 // File selection and upload17 document.getElementById('fileSelect').addEventListener('change', onFileChange);18 document.getElementById('uploadBtn').addEventListener('click', () => {19 document.getElementById('fileUpload').click();20 });21 document.getElementById('fileUpload').addEventListener('change', handleFileUpload);22 23 // Sliders24 document.getElementById('startTime').addEventListener('input', updateStartTimeLabel);25 document.getElementById('endTime').addEventListener('input', updateEndTimeLabel);26 document.getElementById('components').addEventListener('input', updateComponentsLabel);27 document.getElementById('maskContrast').addEventListener('input', updateMaskContrastLabel);28 document.getElementById('maskThreshold').addEventListener('input', updateMaskThresholdLabel);29 document.getElementById('enhanceOutput').addEventListener('change', toggleEnhanceSuboptions);30 31 // Buttons32 document.getElementById('runBtn').addEventListener('click', runSeparation);33 document.getElementById('detectBtn').addEventListener('click', detectSounds);34}35 36// Handle file upload37async function handleFileUpload(event) {38 const file = event.target.files[0];39 if (!file) return;40 41 const uploadStatus = document.getElementById('uploadStatus');42 uploadStatus.style.display = 'block';43 uploadStatus.textContent = '⏳ Uploading...';44 uploadStatus.style.background = '#ffc107';45 46 try {47 const formData = new FormData();48 formData.append('file', file);49 50 const response = await fetch('/api/upload', {51 method: 'POST',52 body: formData53 });54 55 if (!response.ok) {56 const error = await response.json();57 throw new Error(error.detail || 'Upload failed');58 }59 60 uploadedFile = await response.json();61 62 // Clear file select63 document.getElementById('fileSelect').value = '';64 65 // Update UI66 currentFileId = uploadedFile.id;67 document.getElementById('classInfo').textContent = `Uploaded: ${uploadedFile.filename}`;68 document.getElementById('mixPlayer').src = `/api/audio/${uploadedFile.id}`;69 70 uploadStatus.textContent = `✅ Uploaded: ${uploadedFile.filename}`;71 uploadStatus.style.background = '#28a745';72 73 // Clear old detection results74 document.getElementById('detectionResults').innerHTML = '';75 76 // Load spectrogram77 loadSpectrogram(uploadedFile.id);78 79 // Hide results80 document.getElementById('resultsSection').style.display = 'none';81 82 showToast('File uploaded successfully! Click "Detect Classes" to analyze.', 'success');83 84 } catch (error) {85 uploadStatus.textContent = `❌ Error: ${error.message}`;86 uploadStatus.style.background = '#dc3545';87 showToast('Upload failed: ' + error.message, 'error');88 }89}90 91// Use a suggested prompt92function usePrompt(prompt) {93 document.getElementById('promptInput').value = prompt;94 showToast(`Prompt set: "${prompt}"`, 'success');95 // Scroll to separation section96 document.querySelector('.panel:nth-child(2)').scrollIntoView({ behavior: 'smooth', block: 'start' });97}98 99// Load available files from API100async function loadFiles() {101 try {102 const response = await fetch('/api/files');103 if (!response.ok) throw new Error('Failed to load files');104 105 currentFiles = await response.json();106 107 const select = document.getElementById('fileSelect');108 select.innerHTML = '<option value="">-- Select a file --</option>';109 110 currentFiles.forEach(file => {111 const option = document.createElement('option');112 option.value = file.id;113 option.textContent = `${file.id} (${file.class_label})`;114 select.appendChild(option);115 });116 117 } catch (error) {118 showToast('Error loading files: ' + error.message, 'error');119 }120}121 122// Handle file selection change123async function onFileChange(event) {124 const fileId = event.target.value;125 if (!fileId) {126 currentFileId = null;127 document.getElementById('mixPlayer').src = '';128 return;129 }130 131 // Clear uploaded file status132 uploadedFile = null;133 document.getElementById('uploadStatus').style.display = 'none';134 document.getElementById('fileUpload').value = '';135 136 // Clear old detection results137 document.getElementById('detectionResults').innerHTML = '';138 139 currentFileId = fileId;140 const file = currentFiles.find(f => f.id === fileId);141 142 // Update class info143 document.getElementById('classInfo').textContent = `Class: ${file.class_label}`;144 145 // Update audio player with actual audio file146 document.getElementById('mixPlayer').src = file.audio_url || `/api/audio/${fileId}`;147 148 // Load spectrogram149 loadSpectrogram(fileId);150 151 // Hide results section152 document.getElementById('resultsSection').style.display = 'none';153}154 155// Load and display spectrogram156async function loadSpectrogram(fileId) {157 const img = document.getElementById('mixSpectrogram');158 const loading = document.getElementById('mixLoading');159 160 img.style.display = 'none';161 loading.style.display = 'block';162 163 try {164 const response = await fetch(`/api/spectrogram?file_id=${fileId}`);165 if (!response.ok) throw new Error('Failed to load spectrogram');166 167 const data = await response.json();168 img.src = data.url;169 img.style.display = 'block';170 loading.style.display = 'none';171 172 } catch (error) {173 loading.textContent = 'Error loading spectrogram';174 showToast('Error loading spectrogram: ' + error.message, 'error');175 }176}177 178// Update slider labels179function updateStartTimeLabel() {180 const value = document.getElementById('startTime').value;181 document.getElementById('startTimeValue').textContent = value;182}183 184function updateEndTimeLabel() {185 const value = document.getElementById('endTime').value;186 const label = value >= 5 ? `${value}s (full duration)` : `${value}s`;187 document.getElementById('endTimeValue').textContent = label;188}189 190function updateComponentsLabel() {191 const value = document.getElementById('components').value;192 document.getElementById('componentsValue').textContent = value;193}194 195function updateMaskContrastLabel() {196 const value = document.getElementById('maskContrast').value;197 document.getElementById('maskContrastValue').textContent = value;198}199 200function updateMaskThresholdLabel() {201 const value = parseFloat(document.getElementById('maskThreshold').value).toFixed(2);202 document.getElementById('maskThresholdValue').textContent = value;203}204 205function toggleEnhanceSuboptions() {206 const checked = document.getElementById('enhanceOutput').checked;207 document.getElementById('enhanceSuboptions').style.display = checked ? 'block' : 'none';208}209 210// Run audio separation211async function runSeparation() {212 if (isProcessing) return;213 214 // Validate inputs215 if (!currentFileId) {216 showToast('Please select an audio file', 'error');217 return;218 }219 220 const prompt = document.getElementById('promptInput').value.trim();221 if (!prompt) {222 showToast('Please enter a text prompt', 'error');223 return;224 }225 226 const mode = document.querySelector('input[name="mode"]:checked').value;227 const method = document.querySelector('input[name="method"]:checked').value;228 const t0 = parseFloat(document.getElementById('startTime').value);229 const t1 = parseFloat(document.getElementById('endTime').value);230 const k_components = parseInt(document.getElementById('components').value);231 const mask_contrast = parseFloat(document.getElementById('maskContrast').value);232 const mask_threshold = parseFloat(document.getElementById('maskThreshold').value);233 const enhance_output = document.getElementById('enhanceOutput').checked;234 const enhance_denoise = document.getElementById('enhanceDenoise').checked;235 const enhance_dereverb = document.getElementById('enhanceDereverb').checked;236 const enhance_eq = document.getElementById('enhanceEq').checked;237 238 if (t1 <= t0) {239 showToast('End time must be greater than start time', 'error');240 return;241 }242 243 // Show spinner, disable button244 isProcessing = true;245 document.getElementById('runBtn').disabled = true;246 document.getElementById('spinner').style.display = 'block';247 248 try {249 // Choose endpoint based on method250 const endpoint = method === 'unet' ? '/api/separate_unet' : '/api/separate';251 252 const response = await fetch(endpoint, {253 method: 'POST',254 headers: {255 'Content-Type': 'application/json'256 },257 body: JSON.stringify({258 file_id: currentFileId,259 prompt: prompt,260 mode: mode,261 t0: t0,262 t1: t1 >= 5 ? null : t1, // null means full duration263 k_components: k_components,264 mask_contrast: mask_contrast,265 mask_threshold: mask_threshold,266 enhance_output: enhance_output,267 enhance_denoise: enhance_denoise,268 enhance_dereverb: enhance_dereverb,269 enhance_eq: enhance_eq270 })271 });272 273 if (!response.ok) {274 const error = await response.json();275 throw new Error(error.detail || 'Separation failed');276 }277 278 const result = await response.json();279 displayResults(result);280 281 const methodName = method === 'unet' ? 'UNet (Trained Model)' : 'NMF (Baseline)';282 showToast(`Separation completed with ${methodName}!`, 'success');283 284 } catch (error) {285 showToast('Separation error: ' + error.message, 'error');286 } finally {287 isProcessing = false;288 document.getElementById('runBtn').disabled = false;289 document.getElementById('spinner').style.display = 'none';290 }291}292 293// Display separation results294function displayResults(result) {295 // Show results section296 const resultsSection = document.getElementById('resultsSection');297 resultsSection.style.display = 'block';298 299 // Update audio players300 document.getElementById('outPlayer').src = result.out_wav;301 document.getElementById('residualPlayer').src = result.residual_wav;302 303 // Update images304 document.getElementById('outSpectrogram').src = result.out_spec_png;305 document.getElementById('maskImage').src = result.mask_png;306 307 // Scroll to results308 resultsSection.scrollIntoView({ behavior: 'smooth', block: 'nearest' });309}310 311// Detect sound classes312async function detectSounds() {313 if (!currentFileId) {314 showToast('Please select an audio file', 'error');315 return;316 }317 318 const btn = document.getElementById('detectBtn');319 btn.disabled = true;320 btn.textContent = 'Detecting...';321 322 try {323 const response = await fetch('/api/classes', {324 method: 'POST',325 headers: {326 'Content-Type': 'application/json'327 },328 body: JSON.stringify({329 file_id: currentFileId,330 k_components: 10331 })332 });333 334 if (!response.ok) throw new Error('Detection failed');335 336 const results = await response.json();337 displayDetectionResults(results);338 339 } catch (error) {340 showToast('Detection error: ' + error.message, 'error');341 } finally {342 btn.disabled = false;343 btn.textContent = 'Detect Classes';344 }345}346 347// Display detection results as bar chart348function displayDetectionResults(results) {349 const container = document.getElementById('detectionResults');350 container.innerHTML = '';351 352 results.forEach(result => {353 const bar = document.createElement('div');354 bar.className = 'detection-bar';355 356 const label = document.createElement('div');357 label.className = 'detection-label';358 label.style.cursor = 'pointer';359 label.title = 'Click to use this prompt';360 label.onclick = () => usePrompt(result.class);361 label.innerHTML = `<span>🔍 ${result.class}</span><span>${(result.score * 100).toFixed(1)}%</span>`;362 363 const progress = document.createElement('div');364 progress.className = 'detection-progress';365 366 const fill = document.createElement('div');367 fill.className = 'detection-fill';368 fill.style.width = `${result.score * 100}%`;369 370 progress.appendChild(fill);371 bar.appendChild(label);372 bar.appendChild(progress);373 container.appendChild(bar);374 });375}376 377// Show toast notification378function showToast(message, type = 'info') {379 const toast = document.getElementById('toast');380 toast.textContent = message;381 toast.className = 'toast show ' + type;382 383 setTimeout(() => {384 toast.className = 'toast ' + type;385 }, 3000);386}387 