Spaces:
Sleeping
Sleeping
| /* ============================================================ | |
| SoundCloud-Style Audio Recorder + Waveform Display + Analyzer | |
| ============================================================ | |
| */ | |
| // Global variables | |
| let audioContext; | |
| let gumStream; | |
| let rec; | |
| let input; | |
| // DOM Elements | |
| const recordBtn = document.getElementById("recordButton"); | |
| const stopBtn = document.getElementById("stopButton"); | |
| const pauseBtn = document.getElementById("pauseButton"); | |
| const recordingsList = document.getElementById("recordingsList"); | |
| const uploadButton = document.getElementById("upload-button"); | |
| const uploadInput = document.getElementById("audio-file"); | |
| const responseBox = document.getElementById("response"); | |
| const metadataBox = document.getElementById("metadata-display"); | |
| // ============================== | |
| // Recording Controls | |
| // ============================== | |
| recordBtn.addEventListener("click", startRecording); | |
| stopBtn.addEventListener("click", stopRecording); | |
| pauseBtn.addEventListener("click", pauseRecording); | |
| // Start recording | |
| function startRecording() { | |
| console.log("Recording started..."); | |
| recordBtn.disabled = true; | |
| stopBtn.disabled = false; | |
| pauseBtn.disabled = false; | |
| navigator.mediaDevices.getUserMedia({ audio: true }).then(function (stream) { | |
| audioContext = new (window.AudioContext || window.webkitAudioContext)(); | |
| document.getElementById("formats").innerText = | |
| "Sample rate: " + audioContext.sampleRate + " Hz"; | |
| gumStream = stream; | |
| input = audioContext.createMediaStreamSource(stream); | |
| rec = new Recorder(input, { numChannels: 1 }); | |
| rec.record(); | |
| }).catch(function (e) { | |
| console.error("Could not start recording:", e); | |
| recordBtn.disabled = false; | |
| stopBtn.disabled = true; | |
| pauseBtn.disabled = true; | |
| }); | |
| } | |
| // Pause recording | |
| function pauseRecording() { | |
| if (rec.recording) { | |
| rec.stop(); | |
| pauseBtn.innerText = "Resume"; | |
| } else { | |
| rec.record(); | |
| pauseBtn.innerText = "Pause"; | |
| } | |
| } | |
| // Stop recording | |
| function stopRecording() { | |
| console.log("Recording stopped."); | |
| stopBtn.disabled = true; | |
| recordBtn.disabled = false; | |
| pauseBtn.disabled = true; | |
| pauseBtn.innerText = "Pause"; | |
| rec.stop(); | |
| gumStream.getAudioTracks()[0].stop(); | |
| rec.exportWAV(createWaveformItem); | |
| } | |
| // ============================== | |
| // File Upload | |
| // ============================== | |
| uploadButton.addEventListener("click", () => { | |
| if (!uploadInput.files.length) { | |
| alert("Select at least one file."); | |
| return; | |
| } | |
| Array.from(uploadInput.files).forEach((file) => handleUploadedFile(file)); | |
| }); | |
| function handleUploadedFile(file) { | |
| const url = URL.createObjectURL(file); | |
| createWaveformItem(file, url); | |
| } | |
| // ============================== | |
| // Create SoundCloud-like Waveform | |
| // ============================== | |
| function createWaveformItem(blob, forcedUrl = null) { | |
| const url = forcedUrl || URL.createObjectURL(blob); | |
| const li = document.createElement("li"); | |
| li.className = "wave-item"; | |
| const container = document.createElement("div"); | |
| container.className = "wave-canvas"; | |
| const meta = document.createElement("div"); | |
| meta.className = "wave-meta"; | |
| meta.innerHTML = `<div class="file-title">${blob.name || "recording.wav"}</div>`; | |
| // Play button | |
| const playBtn = document.createElement("button"); | |
| playBtn.className = "btn btn-play btn-sm"; | |
| playBtn.innerText = "Play"; | |
| // Analyze button | |
| const analyzeBtn = document.createElement("button"); | |
| analyzeBtn.className = "btn btn-outline-secondary btn-sm"; | |
| analyzeBtn.innerText = "Analyze"; | |
| const controls = document.createElement("div"); | |
| controls.className = "wave-controls"; | |
| controls.appendChild(analyzeBtn); | |
| controls.appendChild(playBtn); | |
| li.appendChild(controls); | |
| li.appendChild(container); | |
| li.appendChild(meta); | |
| recordingsList.appendChild(li); | |
| // Create WaveSurfer | |
| const wavesurfer = WaveSurfer.create({ | |
| container, | |
| autoCenter: true, | |
| waveColor: "#bbb", | |
| progressColor: "#ff5500", | |
| cursorColor: "#333", | |
| barWidth: 2, | |
| height: 70, | |
| responsive: true, | |
| }); | |
| // Analyze | |
| analyzeBtn.addEventListener("click", () => { | |
| analyzeAudio(blob); | |
| }); | |
| // Toggle play/pause | |
| playBtn.addEventListener("click", () => { | |
| wavesurfer.playPause(); | |
| playBtn.innerText = wavesurfer.isPlaying() ? "Pause" : "Play"; | |
| }); | |
| wavesurfer.load(url); | |
| // Metadata | |
| blob.arrayBuffer().then((buffer) => { | |
| const ctx = new AudioContext(); | |
| ctx.decodeAudioData(buffer).then((decoded) => { | |
| metadataBox.innerHTML = ` | |
| <strong>Channels:</strong> ${decoded.numberOfChannels}<br> | |
| <strong>Duration:</strong> ${decoded.duration.toFixed(2)}s<br> | |
| <strong>Sample Rate:</strong> ${decoded.sampleRate} Hz | |
| `; | |
| }); | |
| }); | |
| } | |
| // ============================== | |
| // Audio Analysis API (placeholder) | |
| // ============================== | |
| async function analyzeAudio(blob) { | |
| // Check if the response element exists | |
| const responseBox = document.getElementById('response'); | |
| if (!responseBox) return; | |
| responseBox.innerHTML = "<em>Analyzing...</em>"; | |
| // Convert Blob to File | |
| const file = new File([blob], blob.name || "recording.wav", { type: blob.type }); | |
| const formData = new FormData(); | |
| formData.append("files", file); | |
| try { | |
| // Replace the URL with your real backend endpoint | |
| const res = await fetch("/predict/", { | |
| method: "POST", | |
| body: formData, | |
| }); | |
| const data = await res.json(); | |
| let message = "Prediction Failed!"; | |
| if (data[0]?.status === "success") { | |
| const item = data[0]; | |
| const confidencePercent = Math.round(item.confidence * 100); | |
| message = `${item.filename} is ${item.label} with confidence of ${confidencePercent}%`; | |
| } | |
| let messageVolume = "... "; | |
| if (data[0]?.volume === "silent") { | |
| messageVolume = "The audio appears to be silent or contains no detectable speech. Please record/select another file! "; | |
| message = "" | |
| } | |
| if (data[0]?.volume === "augmented") { | |
| messageVolume = "The detected speech volume was low, so a volume augmentation was applied before analysis. "; | |
| } | |
| const volumeColor = "orange"; | |
| let predictionColor = message.includes("Genuine") ? "green" : "red"; | |
| responseBox.innerHTML = ` | |
| <div style="font-size: 1.1rem; margin-bottom: 6px; color:${volumeColor};"> | |
| ${messageVolume} | |
| </div> | |
| <div style="font-size: 1.25rem; font-weight:600; color:${predictionColor};"> | |
| ${message} | |
| </div> | |
| `; | |
| } catch (err) { | |
| responseBox.innerHTML = `<div style="font-size: 1.25rem; font-weight:600; color:red;">/!\ Analysis failed.</div>`; | |
| console.error(err); | |
| } | |
| } | |