Spaces:
Running
Running
Commit ·
e0cb8bc
1
Parent(s): 77deeb4
Disentangle gen wall-clock from audio length; convert recorded blobs to WAV before upload
Browse files- index.html +102 -38
index.html
CHANGED
|
@@ -1745,6 +1745,40 @@ function pickRecorderMimeType() {
|
|
| 1745 |
return "";
|
| 1746 |
}
|
| 1747 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1748 |
window.toggleRecord = async function() {
|
| 1749 |
const btn = document.getElementById("record-mic-btn");
|
| 1750 |
const hint = document.getElementById("record-hint");
|
|
@@ -1767,22 +1801,25 @@ window.toggleRecord = async function() {
|
|
| 1767 |
mediaRecorder = mimeType ? new MediaRecorder(mediaStream, { mimeType }) : new MediaRecorder(mediaStream);
|
| 1768 |
recordedChunks = [];
|
| 1769 |
mediaRecorder.ondataavailable = e => { if (e.data && e.data.size) recordedChunks.push(e.data); };
|
| 1770 |
-
mediaRecorder.onstop = () => {
|
| 1771 |
clearInterval(recordTimerInterval);
|
| 1772 |
btn.classList.remove("recording");
|
| 1773 |
const elapsedSec = (Date.now() - recordStartTime) / 1000;
|
| 1774 |
const type = mediaRecorder.mimeType || "audio/webm";
|
| 1775 |
-
const
|
| 1776 |
-
|
| 1777 |
-
|
| 1778 |
-
|
| 1779 |
-
|
|
|
|
| 1780 |
const preview = document.getElementById("record-preview");
|
| 1781 |
-
preview.src = URL.createObjectURL(
|
| 1782 |
preview.style.display = "";
|
| 1783 |
hint.textContent = `Recorded ${elapsedSec.toFixed(1)}s · click mic to re-record`;
|
| 1784 |
checkModalSave();
|
| 1785 |
-
})
|
|
|
|
|
|
|
| 1786 |
if (mediaStream) { mediaStream.getTracks().forEach(t => t.stop()); mediaStream = null; }
|
| 1787 |
};
|
| 1788 |
recordStartTime = Date.now();
|
|
@@ -1947,6 +1984,7 @@ window.generateDesign = async function() {
|
|
| 1947 |
progressFill.style.width = "0%";
|
| 1948 |
|
| 1949 |
const sessionHash = crypto.randomUUID();
|
|
|
|
| 1950 |
|
| 1951 |
try {
|
| 1952 |
const joinRes = await fetch(`${QWEN_BASE}/queue/join`, {
|
|
@@ -1989,14 +2027,14 @@ window.generateDesign = async function() {
|
|
| 1989 |
else if (data.msg === "process_completed") {
|
| 1990 |
progressFill.style.width = "100%";
|
| 1991 |
if (data.success) {
|
| 1992 |
-
const
|
| 1993 |
designAudioUrl = data.output.data[0].url;
|
| 1994 |
document.getElementById("design-audio-player").src = designAudioUrl;
|
| 1995 |
document.getElementById("design-player-title").textContent = text.slice(0, 60) + (text.length > 60 ? "..." : "");
|
| 1996 |
-
|
| 1997 |
result.classList.add("visible");
|
| 1998 |
-
showDesignStatus("success", `Generated in ${
|
| 1999 |
-
addToHistory(text, "Voice Design",
|
| 2000 |
} else {
|
| 2001 |
const errMsg = data.output?.error || "Generation failed";
|
| 2002 |
showDesignStatusWithCTA(errMsg);
|
|
@@ -2118,6 +2156,7 @@ window.transcribe = async function() {
|
|
| 2118 |
const lang = document.getElementById("transcribe-lang").value;
|
| 2119 |
const btn = transcribeBtn;
|
| 2120 |
const result = document.getElementById("transcribe-result");
|
|
|
|
| 2121 |
|
| 2122 |
btn.disabled = true;
|
| 2123 |
btn.classList.add("loading");
|
|
@@ -2182,7 +2221,7 @@ window.transcribe = async function() {
|
|
| 2182 |
const transcript = data.output.data[0];
|
| 2183 |
document.getElementById("transcribe-text").textContent = transcript;
|
| 2184 |
result.style.display = "block";
|
| 2185 |
-
showTranscribeStatus("success", `Transcribed in ${
|
| 2186 |
} else {
|
| 2187 |
showTranscribeStatusWithCTA(data.output?.error || "Transcription failed");
|
| 2188 |
}
|
|
@@ -2897,21 +2936,26 @@ window.toggleInputRecord = async function(prefix) {
|
|
| 2897 |
document.getElementById(`${prefix}-timer`).textContent = `${Math.floor(e/60)}:${(e%60).toString().padStart(2,"0")}`;
|
| 2898 |
}, 200);
|
| 2899 |
inputRecorders[prefix] = { mediaRecorder: mr, stream, chunks, timerInterval, startTime };
|
| 2900 |
-
mr.onstop = () => {
|
| 2901 |
clearInterval(timerInterval);
|
| 2902 |
btn.classList.remove("recording");
|
| 2903 |
const elapsedSec = (Date.now() - startTime) / 1000;
|
| 2904 |
const type = mr.mimeType || "audio/webm";
|
| 2905 |
-
const
|
| 2906 |
-
|
| 2907 |
-
|
| 2908 |
-
|
| 2909 |
-
|
| 2910 |
-
|
| 2911 |
-
|
| 2912 |
-
|
| 2913 |
-
|
| 2914 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2915 |
};
|
| 2916 |
hint.textContent = "Recording… click again to stop";
|
| 2917 |
btn.classList.add("recording");
|
|
@@ -3007,6 +3051,7 @@ async function uploadFile(base, file) {
|
|
| 3007 |
// Run a queue/join + queue/data SSE call. Returns { output, duration }.
|
| 3008 |
// statusElId/progressFillId are optional and used for UI updates.
|
| 3009 |
async function gradioCall({ base, fnIndex, data, statusElId, progressFillId, processingMsg }) {
|
|
|
|
| 3010 |
const session = crypto.randomUUID();
|
| 3011 |
if (statusElId) showStatusOn(statusElId, "info", "Joining queue...");
|
| 3012 |
|
|
@@ -3054,7 +3099,10 @@ async function gradioCall({ base, fnIndex, data, statusElId, progressFillId, pro
|
|
| 3054 |
}
|
| 3055 |
if (evt.msg === "process_completed") {
|
| 3056 |
if (progressFillId) document.getElementById(progressFillId).style.width = "100%";
|
| 3057 |
-
if (evt.success)
|
|
|
|
|
|
|
|
|
|
| 3058 |
throw new Error(evt.output?.error || "Generation failed");
|
| 3059 |
}
|
| 3060 |
}
|
|
@@ -3062,6 +3110,21 @@ async function gradioCall({ base, fnIndex, data, statusElId, progressFillId, pro
|
|
| 3062 |
throw new Error("Stream ended without completion");
|
| 3063 |
}
|
| 3064 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3065 |
// Pull a URL out of a gradio Audio output (object {url} or {path} ref).
|
| 3066 |
function audioUrlFromOutput(out, base) {
|
| 3067 |
if (!out) return null;
|
|
@@ -3104,7 +3167,7 @@ window.voiceChange = async function() {
|
|
| 3104 |
showStatusOn("vc-status", "info", "Uploading audio...");
|
| 3105 |
const [src, tgt] = await Promise.all([uploadFile(SEED_VC_BASE, vcSource), uploadFile(SEED_VC_BASE, vcTarget)]);
|
| 3106 |
|
| 3107 |
-
const { output,
|
| 3108 |
base: SEED_VC_BASE,
|
| 3109 |
fnIndex: 3,
|
| 3110 |
data: [src, tgt, parseInt(vcSteps.value), 1.0, 0.0, vcSim.value / 100, 0.9, 1.0, 1.0, false, false],
|
|
@@ -3118,9 +3181,9 @@ window.voiceChange = async function() {
|
|
| 3118 |
if (!url) throw new Error("No audio returned");
|
| 3119 |
resultUrls.vc = url;
|
| 3120 |
document.getElementById("vc-audio-player").src = url;
|
| 3121 |
-
|
| 3122 |
result.classList.add("visible");
|
| 3123 |
-
showStatusOn("vc-status", "success", `Converted in ${
|
| 3124 |
} catch (err) {
|
| 3125 |
showStatusCtaOn("vc-status", err.message);
|
| 3126 |
} finally {
|
|
@@ -3166,7 +3229,7 @@ window.voiceIsolate = async function() {
|
|
| 3166 |
showStatusOn("iso-status", "info", "Uploading audio...");
|
| 3167 |
const fileRef = await uploadFile(RESEMBLE_BASE, isoFile);
|
| 3168 |
|
| 3169 |
-
const { output,
|
| 3170 |
base: RESEMBLE_BASE,
|
| 3171 |
fnIndex: 2,
|
| 3172 |
data: [fileRef, "Midpoint", parseInt(isoNfe.value), isoTau.value / 100, denoiseOnly],
|
|
@@ -3182,9 +3245,9 @@ window.voiceIsolate = async function() {
|
|
| 3182 |
resultUrls.iso = url;
|
| 3183 |
document.getElementById("iso-audio-player").src = url;
|
| 3184 |
document.getElementById("iso-player-title").textContent = denoiseOnly ? "Denoised audio" : "Enhanced audio";
|
| 3185 |
-
|
| 3186 |
result.classList.add("visible");
|
| 3187 |
-
showStatusOn("iso-status", "success", `Done in ${
|
| 3188 |
} catch (err) {
|
| 3189 |
showStatusCtaOn("iso-status", err.message);
|
| 3190 |
} finally {
|
|
@@ -3220,7 +3283,7 @@ window.soundEffect = async function() {
|
|
| 3220 |
progressFill.style.width = "0%";
|
| 3221 |
|
| 3222 |
try {
|
| 3223 |
-
const { output,
|
| 3224 |
base: STABLE_AUDIO_BASE,
|
| 3225 |
fnIndex: 3,
|
| 3226 |
// /infer params: variant_key, prompt, duration, steps, cfg_scale, sampler_type, seed
|
|
@@ -3235,9 +3298,9 @@ window.soundEffect = async function() {
|
|
| 3235 |
resultUrls.sfx = url;
|
| 3236 |
document.getElementById("sfx-audio-player").src = url;
|
| 3237 |
document.getElementById("sfx-player-title").textContent = prompt.slice(0, 60) + (prompt.length > 60 ? "..." : "");
|
| 3238 |
-
|
| 3239 |
result.classList.add("visible");
|
| 3240 |
-
showStatusOn("sfx-status", "success", `Generated in ${
|
| 3241 |
} catch (err) {
|
| 3242 |
showStatusCtaOn("sfx-status", err.message);
|
| 3243 |
} finally {
|
|
@@ -3284,6 +3347,7 @@ window.generate = async function() {
|
|
| 3284 |
progressBar.classList.add("visible");
|
| 3285 |
progressFill.style.width = "0%";
|
| 3286 |
|
|
|
|
| 3287 |
try {
|
| 3288 |
const blob = new Blob([selectedVoice.audioData], { type: selectedVoice.audioType || "audio/mpeg" });
|
| 3289 |
const form = new FormData();
|
|
@@ -3346,14 +3410,14 @@ window.generate = async function() {
|
|
| 3346 |
} else if (data.msg === "process_completed") {
|
| 3347 |
progressFill.style.width = "100%";
|
| 3348 |
if (data.success) {
|
| 3349 |
-
const
|
| 3350 |
resultAudioUrl = data.output.data[0].url;
|
| 3351 |
document.getElementById("audio-player").src = resultAudioUrl;
|
| 3352 |
document.getElementById("player-title").textContent = text.slice(0, 60) + (text.length > 60 ? "..." : "");
|
| 3353 |
-
|
| 3354 |
result.classList.add("visible");
|
| 3355 |
-
showStatus("success", `Generated in ${
|
| 3356 |
-
addToHistory(text, selectedVoice.name,
|
| 3357 |
} else {
|
| 3358 |
const errMsg = data.output?.error || "Generation failed";
|
| 3359 |
showStatusWithCTA(errMsg);
|
|
|
|
| 1745 |
return "";
|
| 1746 |
}
|
| 1747 |
|
| 1748 |
+
// Decode any recorded blob and re-encode as 16-bit PCM WAV so soundfile-based
|
| 1749 |
+
// upstreams (resemble-enhance, etc.) can read it.
|
| 1750 |
+
async function recordedBlobToWavFile(blob, baseName) {
|
| 1751 |
+
const Ctx = window.AudioContext || window.webkitAudioContext;
|
| 1752 |
+
const ctx = new Ctx();
|
| 1753 |
+
const arr = await blob.arrayBuffer();
|
| 1754 |
+
const audioBuf = await ctx.decodeAudioData(arr.slice(0));
|
| 1755 |
+
ctx.close?.();
|
| 1756 |
+
const numCh = audioBuf.numberOfChannels;
|
| 1757 |
+
const sampleRate = audioBuf.sampleRate;
|
| 1758 |
+
const numFrames = audioBuf.length;
|
| 1759 |
+
const dataLen = numFrames * numCh * 2;
|
| 1760 |
+
const buf = new ArrayBuffer(44 + dataLen);
|
| 1761 |
+
const view = new DataView(buf);
|
| 1762 |
+
let p = 0;
|
| 1763 |
+
const w8 = s => { for (let i = 0; i < s.length; i++) view.setUint8(p++, s.charCodeAt(i)); };
|
| 1764 |
+
const w32 = v => { view.setUint32(p, v, true); p += 4; };
|
| 1765 |
+
const w16 = v => { view.setUint16(p, v, true); p += 2; };
|
| 1766 |
+
w8("RIFF"); w32(36 + dataLen); w8("WAVE");
|
| 1767 |
+
w8("fmt "); w32(16); w16(1); w16(numCh); w32(sampleRate);
|
| 1768 |
+
w32(sampleRate * numCh * 2); w16(numCh * 2); w16(16);
|
| 1769 |
+
w8("data"); w32(dataLen);
|
| 1770 |
+
const channels = [];
|
| 1771 |
+
for (let c = 0; c < numCh; c++) channels.push(audioBuf.getChannelData(c));
|
| 1772 |
+
for (let i = 0; i < numFrames; i++) {
|
| 1773 |
+
for (let c = 0; c < numCh; c++) {
|
| 1774 |
+
const s = Math.max(-1, Math.min(1, channels[c][i]));
|
| 1775 |
+
view.setInt16(p, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
|
| 1776 |
+
p += 2;
|
| 1777 |
+
}
|
| 1778 |
+
}
|
| 1779 |
+
return new File([buf], `${baseName}.wav`, { type: "audio/wav" });
|
| 1780 |
+
}
|
| 1781 |
+
|
| 1782 |
window.toggleRecord = async function() {
|
| 1783 |
const btn = document.getElementById("record-mic-btn");
|
| 1784 |
const hint = document.getElementById("record-hint");
|
|
|
|
| 1801 |
mediaRecorder = mimeType ? new MediaRecorder(mediaStream, { mimeType }) : new MediaRecorder(mediaStream);
|
| 1802 |
recordedChunks = [];
|
| 1803 |
mediaRecorder.ondataavailable = e => { if (e.data && e.data.size) recordedChunks.push(e.data); };
|
| 1804 |
+
mediaRecorder.onstop = async () => {
|
| 1805 |
clearInterval(recordTimerInterval);
|
| 1806 |
btn.classList.remove("recording");
|
| 1807 |
const elapsedSec = (Date.now() - recordStartTime) / 1000;
|
| 1808 |
const type = mediaRecorder.mimeType || "audio/webm";
|
| 1809 |
+
const rawBlob = new Blob(recordedChunks, { type });
|
| 1810 |
+
hint.textContent = "Converting…";
|
| 1811 |
+
try {
|
| 1812 |
+
const wavFile = await recordedBlobToWavFile(rawBlob, `recording_${Date.now()}`);
|
| 1813 |
+
const wavBuf = await wavFile.arrayBuffer();
|
| 1814 |
+
modalFileData = { name: wavFile.name, data: wavBuf, type: "audio/wav" };
|
| 1815 |
const preview = document.getElementById("record-preview");
|
| 1816 |
+
preview.src = URL.createObjectURL(wavFile);
|
| 1817 |
preview.style.display = "";
|
| 1818 |
hint.textContent = `Recorded ${elapsedSec.toFixed(1)}s · click mic to re-record`;
|
| 1819 |
checkModalSave();
|
| 1820 |
+
} catch (err) {
|
| 1821 |
+
hint.textContent = `Recording failed: ${err.message}`;
|
| 1822 |
+
}
|
| 1823 |
if (mediaStream) { mediaStream.getTracks().forEach(t => t.stop()); mediaStream = null; }
|
| 1824 |
};
|
| 1825 |
recordStartTime = Date.now();
|
|
|
|
| 1984 |
progressFill.style.width = "0%";
|
| 1985 |
|
| 1986 |
const sessionHash = crypto.randomUUID();
|
| 1987 |
+
const t0 = Date.now();
|
| 1988 |
|
| 1989 |
try {
|
| 1990 |
const joinRes = await fetch(`${QWEN_BASE}/queue/join`, {
|
|
|
|
| 2027 |
else if (data.msg === "process_completed") {
|
| 2028 |
progressFill.style.width = "100%";
|
| 2029 |
if (data.success) {
|
| 2030 |
+
const wallSec = (Date.now() - t0) / 1000;
|
| 2031 |
designAudioUrl = data.output.data[0].url;
|
| 2032 |
document.getElementById("design-audio-player").src = designAudioUrl;
|
| 2033 |
document.getElementById("design-player-title").textContent = text.slice(0, 60) + (text.length > 60 ? "..." : "");
|
| 2034 |
+
setPlayerMeta("design", "Voice Design", `${text.length} chars`);
|
| 2035 |
result.classList.add("visible");
|
| 2036 |
+
showDesignStatus("success", `Generated in ${wallSec.toFixed(1)}s`);
|
| 2037 |
+
addToHistory(text, "Voice Design", wallSec, designAudioUrl);
|
| 2038 |
} else {
|
| 2039 |
const errMsg = data.output?.error || "Generation failed";
|
| 2040 |
showDesignStatusWithCTA(errMsg);
|
|
|
|
| 2156 |
const lang = document.getElementById("transcribe-lang").value;
|
| 2157 |
const btn = transcribeBtn;
|
| 2158 |
const result = document.getElementById("transcribe-result");
|
| 2159 |
+
const t0 = Date.now();
|
| 2160 |
|
| 2161 |
btn.disabled = true;
|
| 2162 |
btn.classList.add("loading");
|
|
|
|
| 2221 |
const transcript = data.output.data[0];
|
| 2222 |
document.getElementById("transcribe-text").textContent = transcript;
|
| 2223 |
result.style.display = "block";
|
| 2224 |
+
showTranscribeStatus("success", `Transcribed in ${((Date.now() - t0) / 1000).toFixed(1)}s`);
|
| 2225 |
} else {
|
| 2226 |
showTranscribeStatusWithCTA(data.output?.error || "Transcription failed");
|
| 2227 |
}
|
|
|
|
| 2936 |
document.getElementById(`${prefix}-timer`).textContent = `${Math.floor(e/60)}:${(e%60).toString().padStart(2,"0")}`;
|
| 2937 |
}, 200);
|
| 2938 |
inputRecorders[prefix] = { mediaRecorder: mr, stream, chunks, timerInterval, startTime };
|
| 2939 |
+
mr.onstop = async () => {
|
| 2940 |
clearInterval(timerInterval);
|
| 2941 |
btn.classList.remove("recording");
|
| 2942 |
const elapsedSec = (Date.now() - startTime) / 1000;
|
| 2943 |
const type = mr.mimeType || "audio/webm";
|
| 2944 |
+
const rawBlob = new Blob(chunks, { type });
|
| 2945 |
+
hint.textContent = "Converting…";
|
| 2946 |
+
try {
|
| 2947 |
+
const file = await recordedBlobToWavFile(rawBlob, `recording_${Date.now()}`);
|
| 2948 |
+
const preview = document.getElementById(`${prefix}-preview-audio`);
|
| 2949 |
+
preview.src = URL.createObjectURL(file);
|
| 2950 |
+
preview.style.display = "";
|
| 2951 |
+
hint.textContent = `Recorded ${elapsedSec.toFixed(1)}s · click mic to re-record`;
|
| 2952 |
+
inputRecordHandlers[prefix]?.(file);
|
| 2953 |
+
} catch (err) {
|
| 2954 |
+
hint.textContent = `Recording failed: ${err.message}`;
|
| 2955 |
+
} finally {
|
| 2956 |
+
stream.getTracks().forEach(t => t.stop());
|
| 2957 |
+
delete inputRecorders[prefix];
|
| 2958 |
+
}
|
| 2959 |
};
|
| 2960 |
hint.textContent = "Recording… click again to stop";
|
| 2961 |
btn.classList.add("recording");
|
|
|
|
| 3051 |
// Run a queue/join + queue/data SSE call. Returns { output, duration }.
|
| 3052 |
// statusElId/progressFillId are optional and used for UI updates.
|
| 3053 |
async function gradioCall({ base, fnIndex, data, statusElId, progressFillId, processingMsg }) {
|
| 3054 |
+
const t0 = Date.now();
|
| 3055 |
const session = crypto.randomUUID();
|
| 3056 |
if (statusElId) showStatusOn(statusElId, "info", "Joining queue...");
|
| 3057 |
|
|
|
|
| 3099 |
}
|
| 3100 |
if (evt.msg === "process_completed") {
|
| 3101 |
if (progressFillId) document.getElementById(progressFillId).style.width = "100%";
|
| 3102 |
+
if (evt.success) {
|
| 3103 |
+
const wallClock = (Date.now() - t0) / 1000;
|
| 3104 |
+
return { output: evt.output, duration: evt.output.duration || wallClock, wallClock };
|
| 3105 |
+
}
|
| 3106 |
throw new Error(evt.output?.error || "Generation failed");
|
| 3107 |
}
|
| 3108 |
}
|
|
|
|
| 3110 |
throw new Error("Stream ended without completion");
|
| 3111 |
}
|
| 3112 |
|
| 3113 |
+
// Update a player-meta line with the audio's actual duration once it loads.
|
| 3114 |
+
function setPlayerMeta(prefix, leadingText, trailingText) {
|
| 3115 |
+
const audioId = prefix ? `${prefix}-audio-player` : "audio-player";
|
| 3116 |
+
const metaId = prefix ? `${prefix}-player-meta` : "player-meta";
|
| 3117 |
+
const audio = document.getElementById(audioId);
|
| 3118 |
+
const meta = document.getElementById(metaId);
|
| 3119 |
+
if (!audio || !meta) return;
|
| 3120 |
+
const paint = () => {
|
| 3121 |
+
const d = isFinite(audio.duration) && audio.duration > 0 ? `${audio.duration.toFixed(1)}s` : "";
|
| 3122 |
+
meta.textContent = [leadingText, d, trailingText].filter(Boolean).join(" · ");
|
| 3123 |
+
};
|
| 3124 |
+
audio.addEventListener("loadedmetadata", paint, { once: true });
|
| 3125 |
+
paint();
|
| 3126 |
+
}
|
| 3127 |
+
|
| 3128 |
// Pull a URL out of a gradio Audio output (object {url} or {path} ref).
|
| 3129 |
function audioUrlFromOutput(out, base) {
|
| 3130 |
if (!out) return null;
|
|
|
|
| 3167 |
showStatusOn("vc-status", "info", "Uploading audio...");
|
| 3168 |
const [src, tgt] = await Promise.all([uploadFile(SEED_VC_BASE, vcSource), uploadFile(SEED_VC_BASE, vcTarget)]);
|
| 3169 |
|
| 3170 |
+
const { output, wallClock } = await gradioCall({
|
| 3171 |
base: SEED_VC_BASE,
|
| 3172 |
fnIndex: 3,
|
| 3173 |
data: [src, tgt, parseInt(vcSteps.value), 1.0, 0.0, vcSim.value / 100, 0.9, 1.0, 1.0, false, false],
|
|
|
|
| 3181 |
if (!url) throw new Error("No audio returned");
|
| 3182 |
resultUrls.vc = url;
|
| 3183 |
document.getElementById("vc-audio-player").src = url;
|
| 3184 |
+
setPlayerMeta("vc", `${vcSource.name} → ${vcTarget.name}`);
|
| 3185 |
result.classList.add("visible");
|
| 3186 |
+
showStatusOn("vc-status", "success", `Converted in ${wallClock.toFixed(1)}s`);
|
| 3187 |
} catch (err) {
|
| 3188 |
showStatusCtaOn("vc-status", err.message);
|
| 3189 |
} finally {
|
|
|
|
| 3229 |
showStatusOn("iso-status", "info", "Uploading audio...");
|
| 3230 |
const fileRef = await uploadFile(RESEMBLE_BASE, isoFile);
|
| 3231 |
|
| 3232 |
+
const { output, wallClock } = await gradioCall({
|
| 3233 |
base: RESEMBLE_BASE,
|
| 3234 |
fnIndex: 2,
|
| 3235 |
data: [fileRef, "Midpoint", parseInt(isoNfe.value), isoTau.value / 100, denoiseOnly],
|
|
|
|
| 3245 |
resultUrls.iso = url;
|
| 3246 |
document.getElementById("iso-audio-player").src = url;
|
| 3247 |
document.getElementById("iso-player-title").textContent = denoiseOnly ? "Denoised audio" : "Enhanced audio";
|
| 3248 |
+
setPlayerMeta("iso", isoFile.name);
|
| 3249 |
result.classList.add("visible");
|
| 3250 |
+
showStatusOn("iso-status", "success", `Done in ${wallClock.toFixed(1)}s`);
|
| 3251 |
} catch (err) {
|
| 3252 |
showStatusCtaOn("iso-status", err.message);
|
| 3253 |
} finally {
|
|
|
|
| 3283 |
progressFill.style.width = "0%";
|
| 3284 |
|
| 3285 |
try {
|
| 3286 |
+
const { output, wallClock } = await gradioCall({
|
| 3287 |
base: STABLE_AUDIO_BASE,
|
| 3288 |
fnIndex: 3,
|
| 3289 |
// /infer params: variant_key, prompt, duration, steps, cfg_scale, sampler_type, seed
|
|
|
|
| 3298 |
resultUrls.sfx = url;
|
| 3299 |
document.getElementById("sfx-audio-player").src = url;
|
| 3300 |
document.getElementById("sfx-player-title").textContent = prompt.slice(0, 60) + (prompt.length > 60 ? "..." : "");
|
| 3301 |
+
setPlayerMeta("sfx", `Prompt: "${prompt.slice(0,40)}${prompt.length>40?"...":""}"`);
|
| 3302 |
result.classList.add("visible");
|
| 3303 |
+
showStatusOn("sfx-status", "success", `Generated in ${wallClock.toFixed(1)}s`);
|
| 3304 |
} catch (err) {
|
| 3305 |
showStatusCtaOn("sfx-status", err.message);
|
| 3306 |
} finally {
|
|
|
|
| 3347 |
progressBar.classList.add("visible");
|
| 3348 |
progressFill.style.width = "0%";
|
| 3349 |
|
| 3350 |
+
const t0 = Date.now();
|
| 3351 |
try {
|
| 3352 |
const blob = new Blob([selectedVoice.audioData], { type: selectedVoice.audioType || "audio/mpeg" });
|
| 3353 |
const form = new FormData();
|
|
|
|
| 3410 |
} else if (data.msg === "process_completed") {
|
| 3411 |
progressFill.style.width = "100%";
|
| 3412 |
if (data.success) {
|
| 3413 |
+
const wallSec = (Date.now() - t0) / 1000;
|
| 3414 |
resultAudioUrl = data.output.data[0].url;
|
| 3415 |
document.getElementById("audio-player").src = resultAudioUrl;
|
| 3416 |
document.getElementById("player-title").textContent = text.slice(0, 60) + (text.length > 60 ? "..." : "");
|
| 3417 |
+
setPlayerMeta("", selectedVoice.name, `${text.length} chars`); // prefix="" → uses bare "audio-player"/"player-meta" ids
|
| 3418 |
result.classList.add("visible");
|
| 3419 |
+
showStatus("success", `Generated in ${wallSec.toFixed(1)}s`);
|
| 3420 |
+
addToHistory(text, selectedVoice.name, wallSec, resultAudioUrl);
|
| 3421 |
} else {
|
| 3422 |
const errMsg = data.output?.error || "Generation failed";
|
| 3423 |
showStatusWithCTA(errMsg);
|