Spaces:
Running
Running
Commit ·
2c50831
1
Parent(s): ff50d21
Add Voice Changer, Voice Isolator, and Sound Effects tabs
Browse files- index.html +554 -1
index.html
CHANGED
|
@@ -799,6 +799,18 @@ pre {
|
|
| 799 |
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>
|
| 800 |
Voice Design
|
| 801 |
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 802 |
<button class="nav-item" data-page="voices" onclick="showPage('voices', this)">
|
| 803 |
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
|
| 804 |
Voice Library
|
|
@@ -1000,6 +1012,222 @@ pre {
|
|
| 1000 |
</div>
|
| 1001 |
</div>
|
| 1002 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1003 |
<!-- Voice Library Page -->
|
| 1004 |
<div class="page" id="page-voices">
|
| 1005 |
<div class="page-header">
|
|
@@ -1175,7 +1403,7 @@ result = client.<span class="f">predict</span>(
|
|
| 1175 |
</div>
|
| 1176 |
</div>
|
| 1177 |
|
| 1178 |
-
<div class="footer">Powered by <a href="https://huggingface.co/spaces/k2-fsa/OmniVoice" target="_blank">OmniVoice</a>, <a href="https://huggingface.co/spaces/Qwen/Qwen3-TTS" target="_blank">Qwen3-TTS</a> & <a href="https://huggingface.co/spaces/CohereLabs/cohere-transcribe-03-2026" target="_blank">Cohere Transcribe</a></div>
|
| 1179 |
</div>
|
| 1180 |
</div>
|
| 1181 |
</div>
|
|
@@ -2016,6 +2244,331 @@ window.showApiTab = function(tab, el) {
|
|
| 2016 |
el.classList.add("active");
|
| 2017 |
};
|
| 2018 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2019 |
// ── Text ──
|
| 2020 |
|
| 2021 |
const textInput = document.getElementById("text-input");
|
|
|
|
| 799 |
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>
|
| 800 |
Voice Design
|
| 801 |
</button>
|
| 802 |
+
<button class="nav-item" data-page="changer" onclick="showPage('changer', this)">
|
| 803 |
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="6" cy="9" r="2.5"/><path d="M3 17a3 3 0 0 1 3-3"/><circle cx="18" cy="9" r="2.5"/><path d="M21 17a3 3 0 0 0-3-3"/><path d="M8 19h8"/><polyline points="14 17 16 19 14 21"/><polyline points="10 17 8 19 10 21"/></svg>
|
| 804 |
+
Voice Changer
|
| 805 |
+
</button>
|
| 806 |
+
<button class="nav-item" data-page="isolate" onclick="showPage('isolate', this)">
|
| 807 |
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="8" x2="12" y2="16"/><line x1="9" y1="10" x2="9" y2="14"/><line x1="15" y1="10" x2="15" y2="14"/><path d="M6 6c-3 3-3 9 0 12"/><path d="M18 6c3 3 3 9 0 12"/></svg>
|
| 808 |
+
Voice Isolator
|
| 809 |
+
</button>
|
| 810 |
+
<button class="nav-item" data-page="sfx" onclick="showPage('sfx', this)">
|
| 811 |
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3 C 12 8 13 11 21 12 C 13 13 12 16 12 21 C 12 16 11 13 3 12 C 11 11 12 8 12 3 Z"/></svg>
|
| 812 |
+
Sound Effects
|
| 813 |
+
</button>
|
| 814 |
<button class="nav-item" data-page="voices" onclick="showPage('voices', this)">
|
| 815 |
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
|
| 816 |
Voice Library
|
|
|
|
| 1012 |
</div>
|
| 1013 |
</div>
|
| 1014 |
|
| 1015 |
+
<!-- Voice Changer Page -->
|
| 1016 |
+
<div class="page" id="page-changer">
|
| 1017 |
+
<div class="page-header">
|
| 1018 |
+
<div class="page-title">Voice Changer</div>
|
| 1019 |
+
<div class="page-desc">Transform speech into any target voice. Powered by Seed-VC.</div>
|
| 1020 |
+
</div>
|
| 1021 |
+
|
| 1022 |
+
<div class="setting-label" style="margin-bottom:8px">Source audio</div>
|
| 1023 |
+
<div class="upload-area" id="vc-source-upload" style="margin-bottom:16px">
|
| 1024 |
+
<div id="vc-source-label">Drop source audio or click to upload<div class="upload-area-sub">The speech you want to convert</div></div>
|
| 1025 |
+
<input type="file" id="vc-source-input" accept="audio/*">
|
| 1026 |
+
</div>
|
| 1027 |
+
|
| 1028 |
+
<div class="setting-label" style="margin-bottom:8px">Target voice</div>
|
| 1029 |
+
<div class="upload-area" id="vc-target-upload" style="margin-bottom:16px">
|
| 1030 |
+
<div id="vc-target-label">Drop target voice or click to upload<div class="upload-area-sub">Short clip of the voice to convert to</div></div>
|
| 1031 |
+
<input type="file" id="vc-target-input" accept="audio/*">
|
| 1032 |
+
</div>
|
| 1033 |
+
|
| 1034 |
+
<div class="settings-accordion">
|
| 1035 |
+
<button class="settings-toggle" onclick="this.classList.toggle('open');this.nextElementSibling.classList.toggle('open')">
|
| 1036 |
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
|
| 1037 |
+
Advanced Settings
|
| 1038 |
+
</button>
|
| 1039 |
+
<div class="settings-body">
|
| 1040 |
+
<div class="settings-row">
|
| 1041 |
+
<div class="setting-group">
|
| 1042 |
+
<div class="setting-label">Diffusion Steps</div>
|
| 1043 |
+
<div class="setting-value">
|
| 1044 |
+
<input type="range" id="vc-steps" min="4" max="50" value="10">
|
| 1045 |
+
<span class="setting-num" id="vc-steps-val">10</span>
|
| 1046 |
+
</div>
|
| 1047 |
+
</div>
|
| 1048 |
+
<div class="setting-group">
|
| 1049 |
+
<div class="setting-label">Similarity</div>
|
| 1050 |
+
<div class="setting-value">
|
| 1051 |
+
<input type="range" id="vc-sim" min="0" max="100" value="70">
|
| 1052 |
+
<span class="setting-num" id="vc-sim-val">0.7</span>
|
| 1053 |
+
</div>
|
| 1054 |
+
</div>
|
| 1055 |
+
</div>
|
| 1056 |
+
</div>
|
| 1057 |
+
</div>
|
| 1058 |
+
|
| 1059 |
+
<div class="generate-bar">
|
| 1060 |
+
<button class="btn-generate" id="vc-btn" onclick="voiceChange()" disabled>
|
| 1061 |
+
<span class="btn-text">Convert Voice</span>
|
| 1062 |
+
<div class="spinner"></div>
|
| 1063 |
+
</button>
|
| 1064 |
+
</div>
|
| 1065 |
+
|
| 1066 |
+
<div class="status-bar" id="vc-status"></div>
|
| 1067 |
+
<div class="progress-track" id="vc-progress-bar"><div class="progress-fill" id="vc-progress-fill"></div></div>
|
| 1068 |
+
|
| 1069 |
+
<div class="player-card" id="vc-result">
|
| 1070 |
+
<div class="player-top">
|
| 1071 |
+
<button class="play-btn" onclick="togglePlayer('vc')">
|
| 1072 |
+
<svg id="vc-play-icon" viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
| 1073 |
+
<svg id="vc-pause-icon" viewBox="0 0 24 24" fill="currentColor" style="display:none"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg>
|
| 1074 |
+
</button>
|
| 1075 |
+
<div class="player-info">
|
| 1076 |
+
<div class="player-title" id="vc-player-title">Converted audio</div>
|
| 1077 |
+
<div class="player-meta" id="vc-player-meta"></div>
|
| 1078 |
+
</div>
|
| 1079 |
+
<div style="display:flex;gap:6px">
|
| 1080 |
+
<button class="icon-btn" onclick="downloadResult('vc')" title="Download">
|
| 1081 |
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
|
| 1082 |
+
</button>
|
| 1083 |
+
</div>
|
| 1084 |
+
</div>
|
| 1085 |
+
<audio id="vc-audio-player" class="player-audio"></audio>
|
| 1086 |
+
</div>
|
| 1087 |
+
</div>
|
| 1088 |
+
|
| 1089 |
+
<!-- Voice Isolator Page -->
|
| 1090 |
+
<div class="page" id="page-isolate">
|
| 1091 |
+
<div class="page-header">
|
| 1092 |
+
<div class="page-title">Voice Isolator</div>
|
| 1093 |
+
<div class="page-desc">Clean and enhance noisy audio. Powered by Resemble Enhance.</div>
|
| 1094 |
+
</div>
|
| 1095 |
+
|
| 1096 |
+
<div class="upload-area" id="iso-upload" style="margin-bottom:16px">
|
| 1097 |
+
<div id="iso-upload-label">Drop noisy audio or click to upload<div class="upload-area-sub">MP3, WAV, FLAC, M4A · speech with background noise</div></div>
|
| 1098 |
+
<input type="file" id="iso-file-input" accept="audio/*">
|
| 1099 |
+
</div>
|
| 1100 |
+
|
| 1101 |
+
<div class="settings-accordion">
|
| 1102 |
+
<button class="settings-toggle" onclick="this.classList.toggle('open');this.nextElementSibling.classList.toggle('open')">
|
| 1103 |
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
|
| 1104 |
+
Advanced Settings
|
| 1105 |
+
</button>
|
| 1106 |
+
<div class="settings-body">
|
| 1107 |
+
<div class="settings-row">
|
| 1108 |
+
<div class="setting-group">
|
| 1109 |
+
<div class="setting-label">NFE Steps</div>
|
| 1110 |
+
<div class="setting-value">
|
| 1111 |
+
<input type="range" id="iso-nfe" min="1" max="128" value="64">
|
| 1112 |
+
<span class="setting-num" id="iso-nfe-val">64</span>
|
| 1113 |
+
</div>
|
| 1114 |
+
</div>
|
| 1115 |
+
<div class="setting-group">
|
| 1116 |
+
<div class="setting-label">Prior Tau</div>
|
| 1117 |
+
<div class="setting-value">
|
| 1118 |
+
<input type="range" id="iso-tau" min="1" max="100" value="50">
|
| 1119 |
+
<span class="setting-num" id="iso-tau-val">0.5</span>
|
| 1120 |
+
</div>
|
| 1121 |
+
</div>
|
| 1122 |
+
</div>
|
| 1123 |
+
<label style="display:flex;align-items:center;gap:8px;font-size:13px;color:var(--text);cursor:pointer;margin-top:12px">
|
| 1124 |
+
<input type="checkbox" id="iso-denoise-only" style="accent-color:var(--accent)">
|
| 1125 |
+
Denoise only (skip enhancement)
|
| 1126 |
+
</label>
|
| 1127 |
+
</div>
|
| 1128 |
+
</div>
|
| 1129 |
+
|
| 1130 |
+
<div class="generate-bar">
|
| 1131 |
+
<button class="btn-generate" id="iso-btn" onclick="voiceIsolate()" disabled>
|
| 1132 |
+
<span class="btn-text">Enhance Audio</span>
|
| 1133 |
+
<div class="spinner"></div>
|
| 1134 |
+
</button>
|
| 1135 |
+
</div>
|
| 1136 |
+
|
| 1137 |
+
<div class="status-bar" id="iso-status"></div>
|
| 1138 |
+
<div class="progress-track" id="iso-progress-bar"><div class="progress-fill" id="iso-progress-fill"></div></div>
|
| 1139 |
+
|
| 1140 |
+
<div class="player-card" id="iso-result">
|
| 1141 |
+
<div class="player-top">
|
| 1142 |
+
<button class="play-btn" onclick="togglePlayer('iso')">
|
| 1143 |
+
<svg id="iso-play-icon" viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
| 1144 |
+
<svg id="iso-pause-icon" viewBox="0 0 24 24" fill="currentColor" style="display:none"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg>
|
| 1145 |
+
</button>
|
| 1146 |
+
<div class="player-info">
|
| 1147 |
+
<div class="player-title" id="iso-player-title">Enhanced audio</div>
|
| 1148 |
+
<div class="player-meta" id="iso-player-meta"></div>
|
| 1149 |
+
</div>
|
| 1150 |
+
<div style="display:flex;gap:6px">
|
| 1151 |
+
<button class="icon-btn" onclick="downloadResult('iso')" title="Download">
|
| 1152 |
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
|
| 1153 |
+
</button>
|
| 1154 |
+
</div>
|
| 1155 |
+
</div>
|
| 1156 |
+
<audio id="iso-audio-player" class="player-audio"></audio>
|
| 1157 |
+
</div>
|
| 1158 |
+
</div>
|
| 1159 |
+
|
| 1160 |
+
<!-- Sound Effects Page -->
|
| 1161 |
+
<div class="page" id="page-sfx">
|
| 1162 |
+
<div class="page-header">
|
| 1163 |
+
<div class="page-title">Sound Effects</div>
|
| 1164 |
+
<div class="page-desc">Generate sound effects from a text prompt. Powered by Stable Audio 3.</div>
|
| 1165 |
+
</div>
|
| 1166 |
+
|
| 1167 |
+
<div class="editor">
|
| 1168 |
+
<textarea id="sfx-prompt" placeholder="e.g. dog barking, thunder cracking, footsteps on gravel, lo-fi vinyl crackle..."></textarea>
|
| 1169 |
+
<div class="editor-footer">
|
| 1170 |
+
<div class="editor-left">
|
| 1171 |
+
<span class="editor-stat"><span id="sfx-char-count">0</span> chars</span>
|
| 1172 |
+
</div>
|
| 1173 |
+
</div>
|
| 1174 |
+
</div>
|
| 1175 |
+
|
| 1176 |
+
<div class="settings-accordion">
|
| 1177 |
+
<button class="settings-toggle" onclick="this.classList.toggle('open');this.nextElementSibling.classList.toggle('open')">
|
| 1178 |
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
|
| 1179 |
+
Advanced Settings
|
| 1180 |
+
</button>
|
| 1181 |
+
<div class="settings-body">
|
| 1182 |
+
<div class="settings-row">
|
| 1183 |
+
<div class="setting-group">
|
| 1184 |
+
<div class="setting-label">Duration</div>
|
| 1185 |
+
<div class="setting-value">
|
| 1186 |
+
<input type="range" id="sfx-duration" min="1" max="30" value="5">
|
| 1187 |
+
<span class="setting-num" id="sfx-duration-val">5s</span>
|
| 1188 |
+
</div>
|
| 1189 |
+
</div>
|
| 1190 |
+
<div class="setting-group">
|
| 1191 |
+
<div class="setting-label">Inference Steps</div>
|
| 1192 |
+
<div class="setting-value">
|
| 1193 |
+
<input type="range" id="sfx-steps" min="4" max="32" value="8">
|
| 1194 |
+
<span class="setting-num" id="sfx-steps-val">8</span>
|
| 1195 |
+
</div>
|
| 1196 |
+
</div>
|
| 1197 |
+
</div>
|
| 1198 |
+
</div>
|
| 1199 |
+
</div>
|
| 1200 |
+
|
| 1201 |
+
<div class="generate-bar">
|
| 1202 |
+
<button class="btn-generate" id="sfx-btn" onclick="soundEffect()" disabled>
|
| 1203 |
+
<span class="btn-text">Generate Sound</span>
|
| 1204 |
+
<div class="spinner"></div>
|
| 1205 |
+
</button>
|
| 1206 |
+
</div>
|
| 1207 |
+
|
| 1208 |
+
<div class="status-bar" id="sfx-status"></div>
|
| 1209 |
+
<div class="progress-track" id="sfx-progress-bar"><div class="progress-fill" id="sfx-progress-fill"></div></div>
|
| 1210 |
+
|
| 1211 |
+
<div class="player-card" id="sfx-result">
|
| 1212 |
+
<div class="player-top">
|
| 1213 |
+
<button class="play-btn" onclick="togglePlayer('sfx')">
|
| 1214 |
+
<svg id="sfx-play-icon" viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
| 1215 |
+
<svg id="sfx-pause-icon" viewBox="0 0 24 24" fill="currentColor" style="display:none"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg>
|
| 1216 |
+
</button>
|
| 1217 |
+
<div class="player-info">
|
| 1218 |
+
<div class="player-title" id="sfx-player-title">Generated sound</div>
|
| 1219 |
+
<div class="player-meta" id="sfx-player-meta"></div>
|
| 1220 |
+
</div>
|
| 1221 |
+
<div style="display:flex;gap:6px">
|
| 1222 |
+
<button class="icon-btn" onclick="downloadResult('sfx')" title="Download">
|
| 1223 |
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
|
| 1224 |
+
</button>
|
| 1225 |
+
</div>
|
| 1226 |
+
</div>
|
| 1227 |
+
<audio id="sfx-audio-player" class="player-audio"></audio>
|
| 1228 |
+
</div>
|
| 1229 |
+
</div>
|
| 1230 |
+
|
| 1231 |
<!-- Voice Library Page -->
|
| 1232 |
<div class="page" id="page-voices">
|
| 1233 |
<div class="page-header">
|
|
|
|
| 1403 |
</div>
|
| 1404 |
</div>
|
| 1405 |
|
| 1406 |
+
<div class="footer">Powered by <a href="https://huggingface.co/spaces/k2-fsa/OmniVoice" target="_blank">OmniVoice</a>, <a href="https://huggingface.co/spaces/Qwen/Qwen3-TTS" target="_blank">Qwen3-TTS</a>, <a href="https://huggingface.co/spaces/Plachta/Seed-VC" target="_blank">Seed-VC</a>, <a href="https://huggingface.co/spaces/multimodalart/resemble-enhance-zerogpu" target="_blank">Resemble Enhance</a>, <a href="https://huggingface.co/spaces/stabilityai/stable-audio-3" target="_blank">Stable Audio 3</a> & <a href="https://huggingface.co/spaces/CohereLabs/cohere-transcribe-03-2026" target="_blank">Cohere Transcribe</a></div>
|
| 1407 |
</div>
|
| 1408 |
</div>
|
| 1409 |
</div>
|
|
|
|
| 2244 |
el.classList.add("active");
|
| 2245 |
};
|
| 2246 |
|
| 2247 |
+
// ── Shared gradio helpers (for Voice Changer / Isolator / SFX) ──
|
| 2248 |
+
|
| 2249 |
+
const SEED_VC_BASE = "https://plachta-seed-vc.hf.space/gradio_api";
|
| 2250 |
+
const RESEMBLE_BASE = "https://multimodalart-resemble-enhance-zerogpu.hf.space/gradio_api";
|
| 2251 |
+
const STABLE_AUDIO_BASE = "https://stabilityai-stable-audio-3.hf.space/gradio_api";
|
| 2252 |
+
|
| 2253 |
+
const resultUrls = {};
|
| 2254 |
+
|
| 2255 |
+
function showStatusOn(elId, type, msg) {
|
| 2256 |
+
const el = document.getElementById(elId);
|
| 2257 |
+
el.className = `status-bar visible ${type}`;
|
| 2258 |
+
el.innerHTML = `<div class="status-text">${msg}</div>`;
|
| 2259 |
+
}
|
| 2260 |
+
|
| 2261 |
+
function showStatusCtaOn(elId, errMsg) {
|
| 2262 |
+
const el = document.getElementById(elId);
|
| 2263 |
+
const isQuota = errMsg.includes("GPU quota");
|
| 2264 |
+
const isFree = errMsg.includes("free GPU quota");
|
| 2265 |
+
let html = `<div class="status-text">${errMsg}</div>`;
|
| 2266 |
+
if (isQuota) {
|
| 2267 |
+
html += isFree
|
| 2268 |
+
? `<a class="status-cta upgrade" href="https://huggingface.co/subscribe/pro" target="_blank">🤗 Upgrade to HF Pro — 25 min/day GPU quota</a>`
|
| 2269 |
+
: `<a class="status-cta credits" href="https://huggingface.co/settings/billing" target="_blank">Buy additional GPU credits — $1 per 10 min</a>`;
|
| 2270 |
+
const m = errMsg.match(/Try again in ([\d:]+)/);
|
| 2271 |
+
if (m && m[1] !== "0:00:00") html += `<div style="font-size:11px;color:var(--text-muted);margin-top:2px">Or wait ${m[1]} for your quota to reset</div>`;
|
| 2272 |
+
}
|
| 2273 |
+
el.className = "status-bar visible error";
|
| 2274 |
+
el.innerHTML = html;
|
| 2275 |
+
}
|
| 2276 |
+
|
| 2277 |
+
function wireUploadArea(areaId, inputId, labelId, onFile) {
|
| 2278 |
+
const area = document.getElementById(areaId);
|
| 2279 |
+
const inp = document.getElementById(inputId);
|
| 2280 |
+
area.addEventListener("click", e => { if (e.target.tagName !== "INPUT") inp.click(); });
|
| 2281 |
+
area.addEventListener("dragover", e => { e.preventDefault(); area.classList.add("dragover"); });
|
| 2282 |
+
area.addEventListener("dragleave", () => area.classList.remove("dragover"));
|
| 2283 |
+
area.addEventListener("drop", e => {
|
| 2284 |
+
e.preventDefault();
|
| 2285 |
+
area.classList.remove("dragover");
|
| 2286 |
+
if (e.dataTransfer.files[0]) { onFile(e.dataTransfer.files[0]); document.getElementById(labelId).textContent = e.dataTransfer.files[0].name; area.classList.add("has-file"); }
|
| 2287 |
+
});
|
| 2288 |
+
inp.addEventListener("change", () => {
|
| 2289 |
+
if (inp.files[0]) { onFile(inp.files[0]); document.getElementById(labelId).textContent = inp.files[0].name; area.classList.add("has-file"); }
|
| 2290 |
+
});
|
| 2291 |
+
}
|
| 2292 |
+
|
| 2293 |
+
function setupPlayer(prefix) {
|
| 2294 |
+
const el = document.getElementById(`${prefix}-audio-player`);
|
| 2295 |
+
el.addEventListener("play", () => {
|
| 2296 |
+
document.getElementById(`${prefix}-play-icon`).style.display = "none";
|
| 2297 |
+
document.getElementById(`${prefix}-pause-icon`).style.display = "block";
|
| 2298 |
+
});
|
| 2299 |
+
el.addEventListener("pause", () => {
|
| 2300 |
+
document.getElementById(`${prefix}-play-icon`).style.display = "block";
|
| 2301 |
+
document.getElementById(`${prefix}-pause-icon`).style.display = "none";
|
| 2302 |
+
});
|
| 2303 |
+
}
|
| 2304 |
+
|
| 2305 |
+
window.togglePlayer = function(prefix) {
|
| 2306 |
+
const el = document.getElementById(`${prefix}-audio-player`);
|
| 2307 |
+
el.paused ? el.play() : el.pause();
|
| 2308 |
+
};
|
| 2309 |
+
|
| 2310 |
+
window.downloadResult = async function(prefix) {
|
| 2311 |
+
const url = resultUrls[prefix];
|
| 2312 |
+
if (!url) return;
|
| 2313 |
+
const blob = await fetch(url).then(r => r.blob());
|
| 2314 |
+
const a = document.createElement("a");
|
| 2315 |
+
a.href = URL.createObjectURL(blob);
|
| 2316 |
+
a.download = `zerolabs_${prefix}.wav`;
|
| 2317 |
+
a.click();
|
| 2318 |
+
URL.revokeObjectURL(a.href);
|
| 2319 |
+
};
|
| 2320 |
+
|
| 2321 |
+
// Upload a file to a Space's /upload, return a FileData ref ready for /queue/join data array
|
| 2322 |
+
async function uploadFile(base, file) {
|
| 2323 |
+
const form = new FormData();
|
| 2324 |
+
form.append("files", file);
|
| 2325 |
+
const r = await fetch(`${base}/upload`, {
|
| 2326 |
+
method: "POST",
|
| 2327 |
+
headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : {},
|
| 2328 |
+
body: form
|
| 2329 |
+
});
|
| 2330 |
+
if (!r.ok) throw new Error(`Upload failed: ${r.status}`);
|
| 2331 |
+
const [path] = await r.json();
|
| 2332 |
+
return { path, meta: { _type: "gradio.FileData" }, orig_name: file.name };
|
| 2333 |
+
}
|
| 2334 |
+
|
| 2335 |
+
// Run a queue/join + queue/data SSE call. Returns { output, duration }.
|
| 2336 |
+
// statusElId/progressFillId are optional and used for UI updates.
|
| 2337 |
+
async function gradioCall({ base, fnIndex, data, statusElId, progressFillId, processingMsg }) {
|
| 2338 |
+
const session = crypto.randomUUID();
|
| 2339 |
+
if (statusElId) showStatusOn(statusElId, "info", "Joining queue...");
|
| 2340 |
+
|
| 2341 |
+
const joinRes = await fetch(`${base}/queue/join`, {
|
| 2342 |
+
method: "POST",
|
| 2343 |
+
headers: {
|
| 2344 |
+
"Content-Type": "application/json",
|
| 2345 |
+
...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {})
|
| 2346 |
+
},
|
| 2347 |
+
body: JSON.stringify({ data, fn_index: fnIndex, session_hash: session })
|
| 2348 |
+
});
|
| 2349 |
+
if (!joinRes.ok) throw new Error(`Queue join failed: ${joinRes.status}`);
|
| 2350 |
+
|
| 2351 |
+
const streamRes = await fetch(`${base}/queue/data?session_hash=${session}`, {
|
| 2352 |
+
headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : {}
|
| 2353 |
+
});
|
| 2354 |
+
|
| 2355 |
+
const reader = streamRes.body.getReader();
|
| 2356 |
+
const decoder = new TextDecoder();
|
| 2357 |
+
let buffer = "";
|
| 2358 |
+
|
| 2359 |
+
while (true) {
|
| 2360 |
+
const { done, value } = await reader.read();
|
| 2361 |
+
if (done) break;
|
| 2362 |
+
buffer += decoder.decode(value, { stream: true });
|
| 2363 |
+
const lines = buffer.split("\n\n");
|
| 2364 |
+
buffer = lines.pop();
|
| 2365 |
+
for (const line of lines) {
|
| 2366 |
+
if (!line.startsWith("data: ")) continue;
|
| 2367 |
+
const evt = JSON.parse(line.slice(6));
|
| 2368 |
+
if (statusElId) {
|
| 2369 |
+
if (evt.msg === "estimation") showStatusOn(statusElId, "info", `In queue (position ${evt.rank})...`);
|
| 2370 |
+
else if (evt.msg === "process_starts") {
|
| 2371 |
+
showStatusOn(statusElId, "info", processingMsg || "Processing...");
|
| 2372 |
+
if (progressFillId) document.getElementById(progressFillId).style.width = "10%";
|
| 2373 |
+
}
|
| 2374 |
+
else if (evt.msg === "log") showStatusOn(statusElId, "info", evt.log);
|
| 2375 |
+
else if (evt.msg === "progress" && evt.progress_data?.length) {
|
| 2376 |
+
const p = evt.progress_data[0];
|
| 2377 |
+
if (p.length && progressFillId) {
|
| 2378 |
+
document.getElementById(progressFillId).style.width = `${Math.round((p.index / p.length) * 90) + 10}%`;
|
| 2379 |
+
showStatusOn(statusElId, "info", p.desc || `Step ${p.index}/${p.length}`);
|
| 2380 |
+
}
|
| 2381 |
+
}
|
| 2382 |
+
}
|
| 2383 |
+
if (evt.msg === "process_completed") {
|
| 2384 |
+
if (progressFillId) document.getElementById(progressFillId).style.width = "100%";
|
| 2385 |
+
if (evt.success) return { output: evt.output, duration: evt.output.duration || 0 };
|
| 2386 |
+
throw new Error(evt.output?.error || "Generation failed");
|
| 2387 |
+
}
|
| 2388 |
+
}
|
| 2389 |
+
}
|
| 2390 |
+
throw new Error("Stream ended without completion");
|
| 2391 |
+
}
|
| 2392 |
+
|
| 2393 |
+
// Pull a URL out of a gradio Audio output (object {url} or {path} ref).
|
| 2394 |
+
function audioUrlFromOutput(out, base) {
|
| 2395 |
+
if (!out) return null;
|
| 2396 |
+
if (typeof out === "string") return out;
|
| 2397 |
+
if (out.url) return out.url;
|
| 2398 |
+
if (out.path) return `${base}/file=${out.path}`;
|
| 2399 |
+
return null;
|
| 2400 |
+
}
|
| 2401 |
+
|
| 2402 |
+
// ── Voice Changer (Seed-VC) ──
|
| 2403 |
+
|
| 2404 |
+
let vcSource = null, vcTarget = null;
|
| 2405 |
+
const vcSteps = document.getElementById("vc-steps");
|
| 2406 |
+
const vcSim = document.getElementById("vc-sim");
|
| 2407 |
+
vcSteps.addEventListener("input", () => document.getElementById("vc-steps-val").textContent = vcSteps.value);
|
| 2408 |
+
vcSim.addEventListener("input", () => document.getElementById("vc-sim-val").textContent = (vcSim.value / 100).toFixed(2));
|
| 2409 |
+
|
| 2410 |
+
function updateVcBtn() {
|
| 2411 |
+
document.getElementById("vc-btn").disabled = !(vcSource && vcTarget);
|
| 2412 |
+
}
|
| 2413 |
+
wireUploadArea("vc-source-upload", "vc-source-input", "vc-source-label", f => { vcSource = f; updateVcBtn(); });
|
| 2414 |
+
wireUploadArea("vc-target-upload", "vc-target-input", "vc-target-label", f => { vcTarget = f; updateVcBtn(); });
|
| 2415 |
+
setupPlayer("vc");
|
| 2416 |
+
|
| 2417 |
+
window.voiceChange = async function() {
|
| 2418 |
+
if (!vcSource || !vcTarget) return;
|
| 2419 |
+
const btn = document.getElementById("vc-btn");
|
| 2420 |
+
const progressBar = document.getElementById("vc-progress-bar");
|
| 2421 |
+
const progressFill = document.getElementById("vc-progress-fill");
|
| 2422 |
+
const result = document.getElementById("vc-result");
|
| 2423 |
+
|
| 2424 |
+
btn.disabled = true; btn.classList.add("loading");
|
| 2425 |
+
result.classList.remove("visible");
|
| 2426 |
+
progressBar.classList.add("visible");
|
| 2427 |
+
progressFill.style.width = "0%";
|
| 2428 |
+
|
| 2429 |
+
try {
|
| 2430 |
+
showStatusOn("vc-status", "info", "Uploading audio...");
|
| 2431 |
+
const [src, tgt] = await Promise.all([uploadFile(SEED_VC_BASE, vcSource), uploadFile(SEED_VC_BASE, vcTarget)]);
|
| 2432 |
+
|
| 2433 |
+
const { output, duration } = await gradioCall({
|
| 2434 |
+
base: SEED_VC_BASE,
|
| 2435 |
+
fnIndex: 3,
|
| 2436 |
+
data: [src, tgt, parseInt(vcSteps.value), 1.0, 0.0, vcSim.value / 100, 0.9, 1.0, 1.0, false, false],
|
| 2437 |
+
statusElId: "vc-status",
|
| 2438 |
+
progressFillId: "vc-progress-fill",
|
| 2439 |
+
processingMsg: "Converting voice..."
|
| 2440 |
+
});
|
| 2441 |
+
|
| 2442 |
+
// Seed-VC outputs: [Stream Output (HLS), Full Output (wav)]. Use the full one.
|
| 2443 |
+
const url = audioUrlFromOutput(output.data[1], SEED_VC_BASE);
|
| 2444 |
+
if (!url) throw new Error("No audio returned");
|
| 2445 |
+
resultUrls.vc = url;
|
| 2446 |
+
document.getElementById("vc-audio-player").src = url;
|
| 2447 |
+
document.getElementById("vc-player-meta").textContent = `${vcSource.name} → ${vcTarget.name} · ${duration.toFixed(1)}s`;
|
| 2448 |
+
result.classList.add("visible");
|
| 2449 |
+
showStatusOn("vc-status", "success", `Converted in ${duration.toFixed(1)}s`);
|
| 2450 |
+
updateUsageTracker(duration * 0.5);
|
| 2451 |
+
} catch (err) {
|
| 2452 |
+
showStatusCtaOn("vc-status", err.message);
|
| 2453 |
+
} finally {
|
| 2454 |
+
btn.disabled = false; btn.classList.remove("loading");
|
| 2455 |
+
setTimeout(() => { progressBar.classList.remove("visible"); updateVcBtn(); }, 2000);
|
| 2456 |
+
}
|
| 2457 |
+
};
|
| 2458 |
+
|
| 2459 |
+
// ── Voice Isolator (Resemble Enhance) ──
|
| 2460 |
+
|
| 2461 |
+
let isoFile = null;
|
| 2462 |
+
const isoNfe = document.getElementById("iso-nfe");
|
| 2463 |
+
const isoTau = document.getElementById("iso-tau");
|
| 2464 |
+
isoNfe.addEventListener("input", () => document.getElementById("iso-nfe-val").textContent = isoNfe.value);
|
| 2465 |
+
isoTau.addEventListener("input", () => document.getElementById("iso-tau-val").textContent = (isoTau.value / 100).toFixed(2));
|
| 2466 |
+
|
| 2467 |
+
wireUploadArea("iso-upload", "iso-file-input", "iso-upload-label", f => {
|
| 2468 |
+
isoFile = f;
|
| 2469 |
+
document.getElementById("iso-btn").disabled = false;
|
| 2470 |
+
});
|
| 2471 |
+
setupPlayer("iso");
|
| 2472 |
+
|
| 2473 |
+
window.voiceIsolate = async function() {
|
| 2474 |
+
if (!isoFile) return;
|
| 2475 |
+
const btn = document.getElementById("iso-btn");
|
| 2476 |
+
const progressBar = document.getElementById("iso-progress-bar");
|
| 2477 |
+
const progressFill = document.getElementById("iso-progress-fill");
|
| 2478 |
+
const result = document.getElementById("iso-result");
|
| 2479 |
+
const denoiseOnly = document.getElementById("iso-denoise-only").checked;
|
| 2480 |
+
|
| 2481 |
+
btn.disabled = true; btn.classList.add("loading");
|
| 2482 |
+
result.classList.remove("visible");
|
| 2483 |
+
progressBar.classList.add("visible");
|
| 2484 |
+
progressFill.style.width = "0%";
|
| 2485 |
+
|
| 2486 |
+
try {
|
| 2487 |
+
showStatusOn("iso-status", "info", "Uploading audio...");
|
| 2488 |
+
const fileRef = await uploadFile(RESEMBLE_BASE, isoFile);
|
| 2489 |
+
|
| 2490 |
+
const { output, duration } = await gradioCall({
|
| 2491 |
+
base: RESEMBLE_BASE,
|
| 2492 |
+
fnIndex: 2,
|
| 2493 |
+
data: [fileRef, "Midpoint", parseInt(isoNfe.value), isoTau.value / 100, denoiseOnly],
|
| 2494 |
+
statusElId: "iso-status",
|
| 2495 |
+
progressFillId: "iso-progress-fill",
|
| 2496 |
+
processingMsg: "Enhancing audio..."
|
| 2497 |
+
});
|
| 2498 |
+
|
| 2499 |
+
// Outputs: [Denoised, Enhanced]. Pick enhanced normally; denoised if denoise-only.
|
| 2500 |
+
const idx = denoiseOnly ? 0 : 1;
|
| 2501 |
+
const url = audioUrlFromOutput(output.data[idx], RESEMBLE_BASE);
|
| 2502 |
+
if (!url) throw new Error("No audio returned");
|
| 2503 |
+
resultUrls.iso = url;
|
| 2504 |
+
document.getElementById("iso-audio-player").src = url;
|
| 2505 |
+
document.getElementById("iso-player-title").textContent = denoiseOnly ? "Denoised audio" : "Enhanced audio";
|
| 2506 |
+
document.getElementById("iso-player-meta").textContent = `${isoFile.name} · ${duration.toFixed(1)}s`;
|
| 2507 |
+
result.classList.add("visible");
|
| 2508 |
+
showStatusOn("iso-status", "success", `Done in ${duration.toFixed(1)}s`);
|
| 2509 |
+
updateUsageTracker(duration * 0.5);
|
| 2510 |
+
} catch (err) {
|
| 2511 |
+
showStatusCtaOn("iso-status", err.message);
|
| 2512 |
+
} finally {
|
| 2513 |
+
btn.disabled = false; btn.classList.remove("loading");
|
| 2514 |
+
setTimeout(() => { progressBar.classList.remove("visible"); }, 2000);
|
| 2515 |
+
}
|
| 2516 |
+
};
|
| 2517 |
+
|
| 2518 |
+
// ── Sound Effects (Stable Audio 3) ──
|
| 2519 |
+
|
| 2520 |
+
const sfxPrompt = document.getElementById("sfx-prompt");
|
| 2521 |
+
const sfxDuration = document.getElementById("sfx-duration");
|
| 2522 |
+
const sfxSteps = document.getElementById("sfx-steps");
|
| 2523 |
+
sfxPrompt.addEventListener("input", () => {
|
| 2524 |
+
document.getElementById("sfx-char-count").textContent = sfxPrompt.value.length;
|
| 2525 |
+
document.getElementById("sfx-btn").disabled = !sfxPrompt.value.trim();
|
| 2526 |
+
});
|
| 2527 |
+
sfxDuration.addEventListener("input", () => document.getElementById("sfx-duration-val").textContent = `${sfxDuration.value}s`);
|
| 2528 |
+
sfxSteps.addEventListener("input", () => document.getElementById("sfx-steps-val").textContent = sfxSteps.value);
|
| 2529 |
+
setupPlayer("sfx");
|
| 2530 |
+
|
| 2531 |
+
window.soundEffect = async function() {
|
| 2532 |
+
const prompt = sfxPrompt.value.trim();
|
| 2533 |
+
if (!prompt) return;
|
| 2534 |
+
const btn = document.getElementById("sfx-btn");
|
| 2535 |
+
const progressBar = document.getElementById("sfx-progress-bar");
|
| 2536 |
+
const progressFill = document.getElementById("sfx-progress-fill");
|
| 2537 |
+
const result = document.getElementById("sfx-result");
|
| 2538 |
+
|
| 2539 |
+
btn.disabled = true; btn.classList.add("loading");
|
| 2540 |
+
result.classList.remove("visible");
|
| 2541 |
+
progressBar.classList.add("visible");
|
| 2542 |
+
progressFill.style.width = "0%";
|
| 2543 |
+
|
| 2544 |
+
try {
|
| 2545 |
+
const { output, duration } = await gradioCall({
|
| 2546 |
+
base: STABLE_AUDIO_BASE,
|
| 2547 |
+
fnIndex: 3,
|
| 2548 |
+
// /infer params: variant_key, prompt, duration, steps, cfg_scale, sampler_type, seed
|
| 2549 |
+
data: ["medium", prompt, parseInt(sfxDuration.value), parseInt(sfxSteps.value), 1.0, "pingpong", 0],
|
| 2550 |
+
statusElId: "sfx-status",
|
| 2551 |
+
progressFillId: "sfx-progress-fill",
|
| 2552 |
+
processingMsg: "Generating sound..."
|
| 2553 |
+
});
|
| 2554 |
+
|
| 2555 |
+
const url = audioUrlFromOutput(output.data[0], STABLE_AUDIO_BASE);
|
| 2556 |
+
if (!url) throw new Error("No audio returned");
|
| 2557 |
+
resultUrls.sfx = url;
|
| 2558 |
+
document.getElementById("sfx-audio-player").src = url;
|
| 2559 |
+
document.getElementById("sfx-player-title").textContent = prompt.slice(0, 60) + (prompt.length > 60 ? "..." : "");
|
| 2560 |
+
document.getElementById("sfx-player-meta").textContent = `${sfxDuration.value}s · ${duration.toFixed(1)}s gen`;
|
| 2561 |
+
result.classList.add("visible");
|
| 2562 |
+
showStatusOn("sfx-status", "success", `Generated in ${duration.toFixed(1)}s`);
|
| 2563 |
+
updateUsageTracker(duration * 0.5);
|
| 2564 |
+
} catch (err) {
|
| 2565 |
+
showStatusCtaOn("sfx-status", err.message);
|
| 2566 |
+
} finally {
|
| 2567 |
+
btn.disabled = false; btn.classList.remove("loading");
|
| 2568 |
+
setTimeout(() => { progressBar.classList.remove("visible"); }, 2000);
|
| 2569 |
+
}
|
| 2570 |
+
};
|
| 2571 |
+
|
| 2572 |
// ── Text ──
|
| 2573 |
|
| 2574 |
const textInput = document.getElementById("text-input");
|