Voice consistency: - Read back each voice's pinned seed (Seed Finder / Batch Seeds) on every generation. The seed was saved to voice metadata but only ever read by the Seed Finder's own benchmark path, so all per-voice seed pinning was inert. - Stop coercing the "voice_design_playback" stability profile back to "voice_clone". The pseudo-backend key isn't a real routing target, so the backend-name normalizer silently rewrote it — reintroducing the hardcoded seed:0 that profile exists to avoid, overriding every per-voice pin. - Apply the accent clause on every line, not just at voice-creation time, and reorder the instruct so emotion leads and accent trails (Qwen3-TTS doesn't reliably follow multiple conflicting instructions). - Pass an explicit language to Voice Design instead of leaving it on "Auto". Audio effects: - Add a limiter after compressor makeup gain. Makeup gain pushed peaks to ~1.9, and the final hard clip turned that into broadband distortion that swamped the rest of the chain. - Cascade highpass/lowpass 3 stages each (~18 dB/octave). Single-pole filters were too gentle to band-limit speech audibly. - Add a Bandpass control and wire it into the Telephone/Radio presets — compression alone never sounded like a phone; band-limiting is the defining trait. Persona / Try It Out: - Disable "Apply character persona" with an explanatory tooltip when the voice has no persona saved, and error clearly server-side instead of silently no-op'ing. Persona is typed manually per voice, never auto-filled. - Stop dropping applyPersona in the chunked generation path (>200 chars). - Populate the Voice Design dropdown from the user's own library rather than filtering the engine's discovery list, which never contains custom voices. Navigation and library: - Use pushState instead of replaceState so browser Back/Forward step through in-app navigation instead of leaving the app entirely. - Show real dialogue line counts in the character sidebar instead of the capped reference-quote count (which showed a misleading uniform "12"). Also fixes a crash in /api/transcribe-bytes that referenced an undefined source_id in its cleanup path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
299 lines
20 KiB
HTML
299 lines
20 KiB
HTML
<div class="section-head">
|
||
<span class="section-icon"><span class="mdi mdi-play"></span></span>
|
||
<div class="section-title">
|
||
<h2>Try It Out</h2>
|
||
<p>Generate speech from text using any backend and voice. Also transcribe audio and re-speak it.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- TTS Generation Playground -->
|
||
<div class="tab-content" id="tab-generation">
|
||
|
||
<!-- ① Voice selection ─────────────────────────────────────────── -->
|
||
<div class="card" style="padding:14px">
|
||
<h2><span class="mdi mdi-account-voice"></span> Voice & playback</h2>
|
||
<div class="engine-setup-row" style="margin-bottom:8px">
|
||
<div class="engine-setup-col" style="flex:1">
|
||
<label class="engine-setup-label"><span class="mdi mdi-account-voice"></span> Text to Speech <a href="#" class="engine-setup-link" onclick="navTo('s-llms'); navEnginesCat('tts'); return false;" title="Configure Text to Speech settings"><span class="mdi mdi-cog-outline"></span></a></label>
|
||
<div class="engine-setup-controls">
|
||
<select id="tts-backend-select" aria-label="TTS backend"><option value="">Checking...</option></select>
|
||
<button class="btn-secondary" id="fetch-tts-voices-btn" title="Fetch available voices"><span class="mdi mdi-refresh"></span></button>
|
||
<select id="tts-voice-select" aria-label="Voice"><option value="">— fetch voices —</option></select>
|
||
</div>
|
||
</div>
|
||
<div class="engine-setup-col no-border" style="flex:0.5">
|
||
<label class="engine-setup-label"><span class="mdi mdi-tune"></span> Playback</label>
|
||
<div class="engine-setup-controls">
|
||
<select id="preview-playback-mode" aria-label="Playback mode">
|
||
<option value="settings">Settings default</option>
|
||
<option value="streaming">Streaming only</option>
|
||
<option value="auto">Stream with fallback</option>
|
||
<option value="buffered">Buffered WAV</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="backend-help" id="tts-backend-help" aria-live="polite" style="padding-left:14px">
|
||
<strong>Checking available TTS backends…</strong>
|
||
</div>
|
||
<!-- Reference voice panel (shown when the selected voice matches the library) -->
|
||
<div class="preview-match-panel" id="preview-match-panel" hidden style="margin-top:12px">
|
||
<div class="preview-match-meta">
|
||
<div class="preview-match-title" id="preview-match-title">Reference voice</div>
|
||
<div class="preview-match-detail" id="preview-match-detail"></div>
|
||
<div class="preview-match-warning" id="preview-match-warning"></div>
|
||
<div class="preview-match-actions">
|
||
<button class="btn-secondary" id="preview-ref-play" type="button">Play reference WAV</button>
|
||
<button class="btn-secondary" id="preview-ref-use-text" type="button">Use reference text</button>
|
||
<button class="btn-primary" id="preview-ref-synth" type="button">Synthesize reference text</button>
|
||
</div>
|
||
</div>
|
||
<div class="preview-match-meta">
|
||
<div class="preview-match-transcript" id="preview-match-transcript">No reference text saved for this voice.</div>
|
||
<audio id="preview-ref-audio" controls></audio>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ② Text & style ────────────────────────────────────────────── -->
|
||
<div class="card">
|
||
<h2><span class="mdi mdi-text-long"></span> Text to synthesize</h2>
|
||
<div class="field">
|
||
<textarea id="preview-text-area" placeholder="Enter the text you want to synthesize…" style="min-height:120px">Hello! This is a voice preview from TTS Voice Creator - Clone and Design.</textarea>
|
||
</div>
|
||
<div class="field" style="margin-top:10px">
|
||
<label style="display:flex;align-items:center;gap:8px">
|
||
Style instruction
|
||
<span style="font-weight:400;color:var(--subtext)">(optional)</span>
|
||
<span id="preview-style-support" class="backend-tag" style="font-size:11px;display:none"></span>
|
||
</label>
|
||
<input type="text" id="preview-style-instruction" placeholder="e.g. whisper softly, sound excited, speak very slowly">
|
||
<span id="preview-style-warn" class="note" style="color:var(--yellow);display:none">
|
||
<span class="mdi mdi-alert-outline"></span> The selected backend has <strong>weak style support</strong> — this instruction may be ignored. Switch to a <em>style-aware</em> backend (CustomVoice, VoiceDesign) for full effect.
|
||
</span>
|
||
</div>
|
||
<div class="field" style="margin-top:10px;display:flex;align-items:center;gap:12px">
|
||
<label style="white-space:nowrap;font-weight:500">Native Speed <span class="note">(0.5–2)</span></label>
|
||
<input type="number" id="preview-native-speed" min="0.5" max="2" step="0.05" value="1" placeholder="1.0" autocomplete="off" style="width:80px">
|
||
</div>
|
||
<div class="btn-row" style="gap:20px;margin-top:8px">
|
||
<label class="chunk-toggle-label" title="Rewrite text through this voice's character persona before generating">
|
||
<input type="checkbox" id="preview-persona-toggle">
|
||
<span>Apply character persona</span>
|
||
<span class="note" style="margin-left:2px">(LLM rewrite)</span>
|
||
</label>
|
||
<label class="chunk-toggle-label" title="Split long text at sentence boundaries and stitch chunks together">
|
||
<input type="checkbox" id="preview-chunked-toggle">
|
||
<span>Chunked generation</span>
|
||
<span class="note" style="margin-left:2px">(long text)</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ③ Generate ─────────────────────────────────────────────────── -->
|
||
<div class="card">
|
||
<div class="btn-row" style="gap:10px;flex-wrap:wrap">
|
||
<button class="btn-primary" id="preview-btn" style="padding:10px 24px;font-size:15px"><span class="mdi mdi-play"></span> Generate & play</button>
|
||
<button class="btn-secondary" id="save-preview-mp3-btn" disabled><span class="mdi mdi-download"></span> Save MP3</button>
|
||
<button class="btn-secondary" id="save-preview-btn" disabled><span class="mdi mdi-download"></span> Save WAV</button>
|
||
<button class="btn-secondary" id="add-to-playlist-btn" disabled><span class="mdi mdi-plus"></span> Playlist</button>
|
||
</div>
|
||
<audio id="preview-audio" controls style="display:none;margin-top:12px;width:100%"></audio>
|
||
<div id="preview-chunk-progress" class="chunk-progress" hidden></div>
|
||
<p class="note" style="margin-top:8px">After changing active voices, restart the TTS container so the engine reads the updated voice folder.</p>
|
||
</div>
|
||
|
||
<!-- Audio Effects -->
|
||
<div class="card" id="effects-card">
|
||
<h2>Audio effects</h2>
|
||
<p class="card-subtitle">Apply post-processing to the last generated audio. Non-destructive — re-generate to reset.</p>
|
||
<div class="effects-preset-row">
|
||
<label class="field-label-sm">Preset</label>
|
||
<select id="effects-preset" aria-label="Effects preset">
|
||
<option value="">— Custom —</option>
|
||
<option value="studio">Studio Reverb</option>
|
||
<option value="broadcast">Broadcast (compressed)</option>
|
||
<option value="telephone">Telephone</option>
|
||
<option value="warm">Warm & Intimate</option>
|
||
<option value="radio">Radio Voice</option>
|
||
</select>
|
||
<button class="btn-secondary btn-sm" id="effects-reset-btn">Reset</button>
|
||
</div>
|
||
<div class="effects-grid">
|
||
<div class="fx-row">
|
||
<label class="fx-toggle"><input type="checkbox" id="fx-reverb-on"> Reverb</label>
|
||
<div class="fx-sliders" id="fx-reverb-params">
|
||
<label>Room <input type="range" id="fx-reverb-room" min="0" max="1" step="0.01" value="0.35"><span id="fx-reverb-room-val">0.35</span></label>
|
||
<label>Wet <input type="range" id="fx-reverb-wet" min="0" max="1" step="0.01" value="0.25"><span id="fx-reverb-wet-val">0.25</span></label>
|
||
</div>
|
||
</div>
|
||
<div class="fx-row">
|
||
<label class="fx-toggle"><input type="checkbox" id="fx-compressor-on"> Compressor</label>
|
||
<div class="fx-sliders" id="fx-compressor-params">
|
||
<label>Threshold <input type="range" id="fx-comp-thresh" min="-60" max="0" step="1" value="-20"><span id="fx-comp-thresh-val">-20 dB</span></label>
|
||
<label>Ratio <input type="range" id="fx-comp-ratio" min="1" max="20" step="0.5" value="4"><span id="fx-comp-ratio-val">4:1</span></label>
|
||
</div>
|
||
</div>
|
||
<div class="fx-row">
|
||
<label class="fx-toggle"><input type="checkbox" id="fx-chorus-on"> Chorus</label>
|
||
<div class="fx-sliders" id="fx-chorus-params">
|
||
<label>Rate <input type="range" id="fx-chorus-rate" min="0.1" max="5" step="0.1" value="1"><span id="fx-chorus-rate-val">1.0 Hz</span></label>
|
||
<label>Mix <input type="range" id="fx-chorus-mix" min="0" max="1" step="0.01" value="0.5"><span id="fx-chorus-mix-val">0.50</span></label>
|
||
</div>
|
||
</div>
|
||
<div class="fx-row">
|
||
<label class="fx-toggle"><input type="checkbox" id="fx-pitch-on"> Pitch shift</label>
|
||
<div class="fx-sliders" id="fx-pitch-params">
|
||
<label>Semitones <input type="range" id="fx-pitch-semi" min="-12" max="12" step="0.5" value="0"><span id="fx-pitch-semi-val">0 st</span></label>
|
||
</div>
|
||
</div>
|
||
<div class="fx-row">
|
||
<label class="fx-toggle"><input type="checkbox" id="fx-bandpass-on"> Bandpass (telephone/radio)</label>
|
||
<div class="fx-sliders" id="fx-bandpass-params">
|
||
<label>Low cut <input type="range" id="fx-bandpass-low" min="20" max="1000" step="10" value="300"><span id="fx-bandpass-low-val">300 Hz</span></label>
|
||
<label>High cut <input type="range" id="fx-bandpass-high" min="1000" max="12000" step="100" value="3400"><span id="fx-bandpass-high-val">3400 Hz</span></label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="btn-row" style="margin-top:12px">
|
||
<button class="btn-primary" id="effects-apply-btn" disabled>Apply effects</button>
|
||
<span class="note" id="effects-status"></span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Generation History -->
|
||
<div class="card" id="history-card">
|
||
<h2>Generation history</h2>
|
||
<p class="card-subtitle">Last 20 generations this session. Audio is preserved in memory until you reload.</p>
|
||
<div id="history-list" class="history-list">
|
||
<div class="history-empty">No generations yet.</div>
|
||
</div>
|
||
<div class="btn-row" style="margin-top:8px">
|
||
<button class="btn-secondary btn-sm" id="history-clear-btn">Clear history</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Playlist -->
|
||
<div class="card" id="playlist-card">
|
||
<h2>Playlist</h2>
|
||
<p class="card-subtitle">Queue clips from history. Reorder by dragging, then export as one merged WAV.</p>
|
||
<div id="playlist-list" class="playlist-list">
|
||
<div class="history-empty">No clips in playlist. Use <strong>+ Playlist</strong> after generating.</div>
|
||
</div>
|
||
<div class="btn-row" style="margin-top:12px">
|
||
<button class="btn-primary" id="playlist-export-btn" disabled><span class="mdi mdi-download"></span> Export merged WAV</button>
|
||
<button class="btn-secondary btn-sm" id="playlist-clear-btn">Clear</button>
|
||
</div>
|
||
</div>
|
||
|
||
</div><!-- /tab-generation -->
|
||
|
||
<!-- STT -> TTS -->
|
||
<div class="tab-content" id="tab-stt-tts">
|
||
<div class="card">
|
||
<h2>STT <span class="mdi mdi-arrow-right"></span> TTS workspace</h2>
|
||
<p class="card-subtitle">Upload speech audio, transcribe it with the configured STT endpoint, then synthesize the resulting text with any available TTS backend.</p>
|
||
</div>
|
||
<div class="card">
|
||
<h2>Source speech</h2>
|
||
<p class="card-subtitle">Record from your microphone or upload an audio file, then transcribe it.</p>
|
||
<div class="settings-grid">
|
||
<div class="s-field">
|
||
<label>Speech recognition</label>
|
||
<div style="display:flex;gap:8px">
|
||
<select id="stt-tts-stt-backend" aria-label="Speech-to-text engine"><option value="configured">Configured Whisper/STT</option></select>
|
||
<button class="btn-secondary" id="stt-tts-refresh-stt-btn" type="button">Refresh</button>
|
||
</div>
|
||
<div class="s-hint" id="stt-tts-stt-help">Uses Settings → Whisper/STT URL by default.</div>
|
||
</div>
|
||
<div class="s-field">
|
||
<label>Speech audio</label>
|
||
<div class="btn-row" style="gap:8px;align-items:center">
|
||
<input type="file" id="stt-tts-file" aria-label="Audio file to transcribe" accept="audio/*,video/*">
|
||
<button class="btn-secondary" id="stt-tts-rec-start" type="button" title="Record from microphone"><span class="mdi mdi-microphone-variant"></span> Record</button>
|
||
<button class="btn-secondary" id="stt-tts-rec-stop" type="button" disabled>Stop</button>
|
||
<span class="mic-timer" id="stt-tts-rec-time">0:00</span>
|
||
</div>
|
||
<span class="s-hint">Record or upload speech audio, then transcribe it with the selected recognition engine.</span>
|
||
</div>
|
||
<div class="s-field">
|
||
<label>Source preview</label>
|
||
<audio id="stt-tts-source-audio" controls style="display:none;width:100%"></audio>
|
||
<span class="s-hint" id="stt-tts-source-status">No source audio loaded.</span>
|
||
</div>
|
||
</div>
|
||
<div class="btn-row">
|
||
<button class="btn-secondary" id="stt-tts-transcribe-btn" disabled>Transcribe</button>
|
||
<button class="btn-secondary" id="stt-tts-copy-preview-btn" disabled>Copy to TTS playground</button>
|
||
</div>
|
||
<div class="field">
|
||
<label>Transcribed text</label>
|
||
<textarea id="stt-tts-text" placeholder="Transcribed text appears here. Edit before synthesis if needed."></textarea>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- LLM Refinement card -->
|
||
<div class="card" id="refine-card">
|
||
<h2>Refine with LLM</h2>
|
||
<p class="card-subtitle">Clean up the transcription using a local language model — remove fillers, fix repetitions, and normalise punctuation.</p>
|
||
<div class="engine-setup-row" style="margin-bottom:12px; padding:10px 12px">
|
||
<div class="engine-setup-col" style="flex:1">
|
||
<label class="engine-setup-label"><span class="mdi mdi-brain"></span> Language Model <a href="#" class="engine-setup-link" onclick="navTo('s-llms'); navEnginesCat('llm'); return false;" title="Configure Language Model settings"><span class="mdi mdi-cog-outline"></span></a></label>
|
||
<div class="engine-setup-controls">
|
||
<input type="text" id="refine-llm-url" placeholder="http://localhost:11434/v1" spellcheck="false" title="OpenAI-compatible endpoint (Ollama, LM Studio, vLLM...)">
|
||
<input type="text" id="refine-model" placeholder="auto-detect model" style="max-width:140px">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="refine-toggles">
|
||
<label class="refine-toggle"><input type="checkbox" id="refine-fillers" checked> Remove fillers</label>
|
||
<label class="refine-toggle"><input type="checkbox" id="refine-repetitions" checked> Fix repetitions</label>
|
||
<label class="refine-toggle"><input type="checkbox" id="refine-corrections" checked> Remove corrections</label>
|
||
<label class="refine-toggle"><input type="checkbox" id="refine-punctuation" checked> Fix punctuation</label>
|
||
</div>
|
||
<div class="btn-row" style="margin-top:12px">
|
||
<button class="btn-primary" id="refine-btn" disabled><span class="mdi mdi-auto-fix"></span> Refine transcription</button>
|
||
<button class="btn-secondary" id="refine-restore-btn" disabled>Restore original</button>
|
||
<span class="note" id="refine-status"></span>
|
||
</div>
|
||
</div>
|
||
<div class="card">
|
||
<h2>Synthesize transcription</h2>
|
||
<p class="card-subtitle">Choose a TTS backend and voice, then generate audio from the transcribed text.</p>
|
||
<div class="engine-setup-row" style="margin-bottom:12px; padding:10px 12px">
|
||
<div class="engine-setup-col" style="flex:1">
|
||
<label class="engine-setup-label"><span class="mdi mdi-account-voice"></span> Text to Speech <a href="#" class="engine-setup-link" onclick="navTo('s-llms'); navEnginesCat('tts'); return false;" title="Configure Text to Speech settings"><span class="mdi mdi-cog-outline"></span></a></label>
|
||
<div class="engine-setup-controls">
|
||
<select id="stt-tts-backend-select" aria-label="TTS backend"><option value="">Checking backends...</option></select>
|
||
<button class="btn-secondary" id="stt-tts-fetch-voices-btn" title="Fetch voices"><span class="mdi mdi-refresh"></span></button>
|
||
<select id="stt-tts-voice-select" aria-label="Voice"><option value="">-- select voices --</option></select>
|
||
</div>
|
||
</div>
|
||
<div class="engine-setup-col no-border" style="flex:0.5">
|
||
<label class="engine-setup-label"><span class="mdi mdi-tune"></span> Playback</label>
|
||
<div class="engine-setup-controls">
|
||
<select id="stt-tts-playback-mode" aria-label="Playback mode">
|
||
<option value="settings">Settings default</option>
|
||
<option value="streaming">Streaming only</option>
|
||
<option value="auto">Stream with fallback</option>
|
||
<option value="buffered">Buffered WAV</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="backend-help" id="stt-tts-backend-help" aria-live="polite" style="margin-bottom:10px">
|
||
<strong>Checking available TTS backends...</strong>
|
||
</div>
|
||
<div class="field">
|
||
<label>Style Instruction <span style="font-weight:400">(optional)</span></label>
|
||
<input type="text" id="stt-tts-style-instruction" placeholder="Optional style/emotion instruction">
|
||
</div>
|
||
<div class="btn-row">
|
||
<button class="btn-primary" id="stt-tts-generate-btn">Generate & play</button>
|
||
<button class="btn-secondary" id="stt-tts-save-mp3-btn" disabled>Save MP3</button>
|
||
<button class="btn-secondary" id="stt-tts-save-wav-btn" disabled>Save WAV</button>
|
||
</div>
|
||
<audio id="stt-tts-output-audio" controls style="display:none"></audio>
|
||
</div>
|
||
</div><!-- /tab-stt-tts -->
|