tts-voice-creator-clone-and.../static/sections/s-tryout.html
mARTin-B78 a62dd0bac1 Fix voice stability, audio effects, and character/voice pipeline bugs
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>
2026-07-29 15:33:24 +02:00

299 lines
20 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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 &amp; 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.52)</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 &amp; 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 &amp; 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 &rarr; 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 &amp; 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 -->