tts-voice-creator-clone-and.../static/sections/s-voices.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

329 lines
22 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-account-voice"></span></span>
<div class="section-title">
<h2>My Voices</h2>
<p>Pick a voice on the left, edit on the right.</p>
</div>
<div class="section-head-actions">
<input type="search" class="section-search" id="section-voices-search" placeholder="Search voices&#x2026;" autocomplete="off"
oninput="var f=document.getElementById('library-filter-text');if(f){f.value=this.value;f.dispatchEvent(new Event('input'));}">
<button class="btn-primary section-new-voice-btn" onclick="document.getElementById('add-new-voice-btn')?.click()">+ New voice</button>
</div>
</div>
<!-- Workbench: list pane + inspector pane -->
<div class="voices-workbench" id="tab-library">
<!-- LEFT: compact voice list — two cards -->
<div class="voices-list-pane">
<!-- Card 1: TTS synth mode + preview sentence -->
<div class="vl-card vl-card-synth">
<div class="vl-synth-panel">
<div class="vl-synth-mode-row">
<span class="vl-synth-icon"><span class="mdi mdi-play"></span><span class="mdi mdi-play"></span></span>
<span class="vl-synth-label">Synth uses</span>
<div class="vl-synth-seg" id="vl-synth-mode-seg">
<button class="vl-synth-seg-btn active" data-mode="preview"
title="Synthesize the preview sentence — good for comparing voices side by side">Preview text</button>
<button class="vl-synth-seg-btn" data-mode="transcript"
title="Synthesize this voice&#x2019;s saved reference transcript — good for quality check vs. original recording">Reference transcript</button>
</div>
</div>
<div class="vl-preview-text-wrap" id="vl-preview-text-wrap">
<div class="vl-preview-row">
<button class="vl-preview-lang-btn" id="vl-preview-lang-btn"
title="English — click to change sample language"
onclick="openPreviewLangPicker(this)" data-lang="EN">
<span class="fi fi-gb"></span>
</button>
<textarea class="vl-preview-input" id="vl-preview-sample" rows="2"
placeholder="Sample sentence for TTS preview&#x2026;"
oninput="var t=document.getElementById('benchmark-sample-text');if(t){t.value=this.value;t.dispatchEvent(new Event('input'));}"
></textarea>
</div>
</div>
</div>
</div><!-- /vl-card-synth -->
<!-- Card 2: filters + sort + voice list + toolbar -->
<div class="vl-card vl-card-list">
<div class="vl-filters">
<select id="library-filter-lang" title="Language"><option value="">All</option></select>
<select id="library-filter-sex" title="Gender"><option value="">All</option></select>
<select id="library-filter-type" title="Type"><option value="">All types</option></select>
<select id="library-filter-tag" title="Tag"><option value="">All tags</option></select>
<select id="library-filter-rating" title="Rating">
<option value="">All ratings</option>
<option value="5">★★★★★</option>
<option value="4">★★★★+</option>
<option value="3">★★★+</option>
<option value="2">★★+</option>
<option value="1">★+</option>
<option value="0">Unrated</option>
</select>
<select id="library-filter-group" title="Group"><option value="">All groups</option></select>
<button id="library-clear-filters" class="vl-sort-dir-btn" title="Clear all filters"><span class="mdi mdi-filter-off-outline"></span></button>
<label class="vl-disabled-label"><input type="checkbox" id="show-disabled-cb"> Disabled</label>
</div>
<div class="vl-sort-bar">
<input type="search" id="library-filter-text" placeholder="&#x1F50D; Search voices&#x2026;" autocomplete="off" class="vl-search vl-search-main">
<span class="vl-sort-label">Sort</span>
<select id="voice-sort-field" title="Sort field">
<option value="id">Name</option>
<option value="flag">Language</option>
<option value="gender">Gender</option>
<option value="benchmark">Speed</option>
<option value="rating">Rating</option>
<option value="duration">Duration</option>
<option value="dbfs">Volume dB</option>
<option value="source">Source</option>
<option value="seed">Seed</option>
<option value="note">Note</option>
<option value="tag">Tags</option>
<option value="enabled">Active</option>
</select>
<button id="voice-sort-dir" class="vl-sort-dir-btn" title="Toggle sort direction"><span class="mdi mdi-arrow-up"></span></button>
<button id="voice-group-tag-btn" class="vl-sort-dir-btn" title="Group into virtual folders by tag"><span class="mdi mdi-folder-outline"></span></button>
<button id="voice-table-view-btn" class="vl-sort-dir-btn" title="Toggle full-width table view"><span class="mdi mdi-table"></span></button>
<button id="voice-table-edit-btn" class="vl-sort-dir-btn" title="Toggle table edit mode" style="display:none"><span class="mdi mdi-pencil-box-outline"></span></button>
</div>
<div class="benchmark-confirm" id="benchmark-confirm" hidden role="group" aria-live="polite">
<strong id="benchmark-confirm-title">Benchmark active voices?</strong>
<span id="benchmark-confirm-text"></span>
<button class="btn-secondary" id="benchmark-confirm-cancel" type="button">Cancel</button>
<button class="benchmark-confirm-start" id="benchmark-confirm-start" type="button">Start benchmark</button>
</div>
<div id="voice-groups-bar" class="voice-groups-bar" hidden></div>
<!-- Bulk-edit toolbar — appears when voices are selected -->
<div class="vl-bulk-bar" id="vl-bulk-bar" hidden>
<span class="vl-bulk-count" id="vl-bulk-count">0 selected</span>
<button class="vl-bulk-btn" id="vl-bulk-select-all" title="Select all visible"><span class="mdi mdi-checkbox-multiple-marked-outline"></span> All</button>
<button class="vl-bulk-btn" id="vl-bulk-deselect" title="Clear selection"><span class="mdi mdi-checkbox-multiple-blank-outline"></span> None</button>
<div class="vl-bulk-sep"></div>
<button class="vl-bulk-btn" id="vl-bulk-tag" title="Add a tag to the selected voices, keeping their existing tags"><span class="mdi mdi-tag-outline"></span> Set tag</button>
<button class="vl-bulk-btn" id="vl-bulk-source" title="Set source / origin on selected voices"><span class="mdi mdi-database-outline"></span> Set source</button>
<button class="vl-bulk-btn" id="vl-bulk-hide" title="Disable (hide) selected voices"><span class="mdi mdi-eye-off-outline"></span> Hide</button>
<button class="vl-bulk-btn" id="vl-bulk-unhide" title="Enable (unhide) selected voices"><span class="mdi mdi-eye-outline"></span> Unhide</button>
<button class="vl-bulk-btn" id="vl-bulk-rating" title="Set star rating on selected"><span class="mdi mdi-star-outline"></span> Rate</button>
<div class="vl-bulk-sep"></div>
<button class="vl-bulk-btn vl-bulk-danger" id="vl-bulk-delete" title="Delete selected voices permanently"><span class="mdi mdi-delete-outline"></span> Delete</button>
</div>
<div class="vl-table-header" id="vl-table-header">
<div style="display:flex; justify-content:center; align-items:center;"><input type="checkbox" id="vl-select-all-header" title="Select all visible voices"></div>
<div>Img</div>
<div>Play</div>
<div class="vl-th-sortable" data-sort="id">Name <span class="mdi mdi-sort"></span></div>
<div class="vl-th-sortable" data-sort="flag">Lang <span class="mdi mdi-sort"></span></div>
<div class="vl-th-sortable" data-sort="gender">Gender <span class="mdi mdi-sort"></span></div>
<div class="vl-th-sortable" data-sort="factor">Factor <span class="mdi mdi-sort"></span><span class="vl-th-info mdi mdi-information-outline" title="Speed multiplier: audio duration ÷ render time. Above 1.0× means the GPU is faster than real-time — higher is better."></span></div>
<div class="vl-th-sortable" data-sort="wpm">WPM <span class="mdi mdi-sort"></span><span class="vl-th-info mdi mdi-information-outline" title="Words per minute the voice speaks. 130180 wpm is natural for audiobooks; lower = slower, more deliberate pace."></span></div>
<div class="vl-th-sortable" data-sort="seed">Seed <span class="mdi mdi-sort"></span></div>
<div class="vl-th-sortable" data-sort="dbfs">dBFS <span class="mdi mdi-sort"></span></div>
<div>Type</div>
<div class="vl-th-sortable" data-sort="source">Source <span class="mdi mdi-sort"></span></div>
<div class="vl-th-sortable" data-sort="rating">Rating <span class="mdi mdi-sort"></span></div>
<div class="vl-th-sortable" data-sort="tag">Tags <span class="mdi mdi-sort"></span></div>
<div class="vl-th-sortable" data-sort="note">Note <span class="mdi mdi-sort"></span></div>
<div class="vl-th-sortable" data-sort="enabled">Active <span class="mdi mdi-sort"></span></div>
</div>
<div id="voice-list" tabindex="0" role="region" aria-label="Voice library"></div>
<div class="vl-toolbar">
<button class="btn-secondary vl-tb-btn" id="refresh-voices-btn" title="Refresh voice list"><span class="mdi mdi-refresh"></span> Refresh</button>
<button class="btn-secondary vl-tb-btn" id="sync-voice-folders-btn" title="Sync active/hidden folders">Sync</button>
<button class="btn-secondary vl-tb-btn" id="calculate-db-btn" title="Calculate dBFS">Calc dB</button>
<button class="btn-secondary vl-tb-btn" id="benchmark-voices-btn" title="Benchmark TTS speed">Benchmark</button>
<button class="btn-secondary vl-tb-btn" id="copy-active-voices-btn" title="Copy selected voices (or all active if none checked)">Copy selected</button>
<button class="btn-secondary vl-tb-btn" id="precompute-embeddings-btn" title="Warm all active voices so the TTS engine pre-computes &amp; caches each speaker embedding (.pt) — makes first playback instant"><span class="mdi mdi-flash-outline"></span> Precompute</button>
<button class="btn-secondary vl-tb-btn" id="seed-batch-all-btn" title="Generate &amp; cache Seed Finder samples for every active voice (skips already-cached; resumable)"><span class="mdi mdi-dice-multiple-outline"></span> Batch seeds</button>
<button class="btn-secondary vl-tb-btn" id="verify-voices-stt-btn" title="Synthesize a test sentence with each voice, transcribe it back with Whisper, and compare to the original text — catches repeated/garbled/nonsense output that a duration-only benchmark misses"><span class="mdi mdi-check-decagram-outline"></span> Verify (STT)</button>
</div>
<div class="vl-footer">
<span id="voice-count" class="note"></span>
<button class="vl-add-btn" id="add-new-voice-btn" style="display:none" aria-hidden="true" tabindex="-1" title="Add a new voice to the library">+ Add voice</button>
</div>
</div><!-- /vl-card-list -->
</div><!-- /voices-list-pane -->
<!-- RIGHT: inspector / editor pane -->
<div class="voices-inspector-pane" id="voices-inspector">
<div class="inspector-placeholder">
<span><span class="mdi mdi-account-voice"></span></span>
<p>Pick a voice on the left<br>to edit it here</p>
</div>
</div>
</div><!-- /voices-workbench -->
<!-- Hidden scaffold — IDs required by voice-library.js but not shown in new layout -->
<div id="vl-scaffold" style="display:none" aria-hidden="true">
<div id="library-filter-panel"></div>
<div class="library-insights" id="library-insights"></div>
<div class="disabled-info" id="disabled-info">
<strong>About disabled voices:</strong> The Active toggle moves the voice package between
<code>active_voices</code> and <code>hidden_voices</code>. Qwen3-TTS should scan only <code>active_voices</code>.
</div>
<!-- Audio playback bar -->
<div id="lib-audio-bar">
<div class="lib-audio-label" id="lib-audio-label">-</div>
<audio id="lib-audio" controls></audio>
</div>
<!-- Benchmark config panel -->
<div class="library-benchmark-panel">
<textarea id="benchmark-sample-text" placeholder="Sample sentence for benchmark"></textarea>
<select id="library-tts-backend-select"><option value="">Checking backends...</option></select>
<input type="number" id="library-target-db" value="-20" min="-60" max="-1" step="0.5">
<button id="normalize-volume-btn">Normalize volume</button>
<select id="benchmark-preset-select" title="Load a benchmark sentence preset">
<option value="">Sentence presets…</option>
<option value="de">DE — Die Welt ist voller Geschichten…</option>
<option value="en">EN — The old lighthouse stood firm…</option>
<option value="de2">DE — Der schnelle braune Fuchs…</option>
<option value="en2">EN — She sells seashells…</option>
<option value="reset">↺ Reset to default</option>
</select>
<button id="benchmark-use-preview-btn">Use preview text</button>
</div>
<!-- Benchmark progress -->
<div class="benchmark-progress" id="benchmark-progress" hidden aria-live="polite">
<div class="benchmark-progress-head">
<span id="benchmark-progress-label">Benchmarking voices...</span>
<span id="benchmark-progress-count">0 / 0</span>
</div>
<div class="benchmark-progress-track" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
<div id="benchmark-progress-bar"></div>
</div>
<div class="benchmark-live-stats" id="benchmark-live-stats">
<span>Elapsed -</span><span>Avg -</span><span>ETA -</span><span>OK 0</span><span>Slow 0</span><span>Errors 0</span>
</div>
<div class="benchmark-live-last" id="benchmark-live-last"></div>
</div>
<!-- Add new voice panel -->
<div class="lib-add-panel" id="lib-add-panel">
<div class="lib-add-stack">
<div class="lib-add-section">
<h2 style="margin-bottom:10px">1 Load or record source</h2>
<div class="lib-add-input-grid">
<div class="lib-add-import-box">
<input type="file" id="lib-add-file" accept="audio/*,video/*" style="display:none">
<div class="lib-add-drop" id="lib-add-drop">
<strong>Drop an audio / video file here</strong>
<span>WAV &middot; MP3 &middot; OGG &middot; FLAC &middot; M4A &middot; MP4 &middot; MKV &middot; WEBM</span>
<span>or click to browse</span>
</div>
<div>
<label class="note" for="lib-add-url">Audio or video URL</label>
<div class="url-row">
<input type="text" id="lib-add-url" placeholder="Paste a direct MP3/WAV, YouTube, or Aiartes URL">
<button class="btn-primary" id="lib-add-url-btn">Download URL</button>
</div>
</div>
</div>
<div class="lib-add-source-box lib-add-record-box">
<div class="record-head">
<span class="note">Record a fresh sample</span>
<div class="record-head-actions">
<button class="btn-secondary" id="lib-add-mic-help-btn">How to unlock mic</button>
</div>
</div>
<div class="lib-add-recorder-tools">
<div class="lib-add-mic-row">
<div class="mic-buttons">
<button class="btn-secondary" id="lib-add-monitor-btn">Check level</button>
<button class="btn-secondary" id="lib-add-monitor-stop" disabled>Stop monitor</button>
<button class="btn-red" id="lib-add-rec-start"><span class="mdi mdi-record-circle-outline"></span> Record</button>
<button class="btn-secondary" id="lib-add-rec-stop" disabled><span class="mdi mdi-stop-circle-outline"></span> Stop</button>
<span class="mic-timer" id="lib-add-rec-time">0:00</span>
</div>
<div class="mic-monitor-box">
<div class="mic-monitor-head">
<span>Input level</span>
<span class="meter-readout" id="lib-add-db-readout">-<span class="mdi mdi-infinity"></span> dB</span>
</div>
<div class="mic-meter" id="lib-add-mic-meter" aria-hidden="true"></div>
<div class="mic-gain-row">
<label for="lib-add-mic-gain">Mic gain</label>
<input id="lib-add-mic-gain" type="range" min="0" max="2" step="0.05" value="1">
<span class="mic-gain-value" id="lib-add-mic-gain-value">1.00x</span>
</div>
<div class="note" style="margin-top:5px">Best peaks: -18 to -9 dB, never red.</div>
</div>
</div>
<div class="sample-read-box">
<div class="sample-head">
<label for="lib-add-sample-lang">Read sample</label>
<select id="lib-add-sample-lang">
<option value="EN">English</option><option value="DE">Deutsch</option>
<option value="IT">Italiano</option><option value="ES">Espa&ntilde;ol</option>
<option value="FR">Fran&ccedil;ais</option><option value="PT">Portugu&ecirc;s</option>
<option value="NL">Nederlands</option><option value="PL">Polski</option>
</select>
</div>
<textarea class="sample-sentence" id="lib-add-sample-text" spellcheck="true"></textarea>
<div class="sample-actions">
<button class="btn-secondary" id="lib-add-use-sample">Use as transcript</button>
<button class="btn-secondary" id="lib-add-reset-sample">Reset sentence</button>
</div>
</div>
</div>
<div class="mic-help-panel" id="lib-add-mic-help">
<strong>If the microphone is blocked:</strong>
<ul>
<li><strong>Chrome/Brave/Edge:</strong> click the lock icon in the address bar &rarr; Microphone &rarr; Allow, then reload.</li>
<li><strong>Firefox:</strong> click the lock icon &rarr; remove Blocked &rarr; Allow, then reload.</li>
<li><strong>Safari:</strong> Settings &rarr; Websites &rarr; Microphone &rarr; allow this site.</li>
</ul>
</div>
<div class="opt-status" id="lib-add-status">Load a file, paste a URL, or record. Then trim, name, and save.</div>
</div>
</div>
<div class="lib-add-audio-row">
<audio id="lib-add-audio" controls style="display:none"></audio>
<div class="btn-row">
<button class="btn-secondary" id="lib-add-auto-trim">Auto trim</button>
<button class="btn-primary" id="lib-add-save-crop">Crop selection</button>
<button class="btn-secondary" id="lib-add-play">Play crop</button>
</div>
</div>
<canvas class="opt-wave" id="lib-add-wave" style="display:none;margin-top:10px"></canvas>
<div class="opt-controls">
<div class="opt-field"><label>Start</label><input id="lib-add-start" type="number" step="0.01" value="0"></div>
<div class="opt-field"><label>End</label><input id="lib-add-end" type="number" step="0.01" value="0"></div>
<button class="btn-primary" id="lib-add-save-crop-bottom">Crop selection</button>
<span class="crop-duration-hint" id="lib-add-crop-hint">Select 3-20 seconds for best cloning.</span>
</div>
</div>
<div class="lib-add-section">
<h2 style="margin-bottom:10px">2 Name and save voice</h2>
<div class="lib-add-name-grid">
<div class="opt-field"><label>Language</label><select id="lib-add-lang">
<option value="EN">EN</option><option value="DE">DE</option><option value="IT">IT</option>
<option value="ES">ES</option><option value="FR">FR</option><option value="PT">PT</option>
<option value="NL">NL</option><option value="PL">PL</option>
</select></div>
<div class="opt-field"><label>Gender</label><input id="lib-add-gender" value="N"></div>
<div class="opt-field"><label>Voice ID</label><input id="lib-add-voice-id" placeholder="EN_N_NewVoice"></div>
</div>
<textarea id="lib-add-transcript" placeholder="Reference transcript" style="margin-top:10px"></textarea>
<div class="btn-row" style="margin-top:10px">
<button class="btn-secondary" id="lib-add-recognize">Recognise text</button>
<button class="btn-green" id="lib-add-save">Save voice</button>
</div>
</div>
</div>
</div>
</div><!-- /vl-scaffold -->