No clips in this session.
';return}list.innerHTML=rehState.clips.map((clip,idx)=>{const line=rehState.lines[clip.lineIndex]||{text:"\u2014",speaker:clip.speaker},c=rehState.cast[clip.speaker]||{color:"#89b4fa"},typeLabel=clip.type==="me"?"\u{1F3A4} Recorded":clip.type==="tts"?"\u{1F50A} Synthesized":"\u23ED Skipped",audioHtml=clip.blob?`
+ `}function _checkToneStyleSupport(){const warn=$("reh-tone-warn");if(!warn)return;const txtEl=$("reh-tone-warn-txt"),b=typeof backendById=="function"?backendById(rehState.backend):null;if(!b){warn.hidden=!0;return}const all=typeof availableTtsBackends=="function"?availableTtsBackends():[],hasTone=rehState.lines.some(l=>l.type==="dialog"&&l.emotion);if(_rehBackendIsFish())txtEl&&(txtEl.innerHTML=`
${escHtml(b.label)} keeps each character\u2019s voice consistent
and applies tone. Per-line tones are sent as inline
[tags] (e.g.
[whisper],
[excited],
[laughing]). You can also type a custom tone like
[professional broadcast tone] \u2014 S2 supports free-form descriptions.
Fish-Speech S2 \u2197 `),warn.hidden=!1;else if(!b.style_aware&&hasTone){const styleAware=all.find(x=>x.style_aware&&x.uses_wav)||all.find(x=>x.style_aware);txtEl&&(txtEl.innerHTML=styleAware?`
Tone control Voice stays identical ${_rehToneCmpRow(b,!0)}${_rehToneCmpRow(styleAware,!1)}
`:`
${escHtml(b.label)} keeps each character\u2019s voice consistent but has weak tone control \u2014 tone picks may have little effect.`),warn.hidden=!1}else if(b.style_aware&&!b.uses_wav){const wavBackend=all.find(x=>x.uses_wav&&x.style_aware)||all.find(x=>x.uses_wav),qwenHint=/qwen|voice design|custom/i.test((b.id||"")+" "+(b.label||""))?'
Qwen3TTS tone is sent as the per-line style/instruct text, so this is the right path for directed delivery.
':"";txtEl&&(txtEl.innerHTML=wavBackend?`
Tone control Voice stays identical ${_rehToneCmpRow(b,!0)}${_rehToneCmpRow(wavBackend,!1)}
${qwenHint}`:`
${escHtml(b.label)} gives strong tone but re-generates a fresh voice each line, so a character won\u2019t sound the same throughout.${qwenHint}`),warn.hidden=!1}else warn.hidden=!0}(_Ga=$("reh-tone-warn-close"))==null||_Ga.addEventListener("click",()=>{const w=$("reh-tone-warn");w&&(w.hidden=!0)}),(_Ha=$("reh-tone-warn"))==null||_Ha.addEventListener("click",e=>{const btn=e.target.closest(".reh-tone-switch-btn");if(!btn)return;const id=btn.dataset.backendId,sel=$("reh-backend-select");sel&&[...sel.options].some(o=>o.value===id)&&(sel.value=id),rehState.backend=id,_checkToneStyleSupport();const b=typeof backendById=="function"?backendById(id):null;toast("Switched to "+(b?b.label:id),"success")}),(_Ia=$("reh-tb-play"))==null||_Ia.addEventListener("click",()=>{rehState.playing?pausePlay():startPlay()}),(_Ja=$("reh-tb-stop"))==null||_Ja.addEventListener("click",()=>{var _a2;stopPlay(),rehState.lineIndex=(_a2=rehState.practiceStart)!=null?_a2:0,highlightCurrentLine(),hideRecOverlay()}),(_Ka=$("reh-tb-prev"))==null||_Ka.addEventListener("click",()=>{stopPlay(),rehState.lineIndex=Math.max(0,rehState.lineIndex-1),highlightCurrentLine(),hideRecOverlay()}),(_La=$("reh-tb-next"))==null||_La.addEventListener("click",()=>{stopPlay(),rehState.lineIndex=Math.min(rehState.lines.length-1,rehState.lineIndex+1),highlightCurrentLine(),hideRecOverlay()}),(_Ma=$("reh-tb-repeat"))==null||_Ma.addEventListener("click",()=>{var _a2;rehState.repeat=!rehState.repeat,(_a2=$("reh-tb-repeat"))==null||_a2.classList.toggle("reh-btn-active",rehState.repeat)}),(_Na=$("reh-skip-desc-toggle"))==null||_Na.addEventListener("change",function(){rehState.skipDescriptions=this.checked}),(_Oa=$("reh-edit-script-btn"))==null||_Oa.addEventListener("click",openScriptEditorModal),(_Pa=$("reh-fountain-btn"))==null||_Pa.addEventListener("click",exportFountain),(_Qa=$("reh-fountain-export-p4"))==null||_Qa.addEventListener("click",exportFountain),(_Ra=$("reh-fdx-export-btn"))==null||_Ra.addEventListener("click",exportFDX),(_Sa=$("reh-osf-export-btn"))==null||_Sa.addEventListener("click",exportOSF),(_Ta=$("reh-exit-btn"))==null||_Ta.addEventListener("click",()=>{stopPlay(),stopRehMic(),rehState.clips.length?(renderSummary(),showPhase(4)):showPhase(2)}),(_Ua=$("reh-page-title"))==null||_Ua.addEventListener("dblclick",function(){this.contentEditable="true",this.style.outline="2px solid var(--accent)",this.style.borderRadius="3px",this.focus();const range=document.createRange();range.selectNodeContents(this),window.getSelection().removeAllRanges(),window.getSelection().addRange(range)}),(_Va=$("reh-page-title"))==null||_Va.addEventListener("blur",function(){if(this.contentEditable==="true"){this.contentEditable="false",this.style.outline="";const v=this.textContent.trim()||"Script";this.textContent=v,$("reh-script-title")&&($("reh-script-title").value=v)}}),(_Wa=$("reh-page-title"))==null||_Wa.addEventListener("keydown",function(e){var _a2;e.key==="Enter"&&(e.preventDefault(),this.blur()),e.key==="Escape"&&(this.textContent=((_a2=$("reh-script-title"))==null?void 0:_a2.value)||"Script",this.blur())});let _rehPlayCtx=null;const rehDecodedBuffers=new Map;let rehCurrentSource=null,rehWordHighlightRaf=null;function rehPlayCtx(){return _rehPlayCtx||(_rehPlayCtx=new(window.AudioContext||window.webkitAudioContext)),_rehPlayCtx.state==="suspended"&&_rehPlayCtx.resume().catch(()=>{}),_rehPlayCtx}const REH_DECODE_WINDOW=8;function _rehEvictDecoded(keepIdx){if(rehDecodedBuffers.size<=REH_DECODE_WINDOW*2+4)return;const lo=keepIdx-REH_DECODE_WINDOW,hi=keepIdx+REH_DECODE_WINDOW;for(const k of rehDecodedBuffers.keys())(k
hi)&&rehDecodedBuffers.delete(k)}async function preDecodeBlob(lineIdx,blob){if(!rehDecodedBuffers.has(lineIdx))try{const ab=await blob.arrayBuffer(),buf=await rehPlayCtx().decodeAudioData(ab);rehDecodedBuffers.set(lineIdx,buf),_rehEvictDecoded(lineIdx)}catch{}}function computeWordTimings(text,durationSec){const words=stripMarkdown(text).split(/\s+/).filter(Boolean);if(words.length<2)return[];const totalChars=words.reduce((s,w)=>s+w.length,0)||1;let t=0;return words.map(w=>{const start=t;return t+=w.length/totalChars*durationSec,{word:w,start,end:t}})}function stopAudioSource(){if(rehCurrentSource){try{rehCurrentSource.stop(0)}catch{}rehCurrentSource=null}rehWordHighlightRaf&&(cancelAnimationFrame(rehWordHighlightRaf),rehWordHighlightRaf=null)}async function playPreDecoded(lineIdx,blob,text){rehDecodedBuffers.has(lineIdx)||await preDecodeBlob(lineIdx,blob),_rehEvictDecoded(lineIdx);const buf=rehDecodedBuffers.get(lineIdx);if(!buf){await playAudioBlobFallback(blob);return}const timings=computeWordTimings(text,buf.duration),dialogEl=document.getElementById("reh-diag-"+lineIdx);return dialogEl&&timings.length>=2&&(dialogEl.innerHTML=timings.map((t,i)=>`${escHtml(t.word)} `).join(" ")),new Promise(resolve=>{stopAudioSource();const ctx=rehPlayCtx(),src=ctx.createBufferSource();src.buffer=buf,src.connect(ctx.destination),rehCurrentSource=src;const t0=ctx.currentTime;src.onended=()=>{rehCurrentSource=null,rehWordHighlightRaf&&(cancelAnimationFrame(rehWordHighlightRaf),rehWordHighlightRaf=null),dialogEl&&timings.length>=2&&(dialogEl.innerHTML=renderMarkdownInline(text)),resolve()},src.start(0);const nextI=findNextCachedLine(lineIdx+1);if(nextI>=0&&preDecodeBlob(nextI,rehState.synthCache.get(nextI)),dialogEl&&timings.length>=2){const tick=()=>{if(rehCurrentSource!==src)return;const elapsed=ctx.currentTime-t0;let active=0;for(let i=timings.length-1;i>=0;i--)if(elapsed>=timings[i].start){active=i;break}dialogEl.querySelectorAll(".reh-word").forEach((span,i)=>{span.classList.toggle("reh-word-active",i===active)}),rehWordHighlightRaf=requestAnimationFrame(tick)};rehWordHighlightRaf=requestAnimationFrame(tick)}})}function findNextCachedLine(fromIdx){for(let i=fromIdx;i{audio.addEventListener("canplay",r,{once:!0}),setTimeout(r,3e3)}),await audio.play().catch(()=>{}),await waitForAudioEnd(audio))}function startPlay(){rehPlayCtx(),_ensureNarrator(),rehState.playing=!0,updatePlayBtn(),playNextLine()}function _rehResetActionPlayIcons(){document.querySelectorAll(".reh-action-play-btn").forEach(btn=>{const icon=btn.querySelector(".mdi");icon&&(icon.className="mdi mdi-play"),btn.title="Play or pause this line"})}function pausePlay(){rehState.playing=!1,updatePlayBtn(),stopAudioSource();const audio=$("reh-tts-audio");audio&&!audio.paused&&audio.pause(),hideStatusBar(),_rehResetActionPlayIcons()}function stopPlay(){rehState.playing=!1,updatePlayBtn(),stopAudioSource();const audio=$("reh-tts-audio");audio&&(audio.pause(),audio.src=""),hideStatusBar(),_rehResetActionPlayIcons()}function hideStatusBar(){const bar=$("reh-tts-status-bar");bar&&(bar.hidden=!0)}async function playNextLine(){var _a2,_b2,_c2;if(!rehState.playing)return;if(rehState.practiceEnd!==null&&rehState.lineIndex>rehState.practiceEnd){rehState.playing=!1,updatePlayBtn(),rehState.repeat?(rehState.lineIndex=(_a2=rehState.practiceStart)!=null?_a2:0,startPlay()):(rehState.lineIndex=(_b2=rehState.practiceStart)!=null?_b2:0,highlightCurrentLine());return}if(((_c2=rehState.lines[rehState.lineIndex])==null?void 0:_c2.type)==="pagebreak")return rehState.lineIndex++,playNextLine();const _cur=rehState.lines[rehState.lineIndex];if(_cur&&(_cur.ignored||_cur.hidden))return rehState.lineIndex++,playNextLine();if(rehState.skipDescriptions)for(;rehState.lineIndexrehState.practiceEnd);)rehState.lineIndex++;if(rehState.lineIndex>=rehState.lines.length){if(rehState.playing=!1,updatePlayBtn(),rehState.repeat){rehState.lineIndex=0,startPlay();return}toast("Script finished","success");return}const myLineIndex=rehState.lineIndex,stillCurrent=()=>rehState.playing&&rehState.lineIndex===myLineIndex,line=rehState.lines[rehState.lineIndex];if(highlightCurrentLine(),line.type!=="dialog"){const hasText=!!(line.text||"").trim();if(rehState.narratorVoice&&hasText&&!rehState.skipDescriptions){showStatusBar("Narrator: "+line.text.slice(0,50)+(line.text.length>50?"\u2026":""));const cached2=rehState.synthCache.get(rehState.lineIndex);if(cached2)await playPreDecoded(rehState.lineIndex,cached2,line.text);else try{const book=_lineAudioBookName(),cleanNarr=stripMarkdown(line.text),cacheKey=await _lineAudioCacheKey(cleanNarr,rehState.narratorVoice,"");if(!stillCurrent())return;let blob=await _lineAudioCacheGet(book,cacheKey);if(!stillCurrent())return;if(!blob){if(blob=await fetchTtsPreviewBlob(rehState.narratorVoice,cleanNarr,"wav","",_ttsBackendForVoice(rehState.narratorVoice,rehState.backend)),!stillCurrent())return;_lineAudioCachePut(book,cacheKey,blob)}rehState.synthCache.set(myLineIndex,blob),_markSynthDot(myLineIndex,"ok"),await playPreDecoded(myLineIndex,blob,line.text)}catch{await new Promise(r=>setTimeout(r,200))}}else rehState.skipDescriptions||await new Promise(r=>setTimeout(r,line.type==="direction"?150:250));if(!stillCurrent())return;rehState.lineIndex++,playNextLine();return}const cast=rehState.cast[line.speaker]||{voice:""};if(cast.voice==="me"){rehState.playing=!1,updatePlayBtn(),showRecOverlay(line);return}if(!cast.voice){if(showStatusBar(line.speaker+" has no voice \u2014 skipping\u2026"),await new Promise(r=>setTimeout(r,350)),!stillCurrent())return;rehState.lineIndex++,playNextLine();return}const profile=(cast.instruct||"").trim(),instruct=_buildInstruct(profile,line.emotion,cast.voice),cleanTxt=stripMarkdown(line.text),cached=rehState.synthCache.get(rehState.lineIndex);if(cached)showStatusBar(line.speaker+" is speaking\u2026"),await playPreDecoded(rehState.lineIndex,cached,line.text),rehState.clips.push({lineIndex:rehState.lineIndex,speaker:line.speaker,type:"tts",blob:cached});else{showStatusBar("Synthesizing\u2026");try{const toneText=_rehInlineTone(cleanTxt,line.emotion),book=_lineAudioBookName(),cacheKey=await _lineAudioCacheKey(toneText,cast.voice,instruct);if(!stillCurrent())return;let blob=await _lineAudioCacheGet(book,cacheKey);if(!stillCurrent())return;if(!blob){if(blob=await fetchTtsPreviewBlob(cast.voice,toneText,"wav",instruct,_ttsBackendForVoice(cast.voice,rehState.backend)),!stillCurrent())return;_lineAudioCachePut(book,cacheKey,blob)}rehState.synthCache.set(myLineIndex,blob),showStatusBar(line.speaker+" is speaking\u2026"),await playPreDecoded(myLineIndex,blob,line.text),rehState.clips.push({lineIndex:myLineIndex,speaker:line.speaker,type:"tts",blob})}catch(e){showStatusBar("TTS failed: "+e.message),await new Promise(r=>setTimeout(r,1e3))}}stillCurrent()&&(rehState.lineIndex++,playNextLine())}function waitForAudioEnd(audio){return new Promise(resolve=>{if(!audio||audio.paused||audio.ended){resolve();return}audio.addEventListener("ended",resolve,{once:!0}),audio.addEventListener("pause",resolve,{once:!0}),audio.addEventListener("error",resolve,{once:!0})})}function showStatusBar(msg){const bar=$("reh-tts-status-bar");if(!bar)return;bar.hidden=!1;const txt=$("reh-tts-status-txt");txt&&(txt.textContent=msg)}function highlightCurrentLine(){const i=rehState.lineIndex,total=rehState.lines.length,prog=$("reh-tb-progress");prog&&(prog.style.width=(total?i/total*100:0)+"%");const lbl=$("reh-tb-label");lbl&&(lbl.textContent=`${i+1} / ${total}`),document.querySelectorAll("[data-index]").forEach(el=>{el.classList.toggle("reh-line-active",parseInt(el.dataset.index)===i)}),document.querySelectorAll(".reh-action-play-btn").forEach(btn=>{const icon=btn.querySelector(".mdi");if(!icon)return;const isActive=rehState.playing&&parseInt(btn.dataset.index)===i;icon.className=isActive?"mdi mdi-stop":"mdi mdi-play",btn.title=isActive?"Stop":"Play or pause this line"});const active=document.querySelector(`[data-index="${i}"]`);active&&active.scrollIntoView({behavior:"smooth",block:"center"}),updatePlayBtn()}function updatePlayBtn(){const btn=$("reh-tb-play");btn&&(btn.innerHTML=rehState.playing?' ':' ',btn.title=rehState.playing?"Pause":"Play all")}async function _lineAudioCacheKey(text,voice,instruct){const enc=new TextEncoder().encode(`${text}\0${voice}\0${instruct||""}`),digest=await crypto.subtle.digest("SHA-256",enc);return[...new Uint8Array(digest)].map(b=>b.toString(16).padStart(2,"0")).join("").slice(0,32)}function _lineAudioBookName(){var _a2;return((_a2=$("reh-script-title"))==null?void 0:_a2.value.trim())||"Untitled"}async function _lineAudioCacheGet(book,key){try{const r=await fetch(`/api/line-audio/${encodeURIComponent(book)}/${key}`);return r.ok?await r.blob():null}catch{return null}}function _lineAudioCachePut(book,key,blob){fetch(`/api/line-audio/${encodeURIComponent(book)}/${key}`,{method:"POST",body:blob}).catch(()=>{})}let _lineAudioSyncedFor=null;async function _lineAudioSyncDots(){const book=_lineAudioBookName();_ensureNarrator();const idxToKey=new Map;for(let i=0;i[k,i]));try{const r=await fetch(`/api/line-audio/${encodeURIComponent(book)}/check`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({keys:[...idxToKey.values()]})});if(!r.ok)return;const{existing}=await r.json();(existing||[]).forEach(key=>{const idx=keyToIdx.get(key);idx!=null&&(rehState.staleLines.delete(idx),_markSynthDot(idx,"ok"))})}catch{}}async function synthAll(){var _a2;if(rehState.synthRunning)return;if(!rehState.backend){toast("Select a TTS backend first","error");return}_ensureNarrator();const ttsLines=rehState.lines.map((l,i)=>({line:l,idx:i})).filter(({line})=>{if(line.ignored||line.hidden)return!1;if(line.type==="dialog"){const c=rehState.cast[line.speaker];return c&&c.voice&&c.voice!=="me"}return!!(rehState.narratorVoice&&(line.text||"").trim())});if(!ttsLines.length){toast("No TTS lines to synthesize","error");return}rehState.synthRunning=!0,rehState.synthCancelled=!1;const synthBar=$("reh-synth-bar"),fill=$("reh-synth-fill"),label=$("reh-synth-label");synthBar&&(synthBar.hidden=!1);const prog=(d,t)=>{fill&&(fill.style.width=(t?d/t*100:0)+"%"),label&&(label.textContent=`${d} / ${t} synthesized`)};prog(0,ttsLines.length);let done=0;const book=_lineAudioBookName();try{for(const{line,idx}of ttsLines){if(rehState.synthCancelled)break;let voice,instruct;if(line.type==="dialog"){const c=rehState.cast[line.speaker];if(!c||!c.voice||c.voice==="me"){_markSynthDot(idx,null),prog(++done,ttsLines.length);continue}voice=c.voice,instruct=_buildInstruct(c.instruct,line.emotion,c.voice)}else voice=rehState.narratorVoice,instruct="";_markSynthDot(idx,"synthesizing"),(_a2=document.getElementById("reh-syd-"+idx))==null||_a2.scrollIntoView({behavior:"smooth",block:"nearest"});const toneText=_rehInlineTone(stripMarkdown(line.text),line.emotion);try{const cacheKey=await _lineAudioCacheKey(toneText,voice,instruct);let blob=await _lineAudioCacheGet(book,cacheKey);blob||(blob=await fetchTtsPreviewBlob(voice,toneText,"wav",instruct,_ttsBackendForVoice(voice,rehState.backend)),_lineAudioCachePut(book,cacheKey,blob)),rehState.synthCache.set(idx,blob),rehState.staleLines.delete(idx),_markSynthDot(idx,"ok"),_showReSynthBtn(idx,!1),preDecodeBlob(idx,blob)}catch{_markSynthDot(idx,null)}prog(++done,ttsLines.length)}}finally{synthBar&&(synthBar.hidden=!0),rehState.synthRunning=!1}rehState.synthCancelled||toast(`Pre-synthesized ${done} of ${ttsLines.length} lines \u2014 ready for instant playback`,"success")}function _updateStaleBatchBtn(){const btn=$("reh-tb-resynth-stale");btn&&(btn.hidden=rehState.staleLines.size===0)}(_Xa=$("reh-tb-synth-all"))==null||_Xa.addEventListener("click",()=>synthAll()),(_Ya=$("reh-tb-resynth-stale"))==null||_Ya.addEventListener("click",async()=>{if(rehState.synthRunning)return;const stale=[...rehState.staleLines];if(!stale.length)return;rehState.synthRunning=!0,rehState.synthCancelled=!1;const synthBar=$("reh-synth-bar"),fill=$("reh-synth-fill"),label=$("reh-synth-label");synthBar&&(synthBar.hidden=!1);let done=0;const book=_lineAudioBookName();try{for(const idx of stale){if(rehState.synthCancelled)break;const line=rehState.lines[idx];if(!line||line.type!=="dialog"){rehState.staleLines.delete(idx);continue}const c=rehState.cast[line.speaker];if(!c||!c.voice||c.voice==="me"){rehState.staleLines.delete(idx);continue}const instruct=[c.instruct||"",line.emotion||""].filter(Boolean).join(". ");_markSynthDot(idx,"synthesizing");const toneText=_rehInlineTone(stripMarkdown(line.text),line.emotion);try{const cacheKey=await _lineAudioCacheKey(toneText,c.voice,instruct);let blob=await _lineAudioCacheGet(book,cacheKey);blob||(blob=await fetchTtsPreviewBlob(c.voice,toneText,"wav",instruct,_ttsBackendForVoice(c.voice,rehState.backend)),_lineAudioCachePut(book,cacheKey,blob)),rehState.synthCache.set(idx,blob),rehState.staleLines.delete(idx),preDecodeBlob(idx,blob),_markSynthDot(idx,"ok"),_showReSynthBtn(idx,!1)}catch{_markSynthDot(idx,"stale")}fill&&(fill.style.width=++done/stale.length*100+"%"),label&&(label.textContent=`${done} / ${stale.length} synthesized`)}}finally{synthBar&&(synthBar.hidden=!0),rehState.synthRunning=!1,_updateStaleBatchBtn()}rehState.synthCancelled||toast(`Re-synthesized ${done} stale line${done!==1?"s":""}`,"success")}),(_Za=$("reh-synth-cancel"))==null||_Za.addEventListener("click",()=>{rehState.synthCancelled=!0,rehState.synthRunning=!1}),(__a=$("reh-tb-clean-cache"))==null||__a.addEventListener("click",async()=>{const btn=$("reh-tb-clean-cache");btn&&(btn.disabled=!0,btn.innerHTML=' Scanning\u2026');try{_ensureNarrator();const keep=[];for(const line of rehState.lines){if(line.ignored||line.hidden)continue;let voice,instruct,text;if(line.type==="dialog"){const c=rehState.cast[line.speaker];if(!c||!c.voice||c.voice==="me")continue;voice=c.voice,instruct=_buildInstruct(c.instruct,line.emotion,c.voice),text=_rehInlineTone(stripMarkdown(line.text),line.emotion)}else{if(!rehState.narratorVoice||!(line.text||"").trim())continue;voice=rehState.narratorVoice,instruct="",text=stripMarkdown(line.text)}keep.push(await _lineAudioCacheKey(text,voice,instruct))}const book=_lineAudioBookName(),r=await fetch(`/api/line-audio/${encodeURIComponent(book)}/prune`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({keep})});if(!r.ok)throw new Error((await r.json().catch(()=>({}))).detail||r.statusText);const d=await r.json();toast(d.deleted?`Cleaned up ${d.deleted} unused cached audio file${d.deleted!==1?"s":""}`:"Nothing to clean up \u2014 every cached file is still in use","success")}catch(e){toast("Cache cleanup failed: "+(e.message||e),"error")}finally{btn&&(btn.disabled=!1,btn.innerHTML=' Clean cache')}});function showRecOverlay(line){const overlay=$("reh-rec-overlay");if(!overlay)return;overlay.hidden=!1;const cue=$("reh-rec-cue"),c=rehState.cast[line.speaker]||{color:"#89b4fa"};cue&&(cue.innerHTML=`${escHtml(line.speaker)} \u2014 your line:${escHtml(stripMarkdown(line.text))}
`),$("reh-rec-preview")&&($("reh-rec-preview").style.display="none",$("reh-rec-preview").src=""),$("reh-rec-confirm-row")&&($("reh-rec-confirm-row").hidden=!0),$("reh-rec-start")&&($("reh-rec-start").disabled=!1),$("reh-rec-stop")&&($("reh-rec-stop").disabled=!0),$("reh-rec-time")&&($("reh-rec-time").textContent="0:00"),rehState.lastRecBlob=null}function hideRecOverlay(){const overlay=$("reh-rec-overlay");overlay&&(overlay.hidden=!0),stopRehMic()}function rehRenderMeter(level=0,db=-1/0,clipped=!1){const meter=$("reh-mic-meter");if(!meter)return;if(!meter.children.length)for(let i=0;i<18;i++){const b=document.createElement("div");b.className="bar",meter.appendChild(b)}const active=Math.round(Math.max(0,Math.min(1,level))*meter.children.length);[...meter.children].forEach((bar,i)=>{bar.className="bar",bar.style.height=7+Math.min(i,active)*1.55+"px",i-12&&i>11&&bar.classList.add("hot"),clipped&&i>14&&bar.classList.add("clip"))});const el=$("reh-db-readout");el&&(el.textContent=Number.isFinite(db)?db.toFixed(1)+" dB":"-\u221E dB")}function rehStartMeter(){if(!rehState.recAnalyser)return;rehState.recMeterRaf&&cancelAnimationFrame(rehState.recMeterRaf);const data=new Float32Array(rehState.recAnalyser.fftSize),canvas=$("reh-live-wave"),RING=300,ADD=10;rehState.recWaveRing=new Float32Array(RING);const tick=()=>{rehState.recAnalyser.getFloatTimeDomainData(data);let sum=0,peak=0;for(const s of data)sum+=s*s,peak=Math.max(peak,Math.abs(s));const rms=Math.sqrt(sum/data.length),db=rms>0?20*Math.log10(rms):-1/0;if(rehRenderMeter((db+60)/60,db,peak>.98),canvas&&rehState.recWaveRing){const ring=rehState.recWaveRing;ring.copyWithin(0,ADD);for(let i=0;i.98?"#f38ba8":db>-12?"#f9e2af":"#a6e3a1",ctx.lineWidth=1.5;const mid=h/2;for(let i=0;i{try{n&&n.disconnect()}catch{}}),rehState.recStream&&rehState.recStream.getTracks().forEach(t=>t.stop()),rehState.recDestStream&&rehState.recDestStream.getTracks().forEach(t=>t.stop()),rehState.recAudioCtx&&rehState.recAudioCtx.close().catch(()=>{}),Object.assign(rehState,{recStream:null,recDestStream:null,recSourceNode:null,recGainNode:null,recAnalyser:null,recAudioCtx:null,recWaveRing:null}),rehRenderMeter();const wc=$("reh-live-wave");wc&&wc.getContext("2d").clearRect(0,0,wc.width,wc.height)}(_$a=$("reh-rec-start"))==null||_$a.addEventListener("click",async()=>{try{await startRehMic(),rehState.recChunks=[],rehState.recSecs=0,$("reh-rec-time")&&($("reh-rec-time").textContent="0:00"),$("reh-rec-start")&&($("reh-rec-start").disabled=!0),$("reh-rec-stop")&&($("reh-rec-stop").disabled=!1),$("reh-rec-confirm-row")&&($("reh-rec-confirm-row").hidden=!0),rehState.recTimer=setInterval(()=>{rehState.recSecs++,$("reh-rec-time")&&($("reh-rec-time").textContent=Math.floor(rehState.recSecs/60)+":"+String(rehState.recSecs%60).padStart(2,"0"))},1e3),rehState.mediaRec=new MediaRecorder(rehState.recDestStream||rehState.recStream,{audioBitsPerSecond:256e3}),rehState.mediaRec.ondataavailable=e=>{e.data.size&&rehState.recChunks.push(e.data)},rehState.mediaRec.onstop=()=>{clearInterval(rehState.recTimer),$("reh-rec-start")&&($("reh-rec-start").disabled=!1),$("reh-rec-stop")&&($("reh-rec-stop").disabled=!0);const blob=new Blob(rehState.recChunks,{type:rehState.mediaRec.mimeType||"audio/webm"}),url=URL.createObjectURL(blob),p=$("reh-rec-preview");p&&(p.src=url,p.style.display=""),$("reh-rec-confirm-row")&&($("reh-rec-confirm-row").hidden=!1),rehState.lastRecBlob=blob},rehState.mediaRec.start(100)}catch(e){stopRehMic(),toast(await microphoneErrorMessage(e),"error")}}),(_ab=$("reh-rec-stop"))==null||_ab.addEventListener("click",()=>{var _a2;((_a2=rehState.mediaRec)==null?void 0:_a2.state)!=="inactive"&&rehState.mediaRec.stop()}),(_bb=$("reh-rec-keep"))==null||_bb.addEventListener("click",()=>{var _a2;rehState.lastRecBlob&&rehState.clips.push({lineIndex:rehState.lineIndex,speaker:(_a2=rehState.lines[rehState.lineIndex])==null?void 0:_a2.speaker,type:"me",blob:rehState.lastRecBlob}),stopRehMic(),hideRecOverlay(),rehState.lineIndex++,startPlay()}),(_cb=$("reh-rec-redo"))==null||_cb.addEventListener("click",()=>{stopRehMic();const l=rehState.lines[rehState.lineIndex];l&&showRecOverlay(l)}),(_db=$("reh-skip-line"))==null||_db.addEventListener("click",()=>{var _a2;rehState.clips.push({lineIndex:rehState.lineIndex,speaker:(_a2=rehState.lines[rehState.lineIndex])==null?void 0:_a2.speaker,type:"skip"}),stopRehMic(),hideRecOverlay(),rehState.lineIndex++,startPlay()});function renderSummary(){const list=$("reh-summary-list");if(list){if(!rehState.clips.length){list.innerHTML='No clips in this session.
';return}list.innerHTML=rehState.clips.map((clip,idx)=>{const line=rehState.lines[clip.lineIndex]||{text:"\u2014",speaker:clip.speaker},c=rehState.cast[clip.speaker]||{color:"#89b4fa"},typeLabel=clip.type==="me"?"\u{1F3A4} Recorded":clip.type==="tts"?"\u{1F50A} Synthesized":"\u23ED Skipped",audioHtml=clip.blob?` `:"",dlHtml=clip.blob?` `:"";return`
${escHtml(clip.speaker||"\u2014")} ${typeLabel}
diff --git a/static/js/rehearser.js b/static/js/rehearser.js
index 41e1d8b..03c10c4 100644
--- a/static/js/rehearser.js
+++ b/static/js/rehearser.js
@@ -3745,7 +3745,7 @@ async function playPreDecoded(lineIdx, blob, text) {
function findNextCachedLine(fromIdx) {
for (let i = fromIdx; i < rehState.lines.length; i++) {
- if (rehState.lines[i].type === 'dialog' && rehState.synthCache.has(i)) return i;
+ if (rehState.synthCache.has(i)) return i;
}
return -1;
}