const host=document.getElementById('reference-clip'); const q=s=>element.querySelector(s), player=q('audio'), status=q('.reference-status'); const listeners=new AbortController(), on=(n,e,f)=>n.addEventListener(e,f,{signal:listeners.signal}); let original=null, current=null, objectURL=null, generation=0, recorder=null, stream=null, timer=0, recordStarted=0, recordingRequested=false, disposed=false; const minutes=s=>`${Math.floor(s/60)}:${String(Math.floor(s%60)).padStart(2,'0')}`; function busy(value) { host.dataset.busy=String(value); q('[data-upload]').disabled=value; q('[data-apply]').disabled=value; const generate=document.getElementById('generate-speech'); if(generate)generate.disabled=(value && document.getElementById('voice-options')?.dataset.clip==='true') || document.getElementById('status-state')?.dataset.busy==='true'; } function tell(text) { status.textContent=text; } function setFileData(data) { host.dataset.file=JSON.stringify(data); props.value=JSON.stringify(data); } function stopTracks() { stream?.getTracks().forEach(t=>t.stop());stream=null;clearInterval(timer); } function wav(buffer,start,end) { const rate=buffer.sampleRate, first=Math.floor(start*rate), frames=Math.floor(end*rate)-first; const bytes=new ArrayBuffer(44+frames*2), v=new DataView(bytes); const str=(o,s)=>[...s].forEach((c,i)=>v.setUint8(o+i,c.charCodeAt(0))); str(0,'RIFF');v.setUint32(4,36+frames*2,true);str(8,'WAVE');str(12,'fmt ');v.setUint32(16,16,true);v.setUint16(20,1,true);v.setUint16(22,1,true);v.setUint32(24,rate,true);v.setUint32(28,rate*2,true);v.setUint16(32,2,true);v.setUint16(34,16,true);str(36,'data');v.setUint32(40,frames*2,true); const channels=Array.from({length:buffer.numberOfChannels},(_,i)=>buffer.getChannelData(i)); for(let i=0;i25*1024*1024) throw Error('Choose an audio file smaller than 25 MB.'); const buffer=await decode(file); if(disposed || version!==generation) return; if(buffer.duration<.5) throw Error('The clip must be at least 0.5 seconds long.'); if(buffer.duration>60) throw Error('Choose a clip no longer than 60 seconds.'); tell('Uploading voice clip…'); const uploaded=await upload(file); if(disposed || version!==generation) return; const item={file,buffer,name:file.name};if(!fromTrim) original=item; setFileData({...uploaded,orig_name:file.name,mime_type:file.type||'audio/wav',meta:{_type:'gradio.FileData'}}); preview(item,fromTrim);tell('Voice clip ready.'); } catch(error) { if(version===generation) tell((error.message||'Could not load this audio.')+(current?' Your previous clip is still selected.':'')); } finally {if(version===generation) busy(false);} } function remove() { generation++;recordingRequested=false;busy(false);stopTracks();if(recorder?.state==='recording') recorder.stop();recorder=null; player.pause();player.removeAttribute('src');player.load();if(objectURL)URL.revokeObjectURL(objectURL);objectURL=null;original=current=null;setFileData(null); q('[data-file]').value='';q('[data-file-name]').textContent='Drop a voice clip here'; for(const selector of ['.reference-player','[data-trim]','[data-remove]'])q(selector).hidden=true; q('[data-record]').textContent='Record voice';tell(''); } on(q('[data-upload]'),'click',()=>q('[data-file]').click()); on(q('.drop-area'),'click',()=>q('[data-file]').click()); on(q('.drop-area'),'keydown',e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();q('[data-file]').click();}}); on(q('[data-file]'),'change',e=>accept(e.target.files[0])); on(q('.drop-area'),'dragover',e=>{e.preventDefault();q('.drop-area').dataset.over='true';}); on(q('.drop-area'),'dragleave',()=>q('.drop-area').dataset.over='false'); on(q('.drop-area'),'drop',e=>{e.preventDefault();q('.drop-area').dataset.over='false';accept(e.dataTransfer.files[0]);}); on(q('[data-remove]'),'click',remove); on(q('[data-play]'),'click',async()=>{try{if(player.paused)await player.play();else player.pause();}catch{tell('Playback unavailable. Choose a different audio file.');}}); on(player,'play',()=>q('[data-play]').textContent='Pause');on(player,'pause',()=>q('[data-play]').textContent=player.ended?'Replay':'Play'); on(player,'timeupdate',()=>{q('[data-time]').textContent=minutes(player.currentTime);if(player.duration)q('[data-seek]').value=player.currentTime/player.duration*1000;}); on(q('[data-seek]'),'input',()=>{if(Number.isFinite(player.duration))player.currentTime=+q('[data-seek]').value/1000*player.duration;}); on(q('[data-apply]'),'click',()=>{ if(!original)return;const start=+q('[data-start]').value,end=+q('[data-end]').value; if(!Number.isFinite(start+end)||start<0||end>original.buffer.duration||end-start<.5||end-start>60){tell('Select 0.5–60 seconds within the clip.');return;} accept(wav(original.buffer,start,end),true); }); on(q('[data-reset]'),'click',()=>{if(original)accept(original.file);}); on(q('[data-record]'),'click',async()=>{ if(recorder?.state==='recording'){recorder.stop();stopTracks();return;} if(host.dataset.busy==='true')return; const version=++generation;recordingRequested=true;busy(true);tell('Waiting for microphone permission…'); try { if(!navigator.mediaDevices?.getUserMedia || !window.MediaRecorder)throw Error('Recording is unavailable in this browser. Choose an audio file.'); player.pause();document.getElementById('speech-player')?.dispatchEvent(new CustomEvent('zonos:pause')); const acquired=await navigator.mediaDevices.getUserMedia({audio:true}); if(disposed||version!==generation){acquired.getTracks().forEach(t=>t.stop());return;} stream=acquired;const type=['audio/webm;codecs=opus','audio/mp4','audio/webm'].find(t=>MediaRecorder.isTypeSupported(t)); recorder=new MediaRecorder(stream,type?{mimeType:type}:undefined);const chunks=[]; recorder.ondataavailable=e=>{if(e.data.size)chunks.push(e.data);}; recorder.onerror=()=>{if(disposed||version!==generation)return;recordingRequested=false;generation++;stopTracks();if(recorder?.state==='recording')recorder.stop();q('[data-record]').textContent='Record voice';busy(false);tell('Recording failed. Try uploading a clip.');}; recorder.onstop=()=>{ if(disposed||version!==generation)return; recordingRequested=false;stopTracks();q('[data-record]').textContent='Record voice';busy(false); if(disposed||version!==generation)return; const mime=recorder.mimeType,extension=mime.includes('mp4')?'m4a':'webm'; accept(new File(chunks,`recording.${extension}`,{type:mime})); }; recorder.start();recordStarted=performance.now();q('[data-record]').textContent='Stop recording'; timer=setInterval(()=>{const seconds=(performance.now()-recordStarted)/1000;tell('Recording · '+minutes(seconds));if(seconds>=59){recorder.stop();stopTracks();}},200); } catch(error){if(version!==generation)return;recordingRequested=false;stopTracks();busy(false);tell(error.message||'Microphone unavailable. Choose an audio file.');} }); on(host,'zonos:source',e=>{if(e.detail==='Preset' && recordingRequested){recordingRequested=false;generation++;if(recorder?.state==='recording')recorder.stop();stopTracks();q('[data-record]').textContent='Record voice';busy(false);tell('Recording cancelled.');}else busy(host.dataset.busy==='true');}); function dispose(){disposed=true;generation++;stopTracks();if(recorder?.state==='recording')recorder.stop();player.pause();if(objectURL)URL.revokeObjectURL(objectURL);listeners.abort();observer.disconnect();} const observer=new MutationObserver(()=>{if(!element.isConnected)dispose();});observer.observe(document.documentElement,{childList:true,subtree:true}); on(window,'pagehide',dispose);setFileData(null);busy(false);