Spaces:
Running on Zero
Running on Zero
Download assets/reference.js from fffiloni/ZONOS3-Mini: direct link, hf CLI and curl.
- Browser
- Download file 8.96 kB
-
https://huggingface.co/spaces/fffiloni/ZONOS3-Mini/resolve/main/assets/reference.js
- Command line
-
hf download hf://spaces/fffiloni/ZONOS3-Mini/assets/reference.js
-
curl -L -o reference.js https://huggingface.co/spaces/fffiloni/ZONOS3-Mini/resolve/main/assets/reference.js
8.96 kB
| 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;i<frames;i++) { let sample=0;for(const c of channels) sample+=c[first+i]/channels.length;sample=Math.max(-1,Math.min(1,sample));v.setInt16(44+i*2,sample<0?sample*32768:sample*32767,true); } | |
| return new File([bytes],'reference.wav',{type:'audio/wav'}); | |
| } | |
| async function decode(file) { | |
| const AudioContextClass=window.AudioContext||window.webkitAudioContext; | |
| const context=new AudioContextClass(); | |
| try {return await context.decodeAudioData(await file.arrayBuffer());} | |
| finally {await context.close();} | |
| } | |
| function preview(item, preserveSelection=false) { | |
| player.pause();if(objectURL) URL.revokeObjectURL(objectURL); | |
| objectURL=URL.createObjectURL(item.file);player.src=objectURL;current=item; | |
| q('.reference-player').hidden=false;q('[data-trim]').hidden=false;q('[data-remove]').hidden=false; | |
| q('[data-file-name]').textContent=item.name;q('[data-duration]').textContent=minutes(item.buffer.duration); | |
| if(!preserveSelection){q('[data-start]').value='0';q('[data-end]').value=original.buffer.duration.toFixed(2);} | |
| q('[data-start]').max=q('[data-end]').max=original.buffer.duration; | |
| q('[data-play]').textContent='Play';q('[data-time]').textContent='0:00';q('[data-seek]').value='0'; | |
| } | |
| async function accept(file,fromTrim=false) { | |
| if(!file) return; | |
| const version=++generation;busy(true);tell('Preparing voice clip…'); | |
| try { | |
| if(file.size>25*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); | |