R.cancel=()=>{ R.done=true; try{ venc.close(); }catch(e){} try{ aenc.close(); }catch(e){} };
return R; }
const smallDev=()=>/Mobi|Android|iPhone|iPad|iPod/i.test(navigator.userAgent||'')||(navigator.maxTouchPoints>1&&/Mac/i.test(navigator.platform||''));
const tablet=()=>/iPad|Tablet/i.test(navigator.userAgent||'')||(navigator.maxTouchPoints>1&&/Mac/i.test(navigator.platform||''))||(/Android/i.test(navigator.userAgent||'')&&!/Mobi/i.test(navigator.userAgent||''));
function fits(seconds,mbps){ const perSec=mbps/8*3.5+0.4, needMB=Math.round(seconds*perSec);
const dm=navigator.deviceMemory||0, roomMB=!smallDev()?4500:Math.max(tablet()?1000:600,dm>=8?1200:0);
return {ok:needMB<=roomMB, needMB, roomMB, small:smallDev(), maxSeconds:Math.floor(roomMB/perSec)}; }
async function awake(){ try{ if(navigator.wakeLock&&navigator.wakeLock.request){ const l=await navigator.wakeLock.request('screen'); return ()=>{ try{ l.release(); }catch(e){} }; } }catch(e){} return ()=>{}; }
function plan(o){
const small=smallDev(), L=settings.light||{size:720,video_mbps:{60:6,30:4},parts:[30,60]};
const dur=o.dur, at=Math.max(0,Math.min(dur,o.pos||0)), mm=s=>{ s=Math.max(0,Math.round(s)); return Math.floor(s/60)+':'+String(s%60).padStart(2,'0'); };
const partFrom=len=>(at<0.25?0:Math.max(0,Math.min(at,dur-len)));
let big=!small;
const mk=(fps,len)=>{ const whole=!len; return {fps, light:!big, size:big?settings.size:L.size,
mbps:big?(settings.video_mbps[(small?'phone':'computer')+'_'+fps]||(fps>30?16:10)):((L.video_mbps||{})[fps]||(fps>30?6:4)),
from:whole?0:partFrom(len), len:whole?dur:len, whole}; };
return new Promise(done=>{
const box=document.createElement('div'); box.setAttribute('role','dialog');
box.style.cssText='position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);z-index:60;background:#05080a;color:#cfe3dc;border:1px solid #2b4441;'
+'box-shadow:0 20px 60px rgba(0,0,0,.7);padding:16px 18px;font:13px/1.5 "Courier New",ui-monospace,monospace;letter-spacing:.06em;width:min(94vw,470px)';
const close=v=>{ try{ box.remove(); }catch(e){} removeEventListener('keydown',key); done(v); };
const key=e=>{ if(e.key==='Escape') close(null); }; addEventListener('keydown',key);
const h=document.createElement('div'); h.innerHTML='⏺ RENDER · a clean video of the whole board, made frame by frame';
h.style.cssText='letter-spacing:.14em;margin:0 0 12px;opacity:.9'; box.appendChild(h);
{ const mw=document.querySelector('meta[name="render-weight"]'), wv=mw?parseFloat(mw.content):0;
if(wv>=1.5){ const n=document.createElement('div'); n.textContent='This board draws a lot in every frame: its render takes about '+(Math.round(wv*2)/2)+'× as long as most. For a quicker video, choose 30 FPS, a section, or 720.';
n.style.cssText='margin:-6px 0 12px;font-size:12px;line-height:1.45;color:#e8c08a;opacity:.88;letter-spacing:.03em'; box.appendChild(n); } }
const btn=(html,on,pick)=>{ const b=document.createElement('button'); b.type='button'; b.innerHTML=html;
b.style.cssText='flex:1;text-align:left;padding:8px 10px;background:'+(pick?'#10201d':'transparent')+';color:inherit;border:1px solid '+(pick?'#4ae8d4':'#2b4441')+';font:inherit;letter-spacing:.06em;cursor:pointer;text-transform:none;min-width:0';
b.onclick=on; return b; };
let first=null;
for(const fps of [60,30]){
const row=document.createElement('div'); row.style.cssText='display:flex;gap:8px;align-items:stretch;margin:0 0 8px;flex-wrap:wrap';
const lab=document.createElement('div'); lab.textContent=fps+' FPS'; lab.style.cssText='flex:0 0 64px;align-self:center;letter-spacing:.16em;opacity:.85'; row.appendChild(lab);
const pickFull=(fps===(small?30:60))&&!small;
const fb=btn('FULL SONG
'+mm(dur)+'',()=>close(mk(fps,0)),pickFull); row.appendChild(fb); if(pickFull) first=fb;
const sec=document.createElement('div'); sec.style.cssText='flex:1;display:flex;flex-direction:column;gap:4px;min-width:0';
const st=document.createElement('div'); st.innerHTML='A SECTION from '+mm(partFrom(30))+''; st.style.cssText='font-size:12px;padding:0 2px'; sec.appendChild(st);
const lens=document.createElement('div'); lens.style.cssText='display:flex;gap:6px';
for(const p of (L.parts||[30,60])){ if(dur<=p+1) continue; const pick=small&&fps===30&&p===30;
const b=btn(p+' s',()=>close(mk(fps,p)),pick); b.style.textAlign='center'; b.style.padding='5px 6px'; lens.appendChild(b); if(pick) first=b; }
if(!lens.children.length){ const n=document.createElement('span'); n.textContent='(the song is short: FULL SONG)'; n.style.cssText='font-size:11px;opacity:.55'; lens.appendChild(n); }
sec.appendChild(lens); row.appendChild(sec); box.appendChild(row); }
const sz=document.createElement('div'); sz.style.cssText='display:flex;gap:8px;align-items:center;margin:6px 0 10px;font-size:12px';
const szl=document.createElement('span'); szl.textContent='SIZE'; szl.style.cssText='flex:0 0 64px;letter-spacing:.16em;opacity:.85'; sz.appendChild(szl);
const sb=[]; const paint=()=>sb.forEach(([b,v])=>{ b.style.borderColor=(v===big)?'#4ae8d4':'#2b4441'; b.style.background=(v===big)?'#10201d':'transparent'; });
for(const [v,t] of [[true,settings.size+' · the best pixels'],[false,L.size+' · lighter, quicker on a phone']]){ const b=btn(t,()=>{ big=v; paint(); },false); b.style.padding='5px 8px'; b.style.fontSize='11px'; sb.push([b,v]); sz.appendChild(b); }
paint(); box.appendChild(sz);
const n=document.createElement('div'); n.style.cssText='font-size:11px;opacity:.6;margin:2px 0 10px;line-height:1.5';
n.textContent='A section starts where the song is now: play, pause, then ⏺ RENDER. 60 FPS is as smooth as a computer screen; 30 is lighter. Nothing is uploaded.'; box.appendChild(n);
const c=document.createElement('button'); c.type='button'; c.textContent='cancel'; c.style.cssText='background:transparent;color:inherit;border:1px solid #2b4441;padding:6px 12px;font:inherit;cursor:pointer;opacity:.75';
c.onclick=()=>close(null); box.appendChild(c); document.body.appendChild(box);
if(first) setTimeout(()=>{ try{ first.focus(); }catch(e){} },0);
}).then(p=>{ if(!p){ if(o.say) o.say('render not started'); return null; }
if(p.fps!==o.fps){ const sw=document.getElementById('fps'); if(sw&&sw.onclick){ sw.onclick(); } }
const secs=p.whole?(o.head||0)+dur+(o.tail||0):p.len, f=fits(secs,p.mbps);
const size=mb=>mb>=1000?(mb/1000).toFixed(1)+' GB':Math.round(mb)+' MB';
if(!f.ok){ const why='this render needs about '+size(f.needMB)+' of memory here, and this '+(f.small?'phone or tablet':'computer')+' has room for about '+size(f.roomMB)+', so it may stop near the end and be lost. 720, 30 FPS, or a section needs much less (a section of '+Math.max(10,Math.floor(Math.min(f.maxSeconds,60)))+' s or less fits)';
if(!confirm('This render is long for this device.\n\n'+why+'\n\nRender anyway?')){ if(o.say) o.say('render not started · '+why); return null; } }
p.slow=f.small?' · slower on a phone or tablet, quickest on a computer':''; return p; }); }
(function renderOnly(){ const go=()=>{ if(!document.getElementById('render')||!document.getElementById('take')) return;
if(document.getElementById('render-only-chime')) return; const st=document.createElement('style'); st.id='render-only-chime';
st.textContent='#take,#rec,#fps{display:none!important}'; (document.head||document.documentElement).appendChild(st); };
if(typeof document==='undefined') return; if(document.readyState==='loading') document.addEventListener('DOMContentLoaded',go); else go(); setTimeout(go,0); })();
function mp4Opus(u){
const dv=new DataView(u.buffer,u.byteOffset,u.byteLength), str=(o)=>String.fromCharCode(u[o],u[o+1],u[o+2],u[o+3]);
const kids=(o,end)=>{ const out=[]; while(o+8<=end){ let sz=dv.getUint32(o), h=8; const t=str(o+4);
if(sz===1){ sz=Number(dv.getBigUint64(o+8)); h=16; } else if(sz===0) sz=end-o; if(szlist.find(b=>b.t===t), inside=(b)=>kids(b.o+b.h,b.end);
const moov=find(kids(0,u.length),'moov'); if(!moov) return null;
for(const trak of inside(moov).filter(b=>b.t==='trak')){
const tk=inside(trak), mdia=find(tk,'mdia'); if(!mdia) continue; const md=inside(mdia);
const hdlr=find(md,'hdlr'); if(!hdlr||str(hdlr.o+hdlr.h+8)!=='soun') continue;
const mdhd=find(md,'mdhd'), v=u[mdhd.o+mdhd.h], timescale=dv.getUint32(mdhd.o+mdhd.h+(v===1?20:12));
const stbl=find(inside(find(md,'minf')),'stbl'); if(!stbl) continue; const st=inside(stbl);
const stsd=find(st,'stsd'); const ent=kids(stsd.o+stsd.h+8,stsd.end)[0]; if(!ent||ent.t!=='Opus') continue;
const dops=find(kids(ent.o+ent.h+28,ent.end),'dOps'); if(!dops) continue; const d=dops.o+dops.h;
const ch=u[d+1], preskip=dv.getUint16(d+2), family=u[d+10]; if(family!==0||ch<1||ch>2) return {unsupported:'channel mapping '+family};
const durs=[]; { const b=find(st,'stts'), n=dv.getUint32(b.o+b.h+4); for(let i=0;i=first) per=cnt; let off=chunks[c];
for(let k=0;k=0){ skipTs=mt; break; } } } }
const to48=x=>Math.round(x*48000/timescale), total=to48(samples.reduce((a,s)=>a+s.dur,0));
return {ch,timescale,samples,skip:skipTs>=0?to48(skipTs):preskip,total,bytes:u}; }
return null; }
async function decode(ctx,bytes){ let first=null;
try{ return await ctx.decodeAudioData(bytes.slice(0)); }catch(e){ first=e; }
const fail=m=>{ const e=new Error(m); e.rescue=m; e.first=first; return e; };
let T=null; try{ T=mp4Opus(new Uint8Array(bytes)); }catch(e){ T=null; }
if(!T) throw first; if(T.unsupported) throw fail('no-opus');
if(typeof AudioDecoder==='undefined'||typeof EncodedAudioChunk==='undefined') throw fail('no-decoder');
const cfg={codec:'opus',sampleRate:48000,numberOfChannels:T.ch}; let can=false;
try{ can=!!(await AudioDecoder.isConfigSupported(cfg)).supported; }catch(e){} if(!can) throw fail('no-opus');
const outs=[]; let err=null, frames=0;
const dec=new AudioDecoder({output:a=>{ const n=a.numberOfFrames, planes=[]; for(let c=0;c{ err=e; }});
dec.configure(cfg); let ts=0;
for(const s of T.samples){ dec.decode(new EncodedAudioChunk({type:'key',timestamp:Math.round(ts*1e6/T.timescale),duration:Math.round(s.dur*1e6/T.timescale),data:T.bytes.subarray(s.off,s.off+s.size)}));
ts+=s.dur; if(dec.decodeQueueSize>64) await new Promise(r=>setTimeout(r,0)); if(err) break; }
try{ await dec.flush(); }catch(e){ err=err||e; } try{ dec.close(); }catch(e){} if(err||!outs.length) throw fail('decode-failed');
const firstDur=Math.round(T.samples[0].dur*48000/T.timescale), cut=outs[0][0].length>=firstDur?T.skip:0;
const len=Math.max(1,Math.min(frames-cut,T.total-T.skip)), buf=ctx.createBuffer(T.ch,len,48000);
for(let c=0;cfrom) dst.set(p.subarray(from,to),at+from); at+=p.length; if(at>=len) break; } }
try{ buf.rescued='opus'; }catch(e){} return buf; }
return {settings,ok,okLive,start,render,fits,awake,plan,decode,mp4Opus};
})();
const RenderChild=(()=>{
if(!/#render$/.test(location.hash||'')) return null;
let seed=20261003;
Math.random=()=>{ seed|=0; seed=seed+0x6D2B79F5|0; let t=Math.imul(seed^seed>>>15,1|seed); t=t+Math.imul(t^t>>>7,61|t)^t; return ((t^t>>>14)>>>0)/4294967296; };
const C={t:0, raf:[], ivs:new Map(), nid:1};
try{ performance.now=()=>C.t*1000; }catch(e){}
window.requestAnimationFrame=cb=>{ C.raf.push(cb); return C.raf.length; }; window.cancelAnimationFrame=()=>{};
window.setInterval=fn=>{ const id=C.nid++; C.ivs.set(id,fn); return id; }; window.clearInterval=id=>{ C.ivs.delete(id); };
try{ Object.defineProperty(window,'devicePixelRatio',{get:()=>1.25,configurable:true}); }catch(e){}
return C; })();
(()=>{ const apple=/iPhone|iPad|iPod/i.test(navigator.userAgent||'')||(navigator.maxTouchPoints>1&&/Mac/i.test(navigator.platform||''));
const WIDE='audio/*,video/*,.mp3,.m4a,.aac,.wav,.flac,.ogg,.oga,.opus,.webm,.weba,.mp4,.m4v,.mov,.aif,.aiff,.caf,.3gp,.mkv';
const free=()=>document.querySelectorAll('input[type=file][accept*="audio"]').forEach(i=>{ if(apple) i.removeAttribute('accept'); else i.setAttribute('accept',WIDE); });
if(document.readyState==='loading') document.addEventListener('DOMContentLoaded',free); else free();
let last=null;
document.addEventListener('change',e=>{ const t=e.target; if(t&&t.files&&t.files[0]) last=t.files[0]; },true);
addEventListener('drop',e=>{ const f=e.dataTransfer&&e.dataTransfer.files&&e.dataTransfer.files[0]; if(f) last=f; },true);
if(typeof CleanRec!=='undefined') CleanRec.whyNot=err=>{
const name=(last&&last.name)||'that file', ext=(name.match(/\.([a-z0-9]+)$/i)||[,''])[1].toLowerCase(), raw=String(err&&err.message||err||'');
const keep=' MP3, M4A and WAV open in every browser (on Suno: Download → MP3 or WAV).';
if(err&&err.rescue) return 'could not hear '+name+': its sound is Opus inside an .'+(ext||'m4a')+' (Suno’s app saves songs this way). This browser can play it but won’t hand the whole song over, and '+
(err.rescue==='decode-failed'?'its own Opus decoder stopped part way':'it has no Opus decoder the chime could use instead')+'. It works in Chrome or Edge, or download the song from Suno as MP3 or WAV.';
if(apple&&/^(ogg|oga|opus|webm|weba|mkv)$/.test(ext)) return 'could not hear '+name+': Apple’s browsers can’t open .'+ext+' sound. Save the song as MP3, M4A or WAV and load it again.'+keep;
if(!apple&&/^(aif|aiff|caf)$/.test(ext)) return 'could not hear '+name+': this browser can’t open .'+ext+' (an Apple format). Save it as WAV or MP3 and load it again.'+keep;
if(/^(m4a|mp4|m4v|mov|3gp)$/.test(ext)&&last&&last.arrayBuffer){ const f0=last; f0.arrayBuffer().then(b=>{ const u=new Uint8Array(b);
for(let i=0;i+4{}); }
if(/^(mp4|m4v|mov|3gp|mkv|webm)$/.test(ext)) return 'could not hear '+name+': this video’s sound track could not be read here (it may have no sound, or sound in a format this browser doesn’t open).'+keep;
return 'could not hear '+name+': this browser could not open its sound'+(raw?' ('+raw.slice(0,80)+')':'')+'.'+keep; };
})();