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; }; })();