189 lines
47 KiB
HTML
189 lines
47 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<title>noise</title>
|
|
<link rel="icon" href="data:,">
|
|
<style>
|
|
:root{--bg:#000;--panel:transparent;--muted:#8d95a3;--text:#f5f7fb;--line:transparent;--accent:#f2f2f2;--accent2:#ff7a59;--ok:#73d993;--warn:#ffd166;--hover:#c9f6ff;--active:#ffffff}
|
|
*{box-sizing:border-box}html,body{height:100%;margin:0;background:var(--bg);color:var(--text);font:14px/1.35 system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif}
|
|
a{color:var(--accent);text-decoration:none}a:hover{color:var(--hover)}
|
|
button,input,select,textarea{font:inherit}
|
|
#app{min-height:100%;display:grid;grid-template-rows:auto auto 1fr}
|
|
.hideu{opacity:0;display:none;}
|
|
.top{position:sticky;top:0;z-index:5;background:rgba(0,0,0,.92);backdrop-filter:blur(8px)}
|
|
.hero{padding:14px 12px 6px}
|
|
.title{font-size:22px;font-weight:400;letter-spacing:.02em}
|
|
.sub{display:none}
|
|
.msg{min-height:20px;color:var(--muted);padding:0 12px 10px}
|
|
.layout{display:grid;grid-template-columns:1fr;gap:14px;padding:8px 10px 18px;max-width:1100px;width:100%;margin:0 auto}
|
|
.panel{background:transparent;border:none;border-radius:0;padding:6px 2px}
|
|
.transport{display:grid;grid-template-columns:auto 1fr auto;gap:10px;align-items:center}
|
|
.controls{display:flex;gap:10px;flex-wrap:wrap}
|
|
.btn,.pill{background:transparent;border:none;color:var(--muted);border-radius:0;padding:4px 2px;cursor:pointer;transition:color .16s ease,opacity .16s ease,transform .16s ease}
|
|
.btn:hover,.pill:hover{color:var(--hover)}
|
|
.btn:active,.pill:active{color:var(--active);transform:translateY(1px)}
|
|
.btn.active,.pill.active{color:var(--accent)}
|
|
.btn.warn{color:var(--warn)}
|
|
.btn.accent{color:var(--accent)}
|
|
.btn.danger{color:var(--accent2)}
|
|
.readout{display:flex;gap:10px;align-items:center;justify-content:flex-end;color:var(--muted);font-variant-numeric:tabular-nums}
|
|
.mixer{display:grid;grid-template-columns:repeat(10,minmax(24px,1fr));gap:10px;align-items:end;justify-items:center;height:340px;padding:10px 4px 2px}
|
|
.band{display:flex;flex-direction:column;align-items:center;justify-content:flex-end;gap:8px;height:100%}
|
|
.band label{font-size:10px;color:var(--muted);white-space:nowrap}
|
|
.v{-webkit-appearance:none;appearance:none;width:22px;height:250px;background:transparent;writing-mode:vertical-lr;direction:rtl}
|
|
.v::-webkit-slider-runnable-track{width:10px;height:100%;background:#111;border-radius:999px}
|
|
.v::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:22px;height:22px;border-radius:50%;background:currentColor;border:none;box-shadow:0 0 0 6px #000;margin-left:-6px}
|
|
.v::-moz-range-track{width:10px;height:100%;background:#111;border:none;border-radius:999px}
|
|
.v::-moz-range-progress{background:currentColor;border-radius:999px}
|
|
.v::-moz-range-thumb{width:22px;height:22px;border:none;border-radius:50%;background:currentColor;box-shadow:0 0 0 6px #000}
|
|
.os{width:auto;height:auto;border-radius:0;padding:0}
|
|
.row{display:flex;gap:10px;flex-wrap:wrap;align-items:center}
|
|
.row>*{min-width:0}
|
|
.input{background:#050505;color:var(--text);border:none;border-bottom:1px solid #191919;border-radius:0;padding:6px 4px;outline:none}
|
|
.input:focus{border-bottom-color:var(--accent);color:var(--active)}
|
|
.small{font-size:12px;color:var(--muted)}
|
|
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:18px}
|
|
.list{display:flex;flex-wrap:wrap;gap:10px}
|
|
canvas#fft{width:120px;height:36px;background:#010101;border:none;border-radius:999px}
|
|
.text{color:#cfd6e4}
|
|
code,kbd{background:#050505;border:none;border-radius:4px;padding:1px 5px}
|
|
details{border-top:none;padding-top:0}
|
|
summary{cursor:pointer;color:var(--accent)}
|
|
.footer{display:none}
|
|
.bg{position:fixed;inset:0;background:center/cover no-repeat;opacity:.12;filter:blur(10px);pointer-events:none;transform:translateZ(0)}
|
|
@media (max-width:800px){.grid2{grid-template-columns:1fr}.mixer{gap:6px;height:300px}.v{height:220px;width:20px}.title{font-size:20px}}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="app"></div>
|
|
<script> const cfg={live:0,oneshot:false,custom:0,noiseDir:"RAIN2",cdn:"",shortUrl:"/NoiseMachines/whiteRainNoiseGenerator.php",srcA:["./audio/0a","./audio/1a","./audio/2b","./audio/3a","./audio/4a","./audio/5b","./audio/6a","./audio/7b","./audio/8a","./audio/9a"],srcB:["./audio/0b","./audio/1b","./audio/2a","./audio/3b","./audio/4b","./audio/5a","./audio/6b","./audio/7a","./audio/8b","./audio/9b"],totSizeMP3:5628436,totSizeOGG:5400163,stretch:[1.25,1.25,1.25,1.25,1.25,1.25,1.25,1.25,1.25,1.25],detune:"0",defaultCurve:[.3,.3,.3,.3,.3,.3,.3,.3,.3,.3],magicParam:"",uIDS:"",patronMessage:false,mediaTitle:"White Rain",albumDir:"RAIN2"};
|
|
const state={iNUMBERBANDS:10,bCALIBRATE:0,bMUTE:0,bMUTEsaved:0,bANIMATE:0,bDISABLE:0,bMEDITATIONSESSION:0,bFINISHEDLOADING:0,bSUPPORTOGG:0,bSUPPORTMP3:0,bSUSPENDED:0,bSTARTMUTED:0,bPITCHRAN:0,bOSAUTO:0,bEQ:0,bWAVEVISUALIZER:0,bANIMATIONONSTART:0,bANIMATIONPROFILESET:0,bANIMATIONUSERPROFILESET:0,bDynamics:1,MOBILE:false,bUSE_STREAM_DESTINATION:0,fMASTERGAIN:.5,fTARGETSLIDERLEVEL:.5,fAUDIOFADETIME:cfg.live?.5:.1,fLevelMultiplier:1.1,fSTEREOWIDTH:1,gainForEQ:1,currentLevel:Array(10).fill(.3),savedCurrentLevel:Array(10).fill(.3),savedLevel:Array(10).fill(.3),randomLevel:Array(10).fill(.3),selSliders:[1,1,1,1,1,1,1,1,1,1,0],movedSlider:null,lastSlider:null,detune:cfg.detune,playbackFactor:Array(10).fill(1),pitchTable:[1],stretch:cfg.stretch.slice(),iINITIALANIMATIONSPEED:32,iAnimationFactor:1,iAnimationMode:"s",iCurrentAnimationSpeed:32,randomCounter:0,animationProfileLow:Array(10).fill(0),animationProfileHigh:Array(10).fill(.5),animatedIndices:[],nzSliderIndex:[],mmMin:1,mmMax:0,modulationTimeout:null,iTimer:-1,iMTimer:-1,epoch:0,timerTimeout:null,fadeTimeOut:null,meditationInterval:null,meditationInterval2:null,osInterval:null,meditationBell:null,timeOutOS:Array(10),cloneURL:"",cookieURL:"",appShareURL:"",URLlevels:"",URLanimate:"",URLmute:"",URLbell:"",URLtimer:"",URLdetune:"",URLmagic:"",URLtitle:"",URLwidth:"",uIDS:cfg.uIDS,fileExt:".ogg",bDOWNLOADED:[],iSTARTED:[],iLOADED:[],iTOTAL:[],totSizeMP3:cfg.totSizeMP3,totSizeOGG:cfg.totSizeOGG,serverTotal:cfg.totSizeOGG,dlMonitorThrottle:-500,launchCounter:0,voiceover:[],allContents:"",sto:[],meditationRoomBaselineSliderLevel:.4,l:[],fCONTEXTSTART:0,longPressTimer:null,isLongPress:false,samplesArray:null,analyser:null,levelHistory:[],levelHistorySize:20,levelHistoryIndex:0,smoothedMasterLevel:.3,bLEVELHISTORYINIT:false,aWeighting:[.003,.05,.16,.35,.50,1,1.12,1.12,1.12,1.06,1,.89,.75,.63,.5,.45],preDuckGain:0,isDucking:false,interval:Array(10),nextA:Array(10),nextB:Array(10),lastPlayedOS:[1,1,1,1,1,1,1,1,1,1],sourceFileA:[],sourceFileB:[],sourceA:Array(10),sourceB:Array(10),bufferList:Array(20),gainNode:Array(10),stemAnalyser:Array(10),eqNode:Array(10),context:null,masterGain:null,dynCompressor:null,streamDestination:null,streamAudio:null,splitterNode:null,mergerNode:null,midGain:null,sideGain:null,negateR:null,negateSide:null,schedulerTimer:null,lastSchedulerTime:0,fftData:null,fftCanvas:null,fftCtx:null,pos:[0,0,0,0],posOld:[0,0,0,0],visualizerStartedAt:0};
|
|
const qs=s=>document.querySelector(s),qsa=s=>[...document.querySelectorAll(s)];
|
|
const app=qs("#app");
|
|
app.innerHTML=`<div class="bg" id="bg"></div><div class="top"><div class="hero"><div class="title"></div></div><div class="msg" id="msg"></div></div><div class="layout"><div class="panel transport"><div class="controls"><button class="btn" id="playBtn">play</button><button class="btn" id="resetBtn">reset</button><button class="btn hideu" id="downBtn">down</button><button class="btn hideu" id="upBtn">up</button><button class="btn" id="animBtn">animate</button><button class="btn hideu" id="bellBtn">bell</button><input class="input hideu" id="bellInput" size="7" placeholder=""><button class="btn" id="timerBtn">timer</button><input class="input" id="timerInput" size="4" placeholder="min"></div><div></div><div class="readout"><canvas id="fft" width="120" height="36"></canvas><span class="hideu" id="stateTxt">idle</span></div></div><div class="panel"><div class="mixer" id="mixer"></div></div><div class="grid2"><div class="panel"><div class="row"><strong>presets</strong></div><div class="list" id="presetList"></div><div class="row" style="margin-top:10px"><strong>stereo</strong></div><div class="list" id="stereoList"></div><div class="row" style="margin-top:10px"><strong>tape speed</strong></div><div class="list"><button class="pill" data-tune=".9438743126816935">432hz</button><button class="pill" data-tune="1">reset</button><button class="pill" id="slowerBtn">slower</button><button class="pill" id="fasterBtn">faster</button><button class="pill" id="alternateBtn">alternate</button></div><div class="row" style="margin-top:10px"><strong>ieq</strong></div><div class="list"><button class="pill" data-ieq="0">none</button><button class="pill" data-ieq=".5">balanced</button><button class="pill" data-ieq="1">full</button><button class="pill" id="applyCalBtn">apply</button></div></div><div class="panel"><div class="row"><strong>animation</strong></div><div class="list"><button class="pill" data-am="s">soft</button><button class="pill" data-am="h">hard</button><button class="pill" data-am="i">solo</button><button class="pill" data-am="d">duo</button><button class="pill" data-am="t">trio</button><button class="pill" data-am="q">quad</button></div><div class="list" style="margin-top:8px"><button class="pill" data-as=".125">÷8</button><button class="pill" data-as=".25">÷4</button><button class="pill" data-as=".5">÷2</button><button class="pill" data-as="1">normal</button><button class="pill" data-as="2">x2</button><button class="pill" data-as="4">x4</button><button class="pill" data-as="8">x8</button></div><div class="list" style="margin-top:8px"><button class="pill" id="setLoBtn">set low</button><button class="pill" id="loadLoBtn">low</button><button class="pill" id="loadHiBtn">load high</button><button class="pill" id="setHiBtn">set high</button></div><div class="row" style="margin-top:10px"><strong>save/share</strong></div><div class="list"><button class="pill" id="saveUrlBtn">copy url</button><button class="pill hideu" id="saveCookieBtn">save browser</button><button class="pill hideu" id="loadCookieBtn">load browser</button><button class="pill hideu" id="miniBtn">miniplayer</button><button class="pill hideu" id="qrBtn">app url</button></div></div></div><div class="panel hideu"><details open><summary></summary><div class="text hideu"><p><code>./audio/</code>:</p><p><code>0a.ogg 0b.ogg 1a.ogg 1b.ogg 2a.ogg 2b.ogg 3a.ogg 3b.ogg 4a.ogg 4b.ogg 5a.ogg 5b.ogg 6a.ogg 6b.ogg 7a.ogg 7b.ogg 8a.ogg 8b.ogg 9a.ogg 9b.ogg</code></p></div></details></div></div>`;
|
|
const bands=["sub bass","low bass","bass","high bass","low mids","mids","high mids","low treble","treble","high treble"];
|
|
const colors=["#683500","#ce0f00","#ff5232","#c8e911","#5cc93b","#45edbf","#3ccce4","#0004f3","#9000ca","#c88fff"];
|
|
qs("#mixer").innerHTML=bands.map((b,i)=>`<div class="band"><button class="btn os" id="os${i}">â–¶</button><input class="v" id="s${i}" type="range" min="0" max="0.99" step="0.001" value="0.3"><label>${b}</label></div>`).join("");
|
|
const presets=[["brown",[.5,.46,.42,.38,.34,.3,.27,.24,.21,.18]],["pink",[.3,.3,.3,.3,.3,.3,.3,.3,.3,.3]],["white",[.16,.19,.22,.25,.27,.31,.35,.38,.42,.45]],["grey",[.5,.3,.25,.2,.2,.2,.18,.16,.2,.3]],["infra",[.6,.4,.25,.15,.07,.03,0,0,0,0]],["ultra",[0,0,0,0,.03,.06,.12,.19,.3,.45]],["60hz",[.35,.5,.35,0,0,0,0,0,0,0]],["125hz",[0,.4,.5,.4,0,0,0,0,0,0]],["250hz",[0,0,.35,.45,.35,0,0,0,0,0]],["500hz",[0,0,0,.3,.4,.3,0,0,0,0]],["1khz",[0,0,0,0,.3,.4,.3,0,0,0]],["2khz",[0,0,0,0,0,.3,.4,.3,0,0]],["4khz",[0,0,0,0,0,0,.3,.4,.3,0]],["8khz",[0,0,0,0,0,0,0,.35,.45,.35]],["speech blocker",[.14,.22,.28,.4,.46,.42,.28,.21,.13,.05]],["gentle rain",[0,0,.1,.21,.29,.33,.33,.31,.25,.18]]];
|
|
qs("#presetList").innerHTML=presets.map((p,i)=>`<button class="pill" data-preset="${i}">${p[0]}</button>`).join("");
|
|
qs("#stereoList").innerHTML=`<button class="pill" data-width="0">mono</button><button class="pill" data-width=".5">narrow</button><button class="pill" data-width="1">normal</button><button class="pill" data-width="1.8">wide</button>`;
|
|
function msg(t){qs("#msg").innerHTML=t}
|
|
function createCookie(name,value,days){let expires="";if(days){const date=new Date();date.setTime(date.getTime()+days*24*60*60*1e3);expires="; expires="+date.toUTCString()}document.cookie=name+"="+encodeURIComponent(value)+expires+"; path=/"}
|
|
function readCookie(name){const nameEQ=name+"=";const ca=document.cookie.split(";");for(let c of ca){while(c.charAt(0)==" ")c=c.substring(1);if(c.indexOf(nameEQ)==0)return decodeURIComponent(c.substring(nameEQ.length)).replace(/%2C/g,",")}return null}
|
|
function getUrlVars(str){str=str||location.href;const vars={};str.replace(/[?&]+([^=&]+)=([^&]*)/gi,(m,key,value)=>vars[key]=value);return vars}
|
|
function pad(n){let s="000"+n;return s.substr(s.length-2)}
|
|
function packVector(v){let str="";for(let i=0;i<10;i++)str+=pad(Math.round(v[i]*100));return str}
|
|
function unpackVector(code,target){code=(code||"").replace(/\D/g,"");for(let i=0;i<10;i++)target[i]=((code.substring(2*i,2*i+2)||"0")/100)||0;return target}
|
|
function quantizeLevel(val){if(val<=0)return 0;if(val>=.99)return .99;const step=.001;const m=val%step;let a=val-m;if(Math.abs(m)*2>=step)a+=(m>0?step:-step);return parseFloat(a.toFixed(5))}
|
|
function levelToGain(level){return Math.pow(level,3)}
|
|
function slider(i){return qs("#s"+i)}
|
|
function sliderViewGet(i){return parseFloat(slider(i).value)}
|
|
function sliderViewSet(i,v){slider(i).value=v}
|
|
function afterLevelsChanged(){if(!state.bANIMATE)updateDocumentLinks()}
|
|
function setLevel(i,v,source="program"){state.currentLevel[i]=quantizeLevel(v);if(state.bFINISHEDLOADING)setStemGain(i,state.currentLevel[i],state.fAUDIOFADETIME);if(source!=="user")sliderViewSet(i,state.currentLevel[i]);afterLevelsChanged()}
|
|
function setLevels(vector,source="program"){for(let i=0;i<10;i++)setLevel(i,vector[i],source);if(source!=="anim")afterLevelsChanged()}
|
|
function getCurrentLevelsFromSliders(){for(let i=0;i<10;i++)state.currentLevel[i]=sliderViewGet(i)}
|
|
function setCurrentLevelsToSliders(){setLevels(state.currentLevel,"program")}
|
|
function setAllLevels(){for(let i=0;i<10;i++)setStemGain(i,state.currentLevel[i],state.fAUDIOFADETIME)}
|
|
function normalizeLevels(vector){const boostTable=[0,1.5,1.36,1.23,1.12,1.13,1.02,1,.98,.95,.9];let max=0;for(let i=0;i<10;i++)if(vector[i]>max)max=vector[i];let active=0;for(let i=0;i<10;i++)if(vector[i]>max*.8)active++;if(active>0&&max>0){const mult=state.fTARGETSLIDERLEVEL/max*boostTable[active];for(let i=0;i<10;i++)vector[i]=Math.min(vector[i]*mult,.9)}return vector}
|
|
function setPreset(...args){let text=args[10];if(state.bMUTE)toggleMute();let levels=args.slice(0,10);if(!state.bCALIBRATE)levels=normalizeLevels(levels);for(let i=0;i<10;i++)state.currentLevel[i]=levels[i];saveRandomExchange();if(!state.bANIMATE){setCurrentLevelsToSliders();msg(text||"")}}
|
|
function defaultLevels(){return cfg.defaultCurve||Array(10).fill(state.fTARGETSLIDERLEVEL)}
|
|
function resetSliders(){setPreset(...defaultLevels(),"back to default")}
|
|
function randomSettings(){const r=[];for(let i=0;i<10;i++)r[i]=Math.random()>.3?Math.random():0;setPreset(...r,"random")}
|
|
function slidersUp(){if(state.bMUTE)toggleMute();for(let i=0;i<10;i++){state.currentLevel[i]=Math.min(.99,state.currentLevel[i]*state.fLevelMultiplier);state.savedLevel[i]=Math.min(.99,state.savedLevel[i]*state.fLevelMultiplier);state.randomCounter=0;if(state.bANIMATE){state.animationProfileLow[i]=Math.min(.99,state.animationProfileLow[i]*state.fLevelMultiplier);state.animationProfileHigh[i]=Math.min(.99,state.animationProfileHigh[i]*state.fLevelMultiplier)}}setCurrentLevelsToSliders()}
|
|
function slidersDown(){if(state.bMUTE)toggleMute();for(let i=0;i<10;i++){state.currentLevel[i]/=state.fLevelMultiplier;state.savedLevel[i]/=state.fLevelMultiplier;state.randomCounter=0;if(state.bANIMATE){state.animationProfileLow[i]/=state.fLevelMultiplier;state.animationProfileHigh[i]/=state.fLevelMultiplier}}setCurrentLevelsToSliders()}
|
|
function createAudioContext(){const AC=window.AudioContext||window.webkitAudioContext;if(!AC)return false;state.context=new AC();return true}
|
|
function assignSources(){state.fileExt=".ogg";state.meditationBell=new Audio("./audio/bell"+state.fileExt);state.meditationBell.preload="auto";state.sourceFileA=cfg.srcA.map(u=>u+state.fileExt);state.sourceFileB=cfg.srcB.map(u=>u+state.fileExt)}
|
|
let onLoadProgress=null,onStemDecoded=null;
|
|
function loadAllSounds(progressCb,decodedCb){onLoadProgress=progressCb;onStemDecoded=decodedCb;for(let i=0;i<10;i++)loadWebAudioSound(state.sourceFileA[i],i);for(let i=0;i<10;i++)loadWebAudioSound(state.sourceFileB[i],i+10)}
|
|
function loadWebAudioSound(url,i){const req=new XMLHttpRequest();req.open("GET",url,true);req.responseType="arraybuffer";req.onload=()=>{state.context.decodeAudioData(req.response,d=>{if(d.numberOfChannels===1){const stereo=state.context.createBuffer(2,d.length,d.sampleRate),mono=d.getChannelData(0);stereo.copyToChannel(mono,0);stereo.copyToChannel(mono,1);state.bufferList[i]=stereo}else state.bufferList[i]=d;countIn(i)},()=>msg("decode failed: "+url))};req.onerror=()=>msg("load failed: "+url);req.onprogress=e=>{if(!req.NextSecond)req.NextSecond=0;if(Date.now()<req.NextSecond)return;req.NextSecond=Date.now()+state.dlMonitorThrottle;if(state.dlMonitorThrottle<1000)state.dlMonitorThrottle++;dlMonitor(i,e,req,url)};state.bDOWNLOADED[i]=0;state.iSTARTED[i]=Date.now();req.send()}
|
|
function dlMonitor(index,report,initiator,url){const timeOut=1e4;if(Date.now()-state.iSTARTED[index]>timeOut&&state.iLOADED[index]==report.loaded){initiator.abort();loadWebAudioSound(url,index);return}if(!report.lengthComputable)return;state.iTOTAL[index]=report.total;state.iLOADED[index]=report.loaded;let grand=0,loaded=0;for(let i=0;i<20;i++)if(state.iTOTAL[i]){grand+=state.iTOTAL[i];loaded+=state.iLOADED[i]}if(grand>state.serverTotal)state.serverTotal=grand;if(onLoadProgress)onLoadProgress(loaded,grand,state.serverTotal)}
|
|
function countIn(index){state.bDOWNLOADED[index]=1;++state.launchCounter;if(onStemDecoded)onStemDecoded(state.launchCounter)}
|
|
function makeSource(buffer,rate){const src=state.context.createBufferSource();src.buffer=buffer;src.playbackRate.value=rate;return src}
|
|
function buildAudioGraph(){state.masterGain=state.context.createGain();state.masterGain.gain.value=.5;for(let i=0;i<10;i++){state.sourceA[i]=makeSource(state.bufferList[i],state.playbackFactor[i]);state.gainNode[i]=state.context.createGain();state.gainNode[i].gain.value=0;state.sourceA[i].connect(state.gainNode[i]).connect(state.masterGain)}for(let i=0;i<10;i++){state.sourceB[i]=makeSource(state.bufferList[i+10],state.playbackFactor[i]);state.sourceB[i].connect(state.gainNode[i]).connect(state.masterGain)}state.dynCompressor=new DynamicsCompressorNode(state.context,{threshold:-12,knee:6,ratio:10,attack:.05,release:2});state.dynCompressor.connect(state.context.destination);state.splitterNode=state.context.createChannelSplitter(2);state.mergerNode=state.context.createChannelMerger(2);state.midGain=state.context.createGain();state.midGain.gain.value=.5;state.sideGain=state.context.createGain();state.sideGain.gain.value=.5*state.fSTEREOWIDTH;state.negateR=state.context.createGain();state.negateR.gain.value=-1;state.negateSide=state.context.createGain();state.negateSide.gain.value=-1;state.masterGain.connect(state.splitterNode);state.splitterNode.connect(state.midGain,0);state.splitterNode.connect(state.midGain,1);state.splitterNode.connect(state.sideGain,0);state.splitterNode.connect(state.negateR,1);state.negateR.connect(state.sideGain);state.midGain.connect(state.mergerNode,0,0);state.midGain.connect(state.mergerNode,0,1);state.sideGain.connect(state.mergerNode,0,0);state.sideGain.connect(state.negateSide);state.negateSide.connect(state.mergerNode,0,1);state.mergerNode.connect(state.dynCompressor)}
|
|
function compressorReduction(){return state.dynCompressor&&typeof state.dynCompressor.reduction==="number"?-state.dynCompressor.reduction:0}
|
|
function attachMasterAnalyser(){state.analyser=state.context.createAnalyser();state.analyser.fftSize=32;state.analyser.smoothingTimeConstant=0;state.mergerNode.connect(state.analyser);return state.analyser}
|
|
function stemAnalyserFor(i){if(!state.stemAnalyser[i]){state.stemAnalyser[i]=state.context.createAnalyser();state.stemAnalyser[i].fftSize=512}return state.stemAnalyser[i]}
|
|
function connectStemAnalysers(on){for(let i=0;i<10;i++){const method=on?"connect":"disconnect";try{state.sourceA[i][method](state.stemAnalyser[i])}catch(e){}try{state.sourceB[i][method](state.stemAnalyser[i])}catch(e){}}}
|
|
function setStemGain(i,level,fadeTime){state.gainNode[i].gain.setTargetAtTime(levelToGain(level),state.context.currentTime,fadeTime)}
|
|
function audioContextState(){return state.context.state}
|
|
function resumeAudioContext(force){if(force||state.context.state!=="running")state.context.resume()}
|
|
function suspendAudioContext(){state.context.suspend()}
|
|
function suspendEngine(){if(state.context.state!=="suspended")state.context.suspend();stopScheduler()}
|
|
function fadeMaster(target,seconds,onDone){const now=state.context.currentTime;state.masterGain.gain.cancelScheduledValues(now);state.masterGain.gain.setValueAtTime(state.masterGain.gain.value,now);state.masterGain.gain.linearRampToValueAtTime(target,now+seconds);clearTimeout(state.fadeTimeOut);state.fadeTimeOut=setTimeout(onDone,seconds*1e3)}
|
|
function duckAudio(levelRatio,fadeTime){if(state.isDucking)return;state.isDucking=true;const now=state.context.currentTime;state.preDuckGain=state.masterGain.gain.value;state.masterGain.gain.cancelScheduledValues(now);state.masterGain.gain.setValueAtTime(state.preDuckGain,now);state.masterGain.gain.linearRampToValueAtTime(state.preDuckGain*levelRatio,now+fadeTime)}
|
|
function unduckAudio(fadeTime){if(!state.isDucking)return;state.isDucking=false;const now=state.context.currentTime;state.masterGain.gain.cancelScheduledValues(now);state.masterGain.gain.setValueAtTime(state.masterGain.gain.value,now);state.masterGain.gain.linearRampToValueAtTime(state.preDuckGain,now+fadeTime)}
|
|
function setWidthGains(width){state.midGain.gain.setTargetAtTime(.5,state.context.currentTime,.2);state.sideGain.gain.setTargetAtTime(.5*width,state.context.currentTime,.2)}
|
|
const SCHEDULE_LOOKAHEAD=.5,SCHEDULE_INTERVAL=100;
|
|
function startScheduler(){if(state.schedulerTimer)return;state.schedulerTimer=setInterval(schedulerTick,SCHEDULE_INTERVAL)}
|
|
function stopScheduler(){if(state.schedulerTimer){clearInterval(state.schedulerTimer);state.schedulerTimer=null}}
|
|
function halfPeriod(i){return Math.round(state.bufferList[i].duration*8)/16*state.stretch[i]/state.playbackFactor[i]}
|
|
function computeIntervals(){for(let i=0;i<10;i++){const durA=Math.round(state.sourceA[i].buffer.duration*8)/8,durB=Math.round(state.sourceB[i].buffer.duration*8)/8;state.interval[i]=(durA+durB)/2*state.stretch[i]/state.playbackFactor[i]}}
|
|
function schedulerTick(){const now=state.context.currentTime;if(state.lastSchedulerTime>0&&now-state.lastSchedulerTime>2){for(let i=0;i<10;i++){if(state.stretch[i]===0)continue;const restartTime=Math.ceil(now+.1);state.nextA[i]=restartTime;state.nextB[i]=restartTime+halfPeriod(i)}}state.lastSchedulerTime=now;for(let i=0;i<10;i++){if(state.stretch[i]===0)continue;while(state.nextA[i]<now+SCHEDULE_LOOKAHEAD){scheduleDue(i,0,state.nextA[i],now);state.nextA[i]+=state.interval[i]}while(state.nextB[i]<now+SCHEDULE_LOOKAHEAD){scheduleDue(i,1,state.nextB[i],now);state.nextB[i]+=state.interval[i]}}}
|
|
function scheduleDue(i,side,when,now){if(when>=now)scheduleStem(i,side,when);else{const offset=(now-when)*state.playbackFactor[i];if(offset<state.bufferList[i+side*10].duration)scheduleStem(i,side,now,offset)}}
|
|
function scheduleStem(item,side,when,offset=0){const src=state.context.createBufferSource();src.buffer=state.bufferList[item+side*10];src.playbackRate.value=state.playbackFactor[item];src.connect(state.gainNode[item]);if(state.bWAVEVISUALIZER&&state.stemAnalyser[item])src.connect(state.stemAnalyser[item]);src.onended=()=>{try{src.disconnect()}catch(e){}src.onended=null};src.start(when,offset);if(side===0)state.sourceA[item]=src;else state.sourceB[item]=src}
|
|
function scheduleA(item,when,offset){scheduleStem(item,0,when,offset)}
|
|
function scheduleB(item,when,offset){scheduleStem(item,1,when,offset)}
|
|
function playAllSounds(){for(let i=0;i<10;i++)startWebAudio(i)}
|
|
function startWebAudio(i){const wallNow=Date.now(),nextBeat=Math.ceil(wallNow/1e3)*1e3,startTime=state.context.currentTime+(nextBeat-wallNow)/1e3;if(state.stretch[i]===0){state.sourceA[i].loop=1;state.sourceB[i].loop=1;state.sourceB[i].playbackRate.value=state.playbackFactor[i];state.sourceA[i].start(startTime);state.sourceB[i].start(startTime);return}state.nextA[i]=startTime;state.nextB[i]=startTime+halfPeriod(i);scheduleA(i,state.nextA[i]);state.nextA[i]+=state.interval[i];scheduleB(i,state.nextB[i]);state.nextB[i]+=state.interval[i]}
|
|
function restartWebAudio(i){const fadeTime=.05,now=state.context.currentTime,originalGain=levelToGain(state.currentLevel[i]);state.gainNode[i].gain.cancelScheduledValues(now);state.gainNode[i].gain.setValueAtTime(state.gainNode[i].gain.value,now);state.gainNode[i].gain.linearRampToValueAtTime(0,now+fadeTime);setTimeout(()=>{try{state.sourceA[i].onended=null}catch(e){}try{state.sourceB[i].onended=null}catch(e){}try{state.sourceA[i].disconnect()}catch(e){}try{state.sourceB[i].disconnect()}catch(e){}if(state.stretch[i]===0){for(const side of [0,1]){const src=makeSource(state.bufferList[i+side*10],state.playbackFactor[i]);src.loop=1;src.connect(state.gainNode[i]);src.start(0);if(side===0)state.sourceA[i]=src;else state.sourceB[i]=src}}else{const restartTime=Math.ceil(state.context.currentTime+.05);state.nextA[i]=restartTime;state.nextB[i]=restartTime+halfPeriod(i)}const resumeTime=state.context.currentTime;state.gainNode[i].gain.setValueAtTime(0,resumeTime);state.gainNode[i].gain.linearRampToValueAtTime(originalGain,resumeTime+fadeTime)},fadeTime*1e3)}
|
|
function setPlaybackFactors(){computeIntervals();for(let i=0;i<10;i++)restartWebAudio(i)}
|
|
function playOneShot(stem){state.playbackFactor[stem]=state.pitchTable[Math.floor(Math.random()*state.pitchTable.length)];let side,duration;if(state.lastPlayedOS[stem]){scheduleB(stem,state.context.currentTime);duration=state.sourceB[stem].buffer.duration;side="a"}else{scheduleA(stem,state.context.currentTime);duration=state.sourceA[stem].buffer.duration;side="b"}state.lastPlayedOS[stem]=1-state.lastPlayedOS[stem];return{side,duration:duration*1e3/state.playbackFactor[stem]}}
|
|
function toggleMute(){state.bMUTE=!state.bMUTE;if(state.bMUTE){if(state.iTimer==0)fadeOut(1,30);else fadeOut(1,.5);if(state.iMTimer>0)setMeditationBell(0);updateButtons();stopScheduler();msg("paused")}else{if(state.iTimer==0)fadeOut(0,15);else fadeOut(0,1);updateButtons();resumeContext();updatefftData();startScheduler();msg("now playing")}if(state.iTimer==0)state.iTimer=-1;updateDocumentLinks()}
|
|
function updateButtons(){qs("#playBtn").textContent=state.bMUTE?"play":"pause";qs("#animBtn").classList.toggle("active",!!state.bANIMATE)}
|
|
function fadeOut(out,duration_s){let target=0;if(out==0)target=state.fMASTERGAIN;if(state.bANIMATE&&out==1)clearTimeout(state.modulationTimeout);fadeMaster(target,duration_s,()=>{if(out==1)suspendEngine();else{startScheduler();if(state.bANIMATE)modulationRandom()}})}
|
|
function resumeContext(){resumeAudioContext(true);startScheduler();if(state.bANIMATIONONSTART){startModulation();state.bANIMATIONONSTART=0}msg("now playing")}
|
|
function applyAutoplayPolicy(){msg("click play if browser suspended audio");if(audioContextState()=="suspended"){suspendAudioContext();state.bSUSPENDED=1}else nowPlaying()}
|
|
function nowPlaying(){msg("ready");updateButtons()}
|
|
function progressDots(done,percent,remaining,unknown){return`${"•".repeat(done)} ${percent}% ${"•".repeat(Math.max(0,remaining))}${unknown===undefined?"":" "+"•".repeat(Math.max(0,unknown))}`}
|
|
function showLoadProgress(loaded,grand,server){const loadedMB=Math.floor(loaded/1048576),grandMB=Math.floor(grand/1048576),serverMB=Math.floor(server/1048576),percent=server?Math.round(loaded/server*100):0;msg("loading "+progressDots(loadedMB,percent,grandMB-loadedMB,serverMB-grandMB))}
|
|
function showDecodeProgress(count){if(count==20)finishedLoading();else msg("greasing sliders "+progressDots(count,Math.round(count/20*100),20-count))}
|
|
function playBell(volume=1){if(state.meditationBell&&state.meditationBell.paused&&!state.bMUTE){state.meditationBell.volume=Math.min(1,Math.max(.1,state.smoothedMasterLevel*2*volume));const a=.001,b=10;duckAudio(.66,a);setTimeout(()=>{state.meditationBell.onended=()=>unduckAudio(b);state.meditationBell.play().catch(()=>unduckAudio(b))},a*1e3)}}
|
|
function playOS(stem){const played=playOneShot(stem);msg(`${bands[stem]} • ${played.side} • ${state.playbackFactor[stem].toFixed(3)}`);clearTimeout(state.timeOutOS[stem]);state.timeOutOS[stem]=setTimeout(()=>{if(state.bOSAUTO)playOS(Math.floor(Math.random()*10))},played.duration)}
|
|
function setStereoWidth(widthFactor){state.fSTEREOWIDTH=widthFactor;updateDocumentLinks();setWidthGains(widthFactor);msg("stereo width "+Math.round(widthFactor*100)+"%")}
|
|
function userSettingsCookieName(){return state.uIDS?"uSET_"+state.uIDS:"uSET_unlogged"}
|
|
function readUserSettings(){const tmp=readCookie(userSettingsCookieName());return tmp!=null?JSON.parse(tmp):{}}
|
|
function writeUserSettings(userData){createCookie(userSettingsCookieName(),JSON.stringify(userData),36500)}
|
|
function initTuning(){for(let i=0;i<10;i++)state.playbackFactor[i]=1;const args=getUrlVars();if(args.d!==undefined)state.detune=args.d.replace(/(?!^-)[^0-9.]/g,"");computePlaybackFactorsFromDetune()}
|
|
function computePlaybackFactorsFromDetune(){const isScalar=state.detune<999;for(let i=0;i<10;i++){if(isScalar)state.playbackFactor[i]=Math.pow(2,state.detune/12);else{const step=parseInt(state.detune.toString().substring(i*2,i*2+2))-50;state.playbackFactor[i]=Math.pow(2,step/12)}}}
|
|
function updatePlaybackRate(){computePlaybackFactorsFromDetune();setPlaybackFactors()}
|
|
function tune(factor,bRelative=false){if(bRelative){const currentFactor=Math.pow(2,state.detune/12);factor=currentFactor*factor}state.detune=Math.round(Math.log2(factor)*12*1e3)/1e3;updatePlaybackRate();updateDocumentLinks();msg("now playing at "+Math.round(factor*100)/100+"x speed")}
|
|
function transposeDetune(amount,limitMin,limitMax){const str=state.detune.toString();if(str.length<4){const newValue=parseFloat(state.detune)+amount;if(newValue>=limitMin&&newValue<=limitMax){state.detune=newValue;const value=Math.round(Math.pow(2,state.detune/12)*100)/100;msg(state.detune===0?"original speed":"master tape "+value+"x")}else msg("no further speed change allowed")}else if(str.length===20){let nd="";for(let i=0;i<20;i+=2){const nv=parseInt(str.substring(i,i+2),10)+amount;if(nv>=50+limitMin&&nv<=50+limitMax)nd+=String(nv).padStart(2,"0");else return msg("tape speed out of range")}state.detune=nd}updatePlaybackRate();updateDocumentLinks()}
|
|
function tuneDown(dec){transposeDetune(-dec,-48,36)}
|
|
function tuneUp(inc){transposeDetune(inc,-48,36)}
|
|
function mixedTune(keyScale=null){let str="";for(let i=0;i<10;i++){let s;if(Array.isArray(keyScale)&&keyScale.length>0)s=keyScale[Math.floor(Math.random()*keyScale.length)];else s=Math.round(Math.random()*7)*(Math.random()<.5?-1:1);str+=String(50+s).padStart(2,"0")}state.detune=str;updateDocumentLinks();updatePlaybackRate();msg("alternate tuning")}
|
|
const ANIMATION_SPEEDS=[.125,.25,.5,1,2,4,8],ANIMATION_MODES=["s","h","i","d","t","q"];
|
|
function cycleValue(entries,current){const idx=entries.findIndex(e=>e==current);return idx<0?current:entries[(idx+1)%entries.length]}
|
|
function setAnimationSpeed(value){state.iAnimationFactor=value===undefined?cycleValue(ANIMATION_SPEEDS,state.iAnimationFactor):value;updateDocumentLinks();msg("animation speed "+state.iAnimationFactor)}
|
|
function setAnimationMode(value){state.iAnimationMode=value===undefined?cycleValue(ANIMATION_MODES,state.iAnimationMode):value;updateDocumentLinks();msg("animation mode "+state.iAnimationMode)}
|
|
function setAnimationProfile(type){state.bANIMATIONUSERPROFILESET=1;if(type=="lo"){state.animationProfileLow=state.currentLevel.slice(0);msg("saved low bound")}if(type=="hi"){state.animationProfileHigh=state.currentLevel.slice(0);msg("saved high bound")}updateDocumentLinks()}
|
|
function loadAnimationProfile(type){if(type=="lo"){state.currentLevel=state.animationProfileLow.slice(0);msg("low profile")}if(type=="hi"){state.currentLevel=state.animationProfileHigh.slice(0);msg("high profile")}setCurrentLevelsToSliders()}
|
|
function deriveAnimationProfile(){for(let i=0;i<10;i++){state.animationProfileHigh[i]=Math.min(state.currentLevel[i]*1.25,.99);state.animationProfileLow[i]=state.currentLevel[i]*.5}}
|
|
function toggleModulation(noMsg){state.bANIMATE=!state.bANIMATE;if(state.bANIMATE){if(!state.bANIMATIONUSERPROFILESET){deriveAnimationProfile();let sum=0;for(let i=0;i<10;i++)sum+=state.currentLevel[i];if(sum==0)for(let i=0;i<10;i++)state.animationProfileHigh[i]=state.fTARGETSLIDERLEVEL*1.25}state.savedLevel=state.currentLevel.slice(0);state.savedCurrentLevel=state.currentLevel.slice(0);state.nzSliderIndex=[];state.mmMin=1;state.mmMax=0;for(let i=0;i<10;i++)if(state.savedLevel[i]>0){if(state.savedLevel[i]<state.mmMin)state.mmMin=state.savedLevel[i];if(state.savedLevel[i]>state.mmMax)state.mmMax=state.savedLevel[i];state.nzSliderIndex.push(i)}updateButtons();modulationRandom()}else{clearTimeout(state.modulationTimeout);updateButtons();setCurrentLevelsToSliders();state.randomCounter=0}if(noMsg!=1)msg(state.bANIMATE?"sliders are on the move":"slider animation stopped");updateDocumentLinks()}
|
|
function startModulation(){if(!state.bANIMATE)toggleModulation(1)}
|
|
function getRandomIndices(count,eligible){count=Math.min(count,eligible.length);const copy=eligible.slice();for(let i=copy.length-1;i>copy.length-1-count;i--){const j=Math.floor(Math.random()*(i+1));[copy[i],copy[j]]=[copy[j],copy[i]]}return copy.slice(-count)}
|
|
function rotateAnimatedIndices(count,eligible){if(count===1||!Array.isArray(state.animatedIndices)||state.animatedIndices.length!==count)return getRandomIndices(count,eligible);const ni=state.animatedIndices.slice(),avail=eligible.filter(x=>!state.animatedIndices.includes(x));if(avail.length>0){const pos=Math.floor(Math.random()*count);ni[pos]=avail[Math.floor(Math.random()*avail.length)]}return ni}
|
|
function drawAnimationTarget(){for(let i=0;i<10;i++)state.randomLevel[i]=state.savedLevel[i];if(state.iAnimationMode=="s"){for(let i=0;i<10;i++)state.randomLevel[i]=state.animationProfileLow[i]+Math.random()*(state.animationProfileHigh[i]-state.animationProfileLow[i])}if(state.iAnimationMode=="h"){for(let i=0;i<10;i++)state.randomLevel[i]=Math.random()<.5?state.animationProfileHigh[i]:state.animationProfileLow[i]}if(["i","d","t","q"].includes(state.iAnimationMode)){const eligible=[];for(let i=0;i<10;i++)if(state.animationProfileLow[i]!==state.animationProfileHigh[i])eligible.push(i);const count={i:1,d:2,t:3,q:4}[state.iAnimationMode];state.animatedIndices=rotateAnimatedIndices(count,eligible);for(let i=0;i<10;i++)state.randomLevel[i]=state.animatedIndices.includes(i)?Math.max(state.animationProfileLow[i],state.animationProfileHigh[i]):Math.min(state.animationProfileLow[i],state.animationProfileHigh[i])}}
|
|
function modulationRandom(){state.iCurrentAnimationSpeed=state.iINITIALANIMATIONSPEED/state.iAnimationFactor;const nCycle=50;if(state.randomCounter==0){state.savedCurrentLevel=state.currentLevel.slice(0);drawAnimationTarget()}for(let i=0;i<10;i++)if(state.selSliders[i])state.currentLevel[i]=state.savedCurrentLevel[i]+state.randomCounter*(state.randomLevel[i]-state.savedCurrentLevel[i])/nCycle;setLevels(state.currentLevel,"anim");if(++state.randomCounter==nCycle+1)state.randomCounter=0;clearTimeout(state.modulationTimeout);state.modulationTimeout=setTimeout(modulationRandom,state.iCurrentAnimationSpeed*1e3/nCycle)}
|
|
function saveRandomExchange(){state.savedLevel=state.currentLevel.slice(0);state.randomLevel=state.currentLevel.slice(0);state.randomCounter=0}
|
|
const TIMER_STEPS=[[1,"timer set to 1 minute"],[5,"timer set to 5 minutes"],[10,"timer set to 10 minutes"],[15,"timer set to 15 minutes"],[20,"timer set to 20 minutes"],[25,"pomodoro 25 minutes"],[30,"timer set to 30 minutes"],[60,"timer set to 1 hour"],[120,"timer set to 2 hours"],[240,"timer set to 4 hours"],[480,"timer set to 8 hours"]];
|
|
function setTimer(time){if(time===undefined||isNaN(time)){const elapsed=(Date.now()-state.epoch);state.iTimer=Math.max(0,state.iTimer-elapsed/6e4+2);const step=TIMER_STEPS.find(s=>state.iTimer<s[0]);if(step){state.iTimer=step[0];msg(step[1])}else{state.iTimer=-1;msg("timer disabled")}}else if(time==0){state.iTimer=0;updateTimer()}else{state.iTimer=+time;msg("timer set to "+time+" min")}state.URLtimer=state.iTimer==-1?"":"&tm="+state.iTimer;updateDocumentLinks();qs("#timerInput").value=state.iTimer>0?state.iTimer:"";state.epoch=Date.now();clearTimeout(state.timerTimeout);state.timerTimeout=setTimeout(updateTimer,1e4)}
|
|
function updateTimer(){if(state.iTimer<0)return;const elapsed=(Date.now()-state.epoch),remaining=state.iTimer*60-elapsed/1e3;if(remaining<=0){state.iTimer=0;toggleMute();qs("#timerInput").value=""}else{qs("#timerInput").value=Math.ceil(remaining/60);clearTimeout(state.timerTimeout);state.timerTimeout=setTimeout(updateTimer,1e4)}}
|
|
const BELL_STEPS=[[1,1],[5,5],[10,10],[15,15],[20,20],[25,25],[30,30],[60,60],[99999,"5:60"]];
|
|
function setMeditationBell(time){playBell();clearTimeout(state.meditationInterval);clearTimeout(state.meditationInterval2);if(time===undefined){const step=BELL_STEPS.find(s=>state.iMTimer<s[0]);if(step){state.iMTimer=step[0];time=step[1]}else{state.iMTimer=-1;time=-1}}const bellInput=qs("#bellInput");if(isNaN(time)){time=String(time).replace("?",":").replace("-",":");let parts=time.split(":");if(parts.length==2){if(isNaN(parts[0])||isNaN(parts[1]))return;const minTime=Math.min(parts[0],parts[1]),maxTime=Math.max(parts[0],parts[1]),nextBell=Math.round(minTime+Math.random()*(maxTime-minTime));state.meditationInterval=setTimeout(()=>setMeditationBell(time),nextBell*6e4);msg("random bells "+minTime+" to "+maxTime+" min");bellInput.value="r"}else{parts=time.split("+");if(parts.length==2){if(isNaN(parts[0])||isNaN(parts[1]))return;const firstTime=parts[0],secondTime=parts[1],totalTime=parseFloat(firstTime)+parseFloat(secondTime);state.meditationInterval=setTimeout(()=>playBell(),firstTime*6e4);state.meditationInterval2=setTimeout(()=>setMeditationBell(time),totalTime*6e4);msg("dual session bell "+firstTime+"•"+secondTime);bellInput.value="d"}}}else{state.iMTimer=+time;if(state.iMTimer>0){msg(state.iMTimer==1?"bell every minute":state.iMTimer==25?"pomodoro bell":"bell every "+time+" min");bellInput.value=state.iMTimer}else{msg("bell disabled");bellInput.value=""}if(state.iMTimer>0)state.meditationInterval=setInterval(()=>playBell(),state.iMTimer*6e4)}state.URLbell=time==-1?"":"&bl="+encodeURIComponent(time);updateDocumentLinks()}
|
|
function buildAnimationParams(){let seg="&a="+state.iAnimationFactor+"&am="+state.iAnimationMode;if(state.bANIMATIONUSERPROFILESET)seg+="&apl="+packVector(state.animationProfileLow)+"&aph="+packVector(state.animationProfileHigh);if(state.bANIMATE)seg+="&astart=1";return seg}
|
|
function updateDocumentLinks(){clearTimeout(updateDocumentLinks._timer);updateDocumentLinks._timer=setTimeout(()=>{const stripped=location.origin+location.pathname;state.URLlevels="l="+packVector(state.currentLevel);state.URLanimate=buildAnimationParams();state.URLmagic=cfg.magicParam;state.URLdetune=state.detune?"&d="+state.detune:"";state.URLwidth=typeof state.fSTEREOWIDTH!=="undefined"&&state.fSTEREOWIDTH!=1?"&w="+state.fSTEREOWIDTH:"";state.URLmute=state.bMUTE?"&mt=1":"";state.URLtitle="&title="+encodeURIComponent("white rain");const q=state.URLlevels+state.URLanimate+state.URLmagic+state.URLdetune+state.URLwidth+state.URLmute+state.URLbell+state.URLtimer+state.URLtitle;state.cloneURL=stripped+"?"+q+"&c=1";state.cookieURL=q.substring(2);state.appShareURL=stripped+"?"+q},10)}
|
|
function saveCurrentLevelsToCookie(tag){tag=tag===undefined?"CAL":tag;const userData=readUserSettings();userData[tag]=state.cookieURL;writeUserSettings(userData);if(tag!="CAL")msg("saved in browser")}
|
|
function loadCurrentLevelsFromCookieAndSetSliders(tag){tag=tag===undefined?"CAL":tag;const userData=readUserSettings();if(userData[tag]!=null){loadURLsettings(getUrlVars("?l="+userData[tag]),false);initTuning();updatePlaybackRate();msg(tag=="CAL"?"your personal curve":"loaded custom settings")}else msg(tag=="CAL"?"no calibration found":"no user setting found")}
|
|
function loadURLsettings(args,bNormalizeLevels=true){if(args.l!==undefined){unpackVector(args.l,state.currentLevel);if(bNormalizeLevels)state.currentLevel=normalizeLevels(state.currentLevel);setCurrentLevelsToSliders()}if(args.apl!==undefined){unpackVector(args.apl,state.animationProfileLow);state.bANIMATIONUSERPROFILESET=1}if(args.aph!==undefined){unpackVector(args.aph,state.animationProfileHigh);state.bANIMATIONUSERPROFILESET=1}if(args.a!==undefined)setAnimationSpeed(+args.a);if(args.am!==undefined)setAnimationMode(args.am);if(args.astart!==undefined){if(args.astart==1&&cfg.custom!=2)state.bANIMATIONONSTART=1}if(args.d!==undefined){state.detune=args.d;if(state.bFINISHEDLOADING)updatePlaybackRate()}if(args.tm!==undefined){const minutes=parseInt(args.tm,10);if(!isNaN(minutes))setTimer(minutes)}if(args.bl!==undefined){const minutes=decodeURIComponent(args.bl);if(minutes>0||typeof minutes==="string"&&minutes.includes(":"))setMeditationBell(minutes)}if(args.mt!==undefined){if(parseInt(args.mt,10)===1)toggleMute()}if(args.w!==undefined){const width=parseFloat(args.w);if(!isNaN(width))setStereoWidth(width)}}
|
|
function waveVisualizer(){state.bWAVEVISUALIZER=1-state.bWAVEVISUALIZER;if(state.bWAVEVISUALIZER){msg("visualizer on");for(let i=0;i<10;i++){const created=!state.stemAnalyser[i];const a=stemAnalyserFor(i);if(created&&i===0)state.samplesArray=new Uint8Array(a.frequencyBinCount)}connectStemAnalysers(true)}else{msg("visualizer off");connectStemAnalysers(false)}}
|
|
function startVisualizer(){state.fftCanvas=qs("#fft");state.fftCtx=state.fftCanvas.getContext("2d");attachMasterAnalyser();state.fftData=new Float32Array(state.analyser.frequencyBinCount);state.visualizerStartedAt=performance.now();updatefftData()}
|
|
function updatefftData(){if(!state.bMUTE)setTimeout(()=>requestAnimationFrame(updatefftData),100);if(state.bMUTE||!state.analyser)return;state.analyser.getFloatFrequencyData(state.fftData);if(!state.bLEVELHISTORYINIT){for(let j=0;j<state.levelHistorySize;j++)state.levelHistory.push(.3);state.bLEVELHISTORYINIT=true}let weighted=0;for(let j=0;j<state.fftData.length&&j<state.aWeighting.length;j++)weighted+=Math.pow(10,state.fftData[j]/20)*state.aWeighting[j];const instant=weighted*20;state.levelHistory[state.levelHistoryIndex]=instant;state.levelHistoryIndex=(state.levelHistoryIndex+1)%state.levelHistorySize;let sum=0;for(let j=0;j<state.levelHistorySize;j++)sum+=state.levelHistory[j];state.smoothedMasterLevel=sum/state.levelHistorySize;state.pos[0]=(state.fftData[0]+75)/2;state.pos[1]=(Math.max(state.fftData[2],state.fftData[3])+77)/2;state.pos[2]=(Math.max(state.fftData[5],state.fftData[6],state.fftData[7])+81)/2;state.pos[3]=(Math.max(state.fftData[9],state.fftData[10],state.fftData[11],state.fftData[12],state.fftData[13],state.fftData[14],state.fftData[15])+89)/2;let gr=compressorReduction();if(performance.now()-state.visualizerStartedAt<2e3)gr=0;let t=gr/6;t=t<0?0:t>1?1:t;const t2=Math.pow(t,.5),r=Math.round(121+144*t2),g=Math.round(120-25*t2),b=Math.round(116-56*t2),a=Math.min(1,.5+t),ctx=state.fftCtx,c=state.fftCanvas,color=`rgba(${r},${g},${b},${a})`;ctx.clearRect(0,0,c.width,c.height);ctx.beginPath();ctx.rect(0,0,c.width,c.height);ctx.fillStyle=color;ctx.fill();ctx.closePath();const order=[3,1,0,2];for(let j=0;j<4;j++){const i=order[j];if(state.pos[i]>state.posOld[i])state.posOld[i]=state.pos[i];else state.posOld[i]--;if(state.posOld[i]<0)state.posOld[i]=0;if(state.posOld[i]>15)state.posOld[i]=15;const h=state.posOld[i]*1.6,x=18+j*22,y=c.height-h-6,w=10,rad=3;ctx.beginPath();if(typeof ctx.roundRect==="function")ctx.roundRect(x,y,w,h,rad);else ctx.rect(x,y,w,h);ctx.fillStyle="#fff";ctx.fill();ctx.closePath()}qs("#stateTxt").textContent=state.bMUTE?"paused":"playing"}
|
|
function finishedLoading(){buildAudioGraph();computeIntervals();getCurrentLevelsFromSliders();playAllSounds();setAllLevels();startScheduler();state.bFINISHEDLOADING=1;startVisualizer();loadURLsettings(getUrlVars(),false);qs("#bg").style.backgroundImage=`url(./bg.jpg)`;updateDocumentLinks();applyAutoplayPolicy()}
|
|
function init(){if(!createAudioContext()){msg("web audio api not found");return}initTuning();msg("now loading local audio");assignSources();loadAllSounds(showLoadProgress,showDecodeProgress);setPreset(...defaultLevels());if(!state.bANIMATIONUSERPROFILESET)deriveAnimationProfile()}
|
|
function bindUi(){for(let i=0;i<10;i++){slider(i).addEventListener("input",e=>{if(state.bFINISHEDLOADING){state.movedSlider=String(i);setLevel(i,parseFloat(e.target.value),"user")}updateDocumentLinks()});qs("#os"+i).addEventListener("click",()=>state.bFINISHEDLOADING&&playOS(i));slider(i).style.color=colors[i]}qs("#playBtn").onclick=()=>state.bFINISHEDLOADING?toggleMute():resumeContext();qs("#resetBtn").onclick=resetSliders;qs("#downBtn").onclick=slidersDown;qs("#upBtn").onclick=slidersUp;qs("#animBtn").onclick=()=>toggleModulation();qs("#bellBtn").onclick=()=>setMeditationBell(qs("#bellInput").value||undefined);qs("#timerBtn").onclick=()=>setTimer(qs("#timerInput").value||undefined);qs("#bellInput").addEventListener("keydown",e=>{if(e.key==="Enter")setMeditationBell(qs("#bellInput").value)});qs("#timerInput").addEventListener("keydown",e=>{if(e.key==="Enter")setTimer(qs("#timerInput").value)});qsa("[data-preset]").forEach(el=>el.onclick=()=>setPreset(...presets[+el.dataset.preset][1],presets[+el.dataset.preset][0]));qsa("[data-width]").forEach(el=>el.onclick=()=>setStereoWidth(parseFloat(el.dataset.width)));qsa("[data-tune]").forEach(el=>el.onclick=()=>tune(parseFloat(el.dataset.tune)));qs("#slowerBtn").onclick=()=>tuneDown(1);qs("#fasterBtn").onclick=()=>tuneUp(1);qs("#alternateBtn").onclick=()=>mixedTune();qsa("[data-am]").forEach(el=>el.onclick=()=>setAnimationMode(el.dataset.am));qsa("[data-as]").forEach(el=>el.onclick=()=>setAnimationSpeed(parseFloat(el.dataset.as)));qs("#setLoBtn").onclick=()=>setAnimationProfile("lo");qs("#loadLoBtn").onclick=()=>loadAnimationProfile("lo");qs("#setHiBtn").onclick=()=>setAnimationProfile("hi");qs("#loadHiBtn").onclick=()=>loadAnimationProfile("hi");qs("#saveUrlBtn").onclick=async()=>{updateDocumentLinks();await navigator.clipboard.writeText(state.cloneURL);msg("url copied")};qs("#saveCookieBtn").onclick=()=>saveCurrentLevelsToCookie("RAIN2");qs("#loadCookieBtn").onclick=()=>loadCurrentLevelsFromCookieAndSetSliders("RAIN2");qs("#miniBtn").onclick=()=>{updateDocumentLinks();window.open(state.cloneURL,"_blank")};qs("#qrBtn").onclick=()=>{updateDocumentLinks();navigator.clipboard.writeText(state.appShareURL);msg("app url copied")};addEventListener("keydown",e=>{if(["INPUT","TEXTAREA"].includes(document.activeElement.tagName))return;const k=e.key.toLowerCase();if(k==="p"||e.code==="Space"){e.preventDefault();toggleMute()}else if(k==="r")resetSliders();else if(k==="a")toggleModulation();else if(k==="j")slidersDown();else if(k==="k")slidersUp();else if(k==="t")setTimer();else if(k==="l")setMeditationBell();else if(k==="v")waveVisualizer();else if(k==="w")setPreset(.18,.21,.24,.27,.3,.34,.38,.42,.46,.5,"white");else if(k==="n")setPreset(.3,.3,.3,.3,.3,.3,.3,.3,.3,.3,"pink");else if(k==="b")setPreset(.5,.46,.42,.38,.34,.3,.27,.24,.21,.18,"brown");else if(k==="z")setPreset(0,0,0,0,0,0,0,0,0,0,"zeroed");else if(k==="o"){if(state.bMUTE)setTimer(5);else setTimer(25);msg("pomodoro timer set")}else if(k==="?")randomSettings();else if(k==="y"){tune(432/440,1);msg("tuned to a4=432hz")}})}
|
|
bindUi();init();
|
|
</script>
|
|
</html> |