← back to Aurora Drift
Add sound + selection controller to Aurora: original ambient synth-pad drone (Sound toggle + Volume, key s) and a Soundscape selector (Solar Wind/Glacier/Boreal/Violet) that retunes the drone AND swaps the aurora palette
1c90e32e8182fc133acde349c763c14d6c6fbd1c · 2026-09-02 07:29:44 -0700 · Steve Abrams
Files touched
Diff
commit 1c90e32e8182fc133acde349c763c14d6c6fbd1c
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Sep 2 07:29:44 2026 -0700
Add sound + selection controller to Aurora: original ambient synth-pad drone (Sound toggle + Volume, key s) and a Soundscape selector (Solar Wind/Glacier/Boreal/Violet) that retunes the drone AND swaps the aurora palette
---
index.html | 50 +++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 47 insertions(+), 3 deletions(-)
diff --git a/index.html b/index.html
index cf4f353..206a2ad 100644
--- a/index.html
+++ b/index.html
@@ -52,6 +52,7 @@
.toggle{display:flex;flex-direction:column;gap:6px}
.cams{position:fixed;left:26px;bottom:20px;z-index:7;display:flex;gap:8px;flex-direction:column}
.cams .btn{justify-content:flex-start;min-width:118px}
+ .dock-sel{background:rgba(14,28,44,.7);color:var(--ink);border:1px solid var(--panel-edge);border-radius:8px;padding:6px 8px;font-size:12px;font-family:var(--serif);cursor:pointer}
.label-toggle{position:fixed;right:26px;bottom:20px;z-index:7}
.museum{position:fixed;right:26px;bottom:66px;z-index:8;width:min(88vw,360px);max-height:74vh;overflow:auto;
background:linear-gradient(180deg,rgba(10,20,32,.96),rgba(5,10,18,.96));border:1px solid var(--panel-edge);border-radius:14px;padding:20px 22px;box-shadow:0 18px 50px rgba(0,0,0,.6);font-size:13px;line-height:1.55;color:var(--dim)}
@@ -98,9 +99,12 @@
<div class="toggle">
<button class="btn" id="trailBtn" aria-pressed="true">✧ Motion Trails</button>
<button class="btn" id="reflectBtn" aria-pressed="true">◇ Reflection</button>
+ <button class="btn" id="soundBtn" aria-pressed="false">🔊 Sound</button>
</div>
</div>
<div class="group">
+ <div class="field"><label for="mood">Soundscape <span class="val" id="moodV">Solar Wind</span></label><select id="mood" class="dock-sel"><option value="solar">Solar Wind</option><option value="glacier">Glacier</option><option value="boreal">Boreal Green</option><option value="violet">Violet Veil</option></select></div>
+ <div class="field"><label for="vol">Volume <span class="val" id="volV">55%</span></label><input type="range" id="vol" min="0" max="100" value="55"></div>
<div class="field"><label for="tempo">Drift <span class="val" id="tempoV">Gentle</span></label><input type="range" id="tempo" min="0" max="200" value="100"></div>
<div class="field"><label for="aurora">Aurora <span class="val" id="auroraV">Vivid</span></label><input type="range" id="aurora" min="0" max="100" value="78"></div>
<div class="field"><label for="snow">Snowfall <span class="val" id="snowV">Light</span></label><input type="range" id="snow" min="0" max="100" value="45"></div>
@@ -136,8 +140,31 @@ const sky=document.getElementById('sky'), scene=document.getElementById('scene')
const sctx=sky.getContext('2d'), ctx=scene.getContext('2d');
const live=document.getElementById('live');
const S={ t:0, playing:true, reduced:false, trails:true, reflect:true,
- tempo:1.0, aurora:0.78, snow:0.45, stars:0.70, cam:'wide', W:0,H:0,DPR:1 };
+ tempo:1.0, aurora:0.78, snow:0.45, stars:0.70, cam:'wide',
+ sound:false, volume:0.55, mood:'solar', W:0,H:0,DPR:1 };
try{ window.__auroraS=S; }catch(e){}
+
+// soundscape moods — each retunes the drone AND swaps the aurora palette
+const MOODS={
+ solar: { name:'Solar Wind', cols:[[47,255,154],[47,230,255],[255,120,200],[255,210,110]], root:98 },
+ glacier:{ name:'Glacier', cols:[[120,220,255],[47,230,255],[160,200,255],[90,255,230]], root:110 },
+ boreal: { name:'Boreal Green', cols:[[47,255,154],[120,255,200],[47,230,255],[90,255,140]], root:82 },
+ violet: { name:'Violet Veil', cols:[[140,120,255],[200,120,255],[120,160,255],[170,110,230]], root:65 }
+};
+// ---- ambient synth pad (original, no audio files) ----
+let actx=null, master=null, padOsc=[], padGain=null, padLfo=null;
+function ensureAudio(){ if(actx) return true; try{ actx=new (window.AudioContext||window.webkitAudioContext)(); master=actx.createGain(); master.gain.value=S.volume; master.connect(actx.destination); return true; }catch(e){ return false; } }
+function stopPad(){ padOsc.forEach(o=>{try{o.stop();}catch(e){}}); padOsc=[]; if(padLfo){try{padLfo.stop();}catch(e){}} padLfo=null; padGain=null; }
+function startPad(){
+ if(!actx) return; stopPad();
+ const root=MOODS[S.mood].root, t=actx.currentTime;
+ const filt=actx.createBiquadFilter(); filt.type='lowpass'; filt.frequency.value=520; filt.Q.value=5;
+ const pg=actx.createGain(); pg.gain.value=0.0001; pg.connect(filt); filt.connect(master);
+ padLfo=actx.createOscillator(); const lg=actx.createGain(); padLfo.frequency.value=0.06; lg.gain.value=260; padLfo.connect(lg); lg.connect(filt.frequency); padLfo.start();
+ [1,1.5,2,3].forEach((mult,i)=>{ const o=actx.createOscillator(); o.type=i>2?'triangle':'sine'; o.frequency.value=root*mult*(1+(Math.random()-0.5)*0.004); o.connect(pg); o.start(); padOsc.push(o); });
+ pg.gain.linearRampToValueAtTime(0.14, t+2.5); // slow swell in
+ padGain=pg;
+}
if(window.matchMedia && matchMedia('(prefers-reduced-motion: reduce)').matches) S.reduced=true;
const announce=m=>{ live.textContent=m; };
function clamp(v,a,b){return Math.max(a,Math.min(b,v));}
@@ -172,7 +199,7 @@ function drawSky(){
}
// ---- aurora curtains ----
-const AUR_COLS=[[47,255,154],[47,230,255],[140,120,255],[120,255,200]];
+let AUR_COLS=MOODS[S.mood].cols; // palette follows the selected soundscape
function auroraPath(g, yTop, band, seed, camScale){
const W=S.W; const pts=[];
const step=W/22;
@@ -318,6 +345,23 @@ const trailBtn=document.getElementById('trailBtn');
trailBtn.addEventListener('click',()=>{ S.trails=!S.trails; trailBtn.setAttribute('aria-pressed',S.trails); if(!S.trails) ctx.clearRect(0,0,S.W,S.H); announce(S.trails?'Motion trails on.':'Motion trails off.'); });
const reflectBtn=document.getElementById('reflectBtn');
reflectBtn.addEventListener('click',()=>{ S.reflect=!S.reflect; reflectBtn.setAttribute('aria-pressed',S.reflect); announce(S.reflect?'Reflection on.':'Reflection off.'); });
+
+// sound + soundscape selection
+const soundBtn=document.getElementById('soundBtn');
+soundBtn.addEventListener('click',()=>{
+ if(!S.sound){ if(!ensureAudio()){ announce('Audio unavailable in this browser.'); return; } if(actx.state==='suspended') actx.resume(); startPad(); }
+ else { stopPad(); }
+ S.sound=!S.sound; soundBtn.setAttribute('aria-pressed',S.sound);
+ announce(S.sound?'Sound on — the drone drifts in.':'Sound off.');
+});
+const moodEl=document.getElementById('mood');
+moodEl.addEventListener('change',()=>{
+ S.mood=moodEl.value; AUR_COLS=MOODS[S.mood].cols; skyDirty=true;
+ document.getElementById('moodV').textContent=MOODS[S.mood].name;
+ if(S.sound) startPad(); // retune the drone
+ announce(MOODS[S.mood].name+' soundscape.');
+});
+document.getElementById('vol').addEventListener('input',e=>{ S.volume=+e.target.value/100; if(master) master.gain.value=S.volume; document.getElementById('volV').textContent=e.target.value+'%'; });
document.querySelectorAll('[data-cam]').forEach(b=>{ b.addEventListener('click',()=>{ S.cam=b.dataset.cam; document.querySelectorAll('[data-cam]').forEach(x=>x.setAttribute('aria-pressed',x===b)); announce(b.dataset.cam==='sky'?'Skyward view.':'The lake view.'); }); });
const labelBtn=document.getElementById('labelBtn'), museum=document.getElementById('museum');
@@ -327,7 +371,7 @@ function toggleChrome(){ const hidden=stageEl.classList.toggle('hide-chrome'); c
chromeBtn.addEventListener('click',toggleChrome);
stageEl.addEventListener('pointerdown',e=>{ if(e.target.closest('button,input,select,a,.dock,.cams,.museum,.label-toggle')) return; const r=scene.getBoundingClientRect(); shoot(e.clientX-r.left, e.clientY-r.top); });
addEventListener('keydown',e=>{ if(e.target.matches('input,button')&&(e.key===' '||e.key==='Enter')) return;
- switch(e.key.toLowerCase()){ case ' ':e.preventDefault();playBtn.click();break; case 'm':reduceBtn.click();break; case 't':trailBtn.click();break; case 'r':reflectBtn.click();break; case 'c':toggleChrome();break; case 'l':labelBtn.click();break; case '1':document.querySelector('[data-cam="wide"]').click();break; case '2':document.querySelector('[data-cam="sky"]').click();break; }
+ switch(e.key.toLowerCase()){ case ' ':e.preventDefault();playBtn.click();break; case 'm':reduceBtn.click();break; case 't':trailBtn.click();break; case 'r':reflectBtn.click();break; case 's':soundBtn.click();break; case 'c':toggleChrome();break; case 'l':labelBtn.click();break; case '1':document.querySelector('[data-cam="wide"]').click();break; case '2':document.querySelector('[data-cam="sky"]').click();break; }
});
resize(); requestAnimationFrame(frame);
← f76741e Aurora Drift: single-file animated northern-lights gallery (
·
back to Aurora Drift
·
All lights go to the beat: gentle rhythmic tremolo on the am 608ae8a →