[object Object]

← back to Aurora Drift

All lights go to the beat: gentle rhythmic tremolo on the ambient pad + a live audio analyser drives aurora brightness, cabin glow, and the 3D orb (soft authored breathing when muted)

608ae8ad1b834f6feb33f0932463d7b7686d2b54 · 2026-09-02 07:40:06 -0700 · Steve Abrams

Files touched

Diff

commit 608ae8ad1b834f6feb33f0932463d7b7686d2b54
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Sep 2 07:40:06 2026 -0700

    All lights go to the beat: gentle rhythmic tremolo on the ambient pad + a live audio analyser drives aurora brightness, cabin glow, and the 3D orb (soft authored breathing when muted)
---
 index.html | 22 +++++++++++++++-------
 1 file changed, 15 insertions(+), 7 deletions(-)

diff --git a/index.html b/index.html
index 206a2ad..cd7d2f4 100644
--- a/index.html
+++ b/index.html
@@ -152,9 +152,11 @@ const MOODS={
   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; }
+let actx=null, master=null, padOsc=[], padGain=null, padLfo=null, padTrem=null, analyser=null, freqData=null;
+function ensureAudio(){ if(actx) return true; try{ actx=new (window.AudioContext||window.webkitAudioContext)(); master=actx.createGain(); master.gain.value=S.volume;
+  analyser=actx.createAnalyser(); analyser.fftSize=64; analyser.smoothingTimeConstant=0.8; freqData=new Uint8Array(analyser.frequencyBinCount);
+  master.connect(analyser); master.connect(actx.destination); return true; }catch(e){ return false; } }
+function stopPad(){ padOsc.forEach(o=>{try{o.stop();}catch(e){}}); padOsc=[]; [padLfo,padTrem].forEach(o=>{if(o){try{o.stop();}catch(e){}}}); padLfo=padTrem=null; padGain=null; }
 function startPad(){
   if(!actx) return; stopPad();
   const root=MOODS[S.mood].root, t=actx.currentTime;
@@ -164,6 +166,8 @@ function startPad(){
   [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;
+  // gentle rhythmic tremolo so the lights have a beat to follow (~54 bpm pulse)
+  padTrem=actx.createOscillator(); const tg=actx.createGain(); padTrem.frequency.value=0.9; tg.gain.value=0.05; padTrem.connect(tg); tg.connect(pg.gain); padTrem.start();
 }
 if(window.matchMedia && matchMedia('(prefers-reduced-motion: reduce)').matches) S.reduced=true;
 const announce=m=>{ live.textContent=m; };
@@ -211,7 +215,7 @@ function auroraPath(g, yTop, band, seed, camScale){
 }
 function drawAuroraInto(c, flip){
   if(S.aurora<=0.02) return;
-  const W=S.W,H=S.H, hz=HORIZON(), intensity=S.aurora;
+  const W=S.W,H=S.H, hz=HORIZON(), intensity=S.aurora*(0.72+(S.pulse||0)*0.55);   // aurora brightens to the beat
   c.save(); c.globalCompositeOperation='lighter';
   const bands=4;
   for(let b=0;b<bands;b++){
@@ -259,7 +263,7 @@ function drawCabin(){
   ctx.fillRect(x,y-22,40,22);
   ctx.beginPath(); ctx.moveTo(x-4,y-22); ctx.lineTo(x+20,y-36); ctx.lineTo(x+44,y-22); ctx.closePath(); ctx.fill();
   // warm window
-  const wx=x+15, wy=y-14, glow=0.7+0.3*Math.sin(S.t*2);
+  const wx=x+15, wy=y-14, glow=(0.6+0.4*Math.sin(S.t*2))*(0.8+(S.pulse||0)*0.7);   // cabin window pulses to the beat
   const g=ctx.createRadialGradient(wx,wy,1,wx,wy,20); g.addColorStop(0,`rgba(255,207,110,${0.5*glow})`); g.addColorStop(1,'rgba(0,0,0,0)');
   ctx.fillStyle=g; ctx.beginPath(); ctx.arc(wx,wy,20,0,7); ctx.fill();
   ctx.fillStyle=`rgba(255,207,110,${0.9*glow})`; ctx.fillRect(wx-4,wy-4,10,10);
@@ -315,6 +319,9 @@ let last=performance.now();
 function frame(now){
   const dt=Math.min(0.05,(now-last)/1000); last=now;
   if(S.playing){ S.t += dt*S.tempo*(S.reduced?0.3:1); }
+  // beat pulse: from the live audio when sound is on, else a soft authored breathing
+  if(analyser && S.sound){ analyser.getByteFrequencyData(freqData); let sum=0,n=Math.max(1,freqData.length>>1); for(let i=0;i<n;i++) sum+=freqData[i]; const raw=(sum/n)/255; S.pulse=(S.pulse||0)*0.7+raw*0.3; }
+  else { S.pulse=0.30+0.30*Math.sin(S.t*1.4); }
   if(skyDirty) drawSky();
   const W=S.W,H=S.H;
   if(S.trails && !S.reduced && S.playing){ ctx.fillStyle='rgba(3,8,16,0.16)'; ctx.fillRect(0,0,W,H); } else ctx.clearRect(0,0,W,H);
@@ -397,9 +404,10 @@ resize(); requestAnimationFrame(frame);
   const col=new THREE.Color();
   addEventListener('resize',()=>{ W=innerWidth;H=innerHeight; camera.aspect=W/H; camera.updateProjectionMatrix(); renderer.setSize(W,H,false); });
   function loop(){ requestAnimationFrame(loop);
-    const s=window.__auroraS||{}, t=performance.now()/1000, paused=(s.playing===false)||s.reduced;
+    const s=window.__auroraS||{}, t=performance.now()/1000, paused=(s.playing===false)||s.reduced, pulse=s.pulse||0;
     if(!paused) group.rotation.y+=0.005; group.rotation.x=Math.sin(t*0.2)*0.15;
-    group.scale.setScalar(1+0.06*Math.sin(t*1.1));
+    group.scale.setScalar(1+0.06*Math.sin(t*1.1)+pulse*0.22);
+    core.material.emissiveIntensity=0.4+pulse*1.3;
     const hue=(160+Math.sin(t*0.25)*40)/360; col.setHSL(hue,0.7,0.55); core.material.emissive.copy(col); wire.material.color.setHSL((hue+0.08)%1,0.7,0.7);
     renderer.render(scene,camera);
   }

← 1c90e32 Add sound + selection controller to Aurora: original ambient  ·  back to Aurora Drift  ·  (newest)