← back to Dead Agentabrams
Sync ROOM 01: add optional audio-reactive mode (live archive.org stream drives the visuals on the beat)
d42c6950d1c2d4f46fc9965cb33bf1e909120a16 · 2026-09-01 18:15:11 -0700 · Steve Abrams
Files touched
Diff
commit d42c6950d1c2d4f46fc9965cb33bf1e909120a16
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Sep 1 18:15:11 2026 -0700
Sync ROOM 01: add optional audio-reactive mode (live archive.org stream drives the visuals on the beat)
---
room/index.html | 55 +++++++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 49 insertions(+), 6 deletions(-)
diff --git a/room/index.html b/room/index.html
index c849d94..c2e78aa 100644
--- a/room/index.html
+++ b/room/index.html
@@ -255,6 +255,7 @@
<div class="toggle">
<button class="btn" id="reduceBtn" aria-pressed="false">◐ Reduced Motion</button>
<button class="btn" id="trailBtn" aria-pressed="true">✧ Motion Trails</button>
+ <button class="btn" id="reactBtn" aria-pressed="false">♫ React to Music</button>
</div>
</div>
<div class="group">
@@ -282,7 +283,7 @@
<h2>Scarlet Riverboat Masquerade</h2>
<div class="meta">Generative Canvas · Live Music Archive · Authored Visual Rhythm</div>
<p>A fantastical paddlewheel riverboat drifts through a hand-painted crimson canyon while masked revelers fill three decks. This is a <strong>listening room</strong>: press play in the Music Room and a Grateful Dead concert streams from the Internet Archive's <strong>Live Music Archive</strong> while the boat turns around you.</p>
- <p><strong>The motion is composed, not measured.</strong> The choreography is an <em>authored visual cadence</em> written into the drawing loop itself — it is deliberately <em>not</em> audio analysis and does not react to the waveform. Set the visual tempo to whatever feels right against the night's playing; the two rhythms — heard and drawn — keep company without one driving the other.</p>
+ <p><strong>The motion is composed, not measured.</strong> The choreography is an <em>authored visual cadence</em> written into the drawing loop itself — it is deliberately <em>not</em> audio analysis and does not react to the waveform. Set the visual tempo to whatever feels right against the night's playing; the two rhythms — heard and drawn — keep company without one driving the other. <em>(Flip “React to Music” and that changes on purpose: the lanterns, fireflies, and dancers then bloom to a live analysis of the streaming audio.)</em></p>
<p><strong>The recordings are not copied or re-hosted here.</strong> They are community tapes made available through the Grateful Dead collection at the Internet Archive; playback is user-initiated and every set links back to its source item on archive.org.</p>
<p>The <strong>original visual language</strong> reduces each dancer to a weighted silhouette: couples pivot in shared arcs, a linked circle turns about the smokestack, soloists trail scarves, and musicians appear only as abstract shadow-forms — a communal, exploratory spirit rendered without any borrowed logo, emblem, or album art.</p>
<p>On the shore, four creatures each carry a distinct loop with believable weight — a <strong>trotting fox</strong>, a <strong>slow-stepping heron</strong>, a <strong>squash-and-stretch rabbit</strong>, and a <strong>bear conducting fireflies</strong>. Palette <em>and</em> choreography change across the three chapters as the boat travels from dusk (Departure) through moonrise (Moon Bend) into sunrise (Dawn Landing).</p>
@@ -456,7 +457,7 @@ function canyonSilhouette(baseY, amp, seed, color, offset){
// ---------- Boat transform for the active camera ----------
function boatTransform(){
const W=S.W,H=S.H;
- const bob = S.reduced?0:Math.sin(S.t*0.6)*6;
+ const bob = S.reduced?0:Math.sin(S.t*0.6)*6*(1+(S.amp||0)*1.1);
const sway = S.reduced?0:Math.sin(S.t*0.33)*0.012;
let cx=W*0.5, cy=H*0.60+bob, scale=Math.min(W,H)/620, rot=sway;
if(S.cam==='deck'){ cx=W*0.5; cy=H*0.66+bob; scale=Math.min(W,H)/300; rot=sway*0.6; }
@@ -492,7 +493,7 @@ function drawFigure(x,y,s,phase,type,accent){
armSwing = 0.5+0.3*Math.sin(t*2+ph);
}
ctx.rotate(lean);
- ctx.translate(0,bob);
+ ctx.translate(0,bob*(1+(S.amp||0)*0.7)); // dancers bounce harder on louder passages when reactive
// scarf trail for soloists — length/opacity swell across chapters
if(type==='solo' && scarf){
@@ -563,7 +564,7 @@ function lantern(x,y,s,glow,color){
function drawBoat(){
const {cx,cy,scale,rot}=boatTransform();
const accent=pal('accent',0), lan=pal('lantern',0);
- const glow=S.lantern;
+ const glow=S.lantern*(1+(S.amp||0)*0.9); // lanterns bloom on the beat when reactive
ctx.save();
ctx.translate(cx,cy); ctx.rotate(rot); ctx.scale(scale,scale);
@@ -859,7 +860,7 @@ function bearAndFireflies(x,y,s,t,accent){
const a=t*0.7*(0.6+ (i%3)*0.2) + ph;
const fx=x + Math.cos(a)*orbit + Math.sin(t*0.7)*14;
const fy=y-40 + Math.sin(a*1.3)*orbit*0.5 - (i%4)*6;
- const blink=0.35+0.65*Math.abs(Math.sin(t*3+ph));
+ const blink=Math.min(1,(0.35+0.65*Math.abs(Math.sin(t*3+ph)))*(1+(S.amp||0)*1.3));
const g=ctx.createRadialGradient(fx,fy,0,fx,fy,7);
g.addColorStop(0,`rgba(255,230,140,${blink})`); g.addColorStop(1,'rgba(255,200,90,0)');
ctx.fillStyle=g; ctx.beginPath(); ctx.arc(fx,fy,7,0,7); ctx.fill();
@@ -894,7 +895,7 @@ function drawRiver(){
if(S.lantern>0.05 && S.cam!=='over'){
const {cx}=boatTransform();
const rg=ctx.createRadialGradient(cx,top+30,4,cx,top+30,120);
- rg.addColorStop(0,`rgba(255,190,90,${0.22*S.lantern})`); rg.addColorStop(1,'rgba(0,0,0,0)');
+ rg.addColorStop(0,`rgba(255,190,90,${Math.min(0.6,0.22*S.lantern*(1+(S.amp||0)))})`); rg.addColorStop(1,'rgba(0,0,0,0)');
ctx.fillStyle=rg; ctx.beginPath(); ctx.ellipse(cx,top+40,120,40,0,0,7); ctx.fill();
}
}
@@ -904,6 +905,9 @@ let last=performance.now();
function frame(now){
const dt=Math.min(0.05,(now-last)/1000); last=now;
+ // live audio energy (0..1) when "React to Music" is on — otherwise motion stays authored
+ S.amp = (window.SRM && window.SRM.react) ? (window.SRM.level||0) : 0;
+
if(S.playing){
const spd = S.reduced ? 0.28 : 1.0;
S.t += dt * S.tempo * spd * 1.4;
@@ -1055,6 +1059,7 @@ window.addEventListener('keydown',e=>{
case 'r': document.getElementById('restartBtn').click(); break;
case 'm': reduceBtn.click(); break;
case 't': trailBtn.click(); break;
+ case 'a': { const rb=document.getElementById('reactBtn'); if(rb) rb.click(); } break;
case '1': document.querySelector('[data-cam="wide"]').click(); break;
case '2': document.querySelector('[data-cam="deck"]').click(); break;
case '3': document.querySelector('[data-cam="over"]').click(); break;
@@ -1176,6 +1181,44 @@ function setPlay(on){
b.setAttribute('aria-pressed',String(on)); b.setAttribute('aria-label',on?'Pause':'Play');
}
+// --- optional audio-reactive bridge: analyzes the LIVE archive.org stream (CORS-open) ---
+// Default OFF — visuals stay authored until the visitor flips "React to Music".
+audio.crossOrigin='anonymous'; // set before any src load so the CDN stream is analyzable
+window.SRM = window.SRM || { level:0, react:false };
+let _actx, _analyser, _freq, _looping=false;
+function ensureGraph(){
+ if(_actx) return true;
+ try{
+ _actx = new (window.AudioContext||window.webkitAudioContext)();
+ const src=_actx.createMediaElementSource(audio); // once per element
+ _analyser=_actx.createAnalyser(); _analyser.fftSize=256; _analyser.smoothingTimeConstant=0.82;
+ src.connect(_analyser); _analyser.connect(_actx.destination);
+ _freq=new Uint8Array(_analyser.frequencyBinCount);
+ return true;
+ }catch(e){ return false; }
+}
+function sampleLevel(){
+ if(_analyser){
+ _analyser.getByteFrequencyData(_freq);
+ let sum=0, n=Math.max(1,Math.floor(_freq.length/6)); // low-end (bass) energy
+ for(let i=0;i<n;i++) sum+=_freq[i];
+ window.SRM.level = window.SRM.level*0.6 + ((sum/n)/255)*0.4; // smoothed 0..1
+ }
+ requestAnimationFrame(sampleLevel);
+}
+const reactBtn=$('reactBtn');
+if(reactBtn) reactBtn.addEventListener('click',()=>{
+ const on=!window.SRM.react;
+ if(on){
+ if(!ensureGraph()){ announce('Live audio analysis is not available in this browser.'); return; }
+ if(_actx.state==='suspended') _actx.resume();
+ if(!_looping){ _looping=true; sampleLevel(); }
+ }
+ window.SRM.react=on;
+ reactBtn.setAttribute('aria-pressed',String(on));
+ announce(on?'Reacting to the music — lanterns, fireflies, and dancers now move to the live audio.':'Back to authored motion.');
+});
+
audio.addEventListener('play',()=>setPlay(true));
audio.addEventListener('pause',()=>setPlay(false));
audio.addEventListener('ended',()=>{ if(trackIdx<tracks.length-1) playTrack(trackIdx+1); else setPlay(false); });
← f9a40c3 Sync ROOM 01 with codex-hardened riverboat room (race guard,
·
back to Dead Agentabrams
·
Sync ROOM 01: pulsating lamps + beat/click fireworks ba4b4cb →