[object Object]

← back to Scarlet Riverboat Masquerade

Add Music Room: stream the Grateful Dead Live Music Archive (archive.org) — reuses the dead-agentabrams curated catalog; native transport, set list, seek, source attribution, embed fallback. Motion stays authored (no audio analysis).

85c6c771c12a1ba828ee548bde6e9d62f9ac3339 · 2026-09-01 17:06:22 -0700 · Steve Abrams

Files touched

Diff

commit 85c6c771c12a1ba828ee548bde6e9d62f9ac3339
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Sep 1 17:06:22 2026 -0700

    Add Music Room: stream the Grateful Dead Live Music Archive (archive.org) — reuses the dead-agentabrams curated catalog; native transport, set list, seek, source attribution, embed fallback. Motion stays authored (no audio analysis).
---
 index.html | 228 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 225 insertions(+), 3 deletions(-)

diff --git a/index.html b/index.html
index ea0d548..f543206 100644
--- a/index.html
+++ b/index.html
@@ -119,6 +119,48 @@
     font-size:11px;line-height:1.7;letter-spacing:.02em}
   .museum .keys b{display:inline-block;min-width:74px;color:var(--gold);font-family:var(--serif);font-style:italic;font-weight:600}
 
+  /* ---- Music Room console ---- */
+  .musicroom{
+    position:fixed;left:26px;top:158px;z-index:9;width:min(84vw,300px);
+    background:var(--panel);border:1px solid var(--panel-edge);border-radius:14px;
+    backdrop-filter:blur(9px);box-shadow:0 12px 40px rgba(0,0,0,.5);overflow:hidden;font-size:12px;
+  }
+  .musicroom > header{
+    display:flex;align-items:center;gap:9px;padding:11px 14px;cursor:pointer;
+    font-family:var(--serif);font-style:italic;color:var(--gold);font-size:14px;user-select:none;
+  }
+  .musicroom .live-dot{width:8px;height:8px;border-radius:50%;background:#5a2128;flex:none}
+  .musicroom.playing .live-dot{background:var(--gold);animation:pulse 2s infinite}
+  @keyframes pulse{0%{box-shadow:0 0 0 0 rgba(242,182,74,.5)}70%{box-shadow:0 0 0 8px rgba(242,182,74,0)}100%{box-shadow:0 0 0 0 rgba(242,182,74,0)}}
+  .musicroom .chev{margin-left:auto;transition:transform .3s;font-size:11px}
+  .musicroom[data-open="false"] .chev{transform:rotate(-90deg)}
+  .musicroom[data-open="false"] .mrbody{display:none}
+  .mrbody{padding:0 14px 14px}
+  .mr-show{width:100%;background:rgba(40,12,16,.7);color:var(--ink);border:1px solid var(--panel-edge);
+    border-radius:9px;padding:8px 10px;font-family:var(--serif);font-size:12.5px;cursor:pointer}
+  .mr-now{margin:12px 0 3px;color:var(--ink);font-family:var(--serif);font-style:italic;font-size:13px;min-height:18px;
+    overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
+  .mr-sub{color:var(--ink-dim);font-size:10.5px;letter-spacing:.03em;margin-bottom:9px}
+  .mr-transport{display:flex;align-items:center;gap:8px;margin:6px 0}
+  .mr-transport .btn{padding:7px 10px}
+  .mr-transport .play{min-width:46px;justify-content:center;font-size:13px}
+  .mr-tracks-toggle{margin-left:auto;font-size:10px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-dim);
+    background:none;border:0;cursor:pointer;padding:4px 2px}
+  .mr-tracks-toggle:hover{color:var(--gold)}
+  .mr-seek{display:flex;align-items:center;margin:9px 0 4px}
+  .mr-seek input[type=range]{width:100%}
+  .mr-time{font-variant-numeric:tabular-nums;color:var(--ink-dim);font-size:10px;text-align:right}
+  .mr-tracks{max-height:148px;overflow:auto;margin-top:9px;border-top:1px solid rgba(242,182,74,.16);padding-top:8px;display:none}
+  .musicroom.show-tracks .mr-tracks{display:block}
+  .mr-tracks button{display:flex;gap:9px;width:100%;text-align:left;background:none;border:0;color:var(--ink-dim);
+    font-family:var(--sans);font-size:11.5px;padding:5px 4px;cursor:pointer;border-radius:6px;line-height:1.3}
+  .mr-tracks button:hover{color:var(--ink);background:rgba(70,20,26,.5)}
+  .mr-tracks button[aria-current="true"]{color:var(--gold)}
+  .mr-tracks button .n{color:var(--ink-dim);min-width:20px;font-variant-numeric:tabular-nums;opacity:.7;flex:none}
+  .mr-foot{margin-top:11px;font-size:10px;line-height:1.55;color:var(--ink-dim)}
+  .mr-foot a{color:var(--gold);text-decoration:none;border-bottom:1px solid rgba(242,182,74,.4)}
+  .mr-msg{margin-top:9px;font-size:11px;line-height:1.5;color:var(--ink-dim)}
+
   :focus-visible{outline:3px solid var(--focus);outline-offset:2px;border-radius:6px}
   .sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
 
@@ -132,6 +174,8 @@
     .label-toggle{bottom:100px;right:12px}
     .museum{bottom:148px;right:12px}
     .chapters{top:auto;bottom:100px;right:12px;flex-direction:row}
+    .musicroom{top:auto;bottom:148px;left:12px;right:12px;width:auto}
+    .musicroom .mr-tracks{max-height:96px}
   }
   @media (prefers-reduced-motion: reduce){
     .museum,.chapters button,.btn,.skip{transition:none}
@@ -146,11 +190,36 @@
   <canvas id="scene" aria-hidden="true"></canvas>
 
   <div class="titleplate">
-    <p class="kicker">An Animated Gallery</p>
+    <p class="kicker">A Riverboat Listening Room</p>
     <h1 aria-hidden="true">Scarlet Riverboat<br>Masquerade</h1>
     <p class="chap" id="chapLabel">Chapter I · Departure</p>
   </div>
 
+  <section class="musicroom" id="musicroom" data-open="true" aria-label="Music room — Grateful Dead Live Music Archive">
+    <header id="mrToggle" role="button" tabindex="0" aria-expanded="true" aria-controls="mrbody">
+      <span class="live-dot" aria-hidden="true"></span>
+      <span>Music Room</span>
+      <span class="chev" aria-hidden="true">▾</span>
+    </header>
+    <div class="mrbody" id="mrbody">
+      <select class="mr-show" id="mrShow" aria-label="Choose a recording from the Live Music Archive"></select>
+      <div class="mr-now" id="mrNow">Choose a night, then press play.</div>
+      <div class="mr-sub" id="mrSub"></div>
+      <div class="mr-transport">
+        <button class="btn" id="mrPrev" aria-label="Previous track">⏮</button>
+        <button class="btn play" id="mrPlay" aria-label="Play" aria-pressed="false">▶</button>
+        <button class="btn" id="mrNext" aria-label="Next track">⏭</button>
+        <button class="mr-tracks-toggle" id="mrTracksToggle" aria-expanded="false" aria-controls="mrTracks">Tracks</button>
+      </div>
+      <div class="mr-seek"><input type="range" id="mrSeek" min="0" max="1000" value="0" step="1" aria-label="Seek within track"></div>
+      <div class="mr-time"><span id="mrCur">0:00</span> / <span id="mrDur">0:00</span></div>
+      <div class="mr-tracks" id="mrTracks" role="list" aria-label="Set list"></div>
+      <div class="mr-foot" id="mrFoot"></div>
+      <div class="mr-msg" id="mrMsg" hidden></div>
+    </div>
+    <audio id="mrAudio" preload="none"></audio>
+  </section>
+
   <nav class="chapters" aria-label="Chapters">
     <button data-chapter="0" aria-current="true"><span class="dot" aria-hidden="true"></span>I · Departure</button>
     <button data-chapter="1"><span class="dot" aria-hidden="true"></span>II · Moon Bend</button>
@@ -197,8 +266,10 @@
   <button class="btn label-toggle" id="labelBtn" aria-expanded="false" aria-controls="museum">🏷 Museum Label</button>
   <aside class="museum" id="museum" hidden aria-label="Museum label">
     <h2>Scarlet Riverboat Masquerade</h2>
-    <div class="meta">Generative Canvas · Authored Visual Rhythm · Single File</div>
-    <p>A fantastical paddlewheel riverboat drifts through a hand-painted crimson canyon while masked revelers fill three decks. <strong>Every movement here is composed, not measured</strong> — the tempo you steer is an <em>authored visual cadence</em> written into the drawing loop itself, never an analysis of sound. There is no audio; the "rhythm" is pure choreography of light and line.</p>
+    <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 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>
     <div class="keys">
@@ -981,5 +1052,156 @@ resize();
 requestAnimationFrame(frame);
 })();
 </script>
+
+<script>
+/* ---------- Music Room: streams the Grateful Dead Live Music Archive (archive.org) ----------
+   Reuses the curated show catalog from the dead-agentabrams app. Listen-only:
+   audio streams straight from the Internet Archive, is user-initiated, and links
+   back to each source item. The visual scene stays authored — no audio analysis. */
+(() => {
+"use strict";
+const CATALOG = [
+  {id:'gd69-02-27.sbd.16track.kaplan.6315.sbeok.shnf', date:'February 27, 1969', title:'Fillmore West',    sub:'San Francisco · primal psychedelic era'},
+  {id:'gd72-08-27.sbd.orf.3328.sbeok.shnf',            date:'August 27, 1972',   title:'Veneta',           sub:'Oregon · field, heat, and open sky'},
+  {id:'gd74-06-23.sbd.cribbs.16780.sbeok.shnf',        date:'June 23, 1974',     title:'Jai-Alai Fronton', sub:'Miami · Wall of Sound era'},
+  {id:'gd77-05-08.sbd.hicks.4982.sbeok.shnf',          date:'May 8, 1977',       title:'Cornell',          sub:'Ithaca · Barton Hall'},
+  {id:'gd90-03-29.aud-fob.set2.unknown.1317.sbeok.shnf',date:'March 29, 1990',   title:'Nassau',           sub:'Uniondale · late-era expansion'}
+];
+const DEFAULT_SHOW = 3; // Cornell '77
+
+const $ = id => document.getElementById(id);
+const audio = $('mrAudio'), room = $('musicroom'), sel = $('mrShow'), live = $('live');
+let showIdx=-1, tracks=[], trackIdx=-1, seeking=false;
+
+CATALOG.forEach((s,i)=>{ const o=document.createElement('option'); o.value=String(i); o.textContent=`${s.date} — ${s.title}`; sel.appendChild(o); });
+
+const fmt = t => { if(!isFinite(t)||t<0) t=0; const m=Math.floor(t/60); return m+':'+String(Math.floor(t%60)).padStart(2,'0'); };
+const announce = m => { if(live) live.textContent=m; };
+
+function setFoot(s){
+  const short = s.id.split('.')[0];
+  $('mrFoot').innerHTML =
+    `Source: <a href="https://archive.org/details/${s.id}" target="_blank" rel="noopener noreferrer">archive.org/details/${short}</a> — `+
+    `a community recording from the Grateful Dead Live Music Archive, streamed from the Internet Archive. Listen-only; nothing is copied or re-hosted here.`;
+}
+
+async function loadShow(i, opts){
+  opts=opts||{};
+  try{ audio.pause(); }catch(e){}          // stop the current night before loading the next
+  showIdx=i; tracks=[]; trackIdx=-1;
+  const s=CATALOG[i];
+  sel.value=String(i);
+  $('mrNow').textContent=`${s.title} — loading set list…`;
+  $('mrSub').textContent=`${s.date} · ${s.sub}`;
+  $('mrTracks').innerHTML=''; $('mrMsg').hidden=true; $('mrMsg').innerHTML='';
+  setFoot(s);
+  try{
+    const r=await fetch(`https://archive.org/metadata/${s.id}`,{mode:'cors'});
+    if(!r.ok) throw new Error('metadata '+r.status);
+    const j=await r.json();
+    let mp3=(j.files||[]).filter(f=>/vbr mp3/i.test(f.format||''));
+    if(!mp3.length) mp3=(j.files||[]).filter(f=>/mp3/i.test(f.format||''));
+    mp3.sort((a,b)=> (a.name>b.name?1:-1));
+    tracks=mp3.map(f=>({
+      title:(f.title&&f.title.trim())?f.title.trim():f.name.replace(/\.mp3$/i,''),
+      url:`https://archive.org/download/${s.id}/${encodeURIComponent(f.name)}`
+    }));
+    if(!tracks.length) throw new Error('no audio files');
+    renderTracks();
+    trackIdx=0; audio.src=tracks[0].url;                 // cue the opener, paused (playback stays user-initiated)
+    $('mrNow').textContent=`♪ ${tracks[0].title}`;
+    highlight();
+    $('mrDur').textContent='0:00'; $('mrCur').textContent='0:00'; $('mrSeek').value='0';
+    announce(`Loaded ${s.title}, ${s.date}. ${tracks.length} tracks from the Live Music Archive.`);
+    if(opts.play) playTrack(0);
+  }catch(e){
+    fallbackEmbed(s);
+  }
+}
+
+function fallbackEmbed(s){
+  $('mrNow').textContent=s.title;
+  const msg=$('mrMsg'); msg.hidden=false;
+  const fileNote = location.protocol==='file:' ? ' Opening the file directly (file://) can block cross-site loading — serving it over http fixes it.' : '';
+  msg.innerHTML=`Couldn't load the set list here.${fileNote} `+
+    `<button class="btn" id="mrEmbedBtn" style="margin-top:8px">▶ Open the archive.org player</button>`;
+  $('mrEmbedBtn').onclick=()=>{
+    msg.innerHTML=`<iframe title="Internet Archive player for ${s.title}" src="https://archive.org/embed/${s.id}" width="100%" height="150" frameborder="0" style="border-radius:8px;border:1px solid var(--panel-edge)"></iframe>`;
+    room.classList.add('playing');
+  };
+}
+
+function renderTracks(){
+  const c=$('mrTracks'); c.innerHTML='';
+  tracks.forEach((t,i)=>{
+    const b=document.createElement('button'); b.type='button'; b.setAttribute('role','listitem');
+    b.innerHTML=`<span class="n">${String(i+1).padStart(2,'0')}</span><span>${t.title}</span>`;
+    b.addEventListener('click',()=>playTrack(i));
+    c.appendChild(b);
+  });
+}
+function highlight(){ [...$('mrTracks').children].forEach((b,i)=>b.setAttribute('aria-current', String(i===trackIdx))); }
+
+function playTrack(i){
+  if(i<0||i>=tracks.length) return;
+  if(i!==trackIdx || !audio.src){ trackIdx=i; audio.src=tracks[i].url; }
+  audio.play().catch(()=>setPlay(false));
+  $('mrNow').textContent=`♪ ${tracks[i].title}`;
+  highlight();
+  announce(`Now playing ${tracks[i].title}.`);
+}
+function setPlay(on){
+  room.classList.toggle('playing',on);
+  const b=$('mrPlay'); b.textContent=on?'❚❚':'▶';
+  b.setAttribute('aria-pressed',String(on)); b.setAttribute('aria-label',on?'Pause':'Play');
+}
+
+audio.addEventListener('play',()=>setPlay(true));
+audio.addEventListener('pause',()=>setPlay(false));
+audio.addEventListener('ended',()=>{ if(trackIdx<tracks.length-1) playTrack(trackIdx+1); else setPlay(false); });
+audio.addEventListener('timeupdate',()=>{
+  const d=audio.duration||0, c=audio.currentTime||0;
+  $('mrCur').textContent=fmt(c); $('mrDur').textContent=fmt(d);
+  if(d>0 && !seeking) $('mrSeek').value=String(Math.round(c/d*1000));
+});
+audio.addEventListener('error',()=>{ if(showIdx>=0 && !tracks.length) fallbackEmbed(CATALOG[showIdx]); });
+
+$('mrPlay').addEventListener('click',()=>{
+  if(!tracks.length){ loadShow(showIdx<0?DEFAULT_SHOW:showIdx,{play:true}); return; }
+  if(trackIdx<0){ playTrack(0); return; }
+  if(audio.paused) audio.play().catch(()=>{}); else audio.pause();
+});
+$('mrPrev').addEventListener('click',()=>{ if(tracks.length) playTrack(Math.max(0,trackIdx-1)); });
+$('mrNext').addEventListener('click',()=>{ if(tracks.length) playTrack(Math.min(tracks.length-1,trackIdx+1)); });
+
+const seek=$('mrSeek');
+seek.addEventListener('input',()=>{ seeking=true; });
+seek.addEventListener('change',()=>{ const d=audio.duration||0; if(d>0) audio.currentTime=(+seek.value/1000)*d; seeking=false; });
+
+sel.addEventListener('change',()=>loadShow(+sel.value,{play:true}));
+
+$('mrTracksToggle').addEventListener('click',()=>{
+  const on=room.classList.toggle('show-tracks');
+  $('mrTracksToggle').setAttribute('aria-expanded',String(on));
+});
+
+const mrToggle=$('mrToggle');
+function toggleRoom(){
+  const open=room.getAttribute('data-open')!=='false';
+  room.setAttribute('data-open',String(!open));
+  mrToggle.setAttribute('aria-expanded',String(!open));
+}
+mrToggle.addEventListener('click',toggleRoom);
+mrToggle.addEventListener('keydown',e=>{ if(e.key==='Enter'||e.key===' '){ e.preventDefault(); toggleRoom(); } });
+
+// small screens: start collapsed so the art leads
+if(window.matchMedia && matchMedia('(max-width:820px)').matches){
+  room.setAttribute('data-open','false'); mrToggle.setAttribute('aria-expanded','false');
+}
+
+// cue the default night (no autoplay — the source policy is user-initiated playback)
+loadShow(DEFAULT_SHOW);
+})();
+</script>
 </body>
 </html>

← 8187692 Harden shade(): clamp t to [0,1] and channels to [0,255] (co  ·  back to Scarlet Riverboat Masquerade  ·  Add conditional DEAD back-link (shown only when served as /r 2b38b3c →