KidLisp.com Jukebox Feature - Technical Analysis #
Date: January 28, 2026
Author: Technical Analysis
Status: Planning / Specification
1. Feature Overview #
Add a Jukebox tab to the kidlisp.com editor that:
- Plays audio tracks from
system/public/assets/oskie/wipppps/ - Extracts real-time audio data (amplitude, beat detection, frequency bands)
- Sends audio parameters to the KidLisp iframe for audio-reactive visuals
- Broadcasts audio data over PJ (Piece Jockey) for remote live visuals
2. Current Architecture Analysis #
2.1 Audio Assets Location #
system/public/assets/oskie/wipppps/
├── ARrrGh.wav
├── BLuRP.wav
├── GrRr.wav
├── HoNK.wav
├── KLOnK.wav
├── PaFf_master.wav
├── WHoOSh.wav
├── wipppps_cover.jpeg
└── zzzZWAP.wav
2.2 Existing Audio → KidLisp Pipeline (wipppps.mjs) #
The wipppps.mjs piece demonstrates the complete audio-visual integration:
Audio Data Flow:
sound.speaker.amplitudes.left → amp (0-10 scale)
sound.speaker.frequencies.left → frequency bands
sound.speaker.beat.detected → kick (0 or 1)
KidLisp Global Variables (lib/kidlisp.mjs:3161-3195):
// Audio globals available in KidLisp code:
this.globalDef.amp = 0; // Current audio amplitude (0-10 scale)
this.globalDef.leftAmp = 0; // Left channel amplitude
this.globalDef.rightAmp = 0; // Right channel amplitude
this.globalDef.beat = 0; // Beat detection (0 or 1)
this.globalDef.kick = 0; // Kick drum detected (0 or 1)
this.globalDef.mic = 0; // Microphone amplitude
Update Method (lib/kidlisp.mjs:3161):
updateAudioGlobals(audioData) {
if (audioData && typeof audioData === 'object') {
if (typeof audioData.amp === 'number') this.globalDef.amp = audioData.amp;
if (typeof audioData.kick === 'number') this.globalDef.kick = audioData.kick;
// ... etc
}
}
2.3 KidLisp Code Using Audio (wipppps.mjs examples) #
;; Audio-reactive circle with kick drum
(ink (? lime magenta 0) (+ 100 (* kick 155)))
(circle (/ width 2) (/ height 2) (+ 3 (* kick 4)))
;; Beat-reactive spin
(spin (2s... -1.125 1.125))
;; Amplitude-based effects
(def beat (* kick 12))
(def pulse (* amp 8))
(tri x1 y1 x2 y2 cx cy)
2.4 KidLisp.com Editor → Iframe Communication #
Current Message Types (kidlisp.com/index.html):
// Send code to preview iframe
previewIframe.contentWindow.postMessage({
type: 'kidlisp-reload', // Full code reload
code: code,
createCode: false,
enableTrace: false
}, aestheticUrl);
// Slide mode (low-latency value updates)
previewIframe.contentWindow.postMessage({
type: 'kidlisp-slide',
code: code
}, aestheticUrl);
2.5 PJ Broadcasting System (kidlisp.com/index.html:22670-22750) #
BroadcastChannel (same-origin):
const bc = new BroadcastChannel(`kidlisp-channel-${codeChannelId}`);
bc.postMessage({
type: 'code-update', // or 'slide-update'
code: code,
channelId: codeChannelId,
timestamp: Date.now()
});
WebSocket (cross-origin via session-server):
pjWs.send(JSON.stringify({
type: 'code', // or 'slide'
content: {
piece: code,
source: code,
codeChannel: codeChannelId
}
}));
3. Proposed Architecture #
3.1 DOM-Based Jukebox Component #
┌─────────────────────────────────────────────────────────────┐
│ KidLisp.com Editor │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Tabs: [Examples] [Recent] [History] [🎵 Jukebox] │ │
│ └───────────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Jukebox Panel (DOM-based, outside iframe) │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ 🎵 Now Playing: zzzZWAP.wav │ │ │
│ │ │ [▶️ Play] [⏸️ Pause] [⏹️ Stop] │ │ │
│ │ │ ━━━━━━━━●━━━━━━━━ 2:34 / 5:12 │ │ │
│ │ │ Volume: ━━━━━━━●━━━ 70% │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ Track List: │ │ │
│ │ │ • ARrrGh.wav │ │ │
│ │ │ • BLuRP.wav │ │ │
│ │ │ • GrRr.wav │ │ │
│ │ │ • HoNK.wav │ │ │
│ │ │ • zzzZWAP.wav ◀ Playing │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ Audio Meters (real-time): │ │ │
│ │ │ amp: ████████░░ 0.73 │ │ │
│ │ │ kick: ● │ │ │
│ │ │ L/R: ████░░░░ / ███████░░░ │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ KidLisp Preview Iframe │ │
│ │ (receives audio data via postMessage) │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
3.2 Data Flow Diagram #
┌──────────────────┐ ┌───────────────────────────────────────────┐
│ Audio Files │ │ Jukebox (DOM - kidlisp.com/index.html) │
│ (wipppps/*.wav) │────▶│ │
└──────────────────┘ │ ┌─────────────────────────────────────┐ │
│ │ Web Audio API │ │
│ │ • AudioContext │ │
│ │ • AnalyserNode (FFT) │ │
│ │ • ScriptProcessorNode (amplitude) │ │
│ └─────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ Audio Data Extraction (60fps) │ │
│ │ { amp, leftAmp, rightAmp, kick, ... }│ │
│ └─────────────────────────────────────┘ │
└───────────────┬───────────────────────────┘
│
┌──────────────────────────┼──────────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────────────┐ ┌─────────────────────┐ ┌─────────────────────────┐
│ Local Preview Iframe │ │ BroadcastChannel │ │ WebSocket (pjWs) │
│ (postMessage) │ │ (same-origin PJ) │ │ (cross-origin PJ) │
│ │ │ │ │ │
│ type: 'kidlisp-audio' │ │ type: 'audio-data' │ │ type: 'audio' │
│ data: { amp, kick, ... }│ │ data: { ... } │ │ content: { ... } │
└─────────────────────────┘ └─────────────────────┘ └─────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────┐ ┌─────────────────────┐ ┌─────────────────────────┐
│ KidLisp Runtime │ │ PJ Window │ │ Remote PJ Viewers │
│ (lib/kidlisp.mjs) │ │ (pj.html) │ │ (pj.kidlisp.com) │
│ │ │ │ │ │
│ updateAudioGlobals() │ │ Forward to iframe │ │ Forward to iframe │
│ ↓ │ │ ↓ │ │ ↓ │
│ globalDef.amp = data.amp│ │ postMessage to │ │ postMessage to │
│ globalDef.kick = ... │ │ kidlisp iframe │ │ kidlisp iframe │
└─────────────────────────┘ └─────────────────────┘ └─────────────────────────┘
3.3 Message Protocol Design #
New Message Type for Audio Data:
// Editor → Preview Iframe
{
type: 'kidlisp-audio',
data: {
amp: 0.73, // 0-10 scale (normalized amplitude)
leftAmp: 0.65, // Left channel 0-10
rightAmp: 0.81, // Right channel 0-10
kick: 1, // 0 or 1 (beat detected)
beat: 1, // 0 or 1 (alternative beat signal)
frequencies: [...], // Optional: frequency band data
timestamp: 1706400000 // For sync
}
}
PJ Broadcast Extension:
// BroadcastChannel
bc.postMessage({
type: 'audio-data',
channelId: codeChannelId,
data: { amp, kick, leftAmp, rightAmp },
timestamp: Date.now()
});
// WebSocket
pjWs.send(JSON.stringify({
type: 'audio',
content: {
codeChannel: codeChannelId,
amp, kick, leftAmp, rightAmp,
timestamp: Date.now()
}
}));
4. Implementation Plan #
Phase 1: Web Audio Infrastructure (kidlisp.com/index.html) #
4.1.1 Audio Context & Analyser Setup
// Jukebox audio state
let jukeboxAudioContext = null;
let jukeboxAnalyser = null;
let jukeboxSource = null;
let jukeboxAudioElement = null;
let jukeboxAnimationFrame = null;
const JUKEBOX_TRACKS = [
'ARrrGh.wav', 'BLuRP.wav', 'GrRr.wav', 'HoNK.wav',
'KLOnK.wav', 'PaFf_master.wav', 'WHoOSh.wav', 'zzzZWAP.wav'
];
function initJukeboxAudio() {
jukeboxAudioContext = new (window.AudioContext || window.webkitAudioContext)();
jukeboxAnalyser = jukeboxAudioContext.createAnalyser();
jukeboxAnalyser.fftSize = 256; // 128 frequency bins
jukeboxAnalyser.smoothingTimeConstant = 0.3;
jukeboxAudioElement = document.createElement('audio');
jukeboxAudioElement.crossOrigin = 'anonymous';
jukeboxSource = jukeboxAudioContext.createMediaElementSource(jukeboxAudioElement);
jukeboxSource.connect(jukeboxAnalyser);
jukeboxAnalyser.connect(jukeboxAudioContext.destination);
}
4.1.2 Audio Data Extraction Loop
const frequencyData = new Uint8Array(jukeboxAnalyser.frequencyBinCount);
const timeDomainData = new Uint8Array(jukeboxAnalyser.fftSize);
let lastKickTime = 0;
const KICK_COOLDOWN_MS = 100;
function extractAudioData() {
jukeboxAnalyser.getByteFrequencyData(frequencyData);
jukeboxAnalyser.getByteTimeDomainData(timeDomainData);
// Calculate RMS amplitude
let sum = 0;
for (let i = 0; i < timeDomainData.length; i++) {
const normalized = (timeDomainData[i] - 128) / 128;
sum += normalized * normalized;
}
const rms = Math.sqrt(sum / timeDomainData.length);
const amp = Math.min(10, rms * 15); // Scale to 0-10
// Beat detection (low frequency energy spike)
const lowFreqSum = frequencyData.slice(0, 8).reduce((a, b) => a + b, 0) / 8;
const now = performance.now();
const kick = (lowFreqSum > 180 && now - lastKickTime > KICK_COOLDOWN_MS) ? 1 : 0;
if (kick) lastKickTime = now;
return { amp, kick, leftAmp: amp, rightAmp: amp, timestamp: Date.now() };
}
function jukeboxAudioLoop() {
if (!jukeboxAudioElement.paused) {
const audioData = extractAudioData();
sendAudioToPreview(audioData);
broadcastAudioToPJ(audioData);
}
jukeboxAnimationFrame = requestAnimationFrame(jukeboxAudioLoop);
}
Phase 2: Jukebox UI Tab (kidlisp.com/index.html) #
4.2.1 HTML Structure
<!-- Add to list-subtabs -->
<button class="list-subtab" data-subtab="jukebox">🎵 Jukebox</button>
<!-- Jukebox content panel -->
<div id="jukebox-content" class="list-subcontent hidden">
<div class="jukebox-player">
<div class="jukebox-now-playing">
<span class="jukebox-track-name">Select a track...</span>
</div>
<div class="jukebox-controls">
<button id="jukebox-play" class="jukebox-btn">▶️</button>
<button id="jukebox-pause" class="jukebox-btn">⏸️</button>
<button id="jukebox-stop" class="jukebox-btn">⏹️</button>
</div>
<div class="jukebox-progress">
<input type="range" id="jukebox-seek" min="0" max="100" value="0">
<span class="jukebox-time">0:00 / 0:00</span>
</div>
<div class="jukebox-volume">
<label>🔊</label>
<input type="range" id="jukebox-volume" min="0" max="100" value="70">
</div>
</div>
<div class="jukebox-tracklist" id="jukebox-tracklist">
<!-- Populated by JS -->
</div>
<div class="jukebox-meters">
<div class="jukebox-meter">
<label>amp:</label>
<div class="meter-bar"><div class="meter-fill" id="meter-amp"></div></div>
<span class="meter-value" id="meter-amp-value">0.00</span>
</div>
<div class="jukebox-meter">
<label>kick:</label>
<div class="kick-indicator" id="kick-indicator">●</div>
</div>
</div>
</div>
4.2.2 CSS Styling
.list-subtab[data-subtab="jukebox"] {
background: linear-gradient(135deg, #1a1a2e, #16213e);
color: #00d4ff;
}
.list-subtab[data-subtab="jukebox"].active {
background: linear-gradient(135deg, #0f0f23, #1a1a3e);
box-shadow: 0 0 10px rgba(0, 212, 255, 0.3);
}
.jukebox-player {
padding: 12px;
background: rgba(0, 0, 0, 0.3);
border-radius: 8px;
margin-bottom: 8px;
}
.jukebox-meters {
display: flex;
gap: 12px;
padding: 8px;
background: rgba(0, 0, 0, 0.2);
border-radius: 4px;
}
.meter-bar {
width: 100px;
height: 8px;
background: #333;
border-radius: 4px;
overflow: hidden;
}
.meter-fill {
height: 100%;
background: linear-gradient(90deg, #00ff00, #ffff00, #ff0000);
transition: width 0.05s;
}
.kick-indicator {
font-size: 16px;
color: #333;
transition: color 0.1s;
}
.kick-indicator.active {
color: #ff00ff;
text-shadow: 0 0 10px #ff00ff;
}
Phase 3: Preview Iframe Integration #
4.3.1 Message Handler in kidlisp.mjs
// Add to existing message listener in kidlisp.mjs
window.addEventListener('message', (event) => {
if (event.data?.type === 'kidlisp-audio') {
const audioData = event.data.data;
if (window.kidlispInstance) {
window.kidlispInstance.updateAudioGlobals(audioData);
}
}
});
4.3.2 Send Audio from Editor
function sendAudioToPreview(audioData) {
if (previewIframe?.contentWindow) {
previewIframe.contentWindow.postMessage({
type: 'kidlisp-audio',
data: audioData
}, aestheticUrl);
}
}
Phase 4: PJ Broadcasting #
4.4.1 Broadcast Audio Data
function broadcastAudioToPJ(audioData) {
if (!codeChannelId || !hasActivePjSession()) return;
// BroadcastChannel (same-origin)
if (window.BroadcastChannel) {
const bc = new BroadcastChannel(`kidlisp-channel-${codeChannelId}`);
bc.postMessage({
type: 'audio-data',
data: audioData,
channelId: codeChannelId,
timestamp: Date.now()
});
bc.close();
}
// WebSocket (cross-origin)
if (pjWs && pjWs.readyState === WebSocket.OPEN) {
pjWs.send(JSON.stringify({
type: 'audio',
content: {
codeChannel: codeChannelId,
...audioData
}
}));
}
}
4.4.2 PJ Window Handler (pj.html)
// Add to sessionWs.onmessage
if (msg.type === 'audio' && msg.content) {
sendAudioToIframe(msg.content);
}
function sendAudioToIframe(audioData) {
if (!iframeReady) return;
iframe.contentWindow.postMessage({
type: 'kidlisp-audio',
data: audioData
}, aestheticUrl);
}
// Add to BroadcastChannel handler
if (event.data?.type === 'audio-data') {
sendAudioToIframe(event.data.data);
}
5. Session Server Modifications (Optional Enhancement) #
If audio data relay through session-server is needed:
session-server/src/session.rs or equivalent:
// Handle audio message type
case 'audio':
// Broadcast to all subscribers of this code channel
broadcastToChannel(msg.content.codeChannel, {
type: 'audio',
content: msg.content
});
break;
6. Testing Strategy #
6.1 Local Testing #
- Start
npm run aesthetic - Open
localhost:8888/kidlisp.com - Switch to Jukebox tab
- Play a track, verify audio meters respond
- Write KidLisp code using
ampandkickvariables - Verify visuals react to audio
6.2 PJ Testing #
- Open Jukebox and start playing
- Click PJ button to open broadcast window
- Verify
pj.kidlisp.com/{channel}receives audio data - Confirm visuals in PJ window react to music
6.3 Remote Testing #
- Share PJ URL with another device
- Verify audio data arrives via WebSocket
- Confirm sync between editor and remote viewer
7. File Modifications Summary #
| File | Changes |
|---|---|
system/public/kidlisp.com/index.html |
Add Jukebox tab, audio engine, UI, broadcasting |
system/public/kidlisp.com/pj.html |
Handle audio-data messages, forward to iframe |
system/public/aesthetic.computer/lib/kidlisp.mjs |
Already supports updateAudioGlobals() ✅ |
session-server/ |
(Optional) Add audio message relay |
8. Future Enhancements #
- Waveform visualization in Jukebox panel
- Frequency spectrum display with band-level controls
- Custom audio upload (user's own tracks)
- BPM detection for tempo-synced effects
- MIDI clock output for external sync
- Audio recording from microphone into Jukebox
9. Dependencies #
- Web Audio API (native browser)
- Existing postMessage infrastructure
- Existing BroadcastChannel / WebSocket PJ system
- No new npm packages required
10. Estimated Implementation Time #
| Phase | Estimate |
|---|---|
| Phase 1: Web Audio Infrastructure | 2-3 hours |
| Phase 2: Jukebox UI Tab | 2-3 hours |
| Phase 3: Preview Integration | 1 hour |
| Phase 4: PJ Broadcasting | 1-2 hours |
| Testing & Polish | 2-3 hours |
| Total | 8-12 hours |
This analysis provides a roadmap for implementing the Jukebox feature while leveraging existing infrastructure in the aesthetic-computer codebase.