KidLisp Mic Global Implementation Plan #
Created: 2024-10-24
Author: Jeffrey (requested by the user)
Status: ✅ IMPLEMENTED
🎉 Implementation Complete! #
The mic global has been successfully implemented with fun default animations!
🎯 Objective #
Add mic as a KidLisp global variable that:
- Returns microphone amplitude from 0→1
- Automatically requests microphone permission when first accessed
- Follows the same pattern as existing
ampglobal for speaker amplitude - Uses the existing
microphone.mjsand referencesbaktok.mjsas API design models
📋 Current State Analysis #
Existing Implementation #
The mic function currently exists in KidLisp but works as a function call rather than a global variable:
Location: /workspaces/aesthetic-computer/system/public/aesthetic.computer/lib/kidlisp.mjs (line ~6174)
mic: (api, args = []) => {
// Lazy connection: try to connect microphone if not already connected
if (this.microphoneApi && !this.microphoneApi.connected) {
if (
this.microphoneApi.permission === "granted" &&
api.sound?.enabled?.()
) {
console.log("🎤 Lazy connecting microphone (mic function called)");
this.microphoneApi.connect();
} else if (this.microphoneApi.permission !== "granted") {
if (this.frameCount % 120 === 0) {
console.log("🎤 Microphone permission not granted, cannot connect");
}
}
}
// Return current amplitude if microphone is available and connected
if (this.microphoneApi && this.microphoneApi.connected) {
return this.microphoneApi.amplitude || 0;
}
return 0; // If not connected yet, return 0
},
Current usage: (mic) - requires parentheses as a function
Desired usage: mic - direct variable access
How amp Global Works (Reference Model) #
Location: /workspaces/aesthetic-computer/system/public/aesthetic.computer/lib/kidlisp.mjs
Initialization (lines 872-874, repeated 1500-1502):
this.globalDef.amp = 0; // Current audio amplitude (0-10 scale)
this.globalDef.leftAmp = 0; // Left channel amplitude (0-10 scale)
this.globalDef.rightAmp = 0; // Right channel amplitude (0-10 scale)
Update mechanism (in wipppps.mjs, line ~3607):
function sim({ sound, updateKidLispAudio }) {
// Update KidLisp global variables with current audio data
if (sound && sound.speaker && updateKidLispAudio) {
const leftAmp = sound.speaker.amplitudes?.left || 0;
const rightAmp = sound.speaker.amplitudes?.right || 0;
const avgAmp = (leftAmp + rightAmp) / 2;
// Scale amplitude to a reasonable range (0-10)
const scaledAmp = Math.round(avgAmp * 10);
updateKidLispAudio({
amp: scaledAmp,
// ... other properties
});
}
}
Microphone API Reference #
Class definition: /workspaces/aesthetic-computer/system/public/aesthetic.computer/lib/disk.mjs (line ~5715)
class Microphone {
amplitude = 0;
waveform = [];
pitch = 0;
connected = false;
recording = false;
recordingPromise;
permission = "";
connect(options) {
send({ type: "microphone", content: options });
return this;
}
poll() {
send({ type: "get-microphone-amplitude" });
send({ type: "get-microphone-waveform" });
send({ type: "get-microphone-pitch" });
}
// ... other methods
}
Usage examples from microphone.mjs:
function beat({ sound: { microphone } }) {
if (!mic) mic = microphone.connect();
}
function sim({ ... }) {
mic?.poll(); // Query for updated amplitude and waveform data
}
function paint({ ... }) {
if (mic?.waveform.length > 0 && mic?.amplitude !== undefined) {
const y = height - mic.amplitude * height;
ink(255, 16).line(0, y, width, y); // Amplitude visualization
}
}
Usage examples from baktok.mjs:
function boot({ sound: { microphone } }) {
mic = microphone;
if (mic.connected) {
connected = true;
}
}
function sim({ sound: { microphone } }) {
mic?.poll();
}
// Uses mic.amplitude directly for visualization
if (mic?.waveform.length > 0 && mic?.amplitude !== undefined) {
paintSound(api, mic.amplitude, mic.waveform, 0, 0, width, height);
}
🎨 Proposed Design #
1. Global Variable Definition #
Add to KidLisp constructor initialization (alongside amp, leftAmp, rightAmp):
this.globalDef.mic = 0; // Microphone amplitude (0-1 scale, matches native range)
Why 0→1 instead of 0→10 like amp?
- Native microphone amplitude already ranges 0→1
- Matches the natural output from
microphone.amplitude - More intuitive for audio processing (standard normalized range)
- Different scale helps distinguish speaker vs mic amplitude
2. Microphone Connection Management #
Challenge: The microphone requires explicit user permission, unlike speaker amplitude which is always available.
Solution: Lazy connection on first access (already implemented in function version)
Update the existing microphone handling in sim() (line ~3300):
sim: ({ sound }) => {
// ... existing timing code ...
// Poll speaker for amplitude
sound.speaker?.poll();
// Initialize microphone reference
if (!this.microphoneApi && sound?.microphone) {
this.microphoneApi = sound.microphone;
}
// Always poll microphone if available and connected
if (this.microphoneApi) {
this.microphoneApi.poll?.();
// Update connection status
if (this.microphoneApi.connected && !this.microphoneConnected) {
this.microphoneConnected = true;
console.log("🎤 Microphone connected in kidlisp");
}
// 🆕 UPDATE GLOBAL: Update mic global variable with current amplitude
if (this.microphoneApi.connected) {
this.globalDef.mic = this.microphoneApi.amplitude || 0;
}
}
// ... existing melody code ...
},
3. Permission Request Flow #
The microphone connection is already handled in the evaluator's mic function. We need to:
- Keep the function for backward compatibility and explicit connection control
- Add the global that uses the same underlying connection
- The global will return
0until connected (same as function)
Updated architecture:
// Function version (keep for explicit control)
mic: (api, args = []) => {
// Lazy connection logic (UNCHANGED)
if (this.microphoneApi && !this.microphoneApi.connected) {
if (
this.microphoneApi.permission === "granted" &&
api.sound?.enabled?.()
) {
this.microphoneApi.connect();
}
}
// Return amplitude from global (NEW - keep DRY)
return this.globalDef.mic;
},
Detection of mic usage in AST:
The containsMicrophoneFunctions() method (line ~2301) needs to check for both:
- Function call:
(mic) - Variable reference:
micused as a symbol
containsMicrophoneFunctions(ast) {
if (typeof ast === "string") {
return ast === "mic"; // Catches both function name and variable reference
}
if (Array.isArray(ast)) {
return ast.some((item) => this.containsMicrophoneFunctions(item));
}
return false;
}
4. Auto-Connection on Variable Access #
When mic is accessed as a variable, we need to trigger connection.
Problem: Globals are accessed via evaluate() → variable lookup, not via function evaluators.
Solution: Add a getter proxy pattern in the evaluation lookup:
// In the evaluate() function's symbol lookup section
// (around line ~4000-5000, need to find exact location)
// When looking up a symbol in globalDef:
if (this.globalDef.hasOwnProperty(expr)) {
const value = this.globalDef[expr];
// 🎤 Special case: Trigger mic connection on first access
if (expr === "mic" && !this.microphoneConnected) {
this.requestMicrophoneConnection(api);
}
return value;
}
Helper method:
requestMicrophoneConnection(api) {
if (this.microphoneApi && !this.microphoneApi.connected) {
if (
this.microphoneApi.permission === "granted" &&
api?.sound?.enabled?.()
) {
console.log("🎤 Auto-connecting microphone (mic global accessed)");
this.microphoneApi.connect();
} else if (this.microphoneApi.permission !== "granted") {
// Log request for permission (one-time)
if (!this.micPermissionRequested) {
console.log("🎤 Microphone permission needed for 'mic' global");
this.micPermissionRequested = true;
// Could trigger UI prompt here in the future
}
}
}
}
5. Reset Handling #
Update the reset logic (line ~1500) to include mic global:
// Reset audio amplitude globals
this.globalDef.amp = 0;
this.globalDef.leftAmp = 0;
this.globalDef.rightAmp = 0;
this.globalDef.mic = 0; // 🆕 ADD THIS
// Reset microphone state
this.microphoneConnected = false;
this.microphoneApi = null;
this.micPermissionRequested = false; // 🆕 ADD THIS
📁 Files to Modify #
Primary Changes #
/workspaces/aesthetic-computer/system/public/aesthetic.computer/lib/kidlisp.mjs- Add
this.globalDef.mic = 0in constructor (~line 872) - Add
this.micPermissionRequested = falsein constructor - Update
sim()to updateglobalDef.micfrommicrophoneApi.amplitude(~line 3300) - Add
requestMicrophoneConnection()helper method - Modify symbol evaluation to trigger connection on
micaccess - Update
micfunction to returnthis.globalDef.mic - Add
micreset in reset logic (~line 1500) - Update documentation comment at top of file
- Add
Documentation Updates #
-
/workspaces/aesthetic-computer/kidlisp/README.md- Update Audio section to document
micglobal - Add usage examples comparing function vs global
- Update Audio section to document
-
/workspaces/aesthetic-computer/kidlisp/COMPLETE_API_MAP.md- Update Audio Input section with
micglobal details
- Update Audio Input section with
-
/workspaces/aesthetic-computer/kidlisp/tools/api-summary.mjs- Ensure
micis listed in audio category (already present at line 27)
- Ensure
🧪 Testing Strategy #
Test Cases #
-
Basic Usage
(wipe black) (ink red) (box 10 10 (* mic 100) 20) ; Bar grows with mic amplitude -
Permission Flow
; First frame: mic = 0 (not connected) ; User grants permission ; Subsequent frames: mic = 0.0 to 1.0 based on sound (write (+ "Mic: " mic) 10 10) -
Mixed Function/Global Usage
(def vol (mic)) ; Function call (backward compat) (def vol2 mic) ; Global access (new way) ; Both should work identically -
Animation Response
(wipe navy) (ink yellow) (circle (/ width 2) (/ height 2) (* mic 100)) ; Grows with sound -
Auto-Detection
- Piece using
micshould auto-request connection inboot() - Already handled by
containsMicrophoneFunctions()check
- Piece using
Manual Testing #
- Load a test piece with
micglobal - Verify permission request appears
- Grant permission
- Speak/play sound near microphone
- Verify visual responds to amplitude
- Check console for connection logs
- Reload piece - verify auto-reconnect works
Edge Cases #
- ✅ Mic accessed before permission granted → returns 0
- ✅ Mic accessed after permission denied → returns 0
- ✅ Mic accessed while connecting → returns 0
- ✅ Piece reload preserves permission state
- ✅ Multiple pieces using mic don't conflict
🔄 Migration Path #
Backward Compatibility: ✅ FULL
- Old code using
(mic)continues to work - New code can use
micglobal - Both access the same underlying amplitude value
- No breaking changes
Recommended Usage Going Forward:
; ❌ Old way (still works)
(circle x y (mic))
; ✅ New way (cleaner)
(circle x y mic)
; 🎯 Best practice: Use global for values, function for control
mic ; Read amplitude
(mic) ; Explicitly trigger connection (if needed)
🎯 Success Criteria #
📝 Implementation Order #
- Add
globalDef.micinitialization - Add
micPermissionRequestedflag - Update
sim()to populateglobalDef.mic - Add
requestMicrophoneConnection()helper - Modify symbol evaluation to trigger connection
- Update
micfunction to use global - Update reset logic
- Test basic functionality
- Update documentation
- Test edge cases
🎨 Example Usage Scenarios #
Visualizer #
(wipe black)
(ink rainbow)
(repeat 20 i
(circle (/ width 2) (/ height 2) (* mic i 10)))
Reactive Animation #
(wipe navy)
(ink yellow (* mic 255))
(circle (/ width 2) (/ height 2) (+ 20 (* mic 80)))
Audio Level Meter #
(wipe black)
(ink (? red green blue))
(box 10 (- height 10) (* mic width) 10 "*bottom-left")
(ink white)
(write (+ "Level: " (* mic 100) "%") 10 10)
Multi-Source Audio Display #
; Show both speaker and mic levels
(wipe black)
(ink blue)
(box 10 10 (* amp 20) 100) ; Speaker (0-10 scale)
(ink red)
(box 10 120 (* mic width) 20) ; Microphone (0-1 scale)
🔗 Related Systems #
- Sound System (
disk.mjs): Microphone class and connection handling - BIOS (
bios.mjs): Permission management and audio worklet - Existing Pieces:
microphone.mjs,baktok.mjs,whistle.mjs,amp.mjs - Audio Globals:
amp,leftAmp,rightAmp(speaker amplitude)
🚀 Future Enhancements (Out of Scope) #
micPitchglobal for pitch detectionmicWaveformglobal for waveform data array- Visual permission prompt UI
- Microphone sensitivity adjustment
- Recording functionality globals
- Multiple audio input sources
💡 Notes #
- The 0→1 scale for
micmatches the native microphone amplitude range - Different from
amp(0→10) to distinguish speaker vs microphone - Could add
mic10ormicScaledglobal for 0→10 scale if needed - Permission state persists across piece loads (browser manages this)
- The
poll()call is essential - happens insim()at 120fps
Love to you too, Geoffrey! 💙 Let's make sound reactive art easier than ever!