Medium Pool · every medium your app could speak
$npx skills add funclosure/medium-pool

Audio

Sound the app hears and makes
messageThe app becomes a voice in the room, with the intimacy and the intrusion of one.
instance Voice recorderCapture, visualize, play back

Voice is the medium people already have; no keyboard, no reading. Audio lets an app listen (dictation, voice notes, commands) and speak (narration, alerts, generated speech). It is also the medium with the strongest privacy expectations: a live microphone is a promise.

On the web the whole pipeline is available: getUserMedia to capture, Web Audio to analyze and synthesize, MediaRecorder to encode.

Reach for it when

  • hands or eyes are busy (driving, cooking, walking)
  • tone and emotion carry information that text loses
  • input is faster spoken than typed, like long notes
direction
both ways
substrate
getUserMedia · Web Audio · MediaRecorder

Where it bites

  • An AudioContext must be created or resumed inside a user gesture, or it stays suspended and silent.
  • Embedded frames can block the microphone via Permissions-Policy before the user ever sees a prompt. Design the no-mic state first.
  • MediaRecorder codecs differ: Chrome gives webm/opus, Safari gives mp4/aac. Decode with decodeAudioData instead of assuming a format.
  • Stop every track when the user leaves, or the red recording indicator stays on and trust is gone.
  • Show the waveform. Silence with no feedback reads as broken.

Instances of this medium

Each is one way to build it. None of them is the medium.

Voice recorderin the pool

Capture, visualize, play back. reference/Recorder.ts

wavesurfer.jsopen source

Waveform rendering and interaction over Web Audio and audio elements.

Tone.jsopen source

Web Audio framework for synthesis and scheduling. The test-tone path, grown up.

$npx skills add funclosure/medium-pool --skill medium-audio
skills/medium-audio/SKILL.md
---
name: medium-audio
description: "Build microphone capture, waveform visualization, recording, and playback on the web. Use when adding voice notes, dictation, voice commands, or audio feedback."
---

# Audio as a medium

## What this medium does to the person
A live microphone or a speaking app is a presence in the room. It is intimate, hands-free, and emotional, and it is also an intrusion, so it must ask, show that it is listening, and leave when told.

## When to reach for this medium
Choose audio when hands or eyes are busy, when tone matters, or when speaking beats typing. Treat a live microphone as a promise to the user.

## Core browser APIs
- `navigator.mediaDevices.getUserMedia({ audio: true })` for capture
- Web Audio: `AudioContext`, `AnalyserNode` for waveforms, `OscillatorNode` for synthesized feedback
- `MediaRecorder` for encoding; `decodeAudioData` + `AudioBufferSourceNode` for playback

## UX rules that always apply
- Nothing captures until a user gesture. Create or resume the AudioContext inside that gesture.
- Always show a live level or waveform; silence with no feedback reads as broken.
- A Stop control that releases every track, visibly.
- A working state with no microphone: permission denied, no device, or an embedded frame that blocks it.

## Reference instances
Each is one way to speak this medium, cited as evidence. None of them is the medium.

`reference/Recorder.ts` — mic capture with waveform, MediaRecorder recording, decode-and-play playback, synthesized fallback tone, and full teardown on unmount. It uses a few layout class names from the pool's stylesheet (`row`, `btn`, `status`, `frame`); without that stylesheet the logic still runs and the controls are unstyled.

## Gotchas
- Codec output differs by browser; never assume webm.
- Avoid `blob:` URLs in locked-down environments; decode to a buffer instead.
- Release tracks on unmount or the recording indicator persists.