curiositech/win31-audio-design
Expert in Windows 3.1 era sound vocabulary for modern web/mobile apps. Creates satisfying retro UI sounds using CC-licensed 8-bit audio, Web Audio API, and haptic coordination. Activate on 'win31 sounds', 'retro audio', '90s sound effects', 'chimes', 'tada', 'ding', 'satisfying UI sounds'. NOT for modern flat UI sounds, voice synthesis, or music composition.
npx skills add https://github.com/curiositech/some_claude_skills --skill win31-audio-design
Expert in creating authentic Windows 3.1 era sound experiences for modern web and mobile applications. Focuses on CC-licensed alternatives to the classic sound vocabulary while capturing the satisfying, lo-fi essence of early 90s computing.
Use for:
Do NOT use for:
| File | Character | Duration | Function |
|------|-----------|----------|----------|
| CHIMES.WAV | Ethereal bells | ~1.5s | System notifications |
| CHORD.WAV | Major chord resolve | ~1.2s | Task completion |
| DING.WAV | Single bell strike | ~0.5s | Attention/alert |
| TADA.WAV | Triumphant fanfare | ~2s | Startup/major success |
| RINGIN.WAV | Rising tone | ~0.3s | Modal open |
| RINGOUT.WAV | Falling tone | ~0.2s | Modal close |
Legal Warning: These sounds are copyrighted by Microsoft. We create INSPIRED alternatives, never copies.
| Quality | Win31 Sound Profile |
|---------|---------------------|
| Sample Rate | 11kHz-22kHz (lo-fi charm) |
| Bit Depth | 8-bit (quantization warmth) |
| Frequency Range | 400Hz-4kHz (no sub-bass, gentle highs) |
| Envelope | Fast attack, medium decay, no sustain |
| Reverb | Dry or short room (no cathedral halls) |
| Character | Bright, plasticky, cheerful |
┌─────────────────────────────────────────────────────┐
│ Attack: 5-10ms │ Pure sine starts BRIGHT │
│ Peak: ~880Hz (A5) │ Classic 8-bit "bleep" │
│ Decay: 200-400ms │ Natural damping feel │
│ Overtones: 2nd+3rd│ Bell-like shimmer │
│ Processing: 8-bit │ Adds warmth via quantization │
└─────────────────────────────────────────────────────┘
| Source | License | Best For |
|--------|---------|----------|
| Dominik Braun's 107 Retro Sounds | CC BY 4.0 | UI blips, beeps, positive feedback |
| LittleRobotSoundFactory (Freesound) | CC0/CC BY | 8-bit game sounds |
| OpenGameArt 512 SFX | CC0 | Comprehensive retro library |
| Gritty Retro UI (Exechamp) | CC0 | UI-specific clicks and tones |
Audio: [Sound Name] by [Creator] from [Source]
Licensed under [CC BY 4.0 / CC0]
https://[source-url]
| Action | Sound Character | Duration | Frequency |
|--------|-----------------|----------|-----------|
| Button click | Soft plastic "tik" | 20-40ms | 800-1200Hz |
| Button release | Subtle "tok" | 15-30ms | 600-900Hz |
| Toggle on | Rising chirp | 80-120ms | 600→1200Hz |
| Toggle off | Falling chirp | 80-120ms | 1200→600Hz |
| Window open | Ascending arpeggio | 150-250ms | C4-E4-G4 |
| Window close | Descending arpeggio | 100-200ms | G4-E4-C4 |
| Error | Low buzz + descending | 300-500ms | 200-400Hz |
| Success | Bright ding + shimmer | 200-400ms | 880Hz + harmonics |
| Notification | Double chime | 400-600ms | 660Hz, 880Hz |
| Event | Sound Character | Duration |
|-------|-----------------|----------|
| Startup | Triumphant chord resolve | 1.5-2.5s |
| Shutdown | Gentle descending phrase | 1-2s |
| Critical error | Harsh double-buzz | 400-600ms |
| Task complete | Satisfying "da-ding!" | 300-500ms |
| Navigation | Soft whoosh + settle | 100-200ms |
// Win31-style sound manager for web
class Win31SoundManager {
private audioContext: AudioContext | null = null;
private sounds: Map<string, AudioBuffer> = new Map();
constructor() {
// Lazy init on first user interaction (browser policy)
}
private getContext(): AudioContext {
if (!this.audioContext) {
this.audioContext = new AudioContext({ sampleRate: 22050 }); // Lo-fi!
}
return this.audioContext;
}
async loadSound(name: string, url: string) {
const response = await fetch(url);
const arrayBuffer = await response.arrayBuffer();
const audioBuffer = await this.getContext().decodeAudioData(arrayBuffer);
this.sounds.set(name, audioBuffer);
}
play(name: string, volume = 0.5) {
const buffer = this.sounds.get(name);
if (!buffer) return;
const ctx = this.getContext();
const source = ctx.createBufferSource();
const gain = ctx.createGain();
source.buffer = buffer;
gain.gain.value = volume;
source.connect(gain);
gain.connect(ctx.destination);
source.start(0);
}
// Procedural 8-bit "ding"
playDing(frequency = 880) {
const ctx = this.getContext();
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'triangle'; // Softer than sine, more 8-bit
osc.frequency.setValueAtTime(frequency, ctx.currentTime);
// Fast attack, medium decay
gain.gain.setValueAtTime(0.3, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.4);
osc.connect(gain);
gain.connect(ctx.destination);
osc.start(ctx.currentTime);
osc.stop(ctx.currentTime + 0.4);
}
}
export const win31Sounds = new Win31SoundManager();
// Generate Win31-style sounds procedurally
function createChime(ctx: AudioContext, baseFreq = 660): AudioBufferSourceNode {
const duration = 1.5;
const sampleRate = ctx.sampleRate;
const buffer = ctx.createBuffer(1, duration * sampleRate, sampleRate);
const data = buffer.getChannelData(0);
// Two-tone chime (like CHIMES.WAV character)
for (let i = 0; i < data.length; i++) {
const t = i / sampleRate;
const env = Math.exp(-t * 3); // Decay envelope
// Two harmonically related tones
const tone1 = Math.sin(2 * Math.PI * baseFreq * t);
const tone2 = Math.sin(2 * Math.PI * baseFreq * 1.5 * t) * 0.5;
data[i] = (tone1 + tone2) * env * 0.3;
}
// 8-bit quantization for authentic lo-fi
for (let i = 0; i < data.length; i++) {
data[i] = Math.round(data[i] * 127) / 127;
}
const source = ctx.createBufferSource();
source.buffer = buffer;
return source;
}
| Sound Event | Haptic Type | Intensity |
|-------------|-------------|-----------|
| Button click | .light | 0.5 |
| Toggle switch | .medium | 0.6 |
| Error buzz | .heavy | 0.9 |
| Success ding | .selection | 0.4 |
| Window open | .soft | 0.3 |
import * as Haptics from 'expo-haptics';
async function playWithHaptic(soundName: string, hapticType: Haptics.ImpactFeedbackStyle) {
// Fire both simultaneously
await Promise.all([
win31Sounds.play(soundName),
Haptics.impactAsync(hapticType),
]);
}
// Usage
await playWithHaptic('click', Haptics.ImpactFeedbackStyle.Light);
await playWithHaptic('error', Haptics.ImpactFeedbackStyle.Heavy);
What it looks like: 48kHz, 24-bit crystal-clear audio
Why wrong: Sounds too modern, loses retro charm
Instead: Downsample to 22kHz, apply 8-bit quantization
What it looks like: Sounds echoing in a cathedral
Why wrong: Win31 sounds were DRY or short room
Instead: No reverb or <100ms decay
What it looks like: Deep rumbling under 100Hz
Why wrong: 90s PC speakers couldn't reproduce sub-bass
Instead: Cut everything below 200Hz
What it looks like: Using actual CHIMES.WAV or TADA.WAV
Why wrong: Copyright infringement
Instead: Create inspired alternatives with CC-licensed sources
What it looks like: Every micro-interaction makes noise
Why wrong: Becomes annoying, fatiguing
Instead: Sounds for primary actions only, user toggle for audio
| Category | Level | Notes |
|----------|-------|-------|
| UI feedback | -24 to -18 dB | Subtle, never intrusive |
| Notifications | -18 to -12 dB | Attention-getting but not loud |
| Errors | -15 to -9 dB | Noticeable but not jarring |
| System sounds | -12 to -6 dB | Major events (startup/shutdown) |
Always provide:
Core insight: Win31 sounds were satisfying because they were SIMPLE—short, bright, lo-fi tones that gave immediate feedback without being distracting. The 8-bit quantization and limited frequency response added warmth, not harshness. Capture that spirit with CC-licensed alternatives, never copies.
Remember: Always include a sound toggle. Never play sounds without user consent. Pair audio with haptics on mobile for maximum satisfaction.
Take curiositech/win31-audio-design from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.