Files
Pip---the-Whispering-Cloud/audio.ts
T
2026-09-14 23:34:36 +08:00

251 lines
6.6 KiB
TypeScript

// Web Audio API Synthesizer for Children's Storybook
class SoundEffectsManager {
private ctx: AudioContext | null = null;
public enabled: boolean = true;
private initCtx() {
if (!this.ctx && typeof window !== 'undefined') {
const AudioCtx = window.AudioContext || (window as unknown as { webkitAudioContext: typeof AudioContext }).webkitAudioContext;
if (AudioCtx) {
this.ctx = new AudioCtx();
}
}
if (this.ctx && this.ctx.state === 'suspended') {
this.ctx.resume();
}
}
// Soft cheerful pop
playPop() {
if (!this.enabled) return;
this.initCtx();
if (!this.ctx) return;
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'sine';
const now = this.ctx.currentTime;
osc.frequency.setValueAtTime(400, now);
osc.frequency.exponentialRampToValueAtTime(800, now + 0.08);
gain.gain.setValueAtTime(0.18, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.09);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start(now);
osc.stop(now + 0.1);
}
// Pentatonic magical chime
playChime() {
if (!this.enabled) return;
this.initCtx();
if (!this.ctx) return;
const notes = [523.25, 659.25, 783.99, 1046.5]; // C5, E5, G5, C6
notes.forEach((freq, idx) => {
if (!this.ctx) return;
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'triangle';
const now = this.ctx.currentTime + idx * 0.07;
osc.frequency.setValueAtTime(freq, now);
gain.gain.setValueAtTime(0.12, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.35);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start(now);
osc.stop(now + 0.4);
});
}
// Soft gentle raindrop plop
playRain() {
if (!this.enabled) return;
this.initCtx();
if (!this.ctx) return;
const drops = [700, 1100, 850];
drops.forEach((freq, i) => {
if (!this.ctx) return;
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
const now = this.ctx.currentTime + i * 0.08;
osc.type = 'sine';
osc.frequency.setValueAtTime(freq, now);
osc.frequency.exponentialRampToValueAtTime(freq * 0.5, now + 0.07);
gain.gain.setValueAtTime(0.14, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.12);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start(now);
osc.stop(now + 0.15);
});
}
// Harp strum
playHarp() {
if (!this.enabled) return;
this.initCtx();
if (!this.ctx) return;
const notes = [392.00, 523.25, 659.25, 783.99, 1046.5]; // G4, C5, E5, G5, C6
notes.forEach((freq, i) => {
if (!this.ctx) return;
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
const now = this.ctx.currentTime + i * 0.05;
osc.type = 'sine';
osc.frequency.setValueAtTime(freq, now);
gain.gain.setValueAtTime(0.1, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.6);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start(now);
osc.stop(now + 0.65);
});
}
// Flutter / whoosh
playFlutter() {
if (!this.enabled) return;
this.initCtx();
if (!this.ctx) return;
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
const now = this.ctx.currentTime;
osc.type = 'sine';
osc.frequency.setValueAtTime(220, now);
osc.frequency.linearRampToValueAtTime(440, now + 0.1);
osc.frequency.linearRampToValueAtTime(260, now + 0.2);
gain.gain.setValueAtTime(0.08, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.22);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start(now);
osc.stop(now + 0.25);
}
// Playful giggle / chirp
playGiggle() {
if (!this.enabled) return;
this.initCtx();
if (!this.ctx) return;
const notes = [587.33, 739.99, 587.33, 880.00];
notes.forEach((freq, i) => {
if (!this.ctx) return;
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
const now = this.ctx.currentTime + i * 0.06;
osc.type = 'triangle';
osc.frequency.setValueAtTime(freq, now);
gain.gain.setValueAtTime(0.09, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.1);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start(now);
osc.stop(now + 0.12);
});
}
// Page turn sound
playPageTurn() {
if (!this.enabled) return;
this.initCtx();
if (!this.ctx) return;
const bufferSize = this.ctx.sampleRate * 0.15;
const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
data[i] = (Math.random() * 2 - 1) * Math.exp(-i / (bufferSize * 0.3));
}
const noise = this.ctx.createBufferSource();
noise.buffer = buffer;
const filter = this.ctx.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.value = 1200;
const gain = this.ctx.createGain();
const now = this.ctx.currentTime;
gain.gain.setValueAtTime(0.12, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.14);
noise.connect(filter);
filter.connect(gain);
gain.connect(this.ctx.destination);
noise.start(now);
}
// Star Celebration fanfare
playStarCelebration() {
if (!this.enabled) return;
this.initCtx();
if (!this.ctx) return;
const notes = [523.25, 659.25, 783.99, 1046.50, 1318.51];
notes.forEach((freq, i) => {
if (!this.ctx) return;
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
const now = this.ctx.currentTime + i * 0.08;
osc.type = 'sine';
osc.frequency.setValueAtTime(freq, now);
gain.gain.setValueAtTime(0.15, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.45);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start(now);
osc.stop(now + 0.5);
});
}
playSound(type: 'pop' | 'chime' | 'rain' | 'harp' | 'flutter' | 'giggle') {
switch (type) {
case 'pop': this.playPop(); break;
case 'chime': this.playChime(); break;
case 'rain': this.playRain(); break;
case 'harp': this.playHarp(); break;
case 'flutter': this.playFlutter(); break;
case 'giggle': this.playGiggle(); break;
default: this.playPop();
}
}
}
export const soundManager = new SoundEffectsManager();