Upload files to "/"
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
// 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();
|
||||
Reference in New Issue
Block a user