feat: create a large randomized library of playful game sounds and audio combos
This commit is contained in:
+23
-10
@@ -51,20 +51,22 @@
|
||||
if (!voices.delete(voice)) return;
|
||||
timer.clearTimeout(voice.timeout);
|
||||
try { voice.source.stop(); } catch (_) { /* It may have ended naturally. */ }
|
||||
disconnect(voice.source); disconnect(voice.filter); disconnect(voice.gain);
|
||||
disconnect(voice.source); disconnect(voice.filter); disconnect(voice.gain); disconnect(voice.panner);
|
||||
}
|
||||
function makeVoice(priority, durationMs, sourceFactory) {
|
||||
function makeVoice(priority, durationMs, sourceFactory, settings = {}) {
|
||||
if (!preferences.enabled || hidden || !ensureContext() || voices.size >= MAX_VOICES && !replaceVoice(priority)) return false;
|
||||
const startedAt = context.currentTime;
|
||||
const startedAt = context.currentTime + Math.max(0, settings.delayMs || 0) / 1000;
|
||||
const duration = Math.max(0.1, Math.min(durationMs, 2500) / 1000) * (preferences.reduced ? 0.65 : 1);
|
||||
let source; let gain; let filter;
|
||||
let source; let gain; let filter; let panner;
|
||||
try {
|
||||
source = sourceFactory(); gain = context.createGain(); filter = context.createBiquadFilter();
|
||||
filter.type = 'lowpass'; filter.frequency.setValueAtTime(2400, startedAt);
|
||||
gain.gain.setValueAtTime(0.0001, startedAt); gain.gain.exponentialRampToValueAtTime(0.23, startedAt + 0.012); gain.gain.exponentialRampToValueAtTime(0.0001, startedAt + duration);
|
||||
source.connect(filter); filter.connect(gain); gain.connect(master); source.start(startedAt); source.stop(startedAt + duration + 0.02);
|
||||
} catch (_) { disconnect(source); disconnect(filter); disconnect(gain); return false; }
|
||||
const voice = { source, filter, gain, priority, timeout: undefined };
|
||||
filter.type = 'lowpass'; filter.frequency.setValueAtTime(settings.filterHz || 2400, startedAt);
|
||||
const peak = Math.min(0.75, Math.max(0.04, settings.gain || 0.55));
|
||||
gain.gain.setValueAtTime(0.0001, startedAt); gain.gain.exponentialRampToValueAtTime(peak, startedAt + 0.012); gain.gain.exponentialRampToValueAtTime(0.0001, startedAt + duration);
|
||||
if (typeof context.createStereoPanner === 'function') { panner = context.createStereoPanner(); panner.pan.setValueAtTime(Math.max(-0.4, Math.min(0.4, settings.pan || 0)), startedAt); }
|
||||
source.connect(filter); filter.connect(gain); if (panner) { gain.connect(panner); panner.connect(master); } else gain.connect(master); source.start(startedAt); source.stop(startedAt + duration + 0.02);
|
||||
} catch (_) { disconnect(source); disconnect(filter); disconnect(gain); disconnect(panner); return false; }
|
||||
const voice = { source, filter, gain, panner, priority, timeout: undefined };
|
||||
voice.timeout = timer.setTimeout(() => release(voice), Math.ceil(duration * 1000) + 80);
|
||||
voices.add(voice); return true;
|
||||
}
|
||||
@@ -92,13 +94,24 @@
|
||||
const oscillator = context.createOscillator(); oscillator.type = kind === 'major' ? 'triangle' : 'sine'; oscillator.frequency.setValueAtTime(kind === 'major' ? 392 : 660, context.currentTime); oscillator.frequency.exponentialRampToValueAtTime(kind === 'major' ? 196 : 440, context.currentTime + 0.16); return oscillator;
|
||||
});
|
||||
}
|
||||
function playPreset(preset) {
|
||||
if (!preset || !preferences.enabled || hidden || !ensureContext()) return false;
|
||||
return makeVoice(preset.priority || 1, preset.durationMs || 180, () => {
|
||||
if (preset.noise) { const source = context.createBufferSource(); source.buffer = ensureNoise(); return source; }
|
||||
const oscillator = context.createOscillator(); oscillator.type = preset.wave || 'sine';
|
||||
const startedAt = context.currentTime + Math.max(0, preset.delayMs || 0) / 1000;
|
||||
oscillator.frequency.setValueAtTime(Math.max(100, preset.frequency || 440), startedAt);
|
||||
oscillator.frequency.exponentialRampToValueAtTime(Math.max(100, preset.endFrequency || preset.frequency || 440), startedAt + Math.min(0.8, (preset.durationMs || 180) / 1000));
|
||||
return oscillator;
|
||||
}, preset);
|
||||
}
|
||||
async function setPageHidden(nextHidden) {
|
||||
hidden = Boolean(nextHidden); cancel();
|
||||
if (!context) return;
|
||||
try { if (hidden) await context.suspend(); else if (preferences.enabled && context.state === 'suspended') await context.resume(); } catch (_) { /* Sound remains optional. */ }
|
||||
}
|
||||
function cleanup() { cancel(); lastVersion = -1; if (context) { try { context.close(); } catch (_) { /* Already closed. */ } } context = undefined; master = undefined; limiter = undefined; noiseBuffer = undefined; }
|
||||
return { getPreferences: () => ({ ...preferences }), enable, setEnabled, setVolume: volume => { preferences.volume = validVolume(volume); save(); updateGain(); }, setReduced: reduced => { preferences.reduced = Boolean(reduced); save(); }, play, cancel, setPageHidden, cleanup, getVoiceCount: () => voices.size, getNoiseBuffer: () => noiseBuffer, getMaxGain: () => MAX_GAIN, now };
|
||||
return { getPreferences: () => ({ ...preferences }), enable, setEnabled, setVolume: volume => { preferences.volume = validVolume(volume); save(); updateGain(); }, setReduced: reduced => { preferences.reduced = Boolean(reduced); save(); }, play, playPreset, cancel, setPageHidden, cleanup, getVoiceCount: () => voices.size, getNoiseBuffer: () => noiseBuffer, getMaxGain: () => MAX_GAIN, now };
|
||||
}
|
||||
|
||||
const api = { createAudioEngine, MAX_VOICES, MAX_GAIN, VOLUME_GAINS, PREFERENCES };
|
||||
|
||||
Reference in New Issue
Block a user