Always have audio context and sound manager available.

This commit is contained in:
flash 2024-02-10 02:26:38 +00:00
parent 3bc595c3ee
commit fc58537974
9 changed files with 99 additions and 84 deletions

View file

@ -1,6 +1,5 @@
const MamiAudioBuffer = function(ctx, buffer) { const MamiAudioBuffer = function(ctx, buffer) {
return { return {
getBuffer: () => buffer,
createSource: () => ctx.createSource(buffer), createSource: () => ctx.createSource(buffer),
}; };
}; };

View file

@ -5,53 +5,75 @@
const MamiAudioContext = function() { const MamiAudioContext = function() {
const pub = {}; const pub = {};
let volume = null; let volume;
let isMuted = false; let isMuted = false;
const ctxArgs = { latencyHint: 'playback' }; const ctxArgs = { latencyHint: 'playback' };
let ctx = null; let ctx, mainGain;
let mainGain = null;
const init = () => { const onReset = [];
const reset = () => {
const audObj = window.AudioContext || window.webkitAudioContext; const audObj = window.AudioContext || window.webkitAudioContext;
try { try {
ctx = new audObj(ctxArgs); ctx = new audObj(ctxArgs);
} catch(ex) { } catch(ex) {
ctx = new audObj; ctx = new audObj;
} }
mainGain = ctx.createGain(); mainGain = ctx.createGain();
if(volume === null) { if(volume === undefined) {
volume = mainGain.gain.defaultValue; volume = mainGain.gain.defaultValue;
isMuted = false; isMuted = false;
} else } else
mainGain.gain.value = isMuted ? 0 : volume; mainGain.gain.value = isMuted ? 0 : volume;
mainGain.connect(ctx.destination); mainGain.connect(ctx.destination);
for(const handler of onReset)
handler();
}; };
init(); reset();
pub.getContext = () => ctx; pub.getAudioContext = () => ctx;
pub.resetContext = init; pub.getAudioContextGain = () => mainGain;
pub.isReady = () => ctx !== undefined;
pub.reset = reset;
pub.onReset = handler => {
if(!onReset.includes(handler))
onReset.push(handler);
};
pub.getVolume = () => volume; pub.getVolume = () => volume;
pub.setVolume = vol => { pub.setVolume = vol => {
volume = vol; volume = vol;
if(!isMuted) if(!isMuted && mainGain !== undefined)
mainGain.gain.value = volume; mainGain.gain.value = volume;
}; };
pub.isMuted = () => isMuted; pub.isMuted = () => isMuted;
pub.setMuted = mute => { pub.setMuted = mute => {
mainGain.gain.value = (isMuted = mute) ? 0 : volume; isMuted = mute;
if(mainGain !== undefined)
mainGain.gain.value = isMuted ? 0 : volume;
}; };
pub.createBuffer = async url => { pub.createBuffer = async url => {
if(ctx === undefined)
return new MamiAudioBuffer(pub);
const result = await $x.get(url, { type: 'arraybuffer' }); const result = await $x.get(url, { type: 'arraybuffer' });
const buffer = await ctx.decodeAudioData(result.body()); const buffer = await ctx.decodeAudioData(result.body());
return new MamiAudioBuffer(pub, buffer); return new MamiAudioBuffer(pub, buffer);
}; };
pub.createSource = buffer => { pub.createSource = buffer => {
if(ctx === undefined)
return new MamiAudioSourceDummy;
const gain = ctx.createGain(); const gain = ctx.createGain();
gain.connect(mainGain); gain.connect(mainGain);

View file

@ -1,3 +1,19 @@
const MamiAudioSourceDummy = function() {
return {
play: async () => {},
stop: () => {},
getRate: () => 0,
setRate: rate => {},
getDetune: () => 0,
setDetune: rate => {},
getVolume: () => 0,
setVolume: volume => {},
isMuted: () => true,
setMuted: mute => {},
setLoop: (loop, start, end) => {},
};
};
const MamiAudioSource = function(source, gain, buffer) { const MamiAudioSource = function(source, gain, buffer) {
const pub = {}; const pub = {};
@ -6,8 +22,6 @@ const MamiAudioSource = function(source, gain, buffer) {
let hasDisconnected = false; let hasDisconnected = false;
pub.getSource = () => source;
const play = () => { const play = () => {
return new Promise(resolve => { return new Promise(resolve => {
source.addEventListener('ended', () => resolve()); source.addEventListener('ended', () => resolve());

View file

@ -17,41 +17,20 @@ const MamiContext = function(targetBody) {
const viewsCtx = new MamiViewsControl({ body: targetBody }); const viewsCtx = new MamiViewsControl({ body: targetBody });
pub.getViews = () => viewsCtx; pub.getViews = () => viewsCtx;
let audioCtx = null; const audioCtx = new MamiAudioContext;
pub.hasAudio = function() { return audioCtx !== null; }; pub.getAudio = () => audioCtx;
pub.getAudio = function() { return audioCtx; };
const initAudio = function() {
if(audioCtx !== null)
return;
audioCtx = new MamiAudioContext; const soundMgr = new MamiSoundManager(audioCtx);
}; const soundLib = new MamiSoundLibrary;
pub.initAudio = initAudio; const soundPck = new MamiSoundPacks;
const sndPckPlay = new MamiSoundPackPlayer(soundMgr, soundLib);
let soundMgr = null; pub.getSound = () => soundMgr;
let sndPckPlay = null; pub.getSoundLibrary = () => soundLib;
const soundLib = new MamiSoundLibrary(); pub.getSoundPacks = () => soundPck;
const soundPck = new MamiSoundPacks(); pub.getSoundPackPlayer = () => sndPckPlay;
pub.initSound = function() {
if(soundMgr !== null)
return;
initAudio();
soundMgr = new MamiSoundManager(audioCtx);
sndPckPlay = new MamiSoundPackPlayer(soundMgr, soundLib);
};
pub.getSound = function() { return soundMgr; };
pub.hasSound = function() { return soundMgr !== null; };
pub.getSoundLibrary = function() { return soundLib; };
pub.getSoundPacks = function() { return soundPck; };
pub.getSoundPackPlayer = function() { return sndPckPlay; };
pub.playLibrarySound = async (name, volume, rate) => { pub.playLibrarySound = async (name, volume, rate) => {
if(soundMgr === null)
return;
const buffer = await soundMgr.load(soundLib.getSoundSources(name)); const buffer = await soundMgr.load(soundLib.getSoundSources(name));
const source = buffer.createSource(); const source = buffer.createSource();
@ -65,12 +44,12 @@ const MamiContext = function(targetBody) {
}; };
const txtTriggers = new MamiTextTriggers; const txtTriggers = new MamiTextTriggers;
pub.getTextTriggers = function() { return txtTriggers; }; pub.getTextTriggers = () => txtTriggers;
let eeprom; let eeprom;
pub.hasEEPROM = () => eeprom !== undefined; pub.hasEEPROM = () => eeprom !== undefined;
pub.getEEPROM = () => eeprom; pub.getEEPROM = () => eeprom;
pub.createEEPROM = function() { pub.createEEPROM = () => {
if(eeprom !== undefined) if(eeprom !== undefined)
return; return;

View file

@ -157,8 +157,10 @@ const Umi = { UI: {} };
} }
settings.watch('soundEnable', (v, n, i) => { settings.watch('soundEnable', (v, n, i) => {
if(v && !mami.hasSound()) { if(v) {
mami.initSound(); const audio = mami.getAudio();
if(!audio.isReady())
audio.reset();
settings.touch('soundVolume'); settings.touch('soundVolume');
settings.touch('soundPack'); settings.touch('soundPack');
@ -168,8 +170,7 @@ const Umi = { UI: {} };
player.playEvent('server'); player.playEvent('server');
} }
if(mami.hasAudio()) mami.getAudio().setMuted(!v);
mami.getAudio().setMuted(!v);
}); });
settings.watch('soundPack', (v, n, i) => { settings.watch('soundPack', (v, n, i) => {
@ -187,8 +188,7 @@ const Umi = { UI: {} };
}); });
settings.watch('soundVolume', v => { settings.watch('soundVolume', v => {
if(mami.hasAudio()) mami.getAudio().setVolume(v / 100);
mami.getAudio().setVolume(v / 100);
}) })
@ -248,10 +248,12 @@ const Umi = { UI: {} };
settings.watch('tmpDisableOldThemeSys', (v, n, i) => { if(!i) Umi.UI.View.AccentReload(); }); settings.watch('tmpDisableOldThemeSys', (v, n, i) => { if(!i) Umi.UI.View.AccentReload(); });
settings.watch('minecraft', (v, n, i) => { settings.watch('minecraft', (v, n, i) => {
if(i) if(v !== 'no') {
mami.playLibrarySound('minecraft:nether:enter'); if(i)
else mami.playLibrarySound('minecraft:nether:enter');
Umi.Sound.Play('join'); else
Umi.Sound.Play('join');
}
}); });
settings.watch('enableNotifications', v => { settings.watch('enableNotifications', v => {

View file

@ -105,9 +105,6 @@ Umi.Protocol.SockChat.Protocol = function(views, settings) {
await mami.playLibrarySound('touhou:pichuun'); await mami.playLibrarySound('touhou:pichuun');
}; };
const playBannedBgm = async preload => { const playBannedBgm = async preload => {
if(!mami.hasSound())
return;
const soundMgr = mami.getSound(); const soundMgr = mami.getSound();
const soundLib = mami.getSoundLibrary(); const soundLib = mami.getSoundLibrary();

View file

@ -1,6 +1,4 @@
#include utility.js #include utility.js
#include sound/sndlibrary.js
#include sound/sndpacks.js
const MamiSoundManager = function(context) { const MamiSoundManager = function(context) {
const probablySupported = []; const probablySupported = [];
@ -13,8 +11,6 @@ const MamiSoundManager = function(context) {
wav: 'audio/wav', wav: 'audio/wav',
}; };
const loaded = new Map;
(() => { (() => {
const elem = $e('audio'); const elem = $e('audio');
for(const name in formats) { for(const name in formats) {
@ -28,9 +24,7 @@ const MamiSoundManager = function(context) {
} }
})(); })();
const pub = {}; const extractUrl = urls => {
const extractSupportedUrl = urls => {
if(typeof urls === 'object' && typeof urls.getSources === 'function') if(typeof urls === 'object' && typeof urls.getSources === 'function')
urls = urls.getSources(); urls = urls.getSources();
@ -48,25 +42,26 @@ const MamiSoundManager = function(context) {
throw 'No supported audio format could be determined.'; throw 'No supported audio format could be determined.';
}; };
pub.load = async urls => { const loaded = new Map;
const url = extractSupportedUrl(urls); context.onReset(() => loaded.clear());
if(loaded.has(url)) return {
return loaded.get(url); load: async urls => {
const url = extractUrl(urls);
const buffer = await context.createBuffer(url); if(loaded.has(url))
loaded.set(url, buffer); return loaded.get(url);
return buffer; const buffer = await context.createBuffer(url);
loaded.set(url, buffer);
return buffer;
},
unload: urls => {
loaded.delete(extractUrl(urls));
},
reset: () => {
loaded.clear();
},
}; };
pub.unload = urls => {
loaded.delete(extractSupportedUrl(urls));
};
pub.reset = () => {
loaded.clear();
};
return pub;
}; };

View file

@ -5,7 +5,7 @@
Umi.Sound = (() => { Umi.Sound = (() => {
return { return {
Play: sound => { Play: sound => {
if(!sound || sound === 'none' || !mami.hasSound()) if(!sound || sound === 'none')
return; return;
const settings = mami.getSettings(); const settings = mami.getSettings();

View file

@ -396,6 +396,13 @@ Umi.UI.Settings = (function() {
button.disabled = false; button.disabled = false;
}, },
}, },
{
title: 'Reset audio context',
type: 'button',
invoke: async () => {
mami.getAudio().reset();
},
},
], ],
} }
]; ];