diff --git a/src/mami.css/main.css b/src/mami.css/main.css index 72bdd46..1179e54 100644 --- a/src/mami.css/main.css +++ b/src/mami.css/main.css @@ -4,6 +4,8 @@ @include controls/msgbox.css; @include controls/views.css; +@include sound/sndtest.css; + @include baka.css; @include ping.css; @include chat.css; diff --git a/src/mami.css/sound/sndtest.css b/src/mami.css/sound/sndtest.css new file mode 100644 index 0000000..0cbf7fb --- /dev/null +++ b/src/mami.css/sound/sndtest.css @@ -0,0 +1,79 @@ +.sndtest { + color: #000; + font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; + font-size: 12px; + line-height: 20px; + color: #fff; + background: #000; +} +.sndtest button { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +.sndtest-view { + margin: 2px; + display: flex; +} +.sndtest-view button { + flex-grow: 1; + flex-shrink: 1; + background: #f99; + color: #fff; + border: 0; + border-radius: 0; + border-bottom: #f33; + padding: 10px; +} + +.sndtest-controls { + display: flex; + gap: 10px; + margin: 10px; +} + +.sndtest-library { + display: flex; + flex-wrap: wrap; + gap: 2px; + margin: 10px; +} +.sndtest-library button { + border: 1px solid #222; + border-radius: 0; + background: #111; + color: #fff; + padding: 4px; +} + +.sndtest-playing { + display: flex; + flex-direction: column-reverse; + gap: 2px; + margin: 10px; +} + +.sndtest-player { + display: flex; + background: #111; + border: 1px solid #222; + padding: 4px; + align-items: center; + gap: 10px; + flex-wrap: wrap; +} +.sndtest-player button { + border: 1px solid #333; + border-radius: 0; + background: #222; + color: #fff; + padding: 4px; +} +.sndtest-player-details { + min-width: 500px; +} +.sndtest-player-title { + font-size: 1.4em; + line-height: 1.5em; +} diff --git a/src/mami.js/settings/scoped.js b/src/mami.js/settings/scoped.js index 687851e..bd5a128 100644 --- a/src/mami.js/settings/scoped.js +++ b/src/mami.js/settings/scoped.js @@ -11,6 +11,7 @@ const MamiSettingsScoped = function(settings, prefix) { return { define: name => settings.define(prefix + name), + defined: name => settings.defined(prefix + name), info: name => settings.info(prefix + name), names: () => { const filtered = []; diff --git a/src/mami.js/settings/settings.js b/src/mami.js/settings/settings.js index ae9bb95..3092f81 100644 --- a/src/mami.js/settings/settings.js +++ b/src/mami.js/settings/settings.js @@ -209,6 +209,7 @@ const MamiSettings = function(storageOrPrefix, eventTarget) { const pub = { define: name => new settingBlueprint(name), + defined: name => settings.has(name), info: name => getSetting(name), names: () => Array.from(settings.keys()), has: name => { diff --git a/src/mami.js/sound/context.js b/src/mami.js/sound/context.js index 1139ac3..466f8d3 100644 --- a/src/mami.js/sound/context.js +++ b/src/mami.js/sound/context.js @@ -8,33 +8,27 @@ const MamiSoundContext = function() { const manager = new MamiSoundManager(audioCtx); const library = new MamiSoundLibrary(manager); const packs = new MamiSoundPacks; - let pack = new MamiSoundPack; - const pub = {}; + return { + get audio() { return audioCtx; }, + get manager() { return manager; }, + get library() { return library; }, + get packs() { return packs; }, - Object.defineProperties(pub, { - audio: { value: audioCtx, enumerable: true }, - manager: { value: manager, enumerable: true }, - library: { value: library, enumerable: true }, - packs: { value: packs, enumerable: true }, - - pack: { - get: () => pack, - set: value => { - if(typeof value !== 'object' || typeof value.getEventSound !== 'function') - throw 'value is not a valid soundpack'; - pack = value; - }, - enumerable: true, + get pack() { return pack; }, + set pack(value) { + if(typeof value !== 'object' || typeof value.getEventSound !== 'function') + throw 'value is not a valid soundpack'; + pack = value; }, - ready: { get: audioCtx.isReady, enumerable: true }, - volume: { get: audioCtx.getVolume, set: audioCtx.setVolume, enumerable: true }, - muted: { get: audioCtx.isMuted, set: audioCtx.setMuted, enumerable: true }, - }); + get ready() { return audioCtx.isReady; }, + get volume() { return audioCtx.getVolume(); }, + set volume(value) { audioCtx.setVolume(value); }, + get muted() { return audioCtx.isMuted; }, + set muted(value) { audioCtx.setMuted(value); }, - pub.reset = audioCtx.reset; - - return Object.freeze(pub); + reset: audioCtx.reset, + }; }; diff --git a/src/mami.js/sound/sndtest.jsx b/src/mami.js/sound/sndtest.jsx new file mode 100644 index 0000000..361152d --- /dev/null +++ b/src/mami.js/sound/sndtest.jsx @@ -0,0 +1,157 @@ +#include awaitable.js + +const MamiSoundTest = function(settings, audio, manager, library) { + if(!settings.defined('soundRate')) + settings.define('soundRate').default(1.0).min(0.001).max(2.0).virtual().create(); + if(!settings.defined('soundDetune')) + settings.define('soundDetune').default(0).min(-1200).max(1200).virtual().create(); + if(!settings.defined('soundLoopStart')) + settings.define('soundLoopStart').default(0).min(0).virtual().create(); + if(!settings.defined('soundLoopEnd')) + settings.define('soundLoopEnd').default(0).min(0).virtual().create(); + if(!settings.defined('soundReverse')) + settings.define('soundReverse').default(false).virtual().create(); + + const volumeSetting = settings.info('soundVolume'); + const rateSetting = settings.info('soundRate'); + const detuneSetting = settings.info('soundDetune'); + const loopStartSetting = settings.info('soundLoopStart'); + const loopEndSetting = settings.info('soundLoopEnd'); + const sources = []; + + let libraryButtons; + let nowPlaying; + let searchBox, volumeSlider, rateSlider, detuneSlider; + let loopStartBox, loopEndBox, reverseBox; + const container =
+
+ +
+
+ + + + + + + +
+ {libraryButtons =
} + {nowPlaying =
} +
; + + settings.watch('soundVolume', ev => { + volumeSlider.value = ev.detail.value; + }); + settings.watch('soundRate', ev => { + rateSlider.value = ev.detail.value * 1000; + + for(const source of sources) + source.setRate(ev.detail.value); + }); + settings.watch('soundDetune', ev => { + detuneSlider.value = ev.detail.value; + + for(const source of sources) + source.setDetune(ev.detail.value); + }); + settings.watch('soundLoopStart', ev => { + loopStartBox.value = ev.detail.value; + }); + settings.watch('soundLoopEnd', ev => { + loopEndBox.value = ev.detail.value; + }); + settings.watch('soundReverse', ev => { + reverseBox.checked = ev.detail.value; + }); + + const startPlay = async info => { + let controls, state, name; + const player =
+
+
{info.getTitle()}
+ {name =
{info.getName()}
} +
+ {controls =
} + {state =
} +
; + + nowPlaying.appendChild(player); + + let buffer, source; + try { + state.textContent = 'Loading...'; + buffer = await manager.loadBuffer(info.getSources()); + name.textContent += ` (${buffer.duration})`; + + source = audio.createSource(buffer, settings.get('soundReverse')); + sources.push(source); + + state.textContent = 'Configuring...'; + const rate = settings.get('soundRate'); + source.setRate(rate); + + const detune = settings.get('soundDetune'); + source.setDetune(detune); + + const loopStart = settings.get('soundLoopStart'); + const loopEnd = settings.get('soundLoopEnd'); + source.setLoop(loopEnd > 0, loopStart, loopEnd); + + controls.appendChild(); + + state.textContent = 'Playing...'; + await source.play(); + + state.textContent = 'Finished.'; + } catch(ex) { + console.error(ex); + state.textContent = `Error: ${ex}`; + } finally { + $ari(sources, source); + await MamiSleep(2000); + nowPlaying.removeChild(player); + } + }; + + const names = library.names(); + for(const name of names) { + const info = library.info(name); + libraryButtons.appendChild(); + } + + searchBox.addEventListener('change', () => { + const str = searchBox.value.trim().toLowerCase(); + for(const button of libraryButtons.children) + button.classList.toggle('hidden', !button.dataset.search.includes(str)); + }); + + return { + getElement: () => container, + onViewPop: async () => { + for(const source of sources) + source.stop(); + }, + }; +}; diff --git a/src/mami.js/ui/settings.jsx b/src/mami.js/ui/settings.jsx index 0187ce9..c5b9670 100644 --- a/src/mami.js/ui/settings.jsx +++ b/src/mami.js/ui/settings.jsx @@ -4,6 +4,7 @@ #include emotes.js #include utility.js #include settings/backup.js +#include sound/sndtest.jsx #include ui/baka.jsx #include ui/emotes.js #include ui/menus.js @@ -453,6 +454,20 @@ Umi.UI.Settings = (function() { button.disabled = false; }, }, + { + title: 'Sound Test', + type: 'button', + invoke: async button => { + button.disabled = true; + mami.views.push(new MamiSoundTest( + mami.settings, + mami.sound.audio, + mami.sound.manager, + mami.sound.library, + )); + button.disabled = false; + }, + }, { title: 'Reset audio context', type: 'button',