195 lines
8.5 KiB
JavaScript
195 lines
8.5 KiB
JavaScript
#include awaitable.js
|
|
|
|
const MamiSoundTest = function(settings, audio, manager, library, clickPos) {
|
|
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 hasClickPos = Array.isArray(clickPos) && clickPos.length > 1;
|
|
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 = <div class="sndtest">
|
|
<div class="sndtest-view">
|
|
<button onclick={() => { mami.views.pop(); }}>Exit</button>
|
|
</div>
|
|
<div class="sndtest-controls">
|
|
<label class="sndtest-control">
|
|
<div class="sndtest-control-name">Search</div>
|
|
{searchBox = <input type="text" placeholder="type to filter"/>}
|
|
</label>
|
|
<label class="sndtest-control">
|
|
<div class="sndtest-control-name">Volume</div>
|
|
{volumeSlider = <input type="range" min={volumeSetting.min} max={volumeSetting.max} onchange={() => { settings.set('soundVolume', volumeSlider.value); }} ondblclick={() => { settings.delete('soundVolume'); }}/>}
|
|
</label>
|
|
<label class="sndtest-control">
|
|
<div class="sndtest-control-name">Rate</div>
|
|
{rateSlider = <input type="range" min={rateSetting.min * 1000} max={rateSetting.max * 1000} onchange={() => { settings.set('soundRate', rateSlider.value / 1000); }} ondblclick={() => { settings.delete('soundRate'); }}/>}
|
|
</label>
|
|
<label class="sndtest-control">
|
|
<div class="sndtest-control-name">Detune</div>
|
|
{detuneSlider = <input type="range" min={detuneSetting.min} max={detuneSetting.max} onchange={() => { settings.set('soundDetune', detuneSlider.value); }} ondblclick={() => { settings.delete('soundDetune'); }}/>}
|
|
</label>
|
|
<label class="sndtest-control">
|
|
<div class="sndtest-control-name">Loop start</div>
|
|
{loopStartBox = <input type="number" step="0.00001" min={loopStartSetting.min} onchange={() => { settings.set('soundLoopStart', loopStartBox.value); }} ondblclick={() => { settings.delete('soundLoopStart'); }}/>}
|
|
</label>
|
|
<label class="sndtest-control">
|
|
<div class="sndtest-control-name">Loop end</div>
|
|
{loopEndBox = <input type="number" step="0.00001" min={loopEndSetting.min} onchange={() => { settings.set('soundLoopEnd', loopEndBox.value); }} ondblclick={() => { settings.delete('soundLoopEnd'); }}/>}
|
|
</label>
|
|
<label class="sndtest-control">
|
|
<div class="sndtest-control-name">Reverse</div>
|
|
{reverseBox = <input type="checkbox" onchange={() => { settings.set('soundReverse', reverseBox.checked); }}/>}
|
|
</label>
|
|
</div>
|
|
{libraryButtons = <div class="sndtest-library"/>}
|
|
{nowPlaying = <div class="sndtest-playing"/>}
|
|
</div>;
|
|
|
|
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 = <div class="sndtest-player">
|
|
<div class="sndtest-player-details">
|
|
<div class="sndtest-player-title">{info.getTitle()}</div>
|
|
{name = <div class="sndtest-player-name">{info.getName()}</div>}
|
|
</div>
|
|
{controls = <div class="sndtest-player-controls"/>}
|
|
{state = <div class="sndtest-player-state"/>}
|
|
</div>;
|
|
|
|
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(<button onclick={() => source.stop()}>Stop</button>);
|
|
|
|
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(<button onclick={() => { startPlay(info); }} data-search={`${info.getTitle().toLowerCase()} ${info.getName().toLowerCase()}`}>{info.getTitle()} ({info.getName()})</button>);
|
|
}
|
|
|
|
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();
|
|
},
|
|
getViewTransition: mode => {
|
|
if(!hasClickPos)
|
|
return;
|
|
|
|
if(mode === 'push')
|
|
return ctx => MamiAnimate({
|
|
async: true,
|
|
duration: 1500,
|
|
easing: 'inQuad',
|
|
start: () => {
|
|
library.play('mario:keyhole');
|
|
ctx.toElem.style.transform = 'scale(0) translate(25%, 25%)';
|
|
ctx.toElem.style.transformOrigin = `${clickPos[0]}px ${clickPos[1]}px`;
|
|
},
|
|
update: (t, rt) => ctx.toElem.style.transform = `scale(${t}) translate(${25 * (1 - rt)}%, ${25 * (1 - rt)}%)`,
|
|
end: () => {
|
|
ctx.toElem.style.transform = null;
|
|
ctx.toElem.style.transformOrigin = null;
|
|
},
|
|
});
|
|
|
|
if(mode === 'pop')
|
|
return ctx => MamiAnimate({
|
|
async: true,
|
|
duration: 1000,
|
|
easing: 'outQuad',
|
|
start: () => {
|
|
ctx.fromElem.style.transformOrigin = `${clickPos[0]}px ${clickPos[1]}px`;
|
|
},
|
|
update: (t, rt) => ctx.fromElem.style.transform = `scale(${1 - t}) rotate(${-1080 * t}deg) translate(${50 * rt}%, ${50 * rt}%)`,
|
|
end: () => {
|
|
ctx.fromElem.style.transform = null;
|
|
ctx.fromElem.style.transformOrigin = null;
|
|
},
|
|
});
|
|
},
|
|
};
|
|
};
|