mami/src/mami.js/sound/sndtest.jsx

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;
},
});
},
};
};