diff options
Diffstat (limited to 'client/lib/util.js')
| -rw-r--r-- | client/lib/util.js | 198 |
1 files changed, 198 insertions, 0 deletions
diff --git a/client/lib/util.js b/client/lib/util.js new file mode 100644 index 0000000..35a537d --- /dev/null +++ b/client/lib/util.js @@ -0,0 +1,198 @@ +import Nexus from 'nexusui' +// import Tone from 'tone' +// import StartAudioContext from './startAudioContext' + +const isIphone = (navigator.userAgent.match(/iPhone/i)) || (navigator.userAgent.match(/iPod/i)) +const isIpad = (navigator.userAgent.match(/iPad/i)) +const isAndroid = (navigator.userAgent.match(/Android/i)) +const isMobile = isIphone || isIpad || isAndroid +const isDesktop = ! isMobile + +document.body.classList.add(isMobile ? 'mobile' : 'desktop') + +const browser = { isIphone, isIpad, isMobile, isDesktop } + +function choice (a){ return a[ Math.floor(Math.random() * a.length) ] } +function randint (n) { return Math.floor(Math.random() * n) } +function mod(n,m){ return n-(m * Math.floor(n/m)) } +function norm(n, min, max){ return (n - min) / (max - min) } +function shuffle(a){ + a = a.slice(0) + for (var i = a.length; i > 0; i--){ + var r = randint(i) + var swap = a[i-1] + a[i-1] = a[r] + a[r] = swap + } + return a +} +function shuffleInPlace(a){ + for (var i = a.length; i > 0; i--){ + var r = randint(i) + var swap = a[i-1] + a[i-1] = a[r] + a[r] = swap + } + return a +} +function requestAudioContext (fn) { + if (isMobile) { + const container = document.createElement('div') + const button = document.createElement('div') + button.innerHTML = 'Tap to start - please unmute your phone' + Object.assign(container.style, { + position: 'absolute', + width: '100%', + height: '100%', + zIndex: '10000', + top: '0px', + left: '0px', + backgroundColor: 'rgba(0, 0, 0, 0.8)', + }) + Object.assign(button.style, { + position: 'absolute', + left: '50%', + top: '50%', + padding: '20px', + backgroundColor: '#7F33ED', + color: 'white', + fontFamily: 'monospace', + borderRadius: '3px', + transform: 'translate3D(-50%,-50%,0)', + textAlign: 'center', + lineHeight: '1.5', + }) + container.appendChild(button) + document.body.appendChild(container) + StartAudioContext.setContext(Tone.context) + StartAudioContext.on(button) + StartAudioContext.onStarted(_ => { + container.remove() + fn() + }) + } else { + fn() + } +} + +function dataURItoBlob(dataURI) { + // convert base64 to raw binary data held in a string + // doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this + var byteString = atob(dataURI.split(',')[1]); + + // separate out the mime component + var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0] + + // write the bytes of the string to an ArrayBuffer + var ab = new ArrayBuffer(byteString.length); + + // create a view into the buffer + var ia = new Uint8Array(ab); + + // set the bytes of the buffer to the correct values + for (var i = 0; i < byteString.length; i++) { + ia[i] = byteString.charCodeAt(i); + } + + // write the ArrayBuffer to a blob, and you're done + var blob = new Blob([ab], {type: mimeString}); + return blob; + +} +function ftom(f) { + // return (Math.log(f) - Math.log(261.626)) / Math.log(2) + 4.0 + return 69 + 12 * Math.log2(f / 440) +} +function mtof(m) { + return 440 * Math.pow(2, (m - 69) / 12) +} +function tap (fn) { + return (e) => { + if (browser.isMobile) fn() + else if (e.press) fn() + } +} + +function update_value_on_change(el, id, is_int, fn) { + const label = document.querySelector(id + ' + .val') + const update = v => { + label.innerHTML = is_int ? parseInt(v) : v.toFixed(2) + fn && fn(v) + } + el.on('change', update) + label.innerHTML = is_int ? parseInt(el.value) : el.value.toFixed(2) + el.update = update +} +function update_radio_value_on_change(el, id, values, fn) { + let old_v = el.active + const label = document.querySelector(id + ' + .val') + const update = v => { + if (v === -1) { + v = el.active = old_v + } else { + old_v = v + } + label.innerHTML = values[v][1] + fn && fn(v) + } + el.on('change', update) + update(el.active) + el.update = update +} +function build_options(el, lists, fn) { + Object.keys(lists).forEach(key => { + const list = lists[key] + const option = document.createElement('option') + option.innerHTML = list.name + option.value = key + el.appendChild(option) + }) + el.addEventListener('input', function(e){ + fn(e.target.value) + }) +} +function Slider(parent, tag, title, options, is_int, fn){ + const block = document.createElement('div') + block.classList.add('block') + const el = document.createElement('div') + el.setAttribute('id', tag) + const val = document.createElement('span') + val.classList.add('val') + const label = document.createElement('label') + label.innerHTML = title + block.appendChild(label) + block.appendChild(el) + block.appendChild(val) + parent.appendChild(block) + options.size = [200, 24] + const nx = new Nexus.Slider('#' + tag, options) + update_value_on_change(nx, '#' + tag, is_int, fn) + return nx +} +function Statistic(parent, label, fn){ + const block = document.createElement('div') + block.classList.add('stat') + const key = document.createElement('div') + key.classList.add('key') + const val = document.createElement('div') + val.classList.add('val') + block.appendChild(key) + block.appendChild(val) + parent.appendChild(block) + key.innerHTML = label + val.innerHTML = fn() + let old_v + return () => { + const v = fn() + if (old_v === v) return + val.innerText = old_v = v + } +} + +export { + mod, norm, choice, shuffle, randint, + browser, requestAudioContext, ftom, mtof, tap, dataURItoBlob, + update_value_on_change, update_radio_value_on_change, build_options, + Slider, Statistic, + } + |
