diff --git a/src/html/index.html b/src/html/index.html index 725292b..9c16b94 100644 --- a/src/html/index.html +++ b/src/html/index.html @@ -30,6 +30,17 @@ z-index: 1; background: white; } + + /* + todo: nicer layout and design. see #8; + */ + #sound-effects { + position: absolute; + margin: auto; + bottom: 10%; + padding: 10px; + z-index: 1; + } @@ -41,6 +52,8 @@ +
+
{% for script in scripts -%} diff --git a/src/js/index.js b/src/js/index.js index 97a109d..564eca4 100644 --- a/src/js/index.js +++ b/src/js/index.js @@ -17,6 +17,8 @@ require('imports?THREE=three!three/examples/js/loaders/OBJLoader'); require('imports?THREE=three!three/examples/js/loaders/MTLLoader'); require('imports?THREE=three!three/examples/js/vr/ViveController'); +import SoundEffect from './sound-effect'; + // Setup three.js WebGL renderer. Note: Antialiasing is a big performance hit. // Only enable it if you actually need to. const renderer = new THREE.WebGLRenderer({ @@ -253,6 +255,27 @@ light.shadow.camera.near = 1; scene.add(new THREE.AmbientLight(0x666666)); +/* +Set up sound effects +todo: provide multiple formats +todo: wake up audio context on first touch event on mobile +*/ +const AudioContext = window.AudioContext || window.webkitAudioContext; +const audioContext = new AudioContext(); +const sfx = {}; +[ + 'sounds/bark.wav', + 'sounds/laugh.wav' +].forEach(src => { + const key = src.replace(/^.*\/([a-z]+)\.wav/, '$1'); + sfx[key] = new SoundEffect({ + src, + buttonContainer: '#sound-effects', + context: audioContext, + name: key + }); +}); + // Request animation frame loop function let vrDisplay = null; let lastRender = 0; diff --git a/src/js/sound-effect.js b/src/js/sound-effect.js new file mode 100644 index 0000000..3d88a24 --- /dev/null +++ b/src/js/sound-effect.js @@ -0,0 +1,75 @@ +'use strict'; + +function SoundEffect(options) { + const buttonContainer = typeof options.buttonContainer === 'string' ? + document.querySelector(options.buttonContainer) : + options.buttonContainer; + + const src = options.src; + const context = options.context; + + let buffer = null; + + const sources = []; + + // load audio file + // todo: load appropriate format based on browser support + const xhr = new XMLHttpRequest(); + xhr.responseType = 'arraybuffer'; + xhr.onload = () => { + context.decodeAudioData(xhr.response, decodedBuffer => { + buffer = decodedBuffer; + console.log('loaded buffer', src, buffer); + }); + }; + xhr.onerror = e => { + // keep trying + console.warn('Error loading audio', src, e); + }; + xhr.open('GET', src, true); + xhr.send(); + + let stopSource; + function stopEvent(evt) { + stopSource(evt.target); + } + + stopSource = function (source) { + try { + source.removeEventListener('ended', stopEvent); + source.disconnect(context.destination); + source.stop(0); + } catch (e) {} + const i = sources.indexOf(source); + if (i >= 0) { + sources.splice(i, 1); + } + }; + + this.play = () => { + if (buffer) { + const source = context.createBufferSource(); + source.buffer = buffer; + source.connect(context.destination); + source.addEventListener('ended', stopEvent); + source.start(0); + sources.push(source); + } + }; + + this.stop = () => { + while (sources.length) { + stopSource(sources[0]); + } + }; + + /* + Use better-looking button design #8 + */ + const button = document.createElement('button'); + button.appendChild(document.createTextNode(options.name || 'Sound')); + button.addEventListener('click', this.play); + buttonContainer.appendChild(button); +} + +export default SoundEffect; \ No newline at end of file diff --git a/src/root/sounds/bark.wav b/src/root/sounds/bark.wav new file mode 100644 index 0000000..73141fb Binary files /dev/null and b/src/root/sounds/bark.wav differ diff --git a/src/root/sounds/laugh.wav b/src/root/sounds/laugh.wav new file mode 100644 index 0000000..a69d72a Binary files /dev/null and b/src/root/sounds/laugh.wav differ