sound.js
  1 /*{# Copyright (c) 2010-2013 Turbulenz Limited #}*/
  2 /*
  3 * @title: Sound
  4 * @description:
  5 * This sample shows how to load and play sounds from sources moving in a 3D environment.
  6 * You can enable and disable the movement of the different 3D sources to hear the spatial difference.
  7 * Sounds can also be played with reverb, echo and pitch filtering effects when supported.
  8 */
  9 /*{{ javascript("jslib/aabbtree.js") }}*/
 10 /*{{ javascript("jslib/camera.js") }}*/
 11 /*{{ javascript("jslib/floor.js") }}*/
 12 /*{{ javascript("jslib/geometry.js") }}*/
 13 /*{{ javascript("jslib/material.js") }}*/
 14 /*{{ javascript("jslib/light.js") }}*/
 15 /*{{ javascript("jslib/scenenode.js") }}*/
 16 /*{{ javascript("jslib/scene.js") }}*/
 17 /*{{ javascript("jslib/vmath.js") }}*/
 18 /*{{ javascript("jslib/effectmanager.js") }}*/
 19 /*{{ javascript("jslib/shadermanager.js") }}*/
 20 /*{{ javascript("jslib/texturemanager.js") }}*/
 21 /*{{ javascript("jslib/renderingcommon.js") }}*/
 22 /*{{ javascript("jslib/defaultrendering.js") }}*/
 23 /*{{ javascript("jslib/observer.js") }}*/
 24 /*{{ javascript("jslib/resourceloader.js") }}*/
 25 /*{{ javascript("jslib/utilities.js") }}*/
 26 /*{{ javascript("jslib/requesthandler.js") }}*/
 27 /*{{ javascript("jslib/vertexbuffermanager.js") }}*/
 28 /*{{ javascript("jslib/indexbuffermanager.js") }}*/
 29 /*{{ javascript("jslib/services/turbulenzservices.js") }}*/
 30 /*{{ javascript("jslib/services/turbulenzbridge.js") }}*/
 31 /*{{ javascript("jslib/services/gamesession.js") }}*/
 32 /*{{ javascript("jslib/services/mappingtable.js") }}*/
 33 /*{{ javascript("scripts/motion.js") }}*/
 34 /*{{ javascript("scripts/htmlcontrols.js") }}*/
 35 /*{{ javascript("scripts/sceneloader.js") }}*/
 36 /*global TurbulenzEngine: true */
 37 /*global Motion: false */
 38 /*global window: false */
 39 /*global RequestHandler: false */
 40 /*global TextureManager: false */
 41 /*global ShaderManager: false */
 42 /*global EffectManager: false */
 43 /*global Camera: false */
 44 /*global Scene: false */
 45 /*global SceneLoader: false */
 46 /*global Floor: false */
 47 /*global HTMLControls: false */
 48 /*global DefaultRendering: false */
 49 /*global TurbulenzServices: false */
 50 TurbulenzEngine.onload = function onloadFn() {
 51     var errorCallback = function errorCallback(msg) {
 52         window.alert(msg);
 53     };
 54 
 55     // Create the engine devices objects
 56     var graphicsDeviceParameters = {};
 57     var graphicsDevice = TurbulenzEngine.createGraphicsDevice(graphicsDeviceParameters);
 58 
 59     if (!graphicsDevice.shadingLanguageVersion) {
 60         errorCallback("No shading language support detected.\nPlease check your graphics drivers are up to date.");
 61         graphicsDevice = null;
 62         return;
 63     }
 64 
 65     // Clear the background color of the engine window
 66     var clearColor = [0.95, 0.95, 1.0, 1.0];
 67     if (graphicsDevice.beginFrame()) {
 68         graphicsDevice.clear(clearColor);
 69         graphicsDevice.endFrame();
 70     }
 71 
 72     var mathDeviceParameters = {};
 73     var mathDevice = TurbulenzEngine.createMathDevice(mathDeviceParameters);
 74 
 75     var soundDeviceParameters = {
 76         linearDistance: false
 77     };
 78     var soundDevice = TurbulenzEngine.createSoundDevice(soundDeviceParameters);
 79 
 80     if (!soundDevice) {
 81         errorCallback("No SoundDevice detected.");
 82         return;
 83     }
 84 
 85     var requestHandlerParameters = {};
 86     var requestHandler = RequestHandler.create(requestHandlerParameters);
 87 
 88     var textureManager = TextureManager.create(graphicsDevice, requestHandler, null, errorCallback);
 89     var shaderManager = ShaderManager.create(graphicsDevice, requestHandler, null, errorCallback);
 90     var effectManager = EffectManager.create();
 91 
 92     var scene = Scene.create(mathDevice);
 93     var sceneLoader = SceneLoader.create();
 94     var renderer;
 95 
 96     // Create world
 97     var worldUp = mathDevice.v3BuildYAxis();
 98     var floor = Floor.create(graphicsDevice, mathDevice);
 99 
100     // Create moving objects
101     var duck1 = Motion.create(mathDevice, "Duck01");
102     duck1.setCircularMovement(9.0, [0, 0, 0]);
103     duck1.setDuckMotion(0.1, 0.5, 0.3);
104 
105     var duck1OrientAngle = -90;
106     duck1.setBaseOrientation(duck1OrientAngle);
107 
108     var duck2 = Motion.create(mathDevice, "Duck02");
109     duck2.setCircularMovement(7.0, [0, 0, 0]);
110     duck2.setDuckMotion(0.2, 0.5, 0.3);
111     duck2.reverseDirection();
112 
113     var duck2OrientAngle = 90;
114     duck2.setBaseOrientation(duck2OrientAngle);
115 
116     // Create fixed position for camera
117     // The camera exists between the duck orbits. This is so the one duck passes in front and one behind.
118     var cameraPosition = mathDevice.v3Build(0.0, 2.0, -8.0);
119     var lookAtPosition = mathDevice.v3Build(0.0, 0.0, 100.0);
120 
121     // Create a sound source for each object (different pitch)
122     var duck1SoundSource = soundDevice.createSource({
123         position: duck1.position,
124         relative: false,
125         pitch: 1.0
126     });
127 
128     var duck2SoundSource = soundDevice.createSource({
129         position: duck2.position,
130         relative: false,
131         pitch: 1.5
132     });
133 
134     // Set the interval between playing the sound (in seconds)
135     // The ratio of the intervals is approximately 1:2
136     // The slight variation is so the playing of the sounds don't coincide exactly
137     var duck1SoundInterval = 1.1;
138     var duck2SoundInterval = 1.9;
139 
140     // Accumulates the time elapsed before the next sound is due
141     var duck1TimeElapsed = 0;
142     var duck2TimeElapsed = 0;
143 
144     // Is the duck playing the sound
145     var duck1PlaySound = true;
146     var duck2PlaySound = true;
147 
148     // The sound to play
149     var duck1QuackSound = null;
150     var duck2QuackSound = null;
151 
152     // Sound Effects, Filters
153     var reverbOn = false;
154     var echoOn = false;
155     var lowPassOn = false;
156 
157     // Create effects to apply to the source
158     var reverb = soundDevice.createEffect({
159         name: "DuckReverb",
160         type: "Reverb",
161         gain: 0.3
162     });
163 
164     var echo = soundDevice.createEffect({
165         name: "DuckEcho",
166         type: "Echo",
167         damping: 0.78,
168         lrdelay: 0.24,
169         delay: 0.17,
170         gain: 0.3
171     });
172 
173     var lowPassFilter = soundDevice.createFilter({
174         name: "CutoffLowPass",
175         type: "LowPass",
176         gainHF: 0.05
177     });
178 
179     var reverbSlot = soundDevice.createEffectSlot({
180         effect: reverb,
181         gain: 0.1
182     });
183 
184     var echoSlot = soundDevice.createEffectSlot({
185         effect: echo,
186         gain: 0.1
187     });
188 
189     // Create a basic camera
190     var camera = Camera.create(mathDevice);
191     camera.lookAt(lookAtPosition, worldUp, cameraPosition);
192     camera.updateViewMatrix();
193 
194     var addCustomSceneData = function addCustomSceneDataFn(sceneData) {
195         var addModel = function addModelFn(modelName, modelGeometry, modelMaterial, modelSurface, modelPosition) {
196             var modelMatrix = [
197                 1,
198                 0,
199                 0,
200                 0,
201                 1,
202                 0,
203                 0,
204                 0,
205                 1,
206                 modelPosition[0],
207                 modelPosition[1],
208                 modelPosition[2]
209             ];
210             sceneData.nodes[modelName] = {
211                 geometryinstances: {
212                     "default": {
213                         geometry: modelGeometry,
214                         surface: modelSurface,
215                         material: modelMaterial,
216                         render: true
217                     }
218                 },
219                 matrix: modelMatrix,
220                 dynamic: true,
221                 disabled: false,
222                 visible: true
223             };
224         };
225 
226         // Removes the default duck node in the loaded scene
227         delete sceneData.nodes.LOD3sp;
228 
229         addModel("Duck01", "LOD3spShape", "blinn3", "blinn3SG", duck1.position);
230         addModel("Duck02", "LOD3spShape", "blinn3", "blinn3SG", duck2.position);
231     };
232 
233     // Controls
234     var htmlControls = HTMLControls.create();
235     htmlControls.addCheckboxControl({
236         id: "checkbox01",
237         value: "duck1Move",
238         isSelected: duck1.move,
239         fn: function () {
240             duck1.move = !duck1.move;
241             return duck1.move;
242         }
243     });
244     htmlControls.addCheckboxControl({
245         id: "checkbox02",
246         value: "duck1PlaySound",
247         isSelected: duck1PlaySound,
248         fn: function () {
249             duck1PlaySound = !duck1PlaySound;
250             return duck1PlaySound;
251         }
252     });
253     htmlControls.addCheckboxControl({
254         id: "checkbox03",
255         value: "duck2Move",
256         isSelected: duck2.move,
257         fn: function () {
258             duck2.move = !duck2.move;
259             return duck2.move;
260         }
261     });
262     htmlControls.addCheckboxControl({
263         id: "checkbox04",
264         value: "duck2PlaySound",
265         isSelected: duck2PlaySound,
266         fn: function () {
267             duck2PlaySound = !duck2PlaySound;
268             return duck2PlaySound;
269         }
270     });
271 
272     htmlControls.addCheckboxControl({
273         id: "checkbox05",
274         value: "reverbOn",
275         isSelected: reverbOn,
276         fn: function () {
277             if (reverb && reverbSlot) {
278                 if (reverbOn) {
279                     // Set no effect slot and no filter on auxiliary send at index 0
280                     reverbOn = !(duck1SoundSource.setAuxiliarySendFilter(0, null, null) && duck2SoundSource.setAuxiliarySendFilter(0, null, null));
281                 } else {
282                     // Set the reverb effect slot on auxiliary send at index 0, no filter
283                     reverbOn = (duck1SoundSource.setAuxiliarySendFilter(0, reverbSlot, null) && duck2SoundSource.setAuxiliarySendFilter(0, reverbSlot, null));
284                 }
285             }
286             return reverbOn;
287         }
288     });
289     htmlControls.addCheckboxControl({
290         id: "checkbox06",
291         value: "echoOn",
292         isSelected: echoOn,
293         fn: function () {
294             if (echo && echoSlot) {
295                 if (echoOn) {
296                     // Set no effect slot and no filter on auxiliary send at index 1
297                     echoOn = !(duck1SoundSource.setAuxiliarySendFilter(1, null, null) && duck2SoundSource.setAuxiliarySendFilter(1, null, null));
298                 } else {
299                     // Set the reverb effect slot on auxiliary send at index 1, no filter
300                     echoOn = (duck1SoundSource.setAuxiliarySendFilter(1, echoSlot, null) && duck2SoundSource.setAuxiliarySendFilter(1, echoSlot, null));
301                 }
302             }
303             return echoOn;
304         }
305     });
306     htmlControls.addCheckboxControl({
307         id: "checkbox07",
308         value: "lowPassOn",
309         isSelected: lowPassOn,
310         fn: function () {
311             if (lowPassFilter) {
312                 if (lowPassOn) {
313                     // Set filter on direct output only
314                     lowPassOn = !(duck1SoundSource.setDirectFilter(null) && duck2SoundSource.setDirectFilter(null));
315                 } else {
316                     // Set no filter on direct output only
317                     lowPassOn = (duck1SoundSource.setDirectFilter(lowPassFilter) && duck2SoundSource.setDirectFilter(lowPassFilter));
318                 }
319             }
320             return lowPassOn;
321         }
322     });
323 
324     var slider01ID = "slider1";
325     var initialGain = soundDevice.listenerGain;
326     htmlControls.addSliderControl({
327         id: slider01ID,
328         value: initialGain,
329         max: 1.0,
330         min: 0,
331         step: 0.05,
332         fn: function () {
333             var val = this.value;
334             soundDevice.listenerGain = val;
335             htmlControls.updateSlider(slider01ID, val);
336         }
337     });
338 
339     htmlControls.addButtonControl({
340         id: "button01",
341         value: "Duck 1",
342         fn: function () {
343             duck1.reverseDirection();
344             duck1OrientAngle = -duck1OrientAngle;
345             duck1.setBaseOrientation(duck1OrientAngle);
346         }
347     });
348     htmlControls.addButtonControl({
349         id: "button02",
350         value: "Duck 2",
351         fn: function () {
352             duck2.reverseDirection();
353             duck2OrientAngle = -duck2OrientAngle;
354             duck2.setBaseOrientation(duck2OrientAngle);
355         }
356     });
357     htmlControls.register();
358 
359     var reverbButton = document.getElementById("checkbox05");
360     if (reverbButton) {
361         reverbButton.disabled = !(reverb && reverbSlot);
362     }
363 
364     var echoButton = document.getElementById("checkbox06");
365     if (echoButton) {
366         echoButton.disabled = !(echo && echoSlot);
367     }
368 
369     var lowpassButton = document.getElementById("checkbox07");
370     if (lowpassButton) {
371         lowpassButton.disabled = !(lowPassFilter);
372     }
373 
374     // Update listener position to match camera
375     // This must occur everytime the listener moves
376     soundDevice.listenerTransform = camera.matrix;
377 
378     // Initialize the previous frame time
379     var previousFrameTime = TurbulenzEngine.time;
380     var intervalID;
381 
382     var playSounds = function playSoundsFn(delta) {
383         if (duck1PlaySound) {
384             duck1TimeElapsed += delta;
385             if (duck1TimeElapsed > duck1SoundInterval) {
386                 // Play quack sound
387                 duck1SoundSource.play(duck1QuackSound);
388                 duck1TimeElapsed -= duck1SoundInterval;
389             }
390         }
391 
392         if (duck2PlaySound) {
393             duck2TimeElapsed += delta;
394             if (duck2TimeElapsed > duck2SoundInterval) {
395                 // Play quack sound
396                 duck2SoundSource.play(duck2QuackSound);
397                 duck2TimeElapsed -= duck2SoundInterval;
398             }
399         }
400     };
401 
402     // Callback to draw extra debug information
403     function drawDebugCB() {
404         floor.render(graphicsDevice, camera);
405     }
406 
407     //
408     // Update
409     //
410     var update = function updateFn() {
411         var currentTime = TurbulenzEngine.time;
412         var deltaTime = (currentTime - previousFrameTime);
413         if (deltaTime > 0.1) {
414             deltaTime = 0.1;
415         }
416 
417         // Update the aspect ratio of the camera in case of window resizes
418         var deviceWidth = graphicsDevice.width;
419         var deviceHeight = graphicsDevice.height;
420         var aspectRatio = (deviceWidth / deviceHeight);
421         if (aspectRatio !== camera.aspectRatio) {
422             camera.aspectRatio = aspectRatio;
423             camera.updateProjectionMatrix();
424         }
425         camera.updateViewProjectionMatrix();
426 
427         // Update duck movement and rotation
428         duck1.update(deltaTime);
429         duck2.update(deltaTime);
430 
431         // Update sound source position
432         duck1SoundSource.position = duck1.position;
433         duck2SoundSource.position = duck2.position;
434 
435         // Play sound if interval has elapsed
436         playSounds(deltaTime);
437 
438         // Update duck1 position in scene
439         var duck1Node = scene.findNode("Duck01");
440         if (duck1Node) {
441             duck1Node.setLocalTransform(duck1.matrix);
442         }
443 
444         // Update duck2 position in scene
445         var duck2Node = scene.findNode("Duck02");
446         if (duck2Node) {
447             duck2Node.setLocalTransform(duck2.matrix);
448         }
449 
450         // Update scene
451         scene.update();
452 
453         // Update renderer
454         renderer.update(graphicsDevice, camera, scene, currentTime);
455 
456         soundDevice.update();
457 
458         if (graphicsDevice.beginFrame()) {
459             if (renderer.updateBuffers(graphicsDevice, deviceWidth, deviceHeight)) {
460                 renderer.draw(graphicsDevice, clearColor, null, null, drawDebugCB);
461             }
462 
463             graphicsDevice.endFrame();
464         }
465 
466         previousFrameTime = currentTime;
467     };
468 
469     var loadingLoop = function loadingLoopFn() {
470         if (sceneLoader.complete() && duck1QuackSound && duck2QuackSound) {
471             TurbulenzEngine.clearInterval(intervalID);
472 
473             renderer.updateShader(shaderManager);
474 
475             intervalID = TurbulenzEngine.setInterval(update, 1000 / 60);
476         }
477     };
478     intervalID = TurbulenzEngine.setInterval(loadingLoop, 1000 / 10);
479 
480     var loadAssets = function loadAssetsFn(mappingTable) {
481         // Create the sound for the source to emit
482         var soundURL;
483         if (soundDevice.isSupported("FILEFORMAT_OGG")) {
484             soundURL = mappingTable.getURL("sounds/duck.ogg");
485         } else {
486             soundURL = mappingTable.getURL("sounds/duck.mp3");
487         }
488 
489         soundDevice.createSound({
490             src: soundURL,
491             onload: function (sound) {
492                 if (sound) {
493                     duck1QuackSound = sound;
494                     duck2QuackSound = sound;
495                 } else {
496                     errorCallback('Failed to load sound.');
497                 }
498             }
499         });
500 
501         // Renderer for the scene (requires shader assets).
502         renderer = DefaultRendering.create(graphicsDevice, mathDevice, shaderManager, effectManager);
503 
504         renderer.setGlobalLightPosition(mathDevice.v3Build(0.5, 100.0, 0.5));
505         renderer.setAmbientColor(mathDevice.v3Build(0.3, 0.3, 0.4));
506         renderer.setDefaultTexture(textureManager.get("default"));
507 
508         // Create & load duck scene
509         sceneLoader.load({
510             scene: scene,
511             append: true,
512             assetPath: "models/duck.dae",
513             keepLights: true,
514             baseScene: null,
515             baseMatrix: null,
516             skin: null,
517             graphicsDevice: graphicsDevice,
518             mathDevice: mathDevice,
519             textureManager: textureManager,
520             shaderManager: shaderManager,
521             effectManager: effectManager,
522             requestHandler: requestHandler,
523             preSceneLoadFn: addCustomSceneData
524         });
525     };
526 
527     var mappingTableReceived = function mappingTableReceivedFn(mappingTable) {
528         textureManager.setPathRemapping(mappingTable.urlMapping, mappingTable.assetPrefix);
529         shaderManager.setPathRemapping(mappingTable.urlMapping, mappingTable.assetPrefix);
530         sceneLoader.setPathRemapping(mappingTable.urlMapping, mappingTable.assetPrefix);
531 
532         loadAssets(mappingTable);
533     };
534 
535     var gameSessionCreated = function gameSessionCreatedFn(gameSession) {
536         TurbulenzServices.createMappingTable(requestHandler, gameSession, mappingTableReceived);
537     };
538     var gameSession = TurbulenzServices.createGameSession(requestHandler, gameSessionCreated);
539 
540     // Create a scene destroy callback to run when the window is closed
541     TurbulenzEngine.onunload = function destroyScene() {
542         TurbulenzEngine.clearInterval(intervalID);
543 
544         if (gameSession) {
545             gameSession.destroy();
546             gameSession = null;
547         }
548 
549         clearColor = null;
550         worldUp = null;
551         floor = null;
552 
553         if (renderer) {
554             renderer.destroy();
555             renderer = null;
556         }
557 
558         reverb = null;
559         echo = null;
560         lowPassFilter = null;
561         reverbSlot = null;
562         echoSlot = null;
563 
564         duck1 = null;
565         duck2 = null;
566 
567         if (duck1SoundSource) {
568             duck1SoundSource.setDirectFilter(null);
569             duck1SoundSource.setAuxiliarySendFilter(0, null, null);
570             duck1SoundSource.setAuxiliarySendFilter(1, null, null);
571             duck1SoundSource.destroy();
572             duck1SoundSource = null;
573         }
574         if (duck2SoundSource) {
575             duck2SoundSource.setDirectFilter(null);
576             duck2SoundSource.setAuxiliarySendFilter(0, null, null);
577             duck2SoundSource.setAuxiliarySendFilter(1, null, null);
578             duck2SoundSource.destroy();
579             duck2SoundSource = null;
580         }
581         if (duck1QuackSound) {
582             duck1QuackSound.destroy();
583             duck1QuackSound = null;
584         }
585         if (duck2QuackSound) {
586             duck2QuackSound.destroy();
587             duck2QuackSound = null;
588         }
589 
590         if (scene) {
591             scene.destroy();
592             scene = null;
593         }
594         requestHandler = null;
595         sceneLoader = null;
596         camera = null;
597 
598         if (textureManager) {
599             textureManager.destroy();
600             textureManager = null;
601         }
602 
603         if (shaderManager) {
604             shaderManager.destroy();
605             shaderManager = null;
606         }
607 
608         effectManager = null;
609 
610         TurbulenzEngine.flush();
611         graphicsDevice = null;
612         mathDevice = null;
613         soundDevice = null;
614     };
615 };