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