{"id":973,"date":"2013-08-23T19:25:24","date_gmt":"2013-08-23T18:25:24","guid":{"rendered":"http:\/\/www.radicalfishgames.com\/?p=973"},"modified":"2013-08-27T22:53:23","modified_gmt":"2013-08-27T21:53:23","slug":"technical-music-and-sound","status":"publish","type":"post","link":"https:\/\/www.radicalfishgames.com\/?p=973","title":{"rendered":"Technical: Music and Sound"},"content":{"rendered":"<p><img decoding=\"async\" class=\"alignleft size-full wp-image-379\" alt=\"botchan-music\" src=\"http:\/\/www.radicalfishgames.com\/wp-content\/uploads\/botchan-music2.png\" width=\"142\" height=\"128\" \/>It&#8217;s time for a new technical post!<\/p>\n<p>As promised today, we want to share with you how we implemented music and sound in <strong>CrossCode<\/strong>. This includes how we used the <strong>WebAudio API<\/strong> to increase the sound quality and make perfect loops possible. Any code you will encounter is not directly from the game and might be pseudo-code to some extend.<\/p>\n<p>So&#8230; grab some fish&#8217;n&#8217;chips and tea, this is going to be a long post!<\/p>\n<h2>The WebAudio API<\/h2>\n<p>For starters, the <strong>WebAudio API<\/strong> is a more or less high-level API for processing sound via <strong>JavaScript<\/strong>. Now that means that we can not only play sounds via the API, but we can also manipulate the sound in any way we want. To make this magic happen, the API uses a routing graph. You have a master node (called destination in the API) and attach a number of different nodes to it. Look at this figure showing how this visually looks like:<br \/>\n<a href=\"http:\/\/www.radicalfishgames.com\/wp-content\/uploads\/graph_simple.png\"><img decoding=\"async\" class=\"aligncenter size-full wp-image-976\" alt=\"graph_simple\" src=\"http:\/\/www.radicalfishgames.com\/wp-content\/uploads\/graph_simple.png\" width=\"509\" height=\"290\" srcset=\"https:\/\/www.radicalfishgames.com\/wp-content\/uploads\/graph_simple.png 509w, https:\/\/www.radicalfishgames.com\/wp-content\/uploads\/graph_simple-300x170.png 300w, https:\/\/www.radicalfishgames.com\/wp-content\/uploads\/graph_simple-500x284.png 500w\" sizes=\"(max-width: 509px) 100vw, 509px\" \/><\/a><\/p>\n<p><strong>AudioNode <\/strong>represents a single sound that will be played. As you can see there aren&#8217;t only AudioNodesthough. The <strong>WebAudio API<\/strong> features lots of nodes that make it possible to manipulate the actual sound. In the graph you can see a <strong>GainNode<\/strong>. This node is used to change the volume of the sound. Like any graph structure it is also possible to attach multiple nodes to another node. This can be really useful if you want to pass each sound through a single\u00a0<strong>GainNode <\/strong>to change the volume for all sounds.<br \/>\nOther nodes make it possible to apply filters on a sound. Or you can use multiple sounds to cross-fade them based on your position in the game world. We don&#8217;t want to cover all the possibilities here since there already is a great article over at <strong>Html5Rocks<\/strong>. Check out this link:<\/p>\n<p><a href=\"http:\/\/www.html5rocks.com\/en\/tutorials\/webaudio\/games\/\" target=\"_blank\">Developing Game Audio with the Web Audio API<\/a><\/p>\n<p>This article features some very nice effects you can use and is a great start for learning how to use the API.<\/p>\n<p>The API&#8217;s starting point is a single object that has all the functions to create different nodes:<\/p>\n<pre class=\"lang:js decode:true\" title=\"webaudio access\">window[\"AudioContext\"] = window[\"AudioContext\"] || window[\"webkitAudioContext\"];\r\nthis.context = new window[\"AudioContext\"]();<\/pre>\n<h2>Goals for CrossCode<\/h2>\n<p>For <strong>CrossCode <\/strong>we made a small list of things we wanted to have for both music and sound.<\/p>\n<ul>\n<li>loop music without stutters<\/li>\n<li>play sounds in a 3D enviroment<\/li>\n<li>loop without making the music perfectly loop-able<\/li>\n<li>fade-in\/out music<\/li>\n<li>pausing\/resuming music<\/li>\n<\/ul>\n<p>Lots of these points are already covered by the <strong>audio element<\/strong>. But the first 2 are hard to achieve. Perfect timing for music isn&#8217;t easily achieved and you might have noticed it too while playing <strong>CrossCode<\/strong>. The same goes for positioning sound in 3D. We might also come up with other ideas along the way that aren&#8217;t possible to do with the <strong>audio element<\/strong> so we started working on integrating the <strong>WebAudio API<\/strong>.<\/p>\n<p>So, lets dive into the implementations! We&#8217;re going to begin with sound to explain some basic aspects of the API. We will also talk about the default implementation which is mostly covered by the music and sound classes <a href=\"http:\/\/impactjs.com\/\" target=\"_blank\">impact.js<\/a> provides.<\/p>\n<h2>Sound<\/h2>\n<p>As said, <strong>impact.js<\/strong> covers a lot of ground here already. But as usual we changed a lot to make it work for us. One of these changes is to separate the default implementation, which uses the <strong>audio element<\/strong>, from the <strong>WebAudio<\/strong> Implementation. Normally we load a sound roughly like this:<\/p>\n<pre class=\"lang:js decode:true\" title=\"Creating a sound\">var sound = new Sound(\"path\");<\/pre>\n<p>And we wanted to keep it that way. So we have two sound implementations sharing the same interface, SoundWebKit and SoundDefault:<\/p>\n<pre class=\"lang:js decode:true\" title=\"Checking Sound\">if(window[\"webkitAudioContext\"]) {\r\n    Sound = SoundWebAudio;\r\n} else {\r\n    Sound = SoundDefault;\r\n}<\/pre>\n<p>When the game loads, it will detect your browser and switch to the <strong>WebAudio API\u00a0<\/strong>if it is supported. For the default implementation we simply load an <strong>audio element<\/strong>. Since each audio element can&#8217;t play its sound multiple times in parallel, we need to load several audio elements of the same sound for parallel playback.\u00a0This is all handled nicely by <strong>impact.js<\/strong> so we don&#8217;t have to worry about it.<\/p>\n<p>However for <strong>WebAudio <\/strong>we had to come up with something on our own. To load a sound via the API you mainly use a <a href=\"http:\/\/www.w3.org\/TR\/XMLHttpRequest\/\" target=\"_blank\">XMLHttpRequest<\/a>. But simply loading isn&#8217;t enough. We also need to decode the data we get from the request. This is done via the context mentioned in the <strong>WebAudio API<\/strong> earlier. Let&#8217;s simply assume that we have a global context created and ready to use. The code to load a sound would look like this:<\/p>\n<pre class=\"lang:js decode:true\" title=\"Loading a sound (WebAudio)\">var request = new XMLHttpRequest();\r\nrequest.open('GET', path, true);\r\nrequest.responseType = 'arraybuffer';\r\nrequest.onload = function () {\r\n    sound.context.decodeAudioData(request.response, function (buffer) {\r\n        sound.buffers[path] = buffer;\r\n    }, someFancyErrorHandler())\r\n}\r\nrequest.send();<\/pre>\n<p>As you can see there isn&#8217;t much effort in loading the sound file and decoding it. After we decoded the sound we cache the buffer in some global state, so we don&#8217;t need to load it again. This is also true for quickly repeating the same sound! The buffer will then be referenced in the <strong>WebAudio <\/strong>implementation of the sound object.<\/p>\n<p>You&#8217;ll need to create a node each time you play the sound. That&#8217;s right, In contrast to the <strong>audio element<\/strong> we can&#8217;t simply reset the playback position to zero and play again:<\/p>\n<pre class=\"lang:js decode:true\" title=\"\">play: function() {\r\n    \/\/ creates a AudioNode called AudioBufferSourceNode\r\n    var node = sound.context.createBufferSource(); \r\n    node.buffer = this.buffer;\r\n\r\n    node.connect(sound.context.destination);\r\n    node.start(0); \/\/ or node.noteOn(0);\r\n}<\/pre>\n<p>As you can see we assign the buffer of the sound to the node. The <strong>AudioNode <\/strong>here is a so called <strong>AudioBufferSourceNode<\/strong>. As the name implies it is the base node for playing back any sound. We then need to connect the node to the destination node of the context. As explained at the beginning of this post, this way the sound gets routed through the audio graph. To play it we use the <em><strong>start\/noteOn<\/strong><\/em> method. Don&#8217;t get confused by the parameter. It is mandatory and is used as the offset time to start the playback.<\/p>\n<p>But of course this is not all we do. We also want to position sound, right? For this we simply use another node that <strong>WebAudio <\/strong>already provides to set the position of a sound in 3D: the <strong>PannerNode<\/strong>. We now need to extend the method above to include a possible position:<\/p>\n<pre class=\"lang:js decode:true\" title=\"\">play: function(pos) {\r\n    var node = sound.context.createBufferSource();\r\n    node.buffer = this.buffer;\r\n\r\n    var position = sound.context.createPanner();\r\n    position.setPosition(pos.x, pos.y);\r\n\r\n    node.connect(position);\r\n    position.connect(sound.context.destination);\r\n    node.start(0); \/\/ or node.noteOn(0);\r\n\r\n    return {pos: position, source: node};\r\n}<\/pre>\n<p>For this method, we simply assume that every sound has a position. As you can see we create a new <strong>PannerNode <\/strong>here and connect it to the <strong>AudioBufferSourceNode<\/strong>. We then connect the position node to the destination and start playing the sound like before. In <strong>CrossCode <\/strong>we have a little bit extra code here which adjusts the position to be in world space. There is also a bit of code that makes sure that when you&#8217;re in a certain radius of an object that sound plays normally to create a smoother experience. Otherwise it would be very distracting if every sound makes a sudden gap from the right to the left speaker.<br \/>\nNotice that we also return an object here? In this pseudo-code it returns the position node and the source node. We do this to be able to adjust the position in real-time and be able to stop playback manually.<\/p>\n<p>This is basically what we do with <strong>WebAudio <\/strong>here. Of course not all browsers currently support <strong>WebAudio<\/strong>. So you might wonder what we do with positioned sound for those players. Of course we can&#8217;t achieve the same effect with the <strong>audio element<\/strong>, so we simulate the same effect via the volume of the sound. The further you away from the sound the softer it is. We hope that eventually all browsers will support the API so everyone can have positioned sound in their favorite browser.<\/p>\n<p>And this is it for sound! Let&#8217;s summarize everything in a small list:<\/p>\n<ul>\n<li>create different implementations for <strong>WebAudio <\/strong>and <strong>audio element<\/strong><\/li>\n<li>use <strong>impact.js<\/strong> sound loading for the default implementation<\/li>\n<li>load, decode and cache loaded sounds<\/li>\n<li>play a sound with a position via <strong>PannerNode<\/strong><\/li>\n<li>simulate positioned sound via volume in default implementation<\/li>\n<li>return a handle to manipulate the position of a sound in real-time<\/li>\n<\/ul>\n<h2>Music<\/h2>\n<p>In <a href=\"http:\/\/www.radicalfishgames.com\/?p=311\" target=\"_blank\">Weekly Update #9<\/a> we already told you that we basically rewrote the whole music player <strong>impact.js<\/strong> provides. We created a new interface that fits more our use-cases. We also separated the music player from the actual track. Each track has a minimal interface:<\/p>\n<pre class=\"lang:js decode:true\" title=\"\">Track = {\r\n\r\n    loopEnd: 0,\r\n\r\n    play: function() {},\r\n    pause: function() {},\r\n    stop: function() {},\r\n    setVolume: function() {},\r\n    getVolume: function() {}\r\n}<\/pre>\n<p>Just like with the sound we now have two alternative track implementations. One for the <strong>audio element<\/strong> and one for <strong>WebAudio<\/strong>. Again, for default we use the basic loading that <strong>impact.js<\/strong> provides. When creating a track we make a reference to the <strong>audio element<\/strong> and play it when we need it. The advantage of audio elements here is that they support streaming. That means we don&#8217;t have to wait until the whole track is loaded to start the game. For <strong>WebAudio<\/strong>&#8230; well, here is the bummer: we have the load the whole piece. The API supports the connection to\u00a0<strong>audio elements<\/strong>\u00a0(via the <a href=\"https:\/\/dvcs.w3.org\/hg\/audio\/raw-file\/tip\/webaudio\/specification.html#MediaElementAudioSourceNode\" target=\"_blank\">MediaElementAudioSourceNode<\/a>) to support streaming. However, with the <strong>audio element<\/strong> as source\u00a0we again lose all the timing advantages we get from <strong>WebAudio<\/strong>. Thus, we&#8217;re stuck with an increased loading time when using <strong>WebAudio<\/strong>. Of course since a browser will cache files, restarting the game will make the game load faster again, but we still decided it&#8217;s a better idea to provide music via Web Audio as an option the player can choose.<br \/>\nAs you can see in the code we also have a property called <strong>loopEnd<\/strong>. It stands for the playback position when the track should loop. So our tracks don&#8217;t exactly loop at the end of the file. Instead we created a system for each track implementation which will start playing a second <strong>audio element<\/strong> or <strong>AudioNode <\/strong>when a timer hits the <strong>loopEnd <\/strong>mark. This makes it possible to seamlessly loop tracks. For the <strong>audio element<\/strong> we use the <strong>timeupdate <\/strong>callback that is provided by the element:<\/p>\n<pre class=\"lang:js decode:true\" title=\"\">this.track.addEventListener('timeupdate', function(\r\n    if(this.track.currentTime &gt;= this.loopEnd) {\r\n        \/\/ loop track\r\n    }\r\n));<\/pre>\n<p>For <strong>WebAudio <\/strong>we use the current time of the <strong>WebAudio <\/strong>context to precisely time the next loop. We also have a <strong>loop count<\/strong>, which increases every loop. With this we time each new loop from the first playback of the track. This makes sure that the next loop is always started before the previous loop ended. This can be done easily via <strong>WebAudio <\/strong>since every node has a start time parameter. We simply calculate the offset from the <strong>startTime <\/strong>multiplied with the <strong>loop count<\/strong> and get the same timing for every loop. Here is some code that explains roughly how we did this:<\/p>\n<pre class=\"lang:js decode:true\" title=\"\">\/\/ called periodically \r\nloopcheck: function() {\r\n    var currentTime = sound.context.currenTime;\r\n    var nextOffset = track.duration - loopEnd;\r\n    if (currentTime &gt; (startTime + loopEnd * (loopCount + 1) + nextOffset)) {\r\n        var tempNode = sound.context.createBufferSource();\r\n        tempNode.buffer = soundBuffer;\r\n\r\n        currentNode.stop(0);\r\n\r\n        currentNode = this.nextNode;\r\n        nextNode = tempNode;\r\n\r\n        loopCount++;\r\n        var offset = startTime + (loopCount + 1) * loopEnd;\r\n\r\n        sound.context.connect(this.nextNode);\r\n\r\n        nextNode.start(offset);\r\n    }\r\n\r\n},<\/pre>\n<p>This might seem a little bit confusing. But it does exactly what we want. As you can see we do not exactly start the <strong>next <\/strong>node, but rather the <strong>node after the next node<\/strong>. This also means that when starting the playback we have to start two nodes. However thanks to <strong>WebAudio <\/strong>we can simply shift the start time of the playback and thus time it the way we want.<\/p>\n<p>All of this means that we have some sort of meta-data for each track. Next to the <strong>loopEnd <\/strong>property we might also have an intro that is played before the first loop starts. The intro has an <strong>introEnd <\/strong>property which works just like <strong>loopEnd<\/strong>. We adjust the loop about the time of of <strong>introEnd <\/strong>and voila, we have a custom intro for our tracks!<\/p>\n<p>Now back to the interface we use to play the tracks via our music player. There are several methods our player has:<\/p>\n<ul>\n<li>play<\/li>\n<li>push<\/li>\n<li>pop<\/li>\n<li>inbetween<\/li>\n<li>pause \/ resume<\/li>\n<\/ul>\n<p><strong>Play <\/strong>simply starts a track. It also stops the currently playing track. A track will be played by name, which means each track will be preloaded along with it&#8217;s meta-data and name.<br \/>\n<strong>Push <\/strong>allows us to start a new track while the old one will be paused.<br \/>\n<strong>Pop <\/strong>simply stops the track at the top of the stack and starts the paused track below the top stack element, continuing from where we left off.<br \/>\nWith the push and pop commands we introduce a stack in our music player to control which music piece is currently played. This is done to make sure we can play tracks in between other tracks. Think of a regular background theme. Now a battle starts and the battle music begins to play. After the fight is over the battle music stops and the regular background music start playing at the position it stopped.<br \/>\n<strong>InBetween <\/strong>is a mix of push and pop. You could call it a push with an automated pop. This is even true for tracks that would normally loop. We use this for short tracks like an item get sound (shameless reference to the Zelda series here).<\/p>\n<p>All of these four methods, also include parameters to fade-in and fade-out the tracks. When pushing a new track, we can fade-out the current one and fade-in the pushed one. This is especially nice when you end an battle and while the battle theme is fading out a new battle starts. We simply fade-in the battle theme without even restarting the track. It creates a much better feeling for the player too.<br \/>\n<strong>Pause<\/strong> and <strong>Resume <\/strong>simply pause\/resume the currently playing track. We use this mainly when you switch tabs. The music stops until you re-enter the tab in which the game plays.<\/p>\n<p>Okay, let&#8217;s summarize again what we do for music in CrossCode:<\/p>\n<ul>\n<li>created different track implementations for <strong>WebAudio <\/strong>and <strong>audio element<\/strong><\/li>\n<li>use custom loop mechanic to perfectly loop tracks<\/li>\n<li>music player loads tracks with meta-data<\/li>\n<li>can push\/pop tracks at any time<\/li>\n<li>fade tracks when changing tracks<\/li>\n<li>play a single track in between another track<\/li>\n<\/ul>\n<h2>Conclusion<\/h2>\n<p>All in all creating such a dynamic system sure wasn&#8217;t easy. But it was worth the effort, especially for <strong>WebAudio<\/strong>. We can not only position sound in 3D, we can also perfectly loop our tracks even when using our own looping mechanic. There is the small issue with completely pre-loading each track in <strong>WebAudio<\/strong>, but we hope that somewhere along the line of web technology a solution will emerge. And if not, we will make sure that the player gets some option since no one likes long loading times!<\/p>\n<p>Pheeew~ This is all we have to say on how we do music and sound in <strong>CrossCode<\/strong>! If you have any questions leave a comment and we will make sure to answer it!<br \/>\nWe hope you liked this technical rant. It was really a lot to cover and a lot to read (let&#8217;s hope we did not forget something on the way).<br \/>\nIf you wish to read a technical topic about another feature we use in <strong>CrossCode <\/strong>you can ask us too!<\/p>\n<p>Until next time!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>It&#8217;s time for a new technical post! As promised today, we want to share with you how we implemented music and sound in CrossCode. This includes how we used the WebAudio API to increase the sound quality and make perfect loops possible. Any code you will encounter is not directly from the game and might [&hellip;]<\/p>\n","protected":false},"author":4,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4,5],"tags":[],"class_list":["post-973","post","type-post","status-publish","format-standard","hentry","category-crosscode","category-technical"],"_links":{"self":[{"href":"https:\/\/www.radicalfishgames.com\/index.php?rest_route=\/wp\/v2\/posts\/973","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.radicalfishgames.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.radicalfishgames.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.radicalfishgames.com\/index.php?rest_route=\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/www.radicalfishgames.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=973"}],"version-history":[{"count":74,"href":"https:\/\/www.radicalfishgames.com\/index.php?rest_route=\/wp\/v2\/posts\/973\/revisions"}],"predecessor-version":[{"id":1064,"href":"https:\/\/www.radicalfishgames.com\/index.php?rest_route=\/wp\/v2\/posts\/973\/revisions\/1064"}],"wp:attachment":[{"href":"https:\/\/www.radicalfishgames.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=973"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.radicalfishgames.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=973"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.radicalfishgames.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=973"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}