{"id":4384,"date":"2015-04-28T22:27:16","date_gmt":"2015-04-29T05:27:16","guid":{"rendered":"http:\/\/multimedia.cx\/eggs\/?p=4384"},"modified":"2015-04-28T22:27:16","modified_gmt":"2015-04-29T05:27:16","slug":"emscripten-and-web-audio-api","status":"publish","type":"post","link":"https:\/\/multimedia.cx\/eggs\/emscripten-and-web-audio-api\/","title":{"rendered":"Emscripten and Web Audio API"},"content":{"rendered":"<p><strong>Ha! They said it couldn&#8217;t be done!<\/strong> Well, to be fair, <a href=\"http:\/\/multimedia.cx\/eggs\/playing-with-emscripten-and-asm-js\/\"><strong>I said<\/strong> it couldn&#8217;t be done<\/a>. Or maybe that I just didn&#8217;t have any plans to do it. But I did it&#8211; I used <a href=\"http:\/\/kripken.github.io\/emscripten-site\/\">Emscripten<\/a> to cross-compile a CPU-intensive C\/C++ codebase (<a href=\"http:\/\/www.slack.net\/~ant\/libs\/audio.html\">Game Music Emu<\/a>) to JavaScript. Then I leveraged <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Web_Audio_API\">the Web Audio API<\/a> to output audio and visualize the audio using an HTML5 canvas.<\/p>\n<p><a href=\"http:\/\/gamemusic.multimedia.cx\/js-prototype\/\"><strong>Want to see it in action?<\/strong> Here&#8217;s a demonstration.<\/a> Perhaps I will be able to expand the reach of my <a href=\"http:\/\/gamemusic.multimedia.cx\/\">Game Music site<\/a> when I can drop the odd Native Client plugin. This JS-based player works great on Chrome, Firefox, and Safari across desktop operating systems.<\/p>\n<p><em>But this endeavor was not without its challenges.<\/em><\/p>\n<p><strong>Programmatically Generating Audio<\/strong><br \/>\nFirst, I needed to figure out the proper method for procedurally generating audio and making it available to output. Generally, there are 2 approaches for audio output:<\/p>\n<ol>\n<li>Sit in a loop and generate audio, writing it out via a blocking audio call<\/li>\n<li>Implement a callback that the audio system can invoke in order to generate more audio when needed<\/li>\n<\/ol>\n<p>Option #1 is not a good idea for an event-driven language like JavaScript. So I hunted through the rather flexible Web Audio API for a method that allowed something like approach #2. Callbacks are everywhere, after all.<\/p>\n<p>I eventually found what I was looking for with the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/ScriptProcessorNode\">ScriptProcessorNode<\/a>. It seems to be intended to apply post-processing effects to audio streams. A program registers a callback which is passed configurable chunks of audio for processing. I subverted this by simply overwriting the input buffers with the audio generated by the Emscripten-compiled library.<\/p>\n<p>The ScriptProcessorNode interface is fairly well documented and works across multiple browsers. <strong>However<\/strong>, it is already marked as <strong>deprecated<\/strong>:<\/p>\n<blockquote><p>Note: As of the August 29 2014 Web Audio API spec publication, this feature has been marked as deprecated, and is soon to be replaced by Audio Workers.<\/p><\/blockquote>\n<p>Despite being marked as deprecated for 8 months as of this writing, there exists no appreciable amount of documentation for the successor API, these so-called Audio Workers.<\/p>\n<p><em>Vive la web standards!<\/em><\/p>\n<p><strong>Visualize This<\/strong><br \/>\nThe next problem was visualization. The Web Audio API provides the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Web_Audio_API\/Visualizations_with_Web_Audio_API\">AnalyzerNode<\/a> API for accessing both time and frequency domain data from a running audio stream (and fetching the data as both unsigned bytes or floating-point numbers, depending on what the application needs). This is a pretty neat idea. I just wish I could make the API work. <a href=\"http:\/\/chimera.labs.oreilly.com\/books\/1234000001552\/ch05.html\">The simple demos I could find<\/a> worked well enough. But when I wired up a prototype to fetch and visualize the time-domain wave, all I got were center-point samples (an array of values that were all 128).<\/p>\n<p>Even if the API did work, I&#8217;m not sure if it would have been that useful. Per my reading of the AnalyserNode API, it only returns data as a single channel. Why would I want that? My application supports audio with 2 channels. <strong>I want 2 channels of data<\/strong> for visualization.<\/p>\n<p><strong>How To Synchronize<\/strong><br \/>\nSo I rolled my own visualization solution by maintaining a circular buffer of audio when samples were being generated. Then, requestAnimationFrame() provided the rendering callbacks. The next problem was audio-visual sync. But that certainly is not unique to this situation&#8211; maintaining proper A\/V sync is a perennial puzzle in real-time multimedia programming. I was able to glean enough timing information from the environment to achieve reasonable A\/V sync (verify for yourself).<\/p>\n<p><strong>Pause\/Resume<\/strong><br \/>\nThe next problem I encountered with the Web Audio API was pause\/resume facilities, or the lack thereof. For all its bells and whistles, the API&#8217;s omission of such facilities seems most unusual, as if the design philosophy was, &#8220;Once the user starts playing audio, they will never, ever have cause to pause the audio.&#8221;<\/p>\n<p>Then again, I must understand that mine is not a use case that the design committee considered and I&#8217;m subverting the API in ways the designers didn&#8217;t intend. Typical use cases for this API seem to include such workloads as:<\/p>\n<ul>\n<li>Downloading, decoding, and playing back a compressed audio stream via the network, applying effects, and visualizing the result<\/li>\n<li>Accessing microphone input, applying effects, visualizing, encoding and sending the data across the network<\/li>\n<li>Firing sound effects in a gaming application<\/li>\n<li><a href=\"http:\/\/mudcu.be\/midi-js\/\">MIDI playback via JavaScript<\/a> (this honestly amazes me)<\/li>\n<\/ul>\n<p>What they did not seem to have in mind was what I am trying to do&#8211; synthesize audio in real time.<\/p>\n<p>I implemented pause\/resume in a sub-par manner: pausing has the effect of generating 0 values when the ScriptProcessorNode callback is invoked, while also canceling any animation callbacks. Thus, audio output is technically still occurring, it&#8217;s just that the audio is pure silence. It&#8217;s not a great solution because CPU is still being used.<\/p>\n<p><strong>Future Work<\/strong><br \/>\nI have a lot more player libraries to port to this new system. But I think I have a good framework set up.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>How to convert a CPU-intensive audio library from C\/C++ to JavaScript and play and visualize the audio via Web Audio API and HTML5 canvas<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[223],"tags":[],"class_list":["post-4384","post","type-post","status-publish","format-standard","hentry","category-html5"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/multimedia.cx\/eggs\/wp-json\/wp\/v2\/posts\/4384","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/multimedia.cx\/eggs\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/multimedia.cx\/eggs\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/multimedia.cx\/eggs\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/multimedia.cx\/eggs\/wp-json\/wp\/v2\/comments?post=4384"}],"version-history":[{"count":8,"href":"https:\/\/multimedia.cx\/eggs\/wp-json\/wp\/v2\/posts\/4384\/revisions"}],"predecessor-version":[{"id":4392,"href":"https:\/\/multimedia.cx\/eggs\/wp-json\/wp\/v2\/posts\/4384\/revisions\/4392"}],"wp:attachment":[{"href":"https:\/\/multimedia.cx\/eggs\/wp-json\/wp\/v2\/media?parent=4384"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/multimedia.cx\/eggs\/wp-json\/wp\/v2\/categories?post=4384"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/multimedia.cx\/eggs\/wp-json\/wp\/v2\/tags?post=4384"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}