src/audio/emscripten/SDL_emscriptenaudio.c
author Ryan C. Gordon <icculus@icculus.org>
Thu, 18 Dec 2014 00:19:52 -0500
changeset 9278 8900afb78a19
child 9291 02b47b8164da
permissions -rw-r--r--
Initial merge of Emscripten port!

With this commit, you can compile SDL2 with Emscripten
( http://emscripten.org/ ), and make your SDL-based C/C++ program
into a web app.

This port was due to the efforts of several people, including: Charlie Birks,
Sathyanarayanan Gunasekaran, Jukka Jylänki, Alon Zakai, Edward Rudd,
Bruce Mitchener, and Martin Gerhardy. (Thanks, everyone!)
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2014 Sam Lantinga <slouken@libsdl.org>
     4 
     5   This software is provided 'as-is', without any express or implied
     6   warranty.  In no event will the authors be held liable for any damages
     7   arising from the use of this software.
     8 
     9   Permission is granted to anyone to use this software for any purpose,
    10   including commercial applications, and to alter it and redistribute it
    11   freely, subject to the following restrictions:
    12 
    13   1. The origin of this software must not be misrepresented; you must not
    14      claim that you wrote the original software. If you use this software
    15      in a product, an acknowledgment in the product documentation would be
    16      appreciated but is not required.
    17   2. Altered source versions must be plainly marked as such, and must not be
    18      misrepresented as being the original software.
    19   3. This notice may not be removed or altered from any source distribution.
    20 */
    21 #include "../../SDL_internal.h"
    22 
    23 #if SDL_AUDIO_DRIVER_EMSCRIPTEN
    24 
    25 #include "SDL_audio.h"
    26 #include "SDL_log.h"
    27 #include "../SDL_audio_c.h"
    28 #include "SDL_emscriptenaudio.h"
    29 
    30 #include <emscripten/emscripten.h>
    31 
    32 static int
    33 copyData(_THIS)
    34 {
    35     int byte_len;
    36 
    37     if (this->hidden->write_off + this->convert.len_cvt > this->hidden->mixlen) {
    38         if (this->hidden->write_off > this->hidden->read_off) {
    39             SDL_memmove(this->hidden->mixbuf,
    40                         this->hidden->mixbuf + this->hidden->read_off,
    41                         this->hidden->mixlen - this->hidden->read_off);
    42             this->hidden->write_off = this->hidden->write_off - this->hidden->read_off;
    43         } else {
    44             this->hidden->write_off = 0;
    45         }
    46         this->hidden->read_off = 0;
    47     }
    48 
    49     SDL_memcpy(this->hidden->mixbuf + this->hidden->write_off,
    50                this->convert.buf,
    51                this->convert.len_cvt);
    52     this->hidden->write_off += this->convert.len_cvt;
    53     byte_len = this->hidden->write_off - this->hidden->read_off;
    54 
    55     return byte_len;
    56 }
    57 
    58 static void
    59 HandleAudioProcess(_THIS)
    60 {
    61     Uint8 *buf = NULL;
    62     int byte_len = 0;
    63     int bytes = SDL_AUDIO_BITSIZE(this->spec.format) / 8;
    64     int bytes_in = SDL_AUDIO_BITSIZE(this->convert.src_format) / 8;
    65     int i;
    66 
    67     /* Only do soemthing if audio is enabled */
    68     if (!this->enabled)
    69         return;
    70 
    71     if (this->paused)
    72         return;
    73 
    74     if (this->convert.needed) {
    75         if (this->hidden->conv_in_len != 0) {
    76             this->convert.len = this->hidden->conv_in_len * bytes_in * this->spec.channels;
    77         }
    78 
    79         (*this->spec.callback) (this->spec.userdata,
    80                                  this->convert.buf,
    81                                  this->convert.len);
    82         SDL_ConvertAudio(&this->convert);
    83         buf = this->convert.buf;
    84         byte_len = this->convert.len_cvt;
    85 
    86         /* size mismatch*/
    87         if (byte_len != this->spec.size) {
    88             if (!this->hidden->mixbuf) {
    89                 this->hidden->mixlen = this->spec.size > byte_len ? this->spec.size * 2 : byte_len * 2;
    90                 this->hidden->mixbuf = SDL_malloc(this->hidden->mixlen);
    91             }
    92 
    93             /* copy existing data */
    94             byte_len = copyData(this);
    95 
    96             /* read more data*/
    97             while (byte_len < this->spec.size) {
    98                 (*this->spec.callback) (this->spec.userdata,
    99                                          this->convert.buf,
   100                                          this->convert.len);
   101                 SDL_ConvertAudio(&this->convert);
   102                 byte_len = copyData(this);
   103             }
   104 
   105             byte_len = this->spec.size;
   106             buf = this->hidden->mixbuf + this->hidden->read_off;
   107             this->hidden->read_off += byte_len;
   108         }
   109 
   110     } else {
   111         if (!this->hidden->mixbuf) {
   112             this->hidden->mixlen = this->spec.size;
   113             this->hidden->mixbuf = SDL_malloc(this->hidden->mixlen);
   114         }
   115         (*this->spec.callback) (this->spec.userdata,
   116                                  this->hidden->mixbuf,
   117                                  this->hidden->mixlen);
   118         buf = this->hidden->mixbuf;
   119         byte_len = this->hidden->mixlen;
   120     }
   121 
   122     if (buf) {
   123         EM_ASM_ARGS({
   124             var numChannels = SDL2.audio.currentOutputBuffer['numberOfChannels'];
   125             for (var c = 0; c < numChannels; ++c) {
   126                 var channelData = SDL2.audio.currentOutputBuffer['getChannelData'](c);
   127                 if (channelData.length != $1) {
   128                     throw 'Web Audio output buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!';
   129                 }
   130 
   131                 for (var j = 0; j < $1; ++j) {
   132                     channelData[j] = getValue($0 + (j*numChannels + c)*4, 'float');
   133                 }
   134             }
   135         }, buf, byte_len / bytes / this->spec.channels);
   136     }
   137 }
   138 
   139 static void
   140 Emscripten_CloseDevice(_THIS)
   141 {
   142     if (this->hidden != NULL) {
   143         if (this->hidden->mixbuf != NULL) {
   144             /* Clean up the audio buffer */
   145             SDL_free(this->hidden->mixbuf);
   146             this->hidden->mixbuf = NULL;
   147         }
   148 
   149         SDL_free(this->hidden);
   150         this->hidden = NULL;
   151     }
   152 }
   153 
   154 static int
   155 Emscripten_OpenDevice(_THIS, const char *devname, int iscapture)
   156 {
   157     SDL_bool valid_format = SDL_FALSE;
   158     SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format);
   159     int i;
   160     float f;
   161 
   162     while ((!valid_format) && (test_format)) {
   163         switch (test_format) {
   164         case AUDIO_F32: /* web audio only supports floats */
   165             this->spec.format = test_format;
   166 
   167             valid_format = SDL_TRUE;
   168             break;
   169         }
   170         test_format = SDL_NextAudioFormat();
   171     }
   172 
   173     if (!valid_format) {
   174         /* Didn't find a compatible format :( */
   175         return SDL_SetError("No compatible audio format!");
   176     }
   177 
   178     /* Initialize all variables that we clean on shutdown */
   179     this->hidden = (struct SDL_PrivateAudioData *)
   180         SDL_malloc((sizeof *this->hidden));
   181     if (this->hidden == NULL) {
   182         return SDL_OutOfMemory();
   183     }
   184     SDL_memset(this->hidden, 0, (sizeof *this->hidden));
   185 
   186     /* based on parts of library_sdl.js */
   187 
   188     /* create context (TODO: this puts stuff in the global namespace...)*/
   189     EM_ASM({
   190         if(typeof(SDL2) === 'undefined')
   191             SDL2 = {};
   192 
   193         if(typeof(SDL2.audio) === 'undefined')
   194             SDL2.audio = {};
   195 
   196         if (!SDL2.audioContext) {
   197             if (typeof(AudioContext) !== 'undefined') {
   198                 SDL2.audioContext = new AudioContext();
   199             } else if (typeof(webkitAudioContext) !== 'undefined') {
   200                 SDL2.audioContext = new webkitAudioContext();
   201             } else {
   202                 throw 'Web Audio API is not available!';
   203             }
   204         }
   205     });
   206 
   207     /* limit to native freq */
   208     int sampleRate = EM_ASM_INT_V({
   209         return SDL2.audioContext['sampleRate'];
   210     });
   211 
   212     if(this->spec.freq != sampleRate) {
   213         for (i = this->spec.samples; i > 0; i--) {
   214             f = (float)i / (float)sampleRate * (float)this->spec.freq;
   215             if (SDL_floor(f) == f) {
   216                 this->hidden->conv_in_len = SDL_floor(f);
   217                 break;
   218             }
   219         }
   220 
   221         this->spec.freq = sampleRate;
   222     }
   223 
   224     SDL_CalculateAudioSpec(&this->spec);
   225 
   226     /* setup a ScriptProcessorNode */
   227     EM_ASM_ARGS({
   228         SDL2.audio.scriptProcessorNode = SDL2.audioContext['createScriptProcessor']($1, 0, $0);
   229         SDL2.audio.scriptProcessorNode['onaudioprocess'] = function (e) {
   230             SDL2.audio.currentOutputBuffer = e['outputBuffer'];
   231             Runtime.dynCall('vi', $2, [$3]);
   232         };
   233         SDL2.audio.scriptProcessorNode['connect'](SDL2.audioContext['destination']);
   234     }, this->spec.channels, this->spec.samples, HandleAudioProcess, this);
   235     return 0;
   236 }
   237 
   238 static int
   239 Emscripten_Init(SDL_AudioDriverImpl * impl)
   240 {
   241     /* Set the function pointers */
   242     impl->OpenDevice = Emscripten_OpenDevice;
   243     impl->CloseDevice = Emscripten_CloseDevice;
   244 
   245     /* only one output */
   246     impl->OnlyHasDefaultOutputDevice = 1;
   247 
   248     /* no threads here */
   249     impl->SkipMixerLock = 1;
   250     impl->ProvidesOwnCallbackThread = 1;
   251 
   252     /* check availability */
   253     int available = EM_ASM_INT_V({
   254         if (typeof(AudioContext) !== 'undefined') {
   255             return 1;
   256         } else if (typeof(webkitAudioContext) !== 'undefined') {
   257             return 1;
   258         }
   259         return 0;
   260     });
   261 
   262     return available;
   263 }
   264 
   265 AudioBootStrap EmscriptenAudio_bootstrap = {
   266     "emscripten", "SDL emscripten audio driver", Emscripten_Init, 0
   267 };
   268 
   269 #endif /* SDL_AUDIO_DRIVER_EMSCRIPTEN */
   270 
   271 /* vi: set ts=4 sw=4 expandtab: */