src/audio/emscripten/SDL_emscriptenaudio.c
author Sam Lantinga <slouken@libsdl.org>
Tue, 26 May 2015 06:27:46 -0700
changeset 9619 b94b6d0bff0f
parent 9394 bb28e5281770
child 9831 6c7e2f0747f7
permissions -rw-r--r--
Updated the copyright year to 2015
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2015 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 
    66     /* Only do soemthing if audio is enabled */
    67     if (!this->enabled)
    68         return;
    69 
    70     if (this->paused)
    71         return;
    72 
    73     if (this->convert.needed) {
    74         if (this->hidden->conv_in_len != 0) {
    75             this->convert.len = this->hidden->conv_in_len * bytes_in * this->spec.channels;
    76         }
    77 
    78         (*this->spec.callback) (this->spec.userdata,
    79                                  this->convert.buf,
    80                                  this->convert.len);
    81         SDL_ConvertAudio(&this->convert);
    82         buf = this->convert.buf;
    83         byte_len = this->convert.len_cvt;
    84 
    85         /* size mismatch*/
    86         if (byte_len != this->spec.size) {
    87             if (!this->hidden->mixbuf) {
    88                 this->hidden->mixlen = this->spec.size > byte_len ? this->spec.size * 2 : byte_len * 2;
    89                 this->hidden->mixbuf = SDL_malloc(this->hidden->mixlen);
    90             }
    91 
    92             /* copy existing data */
    93             byte_len = copyData(this);
    94 
    95             /* read more data*/
    96             while (byte_len < this->spec.size) {
    97                 (*this->spec.callback) (this->spec.userdata,
    98                                          this->convert.buf,
    99                                          this->convert.len);
   100                 SDL_ConvertAudio(&this->convert);
   101                 byte_len = copyData(this);
   102             }
   103 
   104             byte_len = this->spec.size;
   105             buf = this->hidden->mixbuf + this->hidden->read_off;
   106             this->hidden->read_off += byte_len;
   107         }
   108 
   109     } else {
   110         if (!this->hidden->mixbuf) {
   111             this->hidden->mixlen = this->spec.size;
   112             this->hidden->mixbuf = SDL_malloc(this->hidden->mixlen);
   113         }
   114         (*this->spec.callback) (this->spec.userdata,
   115                                  this->hidden->mixbuf,
   116                                  this->hidden->mixlen);
   117         buf = this->hidden->mixbuf;
   118         byte_len = this->hidden->mixlen;
   119     }
   120 
   121     if (buf) {
   122         EM_ASM_ARGS({
   123             var numChannels = SDL2.audio.currentOutputBuffer['numberOfChannels'];
   124             for (var c = 0; c < numChannels; ++c) {
   125                 var channelData = SDL2.audio.currentOutputBuffer['getChannelData'](c);
   126                 if (channelData.length != $1) {
   127                     throw 'Web Audio output buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!';
   128                 }
   129 
   130                 for (var j = 0; j < $1; ++j) {
   131                     channelData[j] = getValue($0 + (j*numChannels + c)*4, 'float');
   132                 }
   133             }
   134         }, buf, byte_len / bytes / this->spec.channels);
   135     }
   136 }
   137 
   138 static void
   139 Emscripten_CloseDevice(_THIS)
   140 {
   141     if (this->hidden != NULL) {
   142         if (this->hidden->mixbuf != NULL) {
   143             /* Clean up the audio buffer */
   144             SDL_free(this->hidden->mixbuf);
   145             this->hidden->mixbuf = NULL;
   146         }
   147 
   148         SDL_free(this->hidden);
   149         this->hidden = NULL;
   150     }
   151 }
   152 
   153 static int
   154 Emscripten_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
   155 {
   156     SDL_bool valid_format = SDL_FALSE;
   157     SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format);
   158     int i;
   159     float f;
   160     int result;
   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     result = EM_ASM_INT_V({
   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                 return -1;
   203             }
   204         }
   205         return 0;
   206     });
   207     if (result < 0) {
   208         return SDL_SetError("Web Audio API is not available!");
   209     }
   210 
   211     /* limit to native freq */
   212     int sampleRate = EM_ASM_INT_V({
   213         return SDL2.audioContext['sampleRate'];
   214     });
   215 
   216     if(this->spec.freq != sampleRate) {
   217         for (i = this->spec.samples; i > 0; i--) {
   218             f = (float)i / (float)sampleRate * (float)this->spec.freq;
   219             if (SDL_floor(f) == f) {
   220                 this->hidden->conv_in_len = SDL_floor(f);
   221                 break;
   222             }
   223         }
   224 
   225         this->spec.freq = sampleRate;
   226     }
   227 
   228     SDL_CalculateAudioSpec(&this->spec);
   229 
   230     /* setup a ScriptProcessorNode */
   231     EM_ASM_ARGS({
   232         SDL2.audio.scriptProcessorNode = SDL2.audioContext['createScriptProcessor']($1, 0, $0);
   233         SDL2.audio.scriptProcessorNode['onaudioprocess'] = function (e) {
   234             SDL2.audio.currentOutputBuffer = e['outputBuffer'];
   235             Runtime.dynCall('vi', $2, [$3]);
   236         };
   237         SDL2.audio.scriptProcessorNode['connect'](SDL2.audioContext['destination']);
   238     }, this->spec.channels, this->spec.samples, HandleAudioProcess, this);
   239     return 0;
   240 }
   241 
   242 static int
   243 Emscripten_Init(SDL_AudioDriverImpl * impl)
   244 {
   245     /* Set the function pointers */
   246     impl->OpenDevice = Emscripten_OpenDevice;
   247     impl->CloseDevice = Emscripten_CloseDevice;
   248 
   249     /* only one output */
   250     impl->OnlyHasDefaultOutputDevice = 1;
   251 
   252     /* no threads here */
   253     impl->SkipMixerLock = 1;
   254     impl->ProvidesOwnCallbackThread = 1;
   255 
   256     /* check availability */
   257     int available = EM_ASM_INT_V({
   258         if (typeof(AudioContext) !== 'undefined') {
   259             return 1;
   260         } else if (typeof(webkitAudioContext) !== 'undefined') {
   261             return 1;
   262         }
   263         return 0;
   264     });
   265 
   266     return available;
   267 }
   268 
   269 AudioBootStrap EmscriptenAudio_bootstrap = {
   270     "emscripten", "SDL emscripten audio driver", Emscripten_Init, 0
   271 };
   272 
   273 #endif /* SDL_AUDIO_DRIVER_EMSCRIPTEN */
   274 
   275 /* vi: set ts=4 sw=4 expandtab: */