src/audio/emscripten/SDL_emscriptenaudio.c
author Ryan C. Gordon <icculus@icculus.org>
Fri, 05 Aug 2016 01:59:06 -0400
changeset 10257 f17581d00c26
parent 10255 9530fc07da6c
child 10274 cc6461b9c5bc
permissions -rw-r--r--
audio: changed some SDL_memset() calls to SDL_zero(), other minor corrections.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2016 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 something if audio is enabled */
    67     if (!SDL_AtomicGet(&this->enabled) || SDL_AtomicGet(&this->paused)) {
    68         return;
    69     }
    70 
    71     if (this->convert.needed) {
    72         if (this->hidden->conv_in_len != 0) {
    73             this->convert.len = this->hidden->conv_in_len * bytes_in * this->spec.channels;
    74         }
    75 
    76         (*this->spec.callback) (this->spec.userdata,
    77                                  this->convert.buf,
    78                                  this->convert.len);
    79         SDL_ConvertAudio(&this->convert);
    80         buf = this->convert.buf;
    81         byte_len = this->convert.len_cvt;
    82 
    83         /* size mismatch*/
    84         if (byte_len != this->spec.size) {
    85             if (!this->hidden->mixbuf) {
    86                 this->hidden->mixlen = this->spec.size > byte_len ? this->spec.size * 2 : byte_len * 2;
    87                 this->hidden->mixbuf = SDL_malloc(this->hidden->mixlen);
    88             }
    89 
    90             /* copy existing data */
    91             byte_len = copyData(this);
    92 
    93             /* read more data*/
    94             while (byte_len < this->spec.size) {
    95                 (*this->spec.callback) (this->spec.userdata,
    96                                          this->convert.buf,
    97                                          this->convert.len);
    98                 SDL_ConvertAudio(&this->convert);
    99                 byte_len = copyData(this);
   100             }
   101 
   102             byte_len = this->spec.size;
   103             buf = this->hidden->mixbuf + this->hidden->read_off;
   104             this->hidden->read_off += byte_len;
   105         }
   106 
   107     } else {
   108         if (!this->hidden->mixbuf) {
   109             this->hidden->mixlen = this->spec.size;
   110             this->hidden->mixbuf = SDL_malloc(this->hidden->mixlen);
   111         }
   112         (*this->spec.callback) (this->spec.userdata,
   113                                  this->hidden->mixbuf,
   114                                  this->hidden->mixlen);
   115         buf = this->hidden->mixbuf;
   116         byte_len = this->hidden->mixlen;
   117     }
   118 
   119     if (buf) {
   120         EM_ASM_ARGS({
   121             var numChannels = SDL2.audio.currentOutputBuffer['numberOfChannels'];
   122             for (var c = 0; c < numChannels; ++c) {
   123                 var channelData = SDL2.audio.currentOutputBuffer['getChannelData'](c);
   124                 if (channelData.length != $1) {
   125                     throw 'Web Audio output buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!';
   126                 }
   127 
   128                 for (var j = 0; j < $1; ++j) {
   129                     channelData[j] = getValue($0 + (j*numChannels + c)*4, 'float');
   130                 }
   131             }
   132         }, buf, byte_len / bytes / this->spec.channels);
   133     }
   134 }
   135 
   136 static void
   137 Emscripten_CloseDevice(_THIS)
   138 {
   139     SDL_free(this->hidden->mixbuf);
   140     SDL_free(this->hidden);
   141 }
   142 
   143 static int
   144 Emscripten_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
   145 {
   146     SDL_bool valid_format = SDL_FALSE;
   147     SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format);
   148     int i;
   149     float f;
   150     int result;
   151 
   152     while ((!valid_format) && (test_format)) {
   153         switch (test_format) {
   154         case AUDIO_F32: /* web audio only supports floats */
   155             this->spec.format = test_format;
   156 
   157             valid_format = SDL_TRUE;
   158             break;
   159         }
   160         test_format = SDL_NextAudioFormat();
   161     }
   162 
   163     if (!valid_format) {
   164         /* Didn't find a compatible format :( */
   165         return SDL_SetError("No compatible audio format!");
   166     }
   167 
   168     /* Initialize all variables that we clean on shutdown */
   169     this->hidden = (struct SDL_PrivateAudioData *)
   170         SDL_malloc((sizeof *this->hidden));
   171     if (this->hidden == NULL) {
   172         return SDL_OutOfMemory();
   173     }
   174     SDL_zerop(this->hidden);
   175 
   176     /* based on parts of library_sdl.js */
   177 
   178     /* create context (TODO: this puts stuff in the global namespace...)*/
   179     result = EM_ASM_INT_V({
   180         if(typeof(SDL2) === 'undefined')
   181             SDL2 = {};
   182 
   183         if(typeof(SDL2.audio) === 'undefined')
   184             SDL2.audio = {};
   185 
   186         if (!SDL2.audioContext) {
   187             if (typeof(AudioContext) !== 'undefined') {
   188                 SDL2.audioContext = new AudioContext();
   189             } else if (typeof(webkitAudioContext) !== 'undefined') {
   190                 SDL2.audioContext = new webkitAudioContext();
   191             } else {
   192                 return -1;
   193             }
   194         }
   195         return 0;
   196     });
   197     if (result < 0) {
   198         return SDL_SetError("Web Audio API is not available!");
   199     }
   200 
   201     /* limit to native freq */
   202     int sampleRate = EM_ASM_INT_V({
   203         return SDL2.audioContext['sampleRate'];
   204     });
   205 
   206     if(this->spec.freq != sampleRate) {
   207         for (i = this->spec.samples; i > 0; i--) {
   208             f = (float)i / (float)sampleRate * (float)this->spec.freq;
   209             if (SDL_floor(f) == f) {
   210                 this->hidden->conv_in_len = SDL_floor(f);
   211                 break;
   212             }
   213         }
   214 
   215         this->spec.freq = sampleRate;
   216     }
   217 
   218     SDL_CalculateAudioSpec(&this->spec);
   219 
   220     /* setup a ScriptProcessorNode */
   221     EM_ASM_ARGS({
   222         SDL2.audio.scriptProcessorNode = SDL2.audioContext['createScriptProcessor']($1, 0, $0);
   223         SDL2.audio.scriptProcessorNode['onaudioprocess'] = function (e) {
   224             SDL2.audio.currentOutputBuffer = e['outputBuffer'];
   225             Runtime.dynCall('vi', $2, [$3]);
   226         };
   227         SDL2.audio.scriptProcessorNode['connect'](SDL2.audioContext['destination']);
   228     }, this->spec.channels, this->spec.samples, HandleAudioProcess, this);
   229     return 0;
   230 }
   231 
   232 static int
   233 Emscripten_Init(SDL_AudioDriverImpl * impl)
   234 {
   235     /* Set the function pointers */
   236     impl->OpenDevice = Emscripten_OpenDevice;
   237     impl->CloseDevice = Emscripten_CloseDevice;
   238 
   239     /* only one output */
   240     impl->OnlyHasDefaultOutputDevice = 1;
   241 
   242     /* no threads here */
   243     impl->SkipMixerLock = 1;
   244     impl->ProvidesOwnCallbackThread = 1;
   245 
   246     /* check availability */
   247     int available = EM_ASM_INT_V({
   248         if (typeof(AudioContext) !== 'undefined') {
   249             return 1;
   250         } else if (typeof(webkitAudioContext) !== 'undefined') {
   251             return 1;
   252         }
   253         return 0;
   254     });
   255 
   256     if (!available) {
   257         SDL_SetError("No audio context available");
   258     }
   259 
   260     return available;
   261 }
   262 
   263 AudioBootStrap EmscriptenAudio_bootstrap = {
   264     "emscripten", "SDL emscripten audio driver", Emscripten_Init, 0
   265 };
   266 
   267 #endif /* SDL_AUDIO_DRIVER_EMSCRIPTEN */
   268 
   269 /* vi: set ts=4 sw=4 expandtab: */