src/audio/emscripten/SDL_emscriptenaudio.c
author Ryan C. Gordon <icculus@icculus.org>
Wed, 18 Mar 2015 02:01:17 -0400
changeset 9394 bb28e5281770
parent 9344 83f4ab8fb0b8
child 9619 b94b6d0bff0f
permissions -rw-r--r--
Bunch of reworking to how we manage audio devices.

Device enumeration now happens at startup and then is managed exclusively
through hotplugging instead of full redetection. The device name list now has
a unique "handle" associated with each item and SDL will pass this to the
backend so they don't have to figure out how a human readable name maps to
real hardware for a second time.

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