src/audio/emscripten/SDL_emscriptenaudio.c
author Ryan C. Gordon <icculus@icculus.org>
Fri, 05 Aug 2016 01:44:41 -0400
changeset 10255 9530fc07da6c
parent 10238 6fa358b97f4b
child 10257 f17581d00c26
permissions -rw-r--r--
audio: Clean up some CloseDevice() interface details.

- It's now always called if device->hidden isn't NULL, even if OpenDevice()
failed halfway through. This lets implementation code not have to clean up
itself on every possible failure point; just return an error and SDL will
handle it for you.

- Implementations can assume this->hidden != NULL and not check for it.

- implementations don't have to set this->hidden = NULL when done, because
the caller is always about to free(this).

- Don't reset other fields that are in a block of memory about to be free()'d.

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