| | |
| | | /* |
| | | Simple DirectMedia Layer |
| | | Copyright (C) 1997-2016 Sam Lantinga <slouken@libsdl.org> |
| | | Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org> |
| | | |
| | | This software is provided 'as-is', without any express or implied |
| | | warranty. In no event will the authors be held liable for any damages |
| | |
| | | #include "SDL_log.h" |
| | | #include "../SDL_audio_c.h" |
| | | #include "SDL_emscriptenaudio.h" |
| | | #include "SDL_assert.h" |
| | | |
| | | #include <emscripten/emscripten.h> |
| | | |
| | | static int |
| | | copyData(_THIS) |
| | | static void |
| | | FeedAudioDevice(_THIS, const void *buf, const int buflen) |
| | | { |
| | | int byte_len; |
| | | const int framelen = (SDL_AUDIO_BITSIZE(this->spec.format) / 8) * this->spec.channels; |
| | | EM_ASM_ARGS({ |
| | | var numChannels = SDL2.audio.currentOutputBuffer['numberOfChannels']; |
| | | for (var c = 0; c < numChannels; ++c) { |
| | | var channelData = SDL2.audio.currentOutputBuffer['getChannelData'](c); |
| | | if (channelData.length != $1) { |
| | | throw 'Web Audio output buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!'; |
| | | } |
| | | |
| | | if (this->hidden->write_off + this->convert.len_cvt > this->hidden->mixlen) { |
| | | if (this->hidden->write_off > this->hidden->read_off) { |
| | | SDL_memmove(this->hidden->mixbuf, |
| | | this->hidden->mixbuf + this->hidden->read_off, |
| | | this->hidden->mixlen - this->hidden->read_off); |
| | | this->hidden->write_off = this->hidden->write_off - this->hidden->read_off; |
| | | } else { |
| | | this->hidden->write_off = 0; |
| | | for (var j = 0; j < $1; ++j) { |
| | | channelData[j] = HEAPF32[$0 + ((j*numChannels + c) << 2) >> 2]; /* !!! FIXME: why are these shifts here? */ |
| | | } |
| | | } |
| | | this->hidden->read_off = 0; |
| | | } |
| | | |
| | | SDL_memcpy(this->hidden->mixbuf + this->hidden->write_off, |
| | | this->convert.buf, |
| | | this->convert.len_cvt); |
| | | this->hidden->write_off += this->convert.len_cvt; |
| | | byte_len = this->hidden->write_off - this->hidden->read_off; |
| | | |
| | | return byte_len; |
| | | }, buf, buflen / framelen); |
| | | } |
| | | |
| | | static void |
| | | HandleAudioProcess(_THIS) |
| | | { |
| | | Uint8 *buf = NULL; |
| | | int byte_len = 0; |
| | | int bytes = SDL_AUDIO_BITSIZE(this->spec.format) / 8; |
| | | SDL_AudioCallback callback = this->callbackspec.callback; |
| | | const int stream_len = this->callbackspec.size; |
| | | |
| | | /* Only do something if audio is enabled */ |
| | | if (!SDL_AtomicGet(&this->enabled) || SDL_AtomicGet(&this->paused)) { |
| | | if (this->stream) { |
| | | SDL_AudioStreamClear(this->stream); |
| | | } |
| | | return; |
| | | } |
| | | |
| | | if (this->convert.needed) { |
| | | const int bytes_in = SDL_AUDIO_BITSIZE(this->convert.src_format) / 8; |
| | | |
| | | if (this->hidden->conv_in_len != 0) { |
| | | this->convert.len = this->hidden->conv_in_len * bytes_in * this->spec.channels; |
| | | } |
| | | |
| | | (*this->spec.callback) (this->spec.userdata, |
| | | this->convert.buf, |
| | | this->convert.len); |
| | | SDL_ConvertAudio(&this->convert); |
| | | buf = this->convert.buf; |
| | | byte_len = this->convert.len_cvt; |
| | | |
| | | /* size mismatch*/ |
| | | if (byte_len != this->spec.size) { |
| | | if (!this->hidden->mixbuf) { |
| | | this->hidden->mixlen = this->spec.size > byte_len ? this->spec.size * 2 : byte_len * 2; |
| | | this->hidden->mixbuf = SDL_malloc(this->hidden->mixlen); |
| | | if (this->stream == NULL) { /* no conversion necessary. */ |
| | | SDL_assert(this->spec.size == stream_len); |
| | | callback(this->callbackspec.userdata, this->work_buffer, stream_len); |
| | | } else { /* streaming/converting */ |
| | | int got; |
| | | while (SDL_AudioStreamAvailable(this->stream) < ((int) this->spec.size)) { |
| | | callback(this->callbackspec.userdata, this->work_buffer, stream_len); |
| | | if (SDL_AudioStreamPut(this->stream, this->work_buffer, stream_len) == -1) { |
| | | SDL_AudioStreamClear(this->stream); |
| | | SDL_AtomicSet(&this->enabled, 0); |
| | | break; |
| | | } |
| | | |
| | | /* copy existing data */ |
| | | byte_len = copyData(this); |
| | | |
| | | /* read more data*/ |
| | | while (byte_len < this->spec.size) { |
| | | (*this->spec.callback) (this->spec.userdata, |
| | | this->convert.buf, |
| | | this->convert.len); |
| | | SDL_ConvertAudio(&this->convert); |
| | | byte_len = copyData(this); |
| | | } |
| | | |
| | | byte_len = this->spec.size; |
| | | buf = this->hidden->mixbuf + this->hidden->read_off; |
| | | this->hidden->read_off += byte_len; |
| | | } |
| | | |
| | | } else { |
| | | if (!this->hidden->mixbuf) { |
| | | this->hidden->mixlen = this->spec.size; |
| | | this->hidden->mixbuf = SDL_malloc(this->hidden->mixlen); |
| | | got = SDL_AudioStreamGet(this->stream, this->work_buffer, this->spec.size); |
| | | SDL_assert((got < 0) || (got == this->spec.size)); |
| | | if (got != this->spec.size) { |
| | | SDL_memset(this->work_buffer, this->spec.silence, this->spec.size); |
| | | } |
| | | (*this->spec.callback) (this->spec.userdata, |
| | | this->hidden->mixbuf, |
| | | this->hidden->mixlen); |
| | | buf = this->hidden->mixbuf; |
| | | byte_len = this->hidden->mixlen; |
| | | } |
| | | |
| | | if (buf) { |
| | | EM_ASM_ARGS({ |
| | | var numChannels = SDL2.audio.currentOutputBuffer['numberOfChannels']; |
| | | for (var c = 0; c < numChannels; ++c) { |
| | | var channelData = SDL2.audio.currentOutputBuffer['getChannelData'](c); |
| | | if (channelData.length != $1) { |
| | | throw 'Web Audio output buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!'; |
| | | } |
| | | |
| | | for (var j = 0; j < $1; ++j) { |
| | | channelData[j] = HEAPF32[$0 + ((j*numChannels + c) << 2) >> 2]; |
| | | } |
| | | } |
| | | }, buf, byte_len / bytes / this->spec.channels); |
| | | } |
| | | FeedAudioDevice(this, this->work_buffer, this->spec.size); |
| | | } |
| | | |
| | | static void |
| | | HandleCaptureProcess(_THIS) |
| | | { |
| | | Uint8 *buf; |
| | | int buflen; |
| | | SDL_AudioCallback callback = this->callbackspec.callback; |
| | | const int stream_len = this->callbackspec.size; |
| | | |
| | | /* Only do something if audio is enabled */ |
| | | if (!SDL_AtomicGet(&this->enabled) || SDL_AtomicGet(&this->paused)) { |
| | | SDL_AudioStreamClear(this->stream); |
| | | return; |
| | | } |
| | | |
| | | if (this->convert.needed) { |
| | | buf = this->convert.buf; |
| | | buflen = this->convert.len_cvt; |
| | | } else { |
| | | if (!this->hidden->mixbuf) { |
| | | this->hidden->mixbuf = (Uint8 *) SDL_malloc(this->spec.size); |
| | | if (!this->hidden->mixbuf) { |
| | | return; /* oh well. */ |
| | | } |
| | | } |
| | | buf = this->hidden->mixbuf; |
| | | buflen = this->spec.size; |
| | | } |
| | | |
| | | EM_ASM_ARGS({ |
| | | var numChannels = SDL2.capture.currentCaptureBuffer.numberOfChannels; |
| | | if (numChannels == 1) { /* fastpath this a little for the common (mono) case. */ |
| | | var channelData = SDL2.capture.currentCaptureBuffer.getChannelData(0); |
| | | for (var c = 0; c < numChannels; ++c) { |
| | | var channelData = SDL2.capture.currentCaptureBuffer.getChannelData(c); |
| | | if (channelData.length != $1) { |
| | | throw 'Web Audio capture buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!'; |
| | | } |
| | | for (var j = 0; j < $1; ++j) { |
| | | setValue($0 + (j * 4), channelData[j], 'float'); |
| | | } |
| | | } else { |
| | | for (var c = 0; c < numChannels; ++c) { |
| | | var channelData = SDL2.capture.currentCaptureBuffer.getChannelData(c); |
| | | if (channelData.length != $1) { |
| | | throw 'Web Audio capture buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!'; |
| | | } |
| | | |
| | | if (numChannels == 1) { /* fastpath this a little for the common (mono) case. */ |
| | | for (var j = 0; j < $1; ++j) { |
| | | setValue($0 + (j * 4), channelData[j], 'float'); |
| | | } |
| | | } else { |
| | | for (var j = 0; j < $1; ++j) { |
| | | setValue($0 + (((j * numChannels) + c) * 4), channelData[j], 'float'); |
| | | } |
| | | } |
| | | } |
| | | }, buf, (this->spec.size / sizeof (float)) / this->spec.channels); |
| | | }, this->work_buffer, (this->spec.size / sizeof (float)) / this->spec.channels); |
| | | |
| | | /* okay, we've got an interleaved float32 array in C now. */ |
| | | |
| | | if (this->convert.needed) { |
| | | SDL_ConvertAudio(&this->convert); |
| | | if (this->stream == NULL) { /* no conversion necessary. */ |
| | | SDL_assert(this->spec.size == stream_len); |
| | | callback(this->callbackspec.userdata, this->work_buffer, stream_len); |
| | | } else { /* streaming/converting */ |
| | | if (SDL_AudioStreamPut(this->stream, this->work_buffer, this->spec.size) == -1) { |
| | | SDL_AtomicSet(&this->enabled, 0); |
| | | } |
| | | |
| | | while (SDL_AudioStreamAvailable(this->stream) >= stream_len) { |
| | | const int got = SDL_AudioStreamGet(this->stream, this->work_buffer, stream_len); |
| | | SDL_assert((got < 0) || (got == stream_len)); |
| | | if (got != stream_len) { |
| | | SDL_memset(this->work_buffer, this->callbackspec.silence, stream_len); |
| | | } |
| | | callback(this->callbackspec.userdata, this->work_buffer, stream_len); /* Send it to the app. */ |
| | | } |
| | | } |
| | | |
| | | /* Send it to the app. */ |
| | | (*this->spec.callback) (this->spec.userdata, buf, buflen); |
| | | } |
| | | |
| | | |
| | | |
| | | static void |
| | |
| | | } |
| | | }, this->iscapture); |
| | | |
| | | SDL_free(this->hidden->mixbuf); |
| | | #if 0 /* !!! FIXME: currently not used. Can we move some stuff off the SDL2 namespace? --ryan. */ |
| | | SDL_free(this->hidden); |
| | | #endif |
| | | } |
| | | |
| | | static int |
| | |
| | | { |
| | | SDL_bool valid_format = SDL_FALSE; |
| | | SDL_AudioFormat test_format; |
| | | int i; |
| | | float f; |
| | | int result; |
| | | |
| | | /* based on parts of library_sdl.js */ |
| | |
| | | } |
| | | |
| | | /* Initialize all variables that we clean on shutdown */ |
| | | #if 0 /* !!! FIXME: currently not used. Can we move some stuff off the SDL2 namespace? --ryan. */ |
| | | this->hidden = (struct SDL_PrivateAudioData *) |
| | | SDL_malloc((sizeof *this->hidden)); |
| | | if (this->hidden == NULL) { |
| | | return SDL_OutOfMemory(); |
| | | } |
| | | SDL_zerop(this->hidden); |
| | | #endif |
| | | |
| | | /* limit to native freq */ |
| | | const int sampleRate = EM_ASM_INT_V({ |
| | | return SDL2.audioContext.sampleRate; |
| | | }); |
| | | |
| | | if(this->spec.freq != sampleRate) { |
| | | for (i = this->spec.samples; i > 0; i--) { |
| | | f = (float)i / (float)sampleRate * (float)this->spec.freq; |
| | | if (SDL_floor(f) == f) { |
| | | this->hidden->conv_in_len = SDL_floor(f); |
| | | break; |
| | | } |
| | | } |
| | | |
| | | this->spec.freq = sampleRate; |
| | | } |
| | | this->spec.freq = EM_ASM_INT_V({ return SDL2.audioContext.sampleRate; }); |
| | | |
| | | SDL_CalculateAudioSpec(&this->spec); |
| | | |
| | |
| | | static int |
| | | EMSCRIPTENAUDIO_Init(SDL_AudioDriverImpl * impl) |
| | | { |
| | | int available; |
| | | int capture_available; |
| | | |
| | | /* Set the function pointers */ |
| | | impl->OpenDevice = EMSCRIPTENAUDIO_OpenDevice; |
| | | impl->CloseDevice = EMSCRIPTENAUDIO_CloseDevice; |
| | |
| | | impl->ProvidesOwnCallbackThread = 1; |
| | | |
| | | /* check availability */ |
| | | const int available = EM_ASM_INT_V({ |
| | | available = EM_ASM_INT_V({ |
| | | if (typeof(AudioContext) !== 'undefined') { |
| | | return 1; |
| | | } else if (typeof(webkitAudioContext) !== 'undefined') { |
| | |
| | | SDL_SetError("No audio context available"); |
| | | } |
| | | |
| | | const int capture_available = available && EM_ASM_INT_V({ |
| | | capture_available = available && EM_ASM_INT_V({ |
| | | if ((typeof(navigator.mediaDevices) !== 'undefined') && (typeof(navigator.mediaDevices.getUserMedia) !== 'undefined')) { |
| | | return 1; |
| | | } else if (typeof(navigator.webkitGetUserMedia) !== 'undefined') { |