mirror of
https://github.com/Ryujinx/SDL.git
synced 2025-05-03 18:26:17 +00:00
271 lines
8.5 KiB
C
271 lines
8.5 KiB
C
/*
|
|
Simple DirectMedia Layer
|
|
Copyright (C) 1997-2014 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
|
|
arising from the use of this software.
|
|
|
|
Permission is granted to anyone to use this software for any purpose,
|
|
including commercial applications, and to alter it and redistribute it
|
|
freely, subject to the following restrictions:
|
|
|
|
1. The origin of this software must not be misrepresented; you must not
|
|
claim that you wrote the original software. If you use this software
|
|
in a product, an acknowledgment in the product documentation would be
|
|
appreciated but is not required.
|
|
2. Altered source versions must be plainly marked as such, and must not be
|
|
misrepresented as being the original software.
|
|
3. This notice may not be removed or altered from any source distribution.
|
|
*/
|
|
#include "../../SDL_internal.h"
|
|
|
|
#if SDL_AUDIO_DRIVER_EMSCRIPTEN
|
|
|
|
#include "SDL_audio.h"
|
|
#include "SDL_log.h"
|
|
#include "../SDL_audio_c.h"
|
|
#include "SDL_emscriptenaudio.h"
|
|
|
|
#include <emscripten/emscripten.h>
|
|
|
|
static int
|
|
copyData(_THIS)
|
|
{
|
|
int byte_len;
|
|
|
|
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;
|
|
}
|
|
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;
|
|
}
|
|
|
|
static void
|
|
HandleAudioProcess(_THIS)
|
|
{
|
|
Uint8 *buf = NULL;
|
|
int byte_len = 0;
|
|
int bytes = SDL_AUDIO_BITSIZE(this->spec.format) / 8;
|
|
int bytes_in = SDL_AUDIO_BITSIZE(this->convert.src_format) / 8;
|
|
|
|
/* Only do soemthing if audio is enabled */
|
|
if (!this->enabled)
|
|
return;
|
|
|
|
if (this->paused)
|
|
return;
|
|
|
|
if (this->convert.needed) {
|
|
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);
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
(*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] = getValue($0 + (j*numChannels + c)*4, 'float');
|
|
}
|
|
}
|
|
}, buf, byte_len / bytes / this->spec.channels);
|
|
}
|
|
}
|
|
|
|
static void
|
|
Emscripten_CloseDevice(_THIS)
|
|
{
|
|
if (this->hidden != NULL) {
|
|
if (this->hidden->mixbuf != NULL) {
|
|
/* Clean up the audio buffer */
|
|
SDL_free(this->hidden->mixbuf);
|
|
this->hidden->mixbuf = NULL;
|
|
}
|
|
|
|
SDL_free(this->hidden);
|
|
this->hidden = NULL;
|
|
}
|
|
}
|
|
|
|
static int
|
|
Emscripten_OpenDevice(_THIS, const char *devname, int iscapture)
|
|
{
|
|
SDL_bool valid_format = SDL_FALSE;
|
|
SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format);
|
|
int i;
|
|
float f;
|
|
|
|
while ((!valid_format) && (test_format)) {
|
|
switch (test_format) {
|
|
case AUDIO_F32: /* web audio only supports floats */
|
|
this->spec.format = test_format;
|
|
|
|
valid_format = SDL_TRUE;
|
|
break;
|
|
}
|
|
test_format = SDL_NextAudioFormat();
|
|
}
|
|
|
|
if (!valid_format) {
|
|
/* Didn't find a compatible format :( */
|
|
return SDL_SetError("No compatible audio format!");
|
|
}
|
|
|
|
/* Initialize all variables that we clean on shutdown */
|
|
this->hidden = (struct SDL_PrivateAudioData *)
|
|
SDL_malloc((sizeof *this->hidden));
|
|
if (this->hidden == NULL) {
|
|
return SDL_OutOfMemory();
|
|
}
|
|
SDL_memset(this->hidden, 0, (sizeof *this->hidden));
|
|
|
|
/* based on parts of library_sdl.js */
|
|
|
|
/* create context (TODO: this puts stuff in the global namespace...)*/
|
|
EM_ASM({
|
|
if(typeof(SDL2) === 'undefined')
|
|
SDL2 = {};
|
|
|
|
if(typeof(SDL2.audio) === 'undefined')
|
|
SDL2.audio = {};
|
|
|
|
if (!SDL2.audioContext) {
|
|
if (typeof(AudioContext) !== 'undefined') {
|
|
SDL2.audioContext = new AudioContext();
|
|
} else if (typeof(webkitAudioContext) !== 'undefined') {
|
|
SDL2.audioContext = new webkitAudioContext();
|
|
} else {
|
|
throw 'Web Audio API is not available!';
|
|
}
|
|
}
|
|
});
|
|
|
|
/* limit to native freq */
|
|
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;
|
|
}
|
|
|
|
SDL_CalculateAudioSpec(&this->spec);
|
|
|
|
/* setup a ScriptProcessorNode */
|
|
EM_ASM_ARGS({
|
|
SDL2.audio.scriptProcessorNode = SDL2.audioContext['createScriptProcessor']($1, 0, $0);
|
|
SDL2.audio.scriptProcessorNode['onaudioprocess'] = function (e) {
|
|
SDL2.audio.currentOutputBuffer = e['outputBuffer'];
|
|
Runtime.dynCall('vi', $2, [$3]);
|
|
};
|
|
SDL2.audio.scriptProcessorNode['connect'](SDL2.audioContext['destination']);
|
|
}, this->spec.channels, this->spec.samples, HandleAudioProcess, this);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
Emscripten_Init(SDL_AudioDriverImpl * impl)
|
|
{
|
|
/* Set the function pointers */
|
|
impl->OpenDevice = Emscripten_OpenDevice;
|
|
impl->CloseDevice = Emscripten_CloseDevice;
|
|
|
|
/* only one output */
|
|
impl->OnlyHasDefaultOutputDevice = 1;
|
|
|
|
/* no threads here */
|
|
impl->SkipMixerLock = 1;
|
|
impl->ProvidesOwnCallbackThread = 1;
|
|
|
|
/* check availability */
|
|
int available = EM_ASM_INT_V({
|
|
if (typeof(AudioContext) !== 'undefined') {
|
|
return 1;
|
|
} else if (typeof(webkitAudioContext) !== 'undefined') {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
});
|
|
|
|
return available;
|
|
}
|
|
|
|
AudioBootStrap EmscriptenAudio_bootstrap = {
|
|
"emscripten", "SDL emscripten audio driver", Emscripten_Init, 0
|
|
};
|
|
|
|
#endif /* SDL_AUDIO_DRIVER_EMSCRIPTEN */
|
|
|
|
/* vi: set ts=4 sw=4 expandtab: */
|