aboutsummaryrefslogtreecommitdiff
path: root/src/library_sdl.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/library_sdl.js')
-rw-r--r--src/library_sdl.js465
1 files changed, 290 insertions, 175 deletions
diff --git a/src/library_sdl.js b/src/library_sdl.js
index 116bf547..27f2c0da 100644
--- a/src/library_sdl.js
+++ b/src/library_sdl.js
@@ -35,7 +35,7 @@ var LibrarySDL = {
volume: 1.0
},
mixerFrequency: 22050,
- mixerFormat: 0x8010, // AUDIO_S16LSB
+ mixerFormat: {{{ cDefine('AUDIO_S16LSB') }}}, //0x8010, // AUDIO_S16LSB
mixerNumChannels: 2,
mixerChunkSize: 1024,
channelMinimumNumber: 0,
@@ -151,89 +151,12 @@ var LibrarySDL = {
305: 224, // ctrl
308: 226, // alt
},
-
- structs: {
- Rect: Runtime.generateStructInfo([
- ['i32', 'x'], ['i32', 'y'], ['i32', 'w'], ['i32', 'h'],
- ]),
- PixelFormat: Runtime.generateStructInfo([
- ['i32', 'format'],
- ['void*', 'palette'], ['i8', 'BitsPerPixel'], ['i8', 'BytesPerPixel'],
- ['i8', 'padding1'], ['i8', 'padding2'],
- ['i32', 'Rmask'], ['i32', 'Gmask'], ['i32', 'Bmask'], ['i32', 'Amask'],
- ['i8', 'Rloss'], ['i8', 'Gloss'], ['i8', 'Bloss'], ['i8', 'Aloss'],
- ['i8', 'Rshift'], ['i8', 'Gshift'], ['i8', 'Bshift'], ['i8', 'Ashift']
- ]),
- KeyboardEvent: Runtime.generateStructInfo([
- ['i32', 'type'],
- ['i32', 'windowID'],
- ['i8', 'state'],
- ['i8', 'repeat'],
- ['i8', 'padding2'],
- ['i8', 'padding3'],
- ['i32', 'keysym']
- ]),
- keysym: Runtime.generateStructInfo([
- ['i32', 'scancode'],
- ['i32', 'sym'],
- ['i16', 'mod'],
- ['i32', 'unicode']
- ]),
- TextInputEvent: Runtime.generateStructInfo([
- ['i32', 'type'],
- ['i32', 'windowID'],
- ['b256', 'text'],
- ]),
- MouseMotionEvent: Runtime.generateStructInfo([
- ['i32', 'type'],
- ['i32', 'windowID'],
- ['i8', 'state'],
- ['i8', 'padding1'],
- ['i8', 'padding2'],
- ['i8', 'padding3'],
- ['i32', 'x'],
- ['i32', 'y'],
- ['i32', 'xrel'],
- ['i32', 'yrel']
- ]),
- MouseButtonEvent: Runtime.generateStructInfo([
- ['i32', 'type'],
- ['i32', 'windowID'],
- ['i8', 'button'],
- ['i8', 'state'],
- ['i8', 'padding1'],
- ['i8', 'padding2'],
- ['i32', 'x'],
- ['i32', 'y']
- ]),
- ResizeEvent: Runtime.generateStructInfo([
- ['i32', 'type'],
- ['i32', 'w'],
- ['i32', 'h']
- ]),
- AudioSpec: Runtime.generateStructInfo([
- ['i32', 'freq'],
- ['i16', 'format'],
- ['i8', 'channels'],
- ['i8', 'silence'],
- ['i16', 'samples'],
- ['i32', 'size'],
- ['void*', 'callback'],
- ['void*', 'userdata']
- ]),
- version: Runtime.generateStructInfo([
- ['i8', 'major'],
- ['i8', 'minor'],
- ['i8', 'patch']
- ])
- },
-
loadRect: function(rect) {
return {
- x: {{{ makeGetValue('rect + SDL.structs.Rect.x', '0', 'i32') }}},
- y: {{{ makeGetValue('rect + SDL.structs.Rect.y', '0', 'i32') }}},
- w: {{{ makeGetValue('rect + SDL.structs.Rect.w', '0', 'i32') }}},
- h: {{{ makeGetValue('rect + SDL.structs.Rect.h', '0', 'i32') }}}
+ x: {{{ makeGetValue('rect + ' + C_STRUCTS.SDL_Rect.x, '0', 'i32') }}},
+ y: {{{ makeGetValue('rect + ' + C_STRUCTS.SDL_Rect.y, '0', 'i32') }}},
+ w: {{{ makeGetValue('rect + ' + C_STRUCTS.SDL_Rect.w, '0', 'i32') }}},
+ h: {{{ makeGetValue('rect + ' + C_STRUCTS.SDL_Rect.h, '0', 'i32') }}}
};
},
@@ -261,35 +184,35 @@ var LibrarySDL = {
makeSurface: function(width, height, flags, usePageCanvas, source, rmask, gmask, bmask, amask) {
flags = flags || 0;
- var surf = _malloc(15*Runtime.QUANTUM_SIZE); // SDL_Surface has 15 fields of quantum size
+ var surf = _malloc({{{ C_STRUCTS.SDL_Surface.__size__ }}}); // SDL_Surface has 15 fields of quantum size
var buffer = _malloc(width*height*4); // TODO: only allocate when locked the first time
- var pixelFormat = _malloc(18*Runtime.QUANTUM_SIZE);
+ var pixelFormat = _malloc({{{ C_STRUCTS.SDL_PixelFormat.__size__ }}});
flags |= 1; // SDL_HWSURFACE - this tells SDL_MUSTLOCK that this needs to be locked
//surface with SDL_HWPALETTE flag is 8bpp surface (1 byte)
var is_SDL_HWPALETTE = flags & 0x00200000;
var bpp = is_SDL_HWPALETTE ? 1 : 4;
- {{{ makeSetValue('surf+Runtime.QUANTUM_SIZE*0', '0', 'flags', 'i32') }}} // SDL_Surface.flags
- {{{ makeSetValue('surf+Runtime.QUANTUM_SIZE*1', '0', 'pixelFormat', 'void*') }}} // SDL_Surface.format TODO
- {{{ makeSetValue('surf+Runtime.QUANTUM_SIZE*2', '0', 'width', 'i32') }}} // SDL_Surface.w
- {{{ makeSetValue('surf+Runtime.QUANTUM_SIZE*3', '0', 'height', 'i32') }}} // SDL_Surface.h
- {{{ makeSetValue('surf+Runtime.QUANTUM_SIZE*4', '0', 'width * bpp', 'i32') }}} // SDL_Surface.pitch, assuming RGBA or indexed for now,
+ {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.flags, 'flags', 'i32') }}} // SDL_Surface.flags
+ {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.format, 'pixelFormat', 'void*') }}} // SDL_Surface.format TODO
+ {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.w, 'width', 'i32') }}} // SDL_Surface.w
+ {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.h, 'height', 'i32') }}} // SDL_Surface.h
+ {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.pitch, 'width * bpp', 'i32') }}} // SDL_Surface.pitch, assuming RGBA or indexed for now,
// since that is what ImageData gives us in browsers
- {{{ makeSetValue('surf+Runtime.QUANTUM_SIZE*5', '0', 'buffer', 'void*') }}} // SDL_Surface.pixels
- {{{ makeSetValue('surf+Runtime.QUANTUM_SIZE*6', '0', '0', 'i32*') }}} // SDL_Surface.offset
+ {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.pixels, 'buffer', 'void*') }}} // SDL_Surface.pixels
+ {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.clip_rect, '0', 'i32*') }}} // SDL_Surface.offset
- {{{ makeSetValue('surf+Runtime.QUANTUM_SIZE*14', '0', '1', 'i32') }}}
+ {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.refcount, '1', 'i32') }}}
- {{{ makeSetValue('pixelFormat + SDL.structs.PixelFormat.format', '0', '-2042224636', 'i32') }}} // SDL_PIXELFORMAT_RGBA8888
- {{{ makeSetValue('pixelFormat + SDL.structs.PixelFormat.palette', '0', '0', 'i32') }}} // TODO
- {{{ makeSetValue('pixelFormat + SDL.structs.PixelFormat.BitsPerPixel', '0', 'bpp * 8', 'i8') }}}
- {{{ makeSetValue('pixelFormat + SDL.structs.PixelFormat.BytesPerPixel', '0', 'bpp', 'i8') }}}
+ {{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.format, cDefine('SDL_PIXELFORMAT_RGBA8888'), 'i32') }}} // SDL_PIXELFORMAT_RGBA8888
+ {{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.palette, '0', 'i32') }}} // TODO
+ {{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.BitsPerPixel, 'bpp * 8', 'i8') }}}
+ {{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.BytesPerPixel, 'bpp', 'i8') }}}
- {{{ makeSetValue('pixelFormat + SDL.structs.PixelFormat.Rmask', '0', 'rmask || 0x000000ff', 'i32') }}}
- {{{ makeSetValue('pixelFormat + SDL.structs.PixelFormat.Gmask', '0', 'gmask || 0x0000ff00', 'i32') }}}
- {{{ makeSetValue('pixelFormat + SDL.structs.PixelFormat.Bmask', '0', 'bmask || 0x00ff0000', 'i32') }}}
- {{{ makeSetValue('pixelFormat + SDL.structs.PixelFormat.Amask', '0', 'amask || 0xff000000', 'i32') }}}
+ {{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.Rmask, 'rmask || 0x000000ff', 'i32') }}}
+ {{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.Gmask, 'gmask || 0x0000ff00', 'i32') }}}
+ {{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.Bmask, 'bmask || 0x00ff0000', 'i32') }}}
+ {{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.Amask, 'amask || 0xff000000', 'i32') }}}
// Decide if we want to use WebGL or not
var useWebGL = (flags & 0x04000000) != 0; // SDL_OPENGL
@@ -367,7 +290,7 @@ var LibrarySDL = {
},
freeSurface: function(surf) {
- var refcountPointer = surf + Runtime.QUANTUM_SIZE * 14;
+ var refcountPointer = surf + {{{ C_STRUCTS.SDL_Surface.refcount }}};
var refcount = {{{ makeGetValue('refcountPointer', '0', 'i32') }}};
if (refcount > 1) {
{{{ makeSetValue('refcountPointer', '0', 'refcount - 1', 'i32') }}};
@@ -608,7 +531,7 @@ var LibrarySDL = {
makeCEvent: function(event, ptr) {
if (typeof event === 'number') {
// This is a pointer to a native C event that was SDL_PushEvent'ed
- _memcpy(ptr, event, SDL.structs.KeyboardEvent.__size__); // XXX
+ _memcpy(ptr, event, {{{ C_STRUCTS.SDL_KeyboardEvent.__size__ }}}); // XXX
return;
}
@@ -631,52 +554,52 @@ var LibrarySDL = {
scan = SDL.scanCodes[key] || key;
}
- {{{ makeSetValue('ptr', 'SDL.structs.KeyboardEvent.type', 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}
- {{{ makeSetValue('ptr', 'SDL.structs.KeyboardEvent.state', 'down ? 1 : 0', 'i8') }}}
- {{{ makeSetValue('ptr', 'SDL.structs.KeyboardEvent.repeat', '0', 'i8') }}} // TODO
- {{{ makeSetValue('ptr', 'SDL.structs.KeyboardEvent.keysym + SDL.structs.keysym.scancode', 'scan', 'i32') }}}
- {{{ makeSetValue('ptr', 'SDL.structs.KeyboardEvent.keysym + SDL.structs.keysym.sym', 'key', 'i32') }}}
- {{{ makeSetValue('ptr', 'SDL.structs.KeyboardEvent.keysym + SDL.structs.keysym.mod', 'SDL.modState', 'i16') }}}
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.state, 'down ? 1 : 0', 'i8') }}}
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.repeat, '0', 'i8') }}} // TODO
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.keysym + C_STRUCTS.SDL_Keysym.scancode, 'scan', 'i32') }}}
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.keysym + C_STRUCTS.SDL_Keysym.sym, 'key', 'i32') }}}
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.keysym + C_STRUCTS.SDL_Keysym.mod, 'SDL.modState', 'i16') }}}
// some non-character keys (e.g. backspace and tab) won't have keypressCharCode set, fill in with the keyCode.
- {{{ makeSetValue('ptr', 'SDL.structs.KeyboardEvent.keysym + SDL.structs.keysym.unicode', 'event.keypressCharCode || key', 'i32') }}}
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.keysym + C_STRUCTS.SDL_Keysym.unicode, 'event.keypressCharCode || key', 'i32') }}}
break;
}
case 'keypress': {
- {{{ makeSetValue('ptr', 'SDL.structs.TextInputEvent.type', 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_TextInputEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}
// Not filling in windowID for now
var cStr = intArrayFromString(String.fromCharCode(event.charCode));
for (var i = 0; i < cStr.length; ++i) {
- {{{ makeSetValue('ptr', 'SDL.structs.TextInputEvent.text + i', 'cStr[i]', 'i8') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_TextInputEvent.text + ' + i', 'cStr[i]', 'i8') }}};
}
break;
}
case 'mousedown': case 'mouseup': case 'mousemove': {
if (event.type != 'mousemove') {
var down = event.type === 'mousedown';
- {{{ makeSetValue('ptr', 'SDL.structs.MouseButtonEvent.type', 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
- {{{ makeSetValue('ptr', 'SDL.structs.MouseButtonEvent.button', 'event.button+1', 'i8') }}}; // DOM buttons are 0-2, SDL 1-3
- {{{ makeSetValue('ptr', 'SDL.structs.MouseButtonEvent.state', 'down ? 1 : 0', 'i8') }}};
- {{{ makeSetValue('ptr', 'SDL.structs.MouseButtonEvent.x', 'Browser.mouseX', 'i32') }}};
- {{{ makeSetValue('ptr', 'SDL.structs.MouseButtonEvent.y', 'Browser.mouseY', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.button, 'event.button+1', 'i8') }}}; // DOM buttons are 0-2, SDL 1-3
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.state, 'down ? 1 : 0', 'i8') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.x, 'Browser.mouseX', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.y, 'Browser.mouseY', 'i32') }}};
} else {
- {{{ makeSetValue('ptr', 'SDL.structs.MouseMotionEvent.type', 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
- {{{ makeSetValue('ptr', 'SDL.structs.MouseMotionEvent.state', 'SDL.buttonState', 'i8') }}};
- {{{ makeSetValue('ptr', 'SDL.structs.MouseMotionEvent.x', 'Browser.mouseX', 'i32') }}};
- {{{ makeSetValue('ptr', 'SDL.structs.MouseMotionEvent.y', 'Browser.mouseY', 'i32') }}};
- {{{ makeSetValue('ptr', 'SDL.structs.MouseMotionEvent.xrel', 'Browser.mouseMovementX', 'i32') }}};
- {{{ makeSetValue('ptr', 'SDL.structs.MouseMotionEvent.yrel', 'Browser.mouseMovementY', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.state, 'SDL.buttonState', 'i8') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.x, 'Browser.mouseX', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.y, 'Browser.mouseY', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.xrel, 'Browser.mouseMovementX', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.yrel, 'Browser.mouseMovementY', 'i32') }}};
}
break;
}
case 'unload': {
- {{{ makeSetValue('ptr', 'SDL.structs.KeyboardEvent.type', 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
break;
}
case 'resize': {
- {{{ makeSetValue('ptr', 'SDL.structs.KeyboardEvent.type', 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
- {{{ makeSetValue('ptr', 'SDL.structs.ResizeEvent.w', 'event.w', 'i32') }}};
- {{{ makeSetValue('ptr', 'SDL.structs.ResizeEvent.h', 'event.h', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_ResizeEvent.w, 'event.w', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_ResizeEvent.h, 'event.h', 'i32') }}};
break;
}
default: throw 'Unhandled SDL event: ' + event.type;
@@ -704,7 +627,7 @@ var LibrarySDL = {
// since the browser engine handles that for us. Therefore, in JS we just
// maintain a list of channels and return IDs for them to the SDL consumer.
allocateChannels: function(num) { // called from Mix_AllocateChannels and init
- if (SDL.numChannels && SDL.numChannels >= num) return;
+ if (SDL.numChannels && SDL.numChannels >= num && num != 0) return;
SDL.numChannels = num;
SDL.channels = [];
for (var i = 0; i < num; i++) {
@@ -740,10 +663,10 @@ var LibrarySDL = {
SDL_Linked_Version: function() {
if (SDL.version === null) {
- SDL.version = _malloc(SDL.structs.version.__size__);
- {{{ makeSetValue('SDL.version + SDL.structs.version.major', '0', '1', 'i8') }}}
- {{{ makeSetValue('SDL.version + SDL.structs.version.minor', '0', '3', 'i8') }}}
- {{{ makeSetValue('SDL.version + SDL.structs.version.patch', '0', '0', 'i8') }}}
+ SDL.version = _malloc({{{ C_STRUCTS.SDL_version.__size__ }}});
+ {{{ makeSetValue('SDL.version + ' + C_STRUCTS.SDL_version.major, '0', '1', 'i8') }}}
+ {{{ makeSetValue('SDL.version + ' + C_STRUCTS.SDL_version.minor, '0', '3', 'i8') }}}
+ {{{ makeSetValue('SDL.version + ' + C_STRUCTS.SDL_version.patch, '0', '0', 'i8') }}}
}
return SDL.version;
},
@@ -887,7 +810,7 @@ var LibrarySDL = {
// SDL_Surface has the following fields: Uint32 flags, SDL_PixelFormat *format; int w, h; Uint16 pitch; void *pixels; ...
// So we have fields all of the same size, and 5 of them before us.
// TODO: Use macros like in library.js
- {{{ makeSetValue('surf', '5*Runtime.QUANTUM_SIZE', 'surfData.buffer', 'void*') }}};
+ {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.pixels, 'surfData.buffer', 'void*') }}};
if (surf == SDL.screen && Module.screenIsReadOnly && surfData.image) return 0;
@@ -1131,7 +1054,10 @@ var LibrarySDL = {
} else {
dr = { x: 0, y: 0, w: -1, h: -1 };
}
+ var oldAlpha = dstData.ctx.globalAlpha;
+ dstData.ctx.globalAlpha = srcData.alpha/255;
dstData.ctx.drawImage(srcData.canvas, sr.x, sr.y, sr.w, sr.h, dr.x, dr.y, sr.w, sr.h);
+ dstData.ctx.globalAlpha = oldAlpha;
if (dst != SDL.screen) {
// XXX As in IMG_Load, for compatibility we write out |pixels|
console.log('WARNING: copying canvas data to memory for compatibility');
@@ -1454,60 +1380,240 @@ var LibrarySDL = {
// SDL_Audio
- // TODO fix SDL_OpenAudio, and add some tests for it. It's currently broken.
SDL_OpenAudio: function(desired, obtained) {
- SDL.allocateChannels(32);
-
- SDL.audio = {
- freq: {{{ makeGetValue('desired', 'SDL.structs.AudioSpec.freq', 'i32', 0, 1) }}},
- format: {{{ makeGetValue('desired', 'SDL.structs.AudioSpec.format', 'i16', 0, 1) }}},
- channels: {{{ makeGetValue('desired', 'SDL.structs.AudioSpec.channels', 'i8', 0, 1) }}},
- samples: {{{ makeGetValue('desired', 'SDL.structs.AudioSpec.samples', 'i16', 0, 1) }}},
- callback: {{{ makeGetValue('desired', 'SDL.structs.AudioSpec.callback', 'void*', 0, 1) }}},
- userdata: {{{ makeGetValue('desired', 'SDL.structs.AudioSpec.userdata', 'void*', 0, 1) }}},
- paused: true,
- timer: null
- };
-
- if (obtained) {
- {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.freq', 'SDL.audio.freq', 'i32') }}}; // no good way for us to know if the browser can really handle this
- {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.format', 33040, 'i16') }}}; // float, signed, 16-bit
- {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.channels', 'SDL.audio.channels', 'i8') }}};
- {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.silence', makeGetValue('desired', 'SDL.structs.AudioSpec.silence', 'i8', 0, 1), 'i8') }}}; // unclear if browsers can provide this
- {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.samples', 'SDL.audio.samples', 'i16') }}};
- {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.callback', 'SDL.audio.callback', '*') }}};
- {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.userdata', 'SDL.audio.userdata', '*') }}};
- }
-
- var totalSamples = SDL.audio.samples*SDL.audio.channels;
- SDL.audio.bufferSize = totalSamples*2; // hardcoded 16-bit audio
- SDL.audio.buffer = _malloc(SDL.audio.bufferSize);
- SDL.audio.caller = function() {
- Runtime.dynCall('viii', SDL.audio.callback, [SDL.audio.userdata, SDL.audio.buffer, SDL.audio.bufferSize]);
- SDL.audio.pushAudio(SDL.audio.buffer, SDL.audio.bufferSize);
- };
- // Mozilla Audio API. TODO: Other audio APIs
try {
- SDL.audio.mozOutput = new Audio();
- SDL.audio.mozOutput['mozSetup'](SDL.audio.channels, SDL.audio.freq); // use string attributes on mozOutput for closure compiler
- SDL.audio.mozBuffer = new Float32Array(totalSamples);
- SDL.audio.pushAudio = function(ptr, size) {
- var mozBuffer = SDL.audio.mozBuffer;
- for (var i = 0; i < totalSamples; i++) {
- mozBuffer[i] = ({{{ makeGetValue('ptr', 'i*2', 'i16', 0, 0) }}}) / 0x8000; // hardcoded 16-bit audio, signed (TODO: reSign if not ta2?)
+ SDL.audio = {
+ freq: {{{ makeGetValue('desired', C_STRUCTS.SDL_AudioSpec.freq, 'i32', 0, 1) }}},
+ format: {{{ makeGetValue('desired', C_STRUCTS.SDL_AudioSpec.format, 'i16', 0, 1) }}},
+ channels: {{{ makeGetValue('desired', C_STRUCTS.SDL_AudioSpec.channels, 'i8', 0, 1) }}},
+ samples: {{{ makeGetValue('desired', C_STRUCTS.SDL_AudioSpec.samples, 'i16', 0, 1) }}}, // Samples in the CB buffer per single sound channel.
+ callback: {{{ makeGetValue('desired', C_STRUCTS.SDL_AudioSpec.callback, 'void*', 0, 1) }}},
+ userdata: {{{ makeGetValue('desired', C_STRUCTS.SDL_AudioSpec.userdata, 'void*', 0, 1) }}},
+ paused: true,
+ timer: null
+ };
+ // The .silence field tells the constant sample value that corresponds to the safe un-skewed silence value for the wave data.
+ if (SDL.audio.format == 0x0008 /*AUDIO_U8*/) {
+ SDL.audio.silence = 128; // Audio ranges in [0, 255], so silence is half-way in between.
+ } else if (SDL.audio.format == 0x8010 /*AUDIO_S16LSB*/) {
+ SDL.audio.silence = 0; // Signed data in range [-32768, 32767], silence is 0.
+ } else {
+ throw 'Invalid SDL audio format ' + SDL.audio.format + '!';
+ }
+ // Round the desired audio frequency up to the next 'common' frequency value.
+ // Web Audio API spec states 'An implementation must support sample-rates in at least the range 22050 to 96000.'
+ if (SDL.audio.freq <= 0) {
+ throw 'Unsupported sound frequency ' + SDL.audio.freq + '!';
+ } else if (SDL.audio.freq <= 22050) {
+ SDL.audio.freq = 22050; // Take it safe and clamp everything lower than 22kHz to that.
+ } else if (SDL.audio.freq <= 32000) {
+ SDL.audio.freq = 32000;
+ } else if (SDL.audio.freq <= 44100) {
+ SDL.audio.freq = 44100;
+ } else if (SDL.audio.freq <= 48000) {
+ SDL.audio.freq = 48000;
+ } else if (SDL.audio.freq <= 96000) {
+ SDL.audio.freq = 96000;
+ } else {
+ throw 'Unsupported sound frequency ' + SDL.audio.freq + '!';
+ }
+ if (SDL.audio.channels == 0) {
+ SDL.audio.channels = 1; // In SDL both 0 and 1 mean mono.
+ } else if (SDL.audio.channels < 0 || SDL.audio.channels > 32) {
+ throw 'Unsupported number of audio channels for SDL audio: ' + SDL.audio.channels + '!';
+ } else if (SDL.audio.channels != 1 && SDL.audio.channels != 2) { // Unsure what SDL audio spec supports. Web Audio spec supports up to 32 channels.
+ console.log('Warning: Using untested number of audio channels ' + SDL.audio.channels);
+ }
+ if (SDL.audio.samples < 1024 || SDL.audio.samples > 524288 /* arbitrary cap */) {
+ throw 'Unsupported audio callback buffer size ' + SDL.audio.samples + '!';
+ } else if ((SDL.audio.samples & (SDL.audio.samples-1)) != 0) {
+ throw 'Audio callback buffer size ' + SDL.audio.samples + ' must be a power-of-two!';
+ }
+
+ var totalSamples = SDL.audio.samples*SDL.audio.channels;
+ SDL.audio.bytesPerSample = (SDL.audio.format == 0x0008 /*AUDIO_U8*/ || SDL.audio.format == 0x8008 /*AUDIO_S8*/) ? 1 : 2;
+ SDL.audio.bufferSize = totalSamples*SDL.audio.bytesPerSample;
+ SDL.audio.buffer = _malloc(SDL.audio.bufferSize);
+
+ // Create a callback function that will be routinely called to ask more audio data from the user application.
+ SDL.audio.caller = function() {
+ if (!SDL.audio) {
+ return;
}
- SDL.audio.mozOutput['mozWriteAudio'](mozBuffer);
+ Runtime.dynCall('viii', SDL.audio.callback, [SDL.audio.userdata, SDL.audio.buffer, SDL.audio.bufferSize]);
+ SDL.audio.pushAudio(SDL.audio.buffer, SDL.audio.bufferSize);
+ };
+
+ SDL.audio.audioOutput = new Audio();
+ // As a workaround use Mozilla Audio Data API on Firefox until it ships with Web Audio and sound quality issues are fixed.
+ if (typeof(SDL.audio.audioOutput['mozSetup'])==='function') {
+ SDL.audio.audioOutput['mozSetup'](SDL.audio.channels, SDL.audio.freq); // use string attributes on mozOutput for closure compiler
+ SDL.audio.mozBuffer = new Float32Array(totalSamples);
+ SDL.audio.nextPlayTime = 0;
+ SDL.audio.pushAudio = function(ptr, size) {
+ var mozBuffer = SDL.audio.mozBuffer;
+ // The input audio data for SDL audio is either 8-bit or 16-bit interleaved across channels, output for Mozilla Audio Data API
+ // needs to be Float32 interleaved, so perform a sample conversion.
+ if (SDL.audio.format == 0x8010 /*AUDIO_S16LSB*/) {
+ for (var i = 0; i < totalSamples; i++) {
+ mozBuffer[i] = ({{{ makeGetValue('ptr', 'i*2', 'i16', 0, 0) }}}) / 0x8000;
+ }
+ } else if (SDL.audio.format == 0x0008 /*AUDIO_U8*/) {
+ for (var i = 0; i < totalSamples; i++) {
+ var v = ({{{ makeGetValue('ptr', 'i', 'i8', 0, 0) }}});
+ mozBuffer[i] = ((v >= 0) ? v-128 : v+128) /128;
+ }
+ }
+ // Submit the audio data to audio device.
+ SDL.audio.audioOutput['mozWriteAudio'](mozBuffer);
+
+ // Compute when the next audio callback should be called.
+ var curtime = Date.now() / 1000.0 - SDL.audio.startTime;
+ if (curtime > SDL.audio.nextPlayTime && SDL.audio.nextPlayTime != 0) {
+ console.log('warning: Audio callback had starved sending audio by ' + (curtime - SDL.audio.nextPlayTime) + ' seconds.');
+ }
+ var playtime = Math.max(curtime, SDL.audio.nextPlayTime);
+ var buffer_duration = SDL.audio.samples / SDL.audio.freq;
+ SDL.audio.nextPlayTime = playtime + buffer_duration;
+ // Schedule the next audio callback call.
+ SDL.audio.timer = Browser.safeSetTimeout(SDL.audio.caller, 1000.0 * (playtime-curtime));
+ }
+ } else {
+ // Initialize Web Audio API if we haven't done so yet. Note: Only initialize Web Audio context ever once on the web page,
+ // since initializing multiple times fails on Chrome saying 'audio resources have been exhausted'.
+ if (!SDL.audioContext) {
+ if (typeof(AudioContext) === 'function') {
+ SDL.audioContext = new AudioContext();
+ } else if (typeof(webkitAudioContext) === 'function') {
+ SDL.audioContext = new webkitAudioContext();
+ } else {
+ throw 'Web Audio API is not available!';
+ }
+ }
+ SDL.audio.soundSource = new Array(); // Use an array of sound sources as a ring buffer to queue blocks of synthesized audio to Web Audio API.
+ SDL.audio.nextSoundSource = 0; // Index of the next sound buffer in the ring buffer queue to play.
+ SDL.audio.nextPlayTime = 0; // Time in seconds when the next audio block is due to start.
+
+ // The pushAudio function with a new audio buffer whenever there is new audio data to schedule to be played back on the device.
+ SDL.audio.pushAudio=function(ptr,sizeBytes) {
+ try {
+ --SDL.audio.numAudioTimersPending;
+
+ var sizeSamples = sizeBytes / SDL.audio.bytesPerSample; // How many samples fit in the callback buffer?
+ var sizeSamplesPerChannel = sizeSamples / SDL.audio.channels; // How many samples per a single channel fit in the cb buffer?
+ if (sizeSamplesPerChannel != SDL.audio.samples) {
+ throw 'Received mismatching audio buffer size!';
+ }
+ // Allocate new sound buffer to be played.
+ var source = SDL.audioContext['createBufferSource']();
+ if (SDL.audio.soundSource[SDL.audio.nextSoundSource]) {
+ SDL.audio.soundSource[SDL.audio.nextSoundSource]['disconnect'](); // Explicitly disconnect old source, since we know it shouldn't be running anymore.
+ }
+ SDL.audio.soundSource[SDL.audio.nextSoundSource] = source;
+ var soundBuffer = SDL.audioContext['createBuffer'](SDL.audio.channels,sizeSamplesPerChannel,SDL.audio.freq);
+ SDL.audio.soundSource[SDL.audio.nextSoundSource]['connect'](SDL.audioContext['destination']);
+
+ // The input audio data is interleaved across the channels, i.e. [L, R, L, R, L, R, ...] and is either 8-bit or 16-bit as
+ // supported by the SDL API. The output audio wave data for Web Audio API must be in planar buffers of [-1,1]-normalized Float32 data,
+ // so perform a buffer conversion for the data.
+ var numChannels = SDL.audio.channels;
+ for(var i = 0; i < numChannels; ++i) {
+ var channelData = soundBuffer['getChannelData'](i);
+ if (channelData.length != sizeSamplesPerChannel) {
+ throw 'Web Audio output buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + sizeSamplesPerChannel + ' samples!';
+ }
+ if (SDL.audio.format == 0x8010 /*AUDIO_S16LSB*/) {
+ for(var j = 0; j < sizeSamplesPerChannel; ++j) {
+ channelData[j] = ({{{ makeGetValue('ptr', '(j*numChannels + i)*2', 'i16', 0, 0) }}}) / 0x8000;
+ }
+ } else if (SDL.audio.format == 0x0008 /*AUDIO_U8*/) {
+ for(var j = 0; j < sizeSamplesPerChannel; ++j) {
+ var v = ({{{ makeGetValue('ptr', 'j*numChannels + i', 'i8', 0, 0) }}});
+ channelData[j] = ((v >= 0) ? v-128 : v+128) /128;
+ }
+ }
+ }
+ // Workaround https://bugzilla.mozilla.org/show_bug.cgi?id=883675 by setting the buffer only after filling. The order is important here!
+ source['buffer'] = soundBuffer;
+
+ // Schedule the generated sample buffer to be played out at the correct time right after the previously scheduled
+ // sample buffer has finished.
+ var curtime = SDL.audioContext['currentTime'];
+// if (curtime > SDL.audio.nextPlayTime && SDL.audio.nextPlayTime != 0) {
+// console.log('warning: Audio callback had starved sending audio by ' + (curtime - SDL.audio.nextPlayTime) + ' seconds.');
+// }
+ var playtime = Math.max(curtime, SDL.audio.nextPlayTime);
+ SDL.audio.soundSource[SDL.audio.nextSoundSource]['start'](playtime);
+ var buffer_duration = sizeSamplesPerChannel / SDL.audio.freq;
+ SDL.audio.nextPlayTime = playtime + buffer_duration;
+ SDL.audio.nextSoundSource = (SDL.audio.nextSoundSource + 1) % 4;
+ var secsUntilNextCall = playtime-curtime;
+
+ // Queue the next audio frame push to be performed when the previously queued buffer has finished playing.
+ if (SDL.audio.numAudioTimersPending == 0) {
+ var preemptBufferFeedMSecs = buffer_duration/2.0;
+ SDL.audio.timer = Browser.safeSetTimeout(SDL.audio.caller, Math.max(0.0, 1000.0*secsUntilNextCall-preemptBufferFeedMSecs));
+ ++SDL.audio.numAudioTimersPending;
+ }
+
+ // If we are risking starving, immediately queue an extra second buffer.
+ if (secsUntilNextCall <= buffer_duration && SDL.audio.numAudioTimersPending <= 1) {
+ ++SDL.audio.numAudioTimersPending;
+ Browser.safeSetTimeout(SDL.audio.caller, 1.0);
+ }
+ } catch(e) {
+ console.log('Web Audio API error playing back audio: ' + e.toString());
+ }
+ }
+ }
+
+ if (obtained) {
+ // Report back the initialized audio parameters.
+ {{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.freq, 'SDL.audio.freq', 'i32') }}};
+ {{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.format, 'SDL.audio.format', 'i16') }}};
+ {{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.channels, 'SDL.audio.channels', 'i8') }}};
+ {{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.silence, 'SDL.audio.silence', 'i8') }}};
+ {{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.samples, 'SDL.audio.samples', 'i16') }}};
+ {{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.callback, 'SDL.audio.callback', '*') }}};
+ {{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.userdata, 'SDL.audio.userdata', '*') }}};
}
+ SDL.allocateChannels(32);
+
} catch(e) {
+ console.log('Initializing SDL audio threw an exception: "' + e.toString() + '"! Continuing without audio.');
SDL.audio = null;
+ SDL.allocateChannels(0);
+ if (obtained) {
+ {{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.freq, 0, 'i32') }}};
+ {{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.format, 0, 'i16') }}};
+ {{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.channels, 0, 'i8') }}};
+ {{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.silence, 0, 'i8') }}};
+ {{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.samples, 0, 'i16') }}};
+ {{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.callback, 0, '*') }}};
+ {{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.userdata, 0, '*') }}};
+ }
+ }
+ if (!SDL.audio) {
+ return -1;
}
- if (!SDL.audio) return -1;
return 0;
},
SDL_PauseAudio: function(pauseOn) {
- if (SDL.audio.paused !== pauseOn) {
- SDL.audio.timer = pauseOn ? SDL.audio.timer && clearInterval(SDL.audio.timer) : Browser.safeSetInterval(SDL.audio.caller, 1/35);
+ if (!SDL.audio) {
+ return;
+ }
+ if (pauseOn) {
+ if (SDL.audio.timer !== undefined) {
+ clearTimeout(SDL.audio.timer);
+ SDL.audio.numAudioTimersPending = 0;
+ SDL.audio.timer = undefined;
+ }
+ } else if (!SDL.audio.timer) {
+ // Start the audio playback timer callback loop.
+ SDL.audio.numAudioTimersPending = 1;
+ SDL.audio.timer = Browser.safeSetTimeout(SDL.audio.caller, 1);
+ SDL.audio.startTime = Date.now() / 1000.0; // Only used for Mozilla Audio Data API. Not needed for Web Audio API.
}
SDL.audio.paused = pauseOn;
},
@@ -1515,9 +1621,18 @@ var LibrarySDL = {
SDL_CloseAudio__deps: ['SDL_PauseAudio', 'free'],
SDL_CloseAudio: function() {
if (SDL.audio) {
+ try{
+ for(var i = 0; i < SDL.audio.soundSource.length; ++i) {
+ if (!(typeof(SDL.audio.soundSource[i]==='undefined'))) {
+ SDL.audio.soundSource[i].stop(0);
+ }
+ }
+ } catch(e) {}
+ SDL.audio.soundSource = null;
_SDL_PauseAudio(1);
_free(SDL.audio.buffer);
SDL.audio = null;
+ SDL.allocateChannels(0);
}
},