mirror of
https://github.com/kichikuou/xsystem35-sdl2.git
synced 2026-09-29 18:28:19 +03:00
Emscripten PCM backend
This commit is contained in:
+1
-1
@@ -25,6 +25,6 @@ src/xsystem35: src src/Makefile
|
||||
ln -sf src/xsystem35 xsystem35.bc
|
||||
|
||||
src/Makefile:
|
||||
emconfigure ./configure --disable-shared --enable-gtk=no --enable-sdl --enable-music-server=no --enable-cdrom=emscripten CFLAGS='-s USE_ZLIB=1' FT2_CONFIG=tools/freetype-config
|
||||
emconfigure ./configure --disable-shared --enable-gtk=no --enable-sdl --enable-cdrom=emscripten --enable-midi=no CFLAGS='-s USE_ZLIB=1' FT2_CONFIG=tools/freetype-config
|
||||
|
||||
.PHONY: src
|
||||
|
||||
@@ -1 +1 @@
|
||||
-s EMTERPRETIFY_WHITELIST='["_ags_eCopyArea", "_ags_fadeIn", "_ags_fadeOut", "_ags_fader", "_ags_whiteIn", "_ags_whiteOut", "_checkMessage", "_check_command", "_commandCD", "_commandCE", "_commandH", "_commandHH", "_commandIK", "_commandMP", "_commandPF", "_commandPW", "_commandVA", "_commandY", "_commandZT20", "_commandZT21", "_commands2F21", "_commands2F7C", "_commands2F7D", "_commands2F7E", "_commands2F7F", "_eCopyArea1", "_eCopyArea11", "_eCopyArea12", "_eCopyArea13", "_eCopyArea14", "_eCopyArea15", "_eCopyArea16", "_eCopyArea17", "_eCopyArea18", "_eCopyArea19", "_eCopyArea2", "_eCopyArea20", "_eCopyArea21", "_eCopyArea22", "_eCopyArea23", "_eCopyArea24", "_eCopyArea25", "_eCopyArea26", "_eCopyArea3", "_eCopyArea33", "_eCopyArea34", "_eCopyArea39", "_eCopyArea4", "_eCopyArea40", "_eCopyArea48", "_eCopyArea49", "_eCopyArea5", "_eCopyArea5sp", "_eCopyArea6", "_eCopyArea7", "_eCopyArea8", "_eCopyArea9", "_main", "_mainloop", "_msg_putMessage", "_nact_main", "_sdl_keywait", "_sdl_sleep", "_sel_select", "_sys_addMsg", "_sys_hit_any_key", "_sys_key_releasewait"]'
|
||||
-s EMTERPRETIFY_WHITELIST='["_ags_eCopyArea", "_ags_fadeIn", "_ags_fadeOut", "_ags_fader", "_ags_whiteIn", "_ags_whiteOut", "_checkMessage", "_check_command", "_commandCD", "_commandCE", "_commandH", "_commandHH", "_commandIK", "_commandMP", "_commandPF", "_commandPW", "_commandSM", "_commandSP", "_commandSQ", "_commandVA", "_commandY", "_commandZT20", "_commandZT21", "_commands2F1E", "_commands2F21", "_commands2F7C", "_commands2F7D", "_commands2F7E", "_commands2F7F", "_eCopyArea1", "_eCopyArea11", "_eCopyArea12", "_eCopyArea13", "_eCopyArea14", "_eCopyArea15", "_eCopyArea16", "_eCopyArea17", "_eCopyArea18", "_eCopyArea19", "_eCopyArea2", "_eCopyArea20", "_eCopyArea21", "_eCopyArea22", "_eCopyArea23", "_eCopyArea24", "_eCopyArea25", "_eCopyArea26", "_eCopyArea3", "_eCopyArea33", "_eCopyArea34", "_eCopyArea39", "_eCopyArea4", "_eCopyArea40", "_eCopyArea48", "_eCopyArea49", "_eCopyArea5", "_eCopyArea5sp", "_eCopyArea6", "_eCopyArea7", "_eCopyArea8", "_eCopyArea9", "_main", "_mainloop", "_msg_putMessage", "_mus_pcm_load", "_mus_pcm_mix", "_mus_pcm_start", "_muspcm_load_mixlr", "_muspcm_load_no", "_nact_main", "_sdl_keywait", "_sdl_sleep", "_sel_select", "_sys_addMsg", "_sys_hit_any_key", "_sys_key_releasewait"]'
|
||||
|
||||
@@ -24,8 +24,6 @@
|
||||
#ifndef __MUSIC_PCM_H__
|
||||
#define __MUSIC_PCM_H__
|
||||
|
||||
#include <SDL_mixer.h>
|
||||
|
||||
extern int muspcm_init();
|
||||
extern int muspcm_exit();
|
||||
extern int muspcm_load_no(int slot, int no);
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* pcm.emscripten.c Emscripten PCM backend
|
||||
*
|
||||
* Copyright (C) 2017 <KichikuouChrome@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <emscripten.h>
|
||||
|
||||
#include "portab.h"
|
||||
#include "system.h"
|
||||
#include "ald_manager.h"
|
||||
#include "dri.h"
|
||||
#include "music_pcm.h"
|
||||
#include "music_private.h"
|
||||
#include "nact.h"
|
||||
|
||||
|
||||
int muspcm_init() {
|
||||
prv.pcm_valid = TRUE;
|
||||
return OK;
|
||||
}
|
||||
|
||||
int muspcm_exit() {
|
||||
return OK;
|
||||
}
|
||||
|
||||
// 番号指定のPCMファイル読み込み
|
||||
int muspcm_load_no(int slot, int no) {
|
||||
dridata *dfile = ald_getdata(DRIFILE_WAVE, no -1);
|
||||
if (dfile == NULL) {
|
||||
WARNING("DRIFILE_WAVE fail to open %d\n", no -1);
|
||||
return NG;
|
||||
}
|
||||
|
||||
EM_ASM_({
|
||||
xsystem35.waitPromise(function(){
|
||||
return xsystem35.audio.pcm_load($0, $1, $2);
|
||||
});
|
||||
}, slot, dfile->data, dfile->size);
|
||||
#ifdef EMTERPRETIFY_ADVISE
|
||||
emscripten_sleep(0);
|
||||
#endif
|
||||
|
||||
ald_freedata(dfile);
|
||||
return OK;
|
||||
}
|
||||
|
||||
int muspcm_load_mixlr(int slot, int noL, int noR) {
|
||||
dridata *dfileL = ald_getdata(DRIFILE_WAVE, noL -1);
|
||||
if (dfileL == NULL) {
|
||||
WARNING("DRIFILE_WAVE fail to open %d\n", noL -1);
|
||||
return NG;
|
||||
}
|
||||
dridata *dfileR = ald_getdata(DRIFILE_WAVE, noR -1);
|
||||
if (dfileR == NULL) {
|
||||
ald_freedata(dfileL);
|
||||
WARNING("DRIFILE_WAVE fail to open %d\n", noR -1);
|
||||
return NG;
|
||||
}
|
||||
|
||||
EM_ASM_({
|
||||
xsystem35.waitPromise(function() {
|
||||
return xsystem35.audio.pcm_load_mixlr($0, $1, $2, $3, $4);
|
||||
});
|
||||
}, slot, dfileL->data, dfileL->size, dfileR->data, dfileR->size);
|
||||
#ifdef EMTERPRETIFY_ADVISE
|
||||
emscripten_sleep(0);
|
||||
#endif
|
||||
|
||||
ald_freedata(dfileL);
|
||||
ald_freedata(dfileR);
|
||||
return OK;
|
||||
}
|
||||
|
||||
// PCMデータを再生
|
||||
int muspcm_start(int slot, int loop) {
|
||||
return EM_ASM_INT({
|
||||
return xsystem35.audio.pcm_start($0, $1);
|
||||
}, slot, loop) ? OK : NG;
|
||||
}
|
||||
|
||||
// PCMデータの再生停止
|
||||
int muspcm_stop(int slot) {
|
||||
return EM_ASM_INT({
|
||||
return xsystem35.audio.pcm_stop($0);
|
||||
}, slot) ? OK : NG;
|
||||
}
|
||||
|
||||
// 指定時間のフェードアウトの後に再生停止
|
||||
int muspcm_fadeout(int slot, int msec) {
|
||||
return EM_ASM_INT({
|
||||
return xsystem35.audio.pcm_fadeout($0, $1);
|
||||
}, slot, msec) ? OK : NG;
|
||||
}
|
||||
|
||||
// PCMデータ再生一時停止
|
||||
int muspcm_pause(int slot) {
|
||||
printf("%s not implemented\n", __func__);
|
||||
return NG;
|
||||
}
|
||||
|
||||
// PCMデータ再生一時停止解除
|
||||
int muspcm_unpause(int slot) {
|
||||
printf("%s not implemented\n", __func__);
|
||||
return NG;
|
||||
}
|
||||
|
||||
// 現在の再生位置を返す
|
||||
int muspcm_getpos(int slot) {
|
||||
return EM_ASM_INT({
|
||||
return xsystem35.audio.pcm_getpos($0);
|
||||
}, slot) ? OK : NG;
|
||||
}
|
||||
|
||||
// PCMオブジェクトに対してボリュームをセット
|
||||
int muspcm_setvol(int dev, int slot, int lv) {
|
||||
return EM_ASM_INT({
|
||||
return xsystem35.audio.pcm_setvol($0, $1);
|
||||
}, slot, lv) ? OK : NG;
|
||||
}
|
||||
|
||||
// PCMデータの長さを取得
|
||||
int muspcm_getwavelen(int slot) {
|
||||
int len = EM_ASM_INT({
|
||||
return xsystem35.audio.pcm_getwavelen($0);
|
||||
}, slot);
|
||||
|
||||
return len > 65535 ? 65535 : len;
|
||||
}
|
||||
|
||||
// 指定のスロットが現在演奏中かどうかを取得
|
||||
boolean muspcm_isplaying(int slot) {
|
||||
return EM_ASM_INT({
|
||||
return xsystem35.audio.pcm_isplaying($0);
|
||||
}, slot) ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
int musfade_setvolval_all(int val) {
|
||||
EM_ASM_(xsystem35.audio.setVolume($0), val);
|
||||
}
|
||||
@@ -20,6 +20,10 @@ declare namespace FS {
|
||||
function writeFile(path: string, data: ArrayBufferView | string, opts?: {encoding?: string; flags?: string; canOwn?: boolean}): void;
|
||||
}
|
||||
|
||||
declare namespace EmterpreterAsync {
|
||||
function handle(asyncOp: (resume:()=>void) => void): void;
|
||||
}
|
||||
|
||||
namespace xsystem35 {
|
||||
export const Font = { url: 'fonts/MTLc3m.ttf', fname: 'MTLc3m.ttf'};
|
||||
export const xsys35rc = [
|
||||
@@ -29,6 +33,12 @@ namespace xsystem35 {
|
||||
].join('\n');
|
||||
export var cdPlayer: CDPlayer;
|
||||
export var shell: System35Shell;
|
||||
export var audio: AudioManager;
|
||||
export function waitPromise(p: ()=>Promise<any>) {
|
||||
EmterpreterAsync.handle(function(resume) {
|
||||
p().then(() => {resume()}, (reason) => { console.log(reason); resume() });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class System35Shell {
|
||||
@@ -72,6 +82,7 @@ class System35Shell {
|
||||
this.antialiasCheckbox = <HTMLInputElement>$('#antialias');
|
||||
this.antialiasCheckbox.addEventListener('change', this.antialiasChanged.bind(this));
|
||||
this.antialiasCheckbox.checked = localStorage.getItem('antialias') != 'false';
|
||||
xsystem35.audio = new AudioManager();
|
||||
}
|
||||
|
||||
run() {
|
||||
@@ -286,4 +297,211 @@ class VolumeControl {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class PCMSound {
|
||||
protected context: AudioContext;
|
||||
protected gain: GainNode;
|
||||
protected duration_: number;
|
||||
protected startTime: number;
|
||||
|
||||
constructor(protected dst: AudioNode) {
|
||||
this.context = dst.context;
|
||||
this.gain = this.context.createGain();
|
||||
this.gain.connect(dst);
|
||||
}
|
||||
abstract start(loop: number): void;
|
||||
abstract stop(): void;
|
||||
setGain(gain: number) {
|
||||
this.gain.gain.value = gain;
|
||||
}
|
||||
fadeout(msec: number) {
|
||||
this.gain.gain.linearRampToValueAtTime(0, this.context.currentTime + msec / 1000);
|
||||
}
|
||||
getPosition(): number {
|
||||
if (!this.startTime)
|
||||
return 0;
|
||||
return this.context.currentTime - this.startTime;
|
||||
}
|
||||
isPlaying(): boolean {
|
||||
return !!this.startTime;
|
||||
}
|
||||
get duration(): number {
|
||||
return this.duration_;
|
||||
}
|
||||
}
|
||||
|
||||
class PCMSoundSimple extends PCMSound {
|
||||
private node: AudioBufferSourceNode;
|
||||
|
||||
constructor(dst: AudioNode, buf: AudioBuffer) {
|
||||
super(dst);
|
||||
this.node = this.context.createBufferSource();
|
||||
this.node.buffer = buf;
|
||||
this.node.connect(this.gain);
|
||||
this.node.onended = this.onended.bind(this);
|
||||
this.duration_ = buf.duration;
|
||||
}
|
||||
|
||||
start(loop: number) {
|
||||
if (loop == 0)
|
||||
this.node.loop = true;
|
||||
else if (loop != 1)
|
||||
console.warn('Unsupported PCM loop count ' + loop);
|
||||
this.node.start();
|
||||
this.startTime = this.context.currentTime;
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this.startTime) {
|
||||
this.node.stop();
|
||||
this.startTime = null;
|
||||
}
|
||||
}
|
||||
|
||||
private onended() {
|
||||
this.startTime = null;
|
||||
}
|
||||
}
|
||||
|
||||
class PCMSoundMixLR extends PCMSound {
|
||||
private lsrc: AudioBufferSourceNode;
|
||||
private rsrc: AudioBufferSourceNode;
|
||||
private endCount = 0;
|
||||
|
||||
constructor(dst: AudioNode, lbuf: AudioBuffer, rbuf: AudioBuffer) {
|
||||
super(dst);
|
||||
this.lsrc = this.context.createBufferSource();
|
||||
this.rsrc = this.context.createBufferSource();
|
||||
this.lsrc.buffer = lbuf;
|
||||
this.rsrc.buffer = rbuf;
|
||||
var merger = this.context.createChannelMerger(2);
|
||||
merger.connect(this.gain);
|
||||
this.lsrc.connect(merger, 0, 0);
|
||||
this.rsrc.connect(merger, 0, 1);
|
||||
this.lsrc.onended = this.rsrc.onended = this.onended.bind(this);
|
||||
this.duration_ = Math.max(lbuf.duration, rbuf.duration);
|
||||
}
|
||||
|
||||
start(loop: number) {
|
||||
if (loop != 1)
|
||||
console.warn('PCMSoundMixLR: loop is not supported ' + loop);
|
||||
this.lsrc.start();
|
||||
this.rsrc.start();
|
||||
this.startTime = this.context.currentTime;
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this.startTime) {
|
||||
this.lsrc.stop();
|
||||
this.rsrc.stop();
|
||||
this.startTime = null;
|
||||
}
|
||||
}
|
||||
|
||||
private onended() {
|
||||
this.endCount++;
|
||||
if (this.endCount == 2)
|
||||
this.startTime = null;
|
||||
}
|
||||
}
|
||||
|
||||
declare var webkitAudioContext:any;
|
||||
class AudioManager {
|
||||
private context: AudioContext;
|
||||
private masterGain: GainNode;
|
||||
private slots: PCMSound[];
|
||||
private isSafari: boolean;
|
||||
|
||||
constructor() {
|
||||
if (typeof(AudioContext) !== 'undefined') {
|
||||
this.context = new AudioContext();
|
||||
} else if (typeof(webkitAudioContext) !== 'undefined') {
|
||||
this.context = new webkitAudioContext();
|
||||
this.isSafari = true;
|
||||
}
|
||||
this.masterGain = this.context.createGain();
|
||||
this.masterGain.connect(this.context.destination);
|
||||
this.slots = [];
|
||||
}
|
||||
|
||||
private load(ptr: number, size: number): Promise<AudioBuffer> {
|
||||
var buf = Module.HEAPU8.buffer.slice(ptr, ptr + size);
|
||||
if (this.isSafari) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.context.decodeAudioData(buf, resolve, reject);
|
||||
});
|
||||
}
|
||||
return this.context.decodeAudioData(buf);
|
||||
}
|
||||
|
||||
pcm_load(slot: number, ptr: number, size: number): Promise<any> {
|
||||
this.pcm_stop(slot);
|
||||
var start = performance.now();
|
||||
return this.load(ptr, size).then((audioBuf) => {
|
||||
console.log('pcm_load: ' + (performance.now() - start) + ' ms');
|
||||
this.slots[slot] = new PCMSoundSimple(this.masterGain, audioBuf);
|
||||
});
|
||||
}
|
||||
|
||||
pcm_load_mixlr(slot: number, lptr: number, lsize: number, rptr: number, rsize: number): Promise<any> {
|
||||
this.pcm_stop(slot);
|
||||
var start = performance.now();
|
||||
return Promise.all([this.load(lptr, lsize), this.load(rptr, rsize)]).then((bufs) => {
|
||||
console.log('pcm_load_mixlr: ' + (performance.now() - start) + ' ms');
|
||||
this.slots[slot] = new PCMSoundMixLR(this.masterGain, bufs[0], bufs[1]);
|
||||
});
|
||||
}
|
||||
|
||||
pcm_start(slot: number, loop: number): number {
|
||||
if (this.slots[slot]) {
|
||||
this.slots[slot].start(loop);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
pcm_stop(slot: number): number {
|
||||
if (!this.slots[slot])
|
||||
return 0;
|
||||
this.slots[slot].stop();
|
||||
this.slots[slot] = null;
|
||||
return 1;
|
||||
}
|
||||
|
||||
pcm_fadeout(slot: number, msec: number): number {
|
||||
if (!this.slots[slot])
|
||||
return 0;
|
||||
this.slots[slot].fadeout(msec);
|
||||
return 1;
|
||||
}
|
||||
|
||||
pcm_getpos(slot: number): number {
|
||||
if (!this.slots[slot])
|
||||
return 0;
|
||||
return this.slots[slot].getPosition() * 1000;
|
||||
}
|
||||
|
||||
pcm_setvol(slot: number, vol: number): number {
|
||||
if (!this.slots[slot])
|
||||
return 0;
|
||||
this.slots[slot].setGain(vol / 100);
|
||||
return 1;
|
||||
}
|
||||
|
||||
pcm_getwavelen(slot: number): number {
|
||||
if (!this.slots[slot])
|
||||
return 0;
|
||||
return this.slots[slot].duration * 1000;
|
||||
}
|
||||
|
||||
pcm_isplaying(slot: number): number {
|
||||
if (!this.slots[slot])
|
||||
return 0;
|
||||
return this.slots[slot].isPlaying() ? 1 : 0;
|
||||
}
|
||||
|
||||
setVolume(vol: number) {
|
||||
this.masterGain.gain.value = vol / 100;
|
||||
}
|
||||
}
|
||||
|
||||
xsystem35.shell = new System35Shell();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"target": "es6",
|
||||
"outDir": "../../docs",
|
||||
"noImplicitAny": true,
|
||||
"sourceMap": false
|
||||
|
||||
Reference in New Issue
Block a user