mirror of
https://github.com/kichikuou/xsystem35-sdl2.git
synced 2026-10-14 09:59:10 +03:00
Split ts files
This commit is contained in:
@@ -51,7 +51,6 @@
|
||||
<script>
|
||||
var Module = {};
|
||||
</script>
|
||||
<script src="loader.js"></script>
|
||||
<script src="shell.js"></script>
|
||||
<script src="xsystem35.js" async></script>
|
||||
</body>
|
||||
|
||||
@@ -0,0 +1,258 @@
|
||||
/// <reference path="util.ts" />
|
||||
|
||||
namespace xsystem35 {
|
||||
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;
|
||||
export class AudioManager {
|
||||
private context: AudioContext;
|
||||
private masterGain: GainNode;
|
||||
private slots: PCMSound[];
|
||||
private buffers: AudioBuffer[];
|
||||
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.removeUserGestureRestriction();
|
||||
}
|
||||
this.masterGain = this.context.createGain();
|
||||
this.masterGain.connect(this.context.destination);
|
||||
this.slots = [];
|
||||
this.buffers = [];
|
||||
}
|
||||
|
||||
private removeUserGestureRestriction() {
|
||||
var hanlder = () => {
|
||||
var src = this.context.createBufferSource();
|
||||
src.buffer = this.context.createBuffer(1, 1, 22050);
|
||||
src.connect(this.context.destination);
|
||||
src.start();
|
||||
console.log('AudioContext unlocked');
|
||||
window.removeEventListener('touchend', hanlder);
|
||||
};
|
||||
window.addEventListener('touchend', hanlder);
|
||||
}
|
||||
|
||||
private load(no: number): Promise<AudioBuffer> {
|
||||
var buf = this.getWave(no);
|
||||
if (!buf)
|
||||
return Promise.reject('Failed to open wave ' + no);
|
||||
|
||||
var decoded: Promise<AudioBuffer>;
|
||||
if (this.isSafari) {
|
||||
decoded = new Promise((resolve, reject) => {
|
||||
this.context.decodeAudioData(buf, resolve, reject);
|
||||
});
|
||||
} else {
|
||||
decoded = this.context.decodeAudioData(buf);
|
||||
}
|
||||
return decoded.then((audioBuf) => {
|
||||
this.buffers[no] = audioBuf;
|
||||
return audioBuf;
|
||||
});
|
||||
}
|
||||
|
||||
private getWave(no: number): ArrayBuffer {
|
||||
var dfile = _ald_getdata(2 /* DRIFILE_WAVE */, no - 1);
|
||||
if (!dfile)
|
||||
return null;
|
||||
var ptr = Module.getValue(dfile + 8, '*');
|
||||
var size = Module.getValue(dfile, 'i32');
|
||||
var buf = Module.HEAPU8.buffer.slice(ptr, ptr + size);
|
||||
_ald_freedata(dfile);
|
||||
return buf;
|
||||
}
|
||||
|
||||
pcm_load(slot: number, no: number) {
|
||||
EmterpreterAsync.handle((resume) => {
|
||||
this.pcm_stop(slot);
|
||||
if (this.buffers[no]) {
|
||||
this.slots[slot] = new PCMSoundSimple(this.masterGain, this.buffers[no]);
|
||||
return resume();
|
||||
}
|
||||
this.load(no).then((audioBuf) => {
|
||||
this.slots[slot] = new PCMSoundSimple(this.masterGain, audioBuf);
|
||||
resume();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pcm_load_mixlr(slot: number, noL: number, noR: number) {
|
||||
EmterpreterAsync.handle((resume) => {
|
||||
this.pcm_stop(slot);
|
||||
if (this.buffers[noL] && this.buffers[noR]) {
|
||||
this.slots[slot] = new PCMSoundMixLR(this.masterGain, this.buffers[noL], this.buffers[noR]);
|
||||
return resume();
|
||||
}
|
||||
var ps = [];
|
||||
if (!this.buffers[noL]) ps.push(this.load(noL));
|
||||
if (!this.buffers[noR]) ps.push(this.load(noR));
|
||||
Promise.all(ps).then(() => {
|
||||
this.slots[slot] = new PCMSoundMixLR(this.masterGain, this.buffers[noL], this.buffers[noR]);
|
||||
resume();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/// <reference path="util.ts" />
|
||||
/// <reference path="volume.ts" />
|
||||
|
||||
namespace xsystem35 {
|
||||
export class CDPlayer {
|
||||
private audio = <HTMLAudioElement>$('audio');
|
||||
private blobs: Blob[];
|
||||
private currentTrack: number;
|
||||
waiting: boolean;
|
||||
|
||||
constructor(private imageLoader: ImageLoader, private volumeControl: VolumeControl) {
|
||||
this.blobs = [];
|
||||
this.volumeControl.addEventListener(this.onVolumeChanged.bind(this));
|
||||
this.audio.volume = this.volumeControl.volume();
|
||||
this.waiting = false;
|
||||
this.removeUserGestureRestriction()
|
||||
}
|
||||
|
||||
play(track: number, loop: number) {
|
||||
this.currentTrack = track;
|
||||
this.waiting = true;
|
||||
if (this.blobs[track]) {
|
||||
this.startPlayback(this.blobs[track], loop);
|
||||
return;
|
||||
}
|
||||
this.imageLoader.getCDDA(track, (blob) => {
|
||||
this.blobs[track] = blob;
|
||||
this.startPlayback(blob, loop);
|
||||
});
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.audio.pause();
|
||||
this.currentTrack = null;
|
||||
}
|
||||
|
||||
getPosition(): number {
|
||||
if (!this.currentTrack)
|
||||
return 0;
|
||||
var time = Math.round(this.audio.currentTime * 75);
|
||||
return this.currentTrack | time << 8;
|
||||
}
|
||||
|
||||
private startPlayback(blob: Blob, loop: number) {
|
||||
this.audio.setAttribute('src', URL.createObjectURL(blob));
|
||||
this.audio.loop = (loop != 0);
|
||||
this.audio.load();
|
||||
this.audio.play();
|
||||
this.waiting = false;
|
||||
}
|
||||
|
||||
private onVolumeChanged(evt: CustomEvent) {
|
||||
this.audio.volume = evt.detail;
|
||||
}
|
||||
|
||||
private removeUserGestureRestriction() {
|
||||
var hanlder = () => {
|
||||
this.audio.load();
|
||||
console.log('CDDA unlocked');
|
||||
window.removeEventListener('touchend', hanlder);
|
||||
};
|
||||
window.addEventListener('touchend', hanlder);
|
||||
}
|
||||
}
|
||||
}
|
||||
+123
-119
@@ -1,132 +1,136 @@
|
||||
class ImageLoader {
|
||||
private worker: Worker;
|
||||
private cddaCallback: (wab:Blob)=>void;
|
||||
/// <reference path="util.ts" />
|
||||
|
||||
constructor(private shell:System35Shell) {
|
||||
this.initWorker();
|
||||
$('#fileselect').addEventListener('change', this.handleFileSelect.bind(this), false);
|
||||
document.body.ondragover = this.handleDragOver.bind(this);
|
||||
document.body.ondrop = this.handleDrop.bind(this);
|
||||
}
|
||||
namespace xsystem35 {
|
||||
export class ImageLoader {
|
||||
private worker: Worker;
|
||||
private cddaCallback: (wab: Blob) => void;
|
||||
|
||||
getCDDA(track:number, callback:(wav:Blob)=>void) {
|
||||
this.send({command:'getTrack', track:track});
|
||||
this.cddaCallback = callback;
|
||||
}
|
||||
|
||||
private initWorker() {
|
||||
this.worker = new Worker('imagereader-worker.js');
|
||||
this.worker.addEventListener('message', this.onMessage.bind(this));
|
||||
this.worker.addEventListener('error', this.onWorkerError.bind(this));
|
||||
}
|
||||
|
||||
private setReadyState(imgName:string, cueName:string) {
|
||||
if (imgName) {
|
||||
$('#imgReady').classList.remove('notready');
|
||||
$('#imgReady').textContent = imgName;
|
||||
constructor(private runMain: ()=>void) {
|
||||
this.initWorker();
|
||||
$('#fileselect').addEventListener('change', this.handleFileSelect.bind(this), false);
|
||||
document.body.ondragover = this.handleDragOver.bind(this);
|
||||
document.body.ondrop = this.handleDrop.bind(this);
|
||||
}
|
||||
if (cueName) {
|
||||
$('#cueReady').classList.remove('notready');
|
||||
$('#cueReady').textContent = cueName;
|
||||
|
||||
getCDDA(track: number, callback: (wav: Blob) => void) {
|
||||
this.send({ command: 'getTrack', track: track });
|
||||
this.cddaCallback = callback;
|
||||
}
|
||||
|
||||
private initWorker() {
|
||||
this.worker = new Worker('imagereader-worker.js');
|
||||
this.worker.addEventListener('message', this.onMessage.bind(this));
|
||||
this.worker.addEventListener('error', this.onWorkerError.bind(this));
|
||||
}
|
||||
|
||||
private setReadyState(imgName: string, cueName: string) {
|
||||
if (imgName) {
|
||||
$('#imgReady').classList.remove('notready');
|
||||
$('#imgReady').textContent = imgName;
|
||||
}
|
||||
if (cueName) {
|
||||
$('#cueReady').classList.remove('notready');
|
||||
$('#cueReady').textContent = cueName;
|
||||
}
|
||||
}
|
||||
|
||||
private populateFiles(files: any, volumeLabel: string) {
|
||||
var grGenerator = new GameResourceGenerator();
|
||||
for (var name in files) {
|
||||
var data: ArrayBuffer = files[name];
|
||||
// Store contents in the emscripten heap, so that it can be mmap-ed without copying
|
||||
var ptr = Module.getMemory(data.byteLength);
|
||||
Module.HEAPU8.set(new Uint8Array(data), ptr);
|
||||
FS.writeFile(name, Module.HEAPU8.subarray(ptr, ptr + data.byteLength), { encoding: 'binary', canOwn: true });
|
||||
grGenerator.addFile(name);
|
||||
}
|
||||
FS.writeFile('xsystem35.gr', grGenerator.generate());
|
||||
FS.writeFile('.xsys35rc', xsystem35.xsys35rc);
|
||||
|
||||
this.runMain();
|
||||
}
|
||||
|
||||
private handleFileSelect(evt: Event) {
|
||||
var input = <HTMLInputElement>evt.target;
|
||||
var files = input.files;
|
||||
for (var i = 0; i < files.length; i++)
|
||||
this.setFile(files[i]);
|
||||
input.value = '';
|
||||
}
|
||||
|
||||
private handleDragOver(evt: DragEvent) {
|
||||
evt.stopPropagation();
|
||||
evt.preventDefault();
|
||||
evt.dataTransfer.dropEffect = 'copy';
|
||||
}
|
||||
|
||||
private handleDrop(evt: DragEvent) {
|
||||
evt.stopPropagation();
|
||||
evt.preventDefault();
|
||||
var files = evt.dataTransfer.files;
|
||||
for (var i = 0; i < files.length; i++)
|
||||
this.setFile(files[i]);
|
||||
}
|
||||
|
||||
private setFile(file: File) {
|
||||
this.send({ command: 'setFile', file: file });
|
||||
}
|
||||
|
||||
private startInstall() {
|
||||
this.send({ command: 'extractFiles' });
|
||||
}
|
||||
|
||||
private send(msg: any) {
|
||||
this.worker.postMessage(msg);
|
||||
}
|
||||
|
||||
private onMessage(evt: MessageEvent) {
|
||||
switch (evt.data.command) {
|
||||
case 'readyState':
|
||||
this.setReadyState(evt.data.img, evt.data.cue);
|
||||
if (evt.data.img && evt.data.cue)
|
||||
this.startInstall();
|
||||
break;
|
||||
case 'extractFiles':
|
||||
this.populateFiles(evt.data.files, evt.data.volumeLabel);
|
||||
break;
|
||||
case 'complete':
|
||||
this.cddaCallback(evt.data.wav);
|
||||
this.cddaCallback = null;
|
||||
break;
|
||||
case 'error':
|
||||
console.log(evt.data.message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private onWorkerError(evt: Event) {
|
||||
console.log('worker error', evt);
|
||||
}
|
||||
}
|
||||
|
||||
private populateFiles(files:any, volumeLabel:string) {
|
||||
var grGenerator = new GameResourceGenerator();
|
||||
for (var name in files) {
|
||||
var data: ArrayBuffer = files[name];
|
||||
// Store contents in the emscripten heap, so that it can be mmap-ed without copying
|
||||
var ptr = Module.getMemory(data.byteLength);
|
||||
Module.HEAPU8.set(new Uint8Array(data), ptr);
|
||||
FS.writeFile(name, Module.HEAPU8.subarray(ptr, ptr + data.byteLength), { encoding: 'binary', canOwn: true });
|
||||
grGenerator.addFile(name);
|
||||
class GameResourceGenerator {
|
||||
static resourceType: { [ch: string]: string } = { s: 'Scenario', g: 'Graphics', w: 'Wave', d: 'Data', r: 'Resource', m: 'Midi' };
|
||||
private basename: string;
|
||||
private lines: string[] = [];
|
||||
|
||||
addFile(name: string) {
|
||||
var type = name.charAt(name.length - 6).toLowerCase();
|
||||
var id = name.charAt(name.length - 5);
|
||||
this.basename = name.slice(0, -6);
|
||||
this.lines.push(GameResourceGenerator.resourceType[type] + id.toUpperCase() + ' ' + name);
|
||||
}
|
||||
FS.writeFile('xsystem35.gr', grGenerator.generate());
|
||||
FS.writeFile('.xsys35rc', xsystem35.xsys35rc);
|
||||
|
||||
this.shell.run();
|
||||
}
|
||||
|
||||
private handleFileSelect(evt:Event) {
|
||||
var input = <HTMLInputElement>evt.target;
|
||||
var files = input.files;
|
||||
for (var i = 0; i < files.length; i++)
|
||||
this.setFile(files[i]);
|
||||
input.value = '';
|
||||
}
|
||||
|
||||
private handleDragOver(evt:DragEvent) {
|
||||
evt.stopPropagation();
|
||||
evt.preventDefault();
|
||||
evt.dataTransfer.dropEffect = 'copy';
|
||||
}
|
||||
|
||||
private handleDrop(evt:DragEvent) {
|
||||
evt.stopPropagation();
|
||||
evt.preventDefault();
|
||||
var files = evt.dataTransfer.files;
|
||||
for (var i = 0; i < files.length; i++)
|
||||
this.setFile(files[i]);
|
||||
}
|
||||
|
||||
private setFile(file:File) {
|
||||
this.send({command:'setFile', file:file});
|
||||
}
|
||||
|
||||
private startInstall() {
|
||||
this.send({command:'extractFiles'});
|
||||
}
|
||||
|
||||
private send(msg:any) {
|
||||
this.worker.postMessage(msg);
|
||||
}
|
||||
|
||||
private onMessage(evt: MessageEvent) {
|
||||
switch (evt.data.command) {
|
||||
case 'readyState':
|
||||
this.setReadyState(evt.data.img, evt.data.cue);
|
||||
if (evt.data.img && evt.data.cue)
|
||||
this.startInstall();
|
||||
break;
|
||||
case 'extractFiles':
|
||||
this.populateFiles(evt.data.files, evt.data.volumeLabel);
|
||||
break;
|
||||
case 'complete':
|
||||
this.cddaCallback(evt.data.wav);
|
||||
this.cddaCallback = null;
|
||||
break;
|
||||
case 'error':
|
||||
console.log(evt.data.message);
|
||||
break;
|
||||
generate(): string {
|
||||
for (var i = 0; i < 26; i++) {
|
||||
var id = String.fromCharCode(65 + i);
|
||||
this.lines.push('Save' + id + ' save/' + this.basename + 's' + id.toLowerCase() + '.asd');
|
||||
}
|
||||
return this.lines.join('\n') + '\n';
|
||||
}
|
||||
}
|
||||
|
||||
private onWorkerError(evt: Event) {
|
||||
console.log('worker error', evt);
|
||||
}
|
||||
}
|
||||
|
||||
class GameResourceGenerator {
|
||||
static resourceType: {[ch:string]:string} = {s:'Scenario', g:'Graphics', w:'Wave', d:'Data', r:'Resource', m:'Midi'};
|
||||
private basename:string;
|
||||
private lines:string[] = [];
|
||||
|
||||
addFile(name:string) {
|
||||
var type = name.charAt(name.length - 6).toLowerCase();
|
||||
var id = name.charAt(name.length - 5);
|
||||
this.basename = name.slice(0, -6);
|
||||
this.lines.push(GameResourceGenerator.resourceType[type] + id.toUpperCase() + ' ' + name);
|
||||
}
|
||||
|
||||
generate(): string {
|
||||
for (var i = 0; i < 26; i++) {
|
||||
var id = String.fromCharCode(65 + i);
|
||||
this.lines.push('Save' + id + ' save/' + this.basename + 's' + id.toLowerCase() + '.asd');
|
||||
isEmpty(): boolean {
|
||||
return this.lines.length == 0;
|
||||
}
|
||||
return this.lines.join('\n') + '\n';
|
||||
}
|
||||
|
||||
isEmpty(): boolean {
|
||||
return this.lines.length == 0;
|
||||
}
|
||||
}
|
||||
|
||||
+97
-544
@@ -1,567 +1,120 @@
|
||||
var $: (selector:string)=>HTMLElement = document.querySelector.bind(document);
|
||||
|
||||
// xsystem35 exported functions
|
||||
declare function _musfade_setvolval_all(vol: number): void;
|
||||
declare function _ags_setAntialiasedStringMode(on: number): void;
|
||||
declare function _sdl_rightButton(down: number): void;
|
||||
declare function _ald_getdata(type: number, no: number): number;
|
||||
declare function _ald_freedata(data: number): void;
|
||||
|
||||
declare namespace Module {
|
||||
var noInitialRun: boolean;
|
||||
|
||||
// Undocumented methods / attributes
|
||||
var canvas: HTMLElement;
|
||||
function setStatus(status: string): void;
|
||||
function monitorRunDependencies(left: number): void;
|
||||
function callMain(): void;
|
||||
function getMemory(size: number): number;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
/// <reference path="util.ts" />
|
||||
/// <reference path="loader.ts" />
|
||||
/// <reference path="zoom.ts" />
|
||||
/// <reference path="volume.ts" />
|
||||
/// <reference path="cdda.ts" />
|
||||
/// <reference path="audio.ts" />
|
||||
|
||||
namespace xsystem35 {
|
||||
export const Font = { url: 'fonts/MTLc3m.ttf', fname: 'MTLc3m.ttf'};
|
||||
const Font = { url: 'fonts/MTLc3m.ttf', fname: 'MTLc3m.ttf'};
|
||||
export const xsys35rc = [
|
||||
'font_device: ttf',
|
||||
'ttfont_mincho: ' + Font.fname,
|
||||
'ttfont_gothic: ' + Font.fname, ''
|
||||
].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 {
|
||||
private imageLoader: ImageLoader;
|
||||
status: HTMLElement = document.getElementById('status');
|
||||
private zoom:ZoomManager;
|
||||
private volumeControl: VolumeControl;
|
||||
private antialiasCheckbox: HTMLInputElement;
|
||||
class System35Shell {
|
||||
private imageLoader: ImageLoader;
|
||||
status: HTMLElement = document.getElementById('status');
|
||||
private zoom: ZoomManager;
|
||||
private volumeControl: VolumeControl;
|
||||
private antialiasCheckbox: HTMLInputElement;
|
||||
|
||||
constructor() {
|
||||
this.imageLoader = new ImageLoader(this);
|
||||
this.setStatus('Downloading...');
|
||||
window.onerror = () => {
|
||||
this.setStatus('Exception thrown, see JavaScript console');
|
||||
this.setStatus = (text: string) => {
|
||||
if (text) Module.printErr('[post-exception status] ' + text);
|
||||
constructor() {
|
||||
this.imageLoader = new ImageLoader(this.run.bind(this));
|
||||
this.setStatus('Downloading...');
|
||||
window.onerror = () => {
|
||||
this.setStatus('Exception thrown, see JavaScript console');
|
||||
this.setStatus = (text: string) => {
|
||||
if (text) Module.printErr('[post-exception status] ' + text);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
// Initialize the Module object
|
||||
Module.TOTAL_MEMORY = 96*1024*1024;
|
||||
Module.print = Module.printErr = console.log.bind(console);
|
||||
Module.canvas = document.getElementById('canvas');
|
||||
Module.noInitialRun = true;
|
||||
Module.setStatus = this.setStatus.bind(this);
|
||||
Module.preRun = [
|
||||
function loadFont() {
|
||||
FS.createPreloadedFile('/', xsystem35.Font.fname, xsystem35.Font.url, true, false);
|
||||
},
|
||||
function prepareSaveDir() {
|
||||
FS.mkdir('/save');
|
||||
FS.mount(IDBFS, {}, '/save');
|
||||
Module.addRunDependency('syncfs');
|
||||
FS.syncfs(true, (err) => { Module.removeRunDependency('syncfs') });
|
||||
}
|
||||
];
|
||||
this.volumeControl = new VolumeControl();
|
||||
this.volumeControl.addEventListener(this.updateVolume.bind(this));
|
||||
xsystem35.cdPlayer = new CDPlayer(this.imageLoader, this.volumeControl);
|
||||
this.zoom = new ZoomManager();
|
||||
this.antialiasCheckbox = <HTMLInputElement>$('#antialias');
|
||||
this.antialiasCheckbox.addEventListener('change', this.antialiasChanged.bind(this));
|
||||
this.antialiasCheckbox.checked = localStorage.getItem('antialias') != 'false';
|
||||
xsystem35.audio = new AudioManager();
|
||||
}
|
||||
// Initialize the Module object
|
||||
Module.TOTAL_MEMORY = 96 * 1024 * 1024;
|
||||
Module.print = Module.printErr = console.log.bind(console);
|
||||
Module.canvas = document.getElementById('canvas');
|
||||
Module.noInitialRun = true;
|
||||
Module.setStatus = this.setStatus.bind(this);
|
||||
Module.preRun = [
|
||||
function loadFont() {
|
||||
FS.createPreloadedFile('/', Font.fname, Font.url, true, false);
|
||||
},
|
||||
function prepareSaveDir() {
|
||||
FS.mkdir('/save');
|
||||
FS.mount(IDBFS, {}, '/save');
|
||||
Module.addRunDependency('syncfs');
|
||||
FS.syncfs(true, (err) => { Module.removeRunDependency('syncfs') });
|
||||
}
|
||||
];
|
||||
this.volumeControl = new VolumeControl();
|
||||
this.volumeControl.addEventListener(this.updateVolume.bind(this));
|
||||
xsystem35.cdPlayer = new CDPlayer(this.imageLoader, this.volumeControl);
|
||||
this.zoom = new ZoomManager();
|
||||
this.antialiasCheckbox = <HTMLInputElement>$('#antialias');
|
||||
this.antialiasCheckbox.addEventListener('change', this.antialiasChanged.bind(this));
|
||||
this.antialiasCheckbox.checked = localStorage.getItem('antialias') != 'false';
|
||||
xsystem35.audio = new AudioManager();
|
||||
}
|
||||
|
||||
run() {
|
||||
$('#loader').hidden = true;
|
||||
$('#xsystem35').hidden = false;
|
||||
setTimeout(() => {
|
||||
Module.callMain();
|
||||
this.updateVolume();
|
||||
this.antialiasChanged();
|
||||
}, 0);
|
||||
this.addRightClickEmulation();
|
||||
}
|
||||
run() {
|
||||
$('#loader').hidden = true;
|
||||
$('#xsystem35').hidden = false;
|
||||
setTimeout(() => {
|
||||
Module.callMain();
|
||||
this.updateVolume();
|
||||
this.antialiasChanged();
|
||||
}, 0);
|
||||
this.addRightClickEmulation();
|
||||
}
|
||||
|
||||
setStatus(text: string) {
|
||||
console.log(text);
|
||||
this.status.innerHTML = text;
|
||||
}
|
||||
setStatus(text: string) {
|
||||
console.log(text);
|
||||
this.status.innerHTML = text;
|
||||
}
|
||||
|
||||
windowSizeChanged() {
|
||||
this.zoom.handleZoom();
|
||||
}
|
||||
windowSizeChanged() {
|
||||
this.zoom.handleZoom();
|
||||
}
|
||||
|
||||
private fsyncTimer: number;
|
||||
syncfs(fname: string) {
|
||||
window.clearTimeout(this.fsyncTimer);
|
||||
this.fsyncTimer = window.setTimeout(() => {
|
||||
FS.syncfs(false, (err) => {
|
||||
if (err)
|
||||
console.log("FS.syncfs error: ", err);
|
||||
private fsyncTimer: number;
|
||||
syncfs(fname: string) {
|
||||
window.clearTimeout(this.fsyncTimer);
|
||||
this.fsyncTimer = window.setTimeout(() => {
|
||||
FS.syncfs(false, (err) => {
|
||||
if (err)
|
||||
console.log("FS.syncfs error: ", err);
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
|
||||
private updateVolume() {
|
||||
_musfade_setvolval_all(Math.round(this.volumeControl.volume() * 100));
|
||||
}
|
||||
|
||||
private antialiasChanged() {
|
||||
localStorage.setItem('antialias', String(this.antialiasCheckbox.checked));
|
||||
_ags_setAntialiasedStringMode(this.antialiasCheckbox.checked ? 1 : 0);
|
||||
}
|
||||
|
||||
private addRightClickEmulation() {
|
||||
var emulatingRightClick = false;
|
||||
document.body.addEventListener('touchstart', (e) => {
|
||||
if (e.target !== document.body)
|
||||
return;
|
||||
_sdl_rightButton(1);
|
||||
emulatingRightClick = true;
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
|
||||
private updateVolume() {
|
||||
_musfade_setvolval_all(Math.round(this.volumeControl.volume() * 100));
|
||||
}
|
||||
|
||||
private antialiasChanged() {
|
||||
localStorage.setItem('antialias', String(this.antialiasCheckbox.checked));
|
||||
_ags_setAntialiasedStringMode(this.antialiasCheckbox.checked ? 1 : 0);
|
||||
}
|
||||
|
||||
private addRightClickEmulation() {
|
||||
var emulatingRightClick = false;
|
||||
document.body.addEventListener('touchstart', (e) => {
|
||||
if (e.target !== document.body)
|
||||
return;
|
||||
_sdl_rightButton(1);
|
||||
emulatingRightClick = true;
|
||||
});
|
||||
document.body.addEventListener('touchend', (e) => {
|
||||
if (!emulatingRightClick)
|
||||
return;
|
||||
_sdl_rightButton(0);
|
||||
emulatingRightClick = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class CDPlayer {
|
||||
private audio = <HTMLAudioElement>$('audio');
|
||||
private blobs: Blob[];
|
||||
private currentTrack: number;
|
||||
waiting: boolean;
|
||||
|
||||
constructor(private imageLoader:ImageLoader, private volumeControl: VolumeControl) {
|
||||
this.blobs = [];
|
||||
this.volumeControl.addEventListener(this.onVolumeChanged.bind(this));
|
||||
this.audio.volume = this.volumeControl.volume();
|
||||
this.waiting = false;
|
||||
this.removeUserGestureRestriction()
|
||||
}
|
||||
|
||||
play(track:number, loop:number) {
|
||||
this.currentTrack = track;
|
||||
this.waiting = true;
|
||||
if (this.blobs[track]) {
|
||||
this.startPlayback(this.blobs[track], loop);
|
||||
return;
|
||||
}
|
||||
this.imageLoader.getCDDA(track, (blob) => {
|
||||
this.blobs[track] = blob;
|
||||
this.startPlayback(blob, loop);
|
||||
});
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.audio.pause();
|
||||
this.currentTrack = null;
|
||||
}
|
||||
|
||||
getPosition(): number {
|
||||
if (!this.currentTrack)
|
||||
return 0;
|
||||
var time = Math.round(this.audio.currentTime * 75);
|
||||
return this.currentTrack | time << 8;
|
||||
}
|
||||
|
||||
private startPlayback(blob:Blob, loop:number) {
|
||||
this.audio.setAttribute('src', URL.createObjectURL(blob));
|
||||
this.audio.loop = (loop != 0);
|
||||
this.audio.load();
|
||||
this.audio.play();
|
||||
this.waiting = false;
|
||||
}
|
||||
|
||||
private onVolumeChanged(evt: CustomEvent) {
|
||||
this.audio.volume = evt.detail;
|
||||
}
|
||||
|
||||
private removeUserGestureRestriction() {
|
||||
var hanlder = () => {
|
||||
this.audio.load();
|
||||
console.log('CDDA unlocked');
|
||||
window.removeEventListener('touchend', hanlder);
|
||||
};
|
||||
window.addEventListener('touchend', hanlder);
|
||||
}
|
||||
}
|
||||
|
||||
class ZoomManager {
|
||||
private canvas: HTMLCanvasElement;
|
||||
private zoomSelect: HTMLInputElement;
|
||||
private smoothingCheckbox: HTMLInputElement;
|
||||
|
||||
constructor() {
|
||||
this.canvas = <HTMLCanvasElement>$('#canvas');
|
||||
this.zoomSelect = <HTMLInputElement>$('#zoom');
|
||||
this.zoomSelect.addEventListener('change', this.handleZoom.bind(this));
|
||||
this.zoomSelect.value = localStorage.getItem('zoom') || '1';
|
||||
this.smoothingCheckbox = <HTMLInputElement>$('#smoothing');
|
||||
this.smoothingCheckbox.addEventListener('change', this.handleSmoothing.bind(this));
|
||||
if (localStorage.getItem('smoothing') == 'false') {
|
||||
this.smoothingCheckbox.checked = false;
|
||||
this.handleSmoothing();
|
||||
}
|
||||
}
|
||||
|
||||
handleZoom() {
|
||||
var value = this.zoomSelect.value;
|
||||
localStorage.setItem('zoom', value);
|
||||
var contentsStyle = $('.contents').style;
|
||||
if (value == 'fit') {
|
||||
contentsStyle.maxWidth = 'none';
|
||||
contentsStyle.width = this.canvas.style.width = '100%';
|
||||
} else {
|
||||
var ratio = Number(value);
|
||||
contentsStyle.maxWidth = 'none';
|
||||
contentsStyle.width = this.canvas.style.width = this.canvas.width * ratio + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
private handleSmoothing() {
|
||||
localStorage.setItem('smoothing', String(this.smoothingCheckbox.checked));
|
||||
if (this.smoothingCheckbox.checked)
|
||||
this.canvas.classList.remove('pixelated');
|
||||
else
|
||||
this.canvas.classList.add('pixelated');
|
||||
}
|
||||
}
|
||||
|
||||
class VolumeControl {
|
||||
private vol: number; // 0.0 - 1.0
|
||||
private muted: boolean;
|
||||
private elem: HTMLElement;
|
||||
private icon: HTMLElement;
|
||||
private slider: HTMLInputElement;
|
||||
|
||||
constructor() {
|
||||
this.vol = Number(localStorage.getItem('volume') || 1);
|
||||
this.muted = false;
|
||||
|
||||
this.elem = document.getElementById('volume-control');
|
||||
this.icon = document.getElementById('volume-control-icon');
|
||||
this.slider = <HTMLInputElement>document.getElementById('volume-control-slider');
|
||||
this.slider.value = String(Math.round(this.vol * 100));
|
||||
|
||||
this.icon.addEventListener('click', this.onIconClicked.bind(this));
|
||||
this.slider.addEventListener('input', this.onSliderValueChanged.bind(this));
|
||||
this.slider.addEventListener('change', this.onSliderValueSettled.bind(this));
|
||||
}
|
||||
|
||||
volume(): number {
|
||||
return this.muted ? 0 : parseInt(this.slider.value) / 100;
|
||||
}
|
||||
|
||||
addEventListener(handler: (evt: CustomEvent) => any) {
|
||||
this.elem.addEventListener('volumechange', handler);
|
||||
}
|
||||
|
||||
private onIconClicked(e: Event) {
|
||||
this.muted = !this.muted;
|
||||
if (this.muted) {
|
||||
this.icon.classList.remove('fa-volume-up');
|
||||
this.icon.classList.add('fa-volume-off');
|
||||
this.slider.value = '0';
|
||||
} else {
|
||||
this.icon.classList.remove('fa-volume-off');
|
||||
this.icon.classList.add('fa-volume-up');
|
||||
this.slider.value = String(Math.round(this.vol * 100));
|
||||
}
|
||||
this.dispatchEvent();
|
||||
}
|
||||
|
||||
private onSliderValueChanged(e: Event) {
|
||||
this.vol = parseInt(this.slider.value) / 100;
|
||||
if (this.vol > 0 && this.muted) {
|
||||
this.muted = false;
|
||||
this.icon.classList.remove('fa-volume-off');
|
||||
this.icon.classList.add('fa-volume-up');
|
||||
}
|
||||
this.dispatchEvent();
|
||||
}
|
||||
|
||||
private onSliderValueSettled(e: Event) {
|
||||
localStorage.setItem('volume', this.vol + '');
|
||||
}
|
||||
|
||||
private dispatchEvent() {
|
||||
var event = new CustomEvent('volumechange', {detail: this.volume()});
|
||||
this.elem.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
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 buffers: AudioBuffer[];
|
||||
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.removeUserGestureRestriction();
|
||||
}
|
||||
this.masterGain = this.context.createGain();
|
||||
this.masterGain.connect(this.context.destination);
|
||||
this.slots = [];
|
||||
this.buffers = [];
|
||||
}
|
||||
|
||||
private removeUserGestureRestriction() {
|
||||
var hanlder = () => {
|
||||
var src = this.context.createBufferSource();
|
||||
src.buffer = this.context.createBuffer(1, 1, 22050);
|
||||
src.connect(this.context.destination);
|
||||
src.start();
|
||||
console.log('AudioContext unlocked');
|
||||
window.removeEventListener('touchend', hanlder);
|
||||
};
|
||||
window.addEventListener('touchend', hanlder);
|
||||
}
|
||||
|
||||
private load(no: number): Promise<AudioBuffer> {
|
||||
var buf = this.getWave(no);
|
||||
if (!buf)
|
||||
return Promise.reject('Failed to open wave ' + no);
|
||||
|
||||
var decoded: Promise<AudioBuffer>;
|
||||
if (this.isSafari) {
|
||||
decoded = new Promise((resolve, reject) => {
|
||||
this.context.decodeAudioData(buf, resolve, reject);
|
||||
document.body.addEventListener('touchend', (e) => {
|
||||
if (!emulatingRightClick)
|
||||
return;
|
||||
_sdl_rightButton(0);
|
||||
emulatingRightClick = false;
|
||||
});
|
||||
} else {
|
||||
decoded = this.context.decodeAudioData(buf);
|
||||
}
|
||||
return decoded.then((audioBuf) => {
|
||||
this.buffers[no] = audioBuf;
|
||||
return audioBuf;
|
||||
});
|
||||
}
|
||||
|
||||
private getWave(no: number): ArrayBuffer {
|
||||
var dfile = _ald_getdata(2 /* DRIFILE_WAVE */, no-1);
|
||||
if (!dfile)
|
||||
return null;
|
||||
var ptr = Module.getValue(dfile + 8, '*');
|
||||
var size = Module.getValue(dfile, 'i32');
|
||||
var buf = Module.HEAPU8.buffer.slice(ptr, ptr + size);
|
||||
_ald_freedata(dfile);
|
||||
return buf;
|
||||
}
|
||||
|
||||
pcm_load(slot: number, no: number) {
|
||||
EmterpreterAsync.handle((resume) => {
|
||||
this.pcm_stop(slot);
|
||||
if (this.buffers[no]) {
|
||||
this.slots[slot] = new PCMSoundSimple(this.masterGain, this.buffers[no]);
|
||||
return resume();
|
||||
}
|
||||
this.load(no).then((audioBuf) => {
|
||||
this.slots[slot] = new PCMSoundSimple(this.masterGain, audioBuf);
|
||||
resume();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pcm_load_mixlr(slot: number, noL: number, noR: number) {
|
||||
EmterpreterAsync.handle((resume) => {
|
||||
this.pcm_stop(slot);
|
||||
if (this.buffers[noL] && this.buffers[noR]) {
|
||||
this.slots[slot] = new PCMSoundMixLR(this.masterGain, this.buffers[noL], this.buffers[noR]);
|
||||
return resume();
|
||||
}
|
||||
var ps = [];
|
||||
if (!this.buffers[noL]) ps.push(this.load(noL));
|
||||
if (!this.buffers[noR]) ps.push(this.load(noR));
|
||||
Promise.all(ps).then(() => {
|
||||
this.slots[slot] = new PCMSoundMixLR(this.masterGain, this.buffers[noL], this.buffers[noR]);
|
||||
resume();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
export var shell = new System35Shell();
|
||||
}
|
||||
|
||||
xsystem35.shell = new System35Shell();
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"outDir": "../../docs",
|
||||
"outFile": "../../docs/shell.js",
|
||||
"noImplicitAny": true,
|
||||
"sourceMap": false
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"shell.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
var $: (selector:string)=>HTMLElement = document.querySelector.bind(document);
|
||||
|
||||
// xsystem35 exported functions
|
||||
declare function _musfade_setvolval_all(vol: number): void;
|
||||
declare function _ags_setAntialiasedStringMode(on: number): void;
|
||||
declare function _sdl_rightButton(down: number): void;
|
||||
declare function _ald_getdata(type: number, no: number): number;
|
||||
declare function _ald_freedata(data: number): void;
|
||||
|
||||
declare namespace Module {
|
||||
var noInitialRun: boolean;
|
||||
|
||||
// Undocumented methods / attributes
|
||||
var canvas: HTMLElement;
|
||||
function setStatus(status: string): void;
|
||||
function monitorRunDependencies(left: number): void;
|
||||
function callMain(): void;
|
||||
function getMemory(size: number): number;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/// <reference path="util.ts" />
|
||||
|
||||
namespace xsystem35 {
|
||||
export class VolumeControl {
|
||||
private vol: number; // 0.0 - 1.0
|
||||
private muted: boolean;
|
||||
private elem: HTMLElement;
|
||||
private icon: HTMLElement;
|
||||
private slider: HTMLInputElement;
|
||||
|
||||
constructor() {
|
||||
this.vol = Number(localStorage.getItem('volume') || 1);
|
||||
this.muted = false;
|
||||
|
||||
this.elem = $('#volume-control');
|
||||
this.icon = $('#volume-control-icon');
|
||||
this.slider = <HTMLInputElement>$('#volume-control-slider');
|
||||
this.slider.value = String(Math.round(this.vol * 100));
|
||||
|
||||
this.icon.addEventListener('click', this.onIconClicked.bind(this));
|
||||
this.slider.addEventListener('input', this.onSliderValueChanged.bind(this));
|
||||
this.slider.addEventListener('change', this.onSliderValueSettled.bind(this));
|
||||
}
|
||||
|
||||
volume(): number {
|
||||
return this.muted ? 0 : parseInt(this.slider.value) / 100;
|
||||
}
|
||||
|
||||
addEventListener(handler: (evt: CustomEvent) => any) {
|
||||
this.elem.addEventListener('volumechange', handler);
|
||||
}
|
||||
|
||||
private onIconClicked(e: Event) {
|
||||
this.muted = !this.muted;
|
||||
if (this.muted) {
|
||||
this.icon.classList.remove('fa-volume-up');
|
||||
this.icon.classList.add('fa-volume-off');
|
||||
this.slider.value = '0';
|
||||
} else {
|
||||
this.icon.classList.remove('fa-volume-off');
|
||||
this.icon.classList.add('fa-volume-up');
|
||||
this.slider.value = String(Math.round(this.vol * 100));
|
||||
}
|
||||
this.dispatchEvent();
|
||||
}
|
||||
|
||||
private onSliderValueChanged(e: Event) {
|
||||
this.vol = parseInt(this.slider.value) / 100;
|
||||
if (this.vol > 0 && this.muted) {
|
||||
this.muted = false;
|
||||
this.icon.classList.remove('fa-volume-off');
|
||||
this.icon.classList.add('fa-volume-up');
|
||||
}
|
||||
this.dispatchEvent();
|
||||
}
|
||||
|
||||
private onSliderValueSettled(e: Event) {
|
||||
localStorage.setItem('volume', this.vol + '');
|
||||
}
|
||||
|
||||
private dispatchEvent() {
|
||||
var event = new CustomEvent('volumechange', { detail: this.volume() });
|
||||
this.elem.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/// <reference path="util.ts" />
|
||||
|
||||
namespace xsystem35 {
|
||||
export class ZoomManager {
|
||||
private canvas: HTMLCanvasElement;
|
||||
private zoomSelect: HTMLInputElement;
|
||||
private smoothingCheckbox: HTMLInputElement;
|
||||
|
||||
constructor() {
|
||||
this.canvas = <HTMLCanvasElement>$('#canvas');
|
||||
this.zoomSelect = <HTMLInputElement>$('#zoom');
|
||||
this.zoomSelect.addEventListener('change', this.handleZoom.bind(this));
|
||||
this.zoomSelect.value = localStorage.getItem('zoom') || '1';
|
||||
this.smoothingCheckbox = <HTMLInputElement>$('#smoothing');
|
||||
this.smoothingCheckbox.addEventListener('change', this.handleSmoothing.bind(this));
|
||||
if (localStorage.getItem('smoothing') == 'false') {
|
||||
this.smoothingCheckbox.checked = false;
|
||||
this.handleSmoothing();
|
||||
}
|
||||
}
|
||||
|
||||
handleZoom() {
|
||||
var value = this.zoomSelect.value;
|
||||
localStorage.setItem('zoom', value);
|
||||
var contentsStyle = $('.contents').style;
|
||||
if (value == 'fit') {
|
||||
contentsStyle.maxWidth = 'none';
|
||||
contentsStyle.width = this.canvas.style.width = '100%';
|
||||
} else {
|
||||
var ratio = Number(value);
|
||||
contentsStyle.maxWidth = 'none';
|
||||
contentsStyle.width = this.canvas.style.width = this.canvas.width * ratio + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
private handleSmoothing() {
|
||||
localStorage.setItem('smoothing', String(this.smoothingCheckbox.checked));
|
||||
if (this.smoothingCheckbox.checked)
|
||||
this.canvas.classList.remove('pixelated');
|
||||
else
|
||||
this.canvas.classList.add('pixelated');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user