experimental mp3/zlib support

This commit is contained in:
d07riv
2019-08-05 02:55:46 +03:00
parent 6440d69ad5
commit 0305be8217
7 changed files with 46 additions and 25 deletions

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -7,9 +7,6 @@ import axios from 'axios';
const DiabloSize = 1316452;
const SpawnSize = 1196648;
const SpawnMpqSize = 50274091;
const RetailMpqSize = 517501282;
/* eslint-disable-next-line no-restricted-globals */
const worker = self;
@@ -22,12 +19,19 @@ let is_spawn = false;
const ChunkSize = 1 << 20;
class RemoteFile {
constructor(url, size) {
this.url = url;
this.byteLength = size;
constructor(url) {
const request = new XMLHttpRequest();
request.open('HEAD', url, false);
request.send();
if (request.status < 200 || request.status >= 300) {
worker.postMessage({action: "error", error: `Failed to load remote file`});
}
this.byteLength = parseInt(request.getResponseHeader('Content-Length'));
this.buffer = new Uint8Array(size);
this.chunks = new Uint8Array(((size + ChunkSize - 1) >> 20) | 0);
this.url = url;
this.buffer = new Uint8Array(this.byteLength);
this.chunks = new Uint8Array(((this.byteLength + ChunkSize - 1) >> 20) | 0);
}
subarray(start, end) {
@@ -46,7 +50,7 @@ class RemoteFile {
request.setRequestHeader('Range', `bytes=${missing0 * ChunkSize}-${Math.min(missing1 * ChunkSize + ChunkSize - 1, this.byteLength - 1)}`);
request.responseType = 'arraybuffer';
request.send();
if (request.status < 200 || request.status > 206) {
if (request.status < 200 || request.status >= 300) {
worker.postMessage({action: "error", error: `Failed to load remote file`});
} else {
const header = request.getResponseHeader('Content-Range');
@@ -196,18 +200,18 @@ const DApi_renderOffscreen = {
let audioBatch = null, audioTransfer = null;
let maxSoundId = 0, maxBatchId = 0;
["create_sound", "duplicate_sound"].forEach(func => {
["create_sound_raw", "create_sound", "duplicate_sound"].forEach(func => {
DApi[func] = function(...params) {
if (audioBatch) {
maxBatchId = params[0] + 1;
audioBatch.push({func, params});
if (func === "create_sound") {
if (func !== "duplicate_sound") {
audioTransfer.push(params[1].buffer);
}
} else {
maxSoundId = params[0] + 1;
const transfer = [];
if (func === "create_sound") {
if (func !== "duplicate_sound") {
transfer.push(params[1].buffer);
}
worker.postMessage({action: "audio", func, params}, transfer);
@@ -293,7 +297,7 @@ async function init_game(mpq, spawn, offscreen) {
const name = (spawn ? 'spawn.mpq' : 'diabdat.mpq');
if (!files.has(name)) {
// This should never happen, but we do support remote loading
files.set(name, new RemoteFile(`${process.env.PUBLIC_URL}/${name}`, spawn ? SpawnMpqSize : RetailMpqSize));
files.set(name, new RemoteFile(`${process.env.PUBLIC_URL}/${name}`));
}
}

View File

@@ -36,6 +36,9 @@ function onRender(api, ctx, {bitmap, images, text, clip, belt}) {
function testOffscreen() {
return false;
// This works but I couldn't see any performance difference, and support for 2D canvas in workers is very poor.
// In this mode, instead of sending a batch of areas to draw back to the main thread, the worker does all drawing on its own and sends a complete bitmap object back.
// However, this effectively clears the worker's canvas, so we need to redraw the whole frame every time, which defeats the performance gained from reduced copying.
/*try {
const canvas = document.createElement("canvas");
const offscreen = canvas.transferControlToOffscreen();

View File

@@ -23,7 +23,7 @@ export default function init_sound() {
const sounds = new Map();
return {
create_sound(id, data, length, channels, rate) {
create_sound_raw(id, data, length, channels, rate) {
if (!context) {
return;
}
@@ -31,6 +31,17 @@ export default function init_sound() {
for (let i = 0; i < channels; ++i) {
buffer.copyToChannel(data.subarray(i * length, i * length + length), i);
}
sounds.set(id, {
buffer: Promise.resolve(buffer),
gain: context.createGain(),
panner: new StereoPannerNode(context, {pan: 0}),
});
},
create_sound(id, data) {
if (!context) {
return;
}
const buffer = context.decodeAudioData(data.buffer);
sounds.set(id, {
buffer,
gain: context.createGain(),
@@ -55,16 +66,19 @@ export default function init_sound() {
const src = sounds.get(id);
if (src) {
if (src.source) {
src.source.stop();
src.source.then(source => source.stop());
}
src.gain.gain.value = Math.pow(2.0, volume / 1000.0);
const relVolume = Math.pow(2.0, pan / 1000.0);
src.panner.pan.value = 1.0 - 2.0 / (1.0 + relVolume);
src.source = context.createBufferSource();
src.source.buffer = src.buffer;
src.source.loop = !!loop;
src.source.connect(src.gain).connect(src.panner).connect(context.destination);
src.source.start();
src.source = src.buffer.then(buffer => {
const source = context.createBufferSource();
source.buffer = buffer;
source.loop = !!loop;
source.connect(src.gain).connect(src.panner).connect(context.destination);
source.start();
return source;
});
}
},
set_volume(id, volume) {
@@ -76,14 +90,14 @@ export default function init_sound() {
stop_sound(id) {
const src = sounds.get(id);
if (src && src.source) {
src.source.stop();
src.source.then(source => source.stop());
delete src.source;
}
},
delete_sound(id) {
const src = sounds.get(id);
if (src && src.source) {
src.source.stop();
src.source.then(source => source.stop());
}
sounds.delete(id);
},
@@ -91,7 +105,7 @@ export default function init_sound() {
stop_all() {
for (let [, sound] of sounds) {
if (sound.source) {
sound.source.stop();
sound.source.then(source => source.stop());
}
}
sounds.clear();