Files
Konrad Beckmann 20d7fefaf9 Import mikmod
2021-08-02 02:19:41 +02:00

355 lines
11 KiB
TypeScript

/* MikMod Web Audio library
(c) 2021 Carlos Rafael Gimenes das Neves.
https://github.com/sezero/mikmod
https://github.com/carlosrafaelgn/mikmod/tree/master/libmikmod/webaudio
This library is free software; you can redistribute it and/or modify
it under the terms of the GNU Library 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 Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
interface LibMikModCLib {
HEAP8: Uint8Array;
HEAPF32: Float32Array;
_getVersion(): number;
_init(): number;
_freeModule(): void;
_terminate(): void;
_preLoadModule(length: number): number;
_loadModule(mixfreq: number, reverb: number, hqMixer: number, interpolation: number, noiseReduction: number, wrap: number, loop: number, fadeout: number): number;
_changeGeneralOptions(reverb: number, interpolation: number, noiseReduction: number): void;
_update(): number;
_getErrno(): number;
_getStrerr(code: number): number;
_getSongName(): number;
_getModType(): number;
_getComment(): number;
_getAudioBuffer(): number;
_getAudioBufferMaxLength(): number;
_getAudioBufferUsedLength(): number;
}
class LibMikMod {
private static cLib: LibMikModCLib | null = null;
private static tmpBuffer: Float32Array | null = null;
private static audioBufferPtr = 0;
private static audioBufferUsedLength = 0;
private static lastReverb = 0;
private static lastHqMixer = 1;
private static lastInterpolation = 1;
private static lastNoiseReduction = 1;
private static lastWrap = 0;
private static lastLoop = 0;
private static lastFadeout = 1;
public static loading = false;
public static loaded = false;
public static loadErrorStr: string | null = null;
public static init(wasmBinary: ArrayBuffer): Promise<void> {
return (LibMikMod.loaded ? Promise.resolve() : new Promise((resolve, reject) => {
if (LibMikMod.loading) {
reject(LibMikMod.loadErrorStr = "The library was still loading");
return;
}
if (LibMikMod.loadErrorStr) {
reject(LibMikMod.loadErrorStr);
return;
}
LibMikMod.loading = true;
LibMikModCLib({ wasmBinary }).then((value) => {
LibMikMod.cLib = value;
const r = value._init();
if (!r) {
LibMikMod.loading = false;
LibMikMod.loaded = true;
LibMikMod.loadErrorStr = null;
resolve();
} else {
LibMikMod.loading = false;
reject(LibMikMod.loadErrorStr = LibMikMod.getStrerr(r));
}
}, (reason) => {
LibMikMod.loading = false;
reject(LibMikMod.loadErrorStr = ((reason ? (reason.message || reason.toString()) : null) || "Unknown error while loading the library"));
});
}));
}
public static terminate(): void {
if (LibMikMod.cLib) {
LibMikMod.cLib._terminate();
LibMikMod.cLib = null;
LibMikMod.audioBufferPtr = 0;
LibMikMod.audioBufferUsedLength = 0;
}
}
public static getVersion(): number {
return (LibMikMod.cLib ? LibMikMod.cLib._getVersion() : 0);
}
public static loadModule(destinationSampleRate: number, srcBuffer?: ArrayBuffer | Uint8Array, options?: LibMikModInitialOptions | null): number {
if (!LibMikMod.cLib)
return 3; // MMERR_DYNAMIC_LINKING
if (!srcBuffer)
return 2; // MMERR_OUT_OF_MEMORY
const ptr = LibMikMod.cLib._preLoadModule(srcBuffer.byteLength);
if (!ptr)
return 2; // MMERR_OUT_OF_MEMORY
const dstBuffer = new Uint8Array(LibMikMod.cLib.HEAP8.buffer, ptr, srcBuffer.byteLength);
if ("set" in srcBuffer)
dstBuffer.set(srcBuffer, 0);
else
dstBuffer.set(new Uint8Array(srcBuffer, 0, srcBuffer.byteLength), 0);
LibMikMod.audioBufferPtr = 0;
LibMikMod.audioBufferUsedLength = 0;
if (options) {
if (options.reverb !== undefined && options.reverb >= 0 && options.reverb <= 15)
LibMikMod.lastReverb = options.reverb;
if (options.hqMixer !== undefined)
LibMikMod.lastHqMixer = (options.hqMixer ? 1 : 0);
if (options.interpolation !== undefined)
LibMikMod.lastInterpolation = (options.interpolation ? 1 : 0);
if (options.noiseReduction !== undefined)
LibMikMod.lastNoiseReduction = (options.noiseReduction ? 1 : 0);
if (options.wrap !== undefined)
LibMikMod.lastWrap = (options.wrap ? 1 : 0);
if (options.loop !== undefined)
LibMikMod.lastLoop = (options.loop ? 1 : 0);
if (options.fadeout !== undefined)
LibMikMod.lastFadeout = (options.fadeout ? 1 : 0);
}
const r = LibMikMod.cLib._loadModule(destinationSampleRate, LibMikMod.lastReverb, LibMikMod.lastHqMixer, LibMikMod.lastInterpolation, LibMikMod.lastNoiseReduction, LibMikMod.lastWrap, LibMikMod.lastLoop, LibMikMod.lastFadeout);
if (!r) {
const audioBufferPtr = LibMikMod.cLib._getAudioBuffer();
if (!audioBufferPtr)
return 2; // MMERR_OUT_OF_MEMORY
}
return r;
}
public static changeGeneralOptions(options?: LibMikModGeneralOptions | null): void {
if (options) {
if (options.reverb !== undefined && options.reverb >= 0 && options.reverb <= 15)
LibMikMod.lastReverb = options.reverb;
if (options.interpolation !== undefined)
LibMikMod.lastInterpolation = (options.interpolation ? 1 : 0);
if (options.noiseReduction !== undefined)
LibMikMod.lastNoiseReduction = (options.noiseReduction ? 1 : 0);
if (LibMikMod.cLib)
LibMikMod.cLib._changeGeneralOptions(LibMikMod.lastReverb, LibMikMod.lastInterpolation, LibMikMod.lastNoiseReduction);
}
}
public static stopModule(): void {
if (LibMikMod.cLib) {
LibMikMod.cLib._freeModule();
LibMikMod.audioBufferPtr = 0;
LibMikMod.audioBufferUsedLength = 0;
}
}
private static getString(ptr: number): string | null {
if (LibMikMod.cLib && ptr) {
const heap = LibMikMod.cLib.HEAP8;
let len = 0;
for (let i = ptr; heap[i] && len < 1024; i++)
len++;
if (!len)
return "";
const arr: number[] = new Array(len);
while (len-- > 0)
arr[len] = heap[ptr + len];
return String.fromCharCode.apply(String, arr);
}
return null;
}
public static getSongName(): string | null {
return (LibMikMod.cLib ? LibMikMod.getString(LibMikMod.cLib._getSongName()) : null);
}
public static getModType(): string | null {
return (LibMikMod.cLib ? LibMikMod.getString(LibMikMod.cLib._getModType()) : null);
}
public static getComment(): string | null {
return (LibMikMod.cLib ? LibMikMod.getString(LibMikMod.cLib._getComment()) : null);
}
public static getErrno(): number {
return (LibMikMod.cLib ? LibMikMod.cLib._getErrno() : 0);
}
public static getStrerr(code: number): string | null {
return (LibMikMod.cLib ? LibMikMod.getString(LibMikMod.cLib._getStrerr(code)) : null);
}
public static process(outputs: Float32Array[][]): boolean {
if (!LibMikMod.cLib)
return false;
let audioBufferUsedLength = LibMikMod.audioBufferUsedLength;
if (!audioBufferUsedLength) {
for (let attempts = 0; attempts < 3; attempts++) {
audioBufferUsedLength = LibMikMod.cLib._update();
if (audioBufferUsedLength < 0)
return false;
if (audioBufferUsedLength) {
LibMikMod.audioBufferPtr = LibMikMod.cLib._getAudioBuffer();
LibMikMod.audioBufferUsedLength = audioBufferUsedLength;
break;
}
}
// Output silence if we cannot fill the buffer
if (!audioBufferUsedLength)
return true;
}
let tmpBuffer: Float32Array | null = null;
for (let o = outputs.length - 1; o >= 0; o--) {
const channels = outputs[o];
for (let c = channels.length - 1; c >= 0; c--) {
const channel = channels[c];
if (!tmpBuffer) {
// Convert mono 32-bit float samples into bytes
const maxBytes = channel.length << 2;
if (audioBufferUsedLength >= maxBytes) {
audioBufferUsedLength = maxBytes;
// Convert bytes into mono 32-bit float samples
tmpBuffer = new Float32Array(LibMikMod.cLib.HEAP8.buffer, LibMikMod.audioBufferPtr, audioBufferUsedLength >> 2);
LibMikMod.audioBufferPtr += audioBufferUsedLength;
LibMikMod.audioBufferUsedLength -= audioBufferUsedLength;
} else {
// We had data, but not enough to fill the buffer... This should not
// happen as long as BUFFERSIZE in drv_webaudio.c is a multiple of maxBytes,
// which is currently true, since BUFFERSIZE is 32768, and maxBytes is (128 * 4) = 512
if (!LibMikMod.tmpBuffer || LibMikMod.tmpBuffer.byteLength !== maxBytes)
LibMikMod.tmpBuffer = new Float32Array(channel.length);
// Convert bytes into mono 32-bit float samples
let samplesRead = audioBufferUsedLength >> 2;
tmpBuffer = new Float32Array(LibMikMod.cLib.HEAP8.buffer, LibMikMod.audioBufferPtr, samplesRead);
LibMikMod.audioBufferPtr = 0;
LibMikMod.audioBufferUsedLength = 0;
audioBufferUsedLength = 0;
LibMikMod.tmpBuffer.set(tmpBuffer);
let remainingSamples = channel.length - samplesRead;
while (remainingSamples > 0) {
if (!audioBufferUsedLength) {
for (let attempts = 0; attempts < 3; attempts++) {
audioBufferUsedLength = LibMikMod.cLib._update();
if (audioBufferUsedLength < 0) {
if (LibMikMod.getErrno())
return false;
audioBufferUsedLength = 0;
break;
}
if (audioBufferUsedLength) {
LibMikMod.audioBufferPtr = LibMikMod.cLib._getAudioBuffer();
LibMikMod.audioBufferUsedLength = audioBufferUsedLength;
break;
}
}
// Output silence if we cannot fill the buffer
if (!audioBufferUsedLength) {
LibMikMod.tmpBuffer.fill(0, samplesRead);
break;
}
}
// Convert mono 32-bit float samples into bytes
let remainingBytes = remainingSamples << 2;
if (remainingBytes > audioBufferUsedLength)
remainingBytes = audioBufferUsedLength;
// Convert bytes into mono 32-bit float samples
const samplesToReadNow = remainingBytes >> 2;
tmpBuffer = new Float32Array(LibMikMod.cLib.HEAP8.buffer, LibMikMod.audioBufferPtr, samplesToReadNow);
LibMikMod.audioBufferPtr += remainingBytes;
LibMikMod.audioBufferUsedLength -= remainingBytes;
LibMikMod.tmpBuffer.set(tmpBuffer, samplesRead);
samplesRead += samplesToReadNow;
remainingSamples -= samplesToReadNow;
}
tmpBuffer = LibMikMod.tmpBuffer;
}
}
channel.set(tmpBuffer);
}
}
return true;
}
}