upd: Refactor mod player
Imports parts of the reworked mod player code from Firefish, mainly replacing the canvas based renderer with a DOM based one, and lazily loading libopenmpt only when needed. Notably does not fix the mod player not working on dev mode. The vite dev server doesn't seem to like how libopenmpt loads it's wasm binary. I reverted all the styling changes that weren't necessary for the DOM renderer due to the pending media UI changes[1] from upstream. I'd like to attempt to make the mod player consistent with *that* once it's merged. I also went ahead and module-ified the CSS classes to be more in line with latest Misskey coding practices. [1]: https://github.com/misskey-dev/misskey/pull/12925 Co-authored-by: Essem <smswessem@gmail.com>
This commit is contained in:
parent
6cc81b6a9a
commit
af3065f315
10 changed files with 423 additions and 267 deletions
|
|
@ -1,18 +1,16 @@
|
|||
/* global libopenmpt UTF8ToString writeAsciiToMemory */
|
||||
/* eslint-disable */
|
||||
const ChiptuneAudioContext = window.AudioContext;
|
||||
|
||||
const ChiptuneAudioContext = window.AudioContext || window.webkitAudioContext;
|
||||
|
||||
export function ChiptuneJsConfig (repeatCount: number, context: AudioContext) {
|
||||
export function ChiptuneJsConfig(repeatCount?: number, context?: AudioContext) {
|
||||
this.repeatCount = repeatCount;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
ChiptuneJsConfig.prototype.constructor = ChiptuneJsConfig;
|
||||
|
||||
export function ChiptuneJsPlayer (config: object) {
|
||||
export function ChiptuneJsPlayer(config: object) {
|
||||
this.libopenmpt = null;
|
||||
this.config = config;
|
||||
this.audioContext = config.context || new ChiptuneAudioContext();
|
||||
this.audioContext = config.context ?? new ChiptuneAudioContext();
|
||||
this.context = this.audioContext.createGain();
|
||||
this.currentPlayingNode = null;
|
||||
this.handlers = [];
|
||||
|
|
@ -25,7 +23,7 @@ ChiptuneJsPlayer.prototype.constructor = ChiptuneJsPlayer;
|
|||
ChiptuneJsPlayer.prototype.fireEvent = function (eventName: string, response) {
|
||||
const handlers = this.handlers;
|
||||
if (handlers.length > 0) {
|
||||
for(const handler of handlers) {
|
||||
for (const handler of handlers) {
|
||||
if (handler.eventName === eventName) {
|
||||
handler.handler(response);
|
||||
}
|
||||
|
|
@ -33,10 +31,17 @@ ChiptuneJsPlayer.prototype.fireEvent = function (eventName: string, response) {
|
|||
}
|
||||
};
|
||||
|
||||
ChiptuneJsPlayer.prototype.addHandler = function (eventName: string, handler: Function) {
|
||||
ChiptuneJsPlayer.prototype.addHandler = function (
|
||||
eventName: string,
|
||||
handler: Function,
|
||||
) {
|
||||
this.handlers.push({ eventName, handler });
|
||||
};
|
||||
|
||||
ChiptuneJsPlayer.prototype.clearHandlers = function () {
|
||||
this.handlers = [];
|
||||
};
|
||||
|
||||
ChiptuneJsPlayer.prototype.onEnded = function (handler: Function) {
|
||||
this.addHandler('onEnded', handler);
|
||||
};
|
||||
|
|
@ -46,28 +51,55 @@ ChiptuneJsPlayer.prototype.onError = function (handler: Function) {
|
|||
};
|
||||
|
||||
ChiptuneJsPlayer.prototype.duration = function () {
|
||||
return libopenmpt._openmpt_module_get_duration_seconds(this.currentPlayingNode.modulePtr);
|
||||
return this.libopenmpt._openmpt_module_get_duration_seconds(
|
||||
this.currentPlayingNode.modulePtr,
|
||||
);
|
||||
};
|
||||
|
||||
ChiptuneJsPlayer.prototype.position = function () {
|
||||
return libopenmpt._openmpt_module_get_position_seconds(this.currentPlayingNode.modulePtr);
|
||||
return this.libopenmpt._openmpt_module_get_position_seconds(
|
||||
this.currentPlayingNode.modulePtr,
|
||||
);
|
||||
};
|
||||
|
||||
ChiptuneJsPlayer.prototype.repeat = function (repeatCount: number) {
|
||||
if (this.currentPlayingNode) {
|
||||
this.libopenmpt._openmpt_module_set_repeat_count(
|
||||
this.currentPlayingNode.modulePtr,
|
||||
repeatCount,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
ChiptuneJsPlayer.prototype.seek = function (position: number) {
|
||||
if (this.currentPlayingNode) {
|
||||
libopenmpt._openmpt_module_set_position_seconds(this.currentPlayingNode.modulePtr, position);
|
||||
this.libopenmpt._openmpt_module_set_position_seconds(
|
||||
this.currentPlayingNode.modulePtr,
|
||||
position,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
ChiptuneJsPlayer.prototype.metadata = function () {
|
||||
const data = {};
|
||||
const keys = UTF8ToString(libopenmpt._openmpt_module_get_metadata_keys(this.currentPlayingNode.modulePtr)).split(';');
|
||||
const keys = this.libopenmpt
|
||||
.UTF8ToString(
|
||||
this.libopenmpt._openmpt_module_get_metadata_keys(
|
||||
this.currentPlayingNode.modulePtr,
|
||||
),
|
||||
)
|
||||
.split(';');
|
||||
let keyNameBuffer = 0;
|
||||
for (const key of keys) {
|
||||
keyNameBuffer = libopenmpt._malloc(key.length + 1);
|
||||
writeAsciiToMemory(key, keyNameBuffer);
|
||||
data[key] = UTF8ToString(libopenmpt._openmpt_module_get_metadata(this.currentPlayingNode.modulePtr, keyNameBuffer));
|
||||
libopenmpt._free(keyNameBuffer);
|
||||
keyNameBuffer = this.libopenmpt._malloc(key.length + 1);
|
||||
this.libopenmpt.stringToUTF8(key, keyNameBuffer);
|
||||
data[key] = this.libopenmpt.UTF8ToString(
|
||||
this.libopenmpt._openmpt_module_get_metadata(
|
||||
this.currentPlayingNode.modulePtr,
|
||||
keyNameBuffer,
|
||||
),
|
||||
);
|
||||
this.libopenmpt._free(keyNameBuffer);
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
|
@ -85,10 +117,9 @@ ChiptuneJsPlayer.prototype.unlock = function () {
|
|||
|
||||
ChiptuneJsPlayer.prototype.load = function (input) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if(this.touchLocked) {
|
||||
if (this.touchLocked) {
|
||||
this.unlock();
|
||||
}
|
||||
const player = this;
|
||||
if (input instanceof File) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
|
|
@ -96,30 +127,40 @@ ChiptuneJsPlayer.prototype.load = function (input) {
|
|||
};
|
||||
reader.readAsArrayBuffer(input);
|
||||
} else {
|
||||
window.fetch(input).then((response) => {
|
||||
response.arrayBuffer().then((arrayBuffer) => {
|
||||
resolve(arrayBuffer);
|
||||
}).catch((error) => {
|
||||
window
|
||||
.fetch(input)
|
||||
.then((response) => {
|
||||
response
|
||||
.arrayBuffer()
|
||||
.then((arrayBuffer) => {
|
||||
resolve(arrayBuffer);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
}).catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
ChiptuneJsPlayer.prototype.play = function (buffer: ArrayBuffer) {
|
||||
ChiptuneJsPlayer.prototype.play = async function (buffer: ArrayBuffer) {
|
||||
this.unlock();
|
||||
this.stop();
|
||||
const processNode = this.createLibopenmptNode(buffer, this.buffer);
|
||||
if (processNode === null) {
|
||||
return;
|
||||
}
|
||||
libopenmpt._openmpt_module_set_repeat_count(processNode.modulePtr, this.config.repeatCount || 0);
|
||||
this.currentPlayingNode = processNode;
|
||||
processNode.connect(this.context);
|
||||
this.context.connect(this.audioContext.destination);
|
||||
return this.createLibopenmptNode(buffer, this.buffer).then((processNode) => {
|
||||
if (processNode === null) {
|
||||
return;
|
||||
}
|
||||
this.libopenmpt._openmpt_module_set_repeat_count(
|
||||
processNode.modulePtr,
|
||||
this.config.repeatCount ?? 0,
|
||||
);
|
||||
this.currentPlayingNode = processNode;
|
||||
processNode.connect(this.context);
|
||||
this.context.connect(this.audioContext.destination);
|
||||
});
|
||||
};
|
||||
|
||||
ChiptuneJsPlayer.prototype.stop = function () {
|
||||
|
|
@ -137,58 +178,104 @@ ChiptuneJsPlayer.prototype.togglePause = function () {
|
|||
};
|
||||
|
||||
ChiptuneJsPlayer.prototype.getPattern = function () {
|
||||
if (this.currentPlayingNode && this.currentPlayingNode.modulePtr) {
|
||||
return libopenmpt._openmpt_module_get_current_pattern(this.currentPlayingNode.modulePtr);
|
||||
if (this.currentPlayingNode?.modulePtr) {
|
||||
return this.libopenmpt._openmpt_module_get_current_pattern(
|
||||
this.currentPlayingNode.modulePtr,
|
||||
);
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
ChiptuneJsPlayer.prototype.getRow = function () {
|
||||
if (this.currentPlayingNode && this.currentPlayingNode.modulePtr) {
|
||||
return libopenmpt._openmpt_module_get_current_row(this.currentPlayingNode.modulePtr);
|
||||
if (this.currentPlayingNode?.modulePtr) {
|
||||
return this.libopenmpt._openmpt_module_get_current_row(
|
||||
this.currentPlayingNode.modulePtr,
|
||||
);
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
ChiptuneJsPlayer.prototype.getNumPatterns = function () {
|
||||
if (this.currentPlayingNode?.modulePtr) {
|
||||
return this.libopenmpt._openmpt_module_get_num_patterns(
|
||||
this.currentPlayingNode.modulePtr,
|
||||
);
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
ChiptuneJsPlayer.prototype.getPatternNumRows = function (pattern: number) {
|
||||
if (this.currentPlayingNode && this.currentPlayingNode.modulePtr) {
|
||||
return libopenmpt._openmpt_module_get_pattern_num_rows(this.currentPlayingNode.modulePtr, pattern);
|
||||
if (this.currentPlayingNode?.modulePtr) {
|
||||
return this.libopenmpt._openmpt_module_get_pattern_num_rows(
|
||||
this.currentPlayingNode.modulePtr,
|
||||
pattern,
|
||||
);
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
ChiptuneJsPlayer.prototype.getPatternRowChannel = function (pattern: number, row: number, channel: number) {
|
||||
if (this.currentPlayingNode && this.currentPlayingNode.modulePtr) {
|
||||
return UTF8ToString(libopenmpt._openmpt_module_format_pattern_row_channel(this.currentPlayingNode.modulePtr, pattern, row, channel, 0, true));
|
||||
ChiptuneJsPlayer.prototype.getPatternRowChannel = function (
|
||||
pattern: number,
|
||||
row: number,
|
||||
channel: number,
|
||||
) {
|
||||
if (this.currentPlayingNode?.modulePtr) {
|
||||
return this.libopenmpt.UTF8ToString(
|
||||
this.libopenmpt._openmpt_module_format_pattern_row_channel(
|
||||
this.currentPlayingNode.modulePtr,
|
||||
pattern,
|
||||
row,
|
||||
channel,
|
||||
0,
|
||||
true,
|
||||
),
|
||||
);
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
ChiptuneJsPlayer.prototype.createLibopenmptNode = function (buffer, config: object) {
|
||||
ChiptuneJsPlayer.prototype.createLibopenmptNode = async function (
|
||||
buffer,
|
||||
config: object,
|
||||
) {
|
||||
const maxFramesPerChunk = 4096;
|
||||
const processNode = this.audioContext.createScriptProcessor(2048, 0, 2);
|
||||
processNode.config = config;
|
||||
processNode.player = this;
|
||||
|
||||
if (!this.libopenmpt) {
|
||||
const libopenmpt = await import('libopenmpt-wasm');
|
||||
this.libopenmpt = await libopenmpt.default();
|
||||
}
|
||||
|
||||
const byteArray = new Int8Array(buffer);
|
||||
const ptrToFile = libopenmpt._malloc(byteArray.byteLength);
|
||||
libopenmpt.HEAPU8.set(byteArray, ptrToFile);
|
||||
processNode.modulePtr = libopenmpt._openmpt_module_create_from_memory(ptrToFile, byteArray.byteLength, 0, 0, 0);
|
||||
processNode.nbChannels = libopenmpt._openmpt_module_get_num_channels(processNode.modulePtr);
|
||||
const ptrToFile = this.libopenmpt._malloc(byteArray.byteLength);
|
||||
this.libopenmpt.HEAPU8.set(byteArray, ptrToFile);
|
||||
processNode.modulePtr = this.libopenmpt._openmpt_module_create_from_memory(
|
||||
ptrToFile,
|
||||
byteArray.byteLength,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
processNode.nbChannels = this.libopenmpt._openmpt_module_get_num_channels(
|
||||
processNode.modulePtr,
|
||||
);
|
||||
processNode.patternIndex = -1;
|
||||
processNode.paused = false;
|
||||
processNode.leftBufferPtr = libopenmpt._malloc(4 * maxFramesPerChunk);
|
||||
processNode.rightBufferPtr = libopenmpt._malloc(4 * maxFramesPerChunk);
|
||||
processNode.leftBufferPtr = this.libopenmpt._malloc(4 * maxFramesPerChunk);
|
||||
processNode.rightBufferPtr = this.libopenmpt._malloc(4 * maxFramesPerChunk);
|
||||
processNode.cleanup = function () {
|
||||
if (this.modulePtr !== 0) {
|
||||
libopenmpt._openmpt_module_destroy(this.modulePtr);
|
||||
processNode.player.libopenmpt._openmpt_module_destroy(this.modulePtr);
|
||||
this.modulePtr = 0;
|
||||
}
|
||||
if (this.leftBufferPtr !== 0) {
|
||||
libopenmpt._free(this.leftBufferPtr);
|
||||
processNode.player.libopenmpt._free(this.leftBufferPtr);
|
||||
this.leftBufferPtr = 0;
|
||||
}
|
||||
if (this.rightBufferPtr !== 0) {
|
||||
libopenmpt._free(this.rightBufferPtr);
|
||||
processNode.player.libopenmpt._free(this.rightBufferPtr);
|
||||
this.rightBufferPtr = 0;
|
||||
}
|
||||
};
|
||||
|
|
@ -229,8 +316,14 @@ ChiptuneJsPlayer.prototype.createLibopenmptNode = function (buffer, config: obje
|
|||
let ended = false;
|
||||
let error = false;
|
||||
|
||||
const currentPattern = libopenmpt._openmpt_module_get_current_pattern(this.modulePtr);
|
||||
const currentRow = libopenmpt._openmpt_module_get_current_row(this.modulePtr);
|
||||
const currentPattern =
|
||||
processNode.player.libopenmpt._openmpt_module_get_current_pattern(
|
||||
this.modulePtr,
|
||||
);
|
||||
const currentRow =
|
||||
processNode.player.libopenmpt._openmpt_module_get_current_row(
|
||||
this.modulePtr,
|
||||
);
|
||||
if (currentPattern !== this.patternIndex) {
|
||||
processNode.player.fireEvent('onPatternChange');
|
||||
}
|
||||
|
|
@ -238,14 +331,27 @@ ChiptuneJsPlayer.prototype.createLibopenmptNode = function (buffer, config: obje
|
|||
|
||||
while (framesToRender > 0) {
|
||||
const framesPerChunk = Math.min(framesToRender, maxFramesPerChunk);
|
||||
const actualFramesPerChunk = libopenmpt._openmpt_module_read_float_stereo(this.modulePtr, this.context.sampleRate, framesPerChunk, this.leftBufferPtr, this.rightBufferPtr);
|
||||
const actualFramesPerChunk =
|
||||
processNode.player.libopenmpt._openmpt_module_read_float_stereo(
|
||||
this.modulePtr,
|
||||
this.context.sampleRate,
|
||||
framesPerChunk,
|
||||
this.leftBufferPtr,
|
||||
this.rightBufferPtr,
|
||||
);
|
||||
if (actualFramesPerChunk === 0) {
|
||||
ended = true;
|
||||
// modulePtr will be 0 on openmpt: error: openmpt_module_read_float_stereo: ERROR: module * not valid or other openmpt error
|
||||
error = !this.modulePtr;
|
||||
}
|
||||
const rawAudioLeft = libopenmpt.HEAPF32.subarray(this.leftBufferPtr / 4, this.leftBufferPtr / 4 + actualFramesPerChunk);
|
||||
const rawAudioRight = libopenmpt.HEAPF32.subarray(this.rightBufferPtr / 4, this.rightBufferPtr / 4 + actualFramesPerChunk);
|
||||
const rawAudioLeft = processNode.player.libopenmpt.HEAPF32.subarray(
|
||||
this.leftBufferPtr / 4,
|
||||
this.leftBufferPtr / 4 + actualFramesPerChunk,
|
||||
);
|
||||
const rawAudioRight = processNode.player.libopenmpt.HEAPF32.subarray(
|
||||
this.rightBufferPtr / 4,
|
||||
this.rightBufferPtr / 4 + actualFramesPerChunk,
|
||||
);
|
||||
for (let i = 0; i < actualFramesPerChunk; ++i) {
|
||||
outputL[framesRendered + i] = rawAudioLeft[i];
|
||||
outputR[framesRendered + i] = rawAudioRight[i];
|
||||
|
|
@ -260,7 +366,9 @@ ChiptuneJsPlayer.prototype.createLibopenmptNode = function (buffer, config: obje
|
|||
if (ended) {
|
||||
this.disconnect();
|
||||
this.cleanup();
|
||||
error ? processNode.player.fireEvent('onError', { type: 'openmpt' }) : processNode.player.fireEvent('onEnded');
|
||||
error
|
||||
? processNode.player.fireEvent('onError', { type: 'openmpt' })
|
||||
: processNode.player.fireEvent('onEnded');
|
||||
}
|
||||
};
|
||||
return processNode;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue