mirror of
https://git.joinsharkey.org/Sharkey/Sharkey.git
synced 2024-11-30 04:03:08 +02:00
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
|
@ -121,6 +121,7 @@ pinnedNote: "Pinned note"
|
||||||
pinned: "Pin to profile"
|
pinned: "Pin to profile"
|
||||||
you: "You"
|
you: "You"
|
||||||
clickToShow: "Click to show"
|
clickToShow: "Click to show"
|
||||||
|
patternHidden: "Pattern hidden"
|
||||||
sensitive: "Sensitive"
|
sensitive: "Sensitive"
|
||||||
add: "Add"
|
add: "Add"
|
||||||
reaction: "Reactions"
|
reaction: "Reactions"
|
||||||
|
|
1
locales/index.d.ts
vendored
1
locales/index.d.ts
vendored
|
@ -124,6 +124,7 @@ export interface Locale {
|
||||||
"pinned": string;
|
"pinned": string;
|
||||||
"you": string;
|
"you": string;
|
||||||
"clickToShow": string;
|
"clickToShow": string;
|
||||||
|
"patternHidden": string;
|
||||||
"sensitive": string;
|
"sensitive": string;
|
||||||
"add": string;
|
"add": string;
|
||||||
"reaction": string;
|
"reaction": string;
|
||||||
|
|
|
@ -121,6 +121,7 @@ pinnedNote: "ピン留めされたノート"
|
||||||
pinned: "ピン留め"
|
pinned: "ピン留め"
|
||||||
you: "あなた"
|
you: "あなた"
|
||||||
clickToShow: "クリックして表示"
|
clickToShow: "クリックして表示"
|
||||||
|
patternHidden: "パターン非表示"
|
||||||
sensitive: "センシティブ"
|
sensitive: "センシティブ"
|
||||||
add: "追加"
|
add: "追加"
|
||||||
reaction: "リアクション"
|
reaction: "リアクション"
|
||||||
|
|
|
@ -43,7 +43,6 @@ html
|
||||||
link(rel='stylesheet' href='/assets/phosphor-icons/bold/style.css')
|
link(rel='stylesheet' href='/assets/phosphor-icons/bold/style.css')
|
||||||
link(rel='stylesheet' href='/static-assets/fonts/sharkey-icons/style.css')
|
link(rel='stylesheet' href='/static-assets/fonts/sharkey-icons/style.css')
|
||||||
link(rel='modulepreload' href=`/vite/${clientEntry.file}`)
|
link(rel='modulepreload' href=`/vite/${clientEntry.file}`)
|
||||||
script(src='/client-assets/libopenmpt.js')
|
|
||||||
|
|
||||||
if !config.clientManifestExists
|
if !config.clientManifestExists
|
||||||
script(type="module" src="/vite/@vite/client")
|
script(type="module" src="/vite/@vite/client")
|
||||||
|
@ -73,7 +72,6 @@ html
|
||||||
script.
|
script.
|
||||||
var VERSION = "#{version}";
|
var VERSION = "#{version}";
|
||||||
var CLIENT_ENTRY = "#{clientEntry.file}";
|
var CLIENT_ENTRY = "#{clientEntry.file}";
|
||||||
window.libopenmpt = window.Module;
|
|
||||||
|
|
||||||
script
|
script
|
||||||
include ../boot.js
|
include ../boot.js
|
||||||
|
|
File diff suppressed because one or more lines are too long
Binary file not shown.
|
@ -52,6 +52,7 @@
|
||||||
"is-file-animated": "1.0.2",
|
"is-file-animated": "1.0.2",
|
||||||
"json5": "2.2.3",
|
"json5": "2.2.3",
|
||||||
"katex": "0.16.9",
|
"katex": "0.16.9",
|
||||||
|
"libopenmpt-wasm": "github:TheEssem/libopenmpt-packaging#build",
|
||||||
"matter-js": "0.19.0",
|
"matter-js": "0.19.0",
|
||||||
"misskey-js": "workspace:*",
|
"misskey-js": "workspace:*",
|
||||||
"photoswipe": "5.4.3",
|
"photoswipe": "5.4.3",
|
||||||
|
|
|
@ -1,116 +1,147 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="hide" class="mod-player-disabled" @click="toggleVisible()">
|
<div v-if="!available" :class="$style.disabled">
|
||||||
|
<MkLoading v-if="fetching"/>
|
||||||
|
<MkError v-else-if="error" @retry="load()"/>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="hide" :class="$style.disabled" @click="toggleVisible()">
|
||||||
<div>
|
<div>
|
||||||
<b><i class="ph-eye ph-bold ph-lg"></i> {{ i18n.ts.sensitive }}</b>
|
<b><i class="ph-eye ph-bold ph-lg"></i> {{ i18n.ts.sensitive }}</b>
|
||||||
<span>{{ i18n.ts.clickToShow }}</span>
|
<span>{{ i18n.ts.clickToShow }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="mod-player-enabled">
|
<div v-else :class="$style.enabled">
|
||||||
<div class="pattern-display" @click="togglePattern()">
|
<div :class="$style.patternDisplay">
|
||||||
<div v-if="patternHide" class="pattern-hide">
|
<div v-if="patternShow">
|
||||||
<b><i class="ph-eye ph-bold ph-lg"></i> Pattern Hidden</b>
|
<div v-if="patData.length !== 0" ref="modPattern" :class="$style.pattern">
|
||||||
<span>{{ i18n.ts.clickToShow }}</span>
|
<span
|
||||||
|
v-for="(row, i) in patData[currentPattern]"
|
||||||
|
ref="initRow"
|
||||||
|
:key="i"
|
||||||
|
:class="[$style.row, { [$style.active]: isRowActive(i) }]"
|
||||||
|
>
|
||||||
|
<span :class="{ [$style.colQuarter]: i % 4 === 0 }">{{ indexText(i) }}</span>
|
||||||
|
<span :class="$style.inner">{{ getRowText(row) }}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<MkLoading v-else/>
|
||||||
|
</div>
|
||||||
|
<div v-else :class="$style.pattern" @click="showPattern()">
|
||||||
|
<p>{{ i18n.ts.patternHidden }}</p>
|
||||||
</div>
|
</div>
|
||||||
<canvas ref="displayCanvas" class="pattern-canvas"></canvas>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="controls">
|
<div :class="$style.controls">
|
||||||
<button class="play" @click="playPause()">
|
<button v-if="!loading" :class="$style.play" @click="playPause()">
|
||||||
<i v-if="playing" class="ph-pause ph-bold ph-lg"></i>
|
<i v-if="playing" class="ph-pause ph-bold ph-lg"></i>
|
||||||
<i v-else class="ph-play ph-bold ph-lg"></i>
|
<i v-else class="ph-play ph-bold ph-lg"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="stop" @click="stop()">
|
<MkLoading v-else :em="true"/>
|
||||||
|
<button :class="$style.stop" @click="stop()">
|
||||||
<i class="ph-stop ph-bold ph-lg"></i>
|
<i class="ph-stop ph-bold ph-lg"></i>
|
||||||
</button>
|
</button>
|
||||||
<input ref="progress" v-model="position" class="progress" type="range" min="0" max="1" step="0.1" @mousedown="initSeek()" @mouseup="performSeek()"/>
|
<button :class="$style.loop" @click="toggleLoop()">
|
||||||
|
<i v-if="loop === -1" class="ph-repeat ph-bold ph-lg"></i>
|
||||||
|
<i v-else class="ph-repeat-once ph-bold ph-lg"></i>
|
||||||
|
</button>
|
||||||
|
<input ref="progress" v-model="position" :class="$style.progress" type="range" min="0" :max="length" step="0.1" @mousedown="initSeek()" @mouseup="performSeek()"/>
|
||||||
<input v-model="player.context.gain.value" type="range" min="0" max="1" step="0.1"/>
|
<input v-model="player.context.gain.value" type="range" min="0" max="1" step="0.1"/>
|
||||||
<a class="download" :title="i18n.ts.download" :href="module.url" target="_blank">
|
<a :class="$style.download" :title="i18n.ts.download" :href="module.url" target="_blank">
|
||||||
<i class="ph-download ph-bold ph-lg"></i>
|
<i class="ph-download ph-bold ph-lg"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<i class="hide ph-eye-slash ph-bold ph-lg" @click="toggleVisible()"></i>
|
<i :class="$style.hide" class="ph-eye-slash ph-bold ph-lg" @click="toggleVisible()"></i>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, nextTick, computed } from 'vue';
|
import { ref, shallowRef, nextTick, onDeactivated, onMounted } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import { ChiptuneJsPlayer, ChiptuneJsConfig } from '@/scripts/chiptune2.js';
|
import { ChiptuneJsPlayer, ChiptuneJsConfig } from '@/scripts/chiptune2.js';
|
||||||
|
|
||||||
const CHAR_WIDTH = 6;
|
|
||||||
const CHAR_HEIGHT = 12;
|
|
||||||
const ROW_OFFSET_Y = 10;
|
|
||||||
|
|
||||||
const colours = {
|
|
||||||
background: '#000000',
|
|
||||||
default: {
|
|
||||||
active: '#ffffff',
|
|
||||||
inactive: '#808080',
|
|
||||||
},
|
|
||||||
quarter: {
|
|
||||||
active: '#ffff00',
|
|
||||||
inactive: '#ffe135',
|
|
||||||
},
|
|
||||||
instr: {
|
|
||||||
active: '#80e0ff',
|
|
||||||
inactive: '#0099cc',
|
|
||||||
},
|
|
||||||
volume: {
|
|
||||||
active: '#80ff80',
|
|
||||||
inactive: '#008000',
|
|
||||||
},
|
|
||||||
fx: {
|
|
||||||
active: '#ff80e0',
|
|
||||||
inactive: '#800060',
|
|
||||||
},
|
|
||||||
operant: {
|
|
||||||
active: '#ffe080',
|
|
||||||
inactive: '#806000',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
module: Misskey.entities.DriveFile
|
module: Misskey.entities.DriveFile
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const isSensitive = computed(() => { return props.module.isSensitive; });
|
interface ModRow {
|
||||||
const url = computed(() => { return props.module.url; });
|
notes: string[];
|
||||||
let hide = ref((defaultStore.state.nsfw === 'force') ? true : isSensitive.value && (defaultStore.state.nsfw !== 'ignore'));
|
insts: string[];
|
||||||
let patternHide = ref(false);
|
vols: string[];
|
||||||
let firstFrame = ref(true);
|
fxs: string[];
|
||||||
let playing = ref(false);
|
ops: string[];
|
||||||
let displayCanvas = ref<HTMLCanvasElement>();
|
}
|
||||||
let progress = ref<HTMLProgressElement>();
|
|
||||||
let position = ref(0);
|
|
||||||
const player = ref(new ChiptuneJsPlayer(new ChiptuneJsConfig()));
|
|
||||||
|
|
||||||
const rowBuffer = 24;
|
const available = ref(false);
|
||||||
|
const initRow = shallowRef<HTMLSpanElement>();
|
||||||
|
const hide = ref(defaultStore.state.nsfw === 'force' ? true : props.module.isSensitive && defaultStore.state.nsfw !== 'ignore');
|
||||||
|
const patternShow = ref(false);
|
||||||
|
const playing = ref(false);
|
||||||
|
const modPattern = ref<HTMLDivElement>();
|
||||||
|
const progress = ref<HTMLProgressElement>();
|
||||||
|
const position = ref(0);
|
||||||
|
const player = shallowRef(new ChiptuneJsPlayer(new ChiptuneJsConfig()));
|
||||||
|
const patData = shallowRef([] as ModRow[][]);
|
||||||
|
const currentPattern = ref(0);
|
||||||
|
const nbChannels = ref(0);
|
||||||
|
const length = ref(1);
|
||||||
|
const loop = ref(0);
|
||||||
|
const fetching = ref(true);
|
||||||
|
const error = ref(false);
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
let currentRow = 0;
|
||||||
|
let rowHeight = 0;
|
||||||
let buffer = null;
|
let buffer = null;
|
||||||
let isSeeking = false;
|
let isSeeking = false;
|
||||||
|
|
||||||
player.value.load(url.value).then((result) => {
|
function load() {
|
||||||
buffer = result;
|
player.value.load(props.module.url).then((result) => {
|
||||||
try {
|
buffer = result;
|
||||||
player.value.play(buffer);
|
available.value = true;
|
||||||
progress.value!.max = player.value.duration();
|
error.value = false;
|
||||||
display();
|
fetching.value = false;
|
||||||
} catch (err) {
|
}).catch((err) => {
|
||||||
console.warn(err);
|
console.error(err);
|
||||||
|
error.value = true;
|
||||||
|
fetching.value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(load);
|
||||||
|
|
||||||
|
function showPattern() {
|
||||||
|
patternShow.value = !patternShow.value;
|
||||||
|
nextTick(() => {
|
||||||
|
if (playing.value) display();
|
||||||
|
else stop();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRowText(row: ModRow) {
|
||||||
|
let text = '';
|
||||||
|
for (let i = 0; i < row.notes.length; i++) {
|
||||||
|
text = text.concat(
|
||||||
|
'|',
|
||||||
|
row.notes[i],
|
||||||
|
row.insts[i],
|
||||||
|
row.vols[i],
|
||||||
|
row.fxs[i],
|
||||||
|
row.ops[i],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
player.value.stop();
|
return text;
|
||||||
}).catch((error) => {
|
}
|
||||||
console.error(error);
|
|
||||||
});
|
|
||||||
|
|
||||||
function playPause() {
|
function playPause() {
|
||||||
player.value.addHandler('onRowChange', () => {
|
player.value.addHandler('onRowChange', (i: { index: number }) => {
|
||||||
progress.value!.max = player.value.duration();
|
currentRow = i.index;
|
||||||
|
currentPattern.value = player.value.getPattern();
|
||||||
|
length.value = player.value.duration();
|
||||||
if (!isSeeking) {
|
if (!isSeeking) {
|
||||||
position.value = player.value.position() % player.value.duration();
|
position.value = player.value.position() % length.value;
|
||||||
}
|
}
|
||||||
display();
|
requestAnimationFrame(() => display());
|
||||||
});
|
});
|
||||||
|
|
||||||
player.value.addHandler('onEnded', () => {
|
player.value.addHandler('onEnded', () => {
|
||||||
|
@ -118,29 +149,39 @@ function playPause() {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (player.value.currentPlayingNode === null) {
|
if (player.value.currentPlayingNode === null) {
|
||||||
player.value.play(buffer);
|
loading.value = true;
|
||||||
player.value.seek(position.value);
|
player.value.play(buffer).then(() => {
|
||||||
playing.value = true;
|
player.value.seek(position.value);
|
||||||
|
player.value.repeat(loop.value);
|
||||||
|
playing.value = true;
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
player.value.togglePause();
|
player.value.togglePause();
|
||||||
playing.value = !player.value.currentPlayingNode.paused;
|
playing.value = !player.value.currentPlayingNode.paused;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stop(noDisplayUpdate = false) {
|
async function stop(noDisplayUpdate = false) {
|
||||||
player.value.stop();
|
player.value.stop();
|
||||||
playing.value = false;
|
playing.value = false;
|
||||||
if (!noDisplayUpdate) {
|
if (!noDisplayUpdate) {
|
||||||
try {
|
try {
|
||||||
player.value.play(buffer);
|
await player.value.play(buffer);
|
||||||
display();
|
display(true);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn(err);
|
console.warn(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
player.value.stop();
|
player.value.stop();
|
||||||
position.value = 0;
|
position.value = 0;
|
||||||
player.value.handlers = [];
|
currentRow = 0;
|
||||||
|
player.value.clearHandlers();
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleLoop() {
|
||||||
|
loop.value = loop.value === -1 ? 0 : -1;
|
||||||
|
player.value.repeat(loop.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function initSeek() {
|
function initSeek() {
|
||||||
|
@ -148,122 +189,104 @@ function initSeek() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function performSeek() {
|
function performSeek() {
|
||||||
const noNode = !player.value.currentPlayingNode;
|
|
||||||
if (noNode) {
|
|
||||||
player.value.play(buffer);
|
|
||||||
}
|
|
||||||
player.value.seek(position.value);
|
player.value.seek(position.value);
|
||||||
display();
|
display();
|
||||||
if (noNode) {
|
|
||||||
player.value.stop();
|
|
||||||
}
|
|
||||||
isSeeking = false;
|
isSeeking = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleVisible() {
|
function toggleVisible() {
|
||||||
hide.value = !hide.value;
|
hide.value = !hide.value;
|
||||||
if (!hide.value && patternHide.value) {
|
|
||||||
firstFrame.value = true;
|
|
||||||
patternHide.value = false;
|
|
||||||
}
|
|
||||||
nextTick(() => { stop(hide.value); });
|
nextTick(() => { stop(hide.value); });
|
||||||
}
|
}
|
||||||
|
|
||||||
function togglePattern() {
|
function isRowActive(i: number) {
|
||||||
patternHide.value = !patternHide.value;
|
if (i === currentRow) {
|
||||||
if (!patternHide.value) {
|
if (modPattern.value) {
|
||||||
if (player.value.getRow() === 0) {
|
if (rowHeight === 0 && initRow.value) rowHeight = initRow.value[0].getBoundingClientRect().height;
|
||||||
try {
|
modPattern.value.scrollTop = currentRow * rowHeight;
|
||||||
player.value.play(buffer);
|
|
||||||
display();
|
|
||||||
} catch (err) {
|
|
||||||
console.warn(err);
|
|
||||||
}
|
|
||||||
player.value.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function display() {
|
|
||||||
if (!displayCanvas.value) {
|
|
||||||
stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (patternHide.value) return;
|
|
||||||
|
|
||||||
if (firstFrame.value) {
|
|
||||||
firstFrame.value = false;
|
|
||||||
patternHide.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const canvas = displayCanvas.value;
|
|
||||||
|
|
||||||
const pattern = player.value.getPattern();
|
|
||||||
const row = player.value.getRow();
|
|
||||||
let nbChannels = 0;
|
|
||||||
if (player.value.currentPlayingNode) {
|
|
||||||
nbChannels = player.value.currentPlayingNode.nbChannels;
|
|
||||||
}
|
|
||||||
if (canvas.width !== 12 + 84 * nbChannels + 2) {
|
|
||||||
canvas.width = 12 + 84 * nbChannels + 2;
|
|
||||||
canvas.height = 12 * rowBuffer;
|
|
||||||
}
|
|
||||||
const nbRows = player.value.getPatternNumRows(pattern);
|
|
||||||
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
|
|
||||||
ctx.font = '10px monospace';
|
|
||||||
ctx.fillStyle = colours.background;
|
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
||||||
ctx.fillStyle = colours.default.inactive;
|
|
||||||
for (let rowOffset = 0; rowOffset < rowBuffer; rowOffset++) {
|
|
||||||
const rowToDraw = row - rowBuffer / 2 + rowOffset;
|
|
||||||
if (rowToDraw >= 0 && rowToDraw < nbRows) {
|
|
||||||
const active = (rowToDraw === row) ? 'active' : 'inactive';
|
|
||||||
let rowText = parseInt(rowToDraw).toString(16);
|
|
||||||
if (rowText.length === 1) {
|
|
||||||
rowText = '0' + rowText;
|
|
||||||
}
|
|
||||||
ctx.fillStyle = colours.default[active];
|
|
||||||
if (rowToDraw % 4 === 0) {
|
|
||||||
ctx.fillStyle = colours.quarter[active];
|
|
||||||
}
|
|
||||||
ctx.fillText(rowText, 0, 10 + rowOffset * 12);
|
|
||||||
for (let channel = 0; channel < nbChannels; channel++) {
|
|
||||||
const part = player.value.getPatternRowChannel(pattern, rowToDraw, channel);
|
|
||||||
const baseOffset = (2 + (part.length + 1) * channel) * CHAR_WIDTH;
|
|
||||||
const baseRowOffset = ROW_OFFSET_Y + rowOffset * CHAR_HEIGHT;
|
|
||||||
|
|
||||||
ctx.fillStyle = colours.default[active];
|
|
||||||
ctx.fillText('|', baseOffset, baseRowOffset);
|
|
||||||
|
|
||||||
const note = part.substring(0, 3);
|
|
||||||
ctx.fillStyle = colours.default[active];
|
|
||||||
ctx.fillText(note, baseOffset + CHAR_WIDTH, baseRowOffset);
|
|
||||||
|
|
||||||
const instr = part.substring(4, 6);
|
|
||||||
ctx.fillStyle = colours.instr[active];
|
|
||||||
ctx.fillText(instr, baseOffset + CHAR_WIDTH * 5, baseRowOffset);
|
|
||||||
|
|
||||||
const volume = part.substring(6, 9);
|
|
||||||
ctx.fillStyle = colours.volume[active];
|
|
||||||
ctx.fillText(volume, baseOffset + CHAR_WIDTH * 7, baseRowOffset);
|
|
||||||
|
|
||||||
const fx = part.substring(10, 11);
|
|
||||||
ctx.fillStyle = colours.fx[active];
|
|
||||||
ctx.fillText(fx, baseOffset + CHAR_WIDTH * 11, baseRowOffset);
|
|
||||||
|
|
||||||
const op = part.substring(11, 13);
|
|
||||||
ctx.fillStyle = colours.operant[active];
|
|
||||||
ctx.fillText(op, baseOffset + CHAR_WIDTH * 12, baseRowOffset);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function indexText(i: number) {
|
||||||
|
let rowText = i.toString(16);
|
||||||
|
if (rowText.length === 1) {
|
||||||
|
rowText = '0' + rowText;
|
||||||
|
}
|
||||||
|
return rowText;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRow(pattern: number, rowOffset: number) {
|
||||||
|
let notes: string[] = [],
|
||||||
|
insts: string[] = [],
|
||||||
|
vols: string[] = [],
|
||||||
|
fxs: string[] = [],
|
||||||
|
ops: string[] = [];
|
||||||
|
|
||||||
|
for (let channel = 0; channel < nbChannels.value; channel++) {
|
||||||
|
const part = player.value.getPatternRowChannel(
|
||||||
|
pattern,
|
||||||
|
rowOffset,
|
||||||
|
channel,
|
||||||
|
);
|
||||||
|
|
||||||
|
notes.push(part.substring(0, 3));
|
||||||
|
insts.push(part.substring(4, 6));
|
||||||
|
vols.push(part.substring(6, 9));
|
||||||
|
fxs.push(part.substring(10, 11));
|
||||||
|
ops.push(part.substring(11, 13));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
notes,
|
||||||
|
insts,
|
||||||
|
vols,
|
||||||
|
fxs,
|
||||||
|
ops,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function display(reset = false) {
|
||||||
|
if (!patternShow.value) return;
|
||||||
|
|
||||||
|
if (reset) {
|
||||||
|
const pattern = player.value.getPattern();
|
||||||
|
currentPattern.value = pattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (patData.value.length === 0) {
|
||||||
|
const nbPatterns = player.value.getNumPatterns();
|
||||||
|
const pattern = player.value.getPattern();
|
||||||
|
|
||||||
|
currentPattern.value = pattern;
|
||||||
|
|
||||||
|
if (player.value.currentPlayingNode) {
|
||||||
|
nbChannels.value = player.value.currentPlayingNode.nbChannels;
|
||||||
|
}
|
||||||
|
|
||||||
|
const patternsArray: ModRow[][] = [];
|
||||||
|
|
||||||
|
for (let patOffset = 0; patOffset < nbPatterns; patOffset++) {
|
||||||
|
const rowsArray: ModRow[] = [];
|
||||||
|
const nbRows = player.value.getPatternNumRows(patOffset);
|
||||||
|
for (let rowOffset = 0; rowOffset < nbRows; rowOffset++) {
|
||||||
|
rowsArray.push(getRow(patOffset, rowOffset));
|
||||||
|
}
|
||||||
|
patternsArray.push(rowsArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
patData.value = Object.freeze(patternsArray);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onDeactivated(() => {
|
||||||
|
stop();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" module>
|
||||||
|
|
||||||
.hide {
|
.hide {
|
||||||
border-radius: var(--radius-sm) !important;
|
border-radius: var(--radius-sm) !important;
|
||||||
background-color: black !important;
|
background-color: black !important;
|
||||||
|
@ -271,7 +294,7 @@ function display() {
|
||||||
font-size: 12px !important;
|
font-size: 12px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mod-player-enabled {
|
.enabled {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -292,34 +315,49 @@ function display() {
|
||||||
right: 12px;
|
right: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .pattern-display {
|
> .patternDisplay {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow-x: scroll;
|
overflow: hidden;
|
||||||
overflow-y: hidden;
|
color: white;
|
||||||
background-color: black;
|
background-color: black;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
.pattern-canvas {
|
font: 12px monospace;
|
||||||
background-color: black;
|
white-space: pre;
|
||||||
height: 100%;
|
user-select: none;
|
||||||
}
|
|
||||||
.pattern-hide {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
background: rgba(64, 64, 64, 0.3);
|
|
||||||
backdrop-filter: blur(2em);
|
|
||||||
color: #fff;
|
|
||||||
font-size: 12px;
|
|
||||||
|
|
||||||
position: absolute;
|
.pattern {
|
||||||
z-index: 0;
|
display: grid;
|
||||||
width: 100%;
|
overflow-y: hidden;
|
||||||
height: 100%;
|
height: 0;
|
||||||
|
padding-top: calc((56.25% - 48px) / 2);
|
||||||
|
padding-bottom: calc((56.25% - 48px) / 2);
|
||||||
|
content-visibility: auto;
|
||||||
|
|
||||||
> span {
|
.row {
|
||||||
display: block;
|
opacity: 0.5;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .colQuarter {
|
||||||
|
color: var(--badge);
|
||||||
|
}
|
||||||
|
|
||||||
|
> .inner {
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
to right,
|
||||||
|
var(--fg) 0 4ch,
|
||||||
|
var(--codeBoolean) 4ch 6ch,
|
||||||
|
var(--codeNumber) 6ch 9ch,
|
||||||
|
var(--codeString) 9ch 10ch,
|
||||||
|
var(--error) 10ch 12ch
|
||||||
|
);
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -455,7 +493,7 @@ function display() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mod-player-disabled {
|
.disabled {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
@ -1,18 +1,16 @@
|
||||||
/* global libopenmpt UTF8ToString writeAsciiToMemory */
|
const ChiptuneAudioContext = window.AudioContext;
|
||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
const ChiptuneAudioContext = window.AudioContext || window.webkitAudioContext;
|
export function ChiptuneJsConfig(repeatCount?: number, context?: AudioContext) {
|
||||||
|
|
||||||
export function ChiptuneJsConfig (repeatCount: number, context: AudioContext) {
|
|
||||||
this.repeatCount = repeatCount;
|
this.repeatCount = repeatCount;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
ChiptuneJsConfig.prototype.constructor = ChiptuneJsConfig;
|
ChiptuneJsConfig.prototype.constructor = ChiptuneJsConfig;
|
||||||
|
|
||||||
export function ChiptuneJsPlayer (config: object) {
|
export function ChiptuneJsPlayer(config: object) {
|
||||||
|
this.libopenmpt = null;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.audioContext = config.context || new ChiptuneAudioContext();
|
this.audioContext = config.context ?? new ChiptuneAudioContext();
|
||||||
this.context = this.audioContext.createGain();
|
this.context = this.audioContext.createGain();
|
||||||
this.currentPlayingNode = null;
|
this.currentPlayingNode = null;
|
||||||
this.handlers = [];
|
this.handlers = [];
|
||||||
|
@ -25,7 +23,7 @@ ChiptuneJsPlayer.prototype.constructor = ChiptuneJsPlayer;
|
||||||
ChiptuneJsPlayer.prototype.fireEvent = function (eventName: string, response) {
|
ChiptuneJsPlayer.prototype.fireEvent = function (eventName: string, response) {
|
||||||
const handlers = this.handlers;
|
const handlers = this.handlers;
|
||||||
if (handlers.length > 0) {
|
if (handlers.length > 0) {
|
||||||
for(const handler of handlers) {
|
for (const handler of handlers) {
|
||||||
if (handler.eventName === eventName) {
|
if (handler.eventName === eventName) {
|
||||||
handler.handler(response);
|
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 });
|
this.handlers.push({ eventName, handler });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ChiptuneJsPlayer.prototype.clearHandlers = function () {
|
||||||
|
this.handlers = [];
|
||||||
|
};
|
||||||
|
|
||||||
ChiptuneJsPlayer.prototype.onEnded = function (handler: Function) {
|
ChiptuneJsPlayer.prototype.onEnded = function (handler: Function) {
|
||||||
this.addHandler('onEnded', handler);
|
this.addHandler('onEnded', handler);
|
||||||
};
|
};
|
||||||
|
@ -46,28 +51,55 @@ ChiptuneJsPlayer.prototype.onError = function (handler: Function) {
|
||||||
};
|
};
|
||||||
|
|
||||||
ChiptuneJsPlayer.prototype.duration = 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 () {
|
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) {
|
ChiptuneJsPlayer.prototype.seek = function (position: number) {
|
||||||
if (this.currentPlayingNode) {
|
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 () {
|
ChiptuneJsPlayer.prototype.metadata = function () {
|
||||||
const data = {};
|
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;
|
let keyNameBuffer = 0;
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
keyNameBuffer = libopenmpt._malloc(key.length + 1);
|
keyNameBuffer = this.libopenmpt._malloc(key.length + 1);
|
||||||
writeAsciiToMemory(key, keyNameBuffer);
|
this.libopenmpt.stringToUTF8(key, keyNameBuffer);
|
||||||
data[key] = UTF8ToString(libopenmpt._openmpt_module_get_metadata(this.currentPlayingNode.modulePtr, keyNameBuffer));
|
data[key] = this.libopenmpt.UTF8ToString(
|
||||||
libopenmpt._free(keyNameBuffer);
|
this.libopenmpt._openmpt_module_get_metadata(
|
||||||
|
this.currentPlayingNode.modulePtr,
|
||||||
|
keyNameBuffer,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
this.libopenmpt._free(keyNameBuffer);
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
@ -85,10 +117,9 @@ ChiptuneJsPlayer.prototype.unlock = function () {
|
||||||
|
|
||||||
ChiptuneJsPlayer.prototype.load = function (input) {
|
ChiptuneJsPlayer.prototype.load = function (input) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if(this.touchLocked) {
|
if (this.touchLocked) {
|
||||||
this.unlock();
|
this.unlock();
|
||||||
}
|
}
|
||||||
const player = this;
|
|
||||||
if (input instanceof File) {
|
if (input instanceof File) {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = () => {
|
reader.onload = () => {
|
||||||
|
@ -96,30 +127,40 @@ ChiptuneJsPlayer.prototype.load = function (input) {
|
||||||
};
|
};
|
||||||
reader.readAsArrayBuffer(input);
|
reader.readAsArrayBuffer(input);
|
||||||
} else {
|
} else {
|
||||||
window.fetch(input).then((response) => {
|
window
|
||||||
response.arrayBuffer().then((arrayBuffer) => {
|
.fetch(input)
|
||||||
resolve(arrayBuffer);
|
.then((response) => {
|
||||||
}).catch((error) => {
|
response
|
||||||
|
.arrayBuffer()
|
||||||
|
.then((arrayBuffer) => {
|
||||||
|
resolve(arrayBuffer);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
}).catch((error) => {
|
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
ChiptuneJsPlayer.prototype.play = function (buffer: ArrayBuffer) {
|
ChiptuneJsPlayer.prototype.play = async function (buffer: ArrayBuffer) {
|
||||||
this.unlock();
|
this.unlock();
|
||||||
this.stop();
|
this.stop();
|
||||||
const processNode = this.createLibopenmptNode(buffer, this.buffer);
|
return this.createLibopenmptNode(buffer, this.buffer).then((processNode) => {
|
||||||
if (processNode === null) {
|
if (processNode === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
libopenmpt._openmpt_module_set_repeat_count(processNode.modulePtr, this.config.repeatCount || 0);
|
this.libopenmpt._openmpt_module_set_repeat_count(
|
||||||
this.currentPlayingNode = processNode;
|
processNode.modulePtr,
|
||||||
processNode.connect(this.context);
|
this.config.repeatCount ?? 0,
|
||||||
this.context.connect(this.audioContext.destination);
|
);
|
||||||
|
this.currentPlayingNode = processNode;
|
||||||
|
processNode.connect(this.context);
|
||||||
|
this.context.connect(this.audioContext.destination);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
ChiptuneJsPlayer.prototype.stop = function () {
|
ChiptuneJsPlayer.prototype.stop = function () {
|
||||||
|
@ -137,58 +178,104 @@ ChiptuneJsPlayer.prototype.togglePause = function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
ChiptuneJsPlayer.prototype.getPattern = function () {
|
ChiptuneJsPlayer.prototype.getPattern = function () {
|
||||||
if (this.currentPlayingNode && this.currentPlayingNode.modulePtr) {
|
if (this.currentPlayingNode?.modulePtr) {
|
||||||
return libopenmpt._openmpt_module_get_current_pattern(this.currentPlayingNode.modulePtr);
|
return this.libopenmpt._openmpt_module_get_current_pattern(
|
||||||
|
this.currentPlayingNode.modulePtr,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
ChiptuneJsPlayer.prototype.getRow = function () {
|
ChiptuneJsPlayer.prototype.getRow = function () {
|
||||||
if (this.currentPlayingNode && this.currentPlayingNode.modulePtr) {
|
if (this.currentPlayingNode?.modulePtr) {
|
||||||
return libopenmpt._openmpt_module_get_current_row(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;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
ChiptuneJsPlayer.prototype.getPatternNumRows = function (pattern: number) {
|
ChiptuneJsPlayer.prototype.getPatternNumRows = function (pattern: number) {
|
||||||
if (this.currentPlayingNode && this.currentPlayingNode.modulePtr) {
|
if (this.currentPlayingNode?.modulePtr) {
|
||||||
return libopenmpt._openmpt_module_get_pattern_num_rows(this.currentPlayingNode.modulePtr, pattern);
|
return this.libopenmpt._openmpt_module_get_pattern_num_rows(
|
||||||
|
this.currentPlayingNode.modulePtr,
|
||||||
|
pattern,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
ChiptuneJsPlayer.prototype.getPatternRowChannel = function (pattern: number, row: number, channel: number) {
|
ChiptuneJsPlayer.prototype.getPatternRowChannel = function (
|
||||||
if (this.currentPlayingNode && this.currentPlayingNode.modulePtr) {
|
pattern: number,
|
||||||
return UTF8ToString(libopenmpt._openmpt_module_format_pattern_row_channel(this.currentPlayingNode.modulePtr, pattern, row, channel, 0, true));
|
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 '';
|
return '';
|
||||||
};
|
};
|
||||||
|
|
||||||
ChiptuneJsPlayer.prototype.createLibopenmptNode = function (buffer, config: object) {
|
ChiptuneJsPlayer.prototype.createLibopenmptNode = async function (
|
||||||
|
buffer,
|
||||||
|
config: object,
|
||||||
|
) {
|
||||||
const maxFramesPerChunk = 4096;
|
const maxFramesPerChunk = 4096;
|
||||||
const processNode = this.audioContext.createScriptProcessor(2048, 0, 2);
|
const processNode = this.audioContext.createScriptProcessor(2048, 0, 2);
|
||||||
processNode.config = config;
|
processNode.config = config;
|
||||||
processNode.player = this;
|
processNode.player = this;
|
||||||
|
|
||||||
|
if (!this.libopenmpt) {
|
||||||
|
const libopenmpt = await import('libopenmpt-wasm');
|
||||||
|
this.libopenmpt = await libopenmpt.default();
|
||||||
|
}
|
||||||
|
|
||||||
const byteArray = new Int8Array(buffer);
|
const byteArray = new Int8Array(buffer);
|
||||||
const ptrToFile = libopenmpt._malloc(byteArray.byteLength);
|
const ptrToFile = this.libopenmpt._malloc(byteArray.byteLength);
|
||||||
libopenmpt.HEAPU8.set(byteArray, ptrToFile);
|
this.libopenmpt.HEAPU8.set(byteArray, ptrToFile);
|
||||||
processNode.modulePtr = libopenmpt._openmpt_module_create_from_memory(ptrToFile, byteArray.byteLength, 0, 0, 0);
|
processNode.modulePtr = this.libopenmpt._openmpt_module_create_from_memory(
|
||||||
processNode.nbChannels = libopenmpt._openmpt_module_get_num_channels(processNode.modulePtr);
|
ptrToFile,
|
||||||
|
byteArray.byteLength,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
processNode.nbChannels = this.libopenmpt._openmpt_module_get_num_channels(
|
||||||
|
processNode.modulePtr,
|
||||||
|
);
|
||||||
processNode.patternIndex = -1;
|
processNode.patternIndex = -1;
|
||||||
processNode.paused = false;
|
processNode.paused = false;
|
||||||
processNode.leftBufferPtr = libopenmpt._malloc(4 * maxFramesPerChunk);
|
processNode.leftBufferPtr = this.libopenmpt._malloc(4 * maxFramesPerChunk);
|
||||||
processNode.rightBufferPtr = libopenmpt._malloc(4 * maxFramesPerChunk);
|
processNode.rightBufferPtr = this.libopenmpt._malloc(4 * maxFramesPerChunk);
|
||||||
processNode.cleanup = function () {
|
processNode.cleanup = function () {
|
||||||
if (this.modulePtr !== 0) {
|
if (this.modulePtr !== 0) {
|
||||||
libopenmpt._openmpt_module_destroy(this.modulePtr);
|
processNode.player.libopenmpt._openmpt_module_destroy(this.modulePtr);
|
||||||
this.modulePtr = 0;
|
this.modulePtr = 0;
|
||||||
}
|
}
|
||||||
if (this.leftBufferPtr !== 0) {
|
if (this.leftBufferPtr !== 0) {
|
||||||
libopenmpt._free(this.leftBufferPtr);
|
processNode.player.libopenmpt._free(this.leftBufferPtr);
|
||||||
this.leftBufferPtr = 0;
|
this.leftBufferPtr = 0;
|
||||||
}
|
}
|
||||||
if (this.rightBufferPtr !== 0) {
|
if (this.rightBufferPtr !== 0) {
|
||||||
libopenmpt._free(this.rightBufferPtr);
|
processNode.player.libopenmpt._free(this.rightBufferPtr);
|
||||||
this.rightBufferPtr = 0;
|
this.rightBufferPtr = 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -229,8 +316,14 @@ ChiptuneJsPlayer.prototype.createLibopenmptNode = function (buffer, config: obje
|
||||||
let ended = false;
|
let ended = false;
|
||||||
let error = false;
|
let error = false;
|
||||||
|
|
||||||
const currentPattern = libopenmpt._openmpt_module_get_current_pattern(this.modulePtr);
|
const currentPattern =
|
||||||
const currentRow = libopenmpt._openmpt_module_get_current_row(this.modulePtr);
|
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) {
|
if (currentPattern !== this.patternIndex) {
|
||||||
processNode.player.fireEvent('onPatternChange');
|
processNode.player.fireEvent('onPatternChange');
|
||||||
}
|
}
|
||||||
|
@ -238,14 +331,27 @@ ChiptuneJsPlayer.prototype.createLibopenmptNode = function (buffer, config: obje
|
||||||
|
|
||||||
while (framesToRender > 0) {
|
while (framesToRender > 0) {
|
||||||
const framesPerChunk = Math.min(framesToRender, maxFramesPerChunk);
|
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) {
|
if (actualFramesPerChunk === 0) {
|
||||||
ended = true;
|
ended = true;
|
||||||
// modulePtr will be 0 on openmpt: error: openmpt_module_read_float_stereo: ERROR: module * not valid or other openmpt error
|
// modulePtr will be 0 on openmpt: error: openmpt_module_read_float_stereo: ERROR: module * not valid or other openmpt error
|
||||||
error = !this.modulePtr;
|
error = !this.modulePtr;
|
||||||
}
|
}
|
||||||
const rawAudioLeft = libopenmpt.HEAPF32.subarray(this.leftBufferPtr / 4, this.leftBufferPtr / 4 + actualFramesPerChunk);
|
const rawAudioLeft = processNode.player.libopenmpt.HEAPF32.subarray(
|
||||||
const rawAudioRight = libopenmpt.HEAPF32.subarray(this.rightBufferPtr / 4, this.rightBufferPtr / 4 + actualFramesPerChunk);
|
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) {
|
for (let i = 0; i < actualFramesPerChunk; ++i) {
|
||||||
outputL[framesRendered + i] = rawAudioLeft[i];
|
outputL[framesRendered + i] = rawAudioLeft[i];
|
||||||
outputR[framesRendered + i] = rawAudioRight[i];
|
outputR[framesRendered + i] = rawAudioRight[i];
|
||||||
|
@ -260,7 +366,9 @@ ChiptuneJsPlayer.prototype.createLibopenmptNode = function (buffer, config: obje
|
||||||
if (ended) {
|
if (ended) {
|
||||||
this.disconnect();
|
this.disconnect();
|
||||||
this.cleanup();
|
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;
|
return processNode;
|
||||||
|
|
|
@ -774,6 +774,9 @@ importers:
|
||||||
katex:
|
katex:
|
||||||
specifier: 0.16.9
|
specifier: 0.16.9
|
||||||
version: 0.16.9
|
version: 0.16.9
|
||||||
|
libopenmpt-wasm:
|
||||||
|
specifier: github:TheEssem/libopenmpt-packaging#build
|
||||||
|
version: github.com/TheEssem/libopenmpt-packaging/d05d151a72b638c6312227af0417aca69521172c
|
||||||
matter-js:
|
matter-js:
|
||||||
specifier: 0.19.0
|
specifier: 0.19.0
|
||||||
version: 0.19.0
|
version: 0.19.0
|
||||||
|
@ -20493,6 +20496,12 @@ packages:
|
||||||
readable-stream: 3.6.0
|
readable-stream: 3.6.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
github.com/TheEssem/libopenmpt-packaging/d05d151a72b638c6312227af0417aca69521172c:
|
||||||
|
resolution: {tarball: https://codeload.github.com/TheEssem/libopenmpt-packaging/tar.gz/d05d151a72b638c6312227af0417aca69521172c}
|
||||||
|
name: libopenmpt-wasm
|
||||||
|
version: 0.7.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
github.com/aiscript-dev/aiscript-vscode/b5a8aa0ad927831a0b867d1c183460a14e6c48cd:
|
github.com/aiscript-dev/aiscript-vscode/b5a8aa0ad927831a0b867d1c183460a14e6c48cd:
|
||||||
resolution: {tarball: https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/b5a8aa0ad927831a0b867d1c183460a14e6c48cd}
|
resolution: {tarball: https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/b5a8aa0ad927831a0b867d1c183460a14e6c48cd}
|
||||||
name: aiscript-vscode
|
name: aiscript-vscode
|
||||||
|
|
Loading…
Reference in a new issue