Compare commits

..

6 commits

Author SHA1 Message Date
98a845eda4 resolve merge conflicts 2022-12-29 21:51:57 +01:00
3a9d283630
client: make mod patterns display when seeking on stopped track 2022-12-29 21:43:34 +01:00
ed27f61a4d
client: add mod tracker
Squashed commit of the following:

commit 54f0b67b25
Author: Puniko <me@absturztaube.ch>
Date:   Thu Dec 29 21:27:15 2022 +0100

    use nextTick instead of setTimeout

commit 6998cae7e3
Author: Puniko <me@absturztaube.ch>
Date:   Thu Dec 29 21:14:55 2022 +0100

    my absolute terrible fix to the unhide issue

commit 79f546d150
Author: Puniko <me@absturztaube.ch>
Date:   Thu Dec 29 21:01:35 2022 +0100

    stop player on hide/unhide

commit 6b7f13e8ef
Author: Puniko <me@absturztaube.ch>
Date:   Thu Dec 29 10:36:59 2022 +0100

    make webkit style range slider the same

commit 8a267c5cdc
Author: Puniko <me@absturztaube.ch>
Date:   Thu Dec 29 01:16:18 2022 +0100

    restyling range inputs

commit c39e1671b2
Author: Puniko <me@absturztaube.ch>
Date:   Thu Dec 29 00:57:47 2022 +0100

    make module seekable

commit c1762f27ae
Author: Puniko <me@absturztaube.ch>
Date:   Thu Dec 29 00:14:35 2022 +0100

    remove accesskey attribs

commit 08f75a01f1
Author: Puniko <me@absturztaube.ch>
Date:   Thu Dec 29 00:12:23 2022 +0100

    v-else on play button

commit 9302a9faaa
Author: Puniko <me@absturztaube.ch>
Date:   Thu Dec 29 00:08:19 2022 +0100

    replace filter with some

commit bffd15daed
Author: Puniko <me@absturztaube.ch>
Date:   Wed Dec 28 09:13:20 2022 +0100

    add chiptune2 and libopenmpt into COPYING

commit 794298c21c
Author: Puniko <me@absturztaube.ch>
Date:   Tue Dec 27 15:32:43 2022 +0100

    little cleanup

commit f383aec1cd
Author: Puniko <me@absturztaube.ch>
Date:   Tue Dec 27 15:23:25 2022 +0100

    repeat only once and proper handling of track ending

commit fdaa9614c9
Author: Puniko <me@absturztaube.ch>
Date:   Tue Dec 27 14:52:20 2022 +0100

    prevent losing connection when downloading module

commit 6c5723c795
Author: Puniko <me@absturztaube.ch>
Date:   Tue Dec 27 14:45:59 2022 +0100

    colours!!! 🌈

commit dba4f0a4a9
Author: Puniko <me@absturztaube.ch>
Date:   Tue Dec 27 13:01:06 2022 +0100

    replace  with i18n

commit 4234dfbdbc
Author: Puniko <me@absturztaube.ch>
Date:   Mon Dec 26 15:47:10 2022 +0100

    retab

commit 0cc1ea8c3e
Author: Puniko <me@absturztaube.ch>
Date:   Mon Dec 26 15:19:28 2022 +0100

    include libopenmpt tracker to foundkey

commit c2437c696a
Author: Puniko <me@absturztaube.ch>
Date:   Mon Dec 26 12:08:49 2022 +0100

    add libopenmpt

Reviewed-on: FoundKeyGang/FoundKey#306
Changelog: Added
2022-12-29 21:36:44 +01:00
ed9d4023d4 backend: add argon2 support
Passwords will be automatically re-hashed on sign-in.
All new password hashes will be argon2 by default.

This uses argon2id and is not configurable.
In the very unlikely case someone has more specific needs,
a fork is recommended.

ChangeLog: Added

Co-authored-by: Chloe Kudryavtsev <code@toast.bunkerlabs.net>
Reviewed-on: FoundKeyGang/FoundKey#308
2022-12-29 20:13:47 +00:00
76a8e000a3
client: only catch erroneous key errors in i18n.ts 2022-12-27 21:47:34 +01:00
a673647fba
server: remove avatarColor and bannerColor properties
According to comments next to those properties, they were kept for backward compatibility.
However they were always being set to null.

Changelog: Removed
2022-12-26 18:52:16 +01:00
15 changed files with 419 additions and 157 deletions

3
.gitignore vendored
View file

@ -13,6 +13,9 @@
# vim
/.vimlocal
# vimlocal
.vimlocal
# Node.js
node_modules
report.*.json

View file

@ -13,3 +13,11 @@ https://github.com/muan/emojilib/blob/master/LICENSE
RsaSignature2017 implementation by Transmute Industries Inc
License: MIT
https://github.com/transmute-industries/RsaSignature2017/blob/master/LICENSE
Chiptune2.js by Simon Gündling
License: MIT
https://github.com/deskjet/chiptune2.js#license
libopenmpt (as part of openmpt) by OpenMPT
License: BSD 3-Clause
https://github.com/OpenMPT/openmpt/blob/master/LICENSE

View file

@ -35,6 +35,7 @@
"lodash": "^4.17.21"
},
"dependencies": {
"argon2": "^0.30.2",
"execa": "5.1.1",
"gulp": "4.0.2",
"gulp-cssnano": "2.1.3",

View file

@ -1,10 +1,17 @@
import bcrypt from 'bcryptjs';
import * as argon2 from 'argon2';
export async function hashPassword(password: string): Promise<string> {
const salt = await bcrypt.genSalt(8);
return await bcrypt.hash(password, salt);
return argon2.hash(password);
}
export async function comparePassword(password: string, hash: string): Promise<boolean> {
return await bcrypt.compare(password, hash);
if (isOldAlgorithm(hash)) return bcrypt.compare(password, hash);
return argon2.verify(hash, password);
}
export function isOldAlgorithm(hash: string): boolean {
// bcrypt hashes start with $2[ab]$
return hash.startsWith('$2');
}

View file

@ -307,7 +307,6 @@ export const UserRepository = db.getRepository(User).extend({
host: user.host,
avatarUrl: this.getAvatarUrlSync(user),
avatarBlurhash: user.avatar?.blurhash || null,
avatarColor: null, // 後方互換性のため
isAdmin: user.isAdmin || falsy,
isModerator: user.isModerator || falsy,
isBot: user.isBot || falsy,
@ -332,7 +331,6 @@ export const UserRepository = db.getRepository(User).extend({
lastFetchedAt: user.lastFetchedAt ? user.lastFetchedAt.toISOString() : null,
bannerUrl: user.banner ? DriveFiles.getPublicUrl(user.banner, false) : null,
bannerBlurhash: user.banner?.blurhash || null,
bannerColor: null, // 後方互換性のため
isLocked: user.isLocked,
isSilenced: user.isSilenced || falsy,
isSuspended: user.isSuspended || falsy,

View file

@ -8,7 +8,7 @@ import { Users, Signins, UserProfiles, UserSecurityKeys, AttestationChallenges }
import { ILocalUser } from '@/models/entities/user.js';
import { genId } from '@/misc/gen-id.js';
import { getIpHash } from '@/misc/get-ip-hash.js';
import { comparePassword } from '@/misc/password.js';
import { comparePassword, hashPassword, isOldAlgorithm } from '@/misc/password.js';
import signin from '../common/signin.js';
import { verifyLogin, hash } from '../2fa.js';
import { limiter } from '../limiter.js';
@ -69,6 +69,11 @@ export default async (ctx: Koa.Context) => {
// Compare password
const same = await comparePassword(password, profile.password!);
if (same && isOldAlgorithm(profile.password!)) {
profile.password = await hashPassword(password);
await UserProfiles.save(profile);
}
async function fail(): void {
// Append signin history
await Signins.insert({

View file

@ -36,6 +36,7 @@ html
link(rel='prefetch' href=config.images.error)
link(rel='stylesheet' href='/assets/fontawesome/css/all.css')
link(rel='modulepreload' href=`/assets/${clientEntry.file}`)
script(src='/client-assets/libopenmpt.js')
each href in clientEntry.css
link(rel='preload' href=`/assets/${href}` as='style')
@ -63,6 +64,7 @@ html
script.
var VERSION = "#{version}";
var CLIENT_ENTRY = "#{clientEntry.file}";
window.libopenmpt = window.Module;
body
noscript: p

File diff suppressed because one or more lines are too long

Binary file not shown.

View file

@ -5,9 +5,9 @@
<div ref="gallery" :data-count="mediaList.filter(media => previewable(media)).length">
<template v-for="media in mediaList.filter(media => previewable(media))">
<XVideo v-if="media.type.startsWith('video')" :key="media.id" :video="media"/>
<XModPlayer :module="media" :key="media.id" v-else-if="media.name.endsWith('.mod') || media.name.endsWith('.xm') || media.name.endsWith('.s3m') || media.name.endsWith('.it')"/>
<XAnsi :ansi-file="media" :key="media.id" v-else-if="media.name.endsWith('.ans')"/>
<XAnsi :ansi-file="media" :key="media.id" v-else-if="media.name.endsWith('.ans')"/>
<XImage v-else-if="media.type.startsWith('image')" :key="media.id" class="image" :data-id="media.id" :image="media" :raw="raw"/>
<XModPlayer v-else-if="isModule(media)" :key="media.id" :module="media"/>
</template>
</div>
</div>
@ -25,8 +25,7 @@ import XImage from './media-image.vue';
import XVideo from './media-video.vue';
import XModPlayer from './mod-player.vue';
import XAnsi from './media-ansi.vue';
import { FILE_TYPE_BROWSERSAFE } from '@/const';
import { FILE_TYPE_BROWSERSAFE, FILE_EXT_TRACKER_MODULES } from '@/const';
const props = defineProps<{
mediaList: foundkey.entities.DriveFile[];
@ -127,11 +126,18 @@ onMounted(() => {
const previewable = (file: foundkey.entities.DriveFile): boolean => {
if (file.type === 'image/svg+xml') return true; // svgwebpublic/thumbnailpngtrue
if (file.name.endsWith(".mod") || file.name.endsWith(".xm") || file.name.endsWith(".s3m") || file.name.endsWith(".it")) return true;
if (file.name.endsWith(".ans")) return true;
if (file.name.endsWith(".ans")) return true;
// FILE_TYPE_BROWSERSAFE
if (isModule(file)) return true;
return (file.type.startsWith('video') || file.type.startsWith('image')) && FILE_TYPE_BROWSERSAFE.includes(file.type);
};
const isModule = (file: foundkey.entities.DriveFile): boolean => {
return FILE_EXT_TRACKER_MODULES.some((ext) => {
return file.name.toLowerCase().endsWith("." + ext);
});
};
</script>
<style lang="scss" scoped>

View file

@ -1,127 +1,230 @@
<template>
<div class="mod-player-disabled" v-if="hide" @click="hide = false">
<div class="mod-player-disabled" v-if="hide" @click="toggleVisible()">
<div>
<b><i class="fas fa-exclamation-triangle"></i> {{ $ts.sensitive }}</b>
<span>{{ $ts.clickToShow }}</span>
<b><i class="fas fa-exlamation-triangle"></i> {{ i18n.ts.sensitive }}</b>
<span>{{ i18n.ts.clickToShow }}</span>
</div>
</div>
<div class="mod-player-enabled" v-else>
<div class="pattern-display">
<canvas class="pattern-canvas" ref="display"></canvas>
<canvas class="pattern-canvas" ref="displayCanvas"></canvas>
</div>
<div class="controls">
<button class="play" title="play" accesskey="P" @click="playPause()">
<i class="fas fa-play" v-if="!playing"></i>
<button class="play" @click="playPause()">
<i class="fas fa-pause" v-if="playing"></i>
<i class="fas fa-play" v-else></i>
</button>
<button class="stop" title="stop" accesskey="X" @click="stop()">
<button class="stop" @click="stop()">
<i class="fas fa-stop"></i>
</button>
<progress min="0" max="100" value="0" ref="progress"></progress>
<input type="range" min="0" max="1" v-model="player.context.gain.value" step="0.1" ref="volume" title="volume"/>
<a class="download" title="download" :href="module.url">
<input class="progress" type="range" min="0" max="1" v-model="position" step="0.1" ref="progress" @mousedown="initSeek()" @mouseup="performSeek()"/>
<input type="range" min="0" max="1" v-model="player.context.gain.value" step="0.1"/>
<a class="download" :title="i18n.ts.download" :href="module.url" target="_blank">
<i class="fas fa-download"></i>
</a>
</div>
<i class="fas fa-eye-slash" @click="hide = true"></i>
<i class="fas fa-eye-slash" @click="toggleVisible()"></i>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import * as os from '@client/os';
import { ChiptuneJsPlayer, ChiptuneJsConfig } from '../scripts/chiptune2.ts';
<script lang="ts" setup>
import { ref, nextTick } from 'vue';
import * as foundkey from 'foundkey-js';
import { i18n } from '@/i18n';
import { defaultStore } from '@/store';
import { ChiptuneJsPlayer, ChiptuneJsConfig } from '@/scripts/chiptune2';
export default defineComponent({
props: {
module: {
type: Object,
required: true
}
const CHAR_WIDTH = 6;
const CHAR_HEIGHT = 12;
const ROW_OFFSET_Y = 10;
const colours = {
background: '#000000',
default: {
active: '#ffffff',
inactive: '#808080'
},
data() {
return {
hide: true,
playing: false
};
quarter: {
active: '#ffff00',
inactive: '#ffe135'
},
created() {
this.rowBuffer = 24;
this.hide = (this.$store.state.nsfw === 'force') ? true : this.module.isSensitive && (this.$store.state.nsfw !== 'ignore');
this.player = new ChiptuneJsPlayer(new ChiptuneJsConfig());
this.buffer = null;
this.player.load(this.module.url).then((result) => {
this.buffer = result;
this.player.play(this.buffer);
this.display();
this.player.stop();
}).catch((error) => {
console.error(error);
});
instr: {
active: '#80e0ff',
inactive: '#0099cc'
},
methods: {
display () {
if (this.$refs.display === null) {
this.stop();
return;
}
const pattern = this.player.getPattern() || 0;
const row = this.player.getRow() || 0;
const nbChannels = this.player.currentPlayingNode.nbChannels;
if (this.$refs.display.width !== 12 + 84 * nbChannels + 2) {
this.$refs.display.width = 12 + 84 * nbChannels + 2;
this.$refs.display.height = 12 * this.rowBuffer;
}
const nbRows = this.player.getPatternNumRows(pattern);
const ctx = this.$refs.display.getContext('2d');
ctx.font = '10px monospace';
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, this.$refs.display.width, this.$refs.display.height);
ctx.fillStyle = 'gray';
for (let rowOffset = 0; rowOffset < this.rowBuffer; rowOffset++) {
const rowToDraw = row - this.rowBuffer / 2 + rowOffset;
if (rowToDraw >= 0 && rowToDraw < nbRows) {
let rowText = parseInt(rowToDraw).toString(16);
if (rowText.length === 1) {
rowText = '0' + rowText;
}
ctx.fillStyle = 'gray';
if (rowToDraw === row) {
ctx.fillStyle = 'white';
}
ctx.fillText(rowText, 0, 10 + rowOffset * 12);
for (let channel = 0; channel < nbChannels; channel++) {
const part = this.player.getPatternRowChannel(pattern, rowToDraw, channel);
ctx.fillText("|" + part, 12 + 84 * channel, 10 + rowOffset * 12);
}
}
}
},
playPause() {
this.player.addHandler('onRowChange', () => {
this.$refs.progress.max = this.player.duration();
this.$refs.progress.value = this.player.position() % this.player.duration();
this.display(this.player);
});
if (this.player.currentPlayingNode === null) {
this.player.play(this.buffer);
this.playing = true;
} else {
this.player.togglePause();
this.playing = !this.player.currentPlayingNode.paused;
}
},
stop() {
this.player.stop();
this.playing = false;
this.player.play(this.buffer);
this.display();
this.player.stop();
this.$refs.progress.value = 0;
this.player.handlers = [];
}
},
volume: {
active: '#80ff80',
inactive: '#008000'
},
fx: {
active: '#ff80e0',
inactive: '#800060'
},
operant: {
active: '#ffe080',
inactive: '#806000'
}
};
const props = defineProps<{
module: foundkey.entities.DriveFile
}>();
let hide = ref((defaultStore.state.nsfw === 'force') ? true : props.module.isSensitive && (defaultStore.state.nsfw !== 'ignore'));
let playing = ref(false);
let displayCanvas = ref<HTMLCanvasElement>(null);
let progress = ref<HTMLProgressElement>(null);
let position = ref(0);
const player = ref(new ChiptuneJsPlayer(new ChiptuneJsConfig()));
const rowBuffer = 24;
let buffer = null;
let isSeeking = false;
player.value.load(props.module.url).then((result) => {
buffer = result;
try {
player.value.play(buffer);
progress.value.max = player.value.duration();
display();
} catch (e) {
console.warn(e);
}
player.value.stop();
}).catch((error) => {
console.error(error);
});
function playPause() {
player.value.addHandler('onRowChange', () => {
progress.value.max = player.value.duration();
if (!isSeeking) {
position.value = player.value.position() % player.value.duration();
}
display();
});
player.value.addHandler('onEnded', () => {
stop();
});
if (player.value.currentPlayingNode === null) {
player.value.play(buffer);
player.value.seek(position.value);
playing.value = true;
} else {
player.value.togglePause();
playing.value = !player.value.currentPlayingNode.paused;
}
}
function stop(noDisplayUpdate = false) {
player.value.stop();
playing.value = false;
if (!noDisplayUpdate) {
try {
player.value.play(buffer);
display();
} catch (e) {
console.warn(e);
}
}
player.value.stop();
position.value = 0;
player.value.handlers = [];
}
function initSeek() {
isSeeking = true;
}
function performSeek() {
const noNode = !player.value.currentPlayingNode;
if (noNode) {
player.value.play(buffer);
}
player.value.seek(position.value);
display();
if (noNode) {
player.value.stop();
}
isSeeking = false;
}
function toggleVisible() {
hide.value = !hide.value;
nextTick(() => { stop(hide.value); });
}
function display() {
if (!displayCanvas.value) {
stop();
return;
}
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');
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);
}
}
}
}
</script>
<style lang="scss" scoped>
@ -146,17 +249,17 @@ export default defineComponent({
}
> .pattern-display {
width: 100%;
height: 100%;
width: 100%;
height: 100%;
overflow-x: scroll;
overflow-y: hidden;
background-color: black;
background-color: black;
text-align: center;
.pattern-canvas {
background-color: black;
height: 100%;
}
}
}
> .controls {
display: flex;
@ -171,7 +274,8 @@ export default defineComponent({
border: none;
background-color: transparent;
color: var(--accent);
cursor: pointer;
&:hover {
background-color: var(--fg);
}
@ -183,10 +287,11 @@ export default defineComponent({
width: 90px;
padding: 0;
margin: 4px 8px;
overflow-x: hidden;
&:focus {
outline: none;
&::-webkit-slider-runnable-track {
background: var(--bg);
}
@ -204,6 +309,7 @@ export default defineComponent({
animate: 0.2s;
background: var(--bg);
border: 1px solid var(--fg);
overflow-x: hidden;
}
&::-webkit-slider-thumb {
@ -211,10 +317,12 @@ export default defineComponent({
height: 100%;
width: 14px;
border-radius: 0;
background: var(--accent);
background: var(--accentLighten);
cursor: pointer;
-webkit-appearance: none;
margin-top: -0.5px;
box-shadow: calc(-100vw - 14px) 0 0 100vw var(--accent);
clip-path: polygon(1px 0, 100% 0, 100% 100%, 1px 100%, 1px calc(50% + 10.5px), -100vw calc(50% + 10.5px), -100vw calc(50% - 10.5px), 0 calc(50% - 10.5px));
z-index: 1;
}
&::-moz-range-track {
@ -227,13 +335,19 @@ export default defineComponent({
border: 1px solid var(--fg);
}
&::-moz-range-progress {
cursor: pointer;
height: 100%;
background: var(--accent);
}
&::-moz-range-thumb {
border: none;
height: 100%;
border-radius: 0;
width: 14px;
background: var(--accent);
cursoer: pointer;
background: var(--accentLighten);
cursor: pointer;
}
&::-ms-track {
@ -247,7 +361,13 @@ export default defineComponent({
color: transparent;
}
&::-ms-fill-lower, &::-ms-fill-upper {
&::-ms-fill-lower {
background: var(--accent);
border: 1px solid var(--fg);
border-radius: 0;
}
&::-ms-fill-upper {
background: var(--bg);
border: 1px solid var(--fg);
border-radius: 0;
@ -259,19 +379,13 @@ export default defineComponent({
height: 100%;
width: 14px;
border-radius: 0;
background: var(--accent);
background: var(--accentLighten);
cursor: pointer;
}
}
> progress {
padding: unset;
margin: 4px 8px;
flex-grow: 1;
background-color: var(--bg);
&::-moz-progress-bar, &::-webkit-progress-value {
background-color: var(--accent);
&.progress {
flex-grow: 1;
min-width: 0;
}
}
}

View file

@ -46,6 +46,44 @@ export const FILE_TYPE_BROWSERSAFE = [
'audio/x-flac',
'audio/vnd.wave',
];
export const FILE_EXT_TRACKER_MODULES = [
'mod',
's3m',
'xm',
'it',
'mptm',
'stm',
'nst',
'm15',
'stk',
'wow',
'ult',
'669',
'mtm',
'med',
'far',
'mdl',
'ams',
'dsm',
'amf',
'okt',
'dmf',
'ptm',
'psm',
'mt2',
'dbm',
'digi',
'imf',
'j2b',
'gdm',
'umx',
'plm',
'mo3',
'xpk',
'ppm',
'mmcmp'
];
/*
https://github.com/sindresorhus/file-type/blob/main/supported.js
https://github.com/sindresorhus/file-type/blob/main/core.js

View file

@ -20,23 +20,24 @@ class I18n<T extends Record<string, any>> {
// If a pattern is present in the string but not provided in args, it will not be replaced.
// If `args` is not provided, no interpolation is performed.
public t(key: string, args?: Record<string, string | number>): string {
let str;
try {
// Resolve dot-delimited names as properties of objects.
let str = key.split('.').reduce((o, i) => o[i], this.ts) as unknown as string;
// Perform string interpolation.
if (args) {
for (const [k, v] of Object.entries(args)) {
str = str.replace(`{${k}}`, v.toString());
}
}
return str;
str = key.split('.').reduce((o, i) => o[i], this.ts) as unknown as string;
} catch (err) {
// This should normally not happen because of the English language fallback strings, see comment for ts member.
console.warn(`missing localization '${key}'`);
return key;
}
// Perform string interpolation.
if (args) {
for (const [k, v] of Object.entries(args)) {
str = str.replace(`{${k}}`, v.toString());
}
}
return str;
}
}

View file

@ -109,12 +109,13 @@ ChiptuneJsPlayer.prototype.load = function (input) {
};
ChiptuneJsPlayer.prototype.play = 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 || 1);
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);

101
yarn.lock
View file

@ -1051,6 +1051,25 @@ __metadata:
languageName: node
linkType: hard
"@mapbox/node-pre-gyp@npm:^1.0.10":
version: 1.0.10
resolution: "@mapbox/node-pre-gyp@npm:1.0.10"
dependencies:
detect-libc: ^2.0.0
https-proxy-agent: ^5.0.0
make-dir: ^3.1.0
node-fetch: ^2.6.7
nopt: ^5.0.0
npmlog: ^5.0.1
rimraf: ^3.0.2
semver: ^7.3.5
tar: ^6.1.11
bin:
node-pre-gyp: bin/node-pre-gyp
checksum: 1a98db05d955b74dad3814679593df293b9194853698f3f5f1ed00ecd93128cdd4b14fb8767fe44ac6981ef05c23effcfdc88710e7c1de99ccb6f647890597c8
languageName: node
linkType: hard
"@microsoft/api-extractor-model@npm:7.23.3":
version: 7.23.3
resolution: "@microsoft/api-extractor-model@npm:7.23.3"
@ -1203,6 +1222,13 @@ __metadata:
languageName: node
linkType: hard
"@phc/format@npm:^1.0.0":
version: 1.0.0
resolution: "@phc/format@npm:1.0.0"
checksum: 15ee02504fbc16590923d89b1f1c2f5892df27cf2bf19180e5678511413e87b6e5355815a092749cd01698855ee5a0fc5d2393951c727acd650934eed290e26e
languageName: node
linkType: hard
"@redis/bloom@npm:1.0.2":
version: 1.0.2
resolution: "@redis/bloom@npm:1.0.2"
@ -3177,6 +3203,16 @@ __metadata:
languageName: node
linkType: hard
"are-we-there-yet@npm:^2.0.0":
version: 2.0.0
resolution: "are-we-there-yet@npm:2.0.0"
dependencies:
delegates: ^1.0.0
readable-stream: ^3.6.0
checksum: 6c80b4fd04ecee6ba6e737e0b72a4b41bdc64b7d279edfc998678567ff583c8df27e27523bc789f2c99be603ffa9eaa612803da1d886962d2086e7ff6fa90c7c
languageName: node
linkType: hard
"are-we-there-yet@npm:^3.0.0":
version: 3.0.1
resolution: "are-we-there-yet@npm:3.0.1"
@ -3194,6 +3230,17 @@ __metadata:
languageName: node
linkType: hard
"argon2@npm:^0.30.2":
version: 0.30.2
resolution: "argon2@npm:0.30.2"
dependencies:
"@mapbox/node-pre-gyp": ^1.0.10
"@phc/format": ^1.0.0
node-addon-api: ^5.0.0
checksum: 5b0d680d2bb482ed5f46ae2933ff2dc5c1d5d2a984a5c81c63cb311b55a5f67393e2b6da1adf4a1342e146580dd3f888a695d1c56834df710a141c62e9f73ef7
languageName: node
linkType: hard
"argparse@npm:^1.0.7, argparse@npm:~1.0.9":
version: 1.0.10
resolution: "argparse@npm:1.0.10"
@ -4995,7 +5042,7 @@ __metadata:
languageName: node
linkType: hard
"color-support@npm:^1.1.3":
"color-support@npm:^1.1.2, color-support@npm:^1.1.3":
version: 1.1.3
resolution: "color-support@npm:1.1.3"
bin:
@ -5191,7 +5238,7 @@ __metadata:
languageName: node
linkType: hard
"console-control-strings@npm:^1.1.0":
"console-control-strings@npm:^1.0.0, console-control-strings@npm:^1.1.0":
version: 1.1.0
resolution: "console-control-strings@npm:1.1.0"
checksum: 8755d76787f94e6cf79ce4666f0c5519906d7f5b02d4b884cf41e11dcd759ed69c57da0670afd9236d229a46e0f9cf519db0cd829c6dca820bb5a5c3def584ed
@ -7842,6 +7889,7 @@ __metadata:
"@types/gulp": 4.0.9
"@types/gulp-rename": 2.0.1
"@typescript-eslint/parser": ^5.46.1
argon2: ^0.30.2
cross-env: 7.0.3
cypress: 10.3.0
execa: 5.1.1
@ -8022,6 +8070,23 @@ __metadata:
languageName: node
linkType: hard
"gauge@npm:^3.0.0":
version: 3.0.2
resolution: "gauge@npm:3.0.2"
dependencies:
aproba: ^1.0.3 || ^2.0.0
color-support: ^1.1.2
console-control-strings: ^1.0.0
has-unicode: ^2.0.1
object-assign: ^4.1.1
signal-exit: ^3.0.0
string-width: ^4.2.3
strip-ansi: ^6.0.1
wide-align: ^1.1.2
checksum: 81296c00c7410cdd48f997800155fbead4f32e4f82109be0719c63edc8560e6579946cc8abd04205297640691ec26d21b578837fd13a4e96288ab4b40b1dc3e9
languageName: node
linkType: hard
"gauge@npm:^4.0.3":
version: 4.0.4
resolution: "gauge@npm:4.0.4"
@ -11515,7 +11580,7 @@ __metadata:
languageName: node
linkType: hard
"make-dir@npm:^3.0.0":
"make-dir@npm:^3.0.0, make-dir@npm:^3.1.0":
version: 3.1.0
resolution: "make-dir@npm:3.1.0"
dependencies:
@ -12326,7 +12391,7 @@ __metadata:
languageName: node
linkType: hard
"node-fetch@npm:2.6.7, node-fetch@npm:^2.6.1":
"node-fetch@npm:2.6.7, node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.7":
version: 2.6.7
resolution: "node-fetch@npm:2.6.7"
dependencies:
@ -12567,6 +12632,18 @@ __metadata:
languageName: node
linkType: hard
"npmlog@npm:^5.0.1":
version: 5.0.1
resolution: "npmlog@npm:5.0.1"
dependencies:
are-we-there-yet: ^2.0.0
console-control-strings: ^1.1.0
gauge: ^3.0.0
set-blocking: ^2.0.0
checksum: 516b2663028761f062d13e8beb3f00069c5664925871a9b57989642ebe09f23ab02145bf3ab88da7866c4e112cafff72401f61a672c7c8a20edc585a7016ef5f
languageName: node
linkType: hard
"npmlog@npm:^6.0.0":
version: 6.0.2
resolution: "npmlog@npm:6.0.2"
@ -15329,6 +15406,13 @@ __metadata:
languageName: node
linkType: hard
"signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.7":
version: 3.0.7
resolution: "signal-exit@npm:3.0.7"
checksum: a2f098f247adc367dffc27845853e9959b9e88b01cb301658cfe4194352d8d2bb32e18467c786a7fe15f1d44b233ea35633d076d5e737870b7139949d1ab6318
languageName: node
linkType: hard
"signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3":
version: 3.0.3
resolution: "signal-exit@npm:3.0.3"
@ -15336,13 +15420,6 @@ __metadata:
languageName: node
linkType: hard
"signal-exit@npm:^3.0.7":
version: 3.0.7
resolution: "signal-exit@npm:3.0.7"
checksum: a2f098f247adc367dffc27845853e9959b9e88b01cb301658cfe4194352d8d2bb32e18467c786a7fe15f1d44b233ea35633d076d5e737870b7139949d1ab6318
languageName: node
linkType: hard
"simple-concat@npm:^1.0.0":
version: 1.0.1
resolution: "simple-concat@npm:1.0.1"
@ -17771,7 +17848,7 @@ __metadata:
languageName: node
linkType: hard
"wide-align@npm:^1.1.5":
"wide-align@npm:^1.1.2, wide-align@npm:^1.1.5":
version: 1.1.5
resolution: "wide-align@npm:1.1.5"
dependencies: