sonolus-llsif-engine 1.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/EngineConfiguration +0 -0
- package/dist/EngineData +0 -0
- package/dist/Resource.cjs +20 -0
- package/dist/Resource.d.cts +9 -0
- package/dist/index.cjs +44 -0
- package/dist/index.d.cts +31 -0
- package/dist/nss/convert.cjs +92 -0
- package/dist/nss/convert.d.cts +3 -0
- package/dist/nss/index.cjs +11 -0
- package/dist/nss/index.d.cts +18 -0
- package/dist/sifc/convert.cjs +164 -0
- package/dist/sifc/convert.d.cts +3 -0
- package/dist/sifc/index.cjs +2 -0
- package/dist/sifc/index.d.cts +25 -0
- package/dist/thumbnail.png +0 -0
- package/package.json +33 -0
|
Binary file
|
package/dist/EngineData
ADDED
|
Binary file
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Resource = void 0;
|
|
4
|
+
const node_crypto_1 = require("node:crypto");
|
|
5
|
+
const node_fs_1 = require("node:fs");
|
|
6
|
+
const node_path_1 = require("node:path");
|
|
7
|
+
class Resource {
|
|
8
|
+
constructor(path) {
|
|
9
|
+
this.path = (0, node_path_1.resolve)(__dirname, path);
|
|
10
|
+
}
|
|
11
|
+
get hash() {
|
|
12
|
+
this._hash ??= (0, node_crypto_1.createHash)('sha1').update(this.buffer).digest('hex');
|
|
13
|
+
return this._hash;
|
|
14
|
+
}
|
|
15
|
+
get buffer() {
|
|
16
|
+
this._buffer ??= (0, node_fs_1.readFileSync)(this.path);
|
|
17
|
+
return this._buffer;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.Resource = Resource;
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.engineThumbnail = exports.engineData = exports.engineConfiguration = exports.engineInfo = exports.version = exports.sifcToLevelData = exports.nssToSIFC = void 0;
|
|
4
|
+
const Resource_cjs_1 = require("./Resource.cjs");
|
|
5
|
+
var convert_cjs_1 = require("./nss/convert.cjs");
|
|
6
|
+
Object.defineProperty(exports, "nssToSIFC", { enumerable: true, get: function () { return convert_cjs_1.nssToSIFC; } });
|
|
7
|
+
var convert_cjs_2 = require("./sifc/convert.cjs");
|
|
8
|
+
Object.defineProperty(exports, "sifcToLevelData", { enumerable: true, get: function () { return convert_cjs_2.sifcToLevelData; } });
|
|
9
|
+
exports.version = '1.0.0-beta.0';
|
|
10
|
+
exports.engineInfo = {
|
|
11
|
+
name: 'llsif',
|
|
12
|
+
version: 8,
|
|
13
|
+
title: {
|
|
14
|
+
en: 'Love Live!',
|
|
15
|
+
ja: 'ラブライブ!',
|
|
16
|
+
ko: '러브라이브',
|
|
17
|
+
zhs: 'Love Live!',
|
|
18
|
+
zht: 'Love Live!',
|
|
19
|
+
},
|
|
20
|
+
subtitle: {
|
|
21
|
+
en: 'Love Live! School idol festival',
|
|
22
|
+
ja: 'ラブライブ!スクールアイドルフェスティバル',
|
|
23
|
+
ko: '러브라이브 스쿨 아이돌 페스티벌',
|
|
24
|
+
zhs: 'Love Live! 学园偶像祭',
|
|
25
|
+
zht: 'Love Live! 學園偶像祭',
|
|
26
|
+
},
|
|
27
|
+
author: {
|
|
28
|
+
en: 'Burrito',
|
|
29
|
+
},
|
|
30
|
+
description: {
|
|
31
|
+
en: [
|
|
32
|
+
'A recreation of Love Live! School idol festival engine in Sonolus.',
|
|
33
|
+
'',
|
|
34
|
+
'Version:',
|
|
35
|
+
exports.version,
|
|
36
|
+
'',
|
|
37
|
+
'GitHub Repository:',
|
|
38
|
+
'https://github.com/NonSpicyBurrito/sonolus-llsif-engine',
|
|
39
|
+
].join('\n'),
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
exports.engineConfiguration = new Resource_cjs_1.Resource('EngineConfiguration');
|
|
43
|
+
exports.engineData = new Resource_cjs_1.Resource('EngineData');
|
|
44
|
+
exports.engineThumbnail = new Resource_cjs_1.Resource('thumbnail.png');
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Resource } from './Resource.cjs';
|
|
2
|
+
export { nssToSIFC } from './nss/convert.cjs';
|
|
3
|
+
export { sifcToLevelData } from './sifc/convert.cjs';
|
|
4
|
+
export declare const version = "1.0.0-beta.0";
|
|
5
|
+
export declare const engineInfo: {
|
|
6
|
+
readonly name: "llsif";
|
|
7
|
+
readonly version: 8;
|
|
8
|
+
readonly title: {
|
|
9
|
+
readonly en: "Love Live!";
|
|
10
|
+
readonly ja: "ラブライブ!";
|
|
11
|
+
readonly ko: "러브라이브";
|
|
12
|
+
readonly zhs: "Love Live!";
|
|
13
|
+
readonly zht: "Love Live!";
|
|
14
|
+
};
|
|
15
|
+
readonly subtitle: {
|
|
16
|
+
readonly en: "Love Live! School idol festival";
|
|
17
|
+
readonly ja: "ラブライブ!スクールアイドルフェスティバル";
|
|
18
|
+
readonly ko: "러브라이브 스쿨 아이돌 페스티벌";
|
|
19
|
+
readonly zhs: "Love Live! 学园偶像祭";
|
|
20
|
+
readonly zht: "Love Live! 學園偶像祭";
|
|
21
|
+
};
|
|
22
|
+
readonly author: {
|
|
23
|
+
readonly en: "Burrito";
|
|
24
|
+
};
|
|
25
|
+
readonly description: {
|
|
26
|
+
readonly en: string;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
export declare const engineConfiguration: Resource;
|
|
30
|
+
export declare const engineData: Resource;
|
|
31
|
+
export declare const engineThumbnail: Resource;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.nssToSIFC = void 0;
|
|
4
|
+
const index_cjs_1 = require("./index.cjs");
|
|
5
|
+
const nssToSIFC = (nss) => {
|
|
6
|
+
const objects = [
|
|
7
|
+
{
|
|
8
|
+
type: 'BPM',
|
|
9
|
+
beat: 0,
|
|
10
|
+
bpm: 60,
|
|
11
|
+
},
|
|
12
|
+
];
|
|
13
|
+
const swings = new Map();
|
|
14
|
+
for (const note of nss) {
|
|
15
|
+
const beat = note.timing_sec;
|
|
16
|
+
const lane = 5 - note.position;
|
|
17
|
+
switch (note.effect) {
|
|
18
|
+
case index_cjs_1.NoteEffect.Tap1:
|
|
19
|
+
case index_cjs_1.NoteEffect.Tap2:
|
|
20
|
+
case index_cjs_1.NoteEffect.Tap4:
|
|
21
|
+
objects.push({
|
|
22
|
+
type: 'Tap',
|
|
23
|
+
beat,
|
|
24
|
+
lane,
|
|
25
|
+
});
|
|
26
|
+
break;
|
|
27
|
+
case index_cjs_1.NoteEffect.TapHold:
|
|
28
|
+
objects.push({
|
|
29
|
+
type: 'Tap',
|
|
30
|
+
beat,
|
|
31
|
+
lane,
|
|
32
|
+
hold: {
|
|
33
|
+
beat: beat + note.effect_value,
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
break;
|
|
37
|
+
case index_cjs_1.NoteEffect.Swing:
|
|
38
|
+
case index_cjs_1.NoteEffect.SwingHold: {
|
|
39
|
+
const notes = swings.get(note.notes_level);
|
|
40
|
+
if (notes) {
|
|
41
|
+
notes.push(note);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
swings.set(note.notes_level, [note]);
|
|
45
|
+
}
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
for (const notes of swings.values()) {
|
|
51
|
+
for (const [i, note] of notes.entries()) {
|
|
52
|
+
const beat = note.timing_sec;
|
|
53
|
+
const lane = 5 - note.position;
|
|
54
|
+
const next = notes[i + 1];
|
|
55
|
+
const prev = notes[i - 1];
|
|
56
|
+
const direction = next
|
|
57
|
+
? next.position > note.position
|
|
58
|
+
? 'Left'
|
|
59
|
+
: 'Right'
|
|
60
|
+
: prev
|
|
61
|
+
? prev.position > note.position
|
|
62
|
+
? 'Right'
|
|
63
|
+
: 'Left'
|
|
64
|
+
: 'Left';
|
|
65
|
+
if (note.effect === index_cjs_1.NoteEffect.Swing) {
|
|
66
|
+
objects.push({
|
|
67
|
+
type: 'Swing',
|
|
68
|
+
beat,
|
|
69
|
+
lane,
|
|
70
|
+
direction,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
objects.push({
|
|
75
|
+
type: 'Swing',
|
|
76
|
+
beat,
|
|
77
|
+
lane,
|
|
78
|
+
direction,
|
|
79
|
+
hold: {
|
|
80
|
+
beat: beat + note.effect_value,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
attribute: nss[0].notes_attribute - 1,
|
|
88
|
+
offset: 0,
|
|
89
|
+
objects,
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
exports.nssToSIFC = nssToSIFC;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type NSS = Note[];
|
|
2
|
+
export declare const NoteEffect: {
|
|
3
|
+
readonly Tap1: 1;
|
|
4
|
+
readonly Tap2: 2;
|
|
5
|
+
readonly Tap4: 4;
|
|
6
|
+
readonly TapHold: 3;
|
|
7
|
+
readonly Swing: 11;
|
|
8
|
+
readonly SwingHold: 13;
|
|
9
|
+
};
|
|
10
|
+
export type NoteEffect = (typeof NoteEffect)[keyof typeof NoteEffect];
|
|
11
|
+
export type Note = {
|
|
12
|
+
timing_sec: number;
|
|
13
|
+
position: number;
|
|
14
|
+
notes_attribute: number;
|
|
15
|
+
notes_level: number;
|
|
16
|
+
effect: NoteEffect;
|
|
17
|
+
effect_value: number;
|
|
18
|
+
};
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sifcToLevelData = void 0;
|
|
4
|
+
const sonolus_core_1 = require("sonolus-core");
|
|
5
|
+
function sifcToLevelData(chart) {
|
|
6
|
+
const entities = [];
|
|
7
|
+
const beatToIntermediates = new Map();
|
|
8
|
+
const intermediateToRef = new Map();
|
|
9
|
+
const intermediateToEntity = new Map();
|
|
10
|
+
let i = 0;
|
|
11
|
+
const getRef = (intermediate) => {
|
|
12
|
+
let ref = intermediateToRef.get(intermediate);
|
|
13
|
+
if (ref)
|
|
14
|
+
return ref;
|
|
15
|
+
ref = (i++).toString(36);
|
|
16
|
+
intermediateToRef.set(intermediate, ref);
|
|
17
|
+
const entity = intermediateToEntity.get(intermediate);
|
|
18
|
+
if (entity)
|
|
19
|
+
entity.ref = ref;
|
|
20
|
+
return ref;
|
|
21
|
+
};
|
|
22
|
+
const append = (intermediate) => {
|
|
23
|
+
const entity = {
|
|
24
|
+
archetype: intermediate.archetype,
|
|
25
|
+
data: [],
|
|
26
|
+
};
|
|
27
|
+
if (intermediate.sim) {
|
|
28
|
+
const beat = intermediate.data[sonolus_core_1.EngineArchetypeDataName.Beat];
|
|
29
|
+
if (typeof beat !== 'number')
|
|
30
|
+
throw 'Unexpected beat';
|
|
31
|
+
const intermediates = beatToIntermediates.get(beat);
|
|
32
|
+
if (intermediates) {
|
|
33
|
+
intermediates.push(intermediate);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
beatToIntermediates.set(beat, [intermediate]);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const ref = intermediateToRef.get(intermediate);
|
|
40
|
+
if (ref)
|
|
41
|
+
entity.ref = ref;
|
|
42
|
+
intermediateToEntity.set(intermediate, entity);
|
|
43
|
+
entities.push(entity);
|
|
44
|
+
for (const [name, value] of Object.entries(intermediate.data)) {
|
|
45
|
+
if (typeof value === 'number') {
|
|
46
|
+
entity.data.push({
|
|
47
|
+
name,
|
|
48
|
+
value,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
entity.data.push({
|
|
53
|
+
name,
|
|
54
|
+
ref: getRef(value),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
append({
|
|
60
|
+
archetype: 'Initialization',
|
|
61
|
+
data: {
|
|
62
|
+
attribute: chart.attribute,
|
|
63
|
+
},
|
|
64
|
+
sim: false,
|
|
65
|
+
});
|
|
66
|
+
append({
|
|
67
|
+
archetype: 'InputManager',
|
|
68
|
+
data: {},
|
|
69
|
+
sim: false,
|
|
70
|
+
});
|
|
71
|
+
append({
|
|
72
|
+
archetype: 'Stage',
|
|
73
|
+
data: {},
|
|
74
|
+
sim: false,
|
|
75
|
+
});
|
|
76
|
+
for (const object of chart.objects) {
|
|
77
|
+
handlers[object.type](object, append);
|
|
78
|
+
}
|
|
79
|
+
for (const intermediates of beatToIntermediates.values()) {
|
|
80
|
+
if (intermediates.length < 2)
|
|
81
|
+
continue;
|
|
82
|
+
for (const intermediate of intermediates) {
|
|
83
|
+
const entity = intermediateToEntity.get(intermediate);
|
|
84
|
+
if (!entity)
|
|
85
|
+
throw 'Unexpected missing entity';
|
|
86
|
+
entity.data.push({
|
|
87
|
+
name: 'sim',
|
|
88
|
+
value: 1,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
bgmOffset: chart.offset,
|
|
94
|
+
entities,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
exports.sifcToLevelData = sifcToLevelData;
|
|
98
|
+
const bpm = (object, append) => append({
|
|
99
|
+
archetype: sonolus_core_1.EngineArchetypeName.BpmChange,
|
|
100
|
+
data: {
|
|
101
|
+
[sonolus_core_1.EngineArchetypeDataName.Beat]: object.beat,
|
|
102
|
+
[sonolus_core_1.EngineArchetypeDataName.Bpm]: object.bpm,
|
|
103
|
+
},
|
|
104
|
+
sim: false,
|
|
105
|
+
});
|
|
106
|
+
const tap = (object, append) => {
|
|
107
|
+
const note = {
|
|
108
|
+
archetype: 'TapNote',
|
|
109
|
+
data: {
|
|
110
|
+
[sonolus_core_1.EngineArchetypeDataName.Beat]: object.beat,
|
|
111
|
+
lane: object.lane,
|
|
112
|
+
},
|
|
113
|
+
sim: true,
|
|
114
|
+
};
|
|
115
|
+
if (object.hold) {
|
|
116
|
+
hold(note, object.hold.beat, append);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
append(note);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
const swing = (object, append) => {
|
|
123
|
+
const note = {
|
|
124
|
+
archetype: 'SwingNote',
|
|
125
|
+
data: {
|
|
126
|
+
[sonolus_core_1.EngineArchetypeDataName.Beat]: object.beat,
|
|
127
|
+
lane: object.lane,
|
|
128
|
+
direction: object.direction === 'Left' ? -1 : 1,
|
|
129
|
+
},
|
|
130
|
+
sim: true,
|
|
131
|
+
};
|
|
132
|
+
if (object.hold) {
|
|
133
|
+
hold(note, object.hold.beat, append);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
append(note);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
const hold = (head, beat, append) => {
|
|
140
|
+
head.data.hold = 1;
|
|
141
|
+
const tail = {
|
|
142
|
+
archetype: 'HoldNote',
|
|
143
|
+
data: {
|
|
144
|
+
[sonolus_core_1.EngineArchetypeDataName.Beat]: beat,
|
|
145
|
+
prev: head,
|
|
146
|
+
},
|
|
147
|
+
sim: false,
|
|
148
|
+
};
|
|
149
|
+
append(head);
|
|
150
|
+
append(tail);
|
|
151
|
+
append({
|
|
152
|
+
archetype: 'HoldConnector',
|
|
153
|
+
data: {
|
|
154
|
+
head,
|
|
155
|
+
tail,
|
|
156
|
+
},
|
|
157
|
+
sim: false,
|
|
158
|
+
});
|
|
159
|
+
};
|
|
160
|
+
const handlers = {
|
|
161
|
+
BPM: bpm,
|
|
162
|
+
Tap: tap,
|
|
163
|
+
Swing: swing,
|
|
164
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type SIFChart = {
|
|
2
|
+
offset: number;
|
|
3
|
+
attribute: number;
|
|
4
|
+
objects: ChartObject[];
|
|
5
|
+
};
|
|
6
|
+
export type ChartObject = BPMObject | TapNote | SwingNote;
|
|
7
|
+
type ObjectBase = {
|
|
8
|
+
beat: number;
|
|
9
|
+
};
|
|
10
|
+
export type BPMObject = ObjectBase & {
|
|
11
|
+
type: 'BPM';
|
|
12
|
+
bpm: number;
|
|
13
|
+
};
|
|
14
|
+
type NoteBase = ObjectBase & {
|
|
15
|
+
lane: number;
|
|
16
|
+
hold?: ObjectBase;
|
|
17
|
+
};
|
|
18
|
+
export type TapNote = NoteBase & {
|
|
19
|
+
type: 'Tap';
|
|
20
|
+
};
|
|
21
|
+
export type SwingNote = NoteBase & {
|
|
22
|
+
type: 'Swing';
|
|
23
|
+
direction: 'Left' | 'Right';
|
|
24
|
+
};
|
|
25
|
+
export {};
|
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sonolus-llsif-engine",
|
|
3
|
+
"version": "1.0.0-beta.0",
|
|
4
|
+
"description": "A recreation of Love Live! School idol festival engine in Sonolus",
|
|
5
|
+
"author": "NonSpicyBurrito",
|
|
6
|
+
"repository": "github:NonSpicyBurrito/sonolus-llsif-engine",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"Sonolus"
|
|
10
|
+
],
|
|
11
|
+
"main": "dist/index.cjs",
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"dev": "sonolus-cli --dev",
|
|
17
|
+
"prebuild": "tsc -p . --noEmit && eslint --ext .mts ./src && eslint --ext .cts ./lib",
|
|
18
|
+
"build": "tsc -p ./lib/tsconfig.json && sonolus-cli --build && node ./lib/build.mjs"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/node": "^16.18.30",
|
|
22
|
+
"@typescript-eslint/eslint-plugin": "^5.59.1",
|
|
23
|
+
"@typescript-eslint/parser": "^5.59.1",
|
|
24
|
+
"eslint": "^8.39.0",
|
|
25
|
+
"eslint-config-prettier": "^8.8.0",
|
|
26
|
+
"eslint-plugin-prettier": "^4.2.1",
|
|
27
|
+
"prettier": "^2.8.8",
|
|
28
|
+
"prettier-plugin-organize-imports": "^3.2.2",
|
|
29
|
+
"sonolus-core": "~7.0.0-beta.0",
|
|
30
|
+
"sonolus.js": "~9.0.0-beta.0",
|
|
31
|
+
"typescript": "~5.0.4"
|
|
32
|
+
}
|
|
33
|
+
}
|