kipphi 2.0.0 → 2.0.1
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/bpm.ts +213 -0
- package/chart.ts +172 -80
- package/chartTypes.ts +62 -20
- package/easing.ts +89 -83
- package/env.ts +173 -0
- package/evaluator.ts +35 -11
- package/event.ts +273 -225
- package/index.ts +14 -11
- package/judgeline.ts +381 -84
- package/jumparray.ts +11 -11
- package/note.ts +31 -54
- package/operation.ts +1378 -0
- package/package.json +1 -1
- package/rpeChartCompiler.ts +133 -98
- package/time.ts +35 -223
- package/tsconfig.json +1 -2
- package/util.ts +21 -1
- package/version.ts +1 -1
package/time.ts
CHANGED
|
@@ -1,245 +1,55 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { JumpArray } from "./jumparray";
|
|
4
|
-
import { NodeType } from "./util";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
/// #declaration:global
|
|
8
|
-
|
|
1
|
+
import { TimeT } from "./chartTypes";
|
|
2
|
+
import { err } from "./env";
|
|
9
3
|
/**
|
|
10
|
-
*
|
|
4
|
+
* @static @final
|
|
11
5
|
*/
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
cachedStartIntegral?: number;
|
|
15
|
-
override cachedIntegral?: number;
|
|
16
|
-
override next: BPMEndNode | BPMNodeLike<NodeType.TAIL>;
|
|
17
|
-
override previous: BPMEndNode | BPMNodeLike<NodeType.HEAD>;
|
|
18
|
-
constructor(startTime: TimeT, bpm: number) {
|
|
19
|
-
super(startTime, bpm);
|
|
20
|
-
this.spb = 60 / bpm;
|
|
21
|
-
}
|
|
22
|
-
override getIntegral(beats: number): number {
|
|
23
|
-
return (beats - TimeCalculator.toBeats(this.time)) * 60 / this.value;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* may only used with a startnode whose next is not tail
|
|
27
|
-
* @returns
|
|
28
|
-
*/
|
|
29
|
-
override getFullIntegral(): number {
|
|
30
|
-
return (TimeCalculator.toBeats(this.next.time) - TimeCalculator.toBeats(this.time)) * 60 / this.value;
|
|
31
|
-
}
|
|
32
|
-
/*
|
|
33
|
-
static connect(node1: BPMStartNode, node2: BPMEndNode | Tailer<EventStartNode>): void;
|
|
34
|
-
static connect(node1: BPMEndNode | Header<BPMStartNode>, node2: BPMStartNode): void;
|
|
35
|
-
static connect(node1: EventNode | Header<EventNode>, node2: EventNode | Tailer<EventNode>): void {
|
|
36
|
-
super.connect(node1, node2);
|
|
37
|
-
}
|
|
38
|
-
*/
|
|
39
|
-
}
|
|
40
|
-
class BPMEndNode extends EventEndNode {
|
|
41
|
-
spb: number;
|
|
42
|
-
override previous: BPMStartNode;
|
|
43
|
-
override next: BPMStartNode;
|
|
44
|
-
constructor(endTime: TimeT) {
|
|
45
|
-
super(endTime, null);
|
|
46
|
-
}
|
|
47
|
-
// @ts-expect-error
|
|
48
|
-
get value(): number {
|
|
49
|
-
return this.previous.value
|
|
50
|
-
}
|
|
51
|
-
set value(val) {}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
interface BPMNodeLike<T extends NodeType> extends EventNodeLike<T> {
|
|
55
|
-
next: [BPMStartNode, null, BNOrTail][T] | null;
|
|
56
|
-
previous: [null, BPMStartNode, BNOrHead][T] | null;
|
|
57
|
-
}
|
|
58
|
-
type BPMNode = BPMStartNode | BPMEndNode;
|
|
59
|
-
type AnyBN = (BPMNode | BPMNodeLike<NodeType.TAIL> | BPMNodeLike<NodeType.HEAD>);
|
|
60
|
-
type BNOrTail = BPMNode | BPMNodeLike<NodeType.TAIL>;
|
|
61
|
-
type BNOrHead = BPMNode | BPMNodeLike<NodeType.HEAD>;
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* 拥有与事件类似的逻辑
|
|
65
|
-
* 每对节点之间代表一个BPM相同的片段
|
|
66
|
-
* 片段之间BPM可以发生改变
|
|
67
|
-
*/
|
|
68
|
-
|
|
69
|
-
export class BPMSequence extends EventNodeSequence {
|
|
70
|
-
override head: BPMNodeLike<NodeType.HEAD>;
|
|
71
|
-
override tail: BPMNodeLike<NodeType.TAIL>;
|
|
72
|
-
/** 从拍数访问节点 */
|
|
73
|
-
override jump: JumpArray<AnyEN>;
|
|
74
|
-
/** 以秒计时的跳数组,处理从秒访问节点 */
|
|
75
|
-
secondJump: JumpArray<AnyBN>;
|
|
76
|
-
constructor(bpmList: BPMSegmentData[], public duration: number) {
|
|
77
|
-
super(EventType.bpm, null);
|
|
78
|
-
let curPos: BPMNodeLike<NodeType.HEAD> | BPMEndNode = this.head;
|
|
79
|
-
let next = bpmList[0];
|
|
80
|
-
this.listLength = bpmList.length;
|
|
81
|
-
for (let i = 1; i < bpmList.length; i++) {
|
|
82
|
-
const each = next;
|
|
83
|
-
next = bpmList[i];
|
|
84
|
-
const startNode = new BPMStartNode(each.startTime, each.bpm);
|
|
85
|
-
const endNode = new BPMEndNode(next.startTime);
|
|
86
|
-
BPMStartNode.connect(startNode, endNode);
|
|
87
|
-
BPMStartNode.connect(curPos, startNode);
|
|
88
|
-
curPos = endNode;
|
|
89
|
-
}
|
|
90
|
-
const last = new BPMStartNode(next.startTime, next.bpm)
|
|
91
|
-
BPMStartNode.connect(curPos, last);
|
|
92
|
-
BPMStartNode.connect(last, this.tail);
|
|
93
|
-
this.initJump();
|
|
94
|
-
}
|
|
95
|
-
override initJump(): void {
|
|
96
|
-
console.log(this)
|
|
97
|
-
this.effectiveBeats = TimeCalculator.toBeats(this.tail.previous.time)
|
|
98
|
-
if (this.effectiveBeats !== 0) {
|
|
99
|
-
super.initJump(); // 为0可以跳过jumpArray,用不到
|
|
100
|
-
// 只有一个BPM片段就会这样
|
|
101
|
-
}
|
|
102
|
-
this.updateSecondJump();
|
|
103
|
-
}
|
|
104
|
-
updateSecondJump(): void {
|
|
105
|
-
let integral = 0;
|
|
106
|
-
// 计算积分并缓存到BPMNode
|
|
107
|
-
let node: BPMStartNode = this.head.next;
|
|
108
|
-
while (true) {
|
|
109
|
-
node.cachedStartIntegral = integral;
|
|
110
|
-
if (node.next.type === NodeType.TAIL) {
|
|
111
|
-
break;
|
|
112
|
-
}
|
|
113
|
-
const endNode = <BPMEndNode>(<BPMStartNode>node).next;
|
|
114
|
-
integral += node.getFullIntegral();
|
|
115
|
-
node.cachedIntegral = integral;
|
|
116
|
-
|
|
117
|
-
node = endNode.next;
|
|
118
|
-
}
|
|
119
|
-
node.cachedStartIntegral = integral;
|
|
120
|
-
if (this.effectiveBeats === 0) {
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
const originalListLength = this.listLength;
|
|
124
|
-
this.secondJump = new JumpArray<AnyBN>(
|
|
125
|
-
this.head,
|
|
126
|
-
this.tail,
|
|
127
|
-
originalListLength,
|
|
128
|
-
this.duration,
|
|
129
|
-
(node: BPMStartNode) => {
|
|
130
|
-
if (node.type === NodeType.TAIL) {
|
|
131
|
-
return [null, null];
|
|
132
|
-
}
|
|
133
|
-
if (node.type === NodeType.HEAD) {
|
|
134
|
-
return [0, node.next];
|
|
135
|
-
}
|
|
136
|
-
const endNode = <BPMEndNode>(<BPMStartNode>node).next;
|
|
137
|
-
const time = node.cachedIntegral;
|
|
138
|
-
const nextNode = endNode.next;
|
|
139
|
-
if (nextNode.next.type === NodeType.TAIL) {
|
|
140
|
-
return [time, nextNode.next]; // Tailer代替最后一个StartNode去占位
|
|
141
|
-
} else {
|
|
142
|
-
return [time, nextNode];
|
|
143
|
-
}
|
|
144
|
-
},
|
|
145
|
-
(node: BPMStartNode, seconds: number) => {
|
|
146
|
-
return node.cachedIntegral > seconds ? false : (<BPMEndNode>node.next).next;
|
|
147
|
-
}
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
override updateJump(from: ENOrHead, to: ENOrTail): void {
|
|
151
|
-
super.updateJump(from, to);
|
|
152
|
-
this.updateSecondJump();
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
getNodeBySeconds(seconds: number): BPMStartNode {
|
|
156
|
-
if (this.effectiveBeats === 0) {
|
|
157
|
-
return this.tail.previous
|
|
158
|
-
}
|
|
159
|
-
const node = this.secondJump.getNodeAt(seconds);
|
|
160
|
-
if (node.type === NodeType.TAIL) {
|
|
161
|
-
return node.previous;
|
|
162
|
-
}
|
|
163
|
-
return node as BPMStartNode;
|
|
164
|
-
}
|
|
165
|
-
dumpBPM(): BPMSegmentData[] {
|
|
166
|
-
let cur = this.head.next;
|
|
167
|
-
const ret: BPMSegmentData[] = [];
|
|
168
|
-
while (true) {
|
|
169
|
-
ret.push({
|
|
170
|
-
bpm: cur.value,
|
|
171
|
-
startTime: cur.time
|
|
172
|
-
})
|
|
173
|
-
const end = cur.next;
|
|
174
|
-
if (end.type === NodeType.TAIL) {
|
|
175
|
-
break;
|
|
176
|
-
}
|
|
177
|
-
cur = end.next;
|
|
178
|
-
}
|
|
179
|
-
return ret;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* @alias TC
|
|
185
|
-
*/
|
|
186
|
-
export class TimeCalculator {
|
|
187
|
-
bpmList: BPMSegmentData[];
|
|
188
|
-
bpmSequence: BPMSequence;
|
|
189
|
-
duration: number;
|
|
190
|
-
|
|
191
|
-
constructor() {
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
update() {
|
|
195
|
-
let bpmList = this.bpmList;
|
|
196
|
-
this.bpmSequence = new BPMSequence(bpmList, this.duration);
|
|
197
|
-
}
|
|
198
|
-
toSeconds(beats: number) {
|
|
199
|
-
const node: BPMStartNode = this.bpmSequence.getNodeAt(beats);
|
|
200
|
-
return node.cachedStartIntegral + node.getIntegral(beats)
|
|
201
|
-
}
|
|
202
|
-
segmentToSeconds(beats1: number, beats2: number): number {
|
|
203
|
-
let ret = this.toSeconds(beats2) - this.toSeconds(beats1)
|
|
204
|
-
if (ret < 0) {
|
|
205
|
-
console.warn("segmentToSeconds的第二个参数需大于第一个!", "得到的参数:", arguments)
|
|
206
|
-
}
|
|
207
|
-
return ret
|
|
208
|
-
}
|
|
209
|
-
secondsToBeats(seconds: number) {
|
|
210
|
-
const node = this.bpmSequence.getNodeBySeconds(seconds);
|
|
211
|
-
// console.log("node:", node)
|
|
212
|
-
const beats = (seconds - node.cachedStartIntegral) / node.spb;
|
|
213
|
-
return TimeCalculator.toBeats(node.time) + beats
|
|
214
|
-
}
|
|
6
|
+
export default class TC {
|
|
7
|
+
private constructor() {}
|
|
215
8
|
static toBeats(beaT: TimeT): number {
|
|
216
|
-
if (!beaT) debugger
|
|
217
9
|
return beaT[0] + beaT[1] / beaT[2]
|
|
218
10
|
}
|
|
219
11
|
static getDelta(beaT1: TimeT, beaT2: TimeT): number {
|
|
220
12
|
return this.toBeats(beaT1) - this.toBeats(beaT2)
|
|
221
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* @returns beaT1 == beaT2
|
|
16
|
+
*/
|
|
222
17
|
static eq(beaT1: TimeT, beaT2: TimeT): boolean {
|
|
223
18
|
return beaT1[0] === beaT2 [0] && beaT1[1] * beaT2[2] === beaT1[2] * beaT2[1] // 这里曾经把两个都写成beaT1,特此留念(
|
|
224
19
|
}
|
|
20
|
+
/** @returns beaT1 > beaT2 */
|
|
225
21
|
static gt(beaT1:TimeT, beaT2: TimeT): boolean {
|
|
226
22
|
return beaT1[0] > beaT2[0] || beaT1[0] === beaT2[0] && beaT1[1] * beaT2[2] > beaT1[2] * beaT2[1]
|
|
227
23
|
}
|
|
24
|
+
/** @returns beaT1 < beaT2 */
|
|
228
25
|
static lt(beaT1:TimeT, beaT2: TimeT): boolean {
|
|
229
26
|
return beaT1[0] < beaT2[0] || beaT1[0] === beaT2[0] && beaT1[1] * beaT2[2] < beaT1[2] * beaT2[1]
|
|
230
27
|
}
|
|
28
|
+
/** @returns beaT1 != beaT2 */
|
|
231
29
|
static ne(beaT1:TimeT, beaT2: TimeT): boolean {
|
|
232
30
|
return beaT1[0] !== beaT2[0] || beaT1[1] * beaT2[2] !== beaT1[2] * beaT2[1]
|
|
233
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* @returns beaT1 + beaT2
|
|
34
|
+
*/
|
|
234
35
|
static add(beaT1: TimeT, beaT2: TimeT): TimeT {
|
|
235
36
|
return [beaT1[0] + beaT2[0], beaT1[1] * beaT2[2] + beaT1[2] * beaT2[1], beaT1[2] * beaT2[2]]
|
|
236
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* @returns beaT1 - beaT2
|
|
40
|
+
*/
|
|
237
41
|
static sub(beaT1: TimeT, beaT2: TimeT): TimeT {
|
|
238
42
|
return [beaT1[0] - beaT2[0], beaT1[1] * beaT2[2] - beaT1[2] * beaT2[1], beaT1[2] * beaT2[2]]
|
|
239
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* @returns Ratio(a 2-number tuple) = beaT1 / beaT2
|
|
46
|
+
*/
|
|
240
47
|
static div(beaT1: TimeT, beaT2: TimeT): [number, number] {
|
|
241
48
|
return [(beaT1[0] * beaT1[2] + beaT1[1]) * beaT2[2], (beaT2[0] * beaT2[2] + beaT2[1]) * beaT1[2]]
|
|
242
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* @returns beaT1 * [numerator, denominator]
|
|
52
|
+
*/
|
|
243
53
|
static mul(beaT: TimeT, ratio: [number, number]): TimeT {
|
|
244
54
|
// 将带分数beaT: TimeT乘一个分数[number, number]得到一个新的带分数returnval: TimeT,不要求这个带分数分子不超过分母,但所有的数都是整数
|
|
245
55
|
// (输入的两个元组都是整数元组)
|
|
@@ -251,6 +61,7 @@ export class TimeCalculator {
|
|
|
251
61
|
} else {
|
|
252
62
|
return [Math.floor(b0nume / denominator), beaT[1] * numerator + remainder * beaT[2], beaT[2] * denominator]
|
|
253
63
|
}
|
|
64
|
+
|
|
254
65
|
}
|
|
255
66
|
/**
|
|
256
67
|
* 原地规范化时间元组,但仍然返回这个元组,方便使用
|
|
@@ -259,7 +70,7 @@ export class TimeCalculator {
|
|
|
259
70
|
*/
|
|
260
71
|
static validateIp(beaT: TimeT): TimeT {
|
|
261
72
|
if (beaT === undefined || beaT[2] === 0) {
|
|
262
|
-
throw
|
|
73
|
+
throw err.INVALID_TIME_TUPLE(beaT);
|
|
263
74
|
}
|
|
264
75
|
if (beaT[1] >= beaT[2]) {
|
|
265
76
|
const quotient = Math.floor(beaT[1] / beaT[2]);
|
|
@@ -283,8 +94,17 @@ export class TimeCalculator {
|
|
|
283
94
|
}
|
|
284
95
|
return beaT;
|
|
285
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* 相加并约简
|
|
99
|
+
*/
|
|
286
100
|
static vadd(beaT1: TimeT, beaT2: TimeT) { return this.validateIp(this.add(beaT1, beaT2)); }
|
|
101
|
+
/**
|
|
102
|
+
* 相减并约简
|
|
103
|
+
*/
|
|
287
104
|
static vsub(beaT1: TimeT, beaT2: TimeT) { return this.validateIp(this.sub(beaT1, beaT2)); }
|
|
105
|
+
/**
|
|
106
|
+
* 相乘并约简
|
|
107
|
+
*/
|
|
288
108
|
static vmul(beaT: TimeT, ratio: [number, number]): TimeT { return this.validateIp(this.mul(beaT, ratio)); }
|
|
289
109
|
static gcd(a: number, b: number): number {
|
|
290
110
|
if (a === 0 || b === 0) {
|
|
@@ -297,12 +117,4 @@ export class TimeCalculator {
|
|
|
297
117
|
}
|
|
298
118
|
return a;
|
|
299
119
|
}
|
|
300
|
-
|
|
301
|
-
return this.bpmSequence.dumpBPM();
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
export const TC = TimeCalculator;
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
/// #enddeclaration
|
|
120
|
+
}
|
package/tsconfig.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
3
|
// Environment setup & latest features
|
|
4
|
-
"lib": ["
|
|
4
|
+
"lib": ["ES2021"],
|
|
5
5
|
"target": "ES2016",
|
|
6
6
|
"module": "es2020",
|
|
7
7
|
"moduleDetection": "force",
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
"skipLibCheck": true,
|
|
17
17
|
"noFallthroughCasesInSwitch": true,
|
|
18
18
|
"noUncheckedIndexedAccess": true,
|
|
19
|
-
"noImplicitOverride": true,
|
|
20
19
|
|
|
21
20
|
// Some stricter flags (disabled by default)
|
|
22
21
|
"noUnusedLocals": false,
|
package/util.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RGB } from "./chartTypes";
|
|
1
|
+
import type { RGB, TimeT } from "./chartTypes";
|
|
2
2
|
|
|
3
3
|
/// #declaration:global
|
|
4
4
|
|
|
@@ -9,6 +9,22 @@ export enum NodeType {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export type TupleCoord = [x: number, y: number]
|
|
12
|
+
/**
|
|
13
|
+
* 检查值的类型
|
|
14
|
+
* @param value
|
|
15
|
+
* @param type 为字符串时,用typeof检测,为构造函数时,用instanceof检测,为数组时,识别为元组类型。
|
|
16
|
+
*/
|
|
17
|
+
export const checkType = (value: unknown, type: string | (string | typeof Function)[] | typeof Function) => {
|
|
18
|
+
if (Array.isArray(type)) {
|
|
19
|
+
return Array.isArray(value)
|
|
20
|
+
&& value.length === type.length
|
|
21
|
+
&& type.every((t, i) => checkType(value[i], t))
|
|
22
|
+
} else if (typeof type === "string") {
|
|
23
|
+
return typeof value === type
|
|
24
|
+
} else {
|
|
25
|
+
return value instanceof type
|
|
26
|
+
}
|
|
27
|
+
}
|
|
12
28
|
|
|
13
29
|
export const rgb2hex = (rgb: RGB) => {
|
|
14
30
|
return rgb[0] << 16 | rgb[1] << 8 | rgb[2];
|
|
@@ -23,4 +39,8 @@ export const numberToRatio = (num: number): [number, number] => {
|
|
|
23
39
|
return [Math.round(num * 10000), 10000]
|
|
24
40
|
}
|
|
25
41
|
|
|
42
|
+
|
|
43
|
+
export const toTimeString = (beaT: TimeT): string =>
|
|
44
|
+
`${beaT[0]}:${beaT[1]}/${beaT[2]}`;
|
|
45
|
+
|
|
26
46
|
/// #enddeclaration
|