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/jumparray.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { NodeType } from "./util";
|
|
2
|
+
import Env from "./env";
|
|
2
3
|
|
|
3
4
|
// type EndBeats = number;
|
|
4
|
-
const MIN_LENGTH = 128
|
|
5
|
-
const MAX_LENGTH = 1024
|
|
6
|
-
const MINOR_PARTS = 16;
|
|
7
5
|
/// #declaration:global
|
|
8
6
|
|
|
9
7
|
type EndNextFn<T extends TwoDirectionNodeLike> = (node: T) => [endBeats: number, next: T];
|
|
@@ -23,6 +21,7 @@ export class JumpArray<T extends TwoDirectionNodeLike> {
|
|
|
23
21
|
averageBeats: number;
|
|
24
22
|
effectiveBeats: number;
|
|
25
23
|
goPrev: (node: T) => T;
|
|
24
|
+
readonly MINOR_SCALE_COUNT: number = Env.JUMPARRAY_MINOR_SCALE_COUNT;
|
|
26
25
|
|
|
27
26
|
/**
|
|
28
27
|
*
|
|
@@ -46,7 +45,7 @@ export class JumpArray<T extends TwoDirectionNodeLike> {
|
|
|
46
45
|
this.header = head;
|
|
47
46
|
this.tailer = tail;
|
|
48
47
|
// const originalListLength = this.listLength
|
|
49
|
-
const listLength: number = Math.max(
|
|
48
|
+
const listLength: number = Math.max(Env.JUMPARRAY_MIN_LENGTH, Math.min(originalListLength * 4, Env.JUMPARRAY_MAX_LENGTH));
|
|
50
49
|
const averageBeats: number = Math.pow(2, Math.ceil(Math.log2(effectiveBeats / listLength)));
|
|
51
50
|
const exactLength: number = Math.ceil(effectiveBeats / averageBeats);
|
|
52
51
|
// console.log(exactLength, listLength, averageBeats, exactLength)
|
|
@@ -91,6 +90,7 @@ export class JumpArray<T extends TwoDirectionNodeLike> {
|
|
|
91
90
|
updateRange(firstNode: T, lastNode: T) {
|
|
92
91
|
const {endNextFn, effectiveBeats, resolveLastNode} = this;
|
|
93
92
|
lastNode = resolveLastNode(lastNode);
|
|
93
|
+
const MINOR_PARTS = this.MINOR_SCALE_COUNT;
|
|
94
94
|
// console.log(firstNode, lastNode)
|
|
95
95
|
/**
|
|
96
96
|
*
|
|
@@ -125,16 +125,13 @@ export class JumpArray<T extends TwoDirectionNodeLike> {
|
|
|
125
125
|
if (Array.isArray(jumpArray[jumpIndex])) {
|
|
126
126
|
fillMinor(previousEndTime, endTime)
|
|
127
127
|
} else {
|
|
128
|
-
try {
|
|
129
|
-
// console.log(jumpIndex, currentNode)
|
|
130
128
|
jumpArray[jumpIndex] = currentNode;
|
|
131
|
-
} catch (E) {console.log(jumpIndex, jumpArray);debugger}
|
|
132
129
|
}
|
|
133
130
|
jumpIndex++;
|
|
134
131
|
}
|
|
135
132
|
const currentJumpBeats: number = jumpIndex * averageBeats // 放错了
|
|
136
133
|
if (endTime > currentJumpBeats) {
|
|
137
|
-
|
|
134
|
+
const minor = jumpArray[jumpIndex];
|
|
138
135
|
if (!Array.isArray(minor)) {
|
|
139
136
|
jumpArray[jumpIndex] = new Array(MINOR_PARTS);
|
|
140
137
|
}
|
|
@@ -175,8 +172,9 @@ export class JumpArray<T extends TwoDirectionNodeLike> {
|
|
|
175
172
|
const jumpAverageBeats = this.averageBeats;
|
|
176
173
|
const jumpPos = Math.floor(beats / jumpAverageBeats);
|
|
177
174
|
const rest = beats - jumpPos * jumpAverageBeats;
|
|
175
|
+
const MINOR_PARTS = this.MINOR_SCALE_COUNT;
|
|
178
176
|
for (let i = jumpPos; i >= 0; i--) {
|
|
179
|
-
|
|
177
|
+
const canBeNodeOrArray: T | T[] = this.array[i];
|
|
180
178
|
if (Array.isArray(canBeNodeOrArray)) {
|
|
181
179
|
const minorIndex = Math.floor(rest / (jumpAverageBeats / MINOR_PARTS)) - 1;
|
|
182
180
|
for (let j = minorIndex; j >= 0; j--) {
|
|
@@ -202,11 +200,12 @@ export class JumpArray<T extends TwoDirectionNodeLike> {
|
|
|
202
200
|
if (beats >= this.effectiveBeats) {
|
|
203
201
|
return this.tailer;
|
|
204
202
|
}
|
|
203
|
+
const MINOR_PARTS = this.MINOR_SCALE_COUNT;
|
|
205
204
|
const jumpAverageBeats = this.averageBeats;
|
|
206
205
|
const jumpPos = Math.floor(beats / jumpAverageBeats);
|
|
207
206
|
const rest = beats - jumpPos * jumpAverageBeats;
|
|
208
207
|
const nextFn = this.nextFn;
|
|
209
|
-
|
|
208
|
+
const canBeNodeOrArray: T | T[] = this.array[jumpPos]
|
|
210
209
|
let node: T = Array.isArray(canBeNodeOrArray)
|
|
211
210
|
? canBeNodeOrArray[Math.floor(rest / (jumpAverageBeats / MINOR_PARTS))]
|
|
212
211
|
: canBeNodeOrArray;
|
|
@@ -216,10 +215,11 @@ export class JumpArray<T extends TwoDirectionNodeLike> {
|
|
|
216
215
|
// console.log(this, node, jumpPos, beats)
|
|
217
216
|
if (!node) {
|
|
218
217
|
console.warn("No node:", node, beats)
|
|
219
|
-
|
|
218
|
+
throw new Error();
|
|
220
219
|
}
|
|
221
220
|
let next: T | false;
|
|
222
221
|
// console.log(this)
|
|
222
|
+
// eslint-disable-next-line no-cond-assign
|
|
223
223
|
while (next = nextFn(node, beats)) {
|
|
224
224
|
node = next;
|
|
225
225
|
if (node.type === NodeType.TAIL) {
|
package/note.ts
CHANGED
|
@@ -5,30 +5,17 @@ import type { Chart } from "./chart";
|
|
|
5
5
|
import { NoteType, type NNListDataKPA, type NoteDataKPA, type NoteDataRPE, type NoteNodeDataKPA, type TimeT } from "./chartTypes";
|
|
6
6
|
import type { JudgeLine } from "./judgeline";
|
|
7
7
|
import { JumpArray } from "./jumparray";
|
|
8
|
-
import { TimeCalculator } from "./
|
|
8
|
+
import { type TimeCalculator } from "./bpm";
|
|
9
|
+
import TC from "./time";
|
|
9
10
|
import { hex2rgb, NodeType, rgb2hex } from "./util";
|
|
10
11
|
|
|
11
12
|
/// #declaration:global
|
|
12
13
|
|
|
13
|
-
const TC = TimeCalculator;
|
|
14
14
|
|
|
15
15
|
export type HEX = number;
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
const node2string = (node: AnyNN) => {
|
|
20
|
-
if (!node) {
|
|
21
|
-
return "" + node
|
|
22
|
-
}
|
|
23
|
-
if (node.type === NodeType.HEAD || node.type === NodeType.TAIL) {
|
|
24
|
-
return node.type === NodeType.HEAD ? "H" : node.type === NodeType.TAIL ? "T" : "???"
|
|
25
|
-
}
|
|
26
|
-
if (!node.notes) {
|
|
27
|
-
return "EventNode"
|
|
28
|
-
}
|
|
29
|
-
return `NN(${node.notes.length}) at ${node.startTime}`
|
|
30
|
-
}
|
|
31
|
-
|
|
32
19
|
|
|
33
20
|
|
|
34
21
|
export const notePropTypes = {
|
|
@@ -98,18 +85,18 @@ export class Note {
|
|
|
98
85
|
// posNext?: Note;
|
|
99
86
|
// posPreviousSibling?: Note;
|
|
100
87
|
// posNextSibling: Note;
|
|
101
|
-
constructor(data: NoteDataRPE) {
|
|
88
|
+
constructor(data: NoteDataRPE | NoteDataKPA) {
|
|
102
89
|
this.above = data.above === 1;
|
|
103
90
|
this.alpha = data.alpha ?? 255;
|
|
104
|
-
this.endTime = data.type === NoteType.hold ?
|
|
91
|
+
this.endTime = data.type === NoteType.hold ? TC.validateIp(data.endTime) : TC.validateIp([...data.startTime]);
|
|
105
92
|
this.isFake = Boolean(data.isFake);
|
|
106
93
|
this.positionX = data.positionX;
|
|
107
94
|
this.size = data.size ?? 1.0;
|
|
108
95
|
this.speed = data.speed ?? 1.0;
|
|
109
|
-
this.startTime =
|
|
96
|
+
this.startTime = TC.validateIp(data.startTime);
|
|
110
97
|
this.type = data.type;
|
|
111
98
|
this.visibleTime = data.visibleTime;
|
|
112
|
-
// @ts-expect-error
|
|
99
|
+
// @ts-expect-error 若data是RPE数据,则为undefined,无影响。
|
|
113
100
|
this.yOffset = data.absoluteYOffset ?? data.yOffset * this.speed;
|
|
114
101
|
// @ts-expect-error 若data是RPE数据,则为undefined,无影响。
|
|
115
102
|
// 当然也有可能是KPA数据但是就是没有给
|
|
@@ -137,7 +124,7 @@ export class Note {
|
|
|
137
124
|
this.visibleBeats = Infinity;
|
|
138
125
|
return;
|
|
139
126
|
}
|
|
140
|
-
const hitBeats =
|
|
127
|
+
const hitBeats = TC.toBeats(this.startTime);
|
|
141
128
|
const hitSeconds = timeCalculator.toSeconds(hitBeats);
|
|
142
129
|
const visabilityChangeSeconds = hitSeconds - this.visibleTime;
|
|
143
130
|
const visabilityChangeBeats = timeCalculator.secondsToBeats(visabilityChangeSeconds);
|
|
@@ -150,8 +137,8 @@ export class Note {
|
|
|
150
137
|
*/
|
|
151
138
|
clone(offset: TimeT) {
|
|
152
139
|
const data = this.dumpKPA();
|
|
153
|
-
data.startTime =
|
|
154
|
-
data.endTime =
|
|
140
|
+
data.startTime = TC.add(data.startTime, offset);
|
|
141
|
+
data.endTime = TC.add(data.endTime, offset); // 踩坑
|
|
155
142
|
return new Note(data);
|
|
156
143
|
}
|
|
157
144
|
/*
|
|
@@ -167,7 +154,7 @@ export class Note {
|
|
|
167
154
|
dumpRPE(timeCalculator: TimeCalculator): NoteDataRPE {
|
|
168
155
|
let visibleTime: number;
|
|
169
156
|
if (this.visibleBeats !== Infinity) {
|
|
170
|
-
const beats =
|
|
157
|
+
const beats = TC.toBeats(this.startTime);
|
|
171
158
|
this.visibleBeats = timeCalculator.segmentToSeconds(beats - this.visibleBeats, beats);
|
|
172
159
|
} else {
|
|
173
160
|
visibleTime = 99999.0
|
|
@@ -211,7 +198,6 @@ export class Note {
|
|
|
211
198
|
}
|
|
212
199
|
}
|
|
213
200
|
|
|
214
|
-
type Connectee = NoteNode | NNNode
|
|
215
201
|
|
|
216
202
|
|
|
217
203
|
|
|
@@ -254,13 +240,13 @@ export class NoteNode extends NoteNodeLike<NodeType.MIDDLE> {
|
|
|
254
240
|
id: number;
|
|
255
241
|
constructor(time: TimeT) {
|
|
256
242
|
super(NodeType.MIDDLE);
|
|
257
|
-
this.startTime =
|
|
243
|
+
this.startTime = TC.validateIp([...time]);
|
|
258
244
|
this.notes = [];
|
|
259
245
|
this.id = NoteNode.count++;
|
|
260
246
|
}
|
|
261
247
|
static fromKPAJSON(data: NoteNodeDataKPA, timeCalculator: TimeCalculator) {
|
|
262
248
|
const node = new NoteNode(data.startTime);
|
|
263
|
-
for (
|
|
249
|
+
for (const noteData of data.notes) {
|
|
264
250
|
const note = Note.fromKPAJSON(noteData, timeCalculator);
|
|
265
251
|
node.add(note);
|
|
266
252
|
}
|
|
@@ -276,7 +262,7 @@ export class NoteNode extends NoteNodeLike<NodeType.MIDDLE> {
|
|
|
276
262
|
return (this.notes.length === 0 || this.notes[0].type !== NoteType.hold) ? this.startTime : this.notes[0].endTime
|
|
277
263
|
}
|
|
278
264
|
add(note: Note) {
|
|
279
|
-
if (!
|
|
265
|
+
if (!TC.eq(note.startTime, this.startTime)) {
|
|
280
266
|
console.warn("Wrong addition!")
|
|
281
267
|
}
|
|
282
268
|
this.notes.push(note);
|
|
@@ -303,7 +289,7 @@ export class NoteNode extends NoteNodeLike<NodeType.MIDDLE> {
|
|
|
303
289
|
const note = notes[index];
|
|
304
290
|
for (let i = index; i > 0; i--) {
|
|
305
291
|
const prev = notes[i - 1];
|
|
306
|
-
if (
|
|
292
|
+
if (TC.lt(prev.endTime, note.endTime)) {
|
|
307
293
|
// swap
|
|
308
294
|
notes[i] = prev;
|
|
309
295
|
notes[i - 1] = note;
|
|
@@ -313,7 +299,7 @@ export class NoteNode extends NoteNodeLike<NodeType.MIDDLE> {
|
|
|
313
299
|
}
|
|
314
300
|
for (let i = index; i < notes.length - 1; i++) {
|
|
315
301
|
const next = notes[i + 1];
|
|
316
|
-
if (
|
|
302
|
+
if (TC.gt(next.endTime, note.endTime)) {
|
|
317
303
|
// swap
|
|
318
304
|
notes[i] = next;
|
|
319
305
|
notes[i + 1] = note;
|
|
@@ -410,7 +396,7 @@ export class NNList {
|
|
|
410
396
|
if (prev.type === NodeType.HEAD) {
|
|
411
397
|
return;
|
|
412
398
|
}
|
|
413
|
-
this.effectiveBeats =
|
|
399
|
+
this.effectiveBeats = TC.toBeats(prev.endTime)
|
|
414
400
|
}
|
|
415
401
|
const effectiveBeats: number = this.effectiveBeats;
|
|
416
402
|
this.jump = new JumpArray<AnyNN>(
|
|
@@ -423,12 +409,11 @@ export class NNList {
|
|
|
423
409
|
return [null, null]
|
|
424
410
|
}
|
|
425
411
|
const nextNode = node.next;
|
|
426
|
-
const startTime = (node.type === NodeType.HEAD) ? 0 :
|
|
412
|
+
const startTime = (node.type === NodeType.HEAD) ? 0 : TC.toBeats(node.startTime)
|
|
427
413
|
return [startTime, nextNode]
|
|
428
414
|
},
|
|
429
|
-
// @ts-ignore
|
|
430
415
|
(note: NoteNode, beats: number) => {
|
|
431
|
-
return
|
|
416
|
+
return TC.toBeats(note.startTime) >= beats ? false : <NoteNode>note.next; // getNodeAt有guard
|
|
432
417
|
})
|
|
433
418
|
}
|
|
434
419
|
/**
|
|
@@ -447,12 +432,12 @@ export class NNList {
|
|
|
447
432
|
* @returns
|
|
448
433
|
*/
|
|
449
434
|
getNodeOf(time: TimeT): NoteNode {
|
|
450
|
-
let node = this.getNodeAt(
|
|
435
|
+
let node = this.getNodeAt(TC.toBeats(time), false)
|
|
451
436
|
.previous;
|
|
452
437
|
|
|
453
438
|
|
|
454
|
-
let isEqual = node.type !== NodeType.HEAD &&
|
|
455
|
-
if (node.next.type !== NodeType.TAIL &&
|
|
439
|
+
let isEqual = node.type !== NodeType.HEAD && TC.eq((node as NoteNode).startTime, time)
|
|
440
|
+
if (node.next.type !== NodeType.TAIL && TC.eq((node.next as NoteNode).startTime, time)) {
|
|
456
441
|
isEqual = true;
|
|
457
442
|
node = node.next;
|
|
458
443
|
}
|
|
@@ -567,14 +552,12 @@ export class HNList extends NNList {
|
|
|
567
552
|
if (node.type === NodeType.TAIL) {
|
|
568
553
|
return [null, null]
|
|
569
554
|
}
|
|
570
|
-
if (!node) debugger
|
|
571
555
|
const nextNode = node.next;
|
|
572
|
-
const endTime = node.type === NodeType.HEAD ? 0 :
|
|
556
|
+
const endTime = node.type === NodeType.HEAD ? 0 : TC.toBeats(node.endTime)
|
|
573
557
|
return [endTime, nextNode]
|
|
574
558
|
},
|
|
575
|
-
// @ts-ignore
|
|
576
559
|
(node: NoteNode, beats: number) => {
|
|
577
|
-
return
|
|
560
|
+
return TC.toBeats(node.endTime) >= beats ? false : <NoteNode>node.next; // getNodeAt有guard
|
|
578
561
|
}
|
|
579
562
|
)
|
|
580
563
|
}
|
|
@@ -596,7 +579,7 @@ export type NNNOrHead = NNNode | NNNodeLike<NodeType.HEAD>;
|
|
|
596
579
|
export type NNNOrTail = NNNode | NNNodeLike<NodeType.TAIL>;
|
|
597
580
|
type AnyNNN = NNNode | NNNodeLike<NodeType.HEAD> | NNNodeLike<NodeType.TAIL>;
|
|
598
581
|
|
|
599
|
-
class NNNodeLike<T extends NodeType> {
|
|
582
|
+
export class NNNodeLike<T extends NodeType> {
|
|
600
583
|
previous: NNNOrHead;
|
|
601
584
|
next: NNNOrTail;
|
|
602
585
|
startTime: TimeT;
|
|
@@ -618,7 +601,7 @@ export class NNNode extends NNNodeLike<NodeType.MIDDLE> {
|
|
|
618
601
|
super(NodeType.MIDDLE);
|
|
619
602
|
this.noteNodes = []
|
|
620
603
|
this.holdNodes = [];
|
|
621
|
-
this.startTime =
|
|
604
|
+
this.startTime = TC.validateIp([...time])
|
|
622
605
|
}
|
|
623
606
|
get endTime() {
|
|
624
607
|
let latest: TimeT = this.startTime;
|
|
@@ -681,7 +664,7 @@ export class NNNList {
|
|
|
681
664
|
const originalListLength = this.timesWithNotes || 512;
|
|
682
665
|
/*
|
|
683
666
|
if (!this.effectiveBeats) {
|
|
684
|
-
this.effectiveBeats =
|
|
667
|
+
this.effectiveBeats = TC.toBeats(this.tail.previous.endTime)
|
|
685
668
|
}
|
|
686
669
|
*/
|
|
687
670
|
const effectiveBeats: number = this.effectiveBeats;
|
|
@@ -695,25 +678,19 @@ export class NNNList {
|
|
|
695
678
|
return [null, null]
|
|
696
679
|
}
|
|
697
680
|
const nextNode = node.next;
|
|
698
|
-
const startTime = node.type === NodeType.HEAD ? 0 :
|
|
681
|
+
const startTime = node.type === NodeType.HEAD ? 0 : TC.toBeats((node as NNNode).startTime)
|
|
699
682
|
return [startTime, nextNode]
|
|
700
683
|
},
|
|
701
|
-
// @ts-ignore
|
|
702
684
|
(note: NNNode, beats: number) => {
|
|
703
|
-
return
|
|
704
|
-
}
|
|
705
|
-
/*,
|
|
706
|
-
(note: Note) => {
|
|
707
|
-
const prev = note.previous;
|
|
708
|
-
return prev.type === NodeType.HEAD ? note : prev
|
|
709
|
-
})*/)
|
|
685
|
+
return TC.toBeats(note.startTime) >= beats ? false : <NNNode>note.next; // getNodeAt有guard
|
|
686
|
+
})
|
|
710
687
|
}
|
|
711
688
|
getNodeAt(beats: number, beforeEnd=false): NNNode | NNNodeLike<NodeType.TAIL> {
|
|
712
689
|
return this.jump.getNodeAt(beats) as NNNode | NNNodeLike<NodeType.TAIL>;
|
|
713
690
|
}
|
|
714
691
|
getNode(time: TimeT): NNNode {
|
|
715
|
-
const node = this.getNodeAt(
|
|
716
|
-
if (node.type === NodeType.HEAD ||
|
|
692
|
+
const node = this.getNodeAt(TC.toBeats(time), false).previous;
|
|
693
|
+
if (node.type === NodeType.HEAD || TC.ne((node as NNNode).startTime, time)) {
|
|
717
694
|
const newNode = new NNNode(time);
|
|
718
695
|
const next = node.next
|
|
719
696
|
NNNode.insert(node, newNode, next);
|