kipphi 2.0.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/event.ts ADDED
@@ -0,0 +1,853 @@
1
+ import type { Chart } from "./chart";
2
+ import { EventType, type TimeT, type EventDataKPA, type RGB, type EventDataRPELike, InterpreteAs, type ValueTypeOfEventType, type EventNodeSequenceDataKPA, type EventDataKPA2, type EventNodeSequenceDataKPA2, type EventValueESType, EventValueType } from "./chartTypes";
3
+ import { TemplateEasingLib, BezierEasing, Easing, rpeEasingArray, SegmentedEasing, linearEasing, fixedEasing, TemplateEasing, NormalEasing } from "./easing";
4
+ import { ColorEasedEvaluator, EasedEvaluator, ExpressionEvaluator, NumericEasedEvaluator, TextEasedEvaluator, type Evaluator } from "./evaluator";
5
+ import { JumpArray } from "./jumparray";
6
+ import { TimeCalculator, TC } from "./time";
7
+ import { NodeType } from "./util";
8
+
9
+ /// #declaration:global
10
+ export class EventNodeLike<T extends NodeType, VT = number> {
11
+ type: T;
12
+ /** 后一个事件节点 */
13
+ next: [EventStartNode<VT>, null, ENOrTail<VT>][T] | null = null;
14
+ /** 前一个事件节点 */
15
+ previous: [null, EventStartNode<VT>, ENOrHead<VT>][T] | null = null;
16
+ parentSeq!: EventNodeSequence<VT>;
17
+ constructor(type: T) {
18
+ this.type = type;
19
+ }
20
+ }
21
+ export type ENOrTail<VT = number> = EventNode<VT> | EventNodeLike<NodeType.TAIL, VT>;
22
+ export type ENOrHead<VT = number> = EventNode<VT> | EventNodeLike<NodeType.HEAD, VT>;
23
+ export type AnyEN<VT = number> = EventNode<VT> | EventNodeLike<NodeType.HEAD, VT> | EventNodeLike<NodeType.TAIL, VT>;
24
+ export type EvSoE<VT = number> = EventEndNode<VT> | EventStartNode<VT>;
25
+
26
+ /**
27
+ * 事件节点基类
28
+ * event node.
29
+ * 用于代表事件的开始和结束。(EventStartNode表开始,EventEndNode表结束)
30
+ * Used to represent the starts (EventStartNode) and ends (EventEndNode) of events.
31
+ * 事件指的是判定线在某个时间段上的状态变化。
32
+ * Events is the changing of judge line's state in a certain time.
33
+ * 五种事件类型:移动X,移动Y,旋转,透明度,速度。
34
+ * 5 basic types of events: moveX, moveY, rotate, alpha, speed.
35
+ * 事件节点没有类型,类型由它所属的序列决定。
36
+ * Type is not event nodes' property; it is the property of EventNodeSequence.
37
+ * Events' type is determined by which sequence it belongs to.
38
+ * 与RPE不同的是,KPA使用两个节点来表示一个事件,而不是一个对象。
39
+ * Different from that in RPE, KPA uses two nodes rather than one object to represent an event.
40
+ */
41
+ export abstract class EventNode<VT = number> extends EventNodeLike<NodeType.MIDDLE, VT> {
42
+ time: TimeT;
43
+ value: VT;
44
+ evaluator: Evaluator<VT>;
45
+ constructor(time: TimeT, value: VT) {
46
+ super(NodeType.MIDDLE);
47
+ this.time = TimeCalculator.validateIp([...time]);
48
+ // @ts-ignore 不清楚什么时候会是undefined,但是留着准没错
49
+ this.value = value ?? 0;
50
+ if (typeof value === "number") {
51
+ this.evaluator = NumericEasedEvaluator.default as unknown as Evaluator<VT>;
52
+ } else if (typeof value === "string") {
53
+ this.evaluator = TextEasedEvaluator.default as unknown as Evaluator<VT>;
54
+ } else {
55
+ this.evaluator = ColorEasedEvaluator.default as unknown as Evaluator<VT>;
56
+ }
57
+ }
58
+ clone(offset: TimeT): EventStartNode<VT> | EventEndNode<VT> {
59
+ const ret = new (this.constructor as (typeof EventStartNode | typeof EventEndNode))
60
+ (offset ? TimeCalculator.add(this.time, offset) : this.time, this.value);
61
+ ret.evaluator = this.evaluator;
62
+ return ret;
63
+ }
64
+ //#region
65
+ /**
66
+ *
67
+ * @param data
68
+ * @param templates
69
+ * @returns
70
+ * @deprecated
71
+ */
72
+ static getEasing(data: EventDataKPA<any>, templates: TemplateEasingLib): Easing {
73
+ const left = data.easingLeft;
74
+ const right = data.easingRight;
75
+ if ((left && right) && (left !== 0.0 || right !== 1.0)) {
76
+ return new SegmentedEasing(EventNode.getEasing(data, templates), left, right)
77
+ }
78
+ if (data.bezier) {
79
+ let bp = data.bezierPoints
80
+ let easing = new BezierEasing([bp[0], bp[1]], [bp[2], bp[3]]);
81
+ return easing
82
+ } else if (typeof data.easingType === "string") {
83
+ return templates.get(data.easingType);
84
+ } else if (typeof data.easingType === "number" && data.easingType !== 0) {
85
+ return rpeEasingArray[data.easingType];
86
+ } else if (data.start === data.end) {
87
+ return fixedEasing;
88
+ } else {
89
+ return linearEasing;
90
+ }
91
+ }
92
+ /**
93
+ *
94
+ * @param data
95
+ * @param templates
96
+ * @returns
97
+ * @deprecated
98
+ */
99
+ static getEvaluator(data: EventDataKPA<any>, templates: TemplateEasingLib): Evaluator<any> {
100
+ const left = data.easingLeft;
101
+ const right = data.easingRight;
102
+ const wrap = (easing: Easing) => {
103
+ if (typeof data.start === "number") {
104
+ return new NumericEasedEvaluator(easing);
105
+ } else if (typeof data.start === "string") {
106
+ return new TextEasedEvaluator(easing, data.interpreteAs);
107
+ } else {
108
+ return new ColorEasedEvaluator(easing);
109
+ }
110
+ }
111
+ if ((left && right) && (left !== 0.0 || right !== 1.0)) {
112
+ return wrap(new SegmentedEasing(EventNode.getEasing(data, templates), left, right))
113
+ }
114
+ if (data.bezier) {
115
+ let bp = data.bezierPoints
116
+ let easing = new BezierEasing([bp[0], bp[1]], [bp[2], bp[3]]);
117
+ return wrap(easing)
118
+ } else if (data.isParametric) {
119
+ if (typeof data.easingType !== "string") {
120
+ throw new Error("Invalid easing: " + data.easingType);
121
+ }
122
+ return new ExpressionEvaluator(data.easingType);
123
+ } else if (typeof data.easingType === "string") {
124
+ return wrap(templates.get(data.easingType));
125
+ } else if (typeof data.easingType === "number" && data.easingType !== 0) {
126
+ return wrap(rpeEasingArray[data.easingType]);
127
+ } else if (data.start === data.end) {
128
+ return wrap(fixedEasing);
129
+ } else {
130
+ return wrap(linearEasing);
131
+ }
132
+ }
133
+ /**
134
+ * constructs EventStartNode and EventEndNode from EventDataRPE
135
+ * @param data
136
+ * @param templates
137
+ * @returns
138
+ */
139
+ static fromEvent<VT extends RGB | number>(data: EventDataRPELike<VT>, chart: Chart): [EventStartNode<VT>, EventEndNode<VT>] {
140
+ let start = new EventStartNode(data.startTime, data.start)
141
+ let end = new EventEndNode(data.endTime, data.end);
142
+ start.evaluator = EventNode.getEvaluator(data, chart.templateEasingLib);
143
+ EventNode.connect(start, end);
144
+ return [start, end]
145
+ }
146
+ static fromTextEvent(data: EventDataRPELike<string>, templates: TemplateEasingLib) : [EventStartNode<string>, EventEndNode<string>] {
147
+ let startValue = data.start;
148
+ let endValue = data.end;
149
+ let interpreteAs: InterpreteAs = InterpreteAs.str;
150
+ if (/%P%/.test(startValue) && /%P%/.test(endValue)) {
151
+ startValue = startValue.replace(/%P%/g, "");
152
+ endValue = endValue.replace(/%P%/g, "");
153
+ if (startValue.includes(".") || startValue.includes("e") || startValue.includes("E")
154
+ || endValue.includes(".") || endValue.includes("e") || endValue.includes("E")) {
155
+ startValue = parseFloat(startValue) + "";
156
+ endValue = parseFloat(endValue) + "";
157
+ interpreteAs = InterpreteAs.float;
158
+ } else {
159
+ startValue = parseInt(startValue) + "";
160
+ endValue = parseInt(endValue) + "";
161
+ interpreteAs = InterpreteAs.int;
162
+ }
163
+ }
164
+ let start = new EventStartNode<string>(data.startTime, startValue);
165
+ let end = new EventEndNode<string>(data.endTime, endValue);
166
+ start.interpretedAs = interpreteAs;
167
+ start.evaluator = EventNode.getEvaluator(data, templates);
168
+ EventNode.connect(start, end)
169
+ return [start, end]
170
+ }
171
+ static connect<VT>(node1: EventStartNode<VT>, node2: EventEndNode<VT> | EventNodeLike<NodeType.TAIL, VT>): void
172
+ static connect<VT>(node1: EventEndNode<VT> | EventNodeLike<NodeType.HEAD, VT>, node2: EventStartNode<VT>): void
173
+ static connect<VT>(node1: ENOrHead<VT>, node2: ENOrTail<VT>): void {
174
+ node1.next = node2;
175
+ node2.previous = node1;
176
+ if (node1 && node2) {
177
+ node2.parentSeq = node1.parentSeq
178
+ }
179
+ }
180
+ /**
181
+ *
182
+ * @param endNode
183
+ * @param startNode
184
+ * @returns 应该在何范围内更新跳数组
185
+ */
186
+ static removeNodePair<VT>(endNode: EventEndNode<VT>, startNode: EventStartNode<VT>): [EventStartNode<VT> | EventNodeLike<NodeType.HEAD, VT>, EventStartNode<VT> | EventNodeLike<NodeType.TAIL,VT>] {
187
+ const prev = endNode.previous;
188
+ const next = startNode.next;
189
+ prev.next = next;
190
+ next.previous = prev;
191
+ endNode.previous = null;
192
+ startNode.next = null;
193
+ endNode.parentSeq = null;
194
+ startNode.parentSeq = null; // 每亩的东西(
195
+ return [this.previousStartOfStart(prev), this.nextStartOfEnd(next)]
196
+ }
197
+ static insert<VT>(node: EventStartNode<VT>, tarPrev: EventStartNode<VT>): [EventNodeLike<NodeType.HEAD, VT> | EventStartNode<VT>, EventStartNode<VT> | EventNodeLike<NodeType.TAIL, VT>] {
198
+ const tarNext = tarPrev.next;
199
+ if (node.previous.type === NodeType.HEAD) {
200
+ throw new Error("Cannot insert a head node before any node");
201
+ }
202
+ this.connect(tarPrev, node.previous);
203
+ node.parentSeq = node.previous.parentSeq;
204
+ this.connect(node, tarNext);
205
+ return [this.previousStartOfStart(tarPrev), this.nextStartOfEnd(tarNext)]
206
+ }
207
+ /**
208
+ *
209
+ * @param node
210
+ * @returns the next node if it is a tailer, otherwise the next start node
211
+ */
212
+ static nextStartOfStart<VT>(node: EventStartNode<VT>) {
213
+ return node.next.type === NodeType.TAIL ? node.next : node.next.next
214
+ }
215
+ /**
216
+ *
217
+ * @param node
218
+ * @returns itself if node is a tailer, otherwise the next start node
219
+ */
220
+ static nextStartOfEnd<VT>(node: EventEndNode<VT> | EventNodeLike<NodeType.TAIL, VT>) {
221
+ return node.type === NodeType.TAIL ? node : node.next
222
+ }
223
+ static previousStartOfStart<VT>(node: EventStartNode<VT>): EventStartNode<VT> | EventNodeLike<NodeType.HEAD, VT> {
224
+ return node.previous.type === NodeType.HEAD ? node.previous : node.previous.previous;
225
+ }
226
+ /**
227
+ * It does not return the start node which form an event with it.
228
+ * @param node
229
+ * @returns
230
+ */
231
+ static secondPreviousStartOfEnd<VT>(node: EventEndNode<VT>): EventStartNode<VT> | EventNodeLike<NodeType.HEAD, VT> {
232
+ return this.previousStartOfStart(node.previous);
233
+ }
234
+ static nextStartInJumpArray<VT>(node: EventStartNode<VT>): EventStartNode<VT> | EventNodeLike<NodeType.TAIL, VT> {
235
+ if ((node.next as EventEndNode<VT>).next.isLastStart()) {
236
+ return node.next.next.next as EventNodeLike<NodeType.TAIL, VT>;
237
+ } else {
238
+ return node.next.next;
239
+ }
240
+ }
241
+ /**
242
+ * 获得一对背靠背的节点。不适用于第一个StartNode
243
+ * @param node
244
+ * @returns
245
+ */
246
+ static getEndStart<VT>(node: EventStartNode<VT> | EventEndNode<VT>): [EventEndNode<VT>, EventStartNode<VT>] {
247
+ if (node instanceof EventStartNode) {
248
+ if (node.isFirstStart()) {
249
+ throw new Error("Cannot get previous start node of the first start node");
250
+ }
251
+ return [<EventEndNode<VT>>node.previous, node]
252
+ } else if (node instanceof EventEndNode) {
253
+ return [node, node.next]
254
+ }
255
+ }
256
+ static getStartEnd<VT>(node: EventStartNode<VT> | EventEndNode<VT>): [EventStartNode<VT>, EventEndNode<VT>] {
257
+ if (node instanceof EventStartNode) {
258
+ return [node, <EventEndNode<VT>>node.next]
259
+ } else if (node instanceof EventEndNode) {
260
+ return [<EventStartNode<VT>>node.previous, node]
261
+ } else {
262
+ throw new Error("Invalid node type")
263
+ }
264
+ }
265
+ static setToNewOrderedArray<VT>(dest: TimeT, set: Set<EventStartNode<VT>>): [EventStartNode<VT>[], EventStartNode<VT>[]] {
266
+ const nodes = [...set]
267
+ nodes.sort((a, b) => TimeCalculator.gt(a.time, b.time) ? 1 : -1);
268
+ const offset = TimeCalculator.sub(dest, nodes[0].time)
269
+ return [nodes, nodes.map(node => node.clonePair(offset))]
270
+ }
271
+ static belongToSequence(nodes: Set<EventStartNode>, sequence: EventNodeSequence): boolean {
272
+ for (let each of nodes) {
273
+ if (each.parentSeq !== sequence) {
274
+ return false;
275
+ }
276
+ }
277
+ return true;
278
+ }
279
+ /**
280
+ * 检验这些节点对是不是连续的
281
+ * 如果不是不能封装为模板缓动
282
+ * @param nodes 有序开始节点数组,必须都是带结束节点的(背靠背)(第一个除外)
283
+ * @returns
284
+ */
285
+ static isContinuous(nodes: EventStartNode[]) {
286
+ const l = nodes.length;
287
+ let nextNode = nodes[0]
288
+ for (let i = 0; i < l - 1; i++) {
289
+ const node = nextNode;
290
+ nextNode = nodes[i + 1];
291
+ if (node.next !== nextNode.previous) {
292
+ return false;
293
+ }
294
+ }
295
+ return true;
296
+ }
297
+ // #endregion
298
+ }
299
+
300
+
301
+ const getValueFns = [
302
+ (current: number, timeDelta: number, value: number, nextVal: number, easing: Easing) => {
303
+
304
+ // @ts-ignore TSC脑壳有问题
305
+ const valueDelta = nextVal - value
306
+ // 其他类型,包括普通缓动和非钩定模板缓动
307
+ return value + easing.getValue(current / timeDelta) * valueDelta
308
+ },
309
+ (current: number, timeDelta: number, value: string, nextVal: string, easing: Easing, interpretedAs: InterpreteAs): string => {
310
+
311
+ if (interpretedAs === InterpreteAs.float) {
312
+ const start = parseFloat(value);
313
+ const delta = parseFloat(nextVal as string) - start;
314
+ return start + easing.getValue(current / timeDelta) * delta + "";
315
+ } else if (interpretedAs === InterpreteAs.int) {
316
+ const start = parseInt(value);
317
+ const delta = parseInt(nextVal as string) - start;
318
+ return start + Math.round(easing.getValue(current / timeDelta) * delta) + "";
319
+ } else if (value.startsWith(nextVal as string)) {
320
+ const startLen = (nextVal as string).length;
321
+ const deltaLen = value.length - startLen;
322
+ const len = startLen + Math.floor(deltaLen * easing.getValue(current / timeDelta));
323
+ return value.substring(0, len);
324
+ } else if ((nextVal as string).startsWith(value)) {
325
+ const startLen = value.length;
326
+ const deltaLen = (nextVal as string).length - startLen;
327
+ const len = startLen + Math.floor(deltaLen * easing.getValue(current / timeDelta));
328
+ return (nextVal as string).substring(0, len);
329
+ } else {
330
+ return value;
331
+ }
332
+
333
+ },
334
+ (current: number, timeDelta: number, value: RGB, nextValue: RGB, easing: Easing) => {
335
+ return (value as RGB).map((v, i) => {
336
+ const nextVal = nextValue[i];
337
+ const value = v;
338
+ if (nextVal === value) {
339
+ return value;
340
+ } else {
341
+ const delta = nextVal - value;
342
+ return value + easing.getValue(current / timeDelta) * delta;
343
+ }
344
+ })
345
+ }
346
+ ] as const;
347
+
348
+
349
+
350
+ export class EventStartNode<VT = number> extends EventNode<VT> {
351
+ override next: EventEndNode<VT> | EventNodeLike<NodeType.TAIL, VT>;
352
+ override previous: EventEndNode<VT> | EventNodeLike<NodeType.HEAD, VT>;
353
+ /**
354
+ * 对于速度事件,从计算时的时刻到此节点的总积分
355
+ */
356
+ cachedIntegral?: number;
357
+ constructor(time: TimeT, value: VT) {
358
+ super(time, value);
359
+ // 最史的一集,what can i say
360
+ if (typeof value === "number") {
361
+ // @ts-ignore
362
+ this.getValueFn = getValueFns[0];
363
+ } else if (typeof value === "string") {
364
+ // @ts-ignore
365
+ this.getValueFn = getValueFns[1];
366
+ } else {
367
+ // @ts-ignore
368
+ this.getValueFn = getValueFns[2];
369
+ }
370
+ }
371
+ override parentSeq: EventNodeSequence<VT>;
372
+ /**
373
+ * 因为是RPE和KPA共用的方法所以easingType可以为字符串
374
+ * @returns
375
+ */
376
+ dump(): EventDataKPA2<VT> {
377
+
378
+ const endNode = this.next as EventEndNode<VT>;
379
+ return {
380
+ start: this.value,
381
+ end: endNode.value,
382
+ startTime: this.time,
383
+ endTime: endNode.time,
384
+ evaluator: this.evaluator.dump(),
385
+ }
386
+ }
387
+ /**
388
+ * 产生一个一拍长的短钩定事件
389
+ * 仅用于编译至RPE时解决最后一个StartNode的问题
390
+ * 限最后一个StartNode使用
391
+ * @returns
392
+ * /
393
+ dumpAsLast(): EventDataRPELike<VT> {
394
+ const isSegmented = this.easingIsSegmented
395
+ const easing = isSegmented ? (this.easing as SegmentedEasing).easing : this.easing;
396
+ return {
397
+ bezier: easing instanceof BezierEasing ? 1 : 0,
398
+ bezierPoints: easing instanceof BezierEasing ?
399
+ [easing.cp1[0], easing.cp1[1], easing.cp2[0], easing.cp2[1]] : // 修正了这里 cp2.y 的引用
400
+ [0, 0, 0, 0],
401
+ easingLeft: isSegmented ? (this.easing as SegmentedEasing).left : 0.0,
402
+ easingRight: isSegmented ? (this.easing as SegmentedEasing).right : 1.0,
403
+ easingType: easing instanceof TemplateEasing ?
404
+ (easing.name) :
405
+ easing instanceof NormalEasing ?
406
+ easing.rpeId :
407
+ null,
408
+ end: this.value,
409
+ endTime: TimeCalculator.add(this.time, [1, 0, 1]),
410
+ linkgroup: 0, // 假设默认值为 0
411
+ start: this.value,
412
+ startTime: this.time,
413
+ }
414
+ }//*/
415
+ interpretedAs: InterpreteAs = InterpreteAs.str;
416
+ getValueAt(beats: number): VT {
417
+ // 除了尾部的开始节点,其他都有下个节点
418
+ // 钩定型缓动也有
419
+ if (this.next.type === NodeType.TAIL) {
420
+ return this.value;
421
+ }
422
+ return this.evaluator.eval(this, beats);
423
+ }
424
+ private getValueFn: (current: number, timeDelta: number, value: VT, nextVal: VT, easing: Easing, interpreteAs?: InterpreteAs) => VT
425
+ getSpeedValueAt(this: EventStartNode<number>, beats: number) {
426
+ if (this.next.type === NodeType.TAIL) {
427
+ return this.value
428
+ }
429
+ let timeDelta = TimeCalculator.getDelta(this.next.time, this.time)
430
+ let valueDelta = this.next.value - this.value
431
+ let current = beats - TimeCalculator.toBeats(this.time)
432
+ if (current > timeDelta || current < 0) {
433
+ console.warn("超过事件时间范围!", this, beats)
434
+ // debugger
435
+ }
436
+ return this.value + linearEasing.getValue(current / timeDelta) * valueDelta;
437
+ }
438
+ /**
439
+ * 积分获取位移
440
+ */
441
+ getIntegral(this: EventStartNode<number>, beats: number, timeCalculator: TimeCalculator) {
442
+ return timeCalculator.segmentToSeconds(TimeCalculator.toBeats(this.time), beats) * (this.value + this.getSpeedValueAt(beats)) / 2 * 120 // 每单位120px
443
+ }
444
+ getFullIntegral(this: EventStartNode<number>, timeCalculator: TimeCalculator) {
445
+ if (this.next.type === NodeType.TAIL) {
446
+ console.log(this)
447
+ throw new Error("getFullIntegral不可用于尾部节点")
448
+ }
449
+ let end = this.next;
450
+ let endBeats = TimeCalculator.toBeats(end.time)
451
+ let startBeats = TimeCalculator.toBeats(this.time)
452
+ // 原来这里写反了,气死偶咧!
453
+ return timeCalculator.segmentToSeconds(startBeats, endBeats) * (this.value + end.value) / 2 * 120
454
+ }
455
+ isFirstStart() {
456
+ return this.previous && this.previous.type === NodeType.HEAD
457
+ }
458
+ isLastStart() {
459
+ return this.next && this.next.type === NodeType.TAIL
460
+ }
461
+ override clone(offset?: TimeT): EventStartNode<VT> {
462
+ return super.clone(offset) as EventStartNode<VT>;
463
+ };
464
+ clonePair(offset: TimeT): EventStartNode<VT> {
465
+ const endNode = this.previous.type !== NodeType.HEAD ? this.previous.clone(offset) : new EventEndNode(this.time, this.value);
466
+ const startNode = this.clone(offset);
467
+ EventNode.connect(endNode, startNode);
468
+ return startNode;
469
+ };
470
+
471
+
472
+ drawCurve(context: CanvasRenderingContext2D, startX: number, startY: number, endX: number , endY: number, matrix: Matrix): void {
473
+ if (!(this.easing instanceof ParametricEquationEasing)) {
474
+ return this.easing.drawCurve(context, startX, startY, endX, endY);
475
+ }
476
+ const getValue = (ratio: number) => {
477
+ return matrix.ymul(0, this.easing.getValue(ratio))
478
+ }
479
+ const timeDelta = endX - startX;
480
+ let last = startY;
481
+ context.beginPath()
482
+ context.moveTo(startX, last)
483
+ for (let t = 4; t <= timeDelta; t += 4) {
484
+ const ratio = t / timeDelta
485
+ const curPosY = getValue(ratio);
486
+ context.lineTo(startX + t, curPosY);
487
+ last = curPosY;
488
+ }
489
+ context.stroke();
490
+ }
491
+ }
492
+
493
+ export class EventEndNode<VT = number> extends EventNode<VT> {
494
+ override next!: EventStartNode<VT>;
495
+ override previous!: EventStartNode<VT>;
496
+ override get parentSeq(): EventNodeSequence<VT> {return this.previous?.parentSeq || null}
497
+ override set parentSeq(_parent: EventNodeSequence<VT>) {}
498
+ constructor(time: TimeT, value: VT) {
499
+ super(time, value);
500
+ }
501
+ getValueAt(beats: number) {
502
+ return this.previous.getValueAt(beats);
503
+ }
504
+ override clone(offset?: TimeT): EventEndNode<VT> {
505
+ return super.clone(offset) as EventEndNode<VT>;
506
+ }
507
+ }
508
+
509
+
510
+ /**
511
+ * 为一个链表结构。会有一个数组进行快跳。
512
+ * is the list of event nodes, but not purely start nodes.
513
+ *
514
+ * 结构如下:Header -> (StartNode -> [EndNode) -> (StartNode] -> [EndNode) -> ... -> StartNode] -> Tailer.
515
+ * The structure is like this: Header -> (StartNode -> [EndNode) -> (StartNode] -> [EndNode) -> ... -> StartNode] -> Tailer.
516
+ *
517
+ * 用括号标出的两个节点是一个事件,用方括号标出的两个节点是同一时间点的节点。
518
+ * The each 2 nodes marked by parentheses is an event; the each 2 nodes marked by brackets have the same time.
519
+ *
520
+ * 注意尾节点之前的节点不是一个结束节点,而是一个开始节点,其缓动无效。
521
+ * Note that the node before the tailer is not an end node, but a start node whose easing is meaningless.
522
+ *
523
+ * 就是说最后一个节点后取值,显然会取得这个节点的值,与缓动无关。
524
+ * (i. e. the value after the last event node is its value, not subject to easing, obviously.)
525
+ *
526
+ * 如果尾之前的节点是一个结束节点,那么取值会返回undefined,这是不期望的。
527
+ * If so, the value after that will be undefined, which is not expected.
528
+ * ("so" refers to the assumption that the node before the tailer is an end node)
529
+ *
530
+ * 和NNList和NNNList一样,有跳数组以加速随机读取。
531
+ * Like NNList and NNNList, it has a jump array to speed up random reading.
532
+ *
533
+ * 插入或删除节点时,需要更新跳数组。
534
+ * Remember to update the jump array when inserting or deleting nodes.
535
+ */
536
+ export class EventNodeSequence<VT = number> { // 泛型的传染性这一块
537
+ chart: Chart;
538
+ /** id follows the format `#${lineid}.${layerid}.${typename}` by default */
539
+ id: string;
540
+ /** has no time or value */
541
+ head: EventNodeLike<NodeType.HEAD, VT>;
542
+ /** has no time or value */
543
+ tail: EventNodeLike<NodeType.TAIL, VT>;
544
+ jump?: JumpArray<AnyEN<VT>>;
545
+ listLength: number;
546
+ /** 一定是二的幂,避免浮点误差 */
547
+ jumpAverageBeats: number;
548
+ // nodes: EventNode[];
549
+ // startNodes: EventStartNode[];
550
+ // endNodes: EventEndNode[];
551
+ // eventTime: Float64Array;
552
+ constructor(public type: EventType, public effectiveBeats: number) {
553
+ this.head = new EventNodeLike(NodeType.HEAD);
554
+ this.tail = new EventNodeLike(NodeType.TAIL);
555
+ this.head.parentSeq = this.tail.parentSeq = this;
556
+ this.listLength = 1;
557
+ // this.head = this.tail = new EventStartNode([0, 0, 0], 0)
558
+ // this.nodes = [];
559
+ // this.startNodes = [];
560
+ // this.endNodes = [];
561
+ }
562
+ static getDefaultValueFromEventType(type: EventType) {
563
+
564
+ return type === EventType.speed ? 10 :
565
+ type === EventType.scaleX || type === EventType.scaleY ? 1.0 :
566
+ type === EventType.text ? "" :
567
+ type === EventType.color ? [0, 0, 0] :
568
+ 0
569
+ }
570
+ static fromRPEJSON<T extends EventType, VT = number>(type: T, data: EventDataRPELike<VT>[], chart: Chart, endValue?: number) {
571
+ const {templateEasingLib: templates} = chart
572
+ const length = data.length;
573
+ // const isSpeed = type === EventType.Speed;
574
+ // console.log(isSpeed)
575
+ const seq = new EventNodeSequence<VT>(type, type === EventType.easing ? TimeCalculator.toBeats(data[length - 1].endTime) : chart.effectiveBeats);
576
+ let listLength = length;
577
+ let lastEnd: EventEndNode<VT> | EventNodeLike<NodeType.HEAD, VT> = seq.head;
578
+ // 如果第一个事件不从0时间开始,那么添加一对面对面节点来垫背
579
+ if (data[0] && TC.ne(data[0].startTime, [0, 0, 1])) {
580
+ const value = data[0].start
581
+ const start = new EventStartNode<VT>([0, 0, 1], value as VT);
582
+ const end = new EventEndNode<VT>(data[0].startTime, value as VT);
583
+ EventNode.connect(lastEnd, start);
584
+ EventNode.connect(start, end);
585
+ lastEnd = end;
586
+ }
587
+
588
+ let lastIntegral: number = 0;
589
+ for (let index = 0; index < length; index++) {
590
+ const event = data[index];
591
+ // @ts-ignore
592
+ let [start, end] = (type === EventType.text ? EventNode.fromTextEvent(event, templates) : EventNode.fromEvent(event, templates)) as unknown as [EventStartNode<VT>, EventEndNode<VT>];
593
+ if (lastEnd.type === NodeType.HEAD) {
594
+ EventNode.connect(lastEnd, start)
595
+ // 如果上一个是钩定事件,那么一块捋平
596
+ } else if (lastEnd.value === lastEnd.previous.value && lastEnd.previous.evaluator instanceof EasedEvaluator) {
597
+ lastEnd.time = start.time
598
+ EventNode.connect(lastEnd, start)
599
+ } else if (TimeCalculator.toBeats(lastEnd.time) !== TimeCalculator.toBeats(start.time)) {
600
+ let val = lastEnd.value;
601
+ let midStart = new EventStartNode(lastEnd.time, val);
602
+ let midEnd = new EventEndNode(start.time, val);
603
+ midStart.evaluator = lastEnd.previous.evaluator;
604
+ EventNode.connect(lastEnd, midStart);
605
+ EventNode.connect(midStart, midEnd);
606
+ EventNode.connect(midEnd, start)
607
+ // seq.startNodes.push(midStart);
608
+ // seq.endNodes.push(midEnd);
609
+ listLength++;
610
+ } else {
611
+
612
+ EventNode.connect(lastEnd, start)
613
+ }
614
+
615
+ // seq.startNodes.push(start);
616
+ // seq.endNodes.push(end);
617
+ lastEnd = end;
618
+ // seq.nodes.push(start, end);
619
+ }
620
+ const last = lastEnd;
621
+ const tail = new EventStartNode(
622
+ last.type === NodeType.HEAD ? [0, 0, 1] : last.time,
623
+ last.type === NodeType.HEAD ? endValue : last.value
624
+ );
625
+ EventNode.connect(last, tail);
626
+ // last can be a header, in which case easing is undefined.
627
+ // then we use the easing that initialized in the EventStartNode constructor.
628
+ tail.evaluator = last.previous?.evaluator ?? tail.evaluator;
629
+ tail.cachedIntegral = lastIntegral
630
+ EventNode.connect(tail, seq.tail)
631
+ seq.listLength = listLength;
632
+ seq.initJump();
633
+ return seq;
634
+ }
635
+ static fromKPA2JSON<T extends EventType, VT extends EventValueESType = number>(type: T, data: EventDataKPA2<VT>[], chart: Chart, endValue?: number) {
636
+ const {templateEasingLib: templates} = chart
637
+ const length = data.length;
638
+ // const isSpeed = type === EventType.Speed;
639
+ // console.log(isSpeed)
640
+ const seq = new EventNodeSequence<VT>(type, type === EventType.easing ? TimeCalculator.toBeats(data[length - 1].endTime) : chart.effectiveBeats);
641
+ let listLength = length;
642
+ let lastEnd: EventEndNode<VT> | EventNodeLike<NodeType.HEAD, VT> = seq.head;
643
+ // 如果第一个事件不从0时间开始,那么添加一对面对面节点来垫背
644
+ if (data[0] && TC.ne(data[0].startTime, [0, 0, 1])) {
645
+ const value = data[0].start
646
+ const start = new EventStartNode<VT>([0, 0, 1], value as VT);
647
+ const end = new EventEndNode<VT>(data[0].startTime, value as VT);
648
+ EventNode.connect(lastEnd, start);
649
+ EventNode.connect(start, end);
650
+ lastEnd = end;
651
+ }
652
+
653
+ const valueType = type === EventType.color ? EventValueType.color
654
+ : type === EventType.text ? EventValueType.text
655
+ : EventValueType.numeric;
656
+
657
+ let lastIntegral: number = 0;
658
+ for (let index = 0; index < length; index++) {
659
+ const event = data[index];
660
+ let [start, end] = chart.createEventFromData<VT>(event, valueType);
661
+ if (lastEnd.type === NodeType.HEAD) {
662
+ EventNode.connect(lastEnd, start)
663
+ // 如果上一个是钩定事件,那么一块捋平
664
+ } else if (lastEnd.value === lastEnd.previous.value && lastEnd.previous.evaluator instanceof EasedEvaluator) {
665
+ lastEnd.time = start.time
666
+ EventNode.connect(lastEnd, start)
667
+ } else if (TimeCalculator.toBeats(lastEnd.time) !== TimeCalculator.toBeats(start.time)) {
668
+ let val = lastEnd.value;
669
+ let midStart = new EventStartNode(lastEnd.time, val);
670
+ let midEnd = new EventEndNode(start.time, val);
671
+ midStart.evaluator = lastEnd.previous.evaluator;
672
+ EventNode.connect(lastEnd, midStart);
673
+ EventNode.connect(midStart, midEnd);
674
+ EventNode.connect(midEnd, start);
675
+ listLength++;
676
+ } else {
677
+
678
+ EventNode.connect(lastEnd, start)
679
+ }
680
+
681
+ lastEnd = end;
682
+ }
683
+ const last = lastEnd;
684
+ const tail = new EventStartNode(
685
+ last.type === NodeType.HEAD ? [0, 0, 1] : last.time,
686
+ last.type === NodeType.HEAD ? endValue : last.value
687
+ );
688
+ EventNode.connect(last, tail);
689
+ // last can be a header, in which case easing is undefined.
690
+ // then we use the easing that initialized in the EventStartNode constructor.
691
+ tail.evaluator = last.previous?.evaluator ?? tail.evaluator;
692
+ tail.cachedIntegral = lastIntegral
693
+ EventNode.connect(tail, seq.tail)
694
+ seq.listLength = listLength;
695
+ seq.initJump();
696
+ return seq;
697
+ }
698
+ /**
699
+ * 生成一个新的事件节点序列,仅拥有一个节点。
700
+ * 需要分配ID!!!!!!
701
+ * @param type
702
+ * @param effectiveBeats
703
+ * @returns
704
+ */
705
+ static newSeq<T extends EventType>(type: T, effectiveBeats: number): EventNodeSequence<ValueTypeOfEventType<T>> {
706
+ type V = ValueTypeOfEventType<T>
707
+ const sequence = new EventNodeSequence<V>(type, effectiveBeats);
708
+ const node = new EventStartNode<V>(
709
+ [0, 0, 1], EventNodeSequence.getDefaultValueFromEventType(type) as V
710
+ );
711
+ EventNode.connect(sequence.head, node)
712
+ EventNode.connect(node, sequence.tail)
713
+ sequence.initJump();
714
+ return sequence;
715
+ }
716
+ initJump() {
717
+ const originalListLength = this.listLength;
718
+ const effectiveBeats: number = this.effectiveBeats;
719
+ if (this.head.next === this.tail.previous) {
720
+ return;
721
+ }
722
+ this.jump = new JumpArray<AnyEN<VT>>(
723
+ this.head,
724
+ this.tail,
725
+ originalListLength,
726
+ effectiveBeats,
727
+ (node) => {
728
+ // console.log(node)
729
+ if (node.type === NodeType.TAIL) {
730
+ return [null, null]
731
+ }
732
+ if (node.type === NodeType.HEAD) {
733
+ if (node.next.next.type === NodeType.TAIL) {
734
+ return [0, node.next.next]
735
+ }
736
+ return [0, node.next]
737
+ }
738
+ const endNode = (node as EventStartNode<VT>).next as EventEndNode<VT>;
739
+ const time = TimeCalculator.toBeats(endNode.time);
740
+ const nextNode = endNode.next;
741
+ if (nextNode.next.type === NodeType.TAIL) {
742
+ return [time, nextNode.next] // Tailer代替最后一个StartNode去占位
743
+ } else {
744
+ return [time, nextNode]
745
+ }
746
+ },
747
+ (node: EventStartNode<VT>, beats: number) => {
748
+ return TimeCalculator.toBeats((node.next as EventEndNode<VT>).time) > beats ? false : EventNode.nextStartInJumpArray(node)
749
+ },
750
+ (node: EventStartNode) => {
751
+ return node.next && node.next.type === NodeType.TAIL ? node.next : node;
752
+ }
753
+ /*(node: EventStartNode) => {
754
+ const prev = node.previous;
755
+ return prev.type === NodeType.HEAD ? node : prev.previous;
756
+ }*/
757
+ )
758
+ }
759
+ updateJump(from: ENOrHead<VT>, to: ENOrTail<VT>) {
760
+ if (!this.jump || this.effectiveBeats !== this.jump.effectiveBeats) {
761
+ this.initJump();
762
+
763
+ }
764
+ this.jump.updateRange(from, to);
765
+ }
766
+ insert() {
767
+
768
+ }
769
+ getNodeAt(beats: number, usePrev: boolean = false): EventStartNode<VT> {
770
+ let node = this.jump?.getNodeAt(beats) as (EventStartNode<VT> | EventNodeLike<NodeType.TAIL, VT>)
771
+ || this.head.next as (EventStartNode<VT> | EventNodeLike<NodeType.TAIL, VT>);
772
+ if (node.type === NodeType.TAIL) {
773
+ if (usePrev) {
774
+ return node.previous.previous.previous;
775
+ }
776
+ // 最后一个事件节点本身具有无限延伸的特性
777
+ return node.previous;
778
+ }
779
+ if (usePrev && TimeCalculator.toBeats(node.time) === beats) {
780
+ const prev = node.previous;
781
+ if (!(prev.type === NodeType.HEAD)) {
782
+ node = prev.previous;
783
+ }
784
+ }
785
+ if (TimeCalculator.toBeats(node.time) > beats && beats >= 0) {
786
+ console.warn("Got a node after the given beats. This would only happen when the given beats is negative. Beats and Node:", beats, node)
787
+ }
788
+ return node;
789
+ }
790
+ getValueAt(beats: number, usePrev: boolean = false): VT {
791
+ return this.getNodeAt(beats, usePrev).getValueAt(beats);
792
+ }
793
+ getIntegral(this: EventNodeSequence<number>, beats: number, timeCalculator: TimeCalculator) {
794
+ const node: EventStartNode<number> = this.getNodeAt(beats);
795
+ return node.getIntegral(beats, timeCalculator) + node.cachedIntegral
796
+ }
797
+ updateNodesIntegralFrom(this: EventNodeSequence<number>, beats: number, timeCalculator: TimeCalculator) {
798
+ let previousStartNode = this.getNodeAt(beats);
799
+ previousStartNode.cachedIntegral = -previousStartNode.getIntegral(beats, timeCalculator);
800
+ let totalIntegral: number = previousStartNode.cachedIntegral
801
+ let endNode: EventEndNode | EventNodeLike<NodeType.TAIL>;
802
+ while ((endNode = previousStartNode.next).type !== NodeType.TAIL) {
803
+ const currentStartNode = endNode.next
804
+ totalIntegral += previousStartNode.getFullIntegral(timeCalculator);
805
+ currentStartNode.cachedIntegral = totalIntegral;
806
+ previousStartNode = currentStartNode;
807
+ }
808
+ }
809
+ dump(): EventNodeSequenceDataKPA2<VT> {
810
+ const nodes: EventDataKPA2<VT>[] = [];
811
+ let currentNode: EventStartNode<VT> = this.head.next;
812
+
813
+ while (currentNode && !(currentNode.next.type === NodeType.TAIL)) {
814
+ const eventData = currentNode.dump();
815
+ nodes.push(eventData);
816
+ currentNode = currentNode.next.next;
817
+ }
818
+
819
+ return {
820
+ type: this.type,
821
+ events: nodes,
822
+ id: this.id,
823
+ endValue: currentNode.value
824
+ };
825
+ }
826
+
827
+ getNodesFromOneAndRangeRight(node: EventStartNode<VT>, rangeRight: TimeT) {
828
+ const arr = []
829
+ for (; !TC.gt(node.time, rangeRight); ) {
830
+ const next = node.next;
831
+ arr.push(node);
832
+ if (next.type === NodeType.TAIL) {
833
+ break;
834
+ }
835
+ node = next.next;
836
+ }
837
+ return arr;
838
+ }
839
+ getNodesAfterOne(node: EventStartNode<VT>) {
840
+ const arr = []
841
+ while (true) {
842
+ const next = node.next;
843
+ if (next.type === NodeType.TAIL) {
844
+ break;
845
+ }
846
+ node = next.next;
847
+ arr.push(node);
848
+ }
849
+ return arr;
850
+ }
851
+ }
852
+
853
+ /// #enddeclaration