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/LICENSE +21 -0
- package/README.md +9 -0
- package/chart.ts +481 -0
- package/chartTypes.ts +512 -0
- package/easing.ts +543 -0
- package/env.ts +7 -0
- package/evaluator.ts +173 -0
- package/event.ts +853 -0
- package/index.ts +11 -0
- package/judgeline.ts +605 -0
- package/jumparray.ts +234 -0
- package/note.ts +731 -0
- package/package.json +20 -0
- package/rpeChartCompiler.ts +425 -0
- package/time.ts +308 -0
- package/tsconfig.json +30 -0
- package/util.ts +26 -0
- package/version.ts +8 -0
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
|