kipphi 2.0.0 → 2.1.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.
@@ -0,0 +1,511 @@
1
+ import type { Chart } from "../chart";
2
+ import { EventType, type EventValueESType, type TimeT } from "../chartTypes";
3
+ import { TemplateEasing, TemplateEasingLib } from "../easing";
4
+ import { err } from "../env";
5
+ import { Evaluator, EasedEvaluator, NumericEasedEvaluator } from "../evaluator";
6
+ import { EventEndNode, EventStartNode, EventNodeSequence, EventNode, SpeedENS, NonLastStartNode, EventNodeLike } from "../event";
7
+ import TC from "../time";
8
+ import { NodeType } from "../util";
9
+ import { ComplexOperation, LazyOperation, Operation, UnionOperation } from "./basic";
10
+
11
+ /**
12
+ * 移除一对节点(背靠背)
13
+ */
14
+ export class EventNodePairRemoveOperation extends Operation {
15
+ updatesEditor = true;
16
+ endNode: EventEndNode<any>;
17
+ startNode: EventStartNode<any>;
18
+ sequence: EventNodeSequence<any>;
19
+ originalPrev: EventStartNode<any>;
20
+ updatesFP = false;
21
+ constructor(node: EventStartNode<any>, updatesFP = true) {
22
+ super();
23
+ if (node.previous === null) {
24
+ this.ineffective = true;
25
+ return;
26
+ }
27
+ if (node.isFirstStart()) {
28
+ this.ineffective = true;
29
+ return;
30
+ }
31
+ if (node.isSpeed()) {
32
+ updatesFP = updatesFP;
33
+ }
34
+ [this.endNode, this.startNode] = EventNode.getEndStart(node)
35
+ this.sequence = this.startNode.parentSeq
36
+ this.originalPrev = (<EventEndNode>node.previous).previous
37
+ }
38
+ do(chart: Chart) {
39
+ this.sequence.updateJump(...EventNode.removeNodePair(this.endNode, this.startNode))
40
+ if (this.updatesFP) {
41
+ // updatesFP的校验确保了序列为速度序列
42
+ (this.sequence as SpeedENS).updateFloorPositionAfter(this.originalPrev, chart.timeCalculator)
43
+ }
44
+ }
45
+ undo(chart: Chart) {
46
+ this.sequence.updateJump(...EventNode.insert(this.startNode, this.originalPrev))
47
+ if (this.updatesFP) {
48
+ (this.sequence as SpeedENS).updateFloorPositionAfter(this.originalPrev, chart.timeCalculator)
49
+ }
50
+ }
51
+ }
52
+
53
+ /**
54
+ * 将一对孤立的节点对插入到一个开始节点之后的操作。
55
+ *
56
+ * 如果这个节点对的时刻与节点对的时刻相同,那么抛出错误。
57
+ */
58
+ export class EventNodePairInsertOperation <VT extends EventValueESType> extends Operation {
59
+ updatesEditor = true
60
+ node: EventStartNode<VT>;
61
+ tarPrev: EventStartNode<VT>;
62
+ sequence: EventNodeSequence<VT>;
63
+ originalValue: VT;
64
+ value: VT;
65
+ updatesFP = false;
66
+ /**
67
+ *
68
+ * @param node 要插入的节点 the node to insert
69
+ * @param targetPrevious 要插在谁后面 The node to insert after, accessed through `EventNodeSequence.getNodeAt(TC.toBeats(node))`
70
+ */
71
+ constructor(node: EventStartNode<VT>, targetPrevious: EventStartNode<VT>, updatesFP = true) {
72
+ super()
73
+ this.node = node;
74
+ this.tarPrev = targetPrevious
75
+ this.sequence = targetPrevious.parentSeq
76
+ if (TC.eq(node.time, targetPrevious.time)) {
77
+ throw err.SEQUENCE_NODE_TIME_OCCUPIED(node.time, this.sequence.id);
78
+ }
79
+ if (!this.sequence) {
80
+ throw err.PARENT_SEQUENCE_NOT_FOUND(targetPrevious?.time);
81
+ }
82
+ this.updatesFP = updatesFP && targetPrevious.isSpeed();
83
+ }
84
+ do() {
85
+ const [endNode, startNode] = EventNode.insert(this.node, this.tarPrev);
86
+ this.node.parentSeq.updateJump(endNode, startNode)
87
+ }
88
+ undo() {
89
+ this.sequence.updateJump(...EventNode.removeNodePair(...EventNode.getEndStart(this.node)))
90
+ }
91
+ }
92
+
93
+ export class EventNodePairInsertOrOverwriteOperation <VT extends EventValueESType>
94
+ extends UnionOperation<LazyOperation<typeof EventNodePairInsertOperation<VT>> | EventNodeValueChangeOperation<VT>> {
95
+ overlapping: boolean = false;
96
+ constructor(node: EventStartNode<VT>, targetPrevious: EventStartNode<VT>, updatesFP = true) {
97
+ const equalTime = TC.eq(node.time, targetPrevious.time);
98
+ super(() => {
99
+ if (equalTime) {
100
+ return new EventNodeValueChangeOperation(targetPrevious, node.value);
101
+ } else {
102
+ return EventNodePairInsertOperation.lazy(node, targetPrevious, updatesFP);
103
+ // 此处使用懒操作,防止构造器认为自己在连接两个孤立节点而报错
104
+ }
105
+ });
106
+ if (equalTime) {
107
+ this.overlapping = true;
108
+ }
109
+ }
110
+ }
111
+
112
+
113
+
114
+ export class EventNodeValueChangeOperation <VT extends EventValueESType> extends Operation {
115
+ updatesEditor = true
116
+ node: EventNode<VT>
117
+ value: VT;
118
+ originalValue: VT
119
+ constructor(node: EventNode<VT>, val: VT) {
120
+ super()
121
+ this.node = node
122
+ this.value = val;
123
+ this.originalValue = node.value
124
+ }
125
+ do(chart: Chart) {
126
+ this.node.value = this.value
127
+ if (this.node.parentSeq.type === EventType.speed) {
128
+ this.updateSpeedENS(chart);
129
+ }
130
+ }
131
+ undo(chart: Chart) {
132
+ this.node.value = this.originalValue
133
+ if (this.node.parentSeq.type === EventType.speed) {
134
+ this.updateSpeedENS(chart);
135
+ }
136
+ }
137
+ updateSpeedENS(chart: Chart) {
138
+ (this.node.parentSeq as SpeedENS).updateFloorPositionAfter(
139
+ EventNode.getStartEnd(this.node as unknown as EventStartNode<number> | EventEndNode<number>)[0],
140
+ chart.timeCalculator
141
+ );
142
+ }
143
+ rewrite(operation: EventNodeValueChangeOperation<VT>, chart: Chart): boolean {
144
+ if (operation.node === this.node) {
145
+ this.value = operation.value;
146
+ this.node.value = operation.value;
147
+
148
+ if (this.node.parentSeq.type === EventType.speed) {
149
+ this.updateSpeedENS(chart);
150
+ }
151
+ return true;
152
+ }
153
+ return false;
154
+ }
155
+ }
156
+
157
+ export class EventNodeTimeChangeOperation extends Operation {
158
+ updatesEditor = true
159
+ sequence: EventNodeSequence;
160
+ /**
161
+ * 这里两个node不是面对面,而是背靠背
162
+ * i. e. EndNode -> StartNode
163
+ */
164
+ startNode: EventStartNode<any>;
165
+ endNode: EventEndNode<any>;
166
+ value: TimeT;
167
+ originalValue: TimeT;
168
+ originalPrevious: EventStartNode<any>;
169
+ newPrevious: EventStartNode<any>;
170
+ constructor(node: EventStartNode<any> | EventEndNode<any>, val: TimeT) {
171
+ super()
172
+ if (node.previous.type === NodeType.HEAD) {
173
+ this.ineffective = true;
174
+ return;
175
+ }
176
+ if (!TC.gt(val, [0, 0, 1])) {
177
+ this.ineffective = true;
178
+ return;
179
+ }
180
+ [this.endNode, this.startNode] = EventNode.getEndStart(node)
181
+ const seq = this.sequence = node.parentSeq
182
+ const mayBeThere = seq.getNodeAt(TC.toBeats(val))
183
+ if (mayBeThere && TC.eq(mayBeThere.time, val)) { // 不是arrayEq,这里踩坑
184
+ this.ineffective = true;
185
+ return;
186
+ }
187
+ this.originalPrevious = this.endNode.previous;
188
+ this.newPrevious = mayBeThere === this.startNode ? (<EventEndNode>this.startNode.previous).previous : mayBeThere
189
+ this.value = val;
190
+ this.originalValue = node.time
191
+ console.log("操作:", this)
192
+ }
193
+ do() { // 这里其实还要设计重新选址的问题
194
+ this.startNode.time = this.endNode.time = this.value;
195
+ if (this.newPrevious !== this.originalPrevious) {
196
+ this.sequence.updateJump(...EventNode.removeNodePair(this.endNode, this.startNode))
197
+ EventNode.insert(this.startNode, this.newPrevious)
198
+ }
199
+ this.sequence.updateJump(EventNode.previousStartOfStart(this.endNode.previous), EventNode.nextStartOfStart(this.startNode))
200
+ }
201
+ undo() {
202
+ this.endNode.time = this.startNode.time = this.originalValue;
203
+ if (this.newPrevious !== this.originalPrevious) {
204
+ this.sequence.updateJump(...EventNode.removeNodePair(this.endNode, this.startNode))
205
+ EventNode.insert(this.startNode, this.originalPrevious)
206
+ }
207
+ this.sequence.updateJump(this.endNode.previous, EventNode.nextStartOfStart(this.startNode))
208
+ }
209
+
210
+ }
211
+
212
+
213
+ export class EventNodeEvaluatorChangeOperation <VT extends EventValueESType> extends Operation {
214
+ updatesEditor = true
215
+ originalValue: Evaluator<VT>
216
+ constructor(public node: EventStartNode<VT>, public value: Evaluator<VT>) {
217
+ super();
218
+ this.originalValue = this.node.evaluator
219
+ }
220
+ do() {
221
+ this.node.evaluator = this.value
222
+ }
223
+ undo() {
224
+ this.node.evaluator = this.originalValue
225
+ }
226
+ }
227
+
228
+
229
+
230
+
231
+ // 这个地方得懒一下,不然每亩,导致撤回操作时只能撤回第一个插值节点。
232
+ export class EventInterpolationOperation <VT extends EventValueESType> extends ComplexOperation<LazyOperation<typeof EventNodePairInsertOperation>[]> {
233
+ updatesEditor = true;
234
+ constructor(public eventStartNode: EventStartNode<VT>, public step: TimeT) {
235
+ if (eventStartNode.next.type === NodeType.TAIL) {
236
+ throw err.CANNOT_INTERPOLATE_TAILING_START_NODE();
237
+ }
238
+ const subOps = [];
239
+ const endTime = eventStartNode.next.time;
240
+ let time = TC.validateIp(TC.add(eventStartNode.time, step));
241
+ let lastStart = eventStartNode;
242
+ for (; TC.lt(time, endTime); time = TC.validateIp(TC.add(time, step))) {
243
+ const value = eventStartNode.getValueAt(TC.toBeats(time));
244
+ const start = new EventStartNode(time, value);
245
+ const end = new EventEndNode(time, value);
246
+ EventNode.connect(end, start); // 之前搞成面对面了,写注释留念
247
+ subOps.push(EventNodePairInsertOperation.lazy(start, lastStart));
248
+ lastStart = start;
249
+ }
250
+ super(...subOps);
251
+ }
252
+ }
253
+
254
+
255
+
256
+
257
+ export class EventSubstituteOperation<VT extends EventValueESType> extends ComplexOperation<[...LazyOperation<typeof EventNodePairInsertOperation>[], EventNodeEvaluatorChangeOperation<VT>, EventNodeValueChangeOperation<VT>]> {
258
+ updatesEditor = true;
259
+ /**
260
+ *
261
+ * @param node
262
+ * @throws {KPAError<ERROR_IDS.CANNOT_SUBSTITUTE_EXPRESSION_EVALUATOR>}
263
+ * @throws {KPAError<ERROR_IDS.MUST_INTERPOLATE_TEMPLATE_EASING>}
264
+ */
265
+ constructor(public node: NonLastStartNode<VT>) {
266
+ const evaluator = node.evaluator;
267
+ if (!(evaluator instanceof EasedEvaluator)) {
268
+ throw err.CANNOT_SUBSTITUTE_EXPRESSION_EVALUATOR();
269
+ }
270
+ const easing = evaluator.easing;
271
+ if (!(easing instanceof TemplateEasing)) {
272
+ throw err.MUST_INTERPOLATE_TEMPLATE_EASING();
273
+ }
274
+ const srcSeq = easing.eventNodeSequence;
275
+ const srcStartTime = srcSeq.head.next.time;
276
+ const srcEndTime = srcSeq.tail.previous.time;
277
+ const srcTimeDelta = TC.vsub(srcEndTime, srcStartTime);
278
+ const startValue = node.value;
279
+ const endValue = node.next.value;
280
+ const startTime = node.time;
281
+ const timeDelta = TC.vsub(node.next.time, startTime);
282
+ const convert = (progress: number) => (evaluator as EasedEvaluator<VT>).convert(startValue, endValue, progress);
283
+
284
+ // startTime + (srcTime - srcStartTime) / srcTimeDelta * timeDelta
285
+ const convertTime = (srcTime: TimeT) => TC.vadd(startTime, TC.vmul(timeDelta, TC.div(TC.vsub(srcTime, srcStartTime), srcTimeDelta)));
286
+ const inserts = [];
287
+ let currentNode = srcSeq.head.next.next as EventEndNode;
288
+ let lastPos: EventStartNode<VT> = node;
289
+ while (true) {
290
+ const startNode = currentNode.next;
291
+ const next = startNode.next;
292
+ // 最后一个节点对在循环外处理
293
+ if (next.type === NodeType.TAIL) {
294
+ break;
295
+ }
296
+ const newEndNode = new EventEndNode(convertTime(currentNode.time), convert(currentNode.value));
297
+ const newStartNode = new EventStartNode(convertTime(startNode.time), convert(startNode.value));
298
+ const srcEvaluator = startNode.evaluator;
299
+ if (!(srcEvaluator instanceof EasedEvaluator)) {
300
+ throw new Error()
301
+ }
302
+ newStartNode.evaluator = (evaluator as EasedEvaluator<VT>).deriveWithEasing(srcEvaluator.easing)
303
+ EventNode.connect(newEndNode, newStartNode);
304
+ inserts.push(EventNodePairInsertOperation.lazy(newStartNode, lastPos));
305
+ lastPos = newStartNode;
306
+
307
+ currentNode = next;
308
+ }
309
+ const evaluatorChange = new EventNodeEvaluatorChangeOperation(node, evaluator.deriveWithEasing((srcSeq.head.next.evaluator as NumericEasedEvaluator).easing));
310
+ const valueChange = new EventNodeValueChangeOperation(node.next, convert(currentNode.value));
311
+ super(...inserts, evaluatorChange, valueChange);
312
+ }
313
+ }
314
+
315
+
316
+ export class EncapsuleOperation extends ComplexOperation<[MultiNodeDeleteOperation, EventNodeEvaluatorChangeOperation<number>, EventNodeValueChangeOperation<number>]> {
317
+ updatesEditor = true;
318
+ constructor(nodes: EventStartNode<number>[], easing: TemplateEasing) {
319
+ const len = nodes.length;
320
+ super(
321
+ new MultiNodeDeleteOperation(nodes.slice(1, -1)),
322
+ new EventNodeEvaluatorChangeOperation(nodes[0], (nodes[0].evaluator as NumericEasedEvaluator).deriveWithEasing(easing)),
323
+ // 这里nodes至少都有两个,最后一个node不可能是第一个StartNode
324
+ new EventNodeValueChangeOperation(<EventEndNode>nodes[len - 1].previous, nodes[len - 1].value)
325
+ )
326
+ }
327
+ /**
328
+ * 将一些来自sourceSequence的节点打包为一个用于模板缓动的事件序列
329
+ * 然后把sourceSequence中的源节点集合替换为单个使用了该模板的事件
330
+ * @param sourceSequence
331
+ * @param sourceNodes
332
+ */
333
+ static encapsule(templateEasingLib: TemplateEasingLib, sourceSequence: EventNodeSequence, sourceNodes: Set<EventStartNode>, name: string): EncapsuleOperation {
334
+ if (!EventNode.belongToSequence(sourceNodes, sourceSequence)) {
335
+ throw err.NODES_NOT_BELONG_TO_SAME_SEQUENCE();
336
+ }
337
+ const [oldArray, nodeArray] = EventNode.setToNewOrderedArray([0, 0, 1], sourceNodes);
338
+ if (Math.abs(nodeArray[0].value - nodeArray[nodeArray.length - 1].value) < 1e-10) {
339
+ throw err.NODES_HAS_ZERO_DELTA();
340
+ }
341
+ if (!EventNode.isContinuous(oldArray)) {
342
+ throw err.NODES_NOT_CONTINUOUS();
343
+ }
344
+ const easing = templateEasingLib.getOrNew(name);
345
+ const sequence = easing.eventNodeSequence;
346
+ sequence.effectiveBeats = TC.toBeats(nodeArray[nodeArray.length - 1].time);
347
+ // 直接do,这个不需要做成可撤销的
348
+ // @ts-expect-error 这里序列类型确定,为easing,不需要传入谱面
349
+ new MultiNodeAddOperation(nodeArray, sequence).do();
350
+
351
+ return new EncapsuleOperation(oldArray, easing);
352
+ }
353
+ }
354
+
355
+ /**
356
+ * 批量添加节点对
357
+ *
358
+ * 节点对需要有序的,且不能有重叠
359
+
360
+ */
361
+ export class MultiNodeAddOperation <VT extends EventValueESType> extends ComplexOperation<EventNodePairInsertOrOverwriteOperation<VT>[]> {
362
+ updatesEditor = true
363
+ updatesFP = false;
364
+ constructor(public nodes: EventStartNode<VT>[], public seq: EventNodeSequence<VT>) {
365
+ let prev = seq.getNodeAt(TC.toBeats(nodes[0].time));
366
+ super(...nodes.map(node => {
367
+ const op = new EventNodePairInsertOrOverwriteOperation(node, prev,false);
368
+ if (!op.overlapping) prev = node; // 有种reduce的感觉
369
+ return op
370
+ }));
371
+ this.updatesFP = seq.type === EventType.speed
372
+ }
373
+ do(chart: Chart) {
374
+ super.do();
375
+ if (this.updatesFP) {
376
+ (this.seq as SpeedENS).updateFloorPositionAfter(this.nodes[0] as EventStartNode<number>, chart.timeCalculator)
377
+ }
378
+ }
379
+ undo(chart: Chart) {
380
+ super.undo();
381
+ if (this.updatesFP) {
382
+ (this.seq as SpeedENS).updateFloorPositionAfter(this.nodes[0] as EventStartNode<number>, chart.timeCalculator)
383
+ }
384
+ }
385
+ }
386
+
387
+ export class MultiNodeDeleteOperation extends ComplexOperation<LazyOperation<typeof EventNodePairRemoveOperation>[]> {
388
+ updatesEditor = true;
389
+ constructor(nodes: EventStartNode<any>[]) {
390
+ super(...nodes.map(node => EventNodePairRemoveOperation.lazy(node)));
391
+ }
392
+ }
393
+
394
+ export class EventNodeSequenceRenameOperation extends Operation {
395
+ updatesEditor: boolean = true;
396
+ originalName: string;
397
+ constructor(public sequence: EventNodeSequence<EventValueESType>, public newName: string) {
398
+ super();
399
+ this.originalName = sequence.id;
400
+ if (this.originalName === newName) {
401
+ this.ineffective = true;
402
+ }
403
+ }
404
+ do(chart: Chart) {
405
+ chart.sequenceMap.set(this.newName, this.sequence)
406
+ chart.sequenceMap.delete(this.originalName);
407
+ this.sequence.id = this.newName;
408
+ }
409
+ undo(chart: Chart) {
410
+ chart.sequenceMap.set(this.originalName, this.sequence)
411
+ chart.sequenceMap.delete(this.newName);
412
+ this.sequence.id = this.originalName;
413
+ }
414
+ }
415
+
416
+
417
+
418
+
419
+
420
+
421
+ type TimeRange = [TimeT, TimeT]
422
+
423
+ /**
424
+ * 所有节点事件加上一个值。
425
+ * 此操作假定了节点被偏移时不会产生“碰撞”。
426
+ * 节点要有序
427
+ * @private
428
+ */
429
+ export class MultiNodeOffsetOperation extends Operation {
430
+ constructor(public nodes: readonly EventStartNode<any>[], public offset: TimeT) {
431
+ super();
432
+ }
433
+ do() {
434
+ const offset = this.offset;
435
+ const nodes = this.nodes;
436
+ const len = nodes.length;
437
+ const node = nodes[0];
438
+ if (node.previous.type !== NodeType.HEAD) {
439
+ node.time = TC.validateIp(TC.add(node.time, offset));
440
+ node.previous.time = TC.validateIp(TC.add(node.previous.time, offset));
441
+ }
442
+ for (let i = 1; i < len; i++) {
443
+ const node = nodes[i];
444
+ node.time = TC.validateIp(TC.add(node.time, offset));
445
+ const previous = node.previous as EventEndNode<any>;
446
+ previous.time = TC.validateIp(TC.add(previous.time, offset));
447
+ }
448
+ }
449
+ undo() {
450
+ const offset = this.offset;
451
+ const nodes = this.nodes;
452
+ const len = nodes.length;
453
+ const node = nodes[0];
454
+ if (node.previous.type !== NodeType.HEAD) {
455
+ node.time = TC.vadd(node.time, offset);
456
+ node.previous.time = TC.vadd(node.previous.time, offset);
457
+ }
458
+ for (let i = 1; i < len; i++) {
459
+ const node = nodes[i];
460
+ node.time = TC.vadd(node.time, offset);
461
+ const previous = node.previous as EventEndNode<any>;
462
+ previous.time = TC.vadd(previous.time, offset);
463
+ }
464
+ }
465
+ }
466
+
467
+
468
+ export class ENSTimeRangeDeleteOperation extends ComplexOperation<[MultiNodeDeleteOperation, MultiNodeOffsetOperation]> {
469
+ beforeToStart: EventStartNode<any> | EventNodeLike<NodeType.HEAD, any>;
470
+ constructor(public eventNodeSequence: EventNodeSequence<any>, public timeRange: TimeRange) {
471
+ // 找出所有在范围内的节点并删除
472
+ const node = eventNodeSequence.getNodeAt(TC.toBeats(timeRange[0]));
473
+ const beforeToStart = EventNode.previousStartOfStart(node);
474
+ const toBeDeleted = eventNodeSequence.getNodesFromOneAndRangeRight(node, timeRange[1]);
475
+ // 将后续所有节点加入
476
+ const toBeOffset = eventNodeSequence.getNodesAfterOne(toBeDeleted[toBeDeleted.length - 1]);
477
+ super(new MultiNodeDeleteOperation(toBeDeleted), new MultiNodeOffsetOperation(toBeOffset, TC.vsub(timeRange[0], timeRange[1])))
478
+ this.beforeToStart = beforeToStart;
479
+ }
480
+ do() {
481
+ super.do();
482
+ const ens = this.eventNodeSequence;
483
+ ens.updateJump(this.beforeToStart, ens.tail);
484
+ }
485
+ undo() {
486
+ super.undo();
487
+ const ens = this.eventNodeSequence;
488
+ ens.updateJump(this.beforeToStart, ens.tail);
489
+ }
490
+ }
491
+
492
+ export class ENSAddBlankOperation extends MultiNodeOffsetOperation {
493
+ updatesEditor = true;
494
+ constructor(public ens: EventNodeSequence<any>, pos: TimeT, length: TimeT) {
495
+ super(
496
+ ens.getNodesAfterOne(ens.getNodeAt(TC.toBeats(pos))),
497
+ length
498
+ );
499
+ }
500
+
501
+ do() {
502
+ super.do();
503
+ const ens = this.ens;
504
+ ens.updateJump(ens.head, ens.tail);
505
+ }
506
+ undo() {
507
+ super.undo();
508
+ const ens = this.ens;
509
+ ens.updateJump(ens.head, ens.tail);
510
+ }
511
+ }
@@ -0,0 +1,6 @@
1
+ export * from "./basic"
2
+ export * from "./event";
3
+ export * from "./note";
4
+ export * from "./line";
5
+ export * from "./chart";
6
+ export * from "./macro";