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.
- package/README.md +1 -3
- package/basic.ts +285 -0
- package/bpm.ts +332 -0
- package/chart.ts +418 -106
- package/chartType.schema.json +584 -0
- package/chartType2.schema.json +1107 -0
- package/chartTypes.ts +131 -30
- package/easing.ts +125 -90
- package/env.ts +208 -0
- package/evaluator.ts +106 -20
- package/event.ts +357 -255
- package/index.d.ts +3055 -0
- package/index.js +5530 -0
- package/index.ts +17 -11
- package/judgeline.ts +395 -94
- package/jumparray.ts +10 -11
- package/line.ts +246 -0
- package/macro.ts +215 -0
- package/note.ts +32 -55
- package/operation/basic.ts +285 -0
- package/operation/chart.ts +21 -0
- package/operation/event.ts +511 -0
- package/operation/index.ts +6 -0
- package/operation/line.ts +304 -0
- package/operation/macro.ts +60 -0
- package/operation/note.ts +457 -0
- package/package.json +7 -1
- package/rpeChartCompiler.ts +133 -98
- package/time.ts +35 -223
- package/tsconfig.json +2 -3
- package/util.ts +21 -1
- package/version.ts +2 -1
|
@@ -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
|
+
}
|