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/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "kipphi",
|
|
3
|
+
"description": "Parse your Phigros Chart(.rpe.json or .kpa.json) into an editor-friendly format.",
|
|
4
|
+
"version": "2.0.0",
|
|
5
|
+
"author": "Team Zincs (https://github.com/TeamZincs)",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/TeamZincs/kipphi.git"
|
|
10
|
+
},
|
|
11
|
+
"module": "index.ts",
|
|
12
|
+
"main": "index.ts",
|
|
13
|
+
"type": "module",
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/bun": "latest"
|
|
16
|
+
},
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"typescript": "^5"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import type { Chart, UIName } from "./chart";
|
|
2
|
+
import { type TimeT, type ChartDataRPE, type MetaData, type JudgeLineDataRPE, type EventLayerDataRPE, type EventDataRPELike, EventType, InterpreteAs, type NoteDataRPE, type EventValueESType, EventValueType } from "./chartTypes";
|
|
3
|
+
import { SegmentedEasing, BezierEasing, NormalEasing, fixedEasing, TemplateEasing, Easing } from "./easing";
|
|
4
|
+
import { EasedEvaluator, ExpressionEvaluator, NumericEasedEvaluator, type EasedEvaluatorConstructorOfType, type EasedEvaluatorOfType } from "./evaluator";
|
|
5
|
+
import { EventEndNode, EventNode, EventNodeSequence, EventStartNode, type EventNodeLike } from "./event";
|
|
6
|
+
import type { JudgeLine } from "./judgeline";
|
|
7
|
+
import type { NNList, HNList, NNOrHead } from "./note";
|
|
8
|
+
import { TC, TimeCalculator } from "./time";
|
|
9
|
+
import { NodeType, numberToRatio } from "./util";
|
|
10
|
+
|
|
11
|
+
/// #declaration:global
|
|
12
|
+
|
|
13
|
+
const getInnerEasing = (easing: Easing) => easing instanceof SegmentedEasing ? easing.easing : easing;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 全生命周期只会编译一次,想多次就再构造一个
|
|
17
|
+
*/
|
|
18
|
+
export class RPEChartCompiler {
|
|
19
|
+
sequenceMap: Map<EventNodeSequence<any>, EventNodeSequence<any>> = new Map();
|
|
20
|
+
interpolationStep: TimeT = [0, 1, 16];
|
|
21
|
+
constructor(public chart: Chart) {}
|
|
22
|
+
|
|
23
|
+
compileChart(): ChartDataRPE {
|
|
24
|
+
console.time("compileChart")
|
|
25
|
+
const chart = this.chart;
|
|
26
|
+
const judgeLineGroups = chart.judgeLineGroups.map(group => group.name);
|
|
27
|
+
const judgeLineList = chart.judgeLines.map(line => this.compileJudgeLine(line));
|
|
28
|
+
const BPMList = chart.timeCalculator.dump();
|
|
29
|
+
const META: MetaData = {
|
|
30
|
+
RPEVersion: 1,
|
|
31
|
+
background: 'illustration.png',
|
|
32
|
+
charter: chart.charter,
|
|
33
|
+
composer: chart.composer,
|
|
34
|
+
illustration: chart.illustrator,
|
|
35
|
+
id: Math.random().toString().slice(2, 10),
|
|
36
|
+
level: chart.level,
|
|
37
|
+
name: chart.name,
|
|
38
|
+
offset: chart.offset,
|
|
39
|
+
song: chart.name
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
for (const uiName of ["bar", "combo", "combonumber", "level", "name", "pause", "score"] satisfies UIName[]) {
|
|
43
|
+
const target: JudgeLine | null = chart[`${uiName}Attach` satisfies keyof Chart];
|
|
44
|
+
if (!target) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const lineData = judgeLineList[target.id];
|
|
48
|
+
// RPEJSON里面一条线只能绑一个UI,KPAJSON可以绑多个
|
|
49
|
+
// 所以如果绑了多个,自动给它们创建子线
|
|
50
|
+
if (lineData.attachUI) {
|
|
51
|
+
judgeLineList.push({
|
|
52
|
+
Group: 0,
|
|
53
|
+
Name: "Auto created for " + uiName,
|
|
54
|
+
Texture: "line.png",
|
|
55
|
+
attachUI: uiName,
|
|
56
|
+
notes: [],
|
|
57
|
+
bpmfactor: 1.0,
|
|
58
|
+
eventLayers: [],
|
|
59
|
+
father: target.id,
|
|
60
|
+
isCover: lineData.isCover,
|
|
61
|
+
numOfNotes: 0
|
|
62
|
+
} satisfies Partial<JudgeLineDataRPE> as JudgeLineDataRPE)
|
|
63
|
+
} else {
|
|
64
|
+
lineData.attachUI = uiName;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
console.timeEnd("compileChart");
|
|
70
|
+
return {
|
|
71
|
+
BPMList,
|
|
72
|
+
META,
|
|
73
|
+
judgeLineList,
|
|
74
|
+
judgeLineGroup: judgeLineGroups,
|
|
75
|
+
multiLineString: '',
|
|
76
|
+
multiScale: 1.0,
|
|
77
|
+
chartTime: chart.rpeChartingTime * 60,
|
|
78
|
+
kpaChartTime: chart.chartingTime,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
compileJudgeLine(judgeLine: JudgeLine): JudgeLineDataRPE {
|
|
83
|
+
const chart = this.chart;
|
|
84
|
+
const notes = this.compileNNLists([...judgeLine.nnLists.values()], [...judgeLine.hnLists.values()]);
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
notes: notes,
|
|
88
|
+
Group: chart.judgeLineGroups.indexOf(judgeLine.group),
|
|
89
|
+
Name: judgeLine.name,
|
|
90
|
+
Texture: judgeLine.texture,
|
|
91
|
+
bpmfactor: 1.0,
|
|
92
|
+
eventLayers: judgeLine.eventLayers.map((layer): EventLayerDataRPE => ({
|
|
93
|
+
moveXEvents: layer.moveX ? this.dumpEventNodeSequence(layer.moveX) : null,
|
|
94
|
+
moveYEvents: layer.moveY ? this.dumpEventNodeSequence(layer.moveY) : null,
|
|
95
|
+
rotateEvents: layer.rotate ? this.dumpEventNodeSequence(layer.rotate) : null,
|
|
96
|
+
alphaEvents: layer.alpha ? this.dumpEventNodeSequence(layer.alpha) : null,
|
|
97
|
+
speedEvents: layer.speed ? this.dumpEventNodeSequence(layer.speed) : null
|
|
98
|
+
})),
|
|
99
|
+
extended: {
|
|
100
|
+
scaleXEvents: judgeLine.extendedLayer.scaleX ? this.dumpEventNodeSequence(judgeLine.extendedLayer.scaleX) : null,
|
|
101
|
+
scaleYEvents: judgeLine.extendedLayer.scaleY ? this.dumpEventNodeSequence(judgeLine.extendedLayer.scaleY) : null,
|
|
102
|
+
textEvents: judgeLine.extendedLayer.text ? this.dumpEventNodeSequence(judgeLine.extendedLayer.text) : null,
|
|
103
|
+
colorEvents: judgeLine.extendedLayer.color ? this.dumpEventNodeSequence(judgeLine.extendedLayer.color) : null
|
|
104
|
+
},
|
|
105
|
+
father: judgeLine.father?.id ?? -1,
|
|
106
|
+
isCover: judgeLine.cover ? 1 : 0,
|
|
107
|
+
numOfNotes: notes.length,
|
|
108
|
+
anchor: judgeLine.anchor,
|
|
109
|
+
rotateWithFather: judgeLine.rotatesWithFather,
|
|
110
|
+
isGif: 0,
|
|
111
|
+
zOrder: judgeLine.zOrder
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
compileEasedEvent<VT extends EventValueESType>(
|
|
116
|
+
snode: EventStartNode<VT> & { evaluator: EasedEvaluatorOfType<VT>},
|
|
117
|
+
getValue: (node: EventStartNode<VT> | EventEndNode<VT>) => VT
|
|
118
|
+
): EventDataRPELike<VT> {
|
|
119
|
+
const endNode = snode.next as EventEndNode<VT>;
|
|
120
|
+
const evaluator = snode.evaluator;
|
|
121
|
+
const easing = evaluator.easing;
|
|
122
|
+
const isSegmented = easing instanceof SegmentedEasing;
|
|
123
|
+
const innerEasing = isSegmented ? easing.easing : easing;
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
bezier: innerEasing instanceof BezierEasing ? 1 : 0,
|
|
127
|
+
bezierPoints: innerEasing instanceof BezierEasing ?
|
|
128
|
+
[innerEasing.cp1[0], innerEasing.cp1[1], innerEasing.cp2[0], innerEasing.cp2[1]] : // 修正了这里 cp2.y 的引用
|
|
129
|
+
[0, 0, 0, 0],
|
|
130
|
+
easingLeft: isSegmented ? easing.left : 0.0,
|
|
131
|
+
easingRight: isSegmented ? easing.right : 1.0,
|
|
132
|
+
easingType: easing instanceof NormalEasing ?
|
|
133
|
+
easing.rpeId ?? 1 :
|
|
134
|
+
null,
|
|
135
|
+
end: getValue(easing === fixedEasing ? snode : endNode),
|
|
136
|
+
endTime: endNode.time,
|
|
137
|
+
linkgroup: 0, // 假设默认值为 0
|
|
138
|
+
start: getValue(snode),
|
|
139
|
+
startTime: snode.time
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
dumpEventNodeSequence<VT>(sequence: EventNodeSequence<VT>): EventDataRPELike<VT>[] {
|
|
144
|
+
const nodes: EventDataRPELike<VT>[] = [];
|
|
145
|
+
const interpolationStep = this.interpolationStep;
|
|
146
|
+
if (!(sequence.type === EventType.color || sequence.type === EventType.text)) {
|
|
147
|
+
// @ts-ignore 烦死了烦死了烦死了
|
|
148
|
+
sequence = this.substitute(sequence);
|
|
149
|
+
}
|
|
150
|
+
let node = sequence.head.next;
|
|
151
|
+
// 唯一真史
|
|
152
|
+
const getValue = (sequence.type === EventType.text
|
|
153
|
+
? (node: EventStartNode<string> | EventEndNode<string>) => {
|
|
154
|
+
const interpretedAs = node instanceof EventStartNode ? node.interpretedAs : node.previous.interpretedAs;
|
|
155
|
+
return interpretedAs === InterpreteAs.str ? node.value : "%P%" + node.value;
|
|
156
|
+
}
|
|
157
|
+
: (node: EventStartNode<number> | EventEndNode<number>) => node.value) as unknown as (node: EventStartNode<VT> | EventEndNode<VT>) => VT;
|
|
158
|
+
while (true) {
|
|
159
|
+
const end = node.next;
|
|
160
|
+
if (end.type === NodeType.TAIL) break;
|
|
161
|
+
if (node.evaluator instanceof ExpressionEvaluator) {
|
|
162
|
+
let cur = node.time;
|
|
163
|
+
const endTime = end.time;
|
|
164
|
+
let value = getValue(node);
|
|
165
|
+
for (; TC.lt(cur, endTime);) {
|
|
166
|
+
const nextTime = TC.validateIp(TC.add(cur, interpolationStep));
|
|
167
|
+
const nextValue = node.getValueAt(TC.toBeats(nextTime))
|
|
168
|
+
nodes.push({
|
|
169
|
+
bezier: 0,
|
|
170
|
+
bezierPoints: [0, 0, 0, 0],
|
|
171
|
+
easingLeft: 0.0,
|
|
172
|
+
easingRight: 0.0,
|
|
173
|
+
easingType: 1,
|
|
174
|
+
start: value,
|
|
175
|
+
startTime: cur,
|
|
176
|
+
end: nextValue,
|
|
177
|
+
endTime: nextTime,
|
|
178
|
+
linkgroup: 0
|
|
179
|
+
});
|
|
180
|
+
cur = nextTime;
|
|
181
|
+
value = nextValue;
|
|
182
|
+
}
|
|
183
|
+
// 所切割的事件长度并不必然是step的整数倍
|
|
184
|
+
nodes.push({
|
|
185
|
+
bezier: 0,
|
|
186
|
+
bezierPoints: [0, 0, 0, 0],
|
|
187
|
+
easingLeft: 0.0,
|
|
188
|
+
easingRight: 0.0,
|
|
189
|
+
easingType: 1,
|
|
190
|
+
start: value,
|
|
191
|
+
startTime: cur,
|
|
192
|
+
end: end.value,
|
|
193
|
+
endTime: endTime,
|
|
194
|
+
linkgroup: 0
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
} else {
|
|
198
|
+
nodes.push(this.compileEvent(node, getValue));
|
|
199
|
+
}
|
|
200
|
+
node = end.next;
|
|
201
|
+
}
|
|
202
|
+
nodes.push(node.dumpAsLast());
|
|
203
|
+
|
|
204
|
+
return nodes
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
compileNNLists(nnLists: NNList[], hnLists: HNList[]): NoteDataRPE[] {
|
|
208
|
+
const noteLists = nnLists.map(list => this.nnListToArray(list));
|
|
209
|
+
const holdLists = hnLists.map(list => this.nnListToArray(list));
|
|
210
|
+
const ret: NoteDataRPE[] = []
|
|
211
|
+
const time = (list: NoteDataRPE[]) => list.length === 0 ? [Infinity, 0, 1] as TimeT : list[list.length - 1].startTime;
|
|
212
|
+
const concatWithOrder = (lists: NoteDataRPE[][]) => {
|
|
213
|
+
if (lists.length === 0) return;
|
|
214
|
+
// 先按最早的时间排序
|
|
215
|
+
lists.sort((a, b) => {
|
|
216
|
+
return TimeCalculator.gt(time(a), time(b)) ? 1 : -1;
|
|
217
|
+
});
|
|
218
|
+
// 每次从lists中第一个list pop一个data加入到结果,然后冒泡调整这个list的位置
|
|
219
|
+
while (lists[0].length > 0) {
|
|
220
|
+
const list = lists[0];
|
|
221
|
+
// 只需要pop就可以了,pop复杂度O(1),这是倒序的原因
|
|
222
|
+
const node = list.pop();
|
|
223
|
+
ret.push(node);
|
|
224
|
+
let i = 0;
|
|
225
|
+
while (i + 1 < lists.length && TimeCalculator.gt(time(lists[i]), time(lists[i + 1]))) {
|
|
226
|
+
const temp = lists[i];
|
|
227
|
+
lists[i] = lists[i + 1];
|
|
228
|
+
lists[i + 1] = temp;
|
|
229
|
+
i++;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
};
|
|
234
|
+
concatWithOrder(noteLists);
|
|
235
|
+
concatWithOrder(holdLists);
|
|
236
|
+
return ret;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* 倒序转换为数组
|
|
240
|
+
* @param nnList
|
|
241
|
+
* @returns 一个按照时间降序排列的数组
|
|
242
|
+
*/
|
|
243
|
+
nnListToArray(nnList: NNList) {
|
|
244
|
+
const notes: NoteDataRPE[] = [];
|
|
245
|
+
let node: NNOrHead = nnList.tail.previous;
|
|
246
|
+
while (node.type !== NodeType.HEAD) {
|
|
247
|
+
for (let each of node.notes) {
|
|
248
|
+
notes.push(each.dumpRPE(this.chart.timeCalculator));
|
|
249
|
+
}
|
|
250
|
+
node = node.previous;
|
|
251
|
+
}
|
|
252
|
+
return notes;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* 将当前序列中所有通过模板缓动引用了其他序列的事件直接展开为被引用的序列内容
|
|
257
|
+
* transform all events that reference other sequences by template easing
|
|
258
|
+
* into the content of the referenced sequence
|
|
259
|
+
* 有点类似于MediaWiki的{{subst:templateName}}
|
|
260
|
+
* @param map 由TemplateEasingLib提供
|
|
261
|
+
* @returns
|
|
262
|
+
*/
|
|
263
|
+
substitute<VT extends EventValueESType>(seq: EventNodeSequence<VT>): EventNodeSequence<VT> {
|
|
264
|
+
const map = this.sequenceMap;
|
|
265
|
+
if (map.has(seq)) {
|
|
266
|
+
return map.get(seq);
|
|
267
|
+
}
|
|
268
|
+
let currentNode: EventStartNode<VT> = seq.head.next;
|
|
269
|
+
const newSeq = new EventNodeSequence<VT>(seq.type, seq.effectiveBeats);
|
|
270
|
+
const valueType = seq.type === EventType.color
|
|
271
|
+
? EventValueType.color : seq.type === EventType.text
|
|
272
|
+
? EventValueType.text : EventValueType.numeric;
|
|
273
|
+
map.set(seq, newSeq);
|
|
274
|
+
let currentPos: EventNodeLike<NodeType.HEAD, VT> | EventEndNode<VT> = newSeq.head;
|
|
275
|
+
while (true) {
|
|
276
|
+
if (!currentNode || (currentNode.next.type === NodeType.TAIL)) {
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
const endNode = currentNode.next;
|
|
280
|
+
const evaluator = currentNode.evaluator;
|
|
281
|
+
let innerEasing: Easing;
|
|
282
|
+
if ( evaluator
|
|
283
|
+
&& evaluator instanceof EasedEvaluator
|
|
284
|
+
&& (innerEasing = getInnerEasing(evaluator.easing)) instanceof TemplateEasing
|
|
285
|
+
) {
|
|
286
|
+
const EvaluatorConstructor = evaluator.constructor as EasedEvaluatorConstructorOfType<VT>;
|
|
287
|
+
const srcSeq = this.substitute(innerEasing.eventNodeSequence);
|
|
288
|
+
const easing = evaluator.easing;
|
|
289
|
+
const isSegmented = easing instanceof SegmentedEasing;
|
|
290
|
+
const startValue = currentNode.value;
|
|
291
|
+
const endValue = currentNode.next.value;
|
|
292
|
+
const startTime = currentNode.time;
|
|
293
|
+
const timeDelta = TC.sub(currentNode.next.time, startTime);
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
let srcStart: number, srcEnd: number, leftDividedNodeSrc: EventStartNode, rightDividedNode: EventStartNode,
|
|
297
|
+
srcStartTime: TimeT, srcTimeDelta: TimeT, toStopAt: EventStartNode;
|
|
298
|
+
if (isSegmented) {
|
|
299
|
+
const totalDuration = TC.sub(srcSeq.tail.previous.time, srcSeq.head.next.time);
|
|
300
|
+
srcStart = srcSeq.getValueAt(easing.left * srcSeq.effectiveBeats)
|
|
301
|
+
srcEnd = srcSeq.getValueAt(easing.right * srcSeq.effectiveBeats, true);
|
|
302
|
+
leftDividedNodeSrc = srcSeq.getNodeAt(easing.left * srcSeq.effectiveBeats);
|
|
303
|
+
rightDividedNode = srcSeq.getNodeAt(easing.right * srcSeq.effectiveBeats, true);
|
|
304
|
+
toStopAt = rightDividedNode.next.next;
|
|
305
|
+
srcStartTime = TC.mul(totalDuration, numberToRatio(easing.left));
|
|
306
|
+
const srcEndTime = TC.mul(totalDuration, numberToRatio(easing.right));
|
|
307
|
+
TC.validateIp(srcStartTime);
|
|
308
|
+
TC.validateIp(srcEndTime);
|
|
309
|
+
srcTimeDelta = TC.sub(srcEndTime, srcStartTime);
|
|
310
|
+
TC.validateIp(srcTimeDelta);
|
|
311
|
+
} else {
|
|
312
|
+
srcStart = srcSeq.head.next.value;
|
|
313
|
+
srcEnd = srcSeq.tail.previous.value;
|
|
314
|
+
leftDividedNodeSrc = srcSeq.head.next;
|
|
315
|
+
rightDividedNode = srcSeq.tail.previous;
|
|
316
|
+
toStopAt = rightDividedNode;
|
|
317
|
+
srcStartTime = srcSeq.head.next.time;
|
|
318
|
+
srcTimeDelta = TC.sub(srcSeq.tail.previous.time, srcStartTime);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const srcDelta = srcEnd - srcStart;
|
|
322
|
+
const ratio = TC.div(timeDelta, srcTimeDelta)
|
|
323
|
+
|
|
324
|
+
const convert: (v: number) => VT
|
|
325
|
+
= (value: number) => evaluator.convert(startValue, endValue, (value - srcStart) / srcDelta);
|
|
326
|
+
// 我恨TS没有运算符重载
|
|
327
|
+
const convertTime: (t: TimeT) => TimeT
|
|
328
|
+
= (time: TimeT) => TC.validateIp(TC.add(startTime, TC.mul(TC.sub(time, srcStartTime), ratio)));
|
|
329
|
+
|
|
330
|
+
const first = currentNode.clone();
|
|
331
|
+
EventNode.connect(currentPos, first)
|
|
332
|
+
// 处理第一个节点的截段
|
|
333
|
+
if (isSegmented) {
|
|
334
|
+
const left = easing.left * srcSeq.effectiveBeats
|
|
335
|
+
if (left - TC.toBeats(leftDividedNodeSrc.time) > 1e-6) {
|
|
336
|
+
// 断言:这里left不会大于有效拍数
|
|
337
|
+
const newLeft = left / (TC.toBeats((leftDividedNodeSrc.next as EventEndNode).time) - TC.toBeats(leftDividedNodeSrc.time))
|
|
338
|
+
// 如果切到的这个位置是表达式求值器,这是没办法保留缓动的,只能运用表达式求值器
|
|
339
|
+
if (leftDividedNodeSrc.evaluator instanceof ExpressionEvaluator) {
|
|
340
|
+
const exprEvaluator = leftDividedNodeSrc.evaluator as ExpressionEvaluator<number>;
|
|
341
|
+
first.evaluator = new ExpressionEvaluator("t");
|
|
342
|
+
// 强行修改
|
|
343
|
+
// @ts-ignore
|
|
344
|
+
first.evaluator.func = (t: number) => {
|
|
345
|
+
const value = exprEvaluator.func(
|
|
346
|
+
t * (TC.getDelta((first.next as EventEndNode).time, first.time) * (1 - left)) / srcSeq.effectiveBeats
|
|
347
|
+
); // 脑细胞杀器
|
|
348
|
+
return convert(value);
|
|
349
|
+
}
|
|
350
|
+
} else {
|
|
351
|
+
// 否则就是带缓动求值器
|
|
352
|
+
first.evaluator = new EvaluatorConstructor(
|
|
353
|
+
new SegmentedEasing((leftDividedNodeSrc.evaluator as NumericEasedEvaluator).easing, newLeft, 1.0)
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
}
|
|
357
|
+
} else {
|
|
358
|
+
if (leftDividedNodeSrc.evaluator instanceof ExpressionEvaluator) {
|
|
359
|
+
first.evaluator = new ExpressionEvaluator("t");
|
|
360
|
+
// 也不知道对不对
|
|
361
|
+
// @ts-ignore
|
|
362
|
+
first.evaluator.func = (t: number) => {
|
|
363
|
+
return convert(leftDividedNodeSrc.evaluator.eval(leftDividedNodeSrc, t))
|
|
364
|
+
}
|
|
365
|
+
} else {
|
|
366
|
+
first.evaluator = new EvaluatorConstructor((leftDividedNodeSrc.evaluator as NumericEasedEvaluator).easing, evaluator.interpretedAs);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
} else {
|
|
370
|
+
if (leftDividedNodeSrc.evaluator instanceof ExpressionEvaluator) {
|
|
371
|
+
first.evaluator = new ExpressionEvaluator("t");
|
|
372
|
+
// 也不知道对不对
|
|
373
|
+
// @ts-ignore
|
|
374
|
+
first.evaluator.func = (t: number) => {
|
|
375
|
+
return convert(leftDividedNodeSrc.evaluator.eval(leftDividedNodeSrc, t))
|
|
376
|
+
}
|
|
377
|
+
} else {
|
|
378
|
+
first.evaluator = new EvaluatorConstructor((leftDividedNodeSrc.evaluator as NumericEasedEvaluator).easing, evaluator.interpretedAs);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
let prev = first
|
|
382
|
+
// 这里在到toStopAt之前一直都是非尾的
|
|
383
|
+
for (let n: EventEndNode<number> = leftDividedNodeSrc.next as EventEndNode<number>; n.next !== toStopAt; n = n.next.next) {
|
|
384
|
+
const endNode = n;
|
|
385
|
+
const startNode = n.next;
|
|
386
|
+
const newEnd = new EventEndNode(convertTime(endNode.time), convert(endNode.value));
|
|
387
|
+
const newStart = new EventStartNode(convertTime(startNode.time), convert(startNode.value));
|
|
388
|
+
newStart.evaluator = new EvaluatorConstructor(startNode.evaluator.easing);
|
|
389
|
+
EventNode.connect(prev, newEnd)
|
|
390
|
+
EventNode.connect(newEnd, newStart);
|
|
391
|
+
prev = newStart;
|
|
392
|
+
}
|
|
393
|
+
// 处理最后一个节点的截段
|
|
394
|
+
if (isSegmented) {
|
|
395
|
+
const right = easing.right * srcSeq.effectiveBeats
|
|
396
|
+
if (TC.toBeats((rightDividedNode.next as EventEndNode).time) - right > 1e-6) {
|
|
397
|
+
// 断言:这里right不会大于有效拍数
|
|
398
|
+
const newRight = right / (TC.toBeats((rightDividedNode.next as EventEndNode).time) - TC.toBeats(rightDividedNode.time))
|
|
399
|
+
// 这时候prev是最后一个subst的node
|
|
400
|
+
prev.easing = new SegmentedEasing(rightDividedNode.easing, 0.0, newRight)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
}
|
|
404
|
+
const endNode = currentNode.next.clone();
|
|
405
|
+
EventNode.connect(prev, endNode);
|
|
406
|
+
currentPos = endNode;
|
|
407
|
+
endNode.value = isSegmented ? endNode.value : convert((srcSeq.tail.previous.previous as EventEndNode).value);
|
|
408
|
+
} else {
|
|
409
|
+
const newStartNode = currentNode.clone();
|
|
410
|
+
const newEndNode = endNode.clone();
|
|
411
|
+
EventNode.connect(currentPos, newStartNode)
|
|
412
|
+
EventNode.connect(newStartNode, newEndNode);
|
|
413
|
+
currentPos = newEndNode;
|
|
414
|
+
}
|
|
415
|
+
currentNode = endNode.next;
|
|
416
|
+
}
|
|
417
|
+
const lastStart = currentNode.clone();
|
|
418
|
+
EventNode.connect(currentPos, lastStart);
|
|
419
|
+
EventNode.connect(lastStart, newSeq.tail)
|
|
420
|
+
return newSeq;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
/// #enddeclaration
|