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
package/README.md
CHANGED
|
@@ -4,6 +4,4 @@ This is a Phigros KipphiApparatus/Re:PhiEdit ChartJSON Parser. It is a subprojec
|
|
|
4
4
|
|
|
5
5
|
"Kipphi" comes from the Dutch chemist Kipp, who invented Kipp's Apparatus, which was used to produce gas and stop the reaction at any time.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
## Usage
|
|
9
|
-
|
|
7
|
+
警告:KPA 2.1版本发布以前均为实验性代码,可能会出现API兼容性问题,当前不建议将此代码用于生产!
|
package/basic.ts
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { Chart } from "../chart";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export type OpEventType = "do" | "undo" | "redo" | "error" | "needsupdate" | "maxcombochanged" | "noundo" | "noredo" | "firstmodified" | "needsreflow";
|
|
7
|
+
|
|
8
|
+
// 最讲类型安全的一集(
|
|
9
|
+
// 当然要有,不然的话编辑器那边检测的时候逆变会出问题
|
|
10
|
+
|
|
11
|
+
interface DirectlyInstaciableEventMap {
|
|
12
|
+
"noundo": OpEvent;
|
|
13
|
+
"noredo": OpEvent;
|
|
14
|
+
"firstmodified": OpEvent;
|
|
15
|
+
"needsupdate": OpEvent;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// 创建一个类型来检测意外的 override
|
|
19
|
+
// AI太好用了你知道吗
|
|
20
|
+
type CheckFinalOverrides<T> = {
|
|
21
|
+
[K in keyof T]: K extends keyof OpEventMap ?
|
|
22
|
+
T[K] extends OpEventMap[K] ? T[K] : never :
|
|
23
|
+
T[K]
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
interface OpEventMap extends CheckFinalOverrides<DirectlyInstaciableEventMap> {
|
|
27
|
+
"error": OperationErrorEvent;
|
|
28
|
+
"maxcombochanged": MaxComboChangeEvent;
|
|
29
|
+
"undo": OperationEvent;
|
|
30
|
+
"redo": OperationEvent;
|
|
31
|
+
"do": OperationEvent;
|
|
32
|
+
"needsreflow": NeedsReflowEvent;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class OpEvent extends Event {
|
|
37
|
+
protected constructor(type: OpEventType) {
|
|
38
|
+
super(type);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 如果这个类型没有对应子类应该用这个
|
|
42
|
+
*/
|
|
43
|
+
static create(type: keyof DirectlyInstaciableEventMap) {
|
|
44
|
+
return new OpEvent(type);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class NeedsReflowEvent extends OpEvent {
|
|
49
|
+
constructor(public condition: number) {
|
|
50
|
+
super("needsreflow");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export class OperationEvent extends OpEvent {
|
|
55
|
+
constructor(t: "do" | "undo" | "redo" | "error", public operation: Operation) {
|
|
56
|
+
super(t);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export class OperationErrorEvent extends OperationEvent {
|
|
61
|
+
constructor(operation: Operation, public error: Error) {
|
|
62
|
+
super("error", operation);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export class MaxComboChangeEvent extends OpEvent {
|
|
67
|
+
constructor(public comboDelta: number) {
|
|
68
|
+
super("maxcombochanged");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
export class OperationList extends EventTarget {
|
|
75
|
+
operations: Operation[];
|
|
76
|
+
undoneOperations: Operation[];
|
|
77
|
+
constructor(public chart: Chart) {
|
|
78
|
+
super()
|
|
79
|
+
this.operations = [];
|
|
80
|
+
this.undoneOperations = [];
|
|
81
|
+
}
|
|
82
|
+
undo() {
|
|
83
|
+
const op = this.operations.pop()
|
|
84
|
+
if (op) {
|
|
85
|
+
if (!this.chart.modified){
|
|
86
|
+
this.chart.modified = true;
|
|
87
|
+
this.dispatchEvent(OpEvent.create("firstmodified"))
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
op.undo(this.chart);
|
|
92
|
+
} catch (e) {
|
|
93
|
+
this.dispatchEvent(new OperationErrorEvent(op, e as Error))
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
this.undoneOperations.push(op)
|
|
97
|
+
this.dispatchEvent(new OperationEvent("undo", op))
|
|
98
|
+
this.processFlags(op);
|
|
99
|
+
} else {
|
|
100
|
+
this.dispatchEvent(OpEvent.create("noundo"))
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
redo() {
|
|
104
|
+
const op = this.undoneOperations.pop()
|
|
105
|
+
if (op) {
|
|
106
|
+
if (!this.chart.modified){
|
|
107
|
+
this.chart.modified = true;
|
|
108
|
+
this.dispatchEvent(OpEvent.create("firstmodified"))
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
op.do(this.chart);
|
|
113
|
+
} catch (e) {
|
|
114
|
+
this.dispatchEvent(new OperationErrorEvent(op, e as Error))
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
this.operations.push(op)
|
|
118
|
+
this.dispatchEvent(new OperationEvent("redo", op))
|
|
119
|
+
this.processFlags(op);
|
|
120
|
+
} else {
|
|
121
|
+
this.dispatchEvent(OpEvent.create("noredo"))
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
do(operation: Operation) {
|
|
125
|
+
if (operation.ineffective) {
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
if (!this.chart.modified){
|
|
129
|
+
this.chart.modified = true;
|
|
130
|
+
this.dispatchEvent(OpEvent.create("firstmodified"))
|
|
131
|
+
}
|
|
132
|
+
// 如果上一个操作是同一个构造器的,那么试图修改上一个操作而不是立即推入新的操作
|
|
133
|
+
if (this.operations.length !== 0) {
|
|
134
|
+
|
|
135
|
+
const lastOp = this.operations[this.operations.length - 1]
|
|
136
|
+
if (operation.constructor === lastOp.constructor) {
|
|
137
|
+
// 返回值指示是否重写成功
|
|
138
|
+
if (lastOp.rewrite(operation)) {
|
|
139
|
+
this.processFlags(operation)
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
operation.do(this.chart);
|
|
146
|
+
} catch (e) {
|
|
147
|
+
this.dispatchEvent(new OperationErrorEvent(operation, e as Error))
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
this.dispatchEvent(new OperationEvent("do", operation));
|
|
151
|
+
this.processFlags(operation);
|
|
152
|
+
this.operations.push(operation);
|
|
153
|
+
}
|
|
154
|
+
processFlags(operation: Operation) {
|
|
155
|
+
|
|
156
|
+
if (operation.updatesEditor) {
|
|
157
|
+
this.dispatchEvent(OpEvent.create("needsupdate"));
|
|
158
|
+
}
|
|
159
|
+
if (operation.comboDelta) {
|
|
160
|
+
this.dispatchEvent(new MaxComboChangeEvent(operation.comboDelta));
|
|
161
|
+
}
|
|
162
|
+
if (operation.reflows) {
|
|
163
|
+
this.dispatchEvent(new NeedsReflowEvent(operation.reflows))
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
clear() {
|
|
167
|
+
this.operations = [];
|
|
168
|
+
}
|
|
169
|
+
addEventListener<T extends OpEventType>(type: T, listener: (event: OpEventMap[T]) => void, options?: boolean | AddEventListenerOptions): void {
|
|
170
|
+
super.addEventListener(type, listener, options);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
export abstract class Operation {
|
|
176
|
+
ineffective: boolean;
|
|
177
|
+
updatesEditor: boolean;
|
|
178
|
+
// 用于判定线编辑区的重排,若操作完成时的布局为这个值就会重排
|
|
179
|
+
reflows: number;
|
|
180
|
+
/**
|
|
181
|
+
* 此操作对谱面总物量产生了多少影响,正增负减。
|
|
182
|
+
*
|
|
183
|
+
* 如果操作自身无法评估,应返回NaN,导致全谱重新数清物量
|
|
184
|
+
*/
|
|
185
|
+
comboDelta: number;
|
|
186
|
+
constructor() {
|
|
187
|
+
|
|
188
|
+
}
|
|
189
|
+
abstract do(chart: Chart): void
|
|
190
|
+
abstract undo(chart: Chart): void
|
|
191
|
+
rewrite(op: typeof this): boolean {return false;}
|
|
192
|
+
toString(): string {
|
|
193
|
+
return this.constructor.name;
|
|
194
|
+
}
|
|
195
|
+
static lazy<C extends new (...args: any[]) => any = typeof this>(this: C, ...args: ConstructorParameters<C>) {
|
|
196
|
+
return new LazyOperation<C>(this, ...args)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* 懒操作,实例化的时候不记录任何数据,do的时候才执行真正实例化
|
|
205
|
+
* 防止连续的操作中状态改变导致的错误
|
|
206
|
+
*/
|
|
207
|
+
export class LazyOperation<C extends new (...args: any[]) => any> extends Operation {
|
|
208
|
+
public operationClass: C;
|
|
209
|
+
public args: ConstructorParameters<C>;
|
|
210
|
+
public operation: InstanceType<C> | null = null;
|
|
211
|
+
constructor(
|
|
212
|
+
operationClass: C,
|
|
213
|
+
...args: ConstructorParameters<C>
|
|
214
|
+
) {
|
|
215
|
+
super();
|
|
216
|
+
this.operationClass = operationClass;
|
|
217
|
+
this.args = args;
|
|
218
|
+
}
|
|
219
|
+
do(chart: Chart) {
|
|
220
|
+
this.operation = new this.operationClass(...this.args);
|
|
221
|
+
this.operation.do(chart);
|
|
222
|
+
}
|
|
223
|
+
undo(chart: Chart) {
|
|
224
|
+
this.operation.undo(chart);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* C语言借来的概念
|
|
231
|
+
*
|
|
232
|
+
* 一个不确定类型的子操作
|
|
233
|
+
*
|
|
234
|
+
* 注意这个操作不懒,会在构造时就实例化子操作
|
|
235
|
+
*/
|
|
236
|
+
export class UnionOperation<T extends Operation> extends Operation {
|
|
237
|
+
operation: T;
|
|
238
|
+
constructor(matcher: () => T) {
|
|
239
|
+
super();
|
|
240
|
+
this.operation = matcher();
|
|
241
|
+
if (!this.operation) {
|
|
242
|
+
this.ineffective = true;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
// 这样子写不够严密,如果要继承这个类,并且子操作需要谱面,就要重写这个方法的签名
|
|
246
|
+
do(chart?: Chart) {
|
|
247
|
+
this.operation.do(chart);
|
|
248
|
+
}
|
|
249
|
+
undo(chart?: Chart) {
|
|
250
|
+
this.operation.undo(chart);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
export class ComplexOperation<T extends Operation[]> extends Operation {
|
|
256
|
+
subOperations: T;
|
|
257
|
+
length: number;
|
|
258
|
+
constructor(...sub: T) {
|
|
259
|
+
super()
|
|
260
|
+
this.subOperations = sub
|
|
261
|
+
this.length = sub.length
|
|
262
|
+
this.reflows = sub.reduce((prev, op) => prev | op.reflows, 0);
|
|
263
|
+
this.updatesEditor = sub.some((op) => op.updatesEditor);
|
|
264
|
+
this.comboDelta = sub.reduce((prev, op) => prev + op.comboDelta, 0);
|
|
265
|
+
}
|
|
266
|
+
// 这样子写不够严密,如果要继承这个类,并且子操作需要谱面,就要重写这个方法的签名
|
|
267
|
+
do(chart?: Chart) {
|
|
268
|
+
const length = this.length
|
|
269
|
+
for (let i = 0; i < length; i++) {
|
|
270
|
+
const op = this.subOperations[i]
|
|
271
|
+
if (op.ineffective) {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
op.do(chart)
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
undo(chart?: Chart) {
|
|
278
|
+
const length = this.length
|
|
279
|
+
for (let i = length - 1; i >= 0; i--) {
|
|
280
|
+
const op = this.subOperations[i]
|
|
281
|
+
if (op.ineffective) { continue; }
|
|
282
|
+
op.undo(chart)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
package/bpm.ts
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import { EventType, type BPMSegmentData, type TimeT } from "./chartTypes";
|
|
2
|
+
import { EventEndNode, EventNodeLike, EventNodeSequence, EventStartNode, type AnyEN, type ENOrHead, type ENOrTail } from "./event";
|
|
3
|
+
import { JumpArray } from "./jumparray";
|
|
4
|
+
import { NodeType } from "./util";
|
|
5
|
+
import TC from "./time";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
/// #declaration:global
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* BPM起始节点类,表示BPM变化的开始点
|
|
12
|
+
* 每个BPMStartNode代表一个BPM值的开始,直到下一个BPM节点
|
|
13
|
+
*/
|
|
14
|
+
export class BPMStartNode extends EventStartNode {
|
|
15
|
+
/** 每拍的秒数(Seconds Per Beat) */
|
|
16
|
+
spb: number;
|
|
17
|
+
/** 缓存的起始积分值,用于时间计算 */
|
|
18
|
+
cachedStartIntegral?: number;
|
|
19
|
+
/** 缓存的积分值,用于时间计算 */
|
|
20
|
+
cachedIntegral?: number;
|
|
21
|
+
/** 下一个BPM结束节点或尾部节点 */
|
|
22
|
+
override next: BPMEndNode | BPMNodeLike<NodeType.TAIL>;
|
|
23
|
+
/** 上一个BPM结束节点或头部节点 */
|
|
24
|
+
override previous: BPMEndNode | BPMNodeLike<NodeType.HEAD>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 创建一个新的BPM起始节点
|
|
28
|
+
* @param startTime 节点开始时间
|
|
29
|
+
* @param bpm BPM值
|
|
30
|
+
*/
|
|
31
|
+
constructor(startTime: TimeT, bpm: number) {
|
|
32
|
+
super(startTime, bpm);
|
|
33
|
+
this.spb = 60 / bpm;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 计算指定拍数对应的秒数
|
|
38
|
+
* @param beats 拍数
|
|
39
|
+
* @returns 对应的秒数
|
|
40
|
+
*/
|
|
41
|
+
getSeconds(beats: number): number {
|
|
42
|
+
return (beats - TC.toBeats(this.time)) * 60 / this.value;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 获取当前BPM段的完整持续时间(秒)
|
|
47
|
+
* @this NonLastBPMStartNode 非最后一个BPM节点
|
|
48
|
+
* @returns 当前BPM段的持续时间(秒)
|
|
49
|
+
*/
|
|
50
|
+
getFullSeconds(this: NonLastBPMStartNode): number {
|
|
51
|
+
return (TC.toBeats(this.next.time) - TC.toBeats(this.time)) * 60 / this.value;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* BPM结束节点类,表示BPM段的结束点
|
|
57
|
+
* 用于标记BPM段的结束位置
|
|
58
|
+
*/
|
|
59
|
+
export class BPMEndNode extends EventEndNode {
|
|
60
|
+
/** 每拍的秒数(Seconds Per Beat) */
|
|
61
|
+
spb: number;
|
|
62
|
+
/** 前一个BPM起始节点 */
|
|
63
|
+
override previous: BPMStartNode;
|
|
64
|
+
/** 下一个BPM起始节点 */
|
|
65
|
+
override next: BPMStartNode;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 创建一个新的BPM结束节点
|
|
69
|
+
* @param endTime 节点结束时间
|
|
70
|
+
*/
|
|
71
|
+
constructor(endTime: TimeT) {
|
|
72
|
+
super(endTime, null);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** 非最后一个BPM起始节点类型 */
|
|
77
|
+
type NonLastBPMStartNode = BPMStartNode & { next: BPMEndNode };
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* BPM节点接口,定义了BPM节点的基本结构
|
|
81
|
+
*/
|
|
82
|
+
interface BPMNodeLike<T extends NodeType> extends EventNodeLike<T> {
|
|
83
|
+
/** 下一个节点 */
|
|
84
|
+
next: [BPMStartNode, null, BNOrTail][T] | null;
|
|
85
|
+
/** 上一个节点 */
|
|
86
|
+
previous: [null, BPMStartNode, BNOrHead][T] | null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** BPM节点类型 */
|
|
90
|
+
type BPMNode = BPMStartNode | BPMEndNode;
|
|
91
|
+
|
|
92
|
+
/** 任意BPM节点类型 */
|
|
93
|
+
type AnyBN = (BPMNode | BPMNodeLike<NodeType.TAIL> | BPMNodeLike<NodeType.HEAD>);
|
|
94
|
+
|
|
95
|
+
/** BPM节点或尾部节点类型 */
|
|
96
|
+
type BNOrTail = BPMNode | BPMNodeLike<NodeType.TAIL>;
|
|
97
|
+
|
|
98
|
+
/** BPM节点或头部节点类型 */
|
|
99
|
+
type BNOrHead = BPMNode | BPMNodeLike<NodeType.HEAD>;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* BPM序列类,管理BPM变化序列
|
|
103
|
+
* 拥有与事件类似的逻辑,每对节点之间代表一个BPM相同的片段
|
|
104
|
+
* 片段之间BPM可以发生改变
|
|
105
|
+
*/
|
|
106
|
+
export class BPMSequence extends EventNodeSequence {
|
|
107
|
+
/** 头部节点 */
|
|
108
|
+
declare head: BPMNodeLike<NodeType.HEAD>;
|
|
109
|
+
/** 尾部节点 */
|
|
110
|
+
declare tail: BPMNodeLike<NodeType.TAIL>;
|
|
111
|
+
/** 从拍数访问节点的跳转数组 */
|
|
112
|
+
declare jump: JumpArray<AnyBN>;
|
|
113
|
+
/** 以秒计时的跳转数组,处理从秒访问节点 */
|
|
114
|
+
secondJump: JumpArray<AnyBN>;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 创建BPM序列
|
|
118
|
+
* @param bpmList BPM片段数据列表
|
|
119
|
+
* @param duration 总持续时间
|
|
120
|
+
*/
|
|
121
|
+
constructor(bpmList: BPMSegmentData[], public duration: number) {
|
|
122
|
+
super(EventType.bpm, null);
|
|
123
|
+
let curPos: BPMNodeLike<NodeType.HEAD> | BPMEndNode = this.head;
|
|
124
|
+
let next = bpmList[0];
|
|
125
|
+
this.listLength = bpmList.length;
|
|
126
|
+
for (let i = 1; i < bpmList.length; i++) {
|
|
127
|
+
const each = next;
|
|
128
|
+
next = bpmList[i];
|
|
129
|
+
const startNode = new BPMStartNode(each.startTime, each.bpm);
|
|
130
|
+
const endNode = new BPMEndNode(next.startTime);
|
|
131
|
+
BPMStartNode.connect(startNode, endNode);
|
|
132
|
+
BPMStartNode.connect(curPos, startNode);
|
|
133
|
+
curPos = endNode;
|
|
134
|
+
}
|
|
135
|
+
const last = new BPMStartNode(next.startTime, next.bpm)
|
|
136
|
+
BPMStartNode.connect(curPos, last);
|
|
137
|
+
BPMStartNode.connect(last, this.tail);
|
|
138
|
+
this.initJump();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* 初始化跳转数组
|
|
143
|
+
*/
|
|
144
|
+
override initJump(): void {
|
|
145
|
+
this.effectiveBeats = TC.toBeats(this.tail.previous.time)
|
|
146
|
+
if (this.effectiveBeats !== 0) {
|
|
147
|
+
super.initJump(); // 为0可以跳过jumpArray,用不到
|
|
148
|
+
// 只有一个BPM片段就会这样
|
|
149
|
+
}
|
|
150
|
+
this.updateSecondJump();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* 更新秒跳转数组
|
|
155
|
+
*/
|
|
156
|
+
updateSecondJump(): void {
|
|
157
|
+
let integral = 0;
|
|
158
|
+
// 计算积分并缓存到BPMNode
|
|
159
|
+
let node: BPMStartNode = this.head.next;
|
|
160
|
+
while (true) {
|
|
161
|
+
node.cachedStartIntegral = integral;
|
|
162
|
+
if (node.next.type === NodeType.TAIL) {
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
integral += (node as NonLastBPMStartNode).getFullSeconds();
|
|
166
|
+
|
|
167
|
+
const endNode = <BPMEndNode>(<BPMStartNode>node).next;
|
|
168
|
+
node.cachedIntegral = integral;
|
|
169
|
+
|
|
170
|
+
node = endNode.next;
|
|
171
|
+
}
|
|
172
|
+
node.cachedStartIntegral = integral;
|
|
173
|
+
if (this.effectiveBeats === 0) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const originalListLength = this.listLength;
|
|
177
|
+
this.secondJump = new JumpArray<AnyBN>(
|
|
178
|
+
this.head,
|
|
179
|
+
this.tail,
|
|
180
|
+
originalListLength,
|
|
181
|
+
this.duration,
|
|
182
|
+
(node: AnyBN) => {
|
|
183
|
+
if (node.type === NodeType.TAIL) {
|
|
184
|
+
return [null, null];
|
|
185
|
+
}
|
|
186
|
+
if (node.type === NodeType.HEAD) {
|
|
187
|
+
return [0, node.next];
|
|
188
|
+
}
|
|
189
|
+
const endNode = <BPMEndNode>(<BPMStartNode>node).next;
|
|
190
|
+
const time = node.cachedIntegral;
|
|
191
|
+
const nextNode = endNode.next;
|
|
192
|
+
if (nextNode.next.type === NodeType.TAIL) {
|
|
193
|
+
return [time, nextNode.next]; // Tailer代替最后一个StartNode去占位
|
|
194
|
+
} else {
|
|
195
|
+
return [time, nextNode];
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
(node: BPMStartNode, seconds: number) => {
|
|
199
|
+
return node.cachedIntegral > seconds ? false : (<BPMEndNode>node.next).next;
|
|
200
|
+
}
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* 更新跳转数组
|
|
206
|
+
* @param from 起始节点
|
|
207
|
+
* @param to 结束节点
|
|
208
|
+
*/
|
|
209
|
+
override updateJump(from: ENOrHead, to: ENOrTail): void {
|
|
210
|
+
super.updateJump(from, to);
|
|
211
|
+
this.updateSecondJump();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* 根据秒数获取BPM起始节点
|
|
216
|
+
* @param seconds 秒数
|
|
217
|
+
* @returns 对应的BPM起始节点
|
|
218
|
+
*/
|
|
219
|
+
getNodeBySeconds(seconds: number): BPMStartNode {
|
|
220
|
+
if (this.effectiveBeats === 0) {
|
|
221
|
+
return this.tail.previous
|
|
222
|
+
}
|
|
223
|
+
const node = this.secondJump.getNodeAt(seconds);
|
|
224
|
+
if (node.type === NodeType.TAIL) {
|
|
225
|
+
return node.previous;
|
|
226
|
+
}
|
|
227
|
+
return node as BPMStartNode;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* 导出BPM数据
|
|
232
|
+
* @returns BPM片段数据数组
|
|
233
|
+
*/
|
|
234
|
+
dumpBPM(): BPMSegmentData[] {
|
|
235
|
+
let cur = this.head.next;
|
|
236
|
+
const ret: BPMSegmentData[] = [];
|
|
237
|
+
while (true) {
|
|
238
|
+
ret.push({
|
|
239
|
+
bpm: cur.value,
|
|
240
|
+
startTime: cur.time
|
|
241
|
+
})
|
|
242
|
+
const end = cur.next;
|
|
243
|
+
if (end.type === NodeType.TAIL) {
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
cur = end.next;
|
|
247
|
+
}
|
|
248
|
+
return ret;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* 根据拍数获取节点
|
|
253
|
+
* @param beats 拍数
|
|
254
|
+
* @param usePrev 是否使用前一个节点
|
|
255
|
+
* @returns 对应的BPM起始节点
|
|
256
|
+
*/
|
|
257
|
+
getNodeAt(beats: number, usePrev?: boolean): BPMStartNode {
|
|
258
|
+
return super.getNodeAt(beats, usePrev) as BPMStartNode;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* 时间计算器类,用于处理拍数与秒数之间的转换
|
|
264
|
+
*/
|
|
265
|
+
export class TimeCalculator {
|
|
266
|
+
/** BPM片段数据列表 */
|
|
267
|
+
bpmList: BPMSegmentData[];
|
|
268
|
+
/** BPM序列 */
|
|
269
|
+
readonly bpmSequence: BPMSequence;
|
|
270
|
+
/** 总持续时间 */
|
|
271
|
+
duration: number;
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* 创建时间计算器
|
|
275
|
+
*/
|
|
276
|
+
constructor() {
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* 初始化BPM序列
|
|
281
|
+
*/
|
|
282
|
+
initSequence() {
|
|
283
|
+
const bpmList = this.bpmList;
|
|
284
|
+
// @ts-expect-error 不在构造器中初始化的只读属性
|
|
285
|
+
this.bpmSequence = new BPMSequence(bpmList, this.duration);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* 将拍数转换为秒数
|
|
290
|
+
* @param beats 拍数
|
|
291
|
+
* @returns 对应的秒数
|
|
292
|
+
*/
|
|
293
|
+
toSeconds(beats: number) {
|
|
294
|
+
const node: BPMStartNode = this.bpmSequence.getNodeAt(beats);
|
|
295
|
+
return node.cachedStartIntegral + node.getSeconds(beats)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* 获取从beats1到beats2的秒数
|
|
300
|
+
* @param beats1 起始拍数
|
|
301
|
+
* @param beats2 结束拍数
|
|
302
|
+
* @returns 两拍数之间的秒数差
|
|
303
|
+
*/
|
|
304
|
+
segmentToSeconds(beats1: number, beats2: number): number {
|
|
305
|
+
const ret = this.toSeconds(beats2) - this.toSeconds(beats1)
|
|
306
|
+
return ret
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* 将秒数转换为拍数
|
|
311
|
+
* @param seconds 秒数
|
|
312
|
+
* @returns 对应的拍数
|
|
313
|
+
*/
|
|
314
|
+
secondsToBeats(seconds: number) {
|
|
315
|
+
const node = this.bpmSequence.getNodeBySeconds(seconds);
|
|
316
|
+
// console.log("node:", node)
|
|
317
|
+
const beats = (seconds - node.cachedStartIntegral) / node.spb;
|
|
318
|
+
return TC.toBeats(node.time) + beats
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* 导出BPM数据
|
|
323
|
+
* @returns BPM片段数据数组
|
|
324
|
+
*/
|
|
325
|
+
dump(): BPMSegmentData[] {
|
|
326
|
+
return this.bpmSequence.dumpBPM();
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
/// #enddeclaration
|