kipphi 2.0.1 → 2.1.1
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 +9 -4
- package/basic.ts +285 -0
- package/bpm.ts +133 -14
- package/chart.ts +272 -52
- package/chartType.schema.json +584 -0
- package/chartType2.schema.json +1107 -0
- package/chartTypes.ts +70 -11
- package/easing.ts +37 -8
- package/env.ts +36 -1
- package/evaluator.ts +72 -10
- package/event.ts +97 -43
- package/index.d.ts +3055 -0
- package/index.js +5530 -0
- package/index.ts +5 -2
- package/judgeline.ts +25 -21
- package/jumparray.ts +0 -1
- package/line.ts +246 -0
- package/macro.ts +215 -0
- package/note.ts +2 -2
- 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/tsconfig.json +1 -1
- package/version.ts +2 -1
- package/operation.ts +0 -1378
package/README.md
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 用“奇谱”谱面内核将谱面解析为编辑器友好的格式!
|
|
2
|
+
这是一个Phigros KPA、RPE谱面的解析器兼谱面操作系统。它是[KPA(奇谱发生器)](https://github.com/TeamZincs/KPA)的子项目。
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
“奇谱”来自荷兰化学家启普,他发明了“启普发生器”,用于生产气体并随时停止反应。
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
在(Phigros 自制谱Wiki)[https://pgrfm.miraheze.org/wiki]了解更多。
|
|
6
7
|
|
|
8
|
+
# Parse your chart into an editor-friendly format with Kipphi!
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
This is a Phigros KipphiApparatus/Re:PhiEdit ChartJSON Parser and Chart Operating System. It is a subproject of [KPA](https://github.com/TeamZincs/KPA).
|
|
11
|
+
|
|
12
|
+
"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.
|
|
9
13
|
|
|
14
|
+
Learn more at [Phigros Custom Chart Wiki](https://pgrfm.miraheze.org/wiki).
|
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
CHANGED
|
@@ -8,58 +8,116 @@ import TC from "./time";
|
|
|
8
8
|
/// #declaration:global
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* BPM起始节点类,表示BPM变化的开始点
|
|
12
|
+
* 每个BPMStartNode代表一个BPM值的开始,直到下一个BPM节点
|
|
12
13
|
*/
|
|
13
14
|
export class BPMStartNode extends EventStartNode {
|
|
15
|
+
/** 每拍的秒数(Seconds Per Beat) */
|
|
14
16
|
spb: number;
|
|
17
|
+
/** 缓存的起始积分值,用于时间计算 */
|
|
15
18
|
cachedStartIntegral?: number;
|
|
19
|
+
/** 缓存的积分值,用于时间计算 */
|
|
16
20
|
cachedIntegral?: number;
|
|
21
|
+
/** 下一个BPM结束节点或尾部节点 */
|
|
17
22
|
override next: BPMEndNode | BPMNodeLike<NodeType.TAIL>;
|
|
23
|
+
/** 上一个BPM结束节点或头部节点 */
|
|
18
24
|
override previous: BPMEndNode | BPMNodeLike<NodeType.HEAD>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 创建一个新的BPM起始节点
|
|
28
|
+
* @param startTime 节点开始时间
|
|
29
|
+
* @param bpm BPM值
|
|
30
|
+
*/
|
|
19
31
|
constructor(startTime: TimeT, bpm: number) {
|
|
20
32
|
super(startTime, bpm);
|
|
21
33
|
this.spb = 60 / bpm;
|
|
22
34
|
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 计算指定拍数对应的秒数
|
|
38
|
+
* @param beats 拍数
|
|
39
|
+
* @returns 对应的秒数
|
|
40
|
+
*/
|
|
23
41
|
getSeconds(beats: number): number {
|
|
24
42
|
return (beats - TC.toBeats(this.time)) * 60 / this.value;
|
|
25
43
|
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 获取当前BPM段的完整持续时间(秒)
|
|
47
|
+
* @this NonLastBPMStartNode 非最后一个BPM节点
|
|
48
|
+
* @returns 当前BPM段的持续时间(秒)
|
|
49
|
+
*/
|
|
26
50
|
getFullSeconds(this: NonLastBPMStartNode): number {
|
|
27
51
|
return (TC.toBeats(this.next.time) - TC.toBeats(this.time)) * 60 / this.value;
|
|
28
52
|
}
|
|
29
53
|
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* BPM结束节点类,表示BPM段的结束点
|
|
57
|
+
* 用于标记BPM段的结束位置
|
|
58
|
+
*/
|
|
30
59
|
export class BPMEndNode extends EventEndNode {
|
|
60
|
+
/** 每拍的秒数(Seconds Per Beat) */
|
|
31
61
|
spb: number;
|
|
62
|
+
/** 前一个BPM起始节点 */
|
|
32
63
|
override previous: BPMStartNode;
|
|
64
|
+
/** 下一个BPM起始节点 */
|
|
33
65
|
override next: BPMStartNode;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 创建一个新的BPM结束节点
|
|
69
|
+
* @param endTime 节点结束时间
|
|
70
|
+
*/
|
|
34
71
|
constructor(endTime: TimeT) {
|
|
35
72
|
super(endTime, null);
|
|
36
73
|
}
|
|
37
74
|
}
|
|
38
75
|
|
|
76
|
+
/** 非最后一个BPM起始节点类型 */
|
|
39
77
|
type NonLastBPMStartNode = BPMStartNode & { next: BPMEndNode };
|
|
40
78
|
|
|
79
|
+
/**
|
|
80
|
+
* BPM节点接口,定义了BPM节点的基本结构
|
|
81
|
+
*/
|
|
41
82
|
interface BPMNodeLike<T extends NodeType> extends EventNodeLike<T> {
|
|
83
|
+
/** 下一个节点 */
|
|
42
84
|
next: [BPMStartNode, null, BNOrTail][T] | null;
|
|
85
|
+
/** 上一个节点 */
|
|
43
86
|
previous: [null, BPMStartNode, BNOrHead][T] | null;
|
|
44
87
|
}
|
|
88
|
+
|
|
89
|
+
/** BPM节点类型 */
|
|
45
90
|
type BPMNode = BPMStartNode | BPMEndNode;
|
|
91
|
+
|
|
92
|
+
/** 任意BPM节点类型 */
|
|
46
93
|
type AnyBN = (BPMNode | BPMNodeLike<NodeType.TAIL> | BPMNodeLike<NodeType.HEAD>);
|
|
94
|
+
|
|
95
|
+
/** BPM节点或尾部节点类型 */
|
|
47
96
|
type BNOrTail = BPMNode | BPMNodeLike<NodeType.TAIL>;
|
|
97
|
+
|
|
98
|
+
/** BPM节点或头部节点类型 */
|
|
48
99
|
type BNOrHead = BPMNode | BPMNodeLike<NodeType.HEAD>;
|
|
49
100
|
|
|
50
101
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
102
|
+
* BPM序列类,管理BPM变化序列
|
|
103
|
+
* 拥有与事件类似的逻辑,每对节点之间代表一个BPM相同的片段
|
|
53
104
|
* 片段之间BPM可以发生改变
|
|
54
105
|
*/
|
|
55
|
-
|
|
56
106
|
export class BPMSequence extends EventNodeSequence {
|
|
107
|
+
/** 头部节点 */
|
|
57
108
|
declare head: BPMNodeLike<NodeType.HEAD>;
|
|
109
|
+
/** 尾部节点 */
|
|
58
110
|
declare tail: BPMNodeLike<NodeType.TAIL>;
|
|
59
|
-
/**
|
|
60
|
-
|
|
61
|
-
/**
|
|
111
|
+
/** 从拍数访问节点的跳转数组 */
|
|
112
|
+
declare jump: JumpArray<AnyBN>;
|
|
113
|
+
/** 以秒计时的跳转数组,处理从秒访问节点 */
|
|
62
114
|
secondJump: JumpArray<AnyBN>;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 创建BPM序列
|
|
118
|
+
* @param bpmList BPM片段数据列表
|
|
119
|
+
* @param duration 总持续时间
|
|
120
|
+
*/
|
|
63
121
|
constructor(bpmList: BPMSegmentData[], public duration: number) {
|
|
64
122
|
super(EventType.bpm, null);
|
|
65
123
|
let curPos: BPMNodeLike<NodeType.HEAD> | BPMEndNode = this.head;
|
|
@@ -79,8 +137,11 @@ export class BPMSequence extends EventNodeSequence {
|
|
|
79
137
|
BPMStartNode.connect(last, this.tail);
|
|
80
138
|
this.initJump();
|
|
81
139
|
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* 初始化跳转数组
|
|
143
|
+
*/
|
|
82
144
|
override initJump(): void {
|
|
83
|
-
console.log(this)
|
|
84
145
|
this.effectiveBeats = TC.toBeats(this.tail.previous.time)
|
|
85
146
|
if (this.effectiveBeats !== 0) {
|
|
86
147
|
super.initJump(); // 为0可以跳过jumpArray,用不到
|
|
@@ -88,6 +149,10 @@ export class BPMSequence extends EventNodeSequence {
|
|
|
88
149
|
}
|
|
89
150
|
this.updateSecondJump();
|
|
90
151
|
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* 更新秒跳转数组
|
|
155
|
+
*/
|
|
91
156
|
updateSecondJump(): void {
|
|
92
157
|
let integral = 0;
|
|
93
158
|
// 计算积分并缓存到BPMNode
|
|
@@ -114,7 +179,7 @@ export class BPMSequence extends EventNodeSequence {
|
|
|
114
179
|
this.tail,
|
|
115
180
|
originalListLength,
|
|
116
181
|
this.duration,
|
|
117
|
-
(node:
|
|
182
|
+
(node: AnyBN) => {
|
|
118
183
|
if (node.type === NodeType.TAIL) {
|
|
119
184
|
return [null, null];
|
|
120
185
|
}
|
|
@@ -135,11 +200,22 @@ export class BPMSequence extends EventNodeSequence {
|
|
|
135
200
|
}
|
|
136
201
|
);
|
|
137
202
|
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* 更新跳转数组
|
|
206
|
+
* @param from 起始节点
|
|
207
|
+
* @param to 结束节点
|
|
208
|
+
*/
|
|
138
209
|
override updateJump(from: ENOrHead, to: ENOrTail): void {
|
|
139
210
|
super.updateJump(from, to);
|
|
140
211
|
this.updateSecondJump();
|
|
141
212
|
}
|
|
142
213
|
|
|
214
|
+
/**
|
|
215
|
+
* 根据秒数获取BPM起始节点
|
|
216
|
+
* @param seconds 秒数
|
|
217
|
+
* @returns 对应的BPM起始节点
|
|
218
|
+
*/
|
|
143
219
|
getNodeBySeconds(seconds: number): BPMStartNode {
|
|
144
220
|
if (this.effectiveBeats === 0) {
|
|
145
221
|
return this.tail.previous
|
|
@@ -150,6 +226,11 @@ export class BPMSequence extends EventNodeSequence {
|
|
|
150
226
|
}
|
|
151
227
|
return node as BPMStartNode;
|
|
152
228
|
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* 导出BPM数据
|
|
232
|
+
* @returns BPM片段数据数组
|
|
233
|
+
*/
|
|
153
234
|
dumpBPM(): BPMSegmentData[] {
|
|
154
235
|
let cur = this.head.next;
|
|
155
236
|
const ret: BPMSegmentData[] = [];
|
|
@@ -166,43 +247,81 @@ export class BPMSequence extends EventNodeSequence {
|
|
|
166
247
|
}
|
|
167
248
|
return ret;
|
|
168
249
|
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* 根据拍数获取节点
|
|
253
|
+
* @param beats 拍数
|
|
254
|
+
* @param usePrev 是否使用前一个节点
|
|
255
|
+
* @returns 对应的BPM起始节点
|
|
256
|
+
*/
|
|
169
257
|
getNodeAt(beats: number, usePrev?: boolean): BPMStartNode {
|
|
170
258
|
return super.getNodeAt(beats, usePrev) as BPMStartNode;
|
|
171
259
|
}
|
|
172
260
|
}
|
|
173
261
|
|
|
262
|
+
/**
|
|
263
|
+
* 时间计算器类,用于处理拍数与秒数之间的转换
|
|
264
|
+
*/
|
|
174
265
|
export class TimeCalculator {
|
|
266
|
+
/** BPM片段数据列表 */
|
|
175
267
|
bpmList: BPMSegmentData[];
|
|
176
|
-
|
|
268
|
+
/** BPM序列 */
|
|
269
|
+
readonly bpmSequence: BPMSequence;
|
|
270
|
+
/** 总持续时间 */
|
|
177
271
|
duration: number;
|
|
178
272
|
|
|
273
|
+
/**
|
|
274
|
+
* 创建时间计算器
|
|
275
|
+
*/
|
|
179
276
|
constructor() {
|
|
180
277
|
}
|
|
181
278
|
|
|
279
|
+
/**
|
|
280
|
+
* 初始化BPM序列
|
|
281
|
+
*/
|
|
182
282
|
initSequence() {
|
|
183
283
|
const bpmList = this.bpmList;
|
|
284
|
+
// @ts-expect-error 不在构造器中初始化的只读属性
|
|
184
285
|
this.bpmSequence = new BPMSequence(bpmList, this.duration);
|
|
185
286
|
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* 将拍数转换为秒数
|
|
290
|
+
* @param beats 拍数
|
|
291
|
+
* @returns 对应的秒数
|
|
292
|
+
*/
|
|
186
293
|
toSeconds(beats: number) {
|
|
187
294
|
const node: BPMStartNode = this.bpmSequence.getNodeAt(beats);
|
|
188
295
|
return node.cachedStartIntegral + node.getSeconds(beats)
|
|
189
296
|
}
|
|
297
|
+
|
|
190
298
|
/**
|
|
191
299
|
* 获取从beats1到beats2的秒数
|
|
192
|
-
* @param beats1
|
|
193
|
-
* @param beats2
|
|
194
|
-
* @returns
|
|
300
|
+
* @param beats1 起始拍数
|
|
301
|
+
* @param beats2 结束拍数
|
|
302
|
+
* @returns 两拍数之间的秒数差
|
|
195
303
|
*/
|
|
196
304
|
segmentToSeconds(beats1: number, beats2: number): number {
|
|
197
305
|
const ret = this.toSeconds(beats2) - this.toSeconds(beats1)
|
|
198
306
|
return ret
|
|
199
307
|
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* 将秒数转换为拍数
|
|
311
|
+
* @param seconds 秒数
|
|
312
|
+
* @returns 对应的拍数
|
|
313
|
+
*/
|
|
200
314
|
secondsToBeats(seconds: number) {
|
|
201
315
|
const node = this.bpmSequence.getNodeBySeconds(seconds);
|
|
202
316
|
// console.log("node:", node)
|
|
203
317
|
const beats = (seconds - node.cachedStartIntegral) / node.spb;
|
|
204
318
|
return TC.toBeats(node.time) + beats
|
|
205
319
|
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* 导出BPM数据
|
|
323
|
+
* @returns BPM片段数据数组
|
|
324
|
+
*/
|
|
206
325
|
dump(): BPMSegmentData[] {
|
|
207
326
|
return this.bpmSequence.dumpBPM();
|
|
208
327
|
}
|
|
@@ -210,4 +329,4 @@ export class TimeCalculator {
|
|
|
210
329
|
}
|
|
211
330
|
|
|
212
331
|
|
|
213
|
-
/// #enddeclaration
|
|
332
|
+
/// #enddeclaration
|