kipphi 2.0.0 → 2.0.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/bpm.ts +213 -0
- package/chart.ts +172 -80
- package/chartTypes.ts +62 -20
- package/easing.ts +89 -83
- package/env.ts +173 -0
- package/evaluator.ts +35 -11
- package/event.ts +273 -225
- package/index.ts +14 -11
- package/judgeline.ts +381 -84
- package/jumparray.ts +11 -11
- package/note.ts +31 -54
- package/operation.ts +1378 -0
- package/package.json +1 -1
- package/rpeChartCompiler.ts +133 -98
- package/time.ts +35 -223
- package/tsconfig.json +1 -2
- package/util.ts +21 -1
- package/version.ts +1 -1
package/bpm.ts
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
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
|
+
*
|
|
12
|
+
*/
|
|
13
|
+
export class BPMStartNode extends EventStartNode {
|
|
14
|
+
spb: number;
|
|
15
|
+
cachedStartIntegral?: number;
|
|
16
|
+
cachedIntegral?: number;
|
|
17
|
+
override next: BPMEndNode | BPMNodeLike<NodeType.TAIL>;
|
|
18
|
+
override previous: BPMEndNode | BPMNodeLike<NodeType.HEAD>;
|
|
19
|
+
constructor(startTime: TimeT, bpm: number) {
|
|
20
|
+
super(startTime, bpm);
|
|
21
|
+
this.spb = 60 / bpm;
|
|
22
|
+
}
|
|
23
|
+
getSeconds(beats: number): number {
|
|
24
|
+
return (beats - TC.toBeats(this.time)) * 60 / this.value;
|
|
25
|
+
}
|
|
26
|
+
getFullSeconds(this: NonLastBPMStartNode): number {
|
|
27
|
+
return (TC.toBeats(this.next.time) - TC.toBeats(this.time)) * 60 / this.value;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export class BPMEndNode extends EventEndNode {
|
|
31
|
+
spb: number;
|
|
32
|
+
override previous: BPMStartNode;
|
|
33
|
+
override next: BPMStartNode;
|
|
34
|
+
constructor(endTime: TimeT) {
|
|
35
|
+
super(endTime, null);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type NonLastBPMStartNode = BPMStartNode & { next: BPMEndNode };
|
|
40
|
+
|
|
41
|
+
interface BPMNodeLike<T extends NodeType> extends EventNodeLike<T> {
|
|
42
|
+
next: [BPMStartNode, null, BNOrTail][T] | null;
|
|
43
|
+
previous: [null, BPMStartNode, BNOrHead][T] | null;
|
|
44
|
+
}
|
|
45
|
+
type BPMNode = BPMStartNode | BPMEndNode;
|
|
46
|
+
type AnyBN = (BPMNode | BPMNodeLike<NodeType.TAIL> | BPMNodeLike<NodeType.HEAD>);
|
|
47
|
+
type BNOrTail = BPMNode | BPMNodeLike<NodeType.TAIL>;
|
|
48
|
+
type BNOrHead = BPMNode | BPMNodeLike<NodeType.HEAD>;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 拥有与事件类似的逻辑
|
|
52
|
+
* 每对节点之间代表一个BPM相同的片段
|
|
53
|
+
* 片段之间BPM可以发生改变
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
export class BPMSequence extends EventNodeSequence {
|
|
57
|
+
declare head: BPMNodeLike<NodeType.HEAD>;
|
|
58
|
+
declare tail: BPMNodeLike<NodeType.TAIL>;
|
|
59
|
+
/** 从拍数访问节点 */
|
|
60
|
+
override jump: JumpArray<AnyEN>;
|
|
61
|
+
/** 以秒计时的跳数组,处理从秒访问节点 */
|
|
62
|
+
secondJump: JumpArray<AnyBN>;
|
|
63
|
+
constructor(bpmList: BPMSegmentData[], public duration: number) {
|
|
64
|
+
super(EventType.bpm, null);
|
|
65
|
+
let curPos: BPMNodeLike<NodeType.HEAD> | BPMEndNode = this.head;
|
|
66
|
+
let next = bpmList[0];
|
|
67
|
+
this.listLength = bpmList.length;
|
|
68
|
+
for (let i = 1; i < bpmList.length; i++) {
|
|
69
|
+
const each = next;
|
|
70
|
+
next = bpmList[i];
|
|
71
|
+
const startNode = new BPMStartNode(each.startTime, each.bpm);
|
|
72
|
+
const endNode = new BPMEndNode(next.startTime);
|
|
73
|
+
BPMStartNode.connect(startNode, endNode);
|
|
74
|
+
BPMStartNode.connect(curPos, startNode);
|
|
75
|
+
curPos = endNode;
|
|
76
|
+
}
|
|
77
|
+
const last = new BPMStartNode(next.startTime, next.bpm)
|
|
78
|
+
BPMStartNode.connect(curPos, last);
|
|
79
|
+
BPMStartNode.connect(last, this.tail);
|
|
80
|
+
this.initJump();
|
|
81
|
+
}
|
|
82
|
+
override initJump(): void {
|
|
83
|
+
console.log(this)
|
|
84
|
+
this.effectiveBeats = TC.toBeats(this.tail.previous.time)
|
|
85
|
+
if (this.effectiveBeats !== 0) {
|
|
86
|
+
super.initJump(); // 为0可以跳过jumpArray,用不到
|
|
87
|
+
// 只有一个BPM片段就会这样
|
|
88
|
+
}
|
|
89
|
+
this.updateSecondJump();
|
|
90
|
+
}
|
|
91
|
+
updateSecondJump(): void {
|
|
92
|
+
let integral = 0;
|
|
93
|
+
// 计算积分并缓存到BPMNode
|
|
94
|
+
let node: BPMStartNode = this.head.next;
|
|
95
|
+
while (true) {
|
|
96
|
+
node.cachedStartIntegral = integral;
|
|
97
|
+
if (node.next.type === NodeType.TAIL) {
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
integral += (node as NonLastBPMStartNode).getFullSeconds();
|
|
101
|
+
|
|
102
|
+
const endNode = <BPMEndNode>(<BPMStartNode>node).next;
|
|
103
|
+
node.cachedIntegral = integral;
|
|
104
|
+
|
|
105
|
+
node = endNode.next;
|
|
106
|
+
}
|
|
107
|
+
node.cachedStartIntegral = integral;
|
|
108
|
+
if (this.effectiveBeats === 0) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const originalListLength = this.listLength;
|
|
112
|
+
this.secondJump = new JumpArray<AnyBN>(
|
|
113
|
+
this.head,
|
|
114
|
+
this.tail,
|
|
115
|
+
originalListLength,
|
|
116
|
+
this.duration,
|
|
117
|
+
(node: BPMStartNode) => {
|
|
118
|
+
if (node.type === NodeType.TAIL) {
|
|
119
|
+
return [null, null];
|
|
120
|
+
}
|
|
121
|
+
if (node.type === NodeType.HEAD) {
|
|
122
|
+
return [0, node.next];
|
|
123
|
+
}
|
|
124
|
+
const endNode = <BPMEndNode>(<BPMStartNode>node).next;
|
|
125
|
+
const time = node.cachedIntegral;
|
|
126
|
+
const nextNode = endNode.next;
|
|
127
|
+
if (nextNode.next.type === NodeType.TAIL) {
|
|
128
|
+
return [time, nextNode.next]; // Tailer代替最后一个StartNode去占位
|
|
129
|
+
} else {
|
|
130
|
+
return [time, nextNode];
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
(node: BPMStartNode, seconds: number) => {
|
|
134
|
+
return node.cachedIntegral > seconds ? false : (<BPMEndNode>node.next).next;
|
|
135
|
+
}
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
override updateJump(from: ENOrHead, to: ENOrTail): void {
|
|
139
|
+
super.updateJump(from, to);
|
|
140
|
+
this.updateSecondJump();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
getNodeBySeconds(seconds: number): BPMStartNode {
|
|
144
|
+
if (this.effectiveBeats === 0) {
|
|
145
|
+
return this.tail.previous
|
|
146
|
+
}
|
|
147
|
+
const node = this.secondJump.getNodeAt(seconds);
|
|
148
|
+
if (node.type === NodeType.TAIL) {
|
|
149
|
+
return node.previous;
|
|
150
|
+
}
|
|
151
|
+
return node as BPMStartNode;
|
|
152
|
+
}
|
|
153
|
+
dumpBPM(): BPMSegmentData[] {
|
|
154
|
+
let cur = this.head.next;
|
|
155
|
+
const ret: BPMSegmentData[] = [];
|
|
156
|
+
while (true) {
|
|
157
|
+
ret.push({
|
|
158
|
+
bpm: cur.value,
|
|
159
|
+
startTime: cur.time
|
|
160
|
+
})
|
|
161
|
+
const end = cur.next;
|
|
162
|
+
if (end.type === NodeType.TAIL) {
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
cur = end.next;
|
|
166
|
+
}
|
|
167
|
+
return ret;
|
|
168
|
+
}
|
|
169
|
+
getNodeAt(beats: number, usePrev?: boolean): BPMStartNode {
|
|
170
|
+
return super.getNodeAt(beats, usePrev) as BPMStartNode;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export class TimeCalculator {
|
|
175
|
+
bpmList: BPMSegmentData[];
|
|
176
|
+
bpmSequence: BPMSequence;
|
|
177
|
+
duration: number;
|
|
178
|
+
|
|
179
|
+
constructor() {
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
initSequence() {
|
|
183
|
+
const bpmList = this.bpmList;
|
|
184
|
+
this.bpmSequence = new BPMSequence(bpmList, this.duration);
|
|
185
|
+
}
|
|
186
|
+
toSeconds(beats: number) {
|
|
187
|
+
const node: BPMStartNode = this.bpmSequence.getNodeAt(beats);
|
|
188
|
+
return node.cachedStartIntegral + node.getSeconds(beats)
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* 获取从beats1到beats2的秒数
|
|
192
|
+
* @param beats1
|
|
193
|
+
* @param beats2
|
|
194
|
+
* @returns
|
|
195
|
+
*/
|
|
196
|
+
segmentToSeconds(beats1: number, beats2: number): number {
|
|
197
|
+
const ret = this.toSeconds(beats2) - this.toSeconds(beats1)
|
|
198
|
+
return ret
|
|
199
|
+
}
|
|
200
|
+
secondsToBeats(seconds: number) {
|
|
201
|
+
const node = this.bpmSequence.getNodeBySeconds(seconds);
|
|
202
|
+
// console.log("node:", node)
|
|
203
|
+
const beats = (seconds - node.cachedStartIntegral) / node.spb;
|
|
204
|
+
return TC.toBeats(node.time) + beats
|
|
205
|
+
}
|
|
206
|
+
dump(): BPMSegmentData[] {
|
|
207
|
+
return this.bpmSequence.dumpBPM();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
/// #enddeclaration
|