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/jumparray.ts
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { NodeType } from "./util";
|
|
2
|
+
|
|
3
|
+
// type EndBeats = number;
|
|
4
|
+
const MIN_LENGTH = 128
|
|
5
|
+
const MAX_LENGTH = 1024
|
|
6
|
+
const MINOR_PARTS = 16;
|
|
7
|
+
/// #declaration:global
|
|
8
|
+
|
|
9
|
+
type EndNextFn<T extends TwoDirectionNodeLike> = (node: T) => [endBeats: number, next: T];
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
interface TwoDirectionNodeLike {
|
|
14
|
+
next: this | null;
|
|
15
|
+
previous: this | null;
|
|
16
|
+
type: NodeType;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class JumpArray<T extends TwoDirectionNodeLike> {
|
|
20
|
+
header: T;
|
|
21
|
+
tailer: T;
|
|
22
|
+
array: (T[] | T)[];
|
|
23
|
+
averageBeats: number;
|
|
24
|
+
effectiveBeats: number;
|
|
25
|
+
goPrev: (node: T) => T;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
*
|
|
29
|
+
* @param head 链表头
|
|
30
|
+
* @param tail 链表尾
|
|
31
|
+
* @param originalListLength
|
|
32
|
+
* @param effectiveBeats 有效拍数(等同于音乐拍数)
|
|
33
|
+
* @param endNextFn 接收一个节点,返回该节点分管区段拍数,并给出下个节点。若抵达尾部,返回[null, null](停止遍历的条件是抵达尾部而不是得到null)
|
|
34
|
+
* @param nextFn 接收一个节点,返回下个节点。如果应当停止,返回false。
|
|
35
|
+
*/
|
|
36
|
+
constructor(
|
|
37
|
+
head: T,
|
|
38
|
+
tail: T,
|
|
39
|
+
originalListLength: number,
|
|
40
|
+
effectiveBeats: number,
|
|
41
|
+
public endNextFn: EndNextFn<T>,
|
|
42
|
+
public nextFn: (node: T, beats: number) => T | false,
|
|
43
|
+
public resolveLastNode: (node: T) => T = (node) => node
|
|
44
|
+
// goPrev: (node: T) => T
|
|
45
|
+
) {
|
|
46
|
+
this.header = head;
|
|
47
|
+
this.tailer = tail;
|
|
48
|
+
// const originalListLength = this.listLength
|
|
49
|
+
const listLength: number = Math.max(MIN_LENGTH, Math.min(originalListLength * 4, MAX_LENGTH));
|
|
50
|
+
const averageBeats: number = Math.pow(2, Math.ceil(Math.log2(effectiveBeats / listLength)));
|
|
51
|
+
const exactLength: number = Math.ceil(effectiveBeats / averageBeats);
|
|
52
|
+
// console.log(exactLength, listLength, averageBeats, exactLength)
|
|
53
|
+
// console.log(originalListLength, effectiveBeats, averageBeats, minorBeats, exactLength)
|
|
54
|
+
const jumpArray: (T | T[])[] = new Array(exactLength);
|
|
55
|
+
this.array = jumpArray;
|
|
56
|
+
this.averageBeats = averageBeats;
|
|
57
|
+
this.effectiveBeats = exactLength * averageBeats;
|
|
58
|
+
this.updateRange(head, tail)
|
|
59
|
+
}
|
|
60
|
+
updateEffectiveBeats(val: number) {
|
|
61
|
+
this.effectiveBeats = val;
|
|
62
|
+
const averageBeats = this.averageBeats;
|
|
63
|
+
const exactLength: number = Math.ceil(val / averageBeats);
|
|
64
|
+
const currentLength = this.array.length
|
|
65
|
+
if (exactLength < currentLength) {
|
|
66
|
+
this.array.splice(exactLength, currentLength - exactLength)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
updateAverageBeats() {
|
|
70
|
+
const length = this.array.length;
|
|
71
|
+
if (length >= 1024) {
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
let crowded = 0
|
|
75
|
+
for (let i = 0; i < 50; i++) {
|
|
76
|
+
const index = Math.floor(Math.random() * length);
|
|
77
|
+
if (Array.isArray(this.array[index])) {
|
|
78
|
+
crowded++
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (crowded > 30) {
|
|
82
|
+
this.averageBeats /= 2
|
|
83
|
+
this.updateRange(this.header, this.tailer)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
*
|
|
88
|
+
* @param firstNode 不含
|
|
89
|
+
* @param lastNode 含
|
|
90
|
+
*/
|
|
91
|
+
updateRange(firstNode: T, lastNode: T) {
|
|
92
|
+
const {endNextFn, effectiveBeats, resolveLastNode} = this;
|
|
93
|
+
lastNode = resolveLastNode(lastNode);
|
|
94
|
+
// console.log(firstNode, lastNode)
|
|
95
|
+
/**
|
|
96
|
+
*
|
|
97
|
+
* @param startTime
|
|
98
|
+
* @param endTime 就是节点管辖范围的终止点,可以超过该刻度的最大值
|
|
99
|
+
*/
|
|
100
|
+
const fillMinor = (startTime: number, endTime: number) => {
|
|
101
|
+
const minorArray: T[] = <T[]>jumpArray[jumpIndex];
|
|
102
|
+
const currentJumpBeats: number = jumpIndex * averageBeats
|
|
103
|
+
const startsFrom: number = startTime < currentJumpBeats ? 0 : Math.ceil((startTime - currentJumpBeats) / minorBeats)
|
|
104
|
+
const endsBefore: number = endTime > currentJumpBeats + averageBeats ? MINOR_PARTS : Math.ceil((endTime - currentJumpBeats) / minorBeats)
|
|
105
|
+
for (let minorIndex = startsFrom; minorIndex < endsBefore; minorIndex++) {
|
|
106
|
+
minorArray[minorIndex] = currentNode;
|
|
107
|
+
}
|
|
108
|
+
// console.log(jumpIndex, arrayForIn(minorArray, (n) => node2string(n)).join("]["))
|
|
109
|
+
// console.log("cur:", currentNode)
|
|
110
|
+
}
|
|
111
|
+
const jumpArray = this.array
|
|
112
|
+
const averageBeats: number = this.averageBeats;
|
|
113
|
+
const minorBeats: number = averageBeats / MINOR_PARTS;
|
|
114
|
+
let [previousEndTime, currentNode] = endNextFn(firstNode);
|
|
115
|
+
let jumpIndex = Math.floor(previousEndTime / averageBeats); // 这里写漏了特此留念
|
|
116
|
+
for (;;) {
|
|
117
|
+
let [endTime, nextNode] = endNextFn(currentNode);
|
|
118
|
+
// console.log("----Node:", currentNode, "next:", nextNode, "endTime:", endTime, "previousEndTime:", previousEndTime )
|
|
119
|
+
if (endTime === null) {
|
|
120
|
+
endTime = effectiveBeats;
|
|
121
|
+
}
|
|
122
|
+
// Hold树可能会不出现这种情况,故需特别考虑
|
|
123
|
+
if (endTime >= previousEndTime) {
|
|
124
|
+
while (endTime >= (jumpIndex + 1) * averageBeats) {
|
|
125
|
+
if (Array.isArray(jumpArray[jumpIndex])) {
|
|
126
|
+
fillMinor(previousEndTime, endTime)
|
|
127
|
+
} else {
|
|
128
|
+
try {
|
|
129
|
+
// console.log(jumpIndex, currentNode)
|
|
130
|
+
jumpArray[jumpIndex] = currentNode;
|
|
131
|
+
} catch (E) {console.log(jumpIndex, jumpArray);debugger}
|
|
132
|
+
}
|
|
133
|
+
jumpIndex++;
|
|
134
|
+
}
|
|
135
|
+
const currentJumpBeats: number = jumpIndex * averageBeats // 放错了
|
|
136
|
+
if (endTime > currentJumpBeats) {
|
|
137
|
+
let minor = jumpArray[jumpIndex];
|
|
138
|
+
if (!Array.isArray(minor)) {
|
|
139
|
+
jumpArray[jumpIndex] = new Array(MINOR_PARTS);
|
|
140
|
+
}
|
|
141
|
+
fillMinor(previousEndTime, endTime)
|
|
142
|
+
}
|
|
143
|
+
previousEndTime = endTime;
|
|
144
|
+
}
|
|
145
|
+
if (currentNode === lastNode) {
|
|
146
|
+
currentNode = nextNode; // 为了后续可能的填充,防止刻度不满引发错误
|
|
147
|
+
break
|
|
148
|
+
}
|
|
149
|
+
currentNode = nextNode
|
|
150
|
+
}
|
|
151
|
+
const minor = jumpArray[jumpIndex];
|
|
152
|
+
if (Array.isArray(minor)) {
|
|
153
|
+
// console.log("minor", arrayForIn(minor, (n) => node2string(n)))
|
|
154
|
+
if (!minor[MINOR_PARTS - 1]) {
|
|
155
|
+
if (!currentNode) {
|
|
156
|
+
currentNode = this.tailer
|
|
157
|
+
fillMinor(previousEndTime, effectiveBeats)
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
do {
|
|
161
|
+
let [endTime, nextNode] = endNextFn(currentNode);
|
|
162
|
+
if (endTime === null) {
|
|
163
|
+
endTime = this.effectiveBeats;
|
|
164
|
+
}
|
|
165
|
+
if (endTime > previousEndTime) {
|
|
166
|
+
fillMinor(previousEndTime, endTime)
|
|
167
|
+
previousEndTime = endTime;
|
|
168
|
+
}
|
|
169
|
+
currentNode = nextNode;
|
|
170
|
+
} while (previousEndTime < (jumpIndex + 1) * averageBeats)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
getPreviousOf(node: T, beats: number) {
|
|
175
|
+
const jumpAverageBeats = this.averageBeats;
|
|
176
|
+
const jumpPos = Math.floor(beats / jumpAverageBeats);
|
|
177
|
+
const rest = beats - jumpPos * jumpAverageBeats;
|
|
178
|
+
for (let i = jumpPos; i >= 0; i--) {
|
|
179
|
+
let canBeNodeOrArray: T | T[] = this.array[i];
|
|
180
|
+
if (Array.isArray(canBeNodeOrArray)) {
|
|
181
|
+
const minorIndex = Math.floor(rest / (jumpAverageBeats / MINOR_PARTS)) - 1;
|
|
182
|
+
for (let j = minorIndex; j >= 0; j--) {
|
|
183
|
+
const minorNode = canBeNodeOrArray[j];
|
|
184
|
+
if (minorNode !== node) {
|
|
185
|
+
return minorNode as T;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return this.header
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
*
|
|
194
|
+
* @param beats 拍数
|
|
195
|
+
* @ param usePrev 可选,若设为true,则在取到事件头部时会返回前一个事件(即视为左开右闭)
|
|
196
|
+
* @returns 时间索引链表的节点,一般不是head
|
|
197
|
+
*/
|
|
198
|
+
getNodeAt(beats: number): T {
|
|
199
|
+
if (beats < 0) {
|
|
200
|
+
return this.header.next;
|
|
201
|
+
}
|
|
202
|
+
if (beats >= this.effectiveBeats) {
|
|
203
|
+
return this.tailer;
|
|
204
|
+
}
|
|
205
|
+
const jumpAverageBeats = this.averageBeats;
|
|
206
|
+
const jumpPos = Math.floor(beats / jumpAverageBeats);
|
|
207
|
+
const rest = beats - jumpPos * jumpAverageBeats;
|
|
208
|
+
const nextFn = this.nextFn;
|
|
209
|
+
let canBeNodeOrArray: T | T[] = this.array[jumpPos]
|
|
210
|
+
let node: T = Array.isArray(canBeNodeOrArray)
|
|
211
|
+
? canBeNodeOrArray[Math.floor(rest / (jumpAverageBeats / MINOR_PARTS))]
|
|
212
|
+
: canBeNodeOrArray;
|
|
213
|
+
if (node.type === NodeType.TAIL) {
|
|
214
|
+
return node;
|
|
215
|
+
}
|
|
216
|
+
// console.log(this, node, jumpPos, beats)
|
|
217
|
+
if (!node) {
|
|
218
|
+
console.warn("No node:", node, beats)
|
|
219
|
+
debugger
|
|
220
|
+
}
|
|
221
|
+
let next: T | false;
|
|
222
|
+
// console.log(this)
|
|
223
|
+
while (next = nextFn(node, beats)) {
|
|
224
|
+
node = next;
|
|
225
|
+
if (node.type === NodeType.TAIL) {
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return node
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
/// #enddeclaration
|