kipphi 2.1.3-beta.2 → 2.1.3

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/event.ts CHANGED
@@ -391,7 +391,7 @@ export class EventStartNode<VT extends EventValueESType = number> extends EventN
391
391
  return super.clone(offset) as EventStartNode<VT>;
392
392
  };
393
393
  clonePair(offset: TimeT): EventStartNode<VT> {
394
- const endNode = this.previous.type !== NodeType.HEAD ? this.previous.clone(offset) : new EventEndNode(this.time, this.value);
394
+ const endNode = this.previous.type !== NodeType.HEAD ? this.previous.clone(offset) : null;
395
395
  const startNode = this.clone(offset);
396
396
  EventNode.connect(endNode, startNode);
397
397
  return startNode;
package/index.d.ts CHANGED
@@ -1081,133 +1081,6 @@ declare module "judgeline" {
1081
1081
  cachedFloorPositions: Float64Array;
1082
1082
  computeCurrentFloorPosition(beats: number, timeCalculator: TimeCalculator): void;
1083
1083
  getRelativeFloorPositionAt(beats: number, timeCalculator: TimeCalculator): number;
1084
- /**
1085
- * 通过速度序列的FloorPosition反解出一个时间范围。
1086
- *
1087
- * KPA内核代码中最大的一坨史山,没有之一。
1088
- *
1089
- * 谱面渲染时最耗时的函数
1090
- *
1091
- * startY and endY must not be negative
1092
- * @param beats
1093
- * @param timeCalculator
1094
- * @param startY
1095
- * @param endY
1096
- * @returns
1097
- * /
1098
- computeTimeRange(beats: number, timeCalculator: TimeCalculator , startY: number, endY: number): [number, number][] {
1099
- //return [[0, Infinity]]
1100
- //*
1101
- // 提取所有有变化的时间点
1102
- let times: number[] = [];
1103
- const result: [number, number][] = [];
1104
- for (const eventLayer of this.eventLayers) {
1105
- const sequence = eventLayer?.speed;
1106
- if (!sequence) {
1107
- continue;
1108
- }
1109
- let node: EventStartNode = sequence.getNodeAt(beats);
1110
- let endNode: EventEndNode | EventNodeLike<NodeType.TAIL>
1111
- while (true) {
1112
- times.push(TC.toBeats(node.time))
1113
- if ((endNode = node.next).type === NodeType.TAIL) {
1114
- break;
1115
- }
1116
-
1117
- node = endNode.next
1118
- }
1119
- }
1120
- times = [...new Set(times)].sort((a, b) => a - b)
1121
- const len = times.length;
1122
- let nextTime = times[0]
1123
- let nextPosY = this.getStackedFloorPosition(nextTime, timeCalculator)
1124
- let nextSpeed = this.getStackedValue("speed", nextTime, true)
1125
- let range: [number, number] = [undefined, undefined];
1126
- // console.log(times)
1127
- const computeTime = (speed: number, currentPos: number, fore: number) => timeCalculator.secondsToBeats(currentPos / (speed * 120) + timeCalculator.toSeconds(fore));
1128
- for (let i = 0; i < len - 1;) {
1129
- const thisTime = nextTime;
1130
- const thisPosY = nextPosY;
1131
- let thisSpeed = this.getStackedValue("speed", thisTime);
1132
- if (Math.abs(thisSpeed) < 1e-8) {
1133
- thisSpeed = 0; // 不这样做可能导致下面异号判断为真从而死循环
1134
- }
1135
- nextTime = times[i + 1]
1136
- nextPosY = this.getStackedFloorPosition(nextTime, timeCalculator);
1137
- nextSpeed = this.getStackedValue("speed", nextTime, true)
1138
- // console.log(thisSpeed, nextSpeed, thisSpeed * nextSpeed < 0, i, [...result])
1139
- if (thisSpeed * nextSpeed < 0) { // 有变号零点,再次切断,保证处理的每个区间单调性
1140
- //debugger;
1141
- nextTime = (nextTime - thisTime) * (0 - thisSpeed) / (nextSpeed - thisSpeed) + thisTime;
1142
- nextSpeed = 0
1143
- nextPosY = this.getStackedFloorPosition(nextTime, timeCalculator)
1144
- //debugger
1145
- } else {
1146
- // console.log("i++")
1147
- i++
1148
- }
1149
- if (range[0] === undefined) {
1150
- // 变速区间直接全部囊括,匀速要算一下,因为好算
1151
- /*
1152
- 设两个时间点的位置为a,b
1153
- 开始结束点为s,e
1154
- 选中小段一部分在区间内:
1155
- a < s <= b
1156
- 或a > e >= b
1157
- 全部在区间内
1158
- s <= a <= b
1159
- * /
1160
- if (thisPosY < startY && startY <= nextPosY
1161
- || thisPosY > endY && endY >= nextPosY) {
1162
- range[0] = thisSpeed !== nextSpeed ? thisTime : computeTime(
1163
- thisSpeed,
1164
- (thisPosY < nextPosY ? startY : endY) - thisPosY, thisTime)
1165
- } else if (startY <= thisPosY && thisPosY <= endY) {
1166
- range[0] = thisTime;
1167
- }
1168
- }
1169
- // 要注意这里不能合成双分支if因为想要的Y片段可能在一个区间内
1170
- if (range[0] !== undefined) {
1171
- if (thisPosY < endY && endY <= nextPosY || thisPosY > startY && startY >= nextPosY) {
1172
- range[1] = thisSpeed !== nextSpeed ? nextTime : computeTime(
1173
- thisSpeed,
1174
- (thisPosY > nextPosY ? startY : endY) - thisPosY, thisTime)
1175
- result.push(range)
1176
- range = [undefined, undefined];
1177
- }
1178
- }
1179
- }
1180
- const thisPosY = nextPosY;
1181
- const thisTime = nextTime;
1182
- const thisSpeed = this.getStackedValue("speed", thisTime);
1183
- const inf = thisSpeed > 0 ? Infinity : (thisSpeed < 0 ? -Infinity : thisPosY)
1184
- if (range[0] === undefined) {
1185
- // 变速区间直接全部囊括,匀速要算一下,因为好算
1186
- if (thisPosY < startY && startY <= inf || thisPosY >= endY && endY > inf) {
1187
- range[0] = computeTime(
1188
- thisSpeed,
1189
- (thisPosY < inf ? startY : endY) - thisPosY,
1190
- thisTime)
1191
- } else if (thisSpeed === 0) {
1192
- range[0] = 0;
1193
- }
1194
- }
1195
- // 要注意这里不能合成双分支if因为想要的Y片段可能在一个区间内
1196
- if (range[0] !== undefined) {
1197
- if (thisPosY < endY && endY <= inf || thisPosY >= startY && startY > inf) {
1198
- range[1] = computeTime(
1199
- thisSpeed,
1200
- (thisPosY > inf ? startY : endY) - thisPosY,
1201
- thisTime)
1202
- result.push(range)
1203
- } else if (thisSpeed === 0) {
1204
- range[1] = Infinity;
1205
- result.push(range)
1206
- }
1207
- }
1208
- return result;
1209
- //* /
1210
- }*/
1211
1084
  /**
1212
1085
  * 通过速度序列的FloorPosition反解出一个时间范围。
1213
1086
  *
@@ -2608,6 +2481,9 @@ declare module "operation/event" {
2608
2481
  overlapping: boolean;
2609
2482
  constructor(node: EventStartNode<VT>, targetPrevious: EventStartNode<VT>, updatesFP?: boolean);
2610
2483
  }
2484
+ export class EventNodePairAutoInsertOperation<VT extends EventValueESType> extends EventNodePairInsertOperation<VT> {
2485
+ constructor(node: EventStartNode<VT>, parentSeq: EventNodeSequence<VT>, updatesFP?: boolean);
2486
+ }
2611
2487
  export class EventNodeValueChangeOperation<VT extends EventValueESType> extends Operation {
2612
2488
  updatesEditor: boolean;
2613
2489
  node: EventNode<VT>;
@@ -3082,7 +2958,7 @@ declare module "rpeChartCompiler" {
3082
2958
  import type { Chart } from "chart";
3083
2959
  import { type TimeT, type ChartDataRPE, type JudgeLineDataRPE, type EventDataRPELike, type NoteDataRPE, type EventValueESType } from "chartTypes";
3084
2960
  import { type EasedEvaluatorOfType } from "evaluator";
3085
- import { EventEndNode, EventNodeSequence, EventStartNode } from "event";
2961
+ import { EventEndNode, EventNodeSequence, EventStartNode, SpeedENS } from "event";
3086
2962
  import type { JudgeLine } from "judgeline";
3087
2963
  import type { NNList, HNList } from "note";
3088
2964
  /**
@@ -3100,6 +2976,7 @@ declare module "rpeChartCompiler" {
3100
2976
  evaluator: EasedEvaluatorOfType<VT>;
3101
2977
  }, getValue: (node: EventStartNode<VT> | EventEndNode<VT>) => VT): EventDataRPELike<VT>;
3102
2978
  dumpEventNodeSequence<VT extends EventValueESType>(sequence: EventNodeSequence<VT>): EventDataRPELike<VT>[];
2979
+ dumpSpeedENS(seq: SpeedENS): EventDataRPELike<number>[];
3103
2980
  compileNNLists(nnLists: NNList[], hnLists: HNList[]): NoteDataRPE[];
3104
2981
  /**
3105
2982
  * 倒序转换为数组
package/index.js CHANGED
@@ -24,6 +24,61 @@ var checkType = (value, type) => {
24
24
  if (Array.isArray(type)) {
25
25
  return Array.isArray(value) && value.length === type.length && type.every((t, i) => checkType(value[i], t));
26
26
  } else if (typeof type === "string") {
27
+ if (type.startsWith("int")) {
28
+ if (typeof value !== "number" || !Number.isInteger(value)) {
29
+ return false;
30
+ }
31
+ const match = type.match(/^int(\(|\[)(\-?\d+),(\-?\d+|\+)(\)|\])$/);
32
+ if (!match) {
33
+ return true;
34
+ }
35
+ const [, leftBrac, left, right, rightBrac] = match;
36
+ if (!leftBrac) {
37
+ return true;
38
+ }
39
+ const leftN = left === "-" ? -Infinity : Number(left);
40
+ const rightN = right === "+" ? Infinity : Number(right);
41
+ if (value < leftN) {
42
+ return false;
43
+ }
44
+ if (leftBrac === "(" && value === leftN) {
45
+ return false;
46
+ }
47
+ if (value > rightN) {
48
+ return false;
49
+ }
50
+ if (rightBrac === ")" && value === rightN) {
51
+ return false;
52
+ }
53
+ return true;
54
+ } else if (type.startsWith("number")) {
55
+ if (typeof value !== "number") {
56
+ return false;
57
+ }
58
+ const match = type.match(/^number(\(|\[)(\-?\d+),(\-?\d+|\+)(\)|\])$/);
59
+ if (!match) {
60
+ return true;
61
+ }
62
+ const [, leftBrac, left, right, rightBrac] = match;
63
+ if (!leftBrac) {
64
+ return true;
65
+ }
66
+ const leftN = left === "-" ? -Infinity : Number(left);
67
+ const rightN = right === "+" ? Infinity : Number(right);
68
+ if (value < leftN) {
69
+ return false;
70
+ }
71
+ if (leftBrac === "(" && value === leftN) {
72
+ return false;
73
+ }
74
+ if (value > rightN) {
75
+ return false;
76
+ }
77
+ if (rightBrac === ")" && value === rightN) {
78
+ return false;
79
+ }
80
+ return true;
81
+ }
27
82
  return typeof value === type;
28
83
  } else {
29
84
  return value instanceof type;
@@ -1645,7 +1700,7 @@ class EventStartNode extends EventNode {
1645
1700
  return super.clone(offset);
1646
1701
  }
1647
1702
  clonePair(offset) {
1648
- const endNode = this.previous.type !== 0 /* HEAD */ ? this.previous.clone(offset) : new EventEndNode(this.time, this.value);
1703
+ const endNode = this.previous.type !== 0 /* HEAD */ ? this.previous.clone(offset) : null;
1649
1704
  const startNode = this.clone(offset);
1650
1705
  EventNode.connect(endNode, startNode);
1651
1706
  return startNode;
@@ -2280,7 +2335,12 @@ class NoteNode extends NoteNodeLike {
2280
2335
  }
2281
2336
  }
2282
2337
  remove(note) {
2283
- this.notes.splice(this.notes.indexOf(note), 1);
2338
+ const index = this.notes.indexOf(note);
2339
+ if (index === -1) {
2340
+ console.warn("Note not found in this node!");
2341
+ return;
2342
+ }
2343
+ this.notes.splice(index, 1);
2284
2344
  note.parentNode = null;
2285
2345
  }
2286
2346
  static disconnect(note1, note2) {
@@ -3857,6 +3917,7 @@ __export(exports_operation, {
3857
3917
  EventNodePairRemoveOperation: () => EventNodePairRemoveOperation,
3858
3918
  EventNodePairInsertOrOverwriteOperation: () => EventNodePairInsertOrOverwriteOperation,
3859
3919
  EventNodePairInsertOperation: () => EventNodePairInsertOperation,
3920
+ EventNodePairAutoInsertOperation: () => EventNodePairAutoInsertOperation,
3860
3921
  EventNodeMacroTimeReevaluateOperation: () => EventNodeMacroTimeReevaluateOperation,
3861
3922
  EventNodeEvaluatorChangeOperation: () => EventNodeEvaluatorChangeOperation,
3862
3923
  EventInterpolationOperation: () => EventInterpolationOperation,
@@ -4192,6 +4253,12 @@ class EventNodePairInsertOrOverwriteOperation extends UnionOperation {
4192
4253
  }
4193
4254
  }
4194
4255
 
4256
+ class EventNodePairAutoInsertOperation extends EventNodePairInsertOperation {
4257
+ constructor(node, parentSeq, updatesFP = true) {
4258
+ super(node, parentSeq.getNodeAt(TC2.toBeats(node.time)), updatesFP);
4259
+ }
4260
+ }
4261
+
4195
4262
  class EventNodeValueChangeOperation extends Operation {
4196
4263
  updatesEditor = true;
4197
4264
  node;
@@ -4404,6 +4471,9 @@ class EncapsuleOperation extends ComplexOperation {
4404
4471
  const sequence = easing.eventNodeSequence;
4405
4472
  sequence.effectiveBeats = TC2.toBeats(nodeArray[nodeArray.length - 1].time);
4406
4473
  new MultiNodeAddOperation(nodeArray, sequence).do();
4474
+ const first = sequence.head.next;
4475
+ first.evaluator = nodeArray[0].evaluator;
4476
+ first.value = nodeArray[0].value;
4407
4477
  return new EncapsuleOperation(oldArray, easing);
4408
4478
  }
4409
4479
  }
@@ -5343,8 +5413,20 @@ class RPEChartCompiler {
5343
5413
  compileChart() {
5344
5414
  const chart2 = this.chart;
5345
5415
  const judgeLineGroups = chart2.judgeLineGroups.map((group) => group.name);
5416
+ const hasNotes = (nnList) => {
5417
+ let node = nnList.head.next;
5418
+ while (true) {
5419
+ if (node.type === 1 /* TAIL */) {
5420
+ return false;
5421
+ }
5422
+ if (node.notes.length > 0) {
5423
+ return true;
5424
+ }
5425
+ node = node.next;
5426
+ }
5427
+ };
5346
5428
  const filter = this.deletesEmptyLines ? (line2) => {
5347
- return line2.nnLists.size > 0 || line2.hnLists.size > 0 || line2.eventLayers.length > 0 || ["moveX", "moveY", "rotate", "alpha"].some((evType) => {
5429
+ return [...line2.nnLists].some(([_, l]) => hasNotes(l)) || [...line2.hnLists].some(([_, l]) => hasNotes(l)) || line2.eventLayers.length > 0 && ["moveX", "moveY", "rotate", "alpha"].some((evType) => {
5348
5430
  const seq = line2.eventLayers[0][evType];
5349
5431
  let node = seq.head.next;
5350
5432
  for (let i = 0;i < 2; i++) {
@@ -5427,7 +5509,7 @@ class RPEChartCompiler {
5427
5509
  moveYEvents: layer.moveY ? this.dumpEventNodeSequence(layer.moveY) : undefined,
5428
5510
  rotateEvents: layer.rotate ? this.dumpEventNodeSequence(layer.rotate) : undefined,
5429
5511
  alphaEvents: layer.alpha ? this.dumpEventNodeSequence(layer.alpha) : undefined,
5430
- speedEvents: index === 0 ? this.dumpEventNodeSequence(judgeLine.speedSequence) : undefined
5512
+ speedEvents: index === 0 ? this.dumpSpeedENS(judgeLine.speedSequence) : undefined
5431
5513
  })),
5432
5514
  extended: {
5433
5515
  scaleXEvents: judgeLine.extendedLayer.scaleX ? this.dumpEventNodeSequence(judgeLine.extendedLayer.scaleX) : undefined,
@@ -5525,6 +5607,32 @@ class RPEChartCompiler {
5525
5607
  nodes.push(this.compileEasedEvent(newStart, getValue));
5526
5608
  return nodes;
5527
5609
  }
5610
+ dumpSpeedENS(seq) {
5611
+ const ret = [];
5612
+ let node = seq.head.next;
5613
+ while (true) {
5614
+ const end = node.next;
5615
+ if (end.type === 1 /* TAIL */) {
5616
+ break;
5617
+ }
5618
+ ret.push({
5619
+ start: node.value,
5620
+ end: end.value,
5621
+ startTime: node.time,
5622
+ endTime: end.time,
5623
+ linkgroup: 0
5624
+ });
5625
+ node = end.next;
5626
+ }
5627
+ ret.push({
5628
+ start: node.value,
5629
+ end: node.value,
5630
+ startTime: node.time,
5631
+ endTime: TC2.vadd(node.time, [1, 0, 1]),
5632
+ linkgroup: 0
5633
+ });
5634
+ return ret;
5635
+ }
5528
5636
  compileNNLists(nnLists, hnLists) {
5529
5637
  const noteLists = nnLists.map((list) => this.nnListToArray(list));
5530
5638
  const holdLists = hnLists.map((list) => this.nnListToArray(list));
@@ -5608,8 +5716,8 @@ class RPEChartCompiler {
5608
5716
  srcStart = srcSeq.head.next.value;
5609
5717
  srcEnd = srcSeq.tail.previous.value;
5610
5718
  leftDividedNodeSrc = srcSeq.head.next;
5611
- rightDividedNodeSrc = srcSeq.tail.previous;
5612
- toStopAt = rightDividedNodeSrc;
5719
+ rightDividedNodeSrc = srcSeq.tail.previous.previous.previous;
5720
+ toStopAt = srcSeq.tail.previous;
5613
5721
  srcStartTime = srcSeq.head.next.time;
5614
5722
  srcTimeDelta = TC2.sub(srcSeq.tail.previous.time, srcStartTime);
5615
5723
  }
@@ -5656,13 +5764,13 @@ class RPEChartCompiler {
5656
5764
  if (rightDividedNodeSrc.evaluator instanceof ExpressionEvaluator) {
5657
5765
  throw err.CANNOT_DIVIDE_EXPRESSION_EVALUATOR(seq.id);
5658
5766
  } else {
5659
- first.evaluator = evaluator.deriveWithEasing(new SegmentedEasing(rightDividedNodeSrc.evaluator.easing, 0, newRight));
5767
+ prev.evaluator = evaluator.deriveWithEasing(new SegmentedEasing(rightDividedNodeSrc.evaluator.easing, 0, newRight));
5660
5768
  }
5661
5769
  } else {
5662
5770
  if (rightDividedNodeSrc.evaluator instanceof ExpressionEvaluator) {
5663
5771
  throw err.CANNOT_DIVIDE_EXPRESSION_EVALUATOR(seq.id);
5664
5772
  } else {
5665
- first.evaluator = evaluator.deriveWithEasing(rightDividedNodeSrc.evaluator.easing);
5773
+ prev.evaluator = evaluator.deriveWithEasing(rightDividedNodeSrc.evaluator.easing);
5666
5774
  }
5667
5775
  }
5668
5776
  const endNode2 = currentNode.next.clone();
package/judgeline.ts CHANGED
@@ -405,133 +405,6 @@ export class JudgeLine {
405
405
  getRelativeFloorPositionAt(beats: number, timeCalculator: TimeCalculator) {
406
406
  return this.speedSequence.getFloorPositionAt(beats, timeCalculator) - this.currentFloorPosition;
407
407
  }
408
- /**
409
- * 通过速度序列的FloorPosition反解出一个时间范围。
410
- *
411
- * KPA内核代码中最大的一坨史山,没有之一。
412
- *
413
- * 谱面渲染时最耗时的函数
414
- *
415
- * startY and endY must not be negative
416
- * @param beats
417
- * @param timeCalculator
418
- * @param startY
419
- * @param endY
420
- * @returns
421
- * /
422
- computeTimeRange(beats: number, timeCalculator: TimeCalculator , startY: number, endY: number): [number, number][] {
423
- //return [[0, Infinity]]
424
- //*
425
- // 提取所有有变化的时间点
426
- let times: number[] = [];
427
- const result: [number, number][] = [];
428
- for (const eventLayer of this.eventLayers) {
429
- const sequence = eventLayer?.speed;
430
- if (!sequence) {
431
- continue;
432
- }
433
- let node: EventStartNode = sequence.getNodeAt(beats);
434
- let endNode: EventEndNode | EventNodeLike<NodeType.TAIL>
435
- while (true) {
436
- times.push(TC.toBeats(node.time))
437
- if ((endNode = node.next).type === NodeType.TAIL) {
438
- break;
439
- }
440
-
441
- node = endNode.next
442
- }
443
- }
444
- times = [...new Set(times)].sort((a, b) => a - b)
445
- const len = times.length;
446
- let nextTime = times[0]
447
- let nextPosY = this.getStackedFloorPosition(nextTime, timeCalculator)
448
- let nextSpeed = this.getStackedValue("speed", nextTime, true)
449
- let range: [number, number] = [undefined, undefined];
450
- // console.log(times)
451
- const computeTime = (speed: number, currentPos: number, fore: number) => timeCalculator.secondsToBeats(currentPos / (speed * 120) + timeCalculator.toSeconds(fore));
452
- for (let i = 0; i < len - 1;) {
453
- const thisTime = nextTime;
454
- const thisPosY = nextPosY;
455
- let thisSpeed = this.getStackedValue("speed", thisTime);
456
- if (Math.abs(thisSpeed) < 1e-8) {
457
- thisSpeed = 0; // 不这样做可能导致下面异号判断为真从而死循环
458
- }
459
- nextTime = times[i + 1]
460
- nextPosY = this.getStackedFloorPosition(nextTime, timeCalculator);
461
- nextSpeed = this.getStackedValue("speed", nextTime, true)
462
- // console.log(thisSpeed, nextSpeed, thisSpeed * nextSpeed < 0, i, [...result])
463
- if (thisSpeed * nextSpeed < 0) { // 有变号零点,再次切断,保证处理的每个区间单调性
464
- //debugger;
465
- nextTime = (nextTime - thisTime) * (0 - thisSpeed) / (nextSpeed - thisSpeed) + thisTime;
466
- nextSpeed = 0
467
- nextPosY = this.getStackedFloorPosition(nextTime, timeCalculator)
468
- //debugger
469
- } else {
470
- // console.log("i++")
471
- i++
472
- }
473
- if (range[0] === undefined) {
474
- // 变速区间直接全部囊括,匀速要算一下,因为好算
475
- /*
476
- 设两个时间点的位置为a,b
477
- 开始结束点为s,e
478
- 选中小段一部分在区间内:
479
- a < s <= b
480
- 或a > e >= b
481
- 全部在区间内
482
- s <= a <= b
483
- * /
484
- if (thisPosY < startY && startY <= nextPosY
485
- || thisPosY > endY && endY >= nextPosY) {
486
- range[0] = thisSpeed !== nextSpeed ? thisTime : computeTime(
487
- thisSpeed,
488
- (thisPosY < nextPosY ? startY : endY) - thisPosY, thisTime)
489
- } else if (startY <= thisPosY && thisPosY <= endY) {
490
- range[0] = thisTime;
491
- }
492
- }
493
- // 要注意这里不能合成双分支if因为想要的Y片段可能在一个区间内
494
- if (range[0] !== undefined) {
495
- if (thisPosY < endY && endY <= nextPosY || thisPosY > startY && startY >= nextPosY) {
496
- range[1] = thisSpeed !== nextSpeed ? nextTime : computeTime(
497
- thisSpeed,
498
- (thisPosY > nextPosY ? startY : endY) - thisPosY, thisTime)
499
- result.push(range)
500
- range = [undefined, undefined];
501
- }
502
- }
503
- }
504
- const thisPosY = nextPosY;
505
- const thisTime = nextTime;
506
- const thisSpeed = this.getStackedValue("speed", thisTime);
507
- const inf = thisSpeed > 0 ? Infinity : (thisSpeed < 0 ? -Infinity : thisPosY)
508
- if (range[0] === undefined) {
509
- // 变速区间直接全部囊括,匀速要算一下,因为好算
510
- if (thisPosY < startY && startY <= inf || thisPosY >= endY && endY > inf) {
511
- range[0] = computeTime(
512
- thisSpeed,
513
- (thisPosY < inf ? startY : endY) - thisPosY,
514
- thisTime)
515
- } else if (thisSpeed === 0) {
516
- range[0] = 0;
517
- }
518
- }
519
- // 要注意这里不能合成双分支if因为想要的Y片段可能在一个区间内
520
- if (range[0] !== undefined) {
521
- if (thisPosY < endY && endY <= inf || thisPosY >= startY && startY > inf) {
522
- range[1] = computeTime(
523
- thisSpeed,
524
- (thisPosY > inf ? startY : endY) - thisPosY,
525
- thisTime)
526
- result.push(range)
527
- } else if (thisSpeed === 0) {
528
- range[1] = Infinity;
529
- result.push(range)
530
- }
531
- }
532
- return result;
533
- //* /
534
- }*/
535
408
  /**
536
409
  * 通过速度序列的FloorPosition反解出一个时间范围。
537
410
  *
package/note.ts CHANGED
@@ -307,7 +307,12 @@ export class NoteNode extends NoteNodeLike<NodeType.MIDDLE> {
307
307
  }
308
308
  }
309
309
  remove(note: Note) {
310
- this.notes.splice(this.notes.indexOf(note), 1)
310
+ const index = this.notes.indexOf(note);
311
+ if (index === -1) {
312
+ console.warn("Note not found in this node!")
313
+ return;
314
+ }
315
+ this.notes.splice(index, 1)
311
316
  note.parentNode = null
312
317
  }
313
318
  static disconnect(note1: NNOrHead, note2: NNOrTail) {
@@ -117,7 +117,11 @@ extends UnionOperation<LazyOperation<typeof EventNodePairInsertOperation<VT>> |
117
117
  }
118
118
  }
119
119
 
120
-
120
+ export class EventNodePairAutoInsertOperation<VT extends EventValueESType> extends EventNodePairInsertOperation<VT> {
121
+ constructor(node: EventStartNode<VT>, parentSeq: EventNodeSequence<VT>, updatesFP = true) {
122
+ super(node, parentSeq.getNodeAt(TC.toBeats(node.time)), updatesFP);
123
+ }
124
+ }
121
125
 
122
126
  export class EventNodeValueChangeOperation <VT extends EventValueESType> extends Operation {
123
127
  updatesEditor = true
@@ -362,6 +366,10 @@ export class EncapsuleOperation extends ComplexOperation<[MultiNodeDeleteOperati
362
366
  // 直接do,这个不需要做成可撤销的
363
367
  // @ts-expect-error 这里序列类型确定,为easing,不需要传入谱面
364
368
  new MultiNodeAddOperation(nodeArray, sequence).do();
369
+ // 上面这个操作不能顶替原来的第一个startnode,所以手动来一遍
370
+ const first = sequence.head.next;
371
+ first.evaluator = nodeArray[0].evaluator;
372
+ first.value = nodeArray[0].value;
365
373
 
366
374
  return new EncapsuleOperation(oldArray, easing);
367
375
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kipphi",
3
3
  "description": "Parse your Phigros Chart(.rpe.json or .kpa.json) into an editor-friendly format.",
4
- "version": "2.1.3-beta.2",
4
+ "version": "2.1.3",
5
5
  "author": "Team Zincs (https://github.com/TeamZincs)",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -4,7 +4,7 @@ import { type TimeT, type ChartDataRPE, type MetaData, type JudgeLineDataRPE, ty
4
4
  import { SegmentedEasing, BezierEasing, NormalEasing, fixedEasing, TemplateEasing, Easing } from "./easing";
5
5
  import { err } from "./env";
6
6
  import { EasedEvaluator, Evaluator, ExpressionEvaluator, NumericEasedEvaluator, TextEasedEvaluator, type EasedEvaluatorConstructorOfType, type EasedEvaluatorOfType } from "./evaluator";
7
- import { EventEndNode, EventNode, EventNodeSequence, EventStartNode, type EventNodeLike } from "./event";
7
+ import { EventEndNode, EventNode, EventNodeSequence, EventStartNode, SpeedENS, type EventNodeLike } from "./event";
8
8
  import type { JudgeLine } from "./judgeline";
9
9
  import type { NNList, HNList, NNOrHead } from "./note";
10
10
  import TC from "./time";
@@ -31,11 +31,23 @@ export class RPEChartCompiler {
31
31
  // console.time("compileChart")
32
32
  const chart = this.chart;
33
33
  const judgeLineGroups = chart.judgeLineGroups.map(group => group.name);
34
+ const hasNotes = (nnList: NNList) => {
35
+ let node = nnList.head.next;
36
+ while (true) {
37
+ if (node.type === NodeType.TAIL) {
38
+ return false;
39
+ }
40
+ if (node.notes.length > 0) {
41
+ return true;
42
+ }
43
+ node = node.next;
44
+ }
45
+ }
34
46
  const filter = this.deletesEmptyLines ? (line: JudgeLine) => {
35
- return line.nnLists.size > 0
36
- || line.hnLists.size > 0
47
+ return [...line.nnLists].some(([_, l]) => hasNotes(l))
48
+ || [...line.hnLists].some(([_, l]) => hasNotes(l))
37
49
  || line.eventLayers.length > 0
38
- || (["moveX", "moveY", "rotate", "alpha"] as const).some((evType) => {
50
+ && (["moveX", "moveY", "rotate", "alpha"] as const).some((evType) => {
39
51
  const seq = line.eventLayers[0][evType];
40
52
  let node = seq.head.next;
41
53
  for (let i = 0; i < 2; i++) {
@@ -128,7 +140,7 @@ export class RPEChartCompiler {
128
140
  moveYEvents: layer.moveY ? this.dumpEventNodeSequence(layer.moveY) : undefined,
129
141
  rotateEvents: layer.rotate ? this.dumpEventNodeSequence(layer.rotate) : undefined,
130
142
  alphaEvents: layer.alpha ? this.dumpEventNodeSequence(layer.alpha) : undefined,
131
- speedEvents: index === 0 ? this.dumpEventNodeSequence(judgeLine.speedSequence) : undefined
143
+ speedEvents: index === 0 ? this.dumpSpeedENS(judgeLine.speedSequence) : undefined
132
144
  })),
133
145
  extended: {
134
146
  scaleXEvents: judgeLine.extendedLayer.scaleX ? this.dumpEventNodeSequence(judgeLine.extendedLayer.scaleX) : undefined,
@@ -249,6 +261,32 @@ export class RPEChartCompiler {
249
261
 
250
262
  return nodes
251
263
  }
264
+ dumpSpeedENS(seq: SpeedENS): EventDataRPELike<number>[] {
265
+ const ret: EventDataRPELike<number>[] = [];
266
+ let node = seq.head.next;
267
+ while (true) {
268
+ const end = node.next;
269
+ if (end.type === NodeType.TAIL) {
270
+ break;
271
+ }
272
+ ret.push({
273
+ start: node.value,
274
+ end: end.value,
275
+ startTime: node.time,
276
+ endTime: end.time,
277
+ linkgroup: 0
278
+ } as EventDataRPELike<number>);
279
+ node = end.next;
280
+ }
281
+ ret.push({
282
+ start: node.value,
283
+ end: node.value,
284
+ startTime: node.time,
285
+ endTime: TC.vadd(node.time, [1, 0, 1]),
286
+ linkgroup: 0
287
+ } as EventDataRPELike<number>);
288
+ return ret;
289
+ }
252
290
 
253
291
  compileNNLists(nnLists: NNList[], hnLists: HNList[]): NoteDataRPE[] {
254
292
  const noteLists = nnLists.map(list => this.nnListToArray(list));
@@ -367,8 +405,8 @@ export class RPEChartCompiler {
367
405
  srcStart = srcSeq.head.next!.value;
368
406
  srcEnd = srcSeq.tail.previous!.value;
369
407
  leftDividedNodeSrc = srcSeq.head.next!;
370
- rightDividedNodeSrc = srcSeq.tail.previous!;
371
- toStopAt = rightDividedNodeSrc;
408
+ rightDividedNodeSrc = srcSeq.tail.previous!.previous.previous!;
409
+ toStopAt = srcSeq.tail.previous!;
372
410
  srcStartTime = srcSeq.head.next!.time;
373
411
  srcTimeDelta = TC.sub(srcSeq.tail.previous!.time, srcStartTime);
374
412
  }
@@ -450,7 +488,7 @@ export class RPEChartCompiler {
450
488
  throw err.CANNOT_DIVIDE_EXPRESSION_EVALUATOR(seq.id);
451
489
  } else {
452
490
  // 否则就是带缓动求值器
453
- first.evaluator = evaluator.deriveWithEasing(
491
+ prev.evaluator = evaluator.deriveWithEasing(
454
492
  new SegmentedEasing((rightDividedNodeSrc.evaluator as NumericEasedEvaluator).easing, 0.0, newRight)
455
493
  ) as unknown as Evaluator<VT>;
456
494
  // TypeScript Compiler我*你娘啊
@@ -460,7 +498,7 @@ export class RPEChartCompiler {
460
498
  if (rightDividedNodeSrc.evaluator instanceof ExpressionEvaluator) {
461
499
  throw err.CANNOT_DIVIDE_EXPRESSION_EVALUATOR(seq.id);
462
500
  } else {
463
- first.evaluator = evaluator.deriveWithEasing(
501
+ prev.evaluator = evaluator.deriveWithEasing(
464
502
  (rightDividedNodeSrc.evaluator as NumericEasedEvaluator).easing
465
503
  ) as unknown as Evaluator<VT>;
466
504
  }
package/util.ts CHANGED
@@ -20,6 +20,56 @@ export const checkType = (value: unknown, type: string | (string | typeof Functi
20
20
  && value.length === type.length
21
21
  && type.every((t, i) => checkType(value[i], t))
22
22
  } else if (typeof type === "string") {
23
+ if (type.startsWith("int")) {
24
+ if (typeof value !== "number" || !Number.isInteger(value)) {
25
+ return false;
26
+ }
27
+ const match = type.match(/^int(\(|\[)(\-?\d+),(\-?\d+|\+)(\)|\])$/);
28
+ if (!match) { return true; }
29
+ const [,leftBrac, left, right, rightBrac] = match
30
+ if (!leftBrac) { return true; }
31
+ const leftN = left === "-" ? -Infinity : Number(left);
32
+ const rightN = right === "+" ? +Infinity : Number(right);
33
+ if (value < leftN) {
34
+ return false;
35
+ }
36
+ if (leftBrac === "(" && value === leftN) {
37
+ return false;
38
+ }
39
+
40
+ if (value > rightN) {
41
+ return false;
42
+ }
43
+ if (rightBrac === ")" && value === rightN) {
44
+ return false;
45
+ }
46
+ return true;
47
+ } else if (type.startsWith("number")) {
48
+
49
+ if (typeof value !== "number") {
50
+ return false;
51
+ }
52
+ const match = type.match(/^number(\(|\[)(\-?\d+),(\-?\d+|\+)(\)|\])$/)
53
+ if (!match) { return true; }
54
+ const [, leftBrac, left, right, rightBrac] = match
55
+ if (!leftBrac) { return true; }
56
+ const leftN = left === "-" ? -Infinity : Number(left);
57
+ const rightN = right === "+" ? +Infinity : Number(right);
58
+ if (value < leftN) {
59
+ return false;
60
+ }
61
+ if (leftBrac === "(" && value === leftN) {
62
+ return false;
63
+ }
64
+
65
+ if (value > rightN) {
66
+ return false;
67
+ }
68
+ if (rightBrac === ")" && value === rightN) {
69
+ return false;
70
+ }
71
+ return true;
72
+ }
23
73
  return typeof value === type
24
74
  } else {
25
75
  return value instanceof type