sonolus-next-rush-engine 0.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.
@@ -0,0 +1,681 @@
1
+ import { USCColor, } from '../usc/index.js';
2
+ import { analyze } from './analyze.js';
3
+ const SONOLUS_CONNECTOR_EASES = {
4
+ outin: 5,
5
+ out: 3,
6
+ linear: 1,
7
+ in: 2,
8
+ inout: 4,
9
+ };
10
+ const mmwEaseToSonolusEase = (ease) => {
11
+ switch (ease) {
12
+ case 'linear':
13
+ return SONOLUS_CONNECTOR_EASES.linear;
14
+ case 'easeIn':
15
+ return SONOLUS_CONNECTOR_EASES.in;
16
+ case 'easeOut':
17
+ return SONOLUS_CONNECTOR_EASES.out;
18
+ case 'easeInOut':
19
+ return SONOLUS_CONNECTOR_EASES.inout;
20
+ case 'easeOutIn':
21
+ return SONOLUS_CONNECTOR_EASES.outin;
22
+ default:
23
+ return SONOLUS_CONNECTOR_EASES.linear;
24
+ }
25
+ };
26
+ const EPSILON = 1e-6;
27
+ const TICKS_PER_BEAT = 480;
28
+ const laneToSonolusLane = (lane, width) => {
29
+ return lane - 6 + width / 2;
30
+ };
31
+ const flickTypeToDirection = (type) => {
32
+ switch (type) {
33
+ case 'up':
34
+ return 0;
35
+ case 'left':
36
+ return 1;
37
+ case 'right':
38
+ return 2;
39
+ case 'down':
40
+ return 3;
41
+ case 'down_left':
42
+ return 4;
43
+ case 'down_right':
44
+ return 5;
45
+ default:
46
+ return 0;
47
+ }
48
+ };
49
+ const resolveConnectorKind = (isCritical, isDummy) => {
50
+ if (isDummy) {
51
+ return isCritical ? 52 : 51;
52
+ }
53
+ return isCritical ? 1 : 0;
54
+ };
55
+ const mmwsEaseToUSCEase = {
56
+ linear: 'linear',
57
+ easeOut: 'out',
58
+ easeIn: 'in',
59
+ easeInOut: 'inout',
60
+ easeOutIn: 'outin',
61
+ };
62
+ const laneToUSCLane = ({ lane, width }) => {
63
+ return lane - 6 + width / 2;
64
+ };
65
+ /**
66
+ * Convert MMWS or CCMMWS to a USC
67
+ */
68
+ export const mmwsToUSC = (mmws) => {
69
+ const score = analyze(mmws);
70
+ const usc = {
71
+ objects: [],
72
+ offset: score.metadata.musicOffset / -1000,
73
+ };
74
+ for (const bpmChange of score.events.bpmChanges) {
75
+ usc.objects.push({
76
+ type: 'bpm',
77
+ beat: bpmChange.tick / TICKS_PER_BEAT,
78
+ bpm: bpmChange.bpm,
79
+ });
80
+ }
81
+ const tsGroups = new Map();
82
+ for (let i = 0; i < score.numLayers; i++) {
83
+ tsGroups.set(i, []);
84
+ }
85
+ for (const hispeedChange of score.events.hispeedChanges) {
86
+ const key = hispeedChange.layer;
87
+ if (!tsGroups.has(key)) {
88
+ if (!tsGroups.has(0))
89
+ tsGroups.set(0, []);
90
+ tsGroups.get(0)?.push({
91
+ beat: hispeedChange.tick / TICKS_PER_BEAT,
92
+ timeScale: hispeedChange.speed,
93
+ });
94
+ continue;
95
+ }
96
+ tsGroups.get(key)?.push({
97
+ beat: hispeedChange.tick / TICKS_PER_BEAT,
98
+ timeScale: hispeedChange.speed,
99
+ });
100
+ }
101
+ for (const changes of tsGroups.values()) {
102
+ usc.objects.push({
103
+ type: 'timeScaleGroup',
104
+ changes,
105
+ });
106
+ }
107
+ for (const tap of score.taps) {
108
+ const uscTap = {
109
+ type: 'single',
110
+ beat: tap.tick / TICKS_PER_BEAT,
111
+ timeScaleGroup: tap.layer,
112
+ critical: tap.flags.critical,
113
+ lane: laneToUSCLane(tap),
114
+ size: tap.width / 2,
115
+ trace: tap.flags.friction,
116
+ };
117
+ if (tap.flickType === 'up' || tap.flickType === 'left' || tap.flickType === 'right') {
118
+ uscTap.direction = tap.flickType;
119
+ }
120
+ usc.objects.push(uscTap);
121
+ }
122
+ for (const hold of score.holds) {
123
+ const uscStartNote = {
124
+ type: 'start',
125
+ beat: hold.start.tick / TICKS_PER_BEAT,
126
+ timeScaleGroup: hold.start.layer,
127
+ critical: hold.start.flags.critical,
128
+ ease: mmwsEaseToUSCEase[hold.start.ease],
129
+ lane: laneToUSCLane(hold.start),
130
+ size: hold.start.width / 2,
131
+ judgeType: hold.flags.startHidden
132
+ ? 'none'
133
+ : hold.start.flags.friction
134
+ ? 'trace'
135
+ : 'normal',
136
+ };
137
+ const uscEndNote = {
138
+ type: 'end',
139
+ beat: hold.end.tick / TICKS_PER_BEAT,
140
+ timeScaleGroup: hold.end.layer,
141
+ critical: hold.end.flags.critical,
142
+ lane: laneToUSCLane(hold.end),
143
+ size: hold.end.width / 2,
144
+ judgeType: hold.flags.endHidden ? 'none' : hold.end.flags.friction ? 'trace' : 'normal',
145
+ };
146
+ if (hold.end.flickType === 'up' ||
147
+ hold.end.flickType === 'left' ||
148
+ hold.end.flickType === 'right') {
149
+ uscEndNote.direction = hold.end.flickType;
150
+ }
151
+ if (hold.flags.guide) {
152
+ const uscGuide = {
153
+ type: 'guide',
154
+ fade: hold.fadeType === 0 ? 'out' : hold.fadeType === 1 ? 'none' : 'in',
155
+ color: Object.entries(USCColor).find(([, i]) => i === hold.guideColor)?.[0],
156
+ midpoints: [hold.start, ...hold.steps, hold.end].map((step) => ({
157
+ beat: step.tick / TICKS_PER_BEAT,
158
+ lane: laneToUSCLane(step),
159
+ size: step.width / 2,
160
+ timeScaleGroup: step.layer,
161
+ ease: 'ease' in step ? mmwsEaseToUSCEase[step.ease] : 'linear',
162
+ })),
163
+ };
164
+ usc.objects.push(uscGuide);
165
+ }
166
+ else {
167
+ const uscSlide = {
168
+ type: 'slide',
169
+ critical: hold.start.flags.critical,
170
+ connections: [
171
+ uscStartNote,
172
+ ...hold.steps.map((step) => {
173
+ const beat = step.tick / TICKS_PER_BEAT;
174
+ const lane = laneToUSCLane(step);
175
+ const size = step.width / 2;
176
+ if (step.type === 'ignored') {
177
+ return {
178
+ type: 'attach',
179
+ beat,
180
+ critical: hold.start.flags.critical,
181
+ timeScaleGroup: step.layer,
182
+ };
183
+ }
184
+ else {
185
+ const uscStep = {
186
+ type: 'tick',
187
+ beat,
188
+ timeScaleGroup: step.layer,
189
+ lane,
190
+ size,
191
+ ease: mmwsEaseToUSCEase[step.ease],
192
+ };
193
+ if (step.type === 'visible') {
194
+ uscStep.critical = hold.start.flags.critical;
195
+ }
196
+ return uscStep;
197
+ }
198
+ }),
199
+ uscEndNote,
200
+ ],
201
+ };
202
+ usc.objects.push(uscSlide);
203
+ }
204
+ }
205
+ for (const damage of score.damages) {
206
+ const uscDamage = {
207
+ type: 'damage',
208
+ beat: damage.tick / TICKS_PER_BEAT,
209
+ timeScaleGroup: damage.layer,
210
+ lane: laneToUSCLane(damage),
211
+ size: damage.width / 2,
212
+ };
213
+ usc.objects.push(uscDamage);
214
+ }
215
+ return usc;
216
+ };
217
+ /**
218
+ * Convert CCMMWS to a USC
219
+ */
220
+ export const ccmmwsToUSC = mmwsToUSC;
221
+ /**
222
+ * Convert UCMMWS to a LevelData
223
+ */
224
+ export const ucmmwsToLevelData = (mmws) => {
225
+ const score = analyze(mmws);
226
+ const entities = [];
227
+ const allIntermediateEntities = [];
228
+ const simLineEligibleNotes = [];
229
+ const timeScaleGroupIntermediates = [];
230
+ const createIntermediate = (archetype, data, isSimEligible = false) => {
231
+ const intermediateEntity = { archetype, data };
232
+ allIntermediateEntities.push(intermediateEntity);
233
+ if (isSimEligible) {
234
+ simLineEligibleNotes.push(intermediateEntity);
235
+ }
236
+ return intermediateEntity;
237
+ };
238
+ createIntermediate('Initialization', {});
239
+ createIntermediate('Stage', {});
240
+ if (score.events.bpmChanges.length === 0) {
241
+ createIntermediate('#BPM_CHANGE', { '#BEAT': 0, '#BPM': 120 });
242
+ }
243
+ for (const bpm of score.events.bpmChanges) {
244
+ createIntermediate('#BPM_CHANGE', {
245
+ '#BEAT': bpm.tick / TICKS_PER_BEAT,
246
+ '#BPM': bpm.bpm,
247
+ });
248
+ }
249
+ for (const skillTick of score.events.skills) {
250
+ createIntermediate('Skill', { '#BEAT': skillTick / TICKS_PER_BEAT });
251
+ }
252
+ if (score.events.fever.start > 0) {
253
+ createIntermediate('FeverChance', { '#BEAT': score.events.fever.start / TICKS_PER_BEAT });
254
+ }
255
+ if (score.events.fever.end > 0) {
256
+ createIntermediate('FeverStart', { '#BEAT': score.events.fever.end / TICKS_PER_BEAT });
257
+ }
258
+ const layerChanges = new Map();
259
+ for (let i = 0; i < Math.max(1, score.numLayers); i++) {
260
+ layerChanges.set(i, []);
261
+ }
262
+ for (const hs of score.events.hispeedChanges) {
263
+ if (!layerChanges.has(hs.layer)) {
264
+ if (!layerChanges.has(0))
265
+ layerChanges.set(0, []);
266
+ layerChanges.get(0)?.push(hs);
267
+ continue;
268
+ }
269
+ layerChanges.get(hs.layer)?.push(hs);
270
+ }
271
+ for (let i = 0; i < Math.max(1, score.numLayers); i++) {
272
+ const changes = layerChanges.get(i) || [];
273
+ if (changes.length === 0 || changes[0].tick !== 0) {
274
+ changes.unshift({
275
+ tick: 0,
276
+ speed: 1,
277
+ layer: i,
278
+ skip: 0,
279
+ ease: 0,
280
+ hideNotes: false,
281
+ });
282
+ }
283
+ changes.sort((a, b) => a.tick - b.tick);
284
+ const groupIntermediate = createIntermediate('#TIMESCALE_GROUP', {});
285
+ timeScaleGroupIntermediates[i] = groupIntermediate;
286
+ let lastChangeIntermediate = null;
287
+ for (const hs of changes) {
288
+ const data = {
289
+ '#BEAT': hs.tick / TICKS_PER_BEAT,
290
+ '#TIMESCALE': hs.speed === 0 ? 0.000001 : hs.speed,
291
+ '#TIMESCALE_SKIP': hs.skip || 0,
292
+ '#TIMESCALE_EASE': hs.ease || 0,
293
+ '#TIMESCALE_GROUP': groupIntermediate,
294
+ };
295
+ if (hs.hideNotes) {
296
+ data.hideNotes = 1;
297
+ }
298
+ const newChangeIntermediate = createIntermediate('#TIMESCALE_CHANGE', data);
299
+ if (lastChangeIntermediate === null) {
300
+ groupIntermediate.data.first = newChangeIntermediate;
301
+ }
302
+ else {
303
+ lastChangeIntermediate.data.next = newChangeIntermediate;
304
+ }
305
+ lastChangeIntermediate = newChangeIntermediate;
306
+ }
307
+ }
308
+ for (const tap of score.taps) {
309
+ const name_parts = [];
310
+ name_parts.push(tap.flags.critical ? 'Critical' : 'Normal');
311
+ if (tap.flickType !== 'none') {
312
+ name_parts.push(tap.flags.friction ? 'TraceFlick' : 'Flick');
313
+ }
314
+ else {
315
+ name_parts.push(tap.flags.friction ? 'Trace' : 'Tap');
316
+ }
317
+ name_parts.push('Note');
318
+ const archetype = tap.flags.dummy ? `Fake${name_parts.join('')}` : name_parts.join('');
319
+ const timeScaleGroupRef = timeScaleGroupIntermediates[tap.layer] || timeScaleGroupIntermediates[0];
320
+ const data = {
321
+ '#BEAT': tap.tick / TICKS_PER_BEAT,
322
+ lane: laneToSonolusLane(tap.lane, tap.width),
323
+ size: tap.width,
324
+ isAttached: 0,
325
+ connectorEase: SONOLUS_CONNECTOR_EASES.linear,
326
+ isSeparator: 0,
327
+ segmentKind: tap.flags.critical ? 2 : 1,
328
+ segmentAlpha: 1,
329
+ '#TIMESCALE_GROUP': timeScaleGroupRef,
330
+ };
331
+ if (tap.flickType !== 'none') {
332
+ data.direction = flickTypeToDirection(tap.flickType);
333
+ }
334
+ createIntermediate(archetype, data, true);
335
+ }
336
+ for (const damage of score.damages) {
337
+ const archetype = damage.flags.dummy ? 'FakeDamageNote' : 'DamageNote';
338
+ const timeScaleGroupRef = timeScaleGroupIntermediates[damage.layer] || timeScaleGroupIntermediates[0];
339
+ createIntermediate(archetype, {
340
+ '#BEAT': damage.tick / TICKS_PER_BEAT,
341
+ lane: laneToSonolusLane(damage.lane, damage.width),
342
+ size: damage.width,
343
+ direction: 0,
344
+ isAttached: 0,
345
+ connectorEase: SONOLUS_CONNECTOR_EASES.linear,
346
+ isSeparator: 0,
347
+ segmentKind: 1,
348
+ segmentAlpha: 1,
349
+ '#TIMESCALE_GROUP': timeScaleGroupRef,
350
+ });
351
+ }
352
+ for (const hold of score.holds) {
353
+ const timeScaleGroupRef = timeScaleGroupIntermediates[hold.start.layer] || timeScaleGroupIntermediates[0];
354
+ if (hold.flags.guide) {
355
+ const points = [hold.start, ...hold.steps, hold.end];
356
+ points.sort((a, b) => a.tick - b.tick);
357
+ let prevMidpointIntermediate = null;
358
+ let headMidpointIntermediate = null;
359
+ const guideConnectors = [];
360
+ const stepSize = Math.max(1, points.length - 1);
361
+ let stepIdx = 0;
362
+ for (const point of points) {
363
+ let segmentAlpha = 1;
364
+ if (hold.fadeType === 0) {
365
+ segmentAlpha = 1 - 0.8 * (stepIdx / stepSize);
366
+ }
367
+ else if (hold.fadeType === 2) {
368
+ segmentAlpha = 1 - 0.8 * ((stepSize - stepIdx) / stepSize);
369
+ }
370
+ const sonolusColorId = 101 + hold.guideColor;
371
+ const midpointIntermediate = createIntermediate('AnchorNote', {
372
+ '#BEAT': point.tick / TICKS_PER_BEAT,
373
+ lane: laneToSonolusLane(point.lane, point.width),
374
+ size: point.width,
375
+ direction: 0,
376
+ isAttached: 0,
377
+ connectorEase: 'ease' in point
378
+ ? mmwEaseToSonolusEase(point.ease)
379
+ : SONOLUS_CONNECTOR_EASES.linear,
380
+ isSeparator: 0,
381
+ segmentKind: sonolusColorId,
382
+ segmentAlpha: segmentAlpha,
383
+ segmentLayer: 0,
384
+ '#TIMESCALE_GROUP': timeScaleGroupRef,
385
+ });
386
+ if (headMidpointIntermediate === null) {
387
+ headMidpointIntermediate = midpointIntermediate;
388
+ }
389
+ if (prevMidpointIntermediate !== null) {
390
+ const connectorIntermediate = createIntermediate('Connector', {
391
+ head: prevMidpointIntermediate,
392
+ tail: midpointIntermediate,
393
+ });
394
+ guideConnectors.push(connectorIntermediate);
395
+ prevMidpointIntermediate.data.next = midpointIntermediate;
396
+ }
397
+ prevMidpointIntermediate = midpointIntermediate;
398
+ stepIdx++;
399
+ }
400
+ if (headMidpointIntermediate && prevMidpointIntermediate) {
401
+ for (const conn of guideConnectors) {
402
+ conn.data.segmentHead = headMidpointIntermediate;
403
+ conn.data.segmentTail = prevMidpointIntermediate;
404
+ conn.data.activeHead = headMidpointIntermediate;
405
+ conn.data.activeTail = prevMidpointIntermediate;
406
+ }
407
+ if (hold.fadeType === 2) {
408
+ headMidpointIntermediate.data.segmentAlpha = 0;
409
+ }
410
+ else if (hold.fadeType === 0) {
411
+ prevMidpointIntermediate.data.segmentAlpha = 0;
412
+ }
413
+ }
414
+ }
415
+ else {
416
+ let prevJointIntermediate = null;
417
+ let prevNoteIntermediate = null;
418
+ let headNoteIntermediate = null;
419
+ let currentSegmentHead = null;
420
+ const queuedAttachIntermediates = [];
421
+ const connectors = [];
422
+ const pendingSegmentConnectors = [];
423
+ const events = [];
424
+ events.push({
425
+ tick: hold.start.tick,
426
+ lane: hold.start.lane,
427
+ width: hold.start.width,
428
+ type: 'start',
429
+ critical: hold.start.flags.critical,
430
+ friction: hold.start.flags.friction,
431
+ flick: 'none',
432
+ ease: hold.start.ease,
433
+ });
434
+ for (const step of hold.steps) {
435
+ if (step.type === 'ignored') {
436
+ events.push({
437
+ tick: step.tick,
438
+ lane: step.lane,
439
+ width: step.width,
440
+ type: 'attach',
441
+ critical: hold.start.flags.critical,
442
+ friction: false,
443
+ flick: 'none',
444
+ ease: step.ease,
445
+ });
446
+ }
447
+ else {
448
+ if (step.type === 'visible') {
449
+ events.push({
450
+ tick: step.tick,
451
+ lane: step.lane,
452
+ width: step.width,
453
+ type: 'tick',
454
+ critical: hold.start.flags.critical,
455
+ friction: false,
456
+ flick: 'none',
457
+ ease: step.ease,
458
+ });
459
+ }
460
+ else {
461
+ events.push({
462
+ tick: step.tick,
463
+ lane: step.lane,
464
+ width: step.width,
465
+ type: 'attach',
466
+ critical: hold.start.flags.critical,
467
+ friction: false,
468
+ flick: 'none',
469
+ ease: step.ease,
470
+ });
471
+ }
472
+ }
473
+ }
474
+ events.push({
475
+ tick: hold.end.tick,
476
+ lane: hold.end.lane,
477
+ width: hold.end.width,
478
+ type: 'end',
479
+ critical: hold.end.flags.critical,
480
+ friction: hold.end.flags.friction,
481
+ flick: hold.end.flickType,
482
+ });
483
+ events.sort((a, b) => a.tick - b.tick);
484
+ let nextHiddenTickBeat = Math.floor((hold.start.tick / TICKS_PER_BEAT) * 2 + 1) / 2;
485
+ for (const event of events) {
486
+ let isSimLineEligible = false;
487
+ let isAttached = false;
488
+ const name_parts = [];
489
+ if (event.type === 'start') {
490
+ if (hold.flags.startHidden) {
491
+ name_parts.push('Anchor');
492
+ }
493
+ else {
494
+ name_parts.push(event.critical ? 'Critical' : 'Normal');
495
+ name_parts.push('Head');
496
+ name_parts.push(event.friction ? 'Trace' : 'Tap');
497
+ isSimLineEligible = true;
498
+ }
499
+ }
500
+ else if (event.type === 'end') {
501
+ if (hold.flags.endHidden) {
502
+ name_parts.push('Anchor');
503
+ }
504
+ else {
505
+ name_parts.push(event.critical ? 'Critical' : 'Normal');
506
+ name_parts.push('Tail');
507
+ if (event.flick !== 'none') {
508
+ name_parts.push(event.friction ? 'TraceFlick' : 'Flick');
509
+ }
510
+ else {
511
+ name_parts.push(event.friction ? 'Trace' : 'Release');
512
+ }
513
+ isSimLineEligible = true;
514
+ }
515
+ }
516
+ else if (event.type === 'tick') {
517
+ name_parts.push(event.critical ? 'Critical' : 'Normal');
518
+ name_parts.push('Tick');
519
+ }
520
+ else {
521
+ isAttached = true;
522
+ if (event.critical) {
523
+ name_parts.push('Critical');
524
+ name_parts.push('Tick');
525
+ }
526
+ else {
527
+ name_parts.push('TransientHiddenTick');
528
+ }
529
+ }
530
+ name_parts.push('Note');
531
+ let archetype = name_parts.join('');
532
+ if (hold.flags.dummy)
533
+ archetype = `Fake${archetype}`;
534
+ const data = {
535
+ '#BEAT': event.tick / TICKS_PER_BEAT,
536
+ lane: laneToSonolusLane(event.lane, event.width),
537
+ size: event.width,
538
+ isAttached: isAttached ? 1 : 0,
539
+ connectorEase: event.ease
540
+ ? mmwEaseToSonolusEase(event.ease)
541
+ : SONOLUS_CONNECTOR_EASES.linear,
542
+ isSeparator: 0,
543
+ segmentKind: event.critical ? 2 : 1,
544
+ segmentAlpha: 1,
545
+ segmentLayer: 0,
546
+ '#TIMESCALE_GROUP': timeScaleGroupRef,
547
+ };
548
+ if (event.flick && event.flick !== 'none') {
549
+ data.direction = flickTypeToDirection(event.flick);
550
+ }
551
+ else {
552
+ data.direction = 0;
553
+ }
554
+ const connectionIntermediate = createIntermediate(archetype, data, isSimLineEligible);
555
+ if (headNoteIntermediate === null) {
556
+ headNoteIntermediate = connectionIntermediate;
557
+ }
558
+ if (currentSegmentHead === null) {
559
+ currentSegmentHead = connectionIntermediate;
560
+ }
561
+ connectionIntermediate.data.activeHead = headNoteIntermediate;
562
+ if (isAttached) {
563
+ queuedAttachIntermediates.push(connectionIntermediate);
564
+ }
565
+ else {
566
+ if (prevJointIntermediate !== null) {
567
+ for (const attachIntermediate of queuedAttachIntermediates) {
568
+ attachIntermediate.data.attachHead = prevJointIntermediate;
569
+ attachIntermediate.data.attachTail = connectionIntermediate;
570
+ }
571
+ queuedAttachIntermediates.length = 0;
572
+ while (nextHiddenTickBeat + EPSILON <
573
+ connectionIntermediate.data['#BEAT']) {
574
+ createIntermediate('TransientHiddenTickNote', {
575
+ '#BEAT': nextHiddenTickBeat,
576
+ '#TIMESCALE_GROUP': timeScaleGroupRef,
577
+ lane: connectionIntermediate.data.lane,
578
+ size: connectionIntermediate.data.size,
579
+ direction: 0,
580
+ isAttached: 1,
581
+ connectorEase: SONOLUS_CONNECTOR_EASES.linear,
582
+ isSeparator: 0,
583
+ segmentKind: 1,
584
+ segmentAlpha: 0,
585
+ activeHead: headNoteIntermediate,
586
+ attachHead: prevJointIntermediate,
587
+ attachTail: connectionIntermediate,
588
+ });
589
+ nextHiddenTickBeat += 0.5;
590
+ }
591
+ const connectorIntermediate = createIntermediate('Connector', {
592
+ head: prevJointIntermediate,
593
+ tail: connectionIntermediate,
594
+ kind: resolveConnectorKind(event.critical, !!hold.flags.dummy),
595
+ });
596
+ connectors.push(connectorIntermediate);
597
+ pendingSegmentConnectors.push(connectorIntermediate);
598
+ }
599
+ prevJointIntermediate = connectionIntermediate;
600
+ }
601
+ if (prevNoteIntermediate !== null) {
602
+ prevNoteIntermediate.data.next = connectionIntermediate;
603
+ }
604
+ prevNoteIntermediate = connectionIntermediate;
605
+ }
606
+ if (headNoteIntermediate && prevJointIntermediate) {
607
+ if (currentSegmentHead) {
608
+ for (const conn of pendingSegmentConnectors) {
609
+ conn.data.segmentHead = currentSegmentHead;
610
+ conn.data.segmentTail = prevJointIntermediate;
611
+ }
612
+ }
613
+ for (const conn of connectors) {
614
+ conn.data.activeHead = headNoteIntermediate;
615
+ conn.data.activeTail = prevJointIntermediate;
616
+ }
617
+ }
618
+ }
619
+ }
620
+ simLineEligibleNotes.sort((a, b) => {
621
+ const beatA = a.data['#BEAT'];
622
+ const beatB = b.data['#BEAT'];
623
+ if (Math.abs(beatA - beatB) > EPSILON)
624
+ return beatA - beatB;
625
+ const laneA = a.data.lane;
626
+ const laneB = b.data.lane;
627
+ return laneA - laneB;
628
+ });
629
+ const simGroups = [];
630
+ let currentSimGroup = [];
631
+ for (const simNote of simLineEligibleNotes) {
632
+ if (currentSimGroup.length === 0 ||
633
+ Math.abs(simNote.data['#BEAT'] - currentSimGroup[0].data['#BEAT']) < 1e-2) {
634
+ currentSimGroup.push(simNote);
635
+ }
636
+ else {
637
+ simGroups.push(currentSimGroup);
638
+ currentSimGroup = [simNote];
639
+ }
640
+ }
641
+ if (currentSimGroup.length > 0)
642
+ simGroups.push(currentSimGroup);
643
+ for (const simGroup of simGroups) {
644
+ for (let i = 0; i < simGroup.length - 1; i++) {
645
+ createIntermediate('SimLine', {
646
+ left: simGroup[i],
647
+ right: simGroup[i + 1],
648
+ });
649
+ }
650
+ }
651
+ const intermediateToRef = new Map();
652
+ let entityRefCounter = 0;
653
+ const getRef = (intermediateEntity) => {
654
+ let ref = intermediateToRef.get(intermediateEntity);
655
+ if (ref)
656
+ return ref;
657
+ ref = (entityRefCounter++).toString(16);
658
+ intermediateToRef.set(intermediateEntity, ref);
659
+ return ref;
660
+ };
661
+ for (const intermediateEntity of allIntermediateEntities) {
662
+ const entity = {
663
+ archetype: intermediateEntity.archetype,
664
+ name: getRef(intermediateEntity),
665
+ data: [],
666
+ };
667
+ for (const [dataName, dataValue] of Object.entries(intermediateEntity.data)) {
668
+ if (typeof dataValue === 'number') {
669
+ entity.data.push({ name: dataName, value: dataValue });
670
+ }
671
+ else if (dataValue !== undefined) {
672
+ entity.data.push({ name: dataName, ref: getRef(dataValue) });
673
+ }
674
+ }
675
+ entities.push(entity);
676
+ }
677
+ return {
678
+ bgmOffset: score.metadata.musicOffset / 1000,
679
+ entities,
680
+ };
681
+ };