tja-parser 0.2.2 → 0.2.4
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/dist/class/Bar.d.ts +1 -0
- package/dist/class/Bar.d.ts.map +1 -1
- package/dist/class/Bar.js +5 -0
- package/dist/class/Bar.js.map +1 -1
- package/dist/class/Course.d.ts +1 -0
- package/dist/class/Course.d.ts.map +1 -1
- package/dist/class/Course.js +58 -6
- package/dist/class/Course.js.map +1 -1
- package/format.mediawiki +714 -714
- package/package.json +1 -1
- package/src/class/Bar.ts +72 -67
- package/src/class/Branch.ts +33 -33
- package/src/class/Command.ts +138 -138
- package/src/class/Course.ts +496 -437
- package/src/class/Item.ts +26 -26
- package/src/class/Note.ts +171 -171
- package/src/class/NoteGroup.ts +23 -23
- package/src/class/Song.ts +87 -87
- package/src/exception/ParseException.ts +12 -12
- package/src/index.ts +12 -12
- package/tsconfig.json +22 -22
- package/dist/class/BarLine.d.ts +0 -14
- package/dist/class/BarLine.d.ts.map +0 -1
- package/dist/class/BarLine.js +0 -26
- package/dist/class/BarLine.js.map +0 -1
package/src/class/Course.ts
CHANGED
|
@@ -1,438 +1,497 @@
|
|
|
1
|
-
import { UnknownCourseDifficultyException } from "../exception/ParseException.js";
|
|
2
|
-
import type { Difficulty } from "../types.js";
|
|
3
|
-
import { Bar } from "./Bar.js";
|
|
4
|
-
import { Branch } from "./Branch.js";
|
|
5
|
-
import { BarlineCommand, BPMChangeCommand, Command, MeasureCommand, ScrollCommand } from "./Command.js";
|
|
6
|
-
import type { Item } from "./Item.js";
|
|
7
|
-
import { BalloonNote, EmptyNote, HitNote, Note, RollEndNote, RollNote } from "./Note.js";
|
|
8
|
-
import { NoteGroup } from "./NoteGroup.js";
|
|
9
|
-
import { Song } from "./Song.js";
|
|
10
|
-
import * as math from 'mathjs';
|
|
11
|
-
|
|
12
|
-
export class Course {
|
|
13
|
-
/**
|
|
14
|
-
* @throws {MetadataParseException}
|
|
15
|
-
* @throws {CourseParseException}
|
|
16
|
-
* @todo command와 note 파싱할 것
|
|
17
|
-
*/
|
|
18
|
-
static parse(courseTja: string | string[], song?: Song): Course {
|
|
19
|
-
if (typeof (courseTja) === "string") {
|
|
20
|
-
courseTja = courseTja.split('\n');
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const metadata = new Course.Metadata();
|
|
24
|
-
let difficulty: Difficulty | undefined;
|
|
25
|
-
|
|
26
|
-
let i = 0;
|
|
27
|
-
for (; i < courseTja.length; i++) {
|
|
28
|
-
if (courseTja[i].startsWith('#START')) {
|
|
29
|
-
break;
|
|
30
|
-
}
|
|
31
|
-
const parsedMetadata = Song.parseMetadata(courseTja[i]);
|
|
32
|
-
if (parsedMetadata.key === "course") {
|
|
33
|
-
if (parsedMetadata.value === "0" || parsedMetadata.value === "Easy") {
|
|
34
|
-
difficulty = 'easy';
|
|
35
|
-
}
|
|
36
|
-
else if (parsedMetadata.value === "1" || parsedMetadata.value === "Normal") {
|
|
37
|
-
difficulty = 'normal';
|
|
38
|
-
}
|
|
39
|
-
else if (parsedMetadata.value === "2" || parsedMetadata.value === "Hard") {
|
|
40
|
-
difficulty = 'hard';
|
|
41
|
-
}
|
|
42
|
-
else if (parsedMetadata.value === "3" || parsedMetadata.value === "Oni") {
|
|
43
|
-
difficulty = 'oni';
|
|
44
|
-
}
|
|
45
|
-
else if (parsedMetadata.value === "4" || parsedMetadata.value === "Edit") {
|
|
46
|
-
difficulty = 'edit';
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
metadata[parsedMetadata.key] = parsedMetadata.value;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (!difficulty) {
|
|
55
|
-
throw new UnknownCourseDifficultyException(courseTja[0]);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const course = new Course(difficulty, metadata, song);
|
|
59
|
-
course.pushNoteGroups(...this.parseBar(courseTja.slice(i), course.getBalloonIterator(), course.getBPM()));
|
|
60
|
-
|
|
61
|
-
return course;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
private static parseBar(lines: string[], getNextBalloon: () => number, bpmInit: number): NoteGroup[] {
|
|
65
|
-
const lineGroups = this.groupLines(lines);
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
currentBar
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
items.forEach((item) => {
|
|
233
|
-
item.setTiming(currentTiming);
|
|
234
|
-
if (item instanceof
|
|
235
|
-
if (
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
item
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
let
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
1
|
+
import { UnknownCourseDifficultyException } from "../exception/ParseException.js";
|
|
2
|
+
import type { Difficulty } from "../types.js";
|
|
3
|
+
import { Bar } from "./Bar.js";
|
|
4
|
+
import { Branch } from "./Branch.js";
|
|
5
|
+
import { BarlineCommand, BPMChangeCommand, Command, MeasureCommand, ScrollCommand } from "./Command.js";
|
|
6
|
+
import type { Item } from "./Item.js";
|
|
7
|
+
import { BalloonNote, EmptyNote, HitNote, Note, RollEndNote, RollNote } from "./Note.js";
|
|
8
|
+
import { NoteGroup } from "./NoteGroup.js";
|
|
9
|
+
import { Song } from "./Song.js";
|
|
10
|
+
import * as math from 'mathjs';
|
|
11
|
+
|
|
12
|
+
export class Course {
|
|
13
|
+
/**
|
|
14
|
+
* @throws {MetadataParseException}
|
|
15
|
+
* @throws {CourseParseException}
|
|
16
|
+
* @todo command와 note 파싱할 것
|
|
17
|
+
*/
|
|
18
|
+
static parse(courseTja: string | string[], song?: Song): Course {
|
|
19
|
+
if (typeof (courseTja) === "string") {
|
|
20
|
+
courseTja = courseTja.split('\n');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const metadata = new Course.Metadata();
|
|
24
|
+
let difficulty: Difficulty | undefined;
|
|
25
|
+
|
|
26
|
+
let i = 0;
|
|
27
|
+
for (; i < courseTja.length; i++) {
|
|
28
|
+
if (courseTja[i].startsWith('#START')) {
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
const parsedMetadata = Song.parseMetadata(courseTja[i]);
|
|
32
|
+
if (parsedMetadata.key === "course") {
|
|
33
|
+
if (parsedMetadata.value === "0" || parsedMetadata.value === "Easy") {
|
|
34
|
+
difficulty = 'easy';
|
|
35
|
+
}
|
|
36
|
+
else if (parsedMetadata.value === "1" || parsedMetadata.value === "Normal") {
|
|
37
|
+
difficulty = 'normal';
|
|
38
|
+
}
|
|
39
|
+
else if (parsedMetadata.value === "2" || parsedMetadata.value === "Hard") {
|
|
40
|
+
difficulty = 'hard';
|
|
41
|
+
}
|
|
42
|
+
else if (parsedMetadata.value === "3" || parsedMetadata.value === "Oni") {
|
|
43
|
+
difficulty = 'oni';
|
|
44
|
+
}
|
|
45
|
+
else if (parsedMetadata.value === "4" || parsedMetadata.value === "Edit") {
|
|
46
|
+
difficulty = 'edit';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
metadata[parsedMetadata.key] = parsedMetadata.value;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!difficulty) {
|
|
55
|
+
throw new UnknownCourseDifficultyException(courseTja[0]);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const course = new Course(difficulty, metadata, song);
|
|
59
|
+
course.pushNoteGroups(...this.parseBar(courseTja.slice(i), course.getBalloonIterator(), course.getBPM()));
|
|
60
|
+
|
|
61
|
+
return course;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private static parseBar(lines: string[], getNextBalloon: () => number, bpmInit: number): NoteGroup[] {
|
|
65
|
+
const lineGroups = this.groupLines(lines);
|
|
66
|
+
|
|
67
|
+
const noteGroups = this.convertLineGroupToNoteGroup(lineGroups, getNextBalloon, bpmInit).noteGroups;
|
|
68
|
+
const barGroups: Bar[][] = [];
|
|
69
|
+
let currentBarGroup: Bar[] = [];
|
|
70
|
+
noteGroups.forEach((noteGroup) => {
|
|
71
|
+
if (noteGroup instanceof Bar) {
|
|
72
|
+
currentBarGroup.push(noteGroup);
|
|
73
|
+
}
|
|
74
|
+
else if (noteGroup instanceof Branch) {
|
|
75
|
+
barGroups.push(currentBarGroup);
|
|
76
|
+
currentBarGroup = [];
|
|
77
|
+
if (noteGroup.normal) {
|
|
78
|
+
barGroups.push(noteGroup.normal)
|
|
79
|
+
}
|
|
80
|
+
if (noteGroup.advanced) {
|
|
81
|
+
barGroups.push(noteGroup.advanced)
|
|
82
|
+
}
|
|
83
|
+
if (noteGroup.master) {
|
|
84
|
+
barGroups.push(noteGroup.master)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
if (currentBarGroup.length) {
|
|
89
|
+
barGroups.push(currentBarGroup);
|
|
90
|
+
}
|
|
91
|
+
barGroups.forEach((barGroup) => {
|
|
92
|
+
this.setRollEnd(barGroup)
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
return noteGroups;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Tja line을 마디 별로 묶음
|
|
100
|
+
*/
|
|
101
|
+
private static groupLines(lines: string[]) {
|
|
102
|
+
const LineGroup = Course.LineGroup;
|
|
103
|
+
const BranchedLineGroup = Course.BranchedLineGroup;
|
|
104
|
+
type LineGroup = Course.LineGroup;
|
|
105
|
+
type BranchedLineGroup = Course.BranchedLineGroup;
|
|
106
|
+
|
|
107
|
+
const bars: (LineGroup | BranchedLineGroup)[] = [];
|
|
108
|
+
let currentBar: LineGroup | null = null;
|
|
109
|
+
let currentBranchedLineGroup: BranchedLineGroup | null = null;
|
|
110
|
+
let currentCourse: 'normal' | 'advanced' | 'master' | null = null;
|
|
111
|
+
for (const line of lines) {
|
|
112
|
+
if (line === "#START") continue;
|
|
113
|
+
if (line === "#END") break;
|
|
114
|
+
|
|
115
|
+
if (line.startsWith("#BRANCHSTART")) {
|
|
116
|
+
const branchRawDatas = line.replaceAll('#BRANCHSTART', '').split(',').map(e => e.trim()) as [string, string, string];
|
|
117
|
+
const type = branchRawDatas[0] === "r" ? Branch.Type.ROLL : Branch.Type.ACCURACY;
|
|
118
|
+
const criteria: [number, number] = [Number(branchRawDatas[1]) || 0, Number(branchRawDatas[2]) || 0];
|
|
119
|
+
|
|
120
|
+
currentCourse = null;
|
|
121
|
+
currentBranchedLineGroup = new BranchedLineGroup(type, criteria);
|
|
122
|
+
currentBar = new LineGroup();
|
|
123
|
+
bars.push(currentBranchedLineGroup);
|
|
124
|
+
}
|
|
125
|
+
else if (line === "#BRANCHEND") {
|
|
126
|
+
currentCourse = null;
|
|
127
|
+
currentBar = new LineGroup();
|
|
128
|
+
bars.push(currentBar);
|
|
129
|
+
}
|
|
130
|
+
else if (line === "#N") {
|
|
131
|
+
currentCourse = "normal";
|
|
132
|
+
currentBar = new LineGroup();
|
|
133
|
+
currentBranchedLineGroup?.addNormal(currentBar);
|
|
134
|
+
}
|
|
135
|
+
else if (line === "#E") {
|
|
136
|
+
currentCourse = 'advanced';
|
|
137
|
+
currentBar = new LineGroup();
|
|
138
|
+
currentBranchedLineGroup?.addAdvanced(currentBar);
|
|
139
|
+
}
|
|
140
|
+
else if (line === "#M") {
|
|
141
|
+
currentCourse = 'master';
|
|
142
|
+
currentBar = new LineGroup();
|
|
143
|
+
currentBranchedLineGroup?.addMaster(currentBar);
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
if (!currentBar) {
|
|
147
|
+
currentBar = new LineGroup();
|
|
148
|
+
bars.push(currentBar);
|
|
149
|
+
}
|
|
150
|
+
currentBar.add(line);
|
|
151
|
+
if (line.endsWith(',')) {
|
|
152
|
+
if (!currentCourse) {
|
|
153
|
+
currentBar = new LineGroup();
|
|
154
|
+
bars.push(currentBar);
|
|
155
|
+
}
|
|
156
|
+
else if (currentCourse === "normal") {
|
|
157
|
+
currentBar = new LineGroup();
|
|
158
|
+
currentBranchedLineGroup?.addNormal(currentBar);
|
|
159
|
+
}
|
|
160
|
+
else if (currentCourse === "advanced") {
|
|
161
|
+
currentBar = new LineGroup();
|
|
162
|
+
currentBranchedLineGroup?.addAdvanced(currentBar);
|
|
163
|
+
}
|
|
164
|
+
else if (currentCourse === "master") {
|
|
165
|
+
currentBar = new LineGroup();
|
|
166
|
+
currentBranchedLineGroup?.addMaster(currentBar);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return bars;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* `LineGroup`을 `NoteGroup`으로 변환
|
|
177
|
+
*/
|
|
178
|
+
private static convertLineGroupToNoteGroup(
|
|
179
|
+
lineGroups: (Course.LineGroup | Course.BranchedLineGroup)[],
|
|
180
|
+
getNextBalloon: () => number,
|
|
181
|
+
currentBPM: number,
|
|
182
|
+
currentTiming: math.Fraction = math.fraction(0),
|
|
183
|
+
currentMeasure: math.Fraction = math.fraction(1),
|
|
184
|
+
currentScroll: number = 1,
|
|
185
|
+
barlineHidden: boolean = false,
|
|
186
|
+
): {
|
|
187
|
+
noteGroups: NoteGroup[],
|
|
188
|
+
getNextBalloon: () => number,
|
|
189
|
+
currentBPM: number,
|
|
190
|
+
currentTiming: math.Fraction,
|
|
191
|
+
currentMeasure: math.Fraction,
|
|
192
|
+
currentScroll: number,
|
|
193
|
+
barlineHidden: boolean
|
|
194
|
+
} {
|
|
195
|
+
/**
|
|
196
|
+
* 1000ms * (240 / BPM) * currentMeasure
|
|
197
|
+
*/
|
|
198
|
+
function getBarLength(): math.Fraction {
|
|
199
|
+
return math.fraction(math.divide(math.multiply(1000, 240, currentMeasure), math.fraction(currentBPM)) as math.Fraction);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const noteGroups: (Bar | Branch)[] = [];
|
|
203
|
+
for (const lineGroup of lineGroups) {
|
|
204
|
+
if (lineGroup instanceof Course.LineGroup) {
|
|
205
|
+
if (lineGroup.lines.length === 0) continue;
|
|
206
|
+
|
|
207
|
+
const bar = new Bar(math.fraction(currentTiming), math.fraction(currentTiming));
|
|
208
|
+
let noteCount = 0;
|
|
209
|
+
let items: Item[] = [];
|
|
210
|
+
for (const line of lineGroup.lines) {
|
|
211
|
+
if (line.startsWith('#')) {
|
|
212
|
+
const command = Command.parse(line);
|
|
213
|
+
if (command) items.push(command);
|
|
214
|
+
if (command instanceof BarlineCommand) barlineHidden = command.getHide();
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
for (const char of line) {
|
|
218
|
+
if (char === ',') break;
|
|
219
|
+
const note = Note.parse(char);
|
|
220
|
+
if (note) {
|
|
221
|
+
items.push(note);
|
|
222
|
+
noteCount++;
|
|
223
|
+
};
|
|
224
|
+
if (note instanceof BalloonNote) {
|
|
225
|
+
note.setCount(getNextBalloon());
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (noteCount === 0) {
|
|
232
|
+
items.forEach((item) => {
|
|
233
|
+
item.setTiming(currentTiming);
|
|
234
|
+
if (item instanceof Command) {
|
|
235
|
+
if (item instanceof MeasureCommand) {
|
|
236
|
+
currentMeasure = math.fraction(item.value);
|
|
237
|
+
}
|
|
238
|
+
else if (item instanceof BPMChangeCommand) {
|
|
239
|
+
currentBPM = item.value;
|
|
240
|
+
}
|
|
241
|
+
else if (item instanceof ScrollCommand) {
|
|
242
|
+
currentScroll = item.value;
|
|
243
|
+
}
|
|
244
|
+
else if (item instanceof MeasureCommand) {
|
|
245
|
+
currentMeasure = item.value;
|
|
246
|
+
}
|
|
247
|
+
else if (item instanceof BarlineCommand) {
|
|
248
|
+
barlineHidden = item.getHide();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
bar.setBarlineHidden(barlineHidden);
|
|
253
|
+
bar.setBpm(currentBPM);
|
|
254
|
+
bar.setScroll(currentScroll);
|
|
255
|
+
bar.setMeasure(currentMeasure);
|
|
256
|
+
currentTiming = math.add(currentTiming, getBarLength());
|
|
257
|
+
bar.setEnd(currentTiming);
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
let firstNoteChecked = false;
|
|
261
|
+
// 시작 타이밍 계산
|
|
262
|
+
const notes: Note[] = [];
|
|
263
|
+
items.forEach((item) => {
|
|
264
|
+
item.setTiming(currentTiming);
|
|
265
|
+
if (item instanceof Note) {
|
|
266
|
+
if (!firstNoteChecked) {
|
|
267
|
+
bar.setBarlineHidden(barlineHidden);
|
|
268
|
+
bar.setBpm(currentBPM);
|
|
269
|
+
bar.setScroll(currentScroll);
|
|
270
|
+
bar.setMeasure(currentMeasure);
|
|
271
|
+
firstNoteChecked = true;
|
|
272
|
+
}
|
|
273
|
+
const delay = math.fraction(math.divide(getBarLength(), noteCount) as math.Fraction);
|
|
274
|
+
item.setDelay(delay);
|
|
275
|
+
item.setBpm(currentBPM);
|
|
276
|
+
item.setScroll(currentScroll);
|
|
277
|
+
currentTiming = math.add(currentTiming, delay);
|
|
278
|
+
notes.push(item);
|
|
279
|
+
}
|
|
280
|
+
else if (item instanceof Command) {
|
|
281
|
+
if (item instanceof MeasureCommand) {
|
|
282
|
+
currentMeasure = math.fraction(item.value);
|
|
283
|
+
}
|
|
284
|
+
else if (item instanceof BPMChangeCommand) {
|
|
285
|
+
currentBPM = item.value;
|
|
286
|
+
}
|
|
287
|
+
else if (item instanceof ScrollCommand) {
|
|
288
|
+
currentScroll = item.value;
|
|
289
|
+
}
|
|
290
|
+
else if (item instanceof MeasureCommand) {
|
|
291
|
+
currentMeasure = item.value;
|
|
292
|
+
}
|
|
293
|
+
else if (item instanceof BarlineCommand) {
|
|
294
|
+
barlineHidden = item.getHide();
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
bar.setEnd(currentTiming);
|
|
299
|
+
|
|
300
|
+
// 노트의 delay와 연타 노트의 end 조정
|
|
301
|
+
let delaySum: math.Fraction = math.fraction(0);
|
|
302
|
+
let lengthSum: number = 1;
|
|
303
|
+
for (let i = notes.length - 1; i >= 0; i--) {
|
|
304
|
+
const note = notes[i];
|
|
305
|
+
if (note instanceof EmptyNote) {
|
|
306
|
+
delaySum = math.add(delaySum, note.getDelay());
|
|
307
|
+
lengthSum++;
|
|
308
|
+
}
|
|
309
|
+
else if (note instanceof RollEndNote) {
|
|
310
|
+
delaySum = math.add(delaySum, note.getDelay());
|
|
311
|
+
lengthSum++;
|
|
312
|
+
}
|
|
313
|
+
else if (note instanceof HitNote) {
|
|
314
|
+
note.setDelay(math.add(delaySum, note.getDelay()));
|
|
315
|
+
note.setNoteLength(lengthSum);
|
|
316
|
+
delaySum = math.fraction(0);
|
|
317
|
+
lengthSum = 1;
|
|
318
|
+
}
|
|
319
|
+
else if (note instanceof RollNote || note instanceof BalloonNote) {
|
|
320
|
+
note.setDelay(math.add(delaySum, note.getDelay()));
|
|
321
|
+
note.setNoteLength(lengthSum);
|
|
322
|
+
delaySum = math.fraction(0);
|
|
323
|
+
lengthSum = 1;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
items = items.filter((item) => {
|
|
328
|
+
if (item instanceof EmptyNote) {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
return true;
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
bar.pushItem(...items);
|
|
335
|
+
}
|
|
336
|
+
noteGroups.push(bar);
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
const branch = new Branch(lineGroup.type, lineGroup.criteria, math.fraction(currentTiming), math.fraction(currentTiming));
|
|
340
|
+
let result;
|
|
341
|
+
if (lineGroup.normal) {
|
|
342
|
+
result = this.convertLineGroupToNoteGroup(lineGroup.normal, getNextBalloon, currentBPM, currentTiming, currentMeasure, currentScroll, barlineHidden);
|
|
343
|
+
branch.normal = result.noteGroups as Bar[];
|
|
344
|
+
}
|
|
345
|
+
if (lineGroup.advanced) {
|
|
346
|
+
result = this.convertLineGroupToNoteGroup(lineGroup.advanced, getNextBalloon, currentBPM, currentTiming, currentMeasure, currentScroll, barlineHidden);
|
|
347
|
+
branch.advanced = result.noteGroups as Bar[];
|
|
348
|
+
}
|
|
349
|
+
if (lineGroup.master) {
|
|
350
|
+
result = this.convertLineGroupToNoteGroup(lineGroup.master, getNextBalloon, currentBPM, currentTiming, currentMeasure, currentScroll, barlineHidden);
|
|
351
|
+
branch.master = result.noteGroups as Bar[];
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (result) {
|
|
355
|
+
currentBPM = result.currentBPM;
|
|
356
|
+
currentTiming = result.currentTiming;
|
|
357
|
+
currentMeasure = result.currentMeasure;
|
|
358
|
+
barlineHidden = result.barlineHidden;
|
|
359
|
+
branch.setEnd(result.currentTiming);
|
|
360
|
+
}
|
|
361
|
+
noteGroups.push(branch);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return {
|
|
366
|
+
noteGroups,
|
|
367
|
+
getNextBalloon,
|
|
368
|
+
currentBPM,
|
|
369
|
+
currentTiming,
|
|
370
|
+
currentMeasure,
|
|
371
|
+
currentScroll,
|
|
372
|
+
barlineHidden
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* RollEndNote를 확인하고 앞의 RollNote의 end 타이밍을 설정. 그리고 RollNote를 제거
|
|
378
|
+
*/
|
|
379
|
+
private static setRollEnd(barGroups: Bar[]) {
|
|
380
|
+
const notes = barGroups.flatMap((bar) => bar.getNotes());
|
|
381
|
+
|
|
382
|
+
let rollEnd: math.Fraction | null = null;
|
|
383
|
+
notes.toReversed().forEach((note) => {
|
|
384
|
+
if (note instanceof RollEndNote) {
|
|
385
|
+
rollEnd = note.getTiming();
|
|
386
|
+
}
|
|
387
|
+
else if (note instanceof RollNote) {
|
|
388
|
+
note.end = rollEnd ? rollEnd : note.getTiming();
|
|
389
|
+
rollEnd = null;
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
barGroups.forEach((bar) => {
|
|
394
|
+
const items = bar.getItems().filter((item) => {
|
|
395
|
+
if (item instanceof RollEndNote) {
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
return true;
|
|
399
|
+
});
|
|
400
|
+
bar.clearItems();
|
|
401
|
+
bar.pushItem(...items)
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
difficulty: Difficulty;
|
|
406
|
+
metadata: Course.Metadata;
|
|
407
|
+
noteGroups: NoteGroup[] = [];
|
|
408
|
+
song?: Song;
|
|
409
|
+
|
|
410
|
+
constructor(difficulty: Difficulty, metadata: Course.Metadata, song?: Song) {
|
|
411
|
+
this.difficulty = difficulty;
|
|
412
|
+
this.metadata = metadata;
|
|
413
|
+
this.song = song;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* `song`의 `getBPM`을 호출하여 반환함.
|
|
418
|
+
* `song`이 존재하지 않으면 160을 반환함.
|
|
419
|
+
*/
|
|
420
|
+
getBPM() {
|
|
421
|
+
return this.song?.getBPM() || 160;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* `metadata`에서 level을 가져옴.
|
|
426
|
+
* `metadata`에 level이 존재하지 않으면 1을 반환함.
|
|
427
|
+
*/
|
|
428
|
+
getLevel() {
|
|
429
|
+
return Number(this.metadata.level) || 1;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
pushNoteGroups(...noteGroups: NoteGroup[]) {
|
|
433
|
+
this.noteGroups.push(...noteGroups);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
getBalloon() {
|
|
437
|
+
return (this.metadata.balloon as string ?? '').split(',').map((e) => Number(e));
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
getBalloonIterator() {
|
|
441
|
+
let index = 0;
|
|
442
|
+
const balloons = this.getBalloon();
|
|
443
|
+
function getNext() {
|
|
444
|
+
const balloon = balloons[index];
|
|
445
|
+
index++;
|
|
446
|
+
return balloon;
|
|
447
|
+
}
|
|
448
|
+
return getNext;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
toJSON() {
|
|
452
|
+
return {
|
|
453
|
+
metadata: this.metadata,
|
|
454
|
+
difficulty: this.difficulty,
|
|
455
|
+
noteGroups: this.noteGroups
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
export namespace Course {
|
|
461
|
+
export class Metadata {
|
|
462
|
+
level?: number;
|
|
463
|
+
[key: string]: string | number | undefined;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
export class LineGroup {
|
|
467
|
+
lines: string[] = [];
|
|
468
|
+
add(line: string) {
|
|
469
|
+
this.lines.push(line);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
export class BranchedLineGroup {
|
|
473
|
+
type: Branch.Type;
|
|
474
|
+
criteria: [number, number];
|
|
475
|
+
normal?: LineGroup[];
|
|
476
|
+
advanced?: LineGroup[];
|
|
477
|
+
master?: LineGroup[];
|
|
478
|
+
|
|
479
|
+
constructor(type: Branch.Type, criteria: [number, number]) {
|
|
480
|
+
this.type = type;
|
|
481
|
+
this.criteria = criteria;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
addNormal(lineGroup: LineGroup) {
|
|
485
|
+
if (!this.normal) this.normal = [];
|
|
486
|
+
this.normal.push(lineGroup);
|
|
487
|
+
}
|
|
488
|
+
addAdvanced(lineGroup: LineGroup) {
|
|
489
|
+
if (!this.advanced) this.advanced = [];
|
|
490
|
+
this.advanced.push(lineGroup);
|
|
491
|
+
}
|
|
492
|
+
addMaster(lineGroup: LineGroup) {
|
|
493
|
+
if (!this.master) this.master = [];
|
|
494
|
+
this.master.push(lineGroup);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
438
497
|
}
|