feat: improve chart navigation and show original measure numbers
Publish GitHub Pages output / publish (push) Successful in 49s
Publish GitHub Pages output / publish (push) Successful in 49s
This commit is contained in:
@@ -103,6 +103,8 @@ export function parseSusTimeline(text) {
|
||||
bpmEvents.sort((left, right) => left.beat - right.beat);
|
||||
const uniqueBpmEvents = bpmEvents.filter((event, index) => index === 0 || event.beat !== bpmEvents[index - 1].beat);
|
||||
function secondsAt(beat) {
|
||||
const waveOffset = Number(headers.get('WAVEOFFSET') ?? 0);
|
||||
if (beat < uniqueBpmEvents[0].beat) return beat * 60 / Number(headers.get('BASEBPM') ?? 120) + waveOffset;
|
||||
let seconds = 0;
|
||||
let previous = uniqueBpmEvents[0];
|
||||
if (previous.beat > 0) seconds += previous.beat * 60 / Number(headers.get('BASEBPM') ?? 120);
|
||||
@@ -112,7 +114,7 @@ export function parseSusTimeline(text) {
|
||||
previous = event;
|
||||
}
|
||||
seconds += (beat - previous.beat) * 60 / previous.bpm;
|
||||
return seconds + Number(headers.get('WAVEOFFSET') ?? 0);
|
||||
return seconds + waveOffset;
|
||||
}
|
||||
const key = (note) => `${note.beat}|${note.lane}`;
|
||||
const tapKeys = new Set(taps.filter((note) => note.kind === 1 || note.kind === 2).map(key));
|
||||
@@ -190,6 +192,12 @@ export function parseSusTimeline(text) {
|
||||
visualNotes.push(...visibleRelays.map(note => visualNote(note, 'relay')));
|
||||
const visualHolds = holds.map(hold => hold.filter((note, i) => i === 0 || i === hold.length - 1 || !attached.has(key(note)))
|
||||
.map(note => ({ ...point(note), ...(note.kind === 4 ? { control: true } : {}) })));
|
||||
const lastSourceMeasure = Math.max(0, ...lines.map(line => Number(line.match(/^#(\d{3})\w+:/)?.[1] ?? 0)));
|
||||
const measureCount = Number(headers.get('MEASURE_COUNT') ?? lastSourceMeasure + 1);
|
||||
if (!Number.isInteger(measureCount) || measureCount < 1 || measureCount >= starts.length) throw new Error('잘못된 SUS 마디 수입니다.');
|
||||
// Keep the SUS's zero-based numbers, including silent bars and the end boundary.
|
||||
const measures = Array.from({ length: measureCount + 1 }, (_, number) => ({ number, timeSeconds: secondsAt(starts[number]) }));
|
||||
if (measures.some((measure, index) => !Number.isFinite(measure.timeSeconds) || (index > 0 && measure.timeSeconds <= measures[index - 1].timeSeconds))) throw new Error('잘못된 SUS 마디 시각입니다.');
|
||||
return { songId: headers.get('MUSIC_ID'), notes, specialStartSeconds: specialStarts.sort((left, right) => left - right).map(secondsAt),
|
||||
visual: { laneCount: 12, notes: visualNotes.sort((a, b) => a.timeSeconds - b.timeSeconds), holds: visualHolds } };
|
||||
visual: { laneCount: 12, notes: visualNotes.sort((a, b) => a.timeSeconds - b.timeSeconds), holds: visualHolds, measures } };
|
||||
}
|
||||
|
||||
@@ -76,4 +76,38 @@ test('마디 길이 변경은 다음 변경까지 유지한다', () => {
|
||||
#00212:11
|
||||
`;
|
||||
assert.deepEqual(parseSusTimeline(text).notes.map(n=>n.timeSeconds),[3,6]);
|
||||
assert.deepEqual(parseSusTimeline(text).visual.measures, [
|
||||
{ number: 0, timeSeconds: 0 }, { number: 1, timeSeconds: 3 },
|
||||
{ number: 2, timeSeconds: 6 }, { number: 3, timeSeconds: 9 }
|
||||
]);
|
||||
});
|
||||
|
||||
test('원본 마디 번호는 빈 마디, 마디 중간 BPM 변경과 WAVEOFFSET을 반영한다', () => {
|
||||
const text = `#MUSIC_ID m0003
|
||||
#MEASURE_COUNT 4
|
||||
#BASEBPM 60
|
||||
#WAVEOFFSET 0.5
|
||||
#NORMAL_NOTE_COUNT 1
|
||||
#FLICK_NOTE_COUNT 0
|
||||
#LONG_START_NOTE_COUNT 0
|
||||
#LONG_END_NOTE_COUNT 0
|
||||
#LONG_FLICK_END_NOTE_COUNT 0
|
||||
#LONG_RELAY_NOTE_COUNT 0
|
||||
#LONG_CONTINUE_NOTE_COUNT 0
|
||||
#BPM01:60
|
||||
#BPM02:120
|
||||
#00008:01
|
||||
#00108:0002
|
||||
#00202:3
|
||||
#00312:11
|
||||
`;
|
||||
assert.deepEqual(parseSusTimeline(text).visual.measures, [
|
||||
{ number: 0, timeSeconds: 0.5 }, { number: 1, timeSeconds: 4.5 },
|
||||
{ number: 2, timeSeconds: 7.5 }, { number: 3, timeSeconds: 9 },
|
||||
{ number: 4, timeSeconds: 10.5 }
|
||||
]);
|
||||
assert.deepEqual(parseSusTimeline(text).notes.map(note => note.timeSeconds), [9]);
|
||||
// Before the first explicit BPM event, use BASEBPM rather than extrapolating it backwards.
|
||||
assert.deepEqual(parseSusTimeline(text.replace('#00008:01\n', '')).visual.measures, parseSusTimeline(text).visual.measures);
|
||||
assert.throws(() => parseSusTimeline(text.replace('#MEASURE_COUNT 4', '#MEASURE_COUNT NaN')), /마디 수/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user