From 0841f56d3b93496ad01b283d550bd22c501a087c Mon Sep 17 00:00:00 2001 From: xhayper Date: Sun, 12 Nov 2023 17:00:15 +0700 Subject: [PATCH 1/5] feat: added timing change deserialization --- src/internal/syntacticAnalysis/deserializer.ts | 15 +++++++++++---- src/internal/syntacticAnalysis/serializer.ts | 3 +++ .../syntacticAnalysis/states/noteReader.ts | 4 ++-- .../states/subdivisionReader.ts | 18 ++++++++++++++++-- .../syntacticAnalysis/states/tempoReader.ts | 9 ++++++++- src/internal/syntacticAnalysis/timingChange.ts | 1 + src/structures/maiChart.ts | 2 ++ 7 files changed, 43 insertions(+), 9 deletions(-) create mode 100644 src/internal/syntacticAnalysis/serializer.ts diff --git a/src/internal/syntacticAnalysis/deserializer.ts b/src/internal/syntacticAnalysis/deserializer.ts index 0b8692f..d364da4 100644 --- a/src/internal/syntacticAnalysis/deserializer.ts +++ b/src/internal/syntacticAnalysis/deserializer.ts @@ -21,8 +21,15 @@ export class Deserializer { */ public readonly enumerator: Enumerator; + /** + * INTERNAL USAGE ONLY! DO NOT USE THIS PROPERTY DIRECTLY! + */ + public readonly timingChanges: TimingChange[] = [new TimingChange(0, 4)]; private _maxFinishTime: number = 0; - private _currentTime: number = 0; + /** + * INTERNAL USAGE ONLY! DO NOT USE THIS PROPERTY DIRECTLY! + */ + public currentTime: number = 0; /** * INTERNAL USAGE ONLY! DO NOT USE THIS PROPERTY DIRECTLY! */ @@ -61,7 +68,7 @@ export class Deserializer { break; } case TokenType.Location: { - this.currentNoteCollection ??= new NoteCollection(this._currentTime); + this.currentNoteCollection ??= new NoteCollection(this.currentTime); if (token.lexeme[0] === "0") { if (this.currentNoteCollection.eachStyle !== EachStyle.ForceBroken) @@ -86,7 +93,7 @@ export class Deserializer { this.currentNoteCollection = undefined; } - this._currentTime += this.currentTiming.secondsPerBeat; + this.currentTime += this.timingChanges[this.timingChanges.length - 1].secondsPerBeat; } break; case TokenType.EachDivider: @@ -111,7 +118,7 @@ export class Deserializer { case TokenType.SlideJoiner: throw new ScopeMismatchException(token.line, token.character, ScopeType.Slide); case TokenType.EndOfFile: - this._chart.finishTiming = this._currentTime; + this._chart.finishTiming = this.currentTime; break; case TokenType.None: break; diff --git a/src/internal/syntacticAnalysis/serializer.ts b/src/internal/syntacticAnalysis/serializer.ts new file mode 100644 index 0000000..cbb13f3 --- /dev/null +++ b/src/internal/syntacticAnalysis/serializer.ts @@ -0,0 +1,3 @@ +export class Serializer { + +} \ No newline at end of file diff --git a/src/internal/syntacticAnalysis/states/noteReader.ts b/src/internal/syntacticAnalysis/states/noteReader.ts index 66f34b2..46f8cb5 100644 --- a/src/internal/syntacticAnalysis/states/noteReader.ts +++ b/src/internal/syntacticAnalysis/states/noteReader.ts @@ -59,7 +59,7 @@ export class NoteReader { } case TokenType.Duration: { - NoteReader.readDuration(token, parent.currentTiming, currentNote); + NoteReader.readDuration(parent.timingChanges[parent.timingChanges.length-1], token, currentNote); break; } @@ -126,7 +126,7 @@ export class NoteReader { } } - private static readDuration(token: Token, timing: TimingChange, note: Note) { + private static readDuration(timing: TimingChange, token: Token, note: Note) { if (note.type !== NoteType.Break) note.type = NoteType.Hold; if (token.lexeme[0] === "#") { diff --git a/src/internal/syntacticAnalysis/states/subdivisionReader.ts b/src/internal/syntacticAnalysis/states/subdivisionReader.ts index 60b5e55..4855f7e 100644 --- a/src/internal/syntacticAnalysis/states/subdivisionReader.ts +++ b/src/internal/syntacticAnalysis/states/subdivisionReader.ts @@ -13,7 +13,14 @@ export class SubdivisionReader { if (isNaN(explicitTempo)) throw new UnexpectedCharacterException(token.line, token.character + 1, '0~9, or "."'); - parent.currentTiming.explicitOverride(explicitTempo); + // TODO: This might not copy the object and just be a reference. + const newTimingChange = parent.timingChanges[parent.timingChanges.length - 1]; + newTimingChange.explicitOverride(explicitTempo); + + if (Math.abs(parent.timingChanges[parent.timingChanges.length - 1].time - parent.currentTime) <= 0.0001) + parent.timingChanges.pop(); + + parent.timingChanges.push(newTimingChange); return; } @@ -21,6 +28,13 @@ export class SubdivisionReader { if (isNaN(subdivision)) throw new UnexpectedCharacterException(token.line, token.character, '0~9, or "."'); - parent.currentTiming.subdivisions = subdivision; + const newTimingChange = parent.timingChanges[parent.timingChanges.length - 1]; + newTimingChange.subdivisions = subdivision; + + // TODO: This might not copy the object and just be a reference. + if (Math.abs(parent.timingChanges[parent.timingChanges.length - 1].time - parent.currentTime) <= 0.0001) + parent.timingChanges.pop(); + + parent.timingChanges.push(newTimingChange); } } diff --git a/src/internal/syntacticAnalysis/states/tempoReader.ts b/src/internal/syntacticAnalysis/states/tempoReader.ts index 010533b..56eb454 100644 --- a/src/internal/syntacticAnalysis/states/tempoReader.ts +++ b/src/internal/syntacticAnalysis/states/tempoReader.ts @@ -11,6 +11,13 @@ export class TempoReader { if (isNaN(tempo)) throw new UnexpectedCharacterException(token.line, token.character, '0~9, or "."'); - parent.currentTiming.tempo = tempo; + const newTimingChange = parent.timingChanges[parent.timingChanges.length - 1]; + newTimingChange.tempo = tempo; + newTimingChange.time = parent.currentTime; + + if (Math.abs(parent.timingChanges[parent.timingChanges.length - 1].time - parent.currentTime) <= 0.0001) + parent.timingChanges.pop(); + + parent.timingChanges.push(newTimingChange); } } diff --git a/src/internal/syntacticAnalysis/timingChange.ts b/src/internal/syntacticAnalysis/timingChange.ts index 9dd2002..2e8077b 100644 --- a/src/internal/syntacticAnalysis/timingChange.ts +++ b/src/internal/syntacticAnalysis/timingChange.ts @@ -1,4 +1,5 @@ export class TimingChange { + public time: number = 0; public tempo: number; public subdivisions: number; diff --git a/src/structures/maiChart.ts b/src/structures/maiChart.ts index f3805f4..8dcec4b 100644 --- a/src/structures/maiChart.ts +++ b/src/structures/maiChart.ts @@ -1,6 +1,8 @@ +import { TimingChange } from "../internal/syntacticAnalysis/timingChange"; import { NoteCollection } from "./noteCollection"; export class MaiChart { public finishTiming: number | undefined; public noteCollections: NoteCollection[] = []; + public timingChanges: TimingChange[] = []; } From 070f994e4cdebc0c7fed665696294aafd89639cb Mon Sep 17 00:00:00 2001 From: xhayper Date: Sun, 12 Nov 2023 17:10:19 +0700 Subject: [PATCH 2/5] bugfix: Fixed 0-length hold parsing when note is not break and not invalidated --- src/internal/syntacticAnalysis/states/noteReader.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/internal/syntacticAnalysis/states/noteReader.ts b/src/internal/syntacticAnalysis/states/noteReader.ts index 46f8cb5..fb46860 100644 --- a/src/internal/syntacticAnalysis/states/noteReader.ts +++ b/src/internal/syntacticAnalysis/states/noteReader.ts @@ -59,7 +59,7 @@ export class NoteReader { } case TokenType.Duration: { - NoteReader.readDuration(parent.timingChanges[parent.timingChanges.length-1], token, currentNote); + NoteReader.readDuration(parent.timingChanges[parent.timingChanges.length - 1], token, currentNote); break; } @@ -101,9 +101,8 @@ export class NoteReader { note.styles |= NoteStyles.Mine; return; case "h": - if (note.type === NoteType.Break || note.type === NoteType.ForceInvalidate) break; + if (note.type !== NoteType.Break && note.type !== NoteType.ForceInvalidate) note.type = NoteType.Hold; - note.type = NoteType.Hold; note.length ??= 0; return; case "?": From 8412291c82b61396c0ff17a56ff04dda12c7e368 Mon Sep 17 00:00:00 2001 From: xhayper Date: Sun, 12 Nov 2023 18:30:19 +0700 Subject: [PATCH 3/5] feat: fully work on the serializer --- .../syntacticAnalysis/deserializer.ts | 15 ++-- src/internal/syntacticAnalysis/serializer.ts | 80 ++++++++++++++++++- .../syntacticAnalysis/states/slideReader.ts | 4 +- .../states/subdivisionReader.ts | 27 +++++-- .../syntacticAnalysis/states/tempoReader.ts | 10 ++- .../syntacticAnalysis/timingChange.ts | 2 +- src/internal/utility.ts | 7 ++ src/simaiConvert.ts | 6 ++ src/structures/location.ts | 28 ++++++- src/structures/note.ts | 34 ++++++++ src/structures/slidePath.ts | 14 ++++ src/structures/slideSegment.ts | 44 ++++++++++ tests/chartTests.ts | 10 +++ 13 files changed, 259 insertions(+), 22 deletions(-) diff --git a/src/internal/syntacticAnalysis/deserializer.ts b/src/internal/syntacticAnalysis/deserializer.ts index d364da4..408d41c 100644 --- a/src/internal/syntacticAnalysis/deserializer.ts +++ b/src/internal/syntacticAnalysis/deserializer.ts @@ -24,12 +24,12 @@ export class Deserializer { /** * INTERNAL USAGE ONLY! DO NOT USE THIS PROPERTY DIRECTLY! */ - public readonly timingChanges: TimingChange[] = [new TimingChange(0, 4)]; + public readonly timingChanges: TimingChange[] = []; private _maxFinishTime: number = 0; /** * INTERNAL USAGE ONLY! DO NOT USE THIS PROPERTY DIRECTLY! */ - public currentTime: number = 0; + public currentTime: number; /** * INTERNAL USAGE ONLY! DO NOT USE THIS PROPERTY DIRECTLY! */ @@ -37,14 +37,14 @@ export class Deserializer { /** * INTERNAL USAGE ONLY! DO NOT USE THIS PROPERTY DIRECTLY! */ - public currentTiming: TimingChange = new TimingChange(0, 0); - /** - * INTERNAL USAGE ONLY! DO NOT USE THIS PROPERTY DIRECTLY! - */ - public endOfFile: boolean = false; + public endOfFile: boolean; constructor(sequence: Iterable) { this.enumerator = new Enumerator(sequence); + this.timingChanges.push(new TimingChange(0, 4)); + this.currentNoteCollection = undefined; + this.currentTime = 0; + this.endOfFile = false; } public getChart(): MaiChart { @@ -135,6 +135,7 @@ export class Deserializer { } this._chart.noteCollections = noteCollections; + this._chart.timingChanges = this.timingChanges; return this._chart; } diff --git a/src/internal/syntacticAnalysis/serializer.ts b/src/internal/syntacticAnalysis/serializer.ts index cbb13f3..d6160a9 100644 --- a/src/internal/syntacticAnalysis/serializer.ts +++ b/src/internal/syntacticAnalysis/serializer.ts @@ -1,3 +1,79 @@ +import { EachStyle } from "../../structures/eachStyle"; +import { MaiChart } from "../../structures/maiChart"; +import { NoteCollection } from "../../structures/noteCollection"; + +/** + * INTERNAL USAGE ONLY! DO NOT USE THIS PROPERTY DIRECTLY! + */ export class Serializer { - -} \ No newline at end of file + private _currentTimingChange: number = 0; + private _currentNoteCollection: number = 0; + private _currentTime: number = 0; + + // there's no such thing as StringWriter in TypeScript + public serialize(chart: MaiChart): string { + let writer = ""; + + writer += `(${chart.timingChanges[this._currentTimingChange].tempo})`; + writer += `{{${chart.timingChanges[this._currentTimingChange].subdivisions}}}`; + + while (this._currentTime <= (chart.finishTiming ? chart.finishTiming : 0)) { + if ( + this._currentTimingChange < chart.timingChanges.length - 1 && + Math.abs(chart.timingChanges[this._currentTimingChange + 1].time - this._currentTime) < 1.401298E-45 + ) { + this._currentTimingChange++; + + if ( + Math.abs( + chart.timingChanges[this._currentTimingChange].tempo - + chart.timingChanges[this._currentTimingChange - 1].tempo + ) > 1.401298E-45 + ) { + writer += `(${chart.timingChanges[this._currentTimingChange].tempo})`; + } + + if ( + Math.abs( + chart.timingChanges[this._currentTimingChange].subdivisions - + chart.timingChanges[this._currentTimingChange - 1].subdivisions + ) > 1.401298E-45 + ) { + writer += `{{${chart.timingChanges[this._currentTimingChange].subdivisions}}}`; + } + } + + if ( + this._currentNoteCollection < chart.noteCollections.length && + Math.abs(chart.noteCollections[this._currentNoteCollection].time - this._currentTime) <= 1.401298E-45 + ) { + writer += Serializer.serializeNoteCollection(chart.noteCollections[this._currentNoteCollection]); + + this._currentNoteCollection++; + } + + const timingChange = chart.timingChanges[this._currentTimingChange]; + this._currentTime += timingChange.secondsPerBeat; + writer += ","; + } + writer += "E"; + + return writer; + } + + private static serializeNoteCollection(notes: NoteCollection): string { + let writer = ""; + + const seperator = notes.eachStyle === EachStyle.ForceBroken ? "`" : "/"; + + if (notes.eachStyle === EachStyle.ForceEach) writer += "0/"; + + for (let i = 0; i < notes.length; i++) { + writer += notes[i].write(); + + if (i != notes.length - 1) writer += seperator; + } + + return writer; + } +} diff --git a/src/internal/syntacticAnalysis/states/slideReader.ts b/src/internal/syntacticAnalysis/states/slideReader.ts index be2948d..6e75c9f 100644 --- a/src/internal/syntacticAnalysis/states/slideReader.ts +++ b/src/internal/syntacticAnalysis/states/slideReader.ts @@ -53,7 +53,7 @@ export class SlideReader { } case TokenType.Duration: { - SlideReader.readDuration(token, parent.currentTiming, path); + SlideReader.readDuration(parent.timingChanges[parent.timingChanges.length - 1], token, path); break; } @@ -151,7 +151,7 @@ export class SlideReader { } while (parent.enumerator.current.type === TokenType.Location); } - private static readDuration(token: Token, timing: TimingChange, path: SlidePath) { + private static readDuration(timing: TimingChange, token: Token, path: SlidePath) { const startOfDurationDeclaration = 0; const overrideTiming = timing; diff --git a/src/internal/syntacticAnalysis/states/subdivisionReader.ts b/src/internal/syntacticAnalysis/states/subdivisionReader.ts index 4855f7e..0496adb 100644 --- a/src/internal/syntacticAnalysis/states/subdivisionReader.ts +++ b/src/internal/syntacticAnalysis/states/subdivisionReader.ts @@ -1,6 +1,7 @@ import { UnexpectedCharacterException } from "../../errors/unexpectedCharacterException"; import { Token } from "../../lexicalAnalysis/token"; import { Deserializer } from "../deserializer"; +import { TimingChange } from "../timingChange"; /** * INTERNAL USAGE ONLY! DO NOT USE THIS PROPERTY DIRECTLY! @@ -13,11 +14,19 @@ export class SubdivisionReader { if (isNaN(explicitTempo)) throw new UnexpectedCharacterException(token.line, token.character + 1, '0~9, or "."'); - // TODO: This might not copy the object and just be a reference. - const newTimingChange = parent.timingChanges[parent.timingChanges.length - 1]; - newTimingChange.explicitOverride(explicitTempo); + let newTimingChange; + { + const oldTimingChange = parent.timingChanges[parent.timingChanges.length - 1]; + newTimingChange = new TimingChange(oldTimingChange.tempo, oldTimingChange.subdivisions); + } - if (Math.abs(parent.timingChanges[parent.timingChanges.length - 1].time - parent.currentTime) <= 0.0001) + newTimingChange.setSeconds(explicitTempo); + newTimingChange.time = parent.currentTime; + + if ( + Math.abs(parent.timingChanges[parent.timingChanges.length - 1].time - parent.currentTime) <= + 1.401298e-45 + ) parent.timingChanges.pop(); parent.timingChanges.push(newTimingChange); @@ -28,11 +37,15 @@ export class SubdivisionReader { if (isNaN(subdivision)) throw new UnexpectedCharacterException(token.line, token.character, '0~9, or "."'); - const newTimingChange = parent.timingChanges[parent.timingChanges.length - 1]; + let newTimingChange; + { + const oldTimingChange = parent.timingChanges[parent.timingChanges.length - 1]; + newTimingChange = new TimingChange(oldTimingChange.tempo, oldTimingChange.subdivisions); + } + newTimingChange.subdivisions = subdivision; - // TODO: This might not copy the object and just be a reference. - if (Math.abs(parent.timingChanges[parent.timingChanges.length - 1].time - parent.currentTime) <= 0.0001) + if (Math.abs(parent.timingChanges[parent.timingChanges.length - 1].time - parent.currentTime) <= 1.401298e-45) parent.timingChanges.pop(); parent.timingChanges.push(newTimingChange); diff --git a/src/internal/syntacticAnalysis/states/tempoReader.ts b/src/internal/syntacticAnalysis/states/tempoReader.ts index 56eb454..eb8ed9c 100644 --- a/src/internal/syntacticAnalysis/states/tempoReader.ts +++ b/src/internal/syntacticAnalysis/states/tempoReader.ts @@ -1,6 +1,7 @@ import { UnexpectedCharacterException } from "../../errors/unexpectedCharacterException"; import { Token } from "../../lexicalAnalysis/token"; import { Deserializer } from "../deserializer"; +import { TimingChange } from "../timingChange"; /** * INTERNAL USAGE ONLY! DO NOT USE THIS PROPERTY DIRECTLY! @@ -11,11 +12,16 @@ export class TempoReader { if (isNaN(tempo)) throw new UnexpectedCharacterException(token.line, token.character, '0~9, or "."'); - const newTimingChange = parent.timingChanges[parent.timingChanges.length - 1]; + let newTimingChange; + { + const oldTimingChange = parent.timingChanges[parent.timingChanges.length - 1]; + newTimingChange = new TimingChange(oldTimingChange.tempo, oldTimingChange.subdivisions); + } + newTimingChange.tempo = tempo; newTimingChange.time = parent.currentTime; - if (Math.abs(parent.timingChanges[parent.timingChanges.length - 1].time - parent.currentTime) <= 0.0001) + if (Math.abs(parent.timingChanges[parent.timingChanges.length - 1].time - parent.currentTime) <= 1.401298e-45) parent.timingChanges.pop(); parent.timingChanges.push(newTimingChange); diff --git a/src/internal/syntacticAnalysis/timingChange.ts b/src/internal/syntacticAnalysis/timingChange.ts index 2e8077b..a37706c 100644 --- a/src/internal/syntacticAnalysis/timingChange.ts +++ b/src/internal/syntacticAnalysis/timingChange.ts @@ -18,7 +18,7 @@ export class TimingChange { this.subdivisions = subdivisions; } - explicitOverride(value: number) { + public setSeconds(value: number) { this.tempo = 60 / value; this.subdivisions = 4; } diff --git a/src/internal/utility.ts b/src/internal/utility.ts index 154248c..4083c0e 100644 --- a/src/internal/utility.ts +++ b/src/internal/utility.ts @@ -1,6 +1,13 @@ export type FileEncoding = "utf7" | "utf8" | "utf16le" | "utf16be" | "utf32le" | "utf32be" | "unicode" | "unicodebe"; export class Utility { + /** + * Format a number to `0.0000000` format. + */ + static formatNumber(number: number): string { + return number.toFixed(7); + } + static removeLineEndings(str: string): string { return str.replace(/(\r\n|\n|\r)/gm, ""); } diff --git a/src/simaiConvert.ts b/src/simaiConvert.ts index a75e71b..53218ca 100644 --- a/src/simaiConvert.ts +++ b/src/simaiConvert.ts @@ -1,6 +1,7 @@ import { Deserializer } from "./internal/syntacticAnalysis/deserializer"; import { Tokenizer } from "./internal/lexicalAnalysis/tokenizer"; import { MaiChart } from "./structures/maiChart"; +import { Serializer } from "./internal/syntacticAnalysis/serializer"; export class SimaiConvert { public static deserialize(data: string): MaiChart { @@ -9,4 +10,9 @@ export class SimaiConvert { return chart; } + + public static serialize(chart: MaiChart): string { + const serializer = new Serializer(); + return serializer.serialize(chart); + } } diff --git a/src/structures/location.ts b/src/structures/location.ts index 7c0b712..4597aed 100644 --- a/src/structures/location.ts +++ b/src/structures/location.ts @@ -18,6 +18,32 @@ export class Location { } public toString(): string { - return `index: ${this.index}, group: ${this.group.toString()}`; + switch (this.group) { + case NoteGroup.Tap: + return (this.index + 1).toString(); + case NoteGroup.CSensor: + return "C"; + default: + let groupChar = ""; + + switch (this.group) { + case NoteGroup.ASensor: + groupChar = "A"; + break; + case NoteGroup.BSensor: + groupChar = "B"; + break; + case NoteGroup.DSensor: + groupChar = "D"; + break; + case NoteGroup.ESensor: + groupChar = "E"; + break; + // default: + // throw new ArgumentOutOfRangeException(); + } + + return groupChar + (this.index + 1).toString(); + } } } diff --git a/src/structures/note.ts b/src/structures/note.ts index f2459ce..4cc3035 100644 --- a/src/structures/note.ts +++ b/src/structures/note.ts @@ -41,4 +41,38 @@ export class Note { return baseValue; } + + public write(): string { + let writer = ""; + + writer += this.location.toString(); + + if ((this.styles & NoteStyles.Ex) != 0) writer += "x"; + + if ((this.styles & NoteStyles.Mine) != 0) writer += "m"; + + if (this.type === NoteType.ForceInvalidate) writer += this.slideMorph === SlideMorph.FadeIn ? "?" : "!"; + + switch (this.appearance) { + case NoteAppearance.ForceNormal: + writer += "@"; + break; + case NoteAppearance.ForceStarSpinning: + writer += "$$"; + break; + case NoteAppearance.ForceStar: + writer += "$"; + break; + } + + if (this.length) `h[#${this.length.toFixed(7)}]`; + + for (let i = 0; i < this.slidePaths.length; i++) { + if (i > 0) writer += "*"; + + writer += this.slidePaths[i].write(); + } + + return writer; + } } diff --git a/src/structures/slidePath.ts b/src/structures/slidePath.ts index 508a68c..e40276c 100644 --- a/src/structures/slidePath.ts +++ b/src/structures/slidePath.ts @@ -18,4 +18,18 @@ export class SlidePath { constructor(segments: SlideSegment[]) { this.segments = segments; } + + public write(): string { + let writer = ""; + + for (const segment of this.segments) { + writer += segment.write(this.startLocation); + } + + if (this.type === NoteType.Break) writer += "b"; + + writer += `[${this.delay.toFixed(7)}##${this.duration.toFixed(7)}]`; + + return writer; + } } diff --git a/src/structures/slideSegment.ts b/src/structures/slideSegment.ts index 766cba1..2c41d55 100644 --- a/src/structures/slideSegment.ts +++ b/src/structures/slideSegment.ts @@ -13,4 +13,48 @@ export class SlideSegment { this.vertices = vertices ?? []; this.slideType = SlideType.StraightLine; } + + public write(startLocation: Location): string { + let writer = ""; + + switch (this.slideType) { + case SlideType.StraightLine: + writer += `-${this.vertices[0]}`; + break; + case SlideType.RingCw: + writer += (startLocation.index + 2) % 8 >= 4 ? `<${this.vertices[0]}` : `>${this.vertices[0]}`; + break; + case SlideType.RingCcw: + writer += (startLocation.index + 2) % 8 >= 4 ? `<${this.vertices[0]}` : `>${this.vertices[0]}`; + break; + case SlideType.Fold: + writer += `v${this.vertices[0]}`; + break; + case SlideType.CurveCw: + writer += `q${this.vertices[0]}`; + break; + case SlideType.CurveCcw: + writer += `pp${this.vertices[0]}`; + break; + case SlideType.ZigZagS: + writer += `s${this.vertices[0]}`; + break; + case SlideType.ZigZagZ: + writer += `z${this.vertices[0]}`; + break; + case SlideType.EdgeFold: + writer += `V${this.vertices[0]}${this.vertices[1]}`; + break; + case SlideType.EdgeCurveCw: + writer += `qq${this.vertices[0]}`; + case SlideType.EdgeCurveCcw: + writer += `pp${this.vertices[0]}`; + case SlideType.Fan: + writer += `w${this.vertices[0]}`; + // default: + // throw new ArguementOutOfRangeException(); + } + + return writer; + } } diff --git a/tests/chartTests.ts b/tests/chartTests.ts index 1033f09..c443048 100644 --- a/tests/chartTests.ts +++ b/tests/chartTests.ts @@ -93,4 +93,14 @@ describe("SimaiConvert", () => { assert.equal(chart.noteCollections[1].time, 1); assert.equal(chart.noteCollections[2].time, 1.5); }); + + it("should be able to serialize", async () => { + const chartText = await fs.readFile(path.join(testChartPath, "../fileTests/0.txt"), "utf-8"); + + const simaiFile = new SimaiFile(chartText); + const chart = SimaiConvert.deserialize(simaiFile.getValue("inote_3")); + + const serialized = SimaiConvert.serialize(chart); + assert.notEqual(serialized, ""); + }); }); From 731768399ca7f2a74f7c7a2dd486ffed82d68cc1 Mon Sep 17 00:00:00 2001 From: xhayper Date: Sun, 12 Nov 2023 18:37:44 +0700 Subject: [PATCH 4/5] feat: attempt to fix chart --- src/internal/syntacticAnalysis/serializer.ts | 4 ++-- src/internal/syntacticAnalysis/states/subdivisionReader.ts | 1 + tests/chartTests.ts | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/internal/syntacticAnalysis/serializer.ts b/src/internal/syntacticAnalysis/serializer.ts index d6160a9..2acfd84 100644 --- a/src/internal/syntacticAnalysis/serializer.ts +++ b/src/internal/syntacticAnalysis/serializer.ts @@ -15,7 +15,7 @@ export class Serializer { let writer = ""; writer += `(${chart.timingChanges[this._currentTimingChange].tempo})`; - writer += `{{${chart.timingChanges[this._currentTimingChange].subdivisions}}}`; + writer += `{${chart.timingChanges[this._currentTimingChange].subdivisions}}`; while (this._currentTime <= (chart.finishTiming ? chart.finishTiming : 0)) { if ( @@ -39,7 +39,7 @@ export class Serializer { chart.timingChanges[this._currentTimingChange - 1].subdivisions ) > 1.401298E-45 ) { - writer += `{{${chart.timingChanges[this._currentTimingChange].subdivisions}}}`; + writer += `{${chart.timingChanges[this._currentTimingChange].subdivisions}}`; } } diff --git a/src/internal/syntacticAnalysis/states/subdivisionReader.ts b/src/internal/syntacticAnalysis/states/subdivisionReader.ts index 0496adb..830811e 100644 --- a/src/internal/syntacticAnalysis/states/subdivisionReader.ts +++ b/src/internal/syntacticAnalysis/states/subdivisionReader.ts @@ -44,6 +44,7 @@ export class SubdivisionReader { } newTimingChange.subdivisions = subdivision; + newTimingChange.time = parent.currentTime; if (Math.abs(parent.timingChanges[parent.timingChanges.length - 1].time - parent.currentTime) <= 1.401298e-45) parent.timingChanges.pop(); diff --git a/tests/chartTests.ts b/tests/chartTests.ts index c443048..27d6cc7 100644 --- a/tests/chartTests.ts +++ b/tests/chartTests.ts @@ -101,6 +101,7 @@ describe("SimaiConvert", () => { const chart = SimaiConvert.deserialize(simaiFile.getValue("inote_3")); const serialized = SimaiConvert.serialize(chart); + console.log(serialized); assert.notEqual(serialized, ""); }); }); From ab6b07de50992e7bf5f0ccb6c110e33989c30f67 Mon Sep 17 00:00:00 2001 From: xhayper Date: Sun, 12 Nov 2023 18:44:02 +0700 Subject: [PATCH 5/5] fix: setting the timingchanges wrong --- src/internal/syntacticAnalysis/deserializer.ts | 2 +- src/internal/syntacticAnalysis/states/noteReader.ts | 3 ++- .../syntacticAnalysis/states/subdivisionReader.ts | 8 ++++++-- src/internal/syntacticAnalysis/states/tempoReader.ts | 4 +++- src/internal/syntacticAnalysis/timingChange.ts | 9 ++------- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/internal/syntacticAnalysis/deserializer.ts b/src/internal/syntacticAnalysis/deserializer.ts index 408d41c..5a4ee8d 100644 --- a/src/internal/syntacticAnalysis/deserializer.ts +++ b/src/internal/syntacticAnalysis/deserializer.ts @@ -41,7 +41,7 @@ export class Deserializer { constructor(sequence: Iterable) { this.enumerator = new Enumerator(sequence); - this.timingChanges.push(new TimingChange(0, 4)); + this.timingChanges.push(new TimingChange()); this.currentNoteCollection = undefined; this.currentTime = 0; this.endOfFile = false; diff --git a/src/internal/syntacticAnalysis/states/noteReader.ts b/src/internal/syntacticAnalysis/states/noteReader.ts index fb46860..fac8dae 100644 --- a/src/internal/syntacticAnalysis/states/noteReader.ts +++ b/src/internal/syntacticAnalysis/states/noteReader.ts @@ -26,7 +26,8 @@ export class NoteReader { const currentNote = new Note(parent.currentNoteCollection!); currentNote.location = noteLocation; - const overrideTiming = new TimingChange(parent.currentNoteCollection!.time); + const overrideTiming = new TimingChange(); + overrideTiming.tempo = parent.timingChanges[parent.timingChanges.length - 1].tempo; if (noteLocation.group !== NoteGroup.Tap) currentNote.type = NoteType.Touch; diff --git a/src/internal/syntacticAnalysis/states/subdivisionReader.ts b/src/internal/syntacticAnalysis/states/subdivisionReader.ts index 830811e..7ae843d 100644 --- a/src/internal/syntacticAnalysis/states/subdivisionReader.ts +++ b/src/internal/syntacticAnalysis/states/subdivisionReader.ts @@ -17,7 +17,9 @@ export class SubdivisionReader { let newTimingChange; { const oldTimingChange = parent.timingChanges[parent.timingChanges.length - 1]; - newTimingChange = new TimingChange(oldTimingChange.tempo, oldTimingChange.subdivisions); + newTimingChange = new TimingChange(); + newTimingChange.tempo = oldTimingChange.tempo; + newTimingChange.subdivisions = oldTimingChange.subdivisions; } newTimingChange.setSeconds(explicitTempo); @@ -40,7 +42,9 @@ export class SubdivisionReader { let newTimingChange; { const oldTimingChange = parent.timingChanges[parent.timingChanges.length - 1]; - newTimingChange = new TimingChange(oldTimingChange.tempo, oldTimingChange.subdivisions); + newTimingChange = new TimingChange(); + newTimingChange.tempo = oldTimingChange.tempo; + newTimingChange.subdivisions = oldTimingChange.subdivisions; } newTimingChange.subdivisions = subdivision; diff --git a/src/internal/syntacticAnalysis/states/tempoReader.ts b/src/internal/syntacticAnalysis/states/tempoReader.ts index eb8ed9c..699a98f 100644 --- a/src/internal/syntacticAnalysis/states/tempoReader.ts +++ b/src/internal/syntacticAnalysis/states/tempoReader.ts @@ -15,7 +15,9 @@ export class TempoReader { let newTimingChange; { const oldTimingChange = parent.timingChanges[parent.timingChanges.length - 1]; - newTimingChange = new TimingChange(oldTimingChange.tempo, oldTimingChange.subdivisions); + newTimingChange = new TimingChange(); + newTimingChange.tempo = oldTimingChange.tempo; + newTimingChange.subdivisions = oldTimingChange.subdivisions; } newTimingChange.tempo = tempo; diff --git a/src/internal/syntacticAnalysis/timingChange.ts b/src/internal/syntacticAnalysis/timingChange.ts index a37706c..f722ce3 100644 --- a/src/internal/syntacticAnalysis/timingChange.ts +++ b/src/internal/syntacticAnalysis/timingChange.ts @@ -1,7 +1,7 @@ export class TimingChange { public time: number = 0; - public tempo: number; - public subdivisions: number; + public tempo: number = 0; + public subdivisions: number = 0; get secondsPerBar(): number { // could use || here since number is falsy if 0 @@ -13,11 +13,6 @@ export class TimingChange { return this.secondsPerBar / ((this.subdivisions === 0 ? 4 : this.subdivisions) / 4); } - constructor(tempo: number, subdivisions: number = 4) { - this.tempo = tempo; - this.subdivisions = subdivisions; - } - public setSeconds(value: number) { this.tempo = 60 / value; this.subdivisions = 4;