Skip to content

Commit

Permalink
Merge pull request #1 from reflektone-games/feat/serializer
Browse files Browse the repository at this point in the history
feat: serializer
  • Loading branch information
xhayper authored Nov 12, 2023
2 parents ef7da18 + ab6b07d commit a1da69d
Show file tree
Hide file tree
Showing 15 changed files with 300 additions and 26 deletions.
22 changes: 15 additions & 7 deletions src/internal/syntacticAnalysis/deserializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,30 @@ export class Deserializer {
*/
public readonly enumerator: Enumerator<Token>;

/**
* INTERNAL USAGE ONLY! DO NOT USE THIS PROPERTY DIRECTLY!
*/
public readonly timingChanges: TimingChange[] = [];
private _maxFinishTime: number = 0;
private _currentTime: number = 0;
/**
* INTERNAL USAGE ONLY! DO NOT USE THIS PROPERTY DIRECTLY!
*/
public currentNoteCollection: NoteCollection | undefined;
public currentTime: number;
/**
* INTERNAL USAGE ONLY! DO NOT USE THIS PROPERTY DIRECTLY!
*/
public currentTiming: TimingChange = new TimingChange(0, 0);
public currentNoteCollection: NoteCollection | undefined;
/**
* INTERNAL USAGE ONLY! DO NOT USE THIS PROPERTY DIRECTLY!
*/
public endOfFile: boolean = false;
public endOfFile: boolean;

constructor(sequence: Iterable<Token>) {
this.enumerator = new Enumerator<Token>(sequence);
this.timingChanges.push(new TimingChange());
this.currentNoteCollection = undefined;
this.currentTime = 0;
this.endOfFile = false;
}

public getChart(): MaiChart {
Expand All @@ -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)
Expand All @@ -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:
Expand All @@ -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;
Expand All @@ -128,6 +135,7 @@ export class Deserializer {
}

this._chart.noteCollections = noteCollections;
this._chart.timingChanges = this.timingChanges;

return this._chart;
}
Expand Down
79 changes: 79 additions & 0 deletions src/internal/syntacticAnalysis/serializer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +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 {
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;
}
}
10 changes: 5 additions & 5 deletions src/internal/syntacticAnalysis/states/noteReader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -59,7 +60,7 @@ export class NoteReader {
}

case TokenType.Duration: {
NoteReader.readDuration(token, parent.currentTiming, currentNote);
NoteReader.readDuration(parent.timingChanges[parent.timingChanges.length - 1], token, currentNote);
break;
}

Expand Down Expand Up @@ -101,9 +102,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 "?":
Expand All @@ -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] === "#") {
Expand Down
4 changes: 2 additions & 2 deletions src/internal/syntacticAnalysis/states/slideReader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;

Expand Down
36 changes: 34 additions & 2 deletions src/internal/syntacticAnalysis/states/subdivisionReader.ts
Original file line number Diff line number Diff line change
@@ -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!
Expand All @@ -13,14 +14,45 @@ export class SubdivisionReader {
if (isNaN(explicitTempo))
throw new UnexpectedCharacterException(token.line, token.character + 1, '0~9, or "."');

parent.currentTiming.explicitOverride(explicitTempo);
let newTimingChange;
{
const oldTimingChange = parent.timingChanges[parent.timingChanges.length - 1];
newTimingChange = new TimingChange();
newTimingChange.tempo = oldTimingChange.tempo;
newTimingChange.subdivisions = oldTimingChange.subdivisions;
}

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);
return;
}

const subdivision = parseFloat(token.lexeme);

if (isNaN(subdivision)) throw new UnexpectedCharacterException(token.line, token.character, '0~9, or "."');

parent.currentTiming.subdivisions = subdivision;
let newTimingChange;
{
const oldTimingChange = parent.timingChanges[parent.timingChanges.length - 1];
newTimingChange = new TimingChange();
newTimingChange.tempo = oldTimingChange.tempo;
newTimingChange.subdivisions = oldTimingChange.subdivisions;
}

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();

parent.timingChanges.push(newTimingChange);
}
}
17 changes: 16 additions & 1 deletion src/internal/syntacticAnalysis/states/tempoReader.ts
Original file line number Diff line number Diff line change
@@ -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!
Expand All @@ -11,6 +12,20 @@ export class TempoReader {

if (isNaN(tempo)) throw new UnexpectedCharacterException(token.line, token.character, '0~9, or "."');

parent.currentTiming.tempo = tempo;
let newTimingChange;
{
const oldTimingChange = parent.timingChanges[parent.timingChanges.length - 1];
newTimingChange = new TimingChange();
newTimingChange.tempo = oldTimingChange.tempo;
newTimingChange.subdivisions = oldTimingChange.subdivisions;
}

newTimingChange.tempo = tempo;
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);
}
}
12 changes: 4 additions & 8 deletions src/internal/syntacticAnalysis/timingChange.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export class TimingChange {
public tempo: number;
public subdivisions: number;
public time: number = 0;
public tempo: number = 0;
public subdivisions: number = 0;

get secondsPerBar(): number {
// could use || here since number is falsy if 0
Expand All @@ -12,12 +13,7 @@ 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;
}

explicitOverride(value: number) {
public setSeconds(value: number) {
this.tempo = 60 / value;
this.subdivisions = 4;
}
Expand Down
7 changes: 7 additions & 0 deletions src/internal/utility.ts
Original file line number Diff line number Diff line change
@@ -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, "");
}
Expand Down
6 changes: 6 additions & 0 deletions src/simaiConvert.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -9,4 +10,9 @@ export class SimaiConvert {

return chart;
}

public static serialize(chart: MaiChart): string {
const serializer = new Serializer();
return serializer.serialize(chart);
}
}
28 changes: 27 additions & 1 deletion src/structures/location.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
}
2 changes: 2 additions & 0 deletions src/structures/maiChart.ts
Original file line number Diff line number Diff line change
@@ -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[] = [];
}
Loading

0 comments on commit a1da69d

Please sign in to comment.