2024-05-27 10:59:40 +07:00

255 lines
9.4 KiB
C#

using System.Collections.Generic;
using System.Globalization;
using Manager;
using MoreChartFormats.Simai.Errors;
using MoreChartFormats.Simai.LexicalAnalysis;
namespace MoreChartFormats.Simai.SyntacticAnalysis.States;
internal static class NoteReader
{
public static void Process(Deserializer parent, in Token identityToken, NotesReferences refs, ref int index, ref int noteIndex, ref int slideIndex)
{
if (!Deserializer.TryReadLocation(in identityToken, out var position, out var touchGroup))
{
throw new InvalidSyntaxException(identityToken.line, identityToken.character);
}
var forceNormal = false;
var forceTapless = false;
var noteData = new NoteData
{
type = NotesTypeID.Def.Tap,
index = index,
startButtonPos = position,
beatType = ParserUtilities.GetBeatType(parent.CurrentTime.grid),
indexNote = noteIndex++,
};
noteData.time.copy(parent.CurrentTime);
noteData.end = noteData.time;
if (touchGroup != TouchSensorType.Invalid)
{
// simai does not have syntax for specifying touch size.
noteData.touchSize = NoteSize.M1;
noteData.touchArea = touchGroup;
noteData.type = NotesTypeID.Def.TouchTap;
}
// Some readers (e.g. NoteReader) moves the enumerator automatically.
// We can skip moving the pointer if that's satisfied.
var manuallyMoved = false;
var noteDataAdded = false;
while (!parent.IsEndOfFile && (manuallyMoved || parent.MoveNext()))
{
var token = parent.TokenEnumerator.Current;
manuallyMoved = false;
switch (token.type)
{
case TokenType.Tempo:
case TokenType.Subdivision:
throw new ScopeMismatchException(token.line, token.character, ScopeMismatchException.ScopeType.Global);
case TokenType.Decorator:
DecorateNote(in token, ref noteData, ref forceNormal, ref forceTapless);
break;
case TokenType.Slide:
{
if (!forceNormal && !forceTapless)
{
if (!StarMapping.TryGetValue(noteData.type, out var def))
{
throw new InvalidSyntaxException(token.line, token.character);
}
noteData.type = def;
}
if (!forceTapless)
{
parent.CurrentNoteData.Add(noteData);
}
SlideReader.Process(parent, in token, refs, noteData, ref index, ref noteIndex, ref slideIndex);
noteDataAdded = true;
manuallyMoved = true;
break;
}
case TokenType.Duration:
ReadDuration(refs, parent.CurrentBpmChange, in token, ref noteData);
break;
case TokenType.SlideJoiner:
throw new ScopeMismatchException(token.line, token.character, ScopeMismatchException.ScopeType.Slide);
case TokenType.TimeStep:
case TokenType.EachDivider:
case TokenType.EndOfFile:
case TokenType.Location:
// note terminates here
if (!noteDataAdded)
{
parent.CurrentNoteData.Add(noteData);
}
return;
case TokenType.None:
break;
default:
throw new UnsupportedSyntaxException(token.line, token.character);
}
}
parent.CurrentNoteData.Add(noteData);
}
private static readonly Dictionary<NotesTypeID, NotesTypeID.Def> BreakMapping = new()
{
{ NotesTypeID.Def.Tap, NotesTypeID.Def.Break },
{ NotesTypeID.Def.ExTap, NotesTypeID.Def.ExBreakTap },
{ NotesTypeID.Def.Star, NotesTypeID.Def.BreakStar },
{ NotesTypeID.Def.ExStar, NotesTypeID.Def.ExBreakStar },
{ NotesTypeID.Def.Hold, NotesTypeID.Def.BreakHold },
{ NotesTypeID.Def.ExHold, NotesTypeID.Def.ExBreakHold },
{ NotesTypeID.Def.Slide, NotesTypeID.Def.BreakSlide },
};
private static readonly Dictionary<NotesTypeID, NotesTypeID.Def> ExMapping = new()
{
{ NotesTypeID.Def.Tap, NotesTypeID.Def.ExTap },
{ NotesTypeID.Def.Break, NotesTypeID.Def.ExBreakTap },
{ NotesTypeID.Def.Star, NotesTypeID.Def.ExStar },
{ NotesTypeID.Def.BreakStar, NotesTypeID.Def.ExBreakStar },
{ NotesTypeID.Def.Hold, NotesTypeID.Def.ExHold },
{ NotesTypeID.Def.BreakHold, NotesTypeID.Def.ExBreakHold },
};
private static readonly Dictionary<NotesTypeID, NotesTypeID.Def> HoldMapping = new()
{
{ NotesTypeID.Def.Tap, NotesTypeID.Def.Hold },
{ NotesTypeID.Def.Break, NotesTypeID.Def.BreakHold },
{ NotesTypeID.Def.ExTap, NotesTypeID.Def.ExHold },
{ NotesTypeID.Def.ExBreakTap, NotesTypeID.Def.ExBreakHold },
{ NotesTypeID.Def.TouchTap, NotesTypeID.Def.TouchHold },
};
private static readonly Dictionary<NotesTypeID, NotesTypeID.Def> StarMapping = new()
{
{ NotesTypeID.Def.Tap, NotesTypeID.Def.Star },
{ NotesTypeID.Def.Break, NotesTypeID.Def.BreakStar },
{ NotesTypeID.Def.ExTap, NotesTypeID.Def.ExStar },
{ NotesTypeID.Def.ExBreakTap, NotesTypeID.Def.ExBreakStar },
};
private static void DecorateNote(in Token token, ref NoteData noteData, ref bool forceNormal, ref bool forceTapless)
{
switch (token.lexeme[0])
{
case 'f' when noteData.type.isTouch():
noteData.effect = TouchEffectType.Eff1;
return;
case 'b':
{
if (!BreakMapping.TryGetValue(noteData.type, out var def))
{
return;
}
noteData.type = def;
return;
}
case 'x':
{
if (!ExMapping.TryGetValue(noteData.type, out var def))
{
return;
}
noteData.type = def;
return;
}
case 'h':
{
if (!HoldMapping.TryGetValue(noteData.type, out var def))
{
return;
}
noteData.type = def;
return;
}
case '@':
forceNormal = true;
return;
case '?':
forceTapless = true;
return;
case '!':
forceTapless = true;
return;
case '$':
{
if (!StarMapping.TryGetValue(noteData.type, out var def))
{
return;
}
noteData.type = def;
return;
}
default:
throw new UnsupportedSyntaxException(token.line, token.character);
}
}
private static void ReadDuration(NotesReferences refs, BPMChangeData timing, in Token token, ref NoteData note)
{
if (HoldMapping.TryGetValue(note.type, out var def))
{
note.type = def;
}
var hashIndex = token.lexeme.IndexOf('#');
if (hashIndex == 0)
{
if (!float.TryParse(token.lexeme.Substring(1),
NumberStyles.Any,
CultureInfo.InvariantCulture,
out var explicitValue))
throw new UnexpectedCharacterException(token.line, token.character + 1, "0-9 or \".\"");
note.end = note.time + ParserUtilities.NotesTimeFromBars(refs, explicitValue / timing.SecondsPerBar());
return;
}
if (hashIndex != -1)
{
// The [BPM#a:b] syntax doesn't really make sense for holds? Unless we're adding
// a BPM change event just for this hold. I am not bothering.
throw new UnsupportedSyntaxException(token.line, token.character);
}
var separatorIndex = token.lexeme.IndexOf(':');
if (!float.TryParse(token.lexeme.Substring(0, separatorIndex),
NumberStyles.Any,
CultureInfo.InvariantCulture,
out var denominator))
throw new UnexpectedCharacterException(token.line, token.character, "0-9 or \".\"");
if (!float.TryParse(token.lexeme.Substring(separatorIndex + 1),
NumberStyles.Any,
CultureInfo.InvariantCulture,
out var nominator))
throw new UnexpectedCharacterException(token.line, token.character + separatorIndex + 1, "0-9 or \".\"");
note.end = note.time + ParserUtilities.NotesTimeFromBars(refs, nominator / denominator);
}
}