Skip to content

Commit

Permalink
Implement Pro Guitar parsing into Moonscraper .mid parser (YARC-Offic…
Browse files Browse the repository at this point in the history
…ial#16)

* Add Pro Guitar track/note info to Moonscraper code

* Implement Pro Guitar parsing
Only has feature parity with YARG's current Pro Guitar parser for now

* Add unit tests for Pro Guitar parsing

* Fix Pro Guitar note value retrieval
  • Loading branch information
TheNathannator authored Jun 11, 2023
1 parent f24abe8 commit 203299c
Show file tree
Hide file tree
Showing 8 changed files with 234 additions and 8 deletions.
1 change: 1 addition & 0 deletions YARG.Core.UnitTests/Parsing/ParseBehaviorTests.Chart.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ private static void GenerateSection(StringBuilder builder, List<MoonNote> data,
int rawNote = gameMode switch {
GameMode.Guitar => (int)note.guitarFret,
GameMode.GHLGuitar => (int)note.ghliveGuitarFret,
GameMode.ProGuitar => throw new NotSupportedException(".chart does not support Pro Guitar!"),
GameMode.Drums => (int)note.drumPad,
_ => note.rawNote
};
Expand Down
58 changes: 54 additions & 4 deletions YARG.Core.UnitTests/Parsing/ParseBehaviorTests.Midi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,16 @@ public class MidiParseBehaviorTests
{ MoonInstrument.Rhythm, RHYTHM_TRACK },
{ MoonInstrument.Keys, KEYS_TRACK },
{ MoonInstrument.Drums, DRUMS_TRACK },

{ MoonInstrument.GHLiveGuitar, GHL_GUITAR_TRACK },
{ MoonInstrument.GHLiveBass, GHL_BASS_TRACK },
{ MoonInstrument.GHLiveRhythm, GHL_RHYTHM_TRACK },
{ MoonInstrument.GHLiveCoop, GHL_GUITAR_COOP_TRACK },

{ MoonInstrument.ProGuitar_17Fret, PRO_GUITAR_17_FRET_TRACK },
{ MoonInstrument.ProGuitar_22Fret, PRO_GUITAR_22_FRET_TRACK },
{ MoonInstrument.ProBass_17Fret, PRO_BASS_17_FRET_TRACK },
{ MoonInstrument.ProBass_22Fret, PRO_BASS_22_FRET_TRACK },
};

private static readonly Dictionary<int, int> GuitarNoteOffsetLookup = new()
Expand Down Expand Up @@ -65,6 +71,24 @@ public class MidiParseBehaviorTests
{ MoonNoteType.Strum, 8 },
};

private static readonly Dictionary<int, int> ProGuitarNoteOffsetLookup = new()
{
{ (int)ProGuitarString.Red, 0 },
{ (int)ProGuitarString.Green, 1 },
{ (int)ProGuitarString.Orange, 2 },
{ (int)ProGuitarString.Blue, 3 },
{ (int)ProGuitarString.Yellow, 4 },
{ (int)ProGuitarString.Purple, 5 },
};

private static readonly Dictionary<MoonNoteType, int> ProGuitarForceOffsetLookup = new()
{
{ MoonNoteType.Hopo, 6 },
};

private static readonly Dictionary<Flags, byte> ProGuitarChannelFlagLookup =
PRO_GUITAR_CHANNEL_FLAG_LOOKUP.ToDictionary((pair) => pair.Value, (pair) => pair.Key);

private static readonly Dictionary<int, int> DrumsNoteOffsetLookup = new()
{
{ (int)DrumPad.Kick, 0 },
Expand All @@ -80,23 +104,36 @@ public class MidiParseBehaviorTests
{ GameMode.Guitar, GuitarNoteOffsetLookup },
{ GameMode.Drums, DrumsNoteOffsetLookup },
{ GameMode.GHLGuitar, GhlGuitarNoteOffsetLookup },
{ GameMode.ProGuitar, ProGuitarNoteOffsetLookup },
};

private static readonly Dictionary<GameMode, Dictionary<MoonNoteType, int>> InstrumentForceOffsetLookup = new()
{
{ GameMode.Guitar, GuitarForceOffsetLookup },
{ GameMode.Drums, new() },
{ GameMode.GHLGuitar, GhlGuitarForceOffsetLookup },
{ GameMode.ProGuitar, ProGuitarForceOffsetLookup },
};

private static readonly Dictionary<GameMode, Dictionary<Flags, byte>> InstrumentChannelFlagLookup = new()
{
{ GameMode.Guitar, new() },
{ GameMode.Drums, new() },
{ GameMode.GHLGuitar, new() },
{ GameMode.ProGuitar, ProGuitarChannelFlagLookup },
};

private static readonly Dictionary<GameMode, Dictionary<Difficulty, int>> InstrumentDifficultyStartLookup = new()
{
{ GameMode.Guitar, GUITAR_DIFF_START_LOOKUP },
{ GameMode.Drums, DRUMS_DIFF_START_LOOKUP },
{ GameMode.GHLGuitar, GHL_GUITAR_DIFF_START_LOOKUP },
{ GameMode.ProGuitar, PRO_GUITAR_DIFF_START_LOOKUP },
};

// Because SevenBitNumber andFourBitNumber have no implicit operators for taking in bytes
private static SevenBitNumber S(byte number) => (SevenBitNumber)number;
private static FourBitNumber F(byte number) => (FourBitNumber)number;

private static TrackChunk GenerateTrackChunk(List<MoonNote> data, MoonInstrument instrument)
{
Expand Down Expand Up @@ -150,7 +187,8 @@ private static void GenerateNotesForDifficulty<TNoteEvent>(TrackChunk chunk, Gam

// Whether or not certain note flags can be placed
// 5/6-fret guitar
bool canForce = gameMode is GameMode.Guitar or GameMode.GHLGuitar;
bool canForceStrum = gameMode is not GameMode.Drums or GameMode.ProGuitar;
bool canForceHopo = gameMode is not GameMode.Drums;
bool canTap = gameMode is GameMode.Guitar or GameMode.GHLGuitar && difficulty == Difficulty.Expert; // Tap marker is all-difficulty
// Drums
bool canTom = gameMode is GameMode.Drums && difficulty == Difficulty.Expert; // Tom markers are all-difficulty
Expand All @@ -161,12 +199,14 @@ private static void GenerateNotesForDifficulty<TNoteEvent>(TrackChunk chunk, Gam
int difficultyStart = InstrumentDifficultyStartLookup[gameMode][difficulty];
var noteOffsetLookup = InstrumentNoteOffsetLookup[gameMode];
var forceOffsetLookup = InstrumentForceOffsetLookup[gameMode];
var channelFlagLookup = InstrumentChannelFlagLookup[gameMode];

// Note properties
var flags = note.flags;
int rawNote = gameMode switch {
GameMode.Guitar => (int)note.guitarFret,
GameMode.GHLGuitar => (int)note.ghliveGuitarFret,
GameMode.ProGuitar => (int)note.proGuitarString,
GameMode.Drums => (int)note.drumPad,
_ => note.rawNote
};
Expand All @@ -185,14 +225,22 @@ private static void GenerateNotesForDifficulty<TNoteEvent>(TrackChunk chunk, Gam
velocity = VELOCITY_GHOST;
}

// Pro Guitar fret number
if (gameMode is GameMode.ProGuitar && velocity > 0)
velocity = (byte)(100 + note.proGuitarFret);

// Pro Guitar channel flags
if (!channelFlagLookup.TryGetValue(flags, out byte channel))
channel = 0;

// Main note
chunk.Events.Add(new TNoteEvent() { NoteNumber = S(noteNumber), Velocity = S(velocity), DeltaTime = noteDelta });
chunk.Events.Add(new TNoteEvent() { NoteNumber = S(noteNumber), Velocity = S(velocity), DeltaTime = noteDelta, Channel = F(channel) });

// Note flags
if (canForce && (flags & Flags.Forced) != 0)
if ((canForceStrum || canForceHopo) && (flags & Flags.Forced) != 0)
{
byte forceNote;
if (lastStartDelta is >= HOPO_THRESHOLD and > 0)
if (canForceHopo && lastStartDelta is >= HOPO_THRESHOLD and > 0)
forceNote = (byte)(difficultyStart + forceOffsetLookup[MoonNoteType.Hopo]);
else
forceNote = (byte)(difficultyStart + forceOffsetLookup[MoonNoteType.Strum]);
Expand Down Expand Up @@ -220,6 +268,7 @@ private static MidiFile GenerateMidi()
sync,
GenerateTrackChunk(GuitarNotes, MoonInstrument.Guitar),
GenerateTrackChunk(GhlGuitarNotes, MoonInstrument.GHLiveGuitar),
GenerateTrackChunk(ProGuitarNotes, MoonInstrument.ProGuitar_22Fret),
GenerateTrackChunk(DrumsNotes, MoonInstrument.Drums))
{
TimeDivision = new TicksPerQuarterNoteTimeDivision((short)RESOLUTION)
Expand Down Expand Up @@ -251,6 +300,7 @@ public void GenerateAndParseMidiFile()
{
VerifyTrack(song, GuitarNotes, MoonInstrument.Guitar, difficulty);
VerifyTrack(song, GhlGuitarNotes, MoonInstrument.GHLiveGuitar, difficulty);
VerifyTrack(song, ProGuitarNotes, MoonInstrument.ProGuitar_22Fret, difficulty);
VerifyTrack(song, DrumsNotes, MoonInstrument.Drums, difficulty);
}
});
Expand Down
26 changes: 26 additions & 0 deletions YARG.Core.UnitTests/Parsing/ParseBehaviorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ private static MoonNote NewNote(GHLiveGuitarFret fret, uint length = 0, Flags fl
=> new(0, (int)fret, length, flags);
private static MoonNote NewNote(DrumPad pad, uint length = 0, Flags flags = Flags.None)
=> new(0, (int)pad, length, flags);
private static MoonNote NewNote(ProGuitarString str, int fret, uint length = 0, Flags flags = Flags.None)
=> new(0, MoonNote.MakeProGuitarRawNote(str, fret), length, flags);

public static readonly List<MoonNote> GuitarNotes = new()
{
Expand Down Expand Up @@ -71,6 +73,30 @@ private static MoonNote NewNote(DrumPad pad, uint length = 0, Flags flags = Flag
NewNote(GHLiveGuitarFret.White3, flags: Flags.Tap),
};

public static readonly List<MoonNote> ProGuitarNotes = new()
{
NewNote(ProGuitarString.Red, 0),
NewNote(ProGuitarString.Green, 1),
NewNote(ProGuitarString.Orange, 2),
NewNote(ProGuitarString.Blue, 3),
NewNote(ProGuitarString.Yellow, 4),
NewNote(ProGuitarString.Purple, 5),

NewNote(ProGuitarString.Red, 6, flags: Flags.Forced),
NewNote(ProGuitarString.Green, 7, flags: Flags.Forced),
NewNote(ProGuitarString.Orange, 8, flags: Flags.Forced),
NewNote(ProGuitarString.Blue, 9, flags: Flags.Forced),
NewNote(ProGuitarString.Yellow, 10, flags: Flags.Forced),
NewNote(ProGuitarString.Purple, 11, flags: Flags.Forced),

NewNote(ProGuitarString.Red, 12, flags: Flags.ProGuitar_Muted),
NewNote(ProGuitarString.Green, 13, flags: Flags.ProGuitar_Muted),
NewNote(ProGuitarString.Orange, 14, flags: Flags.ProGuitar_Muted),
NewNote(ProGuitarString.Blue, 15, flags: Flags.ProGuitar_Muted),
NewNote(ProGuitarString.Yellow, 16, flags: Flags.ProGuitar_Muted),
NewNote(ProGuitarString.Purple, 17, flags: Flags.ProGuitar_Muted),
};

public static readonly List<MoonNote> DrumsNotes = new()
{
NewNote(DrumPad.Kick),
Expand Down
48 changes: 44 additions & 4 deletions YARG.Core/MoonscraperChartParser/Events/MoonNote.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,21 @@ public enum GHLiveGuitarFret
Open
}

public enum ProGuitarString
{
Red,
Green,
Orange,
Blue,
Yellow,
Purple
}

private const int PRO_GUITAR_FRET_OFFSET = 0;
private const int PRO_GUITAR_FRET_MASK = 0x1F << PRO_GUITAR_FRET_OFFSET;
private const int PRO_GUITAR_STRING_OFFSET = 5;
private const int PRO_GUITAR_STRING_MASK = 0x07 << PRO_GUITAR_STRING_OFFSET;

public enum MoonNoteType
{
Natural,
Expand All @@ -69,6 +84,9 @@ public enum Flags
Forced = 1 << 0,
Tap = 1 << 1,

// Pro Guitar
ProGuitar_Muted = 1 << 2,

// RB Pro Drums
ProDrums_Cymbal = 1 << 6,

Expand Down Expand Up @@ -102,6 +120,18 @@ public GHLiveGuitarFret ghliveGuitarFret
set => rawNote = (int)value;
}

public int proGuitarFret
{
get => (rawNote & PRO_GUITAR_FRET_MASK) >> PRO_GUITAR_FRET_OFFSET;
set => rawNote = MakeProGuitarRawNote(proGuitarString, value);
}

public ProGuitarString proGuitarString
{
get => (ProGuitarString)((rawNote & PRO_GUITAR_STRING_MASK) >> PRO_GUITAR_STRING_OFFSET);
set => rawNote = MakeProGuitarRawNote(value, proGuitarFret);
}

/// <summary>
/// Properties, such as forced or taps, are stored here in a bitwise format.
/// </summary>
Expand Down Expand Up @@ -374,10 +404,20 @@ IEnumerator IEnumerable.GetEnumerator()

public bool IsOpenNote()
{
if (gameMode == MoonChart.GameMode.GHLGuitar)
return ghliveGuitarFret == GHLiveGuitarFret.Open;
else
return guitarFret == GuitarFret.Open;
return gameMode switch
{
MoonChart.GameMode.GHLGuitar => ghliveGuitarFret == GHLiveGuitarFret.Open,
MoonChart.GameMode.ProGuitar => proGuitarFret == 0,
_ => guitarFret == GuitarFret.Open
};
}

public static int MakeProGuitarRawNote(ProGuitarString proString, int fret)
{
fret = Math.Clamp(fret, 0, 22);
int rawNote = (fret << PRO_GUITAR_FRET_OFFSET) & PRO_GUITAR_FRET_MASK;
rawNote |= ((int)proString << PRO_GUITAR_STRING_OFFSET) & PRO_GUITAR_STRING_MASK;
return rawNote;
}
}
}
40 changes: 40 additions & 0 deletions YARG.Core/MoonscraperChartParser/IO/Midi/MidIOHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ public static class MidIOHelper
public const string BASS_TRACK = "PART BASS";
public const string RHYTHM_TRACK = "PART RHYTHM";
public const string KEYS_TRACK = "PART KEYS";
public const string PRO_GUITAR_17_FRET_TRACK = "PART REAL_GUITAR";
public const string PRO_GUITAR_22_FRET_TRACK = "PART REAL_GUITAR_22";
public const string PRO_BASS_17_FRET_TRACK = "PART REAL_BASS";
public const string PRO_BASS_22_FRET_TRACK = "PART REAL_BASS_22";
public const string DRUMS_TRACK = "PART DRUMS";
public const string DRUMS_REAL_TRACK = "PART REAL_DRUMS_PS";
public const string GHL_GUITAR_TRACK = "PART GUITAR GHL";
Expand Down Expand Up @@ -46,6 +50,18 @@ public static class MidIOHelper
public const byte DRUM_ROLL_STANDARD = 126;
public const byte DRUM_ROLL_SPECIAL = 127;

// Pro Guitar notes
public const byte SOLO_NOTE_PRO_GUITAR = 115;

// Pro Guitar channels
public const byte PRO_GUITAR_CHANNEL_NORMAL = 0;
public const byte PRO_GUITAR_CHANNEL_GHOST = 1;
public const byte PRO_GUITAR_CHANNEL_BEND = 2;
public const byte PRO_GUITAR_CHANNEL_MUTED = 3;
public const byte PRO_GUITAR_CHANNEL_TAP = 4;
public const byte PRO_GUITAR_CHANNEL_HARMONIC = 5;
public const byte PRO_GUITAR_CHANNEL_PINCH_HARMONIC = 6;

// Text events
public const string SOLO_EVENT_TEXT = "solo";
public const string SOLO_END_EVENT_TEXT = "soloend";
Expand Down Expand Up @@ -84,6 +100,10 @@ public static class MidIOHelper
{ GHL_BASS_TRACK, MoonSong.MoonInstrument.GHLiveBass },
{ GHL_RHYTHM_TRACK, MoonSong.MoonInstrument.GHLiveRhythm },
{ GHL_GUITAR_COOP_TRACK, MoonSong.MoonInstrument.GHLiveCoop },
{ PRO_GUITAR_17_FRET_TRACK, MoonSong.MoonInstrument.ProGuitar_17Fret },
{ PRO_GUITAR_22_FRET_TRACK, MoonSong.MoonInstrument.ProGuitar_22Fret },
{ PRO_BASS_17_FRET_TRACK, MoonSong.MoonInstrument.ProBass_17Fret },
{ PRO_BASS_22_FRET_TRACK, MoonSong.MoonInstrument.ProBass_22Fret },
};

public static readonly Dictionary<MoonSong.Difficulty, int> GUITAR_DIFF_START_LOOKUP = new()
Expand All @@ -102,6 +122,14 @@ public static class MidIOHelper
{ MoonSong.Difficulty.Expert, 94 }
};

public static readonly Dictionary<MoonSong.Difficulty, int> PRO_GUITAR_DIFF_START_LOOKUP = new()
{
{ MoonSong.Difficulty.Easy, 24 },
{ MoonSong.Difficulty.Medium, 48 },
{ MoonSong.Difficulty.Hard, 72 },
{ MoonSong.Difficulty.Expert, 96 }
};

public static readonly Dictionary<MoonSong.Difficulty, int> DRUMS_DIFF_START_LOOKUP = new()
{
{ MoonSong.Difficulty.Easy, 60 },
Expand All @@ -119,5 +147,17 @@ public static class MidIOHelper
};

public static readonly Dictionary<int, MoonNote.DrumPad> CYMBAL_TO_PAD_LOOKUP = PAD_TO_CYMBAL_LOOKUP.ToDictionary((i) => i.Value, (i) => i.Key);

public static readonly Dictionary<byte, MoonNote.Flags> PRO_GUITAR_CHANNEL_FLAG_LOOKUP = new()
{
// Not all flags are implemented yet
{ PRO_GUITAR_CHANNEL_NORMAL, MoonNote.Flags.None },
// { PRO_GUITAR_CHANNEL_GHOST, MoonNote.Flags. },
// { PRO_GUITAR_CHANNEL_BEND, MoonNote.Flags. },
{ PRO_GUITAR_CHANNEL_MUTED, MoonNote.Flags.ProGuitar_Muted },
// { PRO_GUITAR_CHANNEL_TAP, MoonNote.Flags. },
// { PRO_GUITAR_CHANNEL_HARMONIC, MoonNote.Flags. },
// { PRO_GUITAR_CHANNEL_PINCH_HARMONIC, MoonNote.Flags. },
};
}
}
Loading

0 comments on commit 203299c

Please sign in to comment.