Skip to content

Commit

Permalink
fret parsing updated
Browse files Browse the repository at this point in the history
  • Loading branch information
pavel-zhur committed Oct 9, 2024
1 parent 66b4067 commit 6082349
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 99 deletions.
18 changes: 18 additions & 0 deletions HarmonyDB.Theory/HarmonyDB.Theory.Chords.Tests/ChordParserTests.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using HarmonyDB.Theory.Chords.Constants;
using HarmonyDB.Theory.Chords.Models;
using HarmonyDB.Theory.Chords.Models.Enums;
using HarmonyDB.Theory.Chords.Options;
Expand Down Expand Up @@ -45,4 +46,21 @@ public void UnwrapParentheses(string input, IReadOnlyList<(string fragment, bool
var (actual, error) = ChordParser.TryUnwrapParentheses(input, ChordTypeParsingOptions.MostForgiving);
Assert.Equal(expected, actual);
}

public static readonly TheoryData<string, (string, NoteRepresentation?, byte?, Note?)> ExtractBassAndFretData = new()
{
{ "X", ("X", null, null, null) },
{ "/A", ("", new(NaturalNoteRepresentation.A, 0, 0), null, new((byte)NaturalNoteRepresentation.A)) },
{ "/A(III)", ("", new(NaturalNoteRepresentation.A, 0, 0), 3, new((byte)NaturalNoteRepresentation.A)) },
{ "X234\\F\\A(III)", ("X234\\F", new(NaturalNoteRepresentation.A, 0, 0), 3, new((byte)NaturalNoteRepresentation.A)) },
};

[Theory]
[MemberData(nameof(ExtractBassAndFretData))]
public void ExtractBassAndFret(string input, (string modified, NoteRepresentation? bass, byte? fret, Note? bassNote) expected)
{
var actual = ChordParser.ExtractBassAndFret(ref input, out var bassNote, ChordParsingOptions.MostForgiving);
Assert.Equal(bassNote.HasValue, actual.bass != null);
Assert.Equal(expected, (input, actual.bass, actual.fret, bassNote));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ public static class ChordConstants
(ChordTypeMeaninglessAddition.Star, "*"),
(ChordTypeMeaninglessAddition.Question, "?"),
(ChordTypeMeaninglessAddition.Slash, "/"),
(ChordTypeMeaninglessAddition.Slash, "\\"),
(ChordTypeMeaninglessAddition.Apostrophe, "'"),
];

Expand All @@ -179,8 +180,6 @@ public static class ChordConstants
.Select(x => (new ChordTypeToken(x.extension), x.representation, x.matchCase, x.matchAmbiguity)))
.Concat(ChordTypeAdditionRepresentations
.Select(x => (new ChordTypeToken(x.addition), x.representation, x.matchCase, x.matchAmbiguity)))
.Concat(Romans.WithIndices()
.Select(x => (new ChordTypeToken((byte)(x.i + 1)), representation: x.x, MatchCase.ExactOnly, MatchAmbiguity.Safe)))
.Concat(ChordTypeMeaninglessAdditionRepresentations
.Select(x => (new ChordTypeToken(x.addition), x.representation, MatchCase.ExactOnly, MatchAmbiguity.Safe)))
.Concat(ChordTypeAmbiguousAdditionRepresentations
Expand All @@ -195,8 +194,6 @@ public static readonly IReadOnlyDictionary<ChordTypeToken, string> CanonicalRepr
.Select(x => (token: new ChordTypeToken(x.extension), x.representation)))
.Concat(ChordTypeAdditionRepresentations
.Select(x => (token: new ChordTypeToken(x.addition), x.representation)))
.Concat(Romans.WithIndices()
.Select(x => (token: new ChordTypeToken((byte)(x.i + 1)), representation: x.x)))
.Concat(ChordTypeMeaninglessAdditionRepresentations
.Select(x => (token: new ChordTypeToken(x.addition), x.representation)))
.Concat(ChordTypeAmbiguousAdditionRepresentations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ public record ChordParseTrace
public List<(ChordTypeToken token, bool fromParentheses, MatchAmbiguity matchAmbiguity)>? ChordTypeTokens { get; set; }
public ChordTypeParseLogic? ChordTypeParseLogic { get; set; }
public byte? ChordTypeParseBranchIndex { get; set; }
public NoteRepresentation? BassRepresentation { get; set; }
public byte? Fret { get; set; }
}
7 changes: 2 additions & 5 deletions HarmonyDB.Theory/HarmonyDB.Theory.Chords/Models/ChordType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace HarmonyDB.Theory.Chords.Models;

public record ChordType(ChordMainType Type, ChordTypeExtension? Extension, ChordTypeAdditions Additions, byte? Fret)
public record ChordType(ChordMainType Type, ChordTypeExtension? Extension, ChordTypeAdditions Additions)
{
public string ToCanonical()
{
Expand All @@ -19,9 +19,6 @@ public string ToCanonical()
.Where(x => Additions.HasFlag(x))
.Select(x => new ChordTypeToken(x)));

if (Fret.HasValue)
tokens.Add(new(Fret.Value));

return string.Join(string.Empty, tokens.Select(x => x.Fret.HasValue ? $"({x.ToCanonical()})" : x.ToCanonical()));
return string.Join(string.Empty, tokens.Select(x => x.ToCanonical()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,6 @@ public ChordTypeToken(ChordTypeAdditions? addition)
Addition = addition;
}

public ChordTypeToken(byte? fret)
{
Fret = fret;
}

public ChordTypeToken(ChordTypeMeaninglessAddition? meaninglessAddition)
{
MeaninglessAddition = meaninglessAddition;
Expand All @@ -38,15 +33,13 @@ public ChordTypeToken(ChordTypeAmbiguousAddition? ambiguousAddition)
public ChordMainType? Type { get; }
public ChordTypeExtension? Extension { get; }
public ChordTypeAdditions? Addition { get; }
public byte? Fret { get; }
public ChordTypeMeaninglessAddition? MeaninglessAddition { get; }
public ChordTypeAmbiguousAddition? AmbiguousAddition { get; }

public override string ToString()
=> Type?.ToString()
?? Extension?.ToString()
?? Addition?.ToString()
?? Fret?.ToString()
?? MeaninglessAddition?.ToString()
?? AmbiguousAddition.ToString()!;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,7 @@ public enum ChordParseError
SameBass,
EmptyString,
BadSymbols,
RomansNotInParentheses,
RomansInParenthesesExpectedOnly,
OnlyIntegerInParentheses,
DuplicateAdditions,
EachExtensionTypeExpectedUnique,
MaxOneMajExtensionExpected,
MultipleFrets
}
117 changes: 38 additions & 79 deletions HarmonyDB.Theory/HarmonyDB.Theory.Chords/Parsers/ChordParser.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.RegularExpressions;
using System.Data;
using System.Text.RegularExpressions;
using HarmonyDB.Theory.Chords.Constants;
using HarmonyDB.Theory.Chords.Models;
using HarmonyDB.Theory.Chords.Models.Enums;
Expand All @@ -19,14 +20,23 @@ public static ChordParseResult Parse(string stringRepresentation, ChordParsingOp
if (options.ForgiveRoundBraces && stringRepresentation.Length > 0 && stringRepresentation[0] == '(' && stringRepresentation[^1] == ')')
stringRepresentation = stringRepresentation.Substring(1, stringRepresentation.Length - 1);

var trace = new ChordParseTrace();

if (stringRepresentation == string.Empty)
return new(ChordParseError.EmptyString, trace);

if (ChordConstants.NoChordVariants.Contains(stringRepresentation))
return new(ChordParseResultType.SpecialNoChord);

var trace = new ChordParseTrace();
var (bassRepresentation, fret) = ExtractBassAndFret(ref stringRepresentation, out var bass, options);
trace.BassRepresentation = bassRepresentation;
trace.Fret = fret;

var (bassRepresentation, fret, error) = ExtractBass(ref stringRepresentation, out var bass, options);
if (error.HasValue)
return new(error.Value, trace);
if (options.ForgiveEdgeWhitespaces)
stringRepresentation = stringRepresentation.Trim();

if (stringRepresentation == string.Empty)
return new(ChordParseError.EmptyString, trace);

var prefixLength = NoteParser.TryParsePrefixNote(stringRepresentation, out var root, out var rootRepresentation, options.NoteParsingOptions);
if (prefixLength == 0)
Expand All @@ -43,7 +53,7 @@ public static ChordParseResult Parse(string stringRepresentation, ChordParsingOp
return new(ChordParseError.SameBass, trace);
}

(var tokens, error) = TryGetTokens(chordTypeRepresentation, options.ChordTypeParsingOptions);
var (tokens, error) = TryGetTokens(chordTypeRepresentation, options.ChordTypeParsingOptions);
if (error.HasValue)
return new(error.Value, trace);

Expand Down Expand Up @@ -138,53 +148,14 @@ void ReplaceWithASeparator2(Func<(ChordTypeToken token, bool fromParentheses, Ma

#endregion

var resultFret = bassFret;
#region Parentheses content, frets. FromParentheses not needed after this point. No romans after this point.

{
if (tokens.Any(x => x.token.Fret.HasValue && !x.fromParentheses))
return (null, ChordParseError.RomansNotInParentheses, null, 1);

var meaningfulFromParentheses = tokens
// separators cannot be from parentheses except when there are multiple things in them.
.Where(x => x.fromParentheses)
.ToList();

if (meaningfulFromParentheses.Any(x => x.token.Fret.HasValue))
{
if (meaningfulFromParentheses.Count > 1)
return (null, ChordParseError.RomansInParenthesesExpectedOnly, null, 2);

if (bassFret.HasValue)
return (null, ChordParseError.MultipleFrets, null, 3);

resultFret = meaningfulFromParentheses[0].token.Fret!.Value;
ReplaceWithASeparator2(x => x is { fromParentheses: true, token.Fret: not null });
}

if (meaningfulFromParentheses.Any(x => x.matchAmbiguity == MatchAmbiguity.AlterableInt))
{
if (meaningfulFromParentheses.Count == 1)
{
if (!options.OnlyIntegersInParenthesesAreAddedDegrees)
return (null, ChordParseError.OnlyIntegerInParentheses, null, 4);
}
}

if (tokens.Any(x => x.token.Fret.HasValue))
throw new("Could not have happened, all frets filtered out.");
}

#endregion

var allAdditions = ChordTypeAdditions.None;
#region Gathering all additions, checking uniqueness

{
foreach (var addition in tokens.Where(x => x.token.Addition.HasValue).GroupBy(x => x.token.Addition!.Value))
{
if (addition.Count() > 1)
return (null, ChordParseError.DuplicateAdditions, null, 5);
return (null, ChordParseError.DuplicateAdditions, null, 1);

allAdditions |= addition.Key;
}
Expand All @@ -197,7 +168,7 @@ void ReplaceWithASeparator2(Func<(ChordTypeToken token, bool fromParentheses, Ma
{
if (tokens.Where(x => x.token.Extension.HasValue).GroupBy(x => x.token.Extension!.Value)
.Any(g => g.Count() > 1))
return (null, ChordParseError.EachExtensionTypeExpectedUnique, null, 6);
return (null, ChordParseError.EachExtensionTypeExpectedUnique, null, 2);
}

#endregion
Expand All @@ -206,7 +177,7 @@ void ReplaceWithASeparator2(Func<(ChordTypeToken token, bool fromParentheses, Ma

{
if (tokens.Count(x => x.token.Extension >= ChordTypeExtension.XMaj7) > 1)
return (null, ChordParseError.MaxOneMajExtensionExpected, null, 7);
return (null, ChordParseError.MaxOneMajExtensionExpected, null, 3);
}

#endregion
Expand Down Expand Up @@ -298,41 +269,29 @@ internal static (List<(string fragment, bool fromParentheses)>? fragments, Chord
return (trimmed, null);
}

private static (NoteRepresentation? bass, byte? fret, ChordParseError? error) ExtractBass(ref string stringRepresentation, out Note? bass, ChordParsingOptions options)
internal static (NoteRepresentation? bass, byte? fret) ExtractBassAndFret(ref string stringRepresentation, out Note? bass, ChordParsingOptions options)
{
var slashParts = stringRepresentation.Split('/');
NoteRepresentation? bassRepresentation = null;
bass = null;
byte? fret = null;

switch (slashParts.Length)
var fretMatch = Regex.Match(stringRepresentation, @"^(.*)\(([XIV]+)\)$");
if (fretMatch.Success && ChordConstants.Romans.WithIndicesNullable().SingleOrDefault(x => x.x == fretMatch.Groups[2].Value).i is { } i)
{
case 0:
return (null, null, ChordParseError.EmptyString);

case > 1:
if (NoteParser.TryParseNote(slashParts[^1], out var note, out bassRepresentation, options.NoteParsingOptions))
{
stringRepresentation = string.Join(string.Empty, slashParts.SkipLast(1));
bass = note;
}
else
{
var bassWithFret = Regex.Match(slashParts[^1], "^([^\\(\\)]+)\\(\\)([^\\(\\)]+)$");
if (bassWithFret.Success)
{
if (NoteParser.TryParseNote(bassWithFret.Groups[1].Value, out note, out bassRepresentation, options.NoteParsingOptions)
&& ChordConstants.Romans.WithIndicesNullable().SingleOrDefault(x => x.x == bassWithFret.Groups[2].Value).i is { } i)
{
stringRepresentation = string.Join(string.Empty, slashParts.SkipLast(1));
fret = (byte)(i + 1);
}
}
}

break;
fret = (byte)(i + 1);
stringRepresentation = fretMatch.Groups[1].Value;
}

return (bassRepresentation, fret, null);
var bassMatch = Regex.Match(stringRepresentation, @"^(.*)[/\\](.+)$");

NoteRepresentation? bassRepresentation = null;
if (bassMatch.Success && NoteParser.TryParseNote(bassMatch.Groups[2].Value, out var note, out bassRepresentation, options.NoteParsingOptions))
{
bass = note;
stringRepresentation = bassMatch.Groups[1].Value;
}
else
{
bass = null;
}

return (bassRepresentation, fret);
}
}

0 comments on commit 6082349

Please sign in to comment.