diff --git a/HarmonyDB.Theory/HarmonyDB.Theory.Chords/Constants/ChordConstants.cs b/HarmonyDB.Theory/HarmonyDB.Theory.Chords/Constants/ChordConstants.cs index a8594831..dc3a48cb 100644 --- a/HarmonyDB.Theory/HarmonyDB.Theory.Chords/Constants/ChordConstants.cs +++ b/HarmonyDB.Theory/HarmonyDB.Theory.Chords/Constants/ChordConstants.cs @@ -33,6 +33,9 @@ public static class ChordConstants (ChordMainType.Dim7, "º", MatchCase.ExactOnly, MatchAmbiguity.Safe), (ChordMainType.Dim7, "°", MatchCase.ExactOnly, MatchAmbiguity.Safe), (ChordMainType.Dim7, "o", MatchCase.ExactOnly, MatchAmbiguity.Safe), + (ChordMainType.Dim7, "º7", MatchCase.ExactOnly, MatchAmbiguity.Safe), + (ChordMainType.Dim7, "°7", MatchCase.ExactOnly, MatchAmbiguity.Safe), + (ChordMainType.Dim7, "o7", MatchCase.ExactOnly, MatchAmbiguity.Safe), (ChordMainType.Sus2, "sus2", MatchCase.MatchAny, MatchAmbiguity.Safe), (ChordMainType.Sus2, "s2", MatchCase.ExactOnly, MatchAmbiguity.Safe), @@ -67,6 +70,14 @@ public static class ChordConstants (ChordTypeExtension.XMaj7, "MA7", MatchCase.ExactOnly, MatchAmbiguity.Safe), (ChordTypeExtension.XMaj7, "M", MatchCase.ExactOnly, MatchAmbiguity.Dangerous), (ChordTypeExtension.XMaj7, "7M", MatchCase.ExactOnly, MatchAmbiguity.Dangerous), + (ChordTypeExtension.XMaj7, "Δ", MatchCase.ExactOnly, MatchAmbiguity.Safe), + (ChordTypeExtension.XMaj7, "∆", MatchCase.ExactOnly, MatchAmbiguity.Safe), + (ChordTypeExtension.XMaj7, "△", MatchCase.ExactOnly, MatchAmbiguity.Safe), + (ChordTypeExtension.XMaj7, "^", MatchCase.ExactOnly, MatchAmbiguity.Safe), + (ChordTypeExtension.XMaj7, "Δ7", MatchCase.ExactOnly, MatchAmbiguity.Safe), + (ChordTypeExtension.XMaj7, "∆7", MatchCase.ExactOnly, MatchAmbiguity.Safe), + (ChordTypeExtension.XMaj7, "△7", MatchCase.ExactOnly, MatchAmbiguity.Safe), + (ChordTypeExtension.XMaj7, "^7", MatchCase.ExactOnly, MatchAmbiguity.Safe), (ChordTypeExtension.XMaj9, "maj9", MatchCase.MatchAny, MatchAmbiguity.AlterableDegree), (ChordTypeExtension.XMaj9, "ma9", MatchCase.ExactOnly, MatchAmbiguity.AlterableDegree), @@ -133,11 +144,16 @@ public static class ChordConstants public static readonly IReadOnlyList<(ChordTypeAmbiguousAddition addition, string representation, MatchCase matchCase, MatchAmbiguity matchAmbiguity)> ChordTypeAmbiguousAdditionRepresentations = [ (ChordTypeAmbiguousAddition.HalfDiminished7, "ø", MatchCase.ExactOnly, MatchAmbiguity.Safe), + (ChordTypeAmbiguousAddition.HalfDiminished7, "Ø", MatchCase.ExactOnly, MatchAmbiguity.Safe), + (ChordTypeAmbiguousAddition.HalfDiminished7, "ø7", MatchCase.ExactOnly, MatchAmbiguity.Safe), + (ChordTypeAmbiguousAddition.HalfDiminished7, "Ø7", MatchCase.ExactOnly, MatchAmbiguity.Safe), (ChordTypeAmbiguousAddition.Sharp, "#", MatchCase.ExactOnly, MatchAmbiguity.DegreeAlteration), + (ChordTypeAmbiguousAddition.Sharp, "♯", MatchCase.ExactOnly, MatchAmbiguity.DegreeAlteration), (ChordTypeAmbiguousAddition.Flat, "b", MatchCase.ExactOnly, MatchAmbiguity.DegreeAlteration), - (ChordTypeAmbiguousAddition.Plus, "+", MatchCase.ExactOnly, MatchAmbiguity.DegreeAlteration), - (ChordTypeAmbiguousAddition.Minus, "-", MatchCase.ExactOnly, MatchAmbiguity.DegreeAlteration), + (ChordTypeAmbiguousAddition.Flat, "♭", MatchCase.ExactOnly, MatchAmbiguity.DegreeAlteration), + (ChordTypeAmbiguousAddition.Plus, "+", MatchCase.ExactOnly, MatchAmbiguity.DegreeAlteration), // generally means augmented if at the beginning and no degree follows + (ChordTypeAmbiguousAddition.Minus, "-", MatchCase.ExactOnly, MatchAmbiguity.DegreeAlteration), // generally means minor if at the beginning and no degree follows (ChordTypeAmbiguousAddition.Add9And11, "add9,11", MatchCase.MatchAny, MatchAmbiguity.AlterableDegree), ]; diff --git a/HarmonyDB.Theory/HarmonyDB.Theory.Chords/Models/Enums/ChordParseError.cs b/HarmonyDB.Theory/HarmonyDB.Theory.Chords/Models/Enums/ChordParseError.cs index f03698fa..b2f87cb0 100644 --- a/HarmonyDB.Theory/HarmonyDB.Theory.Chords/Models/Enums/ChordParseError.cs +++ b/HarmonyDB.Theory/HarmonyDB.Theory.Chords/Models/Enums/ChordParseError.cs @@ -2,10 +2,19 @@ public enum ChordParseError { + [Obsolete] + NotImplemented, + WhitespaceFragments, UnexpectedChordTypeToken, UnreadableRoot, SameBass, EmptyString, BadSymbols, + RomansNotInParentheses, + RomansInParenthesesExpectedOnly, + OnlyIntegerInParentheses, + DuplicateAdditions, + EachExtensionTypeExpectedUnique, + MaxOneMajExtensionExpected, } \ No newline at end of file diff --git a/HarmonyDB.Theory/HarmonyDB.Theory.Chords/Models/Enums/ChordTypeExtension.cs b/HarmonyDB.Theory/HarmonyDB.Theory.Chords/Models/Enums/ChordTypeExtension.cs index 399bad1e..3946c176 100644 --- a/HarmonyDB.Theory/HarmonyDB.Theory.Chords/Models/Enums/ChordTypeExtension.cs +++ b/HarmonyDB.Theory/HarmonyDB.Theory.Chords/Models/Enums/ChordTypeExtension.cs @@ -6,6 +6,7 @@ public enum ChordTypeExtension X9, X11, X13, + XMaj7, XMaj9, XMaj11, diff --git a/HarmonyDB.Theory/HarmonyDB.Theory.Chords/Models/Enums/ChordTypeParseLogic.cs b/HarmonyDB.Theory/HarmonyDB.Theory.Chords/Models/Enums/ChordTypeParseLogic.cs new file mode 100644 index 00000000..6453e549 --- /dev/null +++ b/HarmonyDB.Theory/HarmonyDB.Theory.Chords/Models/Enums/ChordTypeParseLogic.cs @@ -0,0 +1,5 @@ +namespace HarmonyDB.Theory.Chords.Models.Enums; + +public enum ChordTypeParseLogic +{ +} \ No newline at end of file diff --git a/HarmonyDB.Theory/HarmonyDB.Theory.Chords/Options/ChordTypeParsingOptions.cs b/HarmonyDB.Theory/HarmonyDB.Theory.Chords/Options/ChordTypeParsingOptions.cs index 5428b0d1..ee7e38e2 100644 --- a/HarmonyDB.Theory/HarmonyDB.Theory.Chords/Options/ChordTypeParsingOptions.cs +++ b/HarmonyDB.Theory/HarmonyDB.Theory.Chords/Options/ChordTypeParsingOptions.cs @@ -11,6 +11,7 @@ public class ChordTypeParsingOptions IgnoreTrailingSlashes = true, IgnoreTrailingStars = true, QuestionsParsingBehavior = QuestionsParsingBehavior.IgnoreAndTreatOnlyAsPower, + OnlyIntegersInParenthesesAreAddedDegrees = true, }; public bool TrimWhitespaceFragments { get; set; } @@ -22,4 +23,6 @@ public class ChordTypeParsingOptions public bool IgnoreTrailingStars { get; set; } public QuestionsParsingBehavior QuestionsParsingBehavior { get; set; } = QuestionsParsingBehavior.Error; + + public bool OnlyIntegersInParenthesesAreAddedDegrees { get; set; } } \ No newline at end of file diff --git a/HarmonyDB.Theory/HarmonyDB.Theory.Chords/Parsers/ChordParser.cs b/HarmonyDB.Theory/HarmonyDB.Theory.Chords/Parsers/ChordParser.cs index b191c4c1..e72f6a35 100644 --- a/HarmonyDB.Theory/HarmonyDB.Theory.Chords/Parsers/ChordParser.cs +++ b/HarmonyDB.Theory/HarmonyDB.Theory.Chords/Parsers/ChordParser.cs @@ -65,16 +65,19 @@ internal static (ChordType? chordType, ChordParseError? error, ChordTypeParseLog { var tokens = readonlyTokens.ToList(); - #region Removing stars, apostrophes, leading slashes, handling questions. No Star, Apostrophe, Question after this point. Possible middle slashes and separators. + void ReplaceWithASeparator(Func selector) + => ReplaceWithASeparator2(x => selector(x.token)); + void ReplaceWithASeparator2(Func<(ChordTypeToken token, bool fromParentheses, MatchAmbiguity matchAmbiguity), bool> selector) { - void ReplaceQuestionWithASeparator() - { - tokens = tokens - .Select(x => x.token.MeaninglessAddition == ChordTypeMeaninglessAddition.Question ? (new(ChordTypeMeaninglessAddition.FragmentSeparator), false, MatchAmbiguity.Safe) : x) - .ToList(); - } + tokens = tokens + .Select(x => selector(x) ? (new(ChordTypeMeaninglessAddition.FragmentSeparator), false, MatchAmbiguity.Safe) : x) + .ToList(); + } + + #region Removing stars, apostrophes, leading slashes, handling questions. No Star, Apostrophe, Question after this point. Possible middle slashes and separators. + { switch (options.QuestionsParsingBehavior) { case QuestionsParsingBehavior.IgnoreAndTreatOnlyAsPower: @@ -89,14 +92,14 @@ void ReplaceQuestionWithASeparator() } else { - ReplaceQuestionWithASeparator(); + ReplaceWithASeparator(x => x.MeaninglessAddition == ChordTypeMeaninglessAddition.Question); } break; } case QuestionsParsingBehavior.Ignore: - ReplaceQuestionWithASeparator(); + ReplaceWithASeparator(x => x.MeaninglessAddition == ChordTypeMeaninglessAddition.Question); break; } @@ -134,6 +137,78 @@ void ReplaceQuestionWithASeparator() #endregion + byte? resultFret = null; + #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); + else + { + 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, 3); + } + } + + 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, 4); + + allAdditions |= addition.Key; + } + } + + #endregion + + #region Extensions tokens unique of each extension type + + { + if (tokens.Where(x => x.token.Extension.HasValue).GroupBy(x => x.token.Extension!.Value) + .Any(g => g.Count() > 1)) + return (null, ChordParseError.EachExtensionTypeExpectedUnique, null, 5); + } + + #endregion + + #region Max one maj extension token + + { + if (tokens.Count(x => x.token.Extension >= ChordTypeExtension.XMaj7) > 1) + return (null, ChordParseError.MaxOneMajExtensionExpected, null, 6); + } + + #endregion + return (null, ChordParseError.NotImplemented, null, 255); } @@ -204,12 +279,10 @@ internal static (List<(string fragment, bool fromParentheses)>? fragments, Chord } else { - inputs = - [ - (match.Groups[1].Value, false), - (match.Groups[2].Value, true), - (match.Groups[3].Value, false), - ]; + inputs = match.Groups[2].Value.Split(',').Select(x => (x, true)) + .Prepend((match.Groups[1].Value, false)) + .Append((match.Groups[3].Value, false)) + .ToList(); } inputs = inputs.Where(x => x.input != string.Empty).ToList();