Skip to content

Commit

Permalink
chords parser started
Browse files Browse the repository at this point in the history
  • Loading branch information
pavel-zhur committed Oct 2, 2024
1 parent 8601a00 commit 97a18f8
Show file tree
Hide file tree
Showing 20 changed files with 551 additions and 0 deletions.
16 changes: 16 additions & 0 deletions HarmonyDB and OneShelf.sln
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OneShelf.Videos.Database",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OneShelf.Videos.BusinessLogic", "OneShelf.Videos\OneShelf.Videos.BusinessLogic\OneShelf.Videos.BusinessLogic.csproj", "{76A8BAAB-7065-45F8-82E8-E188D4C27EB3}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HarmonyDB.Theory", "HarmonyDB.Theory", "{57055665-0A26-4CEC-AEA3-3F0FE84847B7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HarmonyDB.Theory.Chords", "HarmonyDB.Theory\HarmonyDB.Theory.Chords\HarmonyDB.Theory.Chords.csproj", "{43C1E4C6-1258-49EB-ACE2-61D6F69E82FC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HarmonyDB.Theory.Chords.Tests", "HarmonyDB.Theory\HarmonyDB.Theory.Chords.Tests\HarmonyDB.Theory.Chords.Tests.csproj", "{E5BEE493-F0B6-40CA-98EF-9E94CE07023E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -527,6 +533,14 @@ Global
{76A8BAAB-7065-45F8-82E8-E188D4C27EB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{76A8BAAB-7065-45F8-82E8-E188D4C27EB3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{76A8BAAB-7065-45F8-82E8-E188D4C27EB3}.Release|Any CPU.Build.0 = Release|Any CPU
{43C1E4C6-1258-49EB-ACE2-61D6F69E82FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{43C1E4C6-1258-49EB-ACE2-61D6F69E82FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{43C1E4C6-1258-49EB-ACE2-61D6F69E82FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{43C1E4C6-1258-49EB-ACE2-61D6F69E82FC}.Release|Any CPU.Build.0 = Release|Any CPU
{E5BEE493-F0B6-40CA-98EF-9E94CE07023E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E5BEE493-F0B6-40CA-98EF-9E94CE07023E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E5BEE493-F0B6-40CA-98EF-9E94CE07023E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E5BEE493-F0B6-40CA-98EF-9E94CE07023E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -610,6 +624,8 @@ Global
{99134E7C-CE2F-4641-A9E3-B961F76307F0} = {5F839A7C-B540-4475-9FFE-9E5E4FC10D78}
{D702C117-2C9A-44E0-B428-30371616FBAE} = {5F839A7C-B540-4475-9FFE-9E5E4FC10D78}
{76A8BAAB-7065-45F8-82E8-E188D4C27EB3} = {5F839A7C-B540-4475-9FFE-9E5E4FC10D78}
{43C1E4C6-1258-49EB-ACE2-61D6F69E82FC} = {57055665-0A26-4CEC-AEA3-3F0FE84847B7}
{E5BEE493-F0B6-40CA-98EF-9E94CE07023E} = {57055665-0A26-4CEC-AEA3-3F0FE84847B7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {59093261-FDDA-411A-852D-EA21AEF83E07}
Expand Down
30 changes: 30 additions & 0 deletions HarmonyDB.Theory/HarmonyDB.Theory.Chords.Tests/AllChordsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using HarmonyDB.Theory.Chords.Options;
using HarmonyDB.Theory.Chords.Parsers;
using Xunit.Abstractions;

namespace HarmonyDB.Theory.Chords.Tests;

public class AllChordsTests(ITestOutputHelper testOutputHelper)
{
[Fact]
public void LittleChordsFailed()
{
var success = 0;
var failure = 0;
foreach (var (chord, count) in Resources.AllChords)
{
try
{
ChordParser.Parse(chord, ChordParsingOptions.MostForgiving);
success += count;
}
catch
{
failure += count;
testOutputHelper.WriteLine($"{count}\t{chord}");
}
}

testOutputHelper.WriteLine($"Success: {success}, failure: {failure}.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<None Remove="dataset.json" />
<None Remove="Resources\AllChords.json" />
<None Remove="Resources\AllChords.json.gz" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Include="Resources\AllChords.json.gz" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.5.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\HarmonyDB.Theory.Chords\HarmonyDB.Theory.Chords.csproj" />
</ItemGroup>

<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>

</Project>
19 changes: 19 additions & 0 deletions HarmonyDB.Theory/HarmonyDB.Theory.Chords.Tests/Resources.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.IO.Compression;
using System.Reflection;
using System.Text.Json;

namespace HarmonyDB.Theory.Chords.Tests;

public static class Resources
{
private const string AllChordsResourceName = "HarmonyDB.Theory.Chords.Tests.Resources.AllChords.json.gz";

static Resources()
{
using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(AllChordsResourceName)!;
using var gzip = new GZipStream(stream, CompressionMode.Decompress);
AllChords = JsonSerializer.Deserialize<Dictionary<string, int>>(gzip)!;
}

public static IReadOnlyDictionary<string, int> AllChords { get; }
}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackageReadmeFile>readme.md</PackageReadmeFile>
</PropertyGroup>

<ItemGroup>
<None Include="..\..\nuget readme.md" Pack="true" Link="readme.md" PackagePath="\readme.md" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\OneShelf.Common\OneShelf.Common\OneShelf.Common.csproj" />
</ItemGroup>

</Project>
7 changes: 7 additions & 0 deletions HarmonyDB.Theory/HarmonyDB.Theory.Chords/Models/Chord.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace HarmonyDB.Theory.Chords.Models;

public record Chord(NoteRepresentation RootRepresentation, NoteRepresentation? BassRepresentation, string ChordType)
{
public Note Root = RootRepresentation.Note;
public Note? Bass = BassRepresentation?.Note;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using HarmonyDB.Theory.Chords.Models.Enums;

namespace HarmonyDB.Theory.Chords.Models;

public record ChordParseResult
{
public ChordParseResult(ChordParseResultType resultType)
{
if (resultType == ChordParseResultType.Success)
throw new ArgumentOutOfRangeException(nameof(resultType), resultType, "Use a different constructor.");

ResultType = resultType;
}

public ChordParseResult(Chord chord)
{
ResultType = ChordParseResultType.Success;
Chord = chord;
}

public ChordParseResultType ResultType { get; }
public Chord? Chord { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace HarmonyDB.Theory.Chords.Models.Enums;

public enum Alteration : byte
{
Flat = 0,
Sharp = 1,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace HarmonyDB.Theory.Chords.Models.Enums;

public enum ChordParseResultType
{
SpecialNoChord,
Success,
}
19 changes: 19 additions & 0 deletions HarmonyDB.Theory/HarmonyDB.Theory.Chords/Models/Enums/HHandling.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace HarmonyDB.Theory.Chords.Models.Enums;

public enum HHandling
{
/// <summary>
/// English variant. B = H = Si = A##. A# = Bb.
/// </summary>
BMeansH,

/// <summary>
/// German variant. B = A#. H = B# = Si.
/// </summary>
BbMeansH,

/// <summary>
/// Strict English variant. H is prohibited.
/// </summary>
HProhibited,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace HarmonyDB.Theory.Chords.Models.Enums;

public enum NaturalNote : byte
{
A = 0,
B = 2,
C = 3,
D = 5,
E = 7,
F = 8,
G = 10,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace HarmonyDB.Theory.Chords.Models.Enums;

public enum NaturalNoteRepresentation : sbyte
{
A = 0,
B = 2,
C = 3,
D = 5,
E = 7,
F = 8,
G = 10,

H = -1,
GermanB = -2,

La = -52,
Si = -51,
Do = -50,
Re = -49,
Mi = -48,
Fa = -47,
Sol = -46,
}
19 changes: 19 additions & 0 deletions HarmonyDB.Theory/HarmonyDB.Theory.Chords/Models/Note.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace HarmonyDB.Theory.Chords.Models;

public readonly record struct Note
{
public Note(byte value)
{
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
if (value < NoteConstants.MinNoteValue || value > NoteConstants.MaxNoteValue)
throw new ArgumentOutOfRangeException(nameof(value), value, $"A value between {NoteConstants.MinNoteValue} and {NoteConstants.MaxNoteValue} (inclusive) is expected.");

Value = value;
}

public byte Value { get; }

public static Note Normalized(int value) => new(Normalize(value));

public static byte Normalize(int notes) => (byte)((notes % NoteConstants.TotalNotes + NoteConstants.TotalNotes) % NoteConstants.TotalNotes);
}
62 changes: 62 additions & 0 deletions HarmonyDB.Theory/HarmonyDB.Theory.Chords/Models/NoteConstants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using HarmonyDB.Theory.Chords.Models.Enums;

namespace HarmonyDB.Theory.Chords.Models;

public static class NoteConstants
{
public const byte MinNoteValue = 0;
public const byte MaxNoteValue = 11;

public static readonly Note MinNote = new(MinNoteValue);
public static readonly Note MaxNote = new(MaxNoteValue);

public static readonly Note GermanBNote = new(1);

public const byte TotalNotes = 12;

public const char MinNaturalNoteChar = 'A';
public const char MaxNaturalNoteChar = 'G';

public const char HSynonymChar = 'H';
public const NaturalNote HSynonym = NaturalNote.B;

public const char FlatSymbol = 'b';
public const char SharpSymbol = '#';

public const char FlatUnicodeSymbol = '♭';
public const char SharpUnicodeSymbol = '♯';

public const string NoChord = "N.C.";

public static readonly IReadOnlyList<string> NoChordVariants =
[
NoChord,
"N.C",
"NC",
"N.С.", // russian C
];

public static readonly IReadOnlyList<char> FlatSymbols = new List<char> { FlatSymbol, FlatUnicodeSymbol };
public static readonly IReadOnlyList<char> SharpSymbols = new List<char> { SharpSymbol, SharpUnicodeSymbol };

public static IReadOnlyList<char> NaturalNoteChars =
[
'A', 'B', 'C', 'D', 'E', 'F', 'G',
];

public static IReadOnlyList<string> NaturalNoteSolfegeNames =
[
"LA", "SI", "DO", "RE", "MI", "FA", "SOL",
];

public static IReadOnlyList<NaturalNote> NaturalNotes =
[
NaturalNote.A,
NaturalNote.B,
NaturalNote.C,
NaturalNote.D,
NaturalNote.E,
NaturalNote.F,
NaturalNote.G,
];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using HarmonyDB.Theory.Chords.Models.Enums;

namespace HarmonyDB.Theory.Chords.Models;

public record NoteRepresentation(NaturalNoteRepresentation NaturalNoteRepresentation, int Sharps, int Flats)
{
public Note Note => Note.Normalized((NaturalNoteRepresentation switch
{
>= 0 => (byte)NaturalNoteRepresentation,
NaturalNoteRepresentation.GermanB => NoteConstants.GermanBNote.Value,
NaturalNoteRepresentation.H => (byte)NoteConstants.HSynonym,
>= NaturalNoteRepresentation.La and <= NaturalNoteRepresentation.Sol => NaturalNoteRepresentation - NaturalNoteRepresentation.La,
_ => throw new ArgumentOutOfRangeException(nameof(NaturalNoteRepresentation), NaturalNoteRepresentation, "The representation is out of range."),
}) + Sharps - Flats);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace HarmonyDB.Theory.Chords.Options;

public class ChordParsingOptions
{
public static readonly ChordParsingOptions Default = new();

public static readonly ChordParsingOptions MostForgiving = new()
{
ForgiveSameBass = true,
ForgiveEdgeWhitespaces = true,
NoteParsingOptions = NoteParsingOptions.MostForgiving,
ForgiveRoundBraces = true,
};

public bool ForgiveSameBass { get; set; }
public bool ForgiveEdgeWhitespaces { get; set; }
public NoteParsingOptions NoteParsingOptions { get; set; } = NoteParsingOptions.Default;
public bool ForgiveRoundBraces { get; set; }
}
Loading

0 comments on commit 97a18f8

Please sign in to comment.