From ec9ae9ffe0fc09fc2e907c028c487a354643b81e Mon Sep 17 00:00:00 2001 From: Davidson Bruno Date: Wed, 30 Aug 2023 15:29:57 -0300 Subject: [PATCH 1/5] chore: upgraded .Net version to 7 --- SparksMusic.Library/SparksMusic.Library.csproj | 6 +++++- SparksMusic.Test/SparksMusic.Test.csproj | 16 +++++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/SparksMusic.Library/SparksMusic.Library.csproj b/SparksMusic.Library/SparksMusic.Library.csproj index 4f66c90..ee3ca27 100644 --- a/SparksMusic.Library/SparksMusic.Library.csproj +++ b/SparksMusic.Library/SparksMusic.Library.csproj @@ -1,11 +1,15 @@ - net5.0 + net7.0 C:\davidson\lab\proj\transposer\SparksMusic.Library\SparksMusic.Library.xml + latest + + latest + diff --git a/SparksMusic.Test/SparksMusic.Test.csproj b/SparksMusic.Test/SparksMusic.Test.csproj index 335ee8c..4e8f6b7 100644 --- a/SparksMusic.Test/SparksMusic.Test.csproj +++ b/SparksMusic.Test/SparksMusic.Test.csproj @@ -1,19 +1,25 @@ - net5.0 + net7.0 false + + latest + + + latest + - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all From ef91c87460d34e35e1a2ee997135173592202701 Mon Sep 17 00:00:00 2001 From: Davidson Bruno Date: Wed, 30 Aug 2023 15:31:14 -0300 Subject: [PATCH 2/5] refactor: changed namespace to short syntax --- SparksMusic.Library/Chord.cs | 371 +++++---- SparksMusic.Library/Note.cs | 133 ++- SparksMusic.Library/Transposer.cs | 1253 ++++++++++++++--------------- 3 files changed, 877 insertions(+), 880 deletions(-) diff --git a/SparksMusic.Library/Chord.cs b/SparksMusic.Library/Chord.cs index b1d0448..9bf0087 100644 --- a/SparksMusic.Library/Chord.cs +++ b/SparksMusic.Library/Chord.cs @@ -2,217 +2,216 @@ using System; using System.Text.RegularExpressions; -namespace SparksMusic.Library +namespace SparksMusic.Library; + +/// +/// Chord class +/// +public class Chord : IEquatable { /// - /// Chord class + /// Chord note + /// + public Note Note { get; } + + /// + /// Chord tonality + /// + public Tonality Tonality { get; } + + /// + /// Chord complement + /// + public string Complement { get; } + + /// + /// Chord inversion + /// + public Note Inversion { get; } + + /// + /// True if chord is flat + /// + public bool IsFlat { get => Note.IsFlat; } + + /// + /// True if chord is double flat + /// + public bool IsDoubleFlat { get => Note.IsDoubleFlat; } + + /// + /// True if chord is flat or double flat + /// + public bool IsFlatOrDoubleFlat { get => IsFlat || IsDoubleFlat; } + + /// + /// True if chord is sharp + /// + public bool IsSharp { get => Note.IsSharp; } + + /// + /// True if chord is double sharp + /// + public bool IsDoubleSharp { get => Note.IsDoubleSharp; } + + /// + /// True if chord is sharp or double sharp + /// + public bool IsSharpOrDoubleSharp { get => IsSharp || IsDoubleSharp; } + + /// + /// Creates a chord object from a string. /// - public class Chord : IEquatable + /// The chord + /// Thrown when chord parameter is null. + /// Thrown when chord parameter is not a valid chord. + public Chord(string chord) { - /// - /// Chord note - /// - public Note Note { get; } - - /// - /// Chord tonality - /// - public Tonality Tonality { get; } - - /// - /// Chord complement - /// - public string Complement { get; } - - /// - /// Chord inversion - /// - public Note Inversion { get; } - - /// - /// True if chord is flat - /// - public bool IsFlat { get => Note.IsFlat; } - - /// - /// True if chord is double flat - /// - public bool IsDoubleFlat { get => Note.IsDoubleFlat; } - - /// - /// True if chord is flat or double flat - /// - public bool IsFlatOrDoubleFlat { get => IsFlat || IsDoubleFlat; } - - /// - /// True if chord is sharp - /// - public bool IsSharp { get => Note.IsSharp; } - - /// - /// True if chord is double sharp - /// - public bool IsDoubleSharp { get => Note.IsDoubleSharp; } - - /// - /// True if chord is sharp or double sharp - /// - public bool IsSharpOrDoubleSharp { get => IsSharp || IsDoubleSharp; } - - /// - /// Creates a chord object from a string. - /// - /// The chord - /// Thrown when chord parameter is null. - /// Thrown when chord parameter is not a valid chord. - public Chord(string chord) + if (chord is null) { - if (chord is null) - { - throw new ArgumentNullException(nameof(chord)); - } - - chord = chord.Trim(); - - var regex = new Regex(GetChordPattern(), RegexOptions.Compiled); - var match = regex.Match(chord); - - if (!match.Success || match.Value != chord) - throw new NotAChordException("The string provided does not match a valid chord"); - - const int noteLetterGroup = 1; - const int accidentGroup = 2; - const int diminutiveOrAugmentedGroup = 3; - const int tonalityGroup = 4; - const int firstComplementGroup = 5; - const int secondComplementGroup = 6; - const int inversionLetterGroup = 11; - const int inversionAccidentGroup = 12; - - Note = GetNote(match.Groups[noteLetterGroup].Value, match.Groups[accidentGroup].Value); - Tonality = GetTonality(match.Groups[diminutiveOrAugmentedGroup].Value, match.Groups[tonalityGroup].Value); - Complement = GetComplement(Tonality, $"{match.Groups[firstComplementGroup].Value}{match.Groups[secondComplementGroup].Value}"); - Inversion = GetInversion(match.Groups[inversionLetterGroup].Value, match.Groups[inversionAccidentGroup].Value); + throw new ArgumentNullException(nameof(chord)); } - /// - /// Creates a chord object. - /// - /// The chord note - /// The chord tonality - /// The chord complement - /// The chord inversion - public Chord(Note note, Tonality tonality = Tonality.Major, string complement = "", Note inversion = null) - { - Note = note; - Tonality = tonality; - Complement = complement; - Inversion = inversion; - } + chord = chord.Trim(); - /// - /// Compare two chords. - /// - /// The other chord - /// True if both have same name. - public bool Equals(Chord other) - { - return other != null && ToString().Equals(other.ToString()); - } + var regex = new Regex(GetChordPattern(), RegexOptions.Compiled); + var match = regex.Match(chord); - /// - /// Transposes up chord a semitone. - /// - /// The chord - /// The transposed chord - public static Chord operator ++(Chord chord) - { - return Transposer.TransposeUp(chord, 1); - } + if (!match.Success || match.Value != chord) + throw new NotAChordException("The string provided does not match a valid chord"); - /// - /// Transposes down chord a semitone. - /// - /// The chord - /// The transposed chord - public static Chord operator --(Chord chord) - { - return Transposer.TransposeDown(chord, 1); - } + const int noteLetterGroup = 1; + const int accidentGroup = 2; + const int diminutiveOrAugmentedGroup = 3; + const int tonalityGroup = 4; + const int firstComplementGroup = 5; + const int secondComplementGroup = 6; + const int inversionLetterGroup = 11; + const int inversionAccidentGroup = 12; - public override string ToString() - { - string inversionString = Inversion != null ? $"/{Inversion}" : ""; - return $"{Note}{Tonality.GetDescription()}{Complement}{inversionString}"; - } + Note = GetNote(match.Groups[noteLetterGroup].Value, match.Groups[accidentGroup].Value); + Tonality = GetTonality(match.Groups[diminutiveOrAugmentedGroup].Value, match.Groups[tonalityGroup].Value); + Complement = GetComplement(Tonality, $"{match.Groups[firstComplementGroup].Value}{match.Groups[secondComplementGroup].Value}"); + Inversion = GetInversion(match.Groups[inversionLetterGroup].Value, match.Groups[inversionAccidentGroup].Value); + } - public override bool Equals(object obj) - { - return base.Equals(obj as Chord); - } + /// + /// Creates a chord object. + /// + /// The chord note + /// The chord tonality + /// The chord complement + /// The chord inversion + public Chord(Note note, Tonality tonality = Tonality.Major, string complement = "", Note inversion = null) + { + Note = note; + Tonality = tonality; + Complement = complement; + Inversion = inversion; + } - public override int GetHashCode() - { - return HashCode.Combine(Note, Tonality, Complement, Inversion); - } + /// + /// Compare two chords. + /// + /// The other chord + /// True if both have same name. + public bool Equals(Chord other) + { + return other != null && ToString().Equals(other.ToString()); + } - private static string GetChordPattern() - { - const string noteLetter = @"([A-G]{1})"; - const string accident = @"(##?|bb?)?"; - const string tonality = @"(m|sus2|sus4)?"; - const string interval = @"(2|4|6|7M|7|9|11|13)?"; - const string incrementInterval = @"(b2|2|4|4#|b5|5|#5|6|7|7M|b9|9|11|#11|13)"; + /// + /// Transposes up chord a semitone. + /// + /// The chord + /// The transposed chord + public static Chord operator ++(Chord chord) + { + return Transposer.TransposeUp(chord, 1); + } - string note = $@"{noteLetter}{accident}"; - string complement = $@"(\+|\u00B0|{tonality}{interval}(\({incrementInterval}(,{incrementInterval})*\))?)"; - string inversion = $@"(\/{noteLetter}{accident})?"; + /// + /// Transposes down chord a semitone. + /// + /// The chord + /// The transposed chord + public static Chord operator --(Chord chord) + { + return Transposer.TransposeDown(chord, 1); + } - return $"{note}{complement}{inversion}"; - } + public override string ToString() + { + string inversionString = Inversion != null ? $"/{Inversion}" : ""; + return $"{Note}{Tonality.GetDescription()}{Complement}{inversionString}"; + } + + public override bool Equals(object obj) + { + return base.Equals(obj as Chord); + } - private static Note GetNote(string noteLetterValue, string accidentValue) + public override int GetHashCode() + { + return HashCode.Combine(Note, Tonality, Complement, Inversion); + } + + private static string GetChordPattern() + { + const string noteLetter = @"([A-G]{1})"; + const string accident = @"(##?|bb?)?"; + const string tonality = @"(m|sus2|sus4)?"; + const string interval = @"(2|4|6|7M|7|9|11|13)?"; + const string incrementInterval = @"(b2|2|4|4#|b5|5|#5|6|7|7M|b9|9|11|#11|13)"; + + string note = $@"{noteLetter}{accident}"; + string complement = $@"(\+|\u00B0|{tonality}{interval}(\({incrementInterval}(,{incrementInterval})*\))?)"; + string inversion = $@"(\/{noteLetter}{accident})?"; + + return $"{note}{complement}{inversion}"; + } + + private static Note GetNote(string noteLetterValue, string accidentValue) + { + NoteLetter noteLetter = EnumUtils.Parse(noteLetterValue); + Accident accident = EnumUtils.Parse(accidentValue); + return new Note(noteLetter, accident); + } + + private static Tonality GetTonality(string diminutiveOrAugmentedValue, string tonality) + { + if (diminutiveOrAugmentedValue == Tonality.Augmented.GetDescription()) { - NoteLetter noteLetter = EnumUtils.Parse(noteLetterValue); - Accident accident = EnumUtils.Parse(accidentValue); - return new Note(noteLetter, accident); + return Tonality.Augmented; } - - private static Tonality GetTonality(string diminutiveOrAugmentedValue, string tonality) + else if (diminutiveOrAugmentedValue == Tonality.Diminished.GetDescription()) { - if (diminutiveOrAugmentedValue == Tonality.Augmented.GetDescription()) - { - return Tonality.Augmented; - } - else if (diminutiveOrAugmentedValue == Tonality.Diminished.GetDescription()) - { - return Tonality.Diminished; - } - else if (diminutiveOrAugmentedValue == Tonality.HalfDiminished.GetDescription()) - { - return Tonality.HalfDiminished; - } - else - { - return EnumUtils.Parse(tonality); - } + return Tonality.Diminished; } - - private static string GetComplement(Tonality tonality, string text) + else if (diminutiveOrAugmentedValue == Tonality.HalfDiminished.GetDescription()) + { + return Tonality.HalfDiminished; + } + else { - return tonality == Tonality.Augmented || tonality == Tonality.Diminished || tonality == Tonality.HalfDiminished ? "" : text; + return EnumUtils.Parse(tonality); } + } - private static Note GetInversion(string noteLetterValue, string accidentValue) + private static string GetComplement(Tonality tonality, string text) + { + return tonality == Tonality.Augmented || tonality == Tonality.Diminished || tonality == Tonality.HalfDiminished ? "" : text; + } + + private static Note GetInversion(string noteLetterValue, string accidentValue) + { + try + { + return GetNote(noteLetterValue, accidentValue); + } + catch (Exception) { - try - { - return GetNote(noteLetterValue, accidentValue); - } - catch (Exception) - { - return null; - } + return null; } } } diff --git a/SparksMusic.Library/Note.cs b/SparksMusic.Library/Note.cs index 854fe7d..df20429 100644 --- a/SparksMusic.Library/Note.cs +++ b/SparksMusic.Library/Note.cs @@ -1,86 +1,85 @@ using System; -namespace SparksMusic.Library +namespace SparksMusic.Library; + +/// +/// Note class +/// +public class Note : IEquatable { /// - /// Note class + /// Note letter /// - public class Note : IEquatable - { - /// - /// Note letter - /// - public NoteLetter NoteLetter { get; } + public NoteLetter NoteLetter { get; } - /// - /// Note accident - /// - public Accident Accident { get; } + /// + /// Note accident + /// + public Accident Accident { get; } - /// - /// True if chord is flat - /// - public bool IsFlat { get => Accident == Accident.Flat; } + /// + /// True if chord is flat + /// + public bool IsFlat { get => Accident == Accident.Flat; } - /// - /// True if chord is double flat - /// - public bool IsDoubleFlat { get => Accident == Accident.DoubleFlat; } + /// + /// True if chord is double flat + /// + public bool IsDoubleFlat { get => Accident == Accident.DoubleFlat; } - /// - /// True if chord is flat or double flat - /// - public bool IsFlatOrDoubleFlat { get => IsFlat || IsDoubleFlat; } + /// + /// True if chord is flat or double flat + /// + public bool IsFlatOrDoubleFlat { get => IsFlat || IsDoubleFlat; } - /// - /// True if chord is sharp - /// - public bool IsSharp { get => Accident == Accident.Sharp; } + /// + /// True if chord is sharp + /// + public bool IsSharp { get => Accident == Accident.Sharp; } - /// - /// True if chord is double sharp - /// - public bool IsDoubleSharp { get => Accident == Accident.DoubleSharp; } + /// + /// True if chord is double sharp + /// + public bool IsDoubleSharp { get => Accident == Accident.DoubleSharp; } - /// - /// True if chord is sharp or double sharp - /// - public bool IsSharpOrDoubleSharp { get => IsSharp || IsDoubleSharp; } + /// + /// True if chord is sharp or double sharp + /// + public bool IsSharpOrDoubleSharp { get => IsSharp || IsDoubleSharp; } - /// - /// Creates a note object. - /// - /// The note letter (A, B, C, D, E, F or G) - /// The note accident - public Note(NoteLetter noteLetter, Accident accident = Accident.None) - { - NoteLetter = noteLetter; - Accident = accident; - } + /// + /// Creates a note object. + /// + /// The note letter (A, B, C, D, E, F or G) + /// The note accident + public Note(NoteLetter noteLetter, Accident accident = Accident.None) + { + NoteLetter = noteLetter; + Accident = accident; + } - /// - /// Compare two notes. - /// - /// The other note - /// True if both have the same name. - public bool Equals(Note other) - { - return other != null && ToString().Equals(other.ToString()); - } + /// + /// Compare two notes. + /// + /// The other note + /// True if both have the same name. + public bool Equals(Note other) + { + return other != null && ToString().Equals(other.ToString()); + } - public override string ToString() - { - return $"{NoteLetter}{Accident.GetDescription()}"; - } + public override string ToString() + { + return $"{NoteLetter}{Accident.GetDescription()}"; + } - public override bool Equals(object obj) - { - return base.Equals(obj as Note); - } + public override bool Equals(object obj) + { + return base.Equals(obj as Note); + } - public override int GetHashCode() - { - return HashCode.Combine(NoteLetter, Accident); - } + public override int GetHashCode() + { + return HashCode.Combine(NoteLetter, Accident); } } diff --git a/SparksMusic.Library/Transposer.cs b/SparksMusic.Library/Transposer.cs index 73d1728..d93540e 100644 --- a/SparksMusic.Library/Transposer.cs +++ b/SparksMusic.Library/Transposer.cs @@ -2,761 +2,760 @@ using System.Collections.Generic; using System.Linq; -namespace SparksMusic.Library +namespace SparksMusic.Library; + +/// +/// Transposer class +/// +public static class Transposer { + private static readonly Node _flatMap = BuildFlatMap(); + private static readonly Node _sharpMap = BuildSharpMap(); + private static readonly Dictionary _chromaticCorrespondentDictionary = BuildChromaticCorrespondentDictionary(); + private static readonly object _lock = new(); + + private const int SemitonesOnTheScale = 12; + + #region Public Methods /// - /// Transposer class + /// Transposes up a chord. /// - public static class Transposer + /// The chord + /// The semitones to the transposition + /// A transposed chord. + /// Thrown when chord object is null. + /// Thrown when input is not a valid chord. + /// Thrown when semitones parameter is a negative number. + public static Chord TransposeUp(string chord, int semitones) { - private static readonly Node _flatMap = BuildFlatMap(); - private static readonly Node _sharpMap = BuildSharpMap(); - private static readonly Dictionary _chromaticCorrespondentDictionary = BuildChromaticCorrespondentDictionary(); - private static readonly object _lock = new object(); - - private const int SemitonesOnTheScale = 12; - - #region Public Methods - /// - /// Transposes up a chord. - /// - /// The chord - /// The semitones to the transposition - /// A transposed chord. - /// Thrown when chord object is null. - /// Thrown when input is not a valid chord. - /// Thrown when semitones parameter is a negative number. - public static Chord TransposeUp(string chord, int semitones) - { - return TransposeUp(new Chord(chord), semitones); - } + return TransposeUp(new Chord(chord), semitones); + } - /// - /// Transposes up a chord. - /// - /// The chord - /// The semitones to the transposition - /// A transposed chord. - /// Thrown when chord object is null. - /// Thrown when semitones parameter is a negative number. - public static Chord TransposeUp(Chord chord, int semitones) - { - return Transpose(chord, semitones, TransposeUp); - } + /// + /// Transposes up a chord. + /// + /// The chord + /// The semitones to the transposition + /// A transposed chord. + /// Thrown when chord object is null. + /// Thrown when semitones parameter is a negative number. + public static Chord TransposeUp(Chord chord, int semitones) + { + return Transpose(chord, semitones, TransposeUp); + } - /// - /// Transposes up a note. - /// - /// The note - /// A transposed note. - /// - public static Note TransposeUp(Note note, int semitones) - { - var map = GetCorrectMap(_sharpMap, note); - var initialChordNode = FindHeadNodeFromNote(map, note); + /// + /// Transposes up a note. + /// + /// The note + /// A transposed note. + /// + public static Note TransposeUp(Note note, int semitones) + { + var map = GetCorrectMap(_sharpMap, note); + var initialChordNode = FindHeadNodeFromNote(map, note); - while (semitones > 0) + while (semitones > 0) + { + if (initialChordNode.Right != null) { - if (initialChordNode.Right != null) - { - initialChordNode = initialChordNode.Right; - semitones--; - } - else - { - initialChordNode = initialChordNode.Down; - } + initialChordNode = initialChordNode.Right; + semitones--; } - - if (initialChordNode.Note.IsSharpOrDoubleSharp) + else { - if (initialChordNode.Down != null) - { - initialChordNode = initialChordNode.Down; - } + initialChordNode = initialChordNode.Down; } - - return initialChordNode.Note; } - /// - /// Transposes up a list of chords. - /// - /// The chord list - /// The semitones to the transposition - /// A transposed chord list - /// Thrown when chords object is null. - /// Thrown when semitones parameter is a negative number. - public static List TransposeUp(List chords, int semitones) + if (initialChordNode.Note.IsSharpOrDoubleSharp) { - if (chords is null) - { - throw new ArgumentNullException(nameof(chords)); - } - - var transposedChords = new List(); - - foreach (var chord in chords) + if (initialChordNode.Down != null) { - transposedChords.Add(TransposeUp(chord, semitones)); + initialChordNode = initialChordNode.Down; } - - return transposedChords; } - /// - /// Transposes down a chord. - /// - /// The chord - /// The semitones to the transposition - /// A transposed chord. - /// Thrown when chord object is null. - /// Thrown when input is not a valid chord. - /// Thrown when semitones parameter is a negative number. - public static Chord TransposeDown(string chord, int semitones) + return initialChordNode.Note; + } + + /// + /// Transposes up a list of chords. + /// + /// The chord list + /// The semitones to the transposition + /// A transposed chord list + /// Thrown when chords object is null. + /// Thrown when semitones parameter is a negative number. + public static List TransposeUp(List chords, int semitones) + { + if (chords is null) { - return TransposeDown(new Chord(chord), semitones); + throw new ArgumentNullException(nameof(chords)); } - /// - /// Transposes down a chord. - /// - /// The chord - /// The semitones to the transposition - /// A transposed chord. - /// Thrown when chords object is null. - /// Thrown when semitones parameter is a negative number. - public static Chord TransposeDown(Chord chord, int semitones) + var transposedChords = new List(); + + foreach (var chord in chords) { - return Transpose(chord, semitones, TransposeDown); + transposedChords.Add(TransposeUp(chord, semitones)); } - /// - /// Transposes down a note. - /// - /// The note - /// The semitones - /// A transposed note. - public static Note TransposeDown(Note note, int semitones) - { - var map = GetCorrectMap(_flatMap, note); - var initialChordNode = FindHeadNodeFromNote(map, note); + return transposedChords; + } + + /// + /// Transposes down a chord. + /// + /// The chord + /// The semitones to the transposition + /// A transposed chord. + /// Thrown when chord object is null. + /// Thrown when input is not a valid chord. + /// Thrown when semitones parameter is a negative number. + public static Chord TransposeDown(string chord, int semitones) + { + return TransposeDown(new Chord(chord), semitones); + } - while (semitones > 0) + /// + /// Transposes down a chord. + /// + /// The chord + /// The semitones to the transposition + /// A transposed chord. + /// Thrown when chords object is null. + /// Thrown when semitones parameter is a negative number. + public static Chord TransposeDown(Chord chord, int semitones) + { + return Transpose(chord, semitones, TransposeDown); + } + + /// + /// Transposes down a note. + /// + /// The note + /// The semitones + /// A transposed note. + public static Note TransposeDown(Note note, int semitones) + { + var map = GetCorrectMap(_flatMap, note); + var initialChordNode = FindHeadNodeFromNote(map, note); + + while (semitones > 0) + { + if (initialChordNode.Left != null) { - if (initialChordNode.Left != null) - { - initialChordNode = initialChordNode.Left; - semitones--; - } - else - { - initialChordNode = initialChordNode.Up; - } + initialChordNode = initialChordNode.Left; + semitones--; } - - if (initialChordNode.Note.IsFlatOrDoubleFlat) + else { - if (initialChordNode.Up != null) - { - initialChordNode = initialChordNode.Up; - } + initialChordNode = initialChordNode.Up; } - - return initialChordNode.Note; } - /// - /// Transposes down a list of chords. - /// - /// The chord list - /// The semitones to the transposition - /// A transposed chord list. - /// Thrown when chords object is null. - /// Thrown when semitones parameter is a negative number. - public static List TransposeDown(List chords, int semitones) + if (initialChordNode.Note.IsFlatOrDoubleFlat) { - if (chords is null) + if (initialChordNode.Up != null) { - throw new ArgumentNullException(nameof(chords)); + initialChordNode = initialChordNode.Up; } + } - var transposedChords = new List(); - - foreach (var chord in chords) - { - transposedChords.Add(TransposeDown(chord, semitones)); - } + return initialChordNode.Note; + } - return transposedChords; + /// + /// Transposes down a list of chords. + /// + /// The chord list + /// The semitones to the transposition + /// A transposed chord list. + /// Thrown when chords object is null. + /// Thrown when semitones parameter is a negative number. + public static List TransposeDown(List chords, int semitones) + { + if (chords is null) + { + throw new ArgumentNullException(nameof(chords)); } - /// - /// Extract the chords from a input text. - /// - /// The text - /// A list of chords. - public static List ExtractChords(string text) + var transposedChords = new List(); + + foreach (var chord in chords) { - var words = text.Split(' ').ToList(); - words = words.Where(word => word != "").ToList(); - return GetValidChords(words.ToList()); + transposedChords.Add(TransposeDown(chord, semitones)); } - /// - /// Get valid chord from a chords string list. - /// - /// The chords - /// A list of valid chords. - public static List GetValidChords(List chords) - { - var validChords = new List(); + return transposedChords; + } + + /// + /// Extract the chords from a input text. + /// + /// The text + /// A list of chords. + public static List ExtractChords(string text) + { + var words = text.Split(' ').ToList(); + words = words.Where(word => word != "").ToList(); + return GetValidChords(words.ToList()); + } + + /// + /// Get valid chord from a chords string list. + /// + /// The chords + /// A list of valid chords. + public static List GetValidChords(List chords) + { + var validChords = new List(); - foreach (var chord in chords) + foreach (var chord in chords) + { + if (IsChord(chord)) { - if (IsChord(chord)) - { - validChords.Add(new Chord(chord)); - } + validChords.Add(new Chord(chord)); } - - return validChords; } - /// - /// Get the number of semitones from a chord to another. - /// - /// The origin chord - /// The destiny chord - /// The number of semitones from a chord to another. - public static int GetSemitones(Note from, Note to) - { - var (polarizedOrigin, polarizedDestiny) = MatchChromaticPole(from, to); + return validChords; + } - var map = GetCorrectMap(_sharpMap, polarizedOrigin); - map = GetCorrectMap(map, polarizedDestiny); + /// + /// Get the number of semitones from a chord to another. + /// + /// The origin chord + /// The destiny chord + /// The number of semitones from a chord to another. + public static int GetSemitones(Note from, Note to) + { + var (polarizedOrigin, polarizedDestiny) = MatchChromaticPole(from, to); + + var map = GetCorrectMap(_sharpMap, polarizedOrigin); + map = GetCorrectMap(map, polarizedDestiny); - var headNode = FindHeadNodeFromNote(map, polarizedOrigin); - int semitones = 0; + var headNode = FindHeadNodeFromNote(map, polarizedOrigin); + int semitones = 0; - while (headNode.Note.ToString() != polarizedDestiny.ToString()) + while (headNode.Note.ToString() != polarizedDestiny.ToString()) + { + if (headNode.Right != null) { - if (headNode.Right != null) - { - headNode = headNode.Right; - semitones++; - } - else - { - headNode = headNode.Down; - } + headNode = headNode.Right; + semitones++; + } + else + { + headNode = headNode.Down; } - - return semitones; } - /// - /// Check if two chords have different chromatic poles. - /// - /// The first chord - /// The second chord - /// True if both have different chromatic poles. - public static bool HasDifferentChromaticPole(Note note1, Note note2) + return semitones; + } + + /// + /// Check if two chords have different chromatic poles. + /// + /// The first chord + /// The second chord + /// True if both have different chromatic poles. + public static bool HasDifferentChromaticPole(Note note1, Note note2) + { + return (note1.IsFlatOrDoubleFlat && note2.IsSharpOrDoubleSharp) || (note1.IsSharpOrDoubleSharp && note2.IsFlatOrDoubleFlat); + } + + /// + /// Get the chromatic correspondent note (if it is flat, the correspondent will be sharp and vice versa). + /// + /// The note + /// The chromatic correspondent note. + /// Thrown when note object is null. + public static Note GetChromaticCorrespondent(Note note) + { + if (note is null) { - return (note1.IsFlatOrDoubleFlat && note2.IsSharpOrDoubleSharp) || (note1.IsSharpOrDoubleSharp && note2.IsFlatOrDoubleFlat); + throw new ArgumentNullException(nameof(note)); } - /// - /// Get the chromatic correspondent note (if it is flat, the correspondent will be sharp and vice versa). - /// - /// The note - /// The chromatic correspondent note. - /// Thrown when note object is null. - public static Note GetChromaticCorrespondent(Note note) + if (note.Accident == Accident.None) { - if (note is null) - { - throw new ArgumentNullException(nameof(note)); - } + return note; + } - if (note.Accident == Accident.None) - { - return note; - } + return _chromaticCorrespondentDictionary[note]; + } - return _chromaticCorrespondentDictionary[note]; + /// + /// Check if the given name is a valid chord. + /// + /// The chord name + /// True if the given name is a valid chord. + public static bool IsChord(string chordName) + { + try + { + return new Chord(chordName) != null; } - - /// - /// Check if the given name is a valid chord. - /// - /// The chord name - /// True if the given name is a valid chord. - public static bool IsChord(string chordName) + catch (Exception) { - try - { - return new Chord(chordName) != null; - } - catch (Exception) - { - return false; - } + return false; } + } - /// - /// Apply optmizations to the input chord. - /// - /// The chord - /// The optmized chord. - /// Thrown when chords object is null. - /// Thrown when input is not a valid chord. - public static Chord Optimize(string chord) + /// + /// Apply optmizations to the input chord. + /// + /// The chord + /// The optmized chord. + /// Thrown when chords object is null. + /// Thrown when input is not a valid chord. + public static Chord Optimize(string chord) + { + return Optimize(new Chord(chord)); + } + + /// + /// Apply optmizations to the input chord. + /// + /// The chord + /// The optmized chord. + /// Thrown when chords object is null. + public static Chord Optimize(Chord chord) + { + if (chord is null) { - return Optimize(new Chord(chord)); + throw new ArgumentNullException(nameof(chord)); } - /// - /// Apply optmizations to the input chord. - /// - /// The chord - /// The optmized chord. - /// Thrown when chords object is null. - public static Chord Optimize(Chord chord) + Chord optimizedChord = OptimizeNote(chord); + optimizedChord = OptimizeInversion(optimizedChord); + return optimizedChord; + } + #endregion + + #region Private Methods + private static Chord Transpose(Chord chord, int semitones, TransposeMethod transposeMethod) + { + lock (_lock) { if (chord is null) { throw new ArgumentNullException(nameof(chord)); } - Chord optimizedChord = OptimizeNote(chord); - optimizedChord = OptimizeInversion(optimizedChord); - return optimizedChord; - } - #endregion + chord = OptimizeInversion(chord); + semitones = NormalizeSemitones(semitones); - #region Private Methods - private static Chord Transpose(Chord chord, int semitones, TransposeMethod transposeMethod) - { - lock (_lock) + if (semitones == 0) { - if (chord is null) - { - throw new ArgumentNullException(nameof(chord)); - } - - chord = OptimizeInversion(chord); - semitones = NormalizeSemitones(semitones); - - if (semitones == 0) - { - return chord; - } + return chord; + } - var transposedNote = transposeMethod(chord.Note, semitones); - Note transposedInversion = null; + var transposedNote = transposeMethod(chord.Note, semitones); + Note transposedInversion = null; - if (chord.Inversion != null) - { - transposedInversion = transposeMethod(chord.Inversion, semitones); - } - - return new Chord(transposedNote, chord.Tonality, chord.Complement, transposedInversion); + if (chord.Inversion != null) + { + transposedInversion = transposeMethod(chord.Inversion, semitones); } + + return new Chord(transposedNote, chord.Tonality, chord.Complement, transposedInversion); } + } - private static Chord OptimizeNote(Chord chord) + private static Chord OptimizeNote(Chord chord) + { + if (chord is null) { - if (chord is null) - { - throw new ArgumentNullException(nameof(chord)); - } + throw new ArgumentNullException(nameof(chord)); + } - Note note = chord.Note; + Note note = chord.Note; - if (note != null) + if (note != null) + { + // A##/D becomes B/D, for example + if (note.IsDoubleSharp || note.IsDoubleFlat) { - // A##/D becomes B/D, for example - if (note.IsDoubleSharp || note.IsDoubleFlat) - { - note = GetChromaticCorrespondent(note); - } + note = GetChromaticCorrespondent(note); } + } + + return new Chord(note, chord.Tonality, chord.Complement, chord.Inversion); + } - return new Chord(note, chord.Tonality, chord.Complement, chord.Inversion); + private static Chord OptimizeInversion(Chord chord) + { + if (chord is null) + { + throw new ArgumentNullException(nameof(chord)); } - private static Chord OptimizeInversion(Chord chord) + Note note = chord.Note; + Note inversion = chord.Inversion; + + if (inversion != null) { - if (chord is null) + if (inversion.IsDoubleSharp || inversion.IsDoubleFlat) { - throw new ArgumentNullException(nameof(chord)); + inversion = GetChromaticCorrespondent(inversion); } - Note note = chord.Note; - Note inversion = chord.Inversion; - - if (inversion != null) + if (note != null) { - if (inversion.IsDoubleSharp || inversion.IsDoubleFlat) + // A#/Db becommes A#/C#, for example + if (HasDifferentChromaticPole(note, inversion)) { inversion = GetChromaticCorrespondent(inversion); } - if (note != null) + // A#/A# becommes A#, for example + if (note.Equals(inversion)) { - // A#/Db becommes A#/C#, for example - if (HasDifferentChromaticPole(note, inversion)) - { - inversion = GetChromaticCorrespondent(inversion); - } - - // A#/A# becommes A#, for example - if (note.Equals(inversion)) - { - inversion = null; - } + inversion = null; } } + } + + return new Chord(note, chord.Tonality, chord.Complement, inversion); + } - return new Chord(note, chord.Tonality, chord.Complement, inversion); + private static int NormalizeSemitones(int semitones) + { + if (semitones < 0) + { + throw new ArgumentOutOfRangeException(nameof(semitones), "Semitones can not be negative numbers"); } - private static int NormalizeSemitones(int semitones) + return semitones % SemitonesOnTheScale; + } + + private static Node GetCorrectMap(Node map, Note note) + { + return note switch { - if (semitones < 0) - { - throw new ArgumentOutOfRangeException(nameof(semitones), "Semitones can not be negative numbers"); - } + _ when note.IsFlatOrDoubleFlat => _flatMap, + _ when note.IsSharpOrDoubleSharp => _sharpMap, + _ => map + }; + } - return semitones % SemitonesOnTheScale; - } + private static Node FindHeadNodeFromNote(Node mapHeadNode, Note note) + { + mapHeadNode.HasVisited = true; - private static Node GetCorrectMap(Node map, Note note) + if (mapHeadNode.Note.Equals(note)) { - return note switch - { - _ when note.IsFlatOrDoubleFlat => _flatMap, - _ when note.IsSharpOrDoubleSharp => _sharpMap, - _ => map - }; + mapHeadNode.HasVisited = false; + return mapHeadNode; } - private static Node FindHeadNodeFromNote(Node mapHeadNode, Note note) - { - mapHeadNode.HasVisited = true; + Node node = null; - if (mapHeadNode.Note.Equals(note)) - { - mapHeadNode.HasVisited = false; - return mapHeadNode; - } + if (mapHeadNode.Right != null && !mapHeadNode.Right.HasVisited) + { + node = FindHeadNodeFromNote(mapHeadNode.Right, note); + } - Node node = null; + if (node is null && mapHeadNode.Down != null && !mapHeadNode.Down.HasVisited) + { + node = FindHeadNodeFromNote(mapHeadNode.Down, note); + } - if (mapHeadNode.Right != null && !mapHeadNode.Right.HasVisited) - { - node = FindHeadNodeFromNote(mapHeadNode.Right, note); - } + mapHeadNode.HasVisited = false; - if (node is null && mapHeadNode.Down != null && !mapHeadNode.Down.HasVisited) - { - node = FindHeadNodeFromNote(mapHeadNode.Down, note); - } + return node; + } - mapHeadNode.HasVisited = false; + private static (Note, Note) MatchChromaticPole(Note chord1, Note chord2) + { + var polarizedChord1 = chord1; + var polarizedChord2 = chord2; - return node; + if (polarizedChord1.IsDoubleFlat || polarizedChord1.IsDoubleSharp) + { + polarizedChord1 = GetChromaticCorrespondent(polarizedChord1); } - private static (Note, Note) MatchChromaticPole(Note chord1, Note chord2) + if (polarizedChord2.IsDoubleFlat || polarizedChord2.IsDoubleSharp) { - var polarizedChord1 = chord1; - var polarizedChord2 = chord2; + polarizedChord2 = GetChromaticCorrespondent(polarizedChord2); + } - if (polarizedChord1.IsDoubleFlat || polarizedChord1.IsDoubleSharp) + if (HasDifferentChromaticPole(polarizedChord1, polarizedChord2)) + { + if (polarizedChord1.IsFlat || polarizedChord1.IsSharp) { polarizedChord1 = GetChromaticCorrespondent(polarizedChord1); } - - if (polarizedChord2.IsDoubleFlat || polarizedChord2.IsDoubleSharp) + else if (polarizedChord2.IsFlat || polarizedChord2.IsSharp) { polarizedChord2 = GetChromaticCorrespondent(polarizedChord2); } - - if (HasDifferentChromaticPole(polarizedChord1, polarizedChord2)) - { - if (polarizedChord1.IsFlat || polarizedChord1.IsSharp) - { - polarizedChord1 = GetChromaticCorrespondent(polarizedChord1); - } - else if (polarizedChord2.IsFlat || polarizedChord2.IsSharp) - { - polarizedChord2 = GetChromaticCorrespondent(polarizedChord2); - } - } - - return (polarizedChord1, polarizedChord2); } - private static Node BuildFlatMap() - { - var head = new Node(new Note(NoteLetter.A, Accident.None)); - - // A - var current = head; - current.Down = new Node(new Note(NoteLetter.B, Accident.DoubleFlat)); - current.Down.Up = current; - - // Bbb - current = current.Down; - current.Right = new Node(new Note(NoteLetter.B, Accident.Flat)); - current.Right.Left = current; - - // Bb - current = current.Right; - current.Right = new Node(new Note(NoteLetter.B, Accident.None)); - current.Down = new Node(new Note(NoteLetter.C, Accident.DoubleFlat)); - current.Right.Left = current; - current.Down.Up = current; - - // Cbb - current = current.Down; - current.Right = new Node(new Note(NoteLetter.C, Accident.Flat)); - current.Right.Left = current; - - // Cb - current = current.Right; - current.Up = current.Left.Up.Right; - current.Up.Down = current; - current.Right = new Node(new Note(NoteLetter.C, Accident.None)); - current.Right.Left = current; - - // C - current = current.Right; - current.Down = new Node(new Note(NoteLetter.D, Accident.DoubleFlat)); - current.Down.Up = current; - - // Dbb - current = current.Down; - current.Right = new Node(new Note(NoteLetter.D, Accident.Flat)); - current.Right.Left = current; - - // Db - current = current.Right; - current.Right = new Node(new Note(NoteLetter.D, Accident.None)); - current.Right.Left = current; - - // D - current = current.Right; - current.Down = new Node(new Note(NoteLetter.E, Accident.DoubleFlat)); - current.Down.Up = current; - - // Ebb - current = current.Down; - current.Right = new Node(new Note(NoteLetter.E, Accident.Flat)); - current.Right.Left = current; - - // Eb - current = current.Right; - current.Right = new Node(new Note(NoteLetter.E, Accident.None)); - current.Right.Left = current; - current.Down = new Node(new Note(NoteLetter.F, Accident.DoubleFlat)); - current.Down.Up = current; - - // Fbb - current = current.Down; - current.Right = new Node(new Note(NoteLetter.F, Accident.Flat)); - current.Right.Left = current; - - // Fb - current = current.Right; - current.Up = current.Left.Up.Right; - current.Up.Down = current; - current.Right = new Node(new Note(NoteLetter.F, Accident.None)); - current.Right.Left = current; - - // F - current = current.Right; - current.Down = new Node(new Note(NoteLetter.G, Accident.DoubleFlat)); - current.Down.Up = current; - - // Gbb - current = current.Down; - current.Right = new Node(new Note(NoteLetter.G, Accident.Flat)); - current.Right.Left = current; - - // Gb - current = current.Right; - current.Right = new Node(new Note(NoteLetter.G, Accident.None)); - current.Right.Left = current; - - // G - current = current.Right; - current.Down = new Node(new Note(NoteLetter.A, Accident.DoubleFlat)); - current.Down.Up = current; - - // Abb - current = current.Down; - current.Right = new Node(new Note(NoteLetter.A, Accident.Flat)); - current.Right.Left = current; - - // Ab - current = current.Right; - current.Right = head; - current.Right.Left = current; - - return head; - } + return (polarizedChord1, polarizedChord2); + } - private static Node BuildSharpMap() - { - var head = new Node(new Note(NoteLetter.A, Accident.None)); - - // A - var current = head; - current.Right = new Node(new Note(NoteLetter.A, Accident.Sharp)); - current.Right.Left = current; - - // A# - current = current.Right; - current.Right = new Node(new Note(NoteLetter.A, Accident.DoubleSharp)); - current.Right.Left = current; - - // A## - current = current.Right; - current.Down = new Node(new Note(NoteLetter.B, Accident.None)); - current.Down.Up = current; - - // B - current = current.Down; - current.Right = new Node(new Note(NoteLetter.B, Accident.Sharp)); - current.Right.Left = current; - - // B# - current = current.Right; - current.Right = new Node(new Note(NoteLetter.B, Accident.DoubleSharp)); - current.Right.Left = current; - current.Down = new Node(new Note(NoteLetter.C, Accident.None)); - current.Down.Up = current; - - // B## - current = current.Right; - current.Down = new Node(new Note(NoteLetter.C, Accident.Sharp)); - current.Down.Up = current; - - // C# - current = current.Down; - current.Left = current.Up.Left.Down; - current.Left.Right = current; - current.Right = new Node(new Note(NoteLetter.C, Accident.DoubleSharp)); - current.Right.Left = current; - - // C## - current = current.Right; - current.Down = new Node(new Note(NoteLetter.D, Accident.None)); - current.Down.Up = current; - - // D - current = current.Down; - current.Right = new Node(new Note(NoteLetter.D, Accident.Sharp)); - current.Right.Left = current; - - // D# - current = current.Right; - current.Right = new Node(new Note(NoteLetter.D, Accident.DoubleSharp)); - current.Right.Left = current; - - // D## - current = current.Right; - current.Down = new Node(new Note(NoteLetter.E, Accident.None)); - current.Down.Up = current; - - // E - current = current.Down; - current.Right = new Node(new Note(NoteLetter.E, Accident.Sharp)); - current.Right.Left = current; - - // E# - current = current.Right; - current.Right = new Node(new Note(NoteLetter.E, Accident.DoubleSharp)); - current.Right.Left = current; - current.Down = new Node(new Note(NoteLetter.F, Accident.None)); - current.Down.Up = current; - - // E## - current = current.Right; - current.Down = new Node(new Note(NoteLetter.F, Accident.Sharp)); - current.Down.Up = current; - - // F# - current = current.Down; - current.Left = current.Up.Left.Down; - current.Left.Right = current; - current.Right = new Node(new Note(NoteLetter.F, Accident.DoubleSharp)); - current.Right.Left = current; - - // F## - current = current.Right; - current.Down = new Node(new Note(NoteLetter.G, Accident.None)); - current.Down.Up = current; - - // G - current = current.Down; - current.Right = new Node(new Note(NoteLetter.G, Accident.Sharp)); - current.Right.Left = current; - - // G# - current = current.Right; - current.Right = new Node(new Note(NoteLetter.G, Accident.DoubleSharp)); - current.Right.Left = current; - - // G## - current = current.Right; - current.Down = head; - current.Down.Up = current; - - return head; - } + private static Node BuildFlatMap() + { + var head = new Node(new Note(NoteLetter.A, Accident.None)); + + // A + var current = head; + current.Down = new Node(new Note(NoteLetter.B, Accident.DoubleFlat)); + current.Down.Up = current; + + // Bbb + current = current.Down; + current.Right = new Node(new Note(NoteLetter.B, Accident.Flat)); + current.Right.Left = current; + + // Bb + current = current.Right; + current.Right = new Node(new Note(NoteLetter.B, Accident.None)); + current.Down = new Node(new Note(NoteLetter.C, Accident.DoubleFlat)); + current.Right.Left = current; + current.Down.Up = current; + + // Cbb + current = current.Down; + current.Right = new Node(new Note(NoteLetter.C, Accident.Flat)); + current.Right.Left = current; + + // Cb + current = current.Right; + current.Up = current.Left.Up.Right; + current.Up.Down = current; + current.Right = new Node(new Note(NoteLetter.C, Accident.None)); + current.Right.Left = current; + + // C + current = current.Right; + current.Down = new Node(new Note(NoteLetter.D, Accident.DoubleFlat)); + current.Down.Up = current; + + // Dbb + current = current.Down; + current.Right = new Node(new Note(NoteLetter.D, Accident.Flat)); + current.Right.Left = current; + + // Db + current = current.Right; + current.Right = new Node(new Note(NoteLetter.D, Accident.None)); + current.Right.Left = current; + + // D + current = current.Right; + current.Down = new Node(new Note(NoteLetter.E, Accident.DoubleFlat)); + current.Down.Up = current; + + // Ebb + current = current.Down; + current.Right = new Node(new Note(NoteLetter.E, Accident.Flat)); + current.Right.Left = current; + + // Eb + current = current.Right; + current.Right = new Node(new Note(NoteLetter.E, Accident.None)); + current.Right.Left = current; + current.Down = new Node(new Note(NoteLetter.F, Accident.DoubleFlat)); + current.Down.Up = current; + + // Fbb + current = current.Down; + current.Right = new Node(new Note(NoteLetter.F, Accident.Flat)); + current.Right.Left = current; + + // Fb + current = current.Right; + current.Up = current.Left.Up.Right; + current.Up.Down = current; + current.Right = new Node(new Note(NoteLetter.F, Accident.None)); + current.Right.Left = current; + + // F + current = current.Right; + current.Down = new Node(new Note(NoteLetter.G, Accident.DoubleFlat)); + current.Down.Up = current; + + // Gbb + current = current.Down; + current.Right = new Node(new Note(NoteLetter.G, Accident.Flat)); + current.Right.Left = current; + + // Gb + current = current.Right; + current.Right = new Node(new Note(NoteLetter.G, Accident.None)); + current.Right.Left = current; + + // G + current = current.Right; + current.Down = new Node(new Note(NoteLetter.A, Accident.DoubleFlat)); + current.Down.Up = current; + + // Abb + current = current.Down; + current.Right = new Node(new Note(NoteLetter.A, Accident.Flat)); + current.Right.Left = current; + + // Ab + current = current.Right; + current.Right = head; + current.Right.Left = current; + + return head; + } - private static Dictionary BuildChromaticCorrespondentDictionary() - { - return new Dictionary - { - { new Note(NoteLetter.A, Accident.Flat), new Note(NoteLetter.G, Accident.Sharp) }, - { new Note(NoteLetter.G, Accident.Flat), new Note(NoteLetter.F, Accident.Sharp) }, - { new Note(NoteLetter.F, Accident.Flat), new Note(NoteLetter.E, Accident.None) }, - { new Note(NoteLetter.E, Accident.Flat), new Note(NoteLetter.D, Accident.Sharp) }, - { new Note(NoteLetter.D, Accident.Flat), new Note(NoteLetter.C, Accident.Sharp) }, - { new Note(NoteLetter.C, Accident.Flat), new Note(NoteLetter.B, Accident.None) }, - { new Note(NoteLetter.B, Accident.Flat), new Note(NoteLetter.A, Accident.Sharp) }, - { new Note(NoteLetter.A, Accident.Sharp), new Note(NoteLetter.B, Accident.Flat) }, - { new Note(NoteLetter.B, Accident.Sharp), new Note(NoteLetter.C, Accident.None) }, - { new Note(NoteLetter.C, Accident.Sharp), new Note(NoteLetter.D, Accident.Flat) }, - { new Note(NoteLetter.D, Accident.Sharp), new Note(NoteLetter.E, Accident.Flat) }, - { new Note(NoteLetter.E, Accident.Sharp), new Note(NoteLetter.F, Accident.None) }, - { new Note(NoteLetter.F, Accident.Sharp), new Note(NoteLetter.G, Accident.Flat) }, - { new Note(NoteLetter.G, Accident.Sharp), new Note(NoteLetter.A, Accident.Flat) }, - { new Note(NoteLetter.A, Accident.DoubleFlat), new Note(NoteLetter.G, Accident.None) }, - { new Note(NoteLetter.B, Accident.DoubleFlat), new Note(NoteLetter.A, Accident.None) }, - { new Note(NoteLetter.C, Accident.DoubleFlat), new Note(NoteLetter.B, Accident.Flat) }, - { new Note(NoteLetter.D, Accident.DoubleFlat), new Note(NoteLetter.C, Accident.None) }, - { new Note(NoteLetter.E, Accident.DoubleFlat), new Note(NoteLetter.D, Accident.None) }, - { new Note(NoteLetter.F, Accident.DoubleFlat), new Note(NoteLetter.E, Accident.Flat) }, - { new Note(NoteLetter.G, Accident.DoubleFlat), new Note(NoteLetter.F, Accident.None) }, - { new Note(NoteLetter.A, Accident.DoubleSharp), new Note(NoteLetter.B, Accident.None) }, - { new Note(NoteLetter.B, Accident.DoubleSharp), new Note(NoteLetter.C, Accident.Sharp) }, - { new Note(NoteLetter.C, Accident.DoubleSharp), new Note(NoteLetter.D, Accident.None) }, - { new Note(NoteLetter.D, Accident.DoubleSharp), new Note(NoteLetter.E, Accident.None) }, - { new Note(NoteLetter.E, Accident.DoubleSharp), new Note(NoteLetter.F, Accident.Flat) }, - { new Note(NoteLetter.F, Accident.DoubleSharp), new Note(NoteLetter.G, Accident.None) }, - { new Note(NoteLetter.G, Accident.DoubleSharp), new Note(NoteLetter.A, Accident.None) }, - }; - } - #endregion + private static Node BuildSharpMap() + { + var head = new Node(new Note(NoteLetter.A, Accident.None)); + + // A + var current = head; + current.Right = new Node(new Note(NoteLetter.A, Accident.Sharp)); + current.Right.Left = current; + + // A# + current = current.Right; + current.Right = new Node(new Note(NoteLetter.A, Accident.DoubleSharp)); + current.Right.Left = current; + + // A## + current = current.Right; + current.Down = new Node(new Note(NoteLetter.B, Accident.None)); + current.Down.Up = current; + + // B + current = current.Down; + current.Right = new Node(new Note(NoteLetter.B, Accident.Sharp)); + current.Right.Left = current; + + // B# + current = current.Right; + current.Right = new Node(new Note(NoteLetter.B, Accident.DoubleSharp)); + current.Right.Left = current; + current.Down = new Node(new Note(NoteLetter.C, Accident.None)); + current.Down.Up = current; + + // B## + current = current.Right; + current.Down = new Node(new Note(NoteLetter.C, Accident.Sharp)); + current.Down.Up = current; + + // C# + current = current.Down; + current.Left = current.Up.Left.Down; + current.Left.Right = current; + current.Right = new Node(new Note(NoteLetter.C, Accident.DoubleSharp)); + current.Right.Left = current; + + // C## + current = current.Right; + current.Down = new Node(new Note(NoteLetter.D, Accident.None)); + current.Down.Up = current; + + // D + current = current.Down; + current.Right = new Node(new Note(NoteLetter.D, Accident.Sharp)); + current.Right.Left = current; + + // D# + current = current.Right; + current.Right = new Node(new Note(NoteLetter.D, Accident.DoubleSharp)); + current.Right.Left = current; + + // D## + current = current.Right; + current.Down = new Node(new Note(NoteLetter.E, Accident.None)); + current.Down.Up = current; + + // E + current = current.Down; + current.Right = new Node(new Note(NoteLetter.E, Accident.Sharp)); + current.Right.Left = current; + + // E# + current = current.Right; + current.Right = new Node(new Note(NoteLetter.E, Accident.DoubleSharp)); + current.Right.Left = current; + current.Down = new Node(new Note(NoteLetter.F, Accident.None)); + current.Down.Up = current; + + // E## + current = current.Right; + current.Down = new Node(new Note(NoteLetter.F, Accident.Sharp)); + current.Down.Up = current; + + // F# + current = current.Down; + current.Left = current.Up.Left.Down; + current.Left.Right = current; + current.Right = new Node(new Note(NoteLetter.F, Accident.DoubleSharp)); + current.Right.Left = current; + + // F## + current = current.Right; + current.Down = new Node(new Note(NoteLetter.G, Accident.None)); + current.Down.Up = current; + + // G + current = current.Down; + current.Right = new Node(new Note(NoteLetter.G, Accident.Sharp)); + current.Right.Left = current; + + // G# + current = current.Right; + current.Right = new Node(new Note(NoteLetter.G, Accident.DoubleSharp)); + current.Right.Left = current; + + // G## + current = current.Right; + current.Down = head; + current.Down.Up = current; + + return head; + } - #region Delegates - private delegate Note TransposeMethod(Note note, int semitones); - #endregion + private static Dictionary BuildChromaticCorrespondentDictionary() + { + return new Dictionary + { + { new Note(NoteLetter.A, Accident.Flat), new Note(NoteLetter.G, Accident.Sharp) }, + { new Note(NoteLetter.G, Accident.Flat), new Note(NoteLetter.F, Accident.Sharp) }, + { new Note(NoteLetter.F, Accident.Flat), new Note(NoteLetter.E, Accident.None) }, + { new Note(NoteLetter.E, Accident.Flat), new Note(NoteLetter.D, Accident.Sharp) }, + { new Note(NoteLetter.D, Accident.Flat), new Note(NoteLetter.C, Accident.Sharp) }, + { new Note(NoteLetter.C, Accident.Flat), new Note(NoteLetter.B, Accident.None) }, + { new Note(NoteLetter.B, Accident.Flat), new Note(NoteLetter.A, Accident.Sharp) }, + { new Note(NoteLetter.A, Accident.Sharp), new Note(NoteLetter.B, Accident.Flat) }, + { new Note(NoteLetter.B, Accident.Sharp), new Note(NoteLetter.C, Accident.None) }, + { new Note(NoteLetter.C, Accident.Sharp), new Note(NoteLetter.D, Accident.Flat) }, + { new Note(NoteLetter.D, Accident.Sharp), new Note(NoteLetter.E, Accident.Flat) }, + { new Note(NoteLetter.E, Accident.Sharp), new Note(NoteLetter.F, Accident.None) }, + { new Note(NoteLetter.F, Accident.Sharp), new Note(NoteLetter.G, Accident.Flat) }, + { new Note(NoteLetter.G, Accident.Sharp), new Note(NoteLetter.A, Accident.Flat) }, + { new Note(NoteLetter.A, Accident.DoubleFlat), new Note(NoteLetter.G, Accident.None) }, + { new Note(NoteLetter.B, Accident.DoubleFlat), new Note(NoteLetter.A, Accident.None) }, + { new Note(NoteLetter.C, Accident.DoubleFlat), new Note(NoteLetter.B, Accident.Flat) }, + { new Note(NoteLetter.D, Accident.DoubleFlat), new Note(NoteLetter.C, Accident.None) }, + { new Note(NoteLetter.E, Accident.DoubleFlat), new Note(NoteLetter.D, Accident.None) }, + { new Note(NoteLetter.F, Accident.DoubleFlat), new Note(NoteLetter.E, Accident.Flat) }, + { new Note(NoteLetter.G, Accident.DoubleFlat), new Note(NoteLetter.F, Accident.None) }, + { new Note(NoteLetter.A, Accident.DoubleSharp), new Note(NoteLetter.B, Accident.None) }, + { new Note(NoteLetter.B, Accident.DoubleSharp), new Note(NoteLetter.C, Accident.Sharp) }, + { new Note(NoteLetter.C, Accident.DoubleSharp), new Note(NoteLetter.D, Accident.None) }, + { new Note(NoteLetter.D, Accident.DoubleSharp), new Note(NoteLetter.E, Accident.None) }, + { new Note(NoteLetter.E, Accident.DoubleSharp), new Note(NoteLetter.F, Accident.Flat) }, + { new Note(NoteLetter.F, Accident.DoubleSharp), new Note(NoteLetter.G, Accident.None) }, + { new Note(NoteLetter.G, Accident.DoubleSharp), new Note(NoteLetter.A, Accident.None) }, + }; } + #endregion + + #region Delegates + private delegate Note TransposeMethod(Note note, int semitones); + #endregion } From c7569e50a0ad5a8c5e441ea3377251ee4f64f40b Mon Sep 17 00:00:00 2001 From: Davidson Bruno Date: Wed, 30 Aug 2023 20:34:04 -0300 Subject: [PATCH 3/5] test: added more support for test cases --- SparksMusic.Test/TransposerTest.cs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/SparksMusic.Test/TransposerTest.cs b/SparksMusic.Test/TransposerTest.cs index 1b7f368..1135a69 100644 --- a/SparksMusic.Test/TransposerTest.cs +++ b/SparksMusic.Test/TransposerTest.cs @@ -113,6 +113,8 @@ public void Should_ThrowArgumentOutOfRangeException_When_CallTransposeDownMethod [InlineData("A##m")] [InlineData("Abm")] [InlineData("Abbm")] + [InlineData("A6")] + [InlineData("Am6")] [InlineData("A7")] [InlineData("Am7")] [InlineData("Am7M")] @@ -122,6 +124,18 @@ public void Should_ThrowArgumentOutOfRangeException_When_CallTransposeDownMethod [InlineData("A#m(b5,11,13)/G#")] [InlineData("A+")] [InlineData("A\u00B0")] + [InlineData("Amaj7")] + [InlineData("Amaj9")] + [InlineData("Asus")] + [InlineData("Asus2")] + [InlineData("Asus4")] + [InlineData("Aadd2")] + [InlineData("Aadd3")] + [InlineData("Aadd5")] + [InlineData("Aadd6")] + [InlineData("Aadd9")] + [InlineData("Aadd11")] + [InlineData("Aadd13")] public void Should_ReturnTrue_When_CallIsValidMethodPassingAValidChordName(string chordName) { Assert.True(Transposer.IsChord(chordName)); @@ -145,6 +159,20 @@ public void Should_ReturnTrue_When_CallIsValidMethodPassingAValidChordName(strin [InlineData("A/")] [InlineData("A/GF")] [InlineData("AG")] + [InlineData("Amaj")] + [InlineData("Amaj1")] + [InlineData("Amaj4")] + [InlineData("Amaj5")] + [InlineData("Amaj8")] + [InlineData("Amaj10")] + [InlineData("Asus22")] + [InlineData("Aadd")] + [InlineData("Aadd0")] + [InlineData("Aadd1")] + [InlineData("Aadd4")] + [InlineData("Aadd7")] + [InlineData("Aadd8")] + [InlineData("Aadd10")] public void Should_ReturnFalse_When_CallIsValidMethodPassingAInvalidChordName(string chordName) { Assert.False(Transposer.IsChord(chordName)); From 15bb92eda7bd23c8f64d3f64402003e5ace4be9b Mon Sep 17 00:00:00 2001 From: Davidson Bruno Date: Wed, 30 Aug 2023 20:35:07 -0300 Subject: [PATCH 4/5] fix: fixed chord pattern --- SparksMusic.Library/Chord.cs | 91 ++++++++++---------------- SparksMusic.Library/Transposer.cs | 2 - SparksMusic.Library/Utils/EnumUtils.cs | 4 +- 3 files changed, 37 insertions(+), 60 deletions(-) diff --git a/SparksMusic.Library/Chord.cs b/SparksMusic.Library/Chord.cs index 9bf0087..c622457 100644 --- a/SparksMusic.Library/Chord.cs +++ b/SparksMusic.Library/Chord.cs @@ -7,7 +7,7 @@ namespace SparksMusic.Library; /// /// Chord class /// -public class Chord : IEquatable +public partial class Chord : IEquatable { /// /// Chord note @@ -68,31 +68,27 @@ public class Chord : IEquatable public Chord(string chord) { if (chord is null) - { throw new ArgumentNullException(nameof(chord)); - } chord = chord.Trim(); - var regex = new Regex(GetChordPattern(), RegexOptions.Compiled); + var regex = ChordRegex(); var match = regex.Match(chord); if (!match.Success || match.Value != chord) throw new NotAChordException("The string provided does not match a valid chord"); - const int noteLetterGroup = 1; - const int accidentGroup = 2; - const int diminutiveOrAugmentedGroup = 3; - const int tonalityGroup = 4; - const int firstComplementGroup = 5; - const int secondComplementGroup = 6; - const int inversionLetterGroup = 11; - const int inversionAccidentGroup = 12; - - Note = GetNote(match.Groups[noteLetterGroup].Value, match.Groups[accidentGroup].Value); - Tonality = GetTonality(match.Groups[diminutiveOrAugmentedGroup].Value, match.Groups[tonalityGroup].Value); - Complement = GetComplement(Tonality, $"{match.Groups[firstComplementGroup].Value}{match.Groups[secondComplementGroup].Value}"); - Inversion = GetInversion(match.Groups[inversionLetterGroup].Value, match.Groups[inversionAccidentGroup].Value); + const string key = "key"; + const string chromatism = "chromatism"; + const string complement = "complement"; + const string tonality = "tonality"; + const string inversionKey = "inversionKey"; + const string inversionChromatism = "inversionChromatism"; + + Note = GetNote(match.Groups[key].Value, match.Groups[chromatism].Value); + Tonality = GetTonality(match.Groups[complement].Value, match.Groups[tonality].Value); + Complement = GetComplement(Tonality, match.Groups[complement].Value); + Inversion = GetInversion(match.Groups[inversionKey].Value, match.Groups[inversionChromatism].Value); } /// @@ -146,30 +142,9 @@ public override string ToString() return $"{Note}{Tonality.GetDescription()}{Complement}{inversionString}"; } - public override bool Equals(object obj) - { - return base.Equals(obj as Chord); - } - - public override int GetHashCode() - { - return HashCode.Combine(Note, Tonality, Complement, Inversion); - } + public override bool Equals(object obj) => base.Equals(obj as Chord); - private static string GetChordPattern() - { - const string noteLetter = @"([A-G]{1})"; - const string accident = @"(##?|bb?)?"; - const string tonality = @"(m|sus2|sus4)?"; - const string interval = @"(2|4|6|7M|7|9|11|13)?"; - const string incrementInterval = @"(b2|2|4|4#|b5|5|#5|6|7|7M|b9|9|11|#11|13)"; - - string note = $@"{noteLetter}{accident}"; - string complement = $@"(\+|\u00B0|{tonality}{interval}(\({incrementInterval}(,{incrementInterval})*\))?)"; - string inversion = $@"(\/{noteLetter}{accident})?"; - - return $"{note}{complement}{inversion}"; - } + public override int GetHashCode() => HashCode.Combine(Note, Tonality, Complement, Inversion); private static Note GetNote(string noteLetterValue, string accidentValue) { @@ -178,30 +153,31 @@ private static Note GetNote(string noteLetterValue, string accidentValue) return new Note(noteLetter, accident); } - private static Tonality GetTonality(string diminutiveOrAugmentedValue, string tonality) + private static Tonality GetTonality(string complement, string tonality) { - if (diminutiveOrAugmentedValue == Tonality.Augmented.GetDescription()) - { - return Tonality.Augmented; - } - else if (diminutiveOrAugmentedValue == Tonality.Diminished.GetDescription()) - { - return Tonality.Diminished; - } - else if (diminutiveOrAugmentedValue == Tonality.HalfDiminished.GetDescription()) + try { - return Tonality.HalfDiminished; + return EnumUtils.Parse(complement); } - else + catch (Exception) { - return EnumUtils.Parse(tonality); + try + { + return EnumUtils.Parse(tonality); + } + catch (Exception) + { + throw new Exception("Unknown tonality"); + } } } - private static string GetComplement(Tonality tonality, string text) + private static string GetComplement(Tonality tonality, string text) => tonality switch { - return tonality == Tonality.Augmented || tonality == Tonality.Diminished || tonality == Tonality.HalfDiminished ? "" : text; - } + Tonality.Augmented or Tonality.Diminished or Tonality.HalfDiminished => "", + Tonality.Minor => text[1..], + _ => text + }; private static Note GetInversion(string noteLetterValue, string accidentValue) { @@ -214,4 +190,7 @@ private static Note GetInversion(string noteLetterValue, string accidentValue) return null; } } + + [GeneratedRegex("^((?[A-G])(?##?|bb?)?)(?\\+|°|((?m?)(2|4|5|6|7M?|maj7|9M?|maj9|11|13|sus(2|4)?|add(2|3|5|6|9|11|13))?)?(\\((b2|2|4|4#|b5|5|#5|6|7M?|maj7|b9|9M?|maj9|11|#11|13|sus(2|4)?|add(2|3|5|6|9|11|13))(,(b2|2|4|4#|b5|5|#5|6|7M?|maj7|b9|9|11|#11|13|sus(2|4)|add(2|3|5|6|9|11|13)))*\\))?)(\\/(?[A-G])(?##?|bb?)?)?$", RegexOptions.Compiled)] + private static partial Regex ChordRegex(); } diff --git a/SparksMusic.Library/Transposer.cs b/SparksMusic.Library/Transposer.cs index d93540e..9d92e4e 100644 --- a/SparksMusic.Library/Transposer.cs +++ b/SparksMusic.Library/Transposer.cs @@ -215,9 +215,7 @@ public static List GetValidChords(List chords) foreach (var chord in chords) { if (IsChord(chord)) - { validChords.Add(new Chord(chord)); - } } return validChords; diff --git a/SparksMusic.Library/Utils/EnumUtils.cs b/SparksMusic.Library/Utils/EnumUtils.cs index eec3939..ffc5825 100644 --- a/SparksMusic.Library/Utils/EnumUtils.cs +++ b/SparksMusic.Library/Utils/EnumUtils.cs @@ -14,13 +14,13 @@ public static T Parse(string input) where T : System.Enum var attributes = (DescriptionAttribute[])field.GetCustomAttributes(typeof(DescriptionAttribute), false); if (attributes != null && attributes.Length > 0 && attributes[0].Description == input) - return (T)System.Enum.Parse(typeof(T), field.Name); + return (T)Enum.Parse(typeof(T), field.Name); } foreach (var field in fields) { if (field.Name == input) - return (T)System.Enum.Parse(typeof(T), field.Name); + return (T)Enum.Parse(typeof(T), field.Name); } throw new Exception("Not found"); From f91b918ad51fb6d593c9aefc6266388cc8ccc7c8 Mon Sep 17 00:00:00 2001 From: Davidson Bruno Date: Wed, 30 Aug 2023 20:39:04 -0300 Subject: [PATCH 5/5] chore: upgraded ci file to .net 7 --- .github/workflows/dotnet.yml | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 4708821..e6f6356 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -2,25 +2,24 @@ name: dotnet on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] workflow_dispatch: jobs: build: - runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Setup .NET - uses: actions/setup-dotnet@v1 - with: - dotnet-version: 5.0.x - - name: Restore dependencies - run: dotnet restore - - name: Build - run: dotnet build --no-restore - - name: Test - run: dotnet test --no-build --verbosity normal + - uses: actions/checkout@v2 + - name: Setup .NET + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 7.0.x + - name: Restore dependencies + run: dotnet restore + - name: Build + run: dotnet build --no-restore + - name: Test + run: dotnet test --no-build --verbosity normal