| | 1 | | using System; |
| | 2 | | using System.Linq; |
| | 3 | | using System.Collections.Generic; |
| | 4 | | using MusicTheory.Theory.Pitch; |
| | 5 | | using MTInterval = MusicTheory.Theory.Interval.FunctionalInterval; |
| | 6 | | using MusicTheory.Theory.Interval; |
| | 7 | |
|
| | 8 | | namespace MusicTheory.Theory.Chord |
| | 9 | | { |
| | 10 | | // Interval 型は Interval namespace へ移動 |
| | 11 | |
|
| | 12 | | public class ChordFormula |
| | 13 | | { |
| 0 | 14 | | public string Name { get; } |
| 207 | 15 | | public string Symbol { get; } |
| 214 | 16 | | public MTInterval[] CoreIntervals { get; } |
| 230 | 17 | | public MTInterval[] Tensions { get; } |
| 39 | 18 | | public string[] Aliases { get; } |
| | 19 | |
|
| 13 | 20 | | public ChordFormula(string name, string symbol, MTInterval[] coreIntervals, MTInterval[] tensions, params string[] a |
| | 21 | | { |
| 13 | 22 | | Name = name; |
| 13 | 23 | | Symbol = symbol; |
| 13 | 24 | | CoreIntervals = coreIntervals; |
| 13 | 25 | | Tensions = tensions; |
| 13 | 26 | | Aliases = aliases ?? Array.Empty<string>(); |
| 13 | 27 | | } |
| | 28 | |
|
| | 29 | | public string GetDisplayName() |
| | 30 | | { |
| 0 | 31 | | var tensionPart = Tensions.Any() ? "(" + string.Join(",", Tensions.Select(t => t.DisplayName)) + ")" : strin |
| 0 | 32 | | return Symbol + tensionPart; |
| | 33 | | } |
| | 34 | |
|
| | 35 | | // 代表的コード(最低限) |
| 0 | 36 | | public static readonly ChordFormula Maj7 = new("Maj7", "maj7", new[] { new MTInterval(IntervalType.MajorThird), new |
| 0 | 37 | | public static readonly ChordFormula Min7 = new("Min7", "m7", new[] { new MTInterval(IntervalType.MinorThird), new MT |
| 0 | 38 | | public static readonly ChordFormula Dom7 = new("Dom7", "7", new[] { new MTInterval(IntervalType.MajorThird), new MTI |
| | 39 | | } |
| | 40 | | public class ChordName |
| | 41 | | { |
| | 42 | | public string Root { get; } |
| | 43 | | public ChordFormula Formula { get; } |
| | 44 | |
|
| | 45 | | public ChordName(string root, ChordFormula formula) |
| | 46 | | { |
| | 47 | | Root = root; |
| | 48 | | Formula = formula; |
| | 49 | | } |
| | 50 | |
|
| | 51 | | public override string ToString() => $"{Root}{Formula.Symbol}"; |
| | 52 | | } |
| | 53 | |
|
| | 54 | | public static class ChordFormulas |
| | 55 | | { |
| | 56 | | private static readonly ChordFormula[] formulas = new[] |
| | 57 | | { |
| | 58 | | new ChordFormula("Major Triad", "", new[] { I(IntervalType.MajorThird), I(IntervalType.PerfectFifth) }, Arra |
| | 59 | | new ChordFormula("Minor Triad", "m", new[] { I(IntervalType.MinorThird), I(IntervalType.PerfectFifth) }, Arr |
| | 60 | | new ChordFormula("Diminished Triad", "dim", new[] { I(IntervalType.MinorThird), I(IntervalType.Tritone) }, A |
| | 61 | | new ChordFormula("Augmented Triad", "aug", new[] { I(IntervalType.MajorThird), I(IntervalType.MinorSixth) }, |
| | 62 | | new ChordFormula("Major 7", "maj7", new[] { I(IntervalType.MajorThird), I(IntervalType.PerfectFifth), I(Inte |
| | 63 | | new ChordFormula("Dominant 7", "7", new[] { I(IntervalType.MajorThird), I(IntervalType.PerfectFifth), I(Inte |
| | 64 | | new ChordFormula("Minor 7", "m7", new[] { I(IntervalType.MinorThird), I(IntervalType.PerfectFifth), I(Interv |
| | 65 | | new ChordFormula("Half Diminished", "m7b5", new[] { I(IntervalType.MinorThird), I(IntervalType.Tritone), I(I |
| | 66 | | new ChordFormula("Diminished 7", "dim7", new[] { I(IntervalType.MinorThird), I(IntervalType.Tritone), I(Inte |
| | 67 | | new ChordFormula("Dominant 9", "9", new[] { I(IntervalType.MajorThird), I(IntervalType.PerfectFifth), I(Inte |
| | 68 | | new ChordFormula("Dominant 13", "13", new[] { I(IntervalType.MajorThird), I(IntervalType.PerfectFifth), I(In |
| | 69 | | new ChordFormula("Altered Dom7 (b9 #9 #11 b13)", "7alt", new[] { I(IntervalType.MajorThird), I(IntervalType. |
| | 70 | | new ChordFormula("Major 13", "maj13", new[] { I(IntervalType.MajorThird), I(IntervalType.PerfectFifth), I(In |
| | 71 | | }; |
| | 72 | |
|
| | 73 | | private static MTInterval I(IntervalType t) => new MTInterval(t); |
| | 74 | |
|
| | 75 | | public static IEnumerable<ChordFormula> All => formulas; |
| | 76 | |
|
| | 77 | | public static IEnumerable<ChordFormula> MatchByNotes(IEnumerable<int> inputNotes) |
| | 78 | | { |
| | 79 | | foreach (var formula in formulas) |
| | 80 | | { |
| | 81 | | var semitoneSet = formula.CoreIntervals.Concat(formula.Tensions).Select(i => i.Semitones).ToHashSet(); |
| | 82 | | if (inputNotes.All(note => semitoneSet.Contains(note))) |
| | 83 | | yield return formula; |
| | 84 | | } |
| | 85 | | } |
| | 86 | |
|
| | 87 | | public static IEnumerable<ChordName> GenerateChordNames(string root) |
| | 88 | | { |
| | 89 | | foreach (var formula in formulas) |
| | 90 | | { |
| | 91 | | yield return new ChordName(root, formula); |
| | 92 | | } |
| | 93 | | } |
| | 94 | |
|
| | 95 | | public static ChordName? ParseChordName(string chordText) |
| | 96 | | { |
| | 97 | | if (string.IsNullOrWhiteSpace(chordText)) return null; |
| | 98 | | // 最長一致(シンボルが空の場合は triad などの判定として fallback) |
| | 99 | | ChordName? best = null; |
| | 100 | | int bestLen = -1; |
| | 101 | | // シンボル長で降順ソートして衝突回避 (maj7 vs m7 など) |
| | 102 | | var ordered = formulas.Select(f => new { Formula = f, Symbols = new[] { f.Symbol }.Concat(f.Aliases).Where(s |
| | 103 | | foreach (var entry in ordered) |
| | 104 | | { |
| | 105 | | foreach (var symbol in entry.Symbols) |
| | 106 | | { |
| | 107 | | if (chordText.EndsWith(symbol, StringComparison.Ordinal)) |
| | 108 | | { |
| | 109 | | var root = chordText[..^symbol.Length]; |
| | 110 | | if (root.Length > 0 && symbol.Length > bestLen) |
| | 111 | | { |
| | 112 | | best = new ChordName(root, entry.Formula); |
| | 113 | | bestLen = symbol.Length; |
| | 114 | | break; // 長いシンボル優先で決定 |
| | 115 | | } |
| | 116 | | } |
| | 117 | | } |
| | 118 | | } |
| | 119 | | // triad 等シンボル空の処理: 既に best があれば返す。なければ root 全体を root とみなし、空シンボルを持つ最初の formula を当てる。 |
| | 120 | | if (best != null) return best; |
| | 121 | | var emptySymbolFormula = formulas.FirstOrDefault(f => string.IsNullOrEmpty(f.Symbol)); |
| | 122 | | if (emptySymbolFormula != null) |
| | 123 | | return new ChordName(chordText, emptySymbolFormula); |
| | 124 | | return null; |
| | 125 | | } |
| | 126 | | } |
| | 127 | |
|
| | 128 | | } |