| | 1 | | #pragma warning disable CA2255 // 'ModuleInitializer' is intended for application code or advanced source generators |
| | 2 | | using System.Runtime.CompilerServices; |
| | 3 | |
|
| | 4 | | namespace MusicTheory.Theory.Harmony; |
| | 5 | |
|
| | 6 | | /// <summary> |
| | 7 | | /// Lightweight warm-up for hot parsing/analyzer paths to reduce first-run jitter in tests/CLI/CI. |
| | 8 | | /// Performed once when the assembly loads; side-effect free. |
| | 9 | | /// </summary> |
| | 10 | | internal static class LibraryWarmUp |
| | 11 | | { |
| | 12 | | [ModuleInitializer] |
| | 13 | | public static void Initialize() |
| | 14 | | { |
| | 15 | | try |
| | 16 | | { |
| 1 | 17 | | var key = new Key(60, true); |
| | 18 | | // Exercise RomanInputParser common tokens (mixture, Neapolitan, secondaries, Aug6) |
| 1 | 19 | | RomanInputParser.Parse("bII; bIII; bⅢ; bVI; bVII; N6; bII6; V/ii; vii0/V; viiø/V; viio7/V; It6; Fr43; Ger65" |
| | 20 | | // Touch ChordRomanizer helpers indirectly via HarmonyAnalyzer small triad |
| 1 | 21 | | new Chord((key.TonicMidi + 7) % 12, ChordQuality.DominantSeventh).PitchClasses().ToArray(); |
| | 22 | | // Additional targeted warm-ups for previously flaky first-run cases |
| 1 | 23 | | RomanInputParser.Parse("bIII", key); |
| 1 | 24 | | RomanInputParser.Parse("vii0/V", key); |
| 1 | 25 | | RomanInputParser.Parse("vii°7/V; vii°65/V; vii°43/V; vii°42/V", key); |
| | 26 | |
|
| | 27 | | // Dominant ninth on V (C major): G B D F A — both with and without fifth |
| 1 | 28 | | var vRoot = (key.ScaleDegreeMidi(4) % 12 + 12) % 12; // V root pc |
| 1 | 29 | | var v9full = new[] { vRoot, (vRoot + 4) % 12, (vRoot + 7) % 12, (vRoot + 10) % 12, (vRoot + 14) % 12 }; |
| 1 | 30 | | var v9omit5 = new[] { vRoot, (vRoot + 4) % 12, (vRoot + 10) % 12, (vRoot + 14) % 12 }; |
| 1 | 31 | | _ = HarmonyAnalyzer.AnalyzeTriad(v9full, key, (FourPartVoicing?)null, null); |
| 1 | 32 | | _ = HarmonyAnalyzer.AnalyzeTriad(v9omit5, key, (FourPartVoicing?)null, null); |
| | 33 | |
|
| | 34 | | // Targeted harmony analyzer warm-ups (secondary triad inversions + maj7 inversion option) |
| | 35 | | // Secondary dominant triad V/ii inversions: A C# E |
| 20 | 36 | | int Pc(int m) => ((m % 12) + 12) % 12; |
| 1 | 37 | | var v_over_ii = new[] { Pc(9), Pc(1), Pc(4) }; |
| | 38 | | // Root (A) |
| 1 | 39 | | _ = HarmonyAnalyzer.AnalyzeTriad(v_over_ii, key, new FourPartVoicing(81, 69, 57, 69), null); |
| | 40 | | // 1st inv (C#) |
| 1 | 41 | | _ = HarmonyAnalyzer.AnalyzeTriad(v_over_ii, key, new FourPartVoicing(85, 73, 69, 61), null); |
| | 42 | | // 2nd inv (E) |
| 1 | 43 | | _ = HarmonyAnalyzer.AnalyzeTriad(v_over_ii, key, new FourPartVoicing(88, 76, 72, 64), null); |
| | 44 | |
|
| | 45 | | // Secondary leading-tone triad vii°/V inversions: F# A C |
| 1 | 46 | | var viio_over_V = new[] { Pc(6), Pc(9), Pc(0) }; |
| | 47 | | // Root (F#) |
| 1 | 48 | | _ = HarmonyAnalyzer.AnalyzeTriad(viio_over_V, key, new FourPartVoicing(78, 74, 66, 66), null); |
| | 49 | | // 1st inv (A) |
| 1 | 50 | | _ = HarmonyAnalyzer.AnalyzeTriad(viio_over_V, key, new FourPartVoicing(81, 76, 69, 69), null); |
| | 51 | | // 2nd inv (C) |
| 1 | 52 | | _ = HarmonyAnalyzer.AnalyzeTriad(viio_over_V, key, new FourPartVoicing(84, 72, 72, 60), null); |
| | 53 | |
|
| | 54 | | // Secondary leading-tone seventh vii°7/V inversions: F# A C Eb |
| 1 | 55 | | var vii7_over_V = new[] { Pc(6), Pc(9), Pc(0), Pc(3) }; |
| | 56 | | // Root (F#) |
| 1 | 57 | | _ = HarmonyAnalyzer.AnalyzeTriad(vii7_over_V, key, new FourPartVoicing(87, 81, 72, 66), null); |
| | 58 | | // 1st inv (A) |
| 1 | 59 | | _ = HarmonyAnalyzer.AnalyzeTriad(vii7_over_V, key, new FourPartVoicing(89, 84, 76, 69), null); |
| | 60 | | // 2nd inv (C) |
| 1 | 61 | | _ = HarmonyAnalyzer.AnalyzeTriad(vii7_over_V, key, new FourPartVoicing(89, 84, 76, 60), null); |
| | 62 | | // 3rd inv (Eb) |
| 1 | 63 | | _ = HarmonyAnalyzer.AnalyzeTriad(vii7_over_V, key, new FourPartVoicing(89, 84, 77, 63), null); |
| | 64 | |
|
| | 65 | | // Secondary dominant triad V/vi inversions: E G# B |
| 1 | 66 | | var v_over_vi = new[] { Pc(4), Pc(8), Pc(11) }; |
| | 67 | | // Root (E) |
| 1 | 68 | | _ = HarmonyAnalyzer.AnalyzeTriad(v_over_vi, key, new FourPartVoicing(88, 83, 80, 64), null); |
| | 69 | | // 1st inv (G#) |
| 1 | 70 | | _ = HarmonyAnalyzer.AnalyzeTriad(v_over_vi, key, new FourPartVoicing(92, 83, 76, 68), null); |
| | 71 | | // 2nd inv (B) |
| 1 | 72 | | _ = HarmonyAnalyzer.AnalyzeTriad(v_over_vi, key, new FourPartVoicing(88, 80, 76, 71), null); |
| | 73 | |
|
| | 74 | | // Secondary dominant triad V/vii inversions: F# A# C# |
| 1 | 75 | | var v_over_vii = new[] { Pc(6), Pc(10), Pc(1) }; |
| | 76 | | // Root (F#) |
| 1 | 77 | | _ = HarmonyAnalyzer.AnalyzeTriad(v_over_vii, key, new FourPartVoicing(78, 66, 54, 66), null); |
| | 78 | | // 1st inv (A#) |
| 1 | 79 | | _ = HarmonyAnalyzer.AnalyzeTriad(v_over_vii, key, new FourPartVoicing(82, 70, 58, 70), null); |
| | 80 | | // 2nd inv (C#) |
| 1 | 81 | | _ = HarmonyAnalyzer.AnalyzeTriad(v_over_vii, key, new FourPartVoicing(85, 73, 61, 61), null); |
| | 82 | |
|
| | 83 | | // Minor IIImaj7 first inversion with IncludeMajInSeventhInversions option |
| 1 | 84 | | var keyMin = new Key(60, false); |
| 1 | 85 | | var pcsIIImaj7 = new[] { Pc(63), Pc(67), Pc(70), Pc(74) }; // Eb G Bb D |
| 1 | 86 | | var optsMajInv = new HarmonyOptions { IncludeMajInSeventhInversions = true }; |
| 1 | 87 | | _ = HarmonyAnalyzer.AnalyzeTriad(pcsIIImaj7, keyMin, optsMajInv, new FourPartVoicing(91, 86, 79, 67), null); |
| | 88 | |
|
| | 89 | | // Best-effort self-checks: if canonical tokens don't match expected PC sets on cold start, |
| | 90 | | // re-invoke parsing once to ensure JIT/warm state is complete. |
| | 91 | | static bool SameSet(int[] a, int[] b) |
| | 92 | | { |
| 2 | 93 | | if (a is null || b is null) return false; |
| 2 | 94 | | if (a.Length != b.Length) return false; |
| 2 | 95 | | var aa = (int[])a.Clone(); |
| 2 | 96 | | var bb = (int[])b.Clone(); |
| 4 | 97 | | Array.Sort(aa); Array.Sort(bb); |
| 25 | 98 | | for (int i = 0; i < aa.Length; i++) if (aa[i] != bb[i]) return false; |
| 2 | 99 | | return true; |
| | 100 | | } |
| | 101 | |
|
| 1 | 102 | | int tonic = ((key.TonicMidi % 12) + 12) % 12; |
| | 103 | |
|
| | 104 | | // bIII should be a major triad on lowered mediant |
| 1 | 105 | | var gotBIII = RomanInputParser.Parse("bIII", key)[0].Pcs; |
| 1 | 106 | | var expBIII = new Chord((tonic + 3) % 12, ChordQuality.Major).PitchClasses().ToArray(); |
| 1 | 107 | | if (!SameSet(gotBIII, expBIII)) |
| | 108 | | { |
| 0 | 109 | | RomanInputParser.Parse("bIII", key); |
| | 110 | | } |
| | 111 | |
|
| | 112 | | // viiø/V should be half-diminished seventh on leading tone to the dominant |
| 1 | 113 | | int targetV = ((key.ScaleDegreeMidi(4) % 12) + 12) % 12; // V |
| 1 | 114 | | int ltToV = (targetV + 11) % 12; |
| 1 | 115 | | var expViiSec = new Chord(ltToV, ChordQuality.HalfDiminishedSeventh).PitchClasses().ToArray(); |
| 1 | 116 | | var gotViiSec = RomanInputParser.Parse("viiø/V", key)[0].Pcs; |
| 1 | 117 | | if (!SameSet(gotViiSec, expViiSec)) |
| | 118 | | { |
| 0 | 119 | | RomanInputParser.Parse("viiø/V", key); |
| | 120 | | } |
| 1 | 121 | | } |
| 0 | 122 | | catch |
| | 123 | | { |
| | 124 | | // best-effort only |
| 0 | 125 | | } |
| 1 | 126 | | } |
| | 127 | | } |
| | 128 | |
|
| | 129 | | #pragma warning restore CA2255 |
| | 130 | |
|