/// <summary> /// Perfomrs Chomsky analysis and reduces list elements, extracting equal parts into newly created elements. /// The method is similar to compression. /// </summary> /// <param name="melodyPartNodes">All nodes including input and all produced nodes</param> /// <param name="scales"> /// Scales for diatonic option. Within the list no scale should be similar to any other. /// Two scales are similar if one is inversion of another one, /// for example major and minor scale, /// but minor haromnic, minor melodic and minor natural are not similar to each other. /// If null, no diatonic option. /// </param> public static List <MelodyPartList> Reduce(IEnumerable <MelodyPartList> melodyPartNodes, List <TwelveToneSet> scales = null) { List <MelodyPartList> allNodes = new List <MelodyPartList>(melodyPartNodes); int maxListCount = int.MaxValue; List <TwelveToneSet> allScales = CalculateAllScales(scales); Swap swap = FindLongestSwap(melodyPartNodes, allScales); int count = 500; while (swap.list.Count > 2) { swap.AssertCorrect(); // Assert the chunck size is not increasing Debug.Assert(swap.list.Count <= maxListCount); maxListCount = swap.list.Count; // If there is identical list already, don't add it, use the identical one MelodyPartList nodeExisting; if ((nodeExisting = allNodes.Find(node => node.IsIdentical(swap.list))) != null) { swap.list = nodeExisting; } else { allNodes.Add(swap.list); } //Assert that at most one part is whole if (swap.IsWhole1 && swap.IsWhole2 && swap.pnt1.part.type != MelodyPartList.Type.Voice && swap.pnt2.part.type != MelodyPartList.Type.Voice) { Debug.Fail(message: "There are two identical parts. One should have referenced another instead of being created."); } int offset1 = swap.pnt1.MelodyPart.GetNotes().First().note; swap.pnt1.part.RemoveRange(swap.pnt1.firstNoteIndex, swap.list.Count); MelodyPart mp1 = swap.IsDToC ? new MelodyPartDtoC(offset1, swap.scales1.First(), swap.list) : new MelodyPart(offset1, swap.list); swap.pnt1.part.Insert(swap.pnt1.firstNoteIndex, mp1); if (!swap.IsWhole2 || swap.IsDToC) { // If it's the same part, adjust the pnt2.index if (swap.pnt1.part == swap.pnt2.part) { swap.pnt2.firstNoteIndex -= swap.list.Count - 1; } int offset2 = swap.pnt2.MelodyPart.GetNotes().First().note; swap.pnt2.part.RemoveRange(swap.pnt2.firstNoteIndex, swap.list.Count); MelodyPart mp2 = swap.IsDToC ? new MelodyPartDtoC(offset2, swap.scales2.First(), swap.list) : new MelodyPart(offset2, swap.list); swap.pnt2.part.Insert(swap.pnt2.firstNoteIndex, mp2); } else { // This is unexpected, but may be possible. // If it happens rethink it. Debug.Assert(!swap.IsDToC); } if (--count <= 0) { break; } // Next swap swap = FindLongestSwap(allNodes, allScales); } ; foreach (var list in melodyPartNodes) { Debug.Assert(list.RecursiveCount > 1); } return(allNodes); }