/// <summary> /// Constructs a new candidate with the given generation, based on the /// given composition structure bar sequence. /// <para> If <paramref name="includeExistingMelody"/> is set to true, /// the existing melody in the given composition structure would be copied /// into the bar sequence of the new instansiated candidate. /// Otherwise, only time signature and chord data would be copied, /// and the notes sequence in this candidate's bar sequence would be empty.</para> /// </summary> /// <param name="generation"> The generation of this candidate regarding the candidate /// population of the genetic algorithm which has this candidate in context. </param> /// <param name="compositionStructure"> Fully initialized bar sequence with /// time signatures, chords, and possibly notes, to base on this candidates bar sequence. </param> /// <param name="includeExistingMelody"> If set to true, melody from the given /// composition structure bar sequence would be copied into the candidate's bar sequence /// in addition to the time signature and chord, otherwise, only time signature and chord /// would be copied and notes would be set to empty collections. </param> internal MelodyCandidate(uint generation, IEnumerable <IBar> compositionStructure, bool includeExistingMelody = false) { // initialize the generation of the current candidate Generation = generation; // mark new candidate as "dirty" for evaluation IsDirty = true; // initialize bar sequence if (includeExistingMelody) { /* If this candidate should be based on an existing melody seed, * initialize bar sequence to include all data from the given composition * structure bar sequence: time signature, chords and notes as well. */ Bars = new List <IBar>(compositionStructure.Count()); foreach (IBar bar in compositionStructure) { Bars.Add(MusicTheoryFactory.CreateBar(bar)); } } else { /* If this candidate should not be based on any seed, * initialize bars to contain only the time signature and chords * from the given composition structure bar sequence, setting the * notes in each bar to an empty melody. */ Bars = CompositionContext.CloneChordProgressionBars(compositionStructure); } }
/// <summary> /// Clones the structure and chords from the given bar chord progression /// bar sequence, and returns a new bar sequence which contain the same /// time signatures and chords as the chord progression, but with an empty /// list of notes in each bar (empty melody). /// </summary> /// <param name="chordProgression"> Chord progression bar sequence to clone. </param> /// <returns> A new bar sequence which contains the same time signature durations /// and the same chord progression as the given bar sequence, but with an empty melody.</returns> internal static IList <IBar> CloneChordProgressionBars(IEnumerable <IBar> chordProgression) { // create a new empty bar sequence for the result IList <IBar> clonedBars = new List <IBar>(chordProgression.Count()); /* fill up the new bar sequence with the original bars' time signature & * chords but with an empty note sequence in each bar (empty melody). */ foreach (IBar bar in chordProgression) { clonedBars.Add(MusicTheoryFactory.CreateBar(bar.TimeSignature, bar.Chords)); } // return the cloned bars return(clonedBars); }
/// <summary> /// Implements a crossover between two or more candidate participants in N distinct points. /// </summary> /// <param name="participants"> Collection of candidates that are intended to paricipate /// as parents in the crossover proccess. </param> /// <param name="n"> The number of the crossover points for slicing the candidates. </param> /// <param name="optimizeCrossoverPointsSelection"> If set to true, an optimized wised /// selection of the n crossover points is made, by selecting points which reduce the intervals /// between the bars around the crossover points. If set to false, the crossover points /// are selected randomly. </param> /// <returns> New candidate solutions which are the offsprings of the participating candidates. /// The participating candidates are not changed or modified during the proccess. </returns> protected ICollection <MelodyCandidate> NPointCrossover( IList <MelodyCandidate> participants, int n, bool optimizeCrossoverPointsSelection = true) { // assure N isn't too big int numberOfBars = participants[0].Bars.Count; n = (n < numberOfBars) ? n : numberOfBars - 1; // initialization int currentPosition = 0; List <IBar> offspring1Bars, offspring2Bars; MelodyCandidate parent1, parent2, temp, offspring1, offspring2; List <MelodyCandidate> offsprings = new List <MelodyCandidate>(2 * n); // generate n uniqe crossover points int[] crossoverPoints; // crossover each pair of parent particpants for (int i = 0; i < participants.Count; i++) { // set first parent parent1 = participants[i]; // pair him with each other parent participant for (int j = i + 1; j < participants.Count; j++) { // set second parent participant parent2 = participants[j]; // iniate new empty twin offsprings offspring1Bars = new List <IBar>(numberOfBars); offspring2Bars = new List <IBar>(numberOfBars); // select n crossover points, either wisely or randomly crossoverPoints = optimizeCrossoverPointsSelection ? SelectOptimizedCrossoverPoints(parent1, parent2, n) : SelectRandomCrossoverPoints(ChordProgression.Count, n); // do the actual crossover currentPosition = 0; foreach (int crossoverPoint in crossoverPoints) { while (currentPosition < crossoverPoint) { offspring1Bars.Add(MusicTheoryFactory.CreateBar(parent1.Bars[currentPosition])); offspring2Bars.Add(MusicTheoryFactory.CreateBar(parent2.Bars[currentPosition])); currentPosition++; } // swap parents roll for the crossover switch temp = parent1; parent1 = parent2; parent2 = temp; } // complete filling rest of candidate bars from the other parent for (int k = currentPosition; k < numberOfBars; k++) { offspring1Bars.Add(MusicTheoryFactory.CreateBar(parent1.Bars[k])); offspring2Bars.Add(MusicTheoryFactory.CreateBar(parent2.Bars[k])); } // create two new twin offsprings based on the pre-filled bars from the crossover offspring1 = new MelodyCandidate(_currentGeneration, offspring1Bars, true); offspring2 = new MelodyCandidate(_currentGeneration, offspring2Bars, true); // add the new born offsprings to the result list offsprings.Add(offspring1); offsprings.Add(offspring2); } } return(offsprings); }
/// <inheritdoc cref="ReadChordsFromFile(string)"/> /// <param name="streamReader"> Input stream which contains the chords data. </param> public static IList <IBar> ReadChordsFromFile(StreamReader streamReader) { streamReader.BaseStream.Position = 0; // initialization uint lineNumber = 1; string currentLine = null; List <IBar> chordProgression = new List <IBar>(); IBar bar = null; NoteName chordRoot; ChordType chordType; byte barNumerator = 0; byte barDenominator = 0; byte numberOfBeats = 0; byte totalBeatsInBar = 0; string[] lineTokens = null; string[] barTimeSignature = null; string[] chordProperties = null; string customErrorMessage = string.Empty; string genericErrorMessage = $"Error parsing chord progression file.\n"; // parse file line after line while ((currentLine = streamReader.ReadLine()) != null) { // skip empty lines if (string.IsNullOrWhiteSpace(currentLine)) { continue; } // validate minimum tokens in line (at least a time signature and 1 chord) lineTokens = currentLine.Split('\b', '\t', ' '); if (lineTokens?.Length < 2) { customErrorMessage = $"Line {lineNumber} must include a time signature and at least one chord."; throw new FormatException(genericErrorMessage + customErrorMessage); } // set bar's time signature (first token of each line) barTimeSignature = lineTokens[0].Split('/'); if ((barTimeSignature?.Length != 2) || (!Byte.TryParse(barTimeSignature?[0], out barNumerator)) || (!Byte.TryParse(barTimeSignature?[1], out barDenominator))) { customErrorMessage = $"Invalid time signature format in line {lineNumber}: '{lineTokens[0]}'. The required format is 'numerator/denominator', for example 4/4."; throw new FormatException(genericErrorMessage + customErrorMessage); } bar = MusicTheoryFactory.CreateBar(MusicTheoryFactory.CreateDuration(barNumerator, barDenominator, false)); // set bar's chords (rest of tokens in line) for (int i = 1; i < lineTokens.Length; i++) { // skip white spaces between the tokens if (string.IsNullOrWhiteSpace(lineTokens[i])) { continue; } // parse chord properties: root, type & duration chordProperties = lineTokens[i].Split('-'); if ((!Enum.TryParse(chordProperties?[0], out chordRoot)) || (!Enum.TryParse(chordProperties?[1], out chordType)) || (!Byte.TryParse(chordProperties?[2], out numberOfBeats))) { customErrorMessage = $"Invalid chord format in line {lineNumber}: '{lineTokens[i]}'. The required format is '{typeof(NoteName)}-{typeof(ChordType)}-DurationInBeats', for example F-Major7-2."; throw new FormatException(genericErrorMessage + customErrorMessage); } totalBeatsInBar += numberOfBeats; bar.Chords.Add(MusicTheoryFactory.CreateChord(chordRoot, chordType, MusicTheoryFactory.CreateDuration(numberOfBeats, bar.TimeSignature.Denominator))); } // validate bar's chords total duration == bar's duration if (bar.TimeSignature.Numerator != totalBeatsInBar) { customErrorMessage = $"Line {lineNumber}: Total number of beats of chords in bar {chordProgression.Count + 1} which is {totalBeatsInBar} must be equal to the bar's key signature numerator, which is {bar.TimeSignature.Numerator}."; throw new FormatException(genericErrorMessage + customErrorMessage); } // add curent line's bar chordProgression.Add(bar); lineNumber++; // clean variables old values for next iteration bar = null; currentLine = customErrorMessage = string.Empty; lineTokens = chordProperties = barTimeSignature = null; barNumerator = barDenominator = numberOfBeats = totalBeatsInBar = 0; } return(chordProgression); }