/// <summary> /// Replaces a random concrete note pitch with a hold note. /// </summary> /// <param name="melody"> The candidate melody which contains the bar sequence to operate on. </param> /// <param name="barIndex"> An index of specific requested bar to operate on. </param> private protected virtual void ToggleToHoldNoteMutation(MelodyCandidate melody, int?barIndex) { // intialize random generator Random random = new Random(); // fetch the requested bar randomly select one if no specific bar is requested barIndex = barIndex ?? random.Next(melody.Bars.Count); IBar selectedBar = melody.Bars[(int)barIndex]; // fetch potential notes for the toggle (filter rest, hold & first notes in bar) IList <INote> relevantNotes = selectedBar.Notes.Where((note, noteIndex) => noteIndex > 0 && note.Pitch != NotePitch.RestNote && note.Pitch != NotePitch.HoldNote).ToList(); // assure at least one note as found if (!relevantNotes.Any()) { return; } // fetch a random note from the potential notes found int randomNoteIndex = random.Next(relevantNotes.Count); INote selectedNote = relevantNotes[randomNoteIndex]; // get original index of the selected note in the original sequence int originalNoteIndex = selectedBar.Notes.IndexOf(selectedNote); // replace selected note with a hold note INote holdNote = MusicTheoryFactory.CreateNote(NotePitch.HoldNote, selectedNote.Duration); selectedBar.Notes.RemoveAt(originalNoteIndex); selectedBar.Notes.Insert(originalNoteIndex, holdNote); }
/// <summary> /// Swaps the positions of two chord notes in the given bar, /// or in a randomly selected bar if <paramref name="barIndex"/> is null. /// <para>A random chord in the selected bar is selected, and two randomly /// selected notes which are played under this chord's time span are swapped /// by their positions.</para> /// </summary> /// <param name="melody"> The candidate melody to operate on.</param> /// <param name="barIndex"> Index of the bar to do the swap in. If no bar index supplied, then a random bar would be selected. </param> private protected virtual void SwapTwoNotesMutation(MelodyCandidate melody, int?barIndex = null) { // initialization INote note1, note2; IList <int> noteIndices; Random random = new Random(); int note1Index, note2Index, randomIndex1, randomIndex2, chordIndex; // if no specific bar has been requested then set it randomly int selectedBarIndex = barIndex ?? random.Next(melody.Bars.Count); IBar bar = melody.Bars[selectedBarIndex]; chordIndex = random.Next(bar.Chords.Count); bar.GetOverlappingNotesForChord(chordIndex, out noteIndices); // assure selected chord in bar contains at least two notes if (noteIndices.Count < 2) { return; } // select two random notes from withing the selected chord notes randomIndex1 = random.Next(1, noteIndices.Count); randomIndex2 = random.Next(0, randomIndex1); note1Index = noteIndices[randomIndex1]; note2Index = noteIndices[randomIndex2]; note1 = bar.Notes[note1Index]; note2 = bar.Notes[note2Index]; // swap the notes positions in the bar bar.Notes.RemoveAt(note1Index); bar.Notes.Insert(note1Index, note2); bar.Notes.RemoveAt(note2Index); bar.Notes.Insert(note2Index, note1); }
/// <summary> /// Randomly selects two consecutive notes in the given bar, or in a randomly selected /// bar if <paramref name="barIndex"/> is null, and unifies the two consecutive notes /// into one note by removing the consecutive note entirely and adding it's /// duration length to the first note. /// </summary> /// <param name="melody"> The candidate melody to operate on.</param> /// <param name="barIndex"> Index of the bar to do the unification in. If no bar index supplied, then a random bar would be selected. </param> private protected virtual void DurationUnifyMutation(MelodyCandidate melody, int?barIndex = null) { // initialization INote note1, note2, newNote; IDuration newDuration; int note1Index, note2Index; Random random = new Random(); // if no specific bar has been requested then set it randomly int selectedBarIndex = barIndex ?? random.Next(melody.Bars.Count); IBar bar = melody.Bars[selectedBarIndex]; // assure selected bar contains at least two notes if (bar.Notes.Count < 2) { return; } // randomly select two consecutive notes from within the selected bar note1Index = random.Next(1, bar.Notes.Count); note2Index = note1Index - 1; note1 = bar.Notes[note1Index]; note2 = bar.Notes[note2Index]; /* create a new note with the pitch of the first note and overall duration * duration of the sum of the two notes durations */ newDuration = note1.Duration.Add(note2.Duration); newNote = MusicTheoryFactory.CreateNote(note1.Pitch, newDuration); // replace the two consecutive note with the new note bar.Notes.RemoveAt(note1Index); bar.Notes.Insert(note1Index, newNote); bar.Notes.RemoveAt(note2Index); }
/// <summary> /// Utility method for selecting crossover points in a way that minimizes the interval outcome /// in the transition point after the crossover. /// <para> /// A radnomly selected crossover point could cause an unexpected extreme interval between the /// bars of the candidate arund the crosspoint, because the participants might carry melodies /// on significantly different pitch ranges. /// By selecting the points wisely, this side-effect is mitigated substantialy. /// </para> /// </summary> /// <param name="parent1"> The first crossover participant. </param> /// <param name="parent2"> The second crossover participant. </param> /// <param name="n"> The required number of crossover points. </param> /// <returns> Array containing n indices of the recommended crossover points. </returns> protected int[] SelectOptimizedCrossoverPoints(MelodyCandidate parent1, MelodyCandidate parent2, int n) { // get all non-empty bars except the first one, and project their indices as well var allNonEmptyBarsButFirstWithBarIndex = parent1.Bars .Except(new[] { parent1.Bars.First() }) .Where(b => b.Notes.Any()) .Select((bar, barIndex) => new { Bar = bar, BarIndex = barIndex + 1, }); /* for each bar, project the interval distance between the last note * from the preceding bar to the first note of the current bar */ var barIndicesWithTransitionInterval = allNonEmptyBarsButFirstWithBarIndex .Select(x => new { BarIndex = x.BarIndex, FirstInterval = Math.Abs((int)parent1.Bars[x.BarIndex].Notes .First()?.Pitch - (int)parent2.Bars[x.BarIndex - 1].Notes.Last()?.Pitch), SecondInterval = Math.Abs((int)parent1.Bars[x.BarIndex - 1].Notes .Last()?.Pitch - (int)parent2.Bars[x.BarIndex].Notes.First()?.Pitch) }); // project the indices of the bars with lowest interval outcome var orderedBarIndicesByMinimumInterval = barIndicesWithTransitionInterval .OrderBy(x => x.FirstInterval + x.SecondInterval) .Take(n) .Select(x => x.BarIndex); // return the selected indices ordered by index in ascending order return(orderedBarIndicesByMinimumInterval .OrderBy(crossoverPointIndex => crossoverPointIndex) .ToArray()); }
/// <summary> /// Reverses the order of all note sequences in the given melody. /// <para> The revese operation is made in place locally to each chord. /// The chord notes are determined by the logic in /// <see cref="Bar.GetOverlappingNotesForChord(IChord, out IList{int})"/>.</para> /// </summary> /// <param name="melody"> The candidate melody to operate on.</param> private protected virtual void ReverseAllNotesMutation(MelodyCandidate melody) { // delegate reverse operation to superclass foreach (IBar bar in melody.Bars) { PermutateNotes(bar, permutation: Permutation.Reversed); } }
/// <summary> /// Reverses the order of the note sequence in the given bar, /// or in a randomly selected bar if <paramref name="barIndex"/> is null. /// <para> The revese operation is made in place locally to each chord. /// The chord notes are determined by the logic in /// <see cref="Bar.GetOverlappingNotesForChord(IChord, out IList{int})"/>.</para> /// </summary> /// <param name="melody"> The candidate melody to operate on.</param> /// <param name="barIndex"> Index of the bar to do the reverse in. If no bar index supplied, then a random bar would be selected. </param> private protected virtual void ReverseBarNotesMutation(MelodyCandidate melody, int?barIndex = null) { // if no specific bar has been requested then set it randomly int selectedBarIndex = barIndex ?? new Random().Next(melody.Bars.Count); IBar selectedBar = melody.Bars[selectedBarIndex]; // delegate reverse operation to superclass PermutateNotes(selectedBar, permutation: Permutation.Reversed); }
/// <summary> /// Evaluates fitness according to the note density balance across the melodie's bars. /// <para> This fitness function objective is to assure the amount of notes in each bar /// is more or less balanced, and mitigate obscure sounding phrases of which one bar /// is very dense and another is very sparse, which in general leads to an un pleasant /// drastic change in feel. /// </para> /// <para> Inorder to acheive this objective, the evaluation calculates the /// average of notes in each bar and the standard deviation, and gives a high score /// to candidate of which their average and standard deviation are close.</para> /// </summary> /// <param name="candidate"> The melody candidate to evaluate. </param> /// <returns> The fitness outcome score for the requested evaluation. </returns> private protected double EvaluateDensityBalance(MelodyCandidate candidate) { // calculate averge number of notes in a bar int numOfNotesInBarAverage = (int)Math.Round(candidate.Bars.Average(b => b.Notes.Count)); // calculate the bar set standard deviation from the average double mutualStandardDeviation = Math.Sqrt(candidate.Bars .Select(b => Math.Pow((numOfNotesInBarAverage - b.Notes.Count), 2)) .Average()); // return inversed ratio of distance of standard deviarion from average return(1 - (mutualStandardDeviation / numOfNotesInBarAverage)); }
/// <summary> /// Evaluates the ratio of extreme pitch intervals. /// <para> This evaluation checks the pitch interval between every two consecutive /// notes and check if the interval is considered extreme or not, in regard to the /// <paramref name="maxInterval"/> parameter: If the interval is higher than the /// max interval parameter, the distance between the notes would be considered as /// an extreme distance. </para> /// <para> Besides counting the amount of existing extreme intervals, /// this evaluation takes into account the extreme level, i.e., how large in /// terms of semitones the interval is, so a two octave interval is considered twice /// as extreme than a one octave interval. </para> /// <para> The overall fitness is a weighted sum of the ratio between the non-extreme /// intervals to the total number of consecutive notes intervals, and the invert ratio /// of total semitone distance in the entire melody and the max pitch range. </para> /// </summary> /// <param name="maxInterval"> Max interval that is considered "okay" between two /// consecutive notes. Any interval that exceeds this one would be considered extreme. /// </param> /// <param name="candidate"> The melody candidate to evaluate. </param> /// <returns> The fitness outcome score for the requested evaluation. </returns> private protected double EvaluateExtremeIntervals(MelodyCandidate candidate, PitchInterval maxInterval = PitchInterval.PerfectFifth) { // initialize counters & accumuators ulong totalNumOfIntervals = 0; ulong numOfExtremeIntervals = 0; ulong overallDeviation = 0; int adjacentDistance = 0; // init max pitch range int pitchRange = MaxPitch - MinPitch; // initialize metrics double extremeIntervalMetric; double overallDeviationMetric; // convert max interval nullable int to int int maxDistance = (int)maxInterval; // retrieve and filter all pitches which are not hold/rest notes NotePitch[] pitches = candidate.Bars.GetAllPitches().ToArray(); // accumulate extreme adjacent notes pitch distance for (int i = 0; i < pitches.Length - 1; i++) { // calculate pitch distance between the two adjacent notes adjacentDistance = Math.Abs(pitches[i + 1] - pitches[i]); // if this pitch is extreme, update counter & deviation accumulator if (adjacentDistance > maxDistance) { numOfExtremeIntervals++; overallDeviation += (ulong)(adjacentDistance - maxDistance); } } // set total number of intervals totalNumOfIntervals = (ulong)pitches.Length - 1; // calculate ratio of extreme intervals which exceed the requested max interval extremeIntervalMetric = (totalNumOfIntervals - numOfExtremeIntervals) / (float)totalNumOfIntervals; // calculate metric of overal deviation from max interval overallDeviationMetric = (numOfExtremeIntervals == 0) ? 1 : numOfExtremeIntervals / overallDeviation; // return weighted fitness according to the two different metrics return((0.4 * extremeIntervalMetric) + (0.6 * overallDeviationMetric)); }
/// <summary> /// Evaluates fitness according to the amount of existing syncopations. /// This fitness is calculated as the ratio between the amount of existing syncopations /// in the melody, and the total amount of real pitched notes, i.e., not hold and rest /// notes. /// <para> /// This evaluation considers a note to be a syncopation if it a pitch note, /// it starts on an "off-beat" (not on beginning or middle of bar), /// and it's length is a quarter beat length or longer. /// </para> /// </summary> /// <param name="candidate"> The melody candidate to evaluate. </param> /// <returns> The fitness outcome score for the requested evaluation. </returns> private protected double EvaluateSyncopation(MelodyCandidate candidate) { // initialization IBar bar; INote note; float barDuration, noteDuration; float noteStartTime = 0; uint syncopationCounter = 0; // scan all bars of the subject candidate for (int i = 0; i < candidate.Bars.Count; i++) { // get current bar's duration bar = candidate.Bars[i]; barDuration = (float)bar.TimeSignature.Numerator / bar.TimeSignature.Denominator; // reset note start time in relation to it's containing bar noteStartTime = 0; // scan all notes in current bar for (int j = 0; j < bar.Notes.Count; j++) { // fetch current note note = bar.Notes[j]; // get the duration note in context of the melody it resides in noteDuration = note.GetDurationInContext(bar, candidate.Bars, j, i); /* consider a note to be syncoped if it is a quarter beat or longer, * it does not start on an offbeat of the bar, * and it is not a hold note or a rest note*/ if (noteDuration >= Duration.QuaterNoteFraction && bar.IsOffBeatNote(noteStartTime, barDuration) && note.Pitch != NotePitch.RestNote && note.Pitch != NotePitch.HoldNote) { syncopationCounter++; } } } // return fitness as ratio between the number of syncopes and total "real" notes return((float)syncopationCounter / candidate.Bars .SelectMany(b => b.Notes) .Where(n => n.Pitch != NotePitch.RestNote && n.Pitch != NotePitch.HoldNote) .Count()); }
/// <summary> /// Replaces a random note in the given bar with two new shorter notes /// with durations that sum up together to the originals note duration. /// Regarding pitch, one of the new notes after split would have the originals note pitch, /// and the other note after split would have a pitch which is minor or major second away from /// the original note pitch. /// </summary> /// <param name="melody"> The candidate melody to operate on.</param> /// <param name="barIndex"> Index of the bar to do operate on. If no bar index supplied, then a random bar would be selected. </param> private protected virtual void DurationSplitMutation(MelodyCandidate melody, int?barIndex) { int index = barIndex ?? new Random().Next(melody.Bars.Count); Action <MelodyCandidate, int?>[] durationSplitters = { DurationEqualSplitMutation, DurationAnticipationSplitMutation, DurationDelaySplitMutation }; int randomIndex = new Random().Next(durationSplitters.Length); Action <MelodyCandidate, int?> durationSplitMutation = durationSplitters[randomIndex]; durationSplitMutation(melody, barIndex); }
/// <summary> /// Evaluates fitness according to the melody's pitch range. /// This fitness is calculated as the ration between the user's requested pitch range, /// and the actual candidate's melody pitch range. /// </summary> /// <param name="candidate"> The melody candidate to evaluate. </param> /// <returns> The fitness outcome score for the requested evaluation. </returns> private protected double EvaluatePitchRange(MelodyCandidate candidate) { // get user preference for pitch range int requestedRange = MaxPitch - MinPitch; // calculate the actual pitch range in this candidate's melody IEnumerable <NotePitch> allPitches = candidate.Bars.GetAllPitches(); NotePitch actualMaxPitch = allPitches.Max(p => p); NotePitch actualMinPitch = allPitches.Min(p => p); int actualRange = actualMaxPitch - actualMinPitch; // return fitness as ration between the actual range and requested range return((double)actualRange / requestedRange); }
/// <summary> /// Reverses the order of a sequence of notes for a randomly selected /// chord in the given bar, or in a randomly selected bar if <paramref name="barIndex"/> is null. /// <para> The revese operation is made in place locally to the selected chord. /// The chord notes are determined by the logic in /// <see cref="Bar.GetOverlappingNotesForChord(IChord, out IList{int})"/>.</para> /// </summary> /// <param name="melody"> The candidate melody to operate on.</param> /// <param name="barIndex"> Index of the bar to do the reverse in. If no bar index supplied, then a random bar would be selected. </param> private protected virtual void ReverseChordNotesMutation(MelodyCandidate melody, int?barIndex = null) { // initialize random generator Random randomizer = new Random(); // if no specific bar has been requested then set it randomly int selectedBarIndex = barIndex ?? randomizer.Next(melody.Bars.Count); IBar selectedBar = melody.Bars[selectedBarIndex]; // select a random chord from within the selected bar int randomChordIndex = randomizer.Next(selectedBar.Chords.Count); IChord selectedChord = selectedBar.Chords[randomChordIndex]; // create a single chord element sequence IChord[] chordsToReverse = new[] { selectedChord }; // delegate reverse operation to superclass PermutateNotes(selectedBar, chordsToReverse, Permutation.Reversed); }
/// <summary> /// Evaluates fitness according to the variety of distinct pitches in use. /// </summary> /// <param name="candidate"> The melody candidate to evaluate. </param> /// <returns> The fitness outcome score for the requested evaluation. </returns> private protected double EvaluatePitchVariety(MelodyCandidate candidate) { // initialization int numOfTotalPitches = 0; int numOfDistinctPitches = 0; double distinctPitchesRatio = 0; double barFitness, totalFitness = 0; IEnumerable <NotePitch> barPitches; // caluclate fitness for the individual bars foreach (IBar bar in candidate.Bars) { // get pure pitches for current bar (discard hold & rest notes) barPitches = bar.GetAllPitches(); // count number of overall pitches and distinct pitches numOfTotalPitches = barPitches.Count(); numOfDistinctPitches = barPitches.Distinct().Count(); // assure there is at least one pitch in current bar if (numOfTotalPitches == 0) { continue; } // calculate ratio between num of distinct pitches to overall num of pitches distinctPitchesRatio = ((double)(numOfDistinctPitches)) / numOfTotalPitches; // set current bar fitness proportionally to number of bars barFitness = distinctPitchesRatio / candidate.Bars.Count; // update total fitness accumulator totalFitness += barFitness; } // return accumulated fitness return(totalFitness); }
/// <summary> <inheritdoc cref="Composer.SyncopizeANote(IList{IBar}, int?)"/></summary> /// <param name="melody"> The candidate melody which contains the bar sequence to operate on. </param> /// <param name="barIndex"> <inheritdoc cref="Composer.SyncopizeANote(IList{IBar}, int?)"/> "</param> private protected virtual void SyncopedNoteMutation(MelodyCandidate melody, int?barIndex = null) { SyncopizeANote(melody.Bars, barIndex); }
/// <inheritdoc cref="DurationSplitMutation(MelodyCandidate, int?)"/> private protected virtual void DurationDelaySplitMutation(MelodyCandidate melody, int?barIndex) { int index = barIndex ?? new Random().Next(melody.Bars.Count); NoteDurationSplit(melody.Bars[index], DurationSplitRatio.Delay); }
/// <summary> /// Selects a random note in the requested bar and changes its pitch to one of the scale pitches. /// </summary> /// <param name="melody"> The candidate melody to operate on.</param> /// <param name="barIndex"> Index of the mutated bar. If no bar index supplied, then a random bar would be selected. </param> private protected virtual void ScalePitchMutation(MelodyCandidate melody, int?barIndex) { int index = barIndex ?? new Random().Next(melody.Bars.Count); ChangePitchForARandomNote(melody.Bars[index], mappingSource: ChordNoteMappingSource.Scale); }
/// <summary> /// Replaces a random hold note with a concrete note pitch. /// <para> This method selectes a bar from within the melody /// that contains a hold note, and replaces it with a "regular" note /// by setting the pitch to the adjacent preceding note, if such exists, /// or otherwise, to the adjacent succeeding note. </para> /// </summary> /// <param name="melody"> The candidate melody which contains the bar sequence to operate on. </param> /// <param name="barIndex"> An index of specific requested bar to operate on, which /// contains a hold note. If set to null, or if requested bar does not contain any /// hold notes, then some other bar which contains a hold note would be selected, /// if such bar exists. </param> private protected virtual void ToggleFromHoldNoteMutation(MelodyCandidate melody, int?barIndex) { // delegate actual mutation to base class ToggleAHoldNote(melody.Bars, barIndex); }
/// <summary> /// Evaluates fitness according to the melody's contour direction stability. /// <para> /// Melodies which tend to have more directional flow consecutively, i.e., /// sequences of ascending and descending notes one after the other, /// would generally score higher. /// </para> /// <para> /// This evaluation differs from <see cref="EvaluateContourDirection(MelodyCandidate)"/>, /// by evaluating consecutive sequences of directional intervals, assuring the ups and /// downs are not randomly distributed, but rather stable consistent. /// For current implementation, only directional sequences of 3 notes or more /// are taken into account for positive evaluation. /// </para> /// </summary> /// <param name="candidate"> The melody candidate to evaluate. </param> /// <returns> The fitness outcome score for the requested evaluation. </returns> private protected double EvaluateContourStability(MelodyCandidate candidate) { // init sign holders of previous and current interval direction (up/down) int currentIntervalSign = 1; int prevDirectionSign = 1; // init counters for sequences of consecutive notes in the same direction int directionalSequenceCounter = 0; int directionalIntervalAccumulator = 0; // init individual pitch placeholders and get sequence of all pitches NotePitch prevPitch, currentPitch; NotePitch[] pitches = candidate.Bars.GetAllPitches().ToArray(); // assure there is at least one interval (at least two pitches) if (pitches.Length <= 1) { return(0); } // initially set the previous sign to the first one if (pitches[1] - pitches[0] >= 0) { prevDirectionSign = 1; // ascending direction } else { prevDirectionSign = -1; // descending direction } // scan all intervals for (int i = 1; i < pitches.Length; i++) { // update previous and current pitches prevPitch = pitches[i - 1]; currentPitch = pitches[i]; // calculate the current interval direction (up/down) currentIntervalSign = currentPitch - prevPitch; // if the direction hasn't changed, just update sequence counter if (currentIntervalSign * prevDirectionSign >= 0) { directionalSequenceCounter++; } else // direction has changed { // if last sequence was long enough, add it to accumulator if (directionalSequenceCounter >= 3) { directionalIntervalAccumulator += directionalSequenceCounter; } // reset directional sequence counter directionalSequenceCounter = 1; // reset direction according to last interval prevDirectionSign *= -1; } } /* return ratio between the total number of consecutive directional intervals * and the overall number of intervals in the candidate's melody plus a factor */ double factor = 0.4; return(((double)directionalIntervalAccumulator / (pitches.Length - 1)) + factor); }
/// <summary> /// Evaluates fitness according to the melody's contour direction. /// <para> /// Melodies which tend to have more directional flow, i.e., /// sequences of ascending and descending notes, would generally score higher. /// </para> /// </summary> /// <param name="candidate"> The melody candidate to evaluate. </param> /// <returns> The fitness outcome score for the requested evaluation. </returns> private protected double EvaluateContourDirection(MelodyCandidate candidate) { // initialization int numberOfUpsInBar = 0; int numberOfDownsInBar = 0; int totalNumberOfUps = 0; int totalNumberOfDowns = 0; int totalNumberOfIntervals = 0; NotePitch prevPitch, currentPitch; NotePitch[] barPitches; double barFitness = 0; double totalBarsFitness = 0; double overallFitness = 0; double weightedFitness = 0; double factor = 0.25; int barContourDirectionRadius = 0; int overallContourDirectionRadius = 0; // calculate micro fitness in bar level foreach (var bar in candidate.Bars) { // initialization barFitness = 0; numberOfUpsInBar = 0; numberOfDownsInBar = 0; // get pure pitches for current bar (discard hold & rest notes) barPitches = bar.GetAllPitches().ToArray(); // assure there is at least one pitch in current bar if (barPitches.Length <= 1) { continue; } // count number of ups in downs in bar for (int i = 1; i < barPitches.Length; i++) { prevPitch = barPitches[i - 1]; currentPitch = barPitches[i]; if (currentPitch - prevPitch >= 0) { numberOfUpsInBar++; } else { numberOfDownsInBar++; } } // calculate direction radius and fitness of current bar barContourDirectionRadius = Math.Abs(numberOfUpsInBar - numberOfDownsInBar); barFitness = (double)barContourDirectionRadius / (barPitches.Length - 1); // update the total bar fitness totalBarsFitness += barFitness / candidate.Bars.Count; // update accumulator counters totalNumberOfUps += numberOfUpsInBar; totalNumberOfDowns += numberOfDownsInBar; totalNumberOfIntervals += barPitches.Length - 1; } // calculate macro fitness in candidate level overallContourDirectionRadius = Math.Abs(totalNumberOfUps - totalNumberOfDowns); overallFitness = (double)overallContourDirectionRadius / totalNumberOfIntervals; // return a weighted fitness combined from micro & macro fitnesses weightedFitness = (0.75 * totalBarsFitness) + (0.25 * overallFitness); return(weightedFitness + factor); }
/// <summary> /// Evaluates accented beats in each bar, in terms of the accented pitches and their /// preceding notes, regarding the transition they create towards the accented beats. /// <para> In general, accented beats (for example beats 1 and 3 on a 4/4 bar) sound /// specially well when the note played on them is one of the underlying chord notes. </para> /// <para> Moreover, when the preceding note creats a tension which is solved under the /// accented beat (for example a transition of half a tone, or a perfect fifth when there is /// a tritone interval in the background), the accented note sounds so much better. </para> /// <para> This fitness function objective is to find such good accented beats with good /// leading preceding notes and award this candidate accordingly, in relation to the overall /// amount of strong accented notes and their leading transitions. </para> /// </summary> /// <param name="candidate"> The melody candidate to evaluate. </param> /// <returns> The fitness outcome score for the requested evaluation. </returns> private protected double EvaluateAccentedBeats(MelodyCandidate candidate) { // initialization IBar bar; IChord chord; int noteIndex = 0; double weightedSum; INote note, precedingNote; IList <int> chordNotesIndices; IEnumerable <NotePitch> chordMappedPitches; // total accented beats (beats that are not off-beats) int strongBeatsCounter = 0; // interval between a strong beat note & it's preceding note int transitionDistance = 0; // chord notes that fall on strong beats int goodPrecedingNotesCounter = 0; // offbeats that lead to a strong beat via a minor/major second, or perfect 4th/5th int goodNotesOnStrongBeatsCounter = 0; // start iterating all bars for (int barIndex = 0; barIndex < candidate.Bars.Count; barIndex++) { // fetch next bar bar = candidate.Bars[barIndex]; // iterate over all chords in bar for (int chordIndex = 0; chordIndex < bar.Chords.Count; chordIndex++) { // fetch next chord chord = bar.Chords[chordIndex]; // get the good sounding pitches under this chord chordMappedPitches = chord.GetArpeggioNotes(MinPitch, MaxPitch); // get the actual notes played under this chord bar.GetOverlappingNotesForChord(chordIndex, out chordNotesIndices); // check if the actual notes are good for (int i = 0; i < chordNotesIndices.Count; i++) { // fetch next note under the current chord noteIndex = chordNotesIndices[i]; note = bar.Notes[noteIndex]; // the test is relevant only if this is an accented beat (NOT an off-beat) if (!note.IsOffBeatNote(bar)) { // update the total amount of strong accented beats strongBeatsCounter++; // bonus the candidate if current pitch sounds good under current chord if (chordMappedPitches.Contains(note.Pitch)) { goodNotesOnStrongBeatsCounter++; } // fetch preceding note to if it owns a bonus for a good transition precedingNote = candidate.Bars.GetPredecessorNote(true, barIndex, noteIndex, out int prevBarIndex, out int prevNoteIndex); // assure a preceding note has been found if (precedingNote != null) { // calculate interval distance between current note and it's predecessor transitionDistance = Math.Abs((byte)note.Pitch - (byte)precedingNote.Pitch); // bonus if this a dominant or smooth transition towards the strong beat note if (transitionDistance <= (byte)PitchInterval.MajorSecond || (transitionDistance == (byte)PitchInterval.PerfectFourth && (byte)precedingNote.Pitch < (byte)note.Pitch) || (transitionDistance == (byte)PitchInterval.PerfectFifth && (byte)precedingNote.Pitch > (byte)note.Pitch)) { goodPrecedingNotesCounter++; } } } } } } // calculate weighted sum of bonuses for the good strong beats and their preceding notes weightedSum = (goodNotesOnStrongBeatsCounter * 0.5) + (goodPrecedingNotesCounter * 0.5); // return evaluation grade as ratio between the total awarded bonus and overall strong beats return(weightedSum / strongBeatsCounter); }
/// <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); }
/// <summary> /// Evaluates how "smooth" the movement is from tone to another. /// <para> This evaluation is based on the intervals between consecutive notes /// and the notes' degrees in context of their underlying chord. /// According to the interval type (perfect, consonants, dissonant) and the /// note's degree (chord note, diatonic note, etc) a weighted fitness is set. </para> /// </summary> /// <param name="candidate"> The melody candidate to evaluate. </param> /// <returns> The fitness outcome score for the requested evaluation. </returns> private protected double EvaluateSmoothMovement(MelodyCandidate candidate, PitchInterval maxInterval = PitchInterval.PerfectFifth) { // initialize counters ulong totalNumOfIntervals = 0; ulong numOfDiatonicSteps = 0; ulong numOfChordSteps = 0; ulong numOfDissonants = 0; ulong numOfPerfectConsonants = 0; ulong numOfImperfectConsonants = 0; ulong numOfTritones = 0; ulong numOfBigLeaps = 0; int adjacentDistance = 0; // initialize metrics double diatonicStepsMetric; double chordStepsMetric; double intervalTypeMetric; // additional initializtions double fitness = 0; double factor = 0.5; // factor for balancing this fitness function PitchInterval interval; int maxDistance = (int)maxInterval; // retrieve and filter all pitches which are not hold/rest notes NotePitch[] pitches = candidate.Bars.GetAllPitches().ToArray(); // accumulate extreme adjacent notes pitch distance for (int i = 0; i < pitches.Length - 1; i++) { // calculate pitch distance between the two adjacent notes adjacentDistance = Math.Abs(pitches[i + 1] - pitches[i]); // update relevant counters according to step size Enum.TryParse(adjacentDistance.ToString(), out interval); switch (interval) { case PitchInterval.Unison: numOfPerfectConsonants++; break; case PitchInterval.MinorSecond: case PitchInterval.MajorSecond: numOfDissonants++; numOfDiatonicSteps++; break; case PitchInterval.MinorThird: case PitchInterval.MajorThird: numOfChordSteps++; numOfImperfectConsonants++; break; case PitchInterval.PerfectFourth: numOfPerfectConsonants++; break; case PitchInterval.Tritone: numOfTritones++; break; case PitchInterval.PerfectFifth: numOfChordSteps++; numOfPerfectConsonants++; break; case PitchInterval.MinorSixth: case PitchInterval.MajorSixth: numOfImperfectConsonants++; break; case PitchInterval.MinorSeventh: case PitchInterval.MajorSeventh: numOfDissonants++; break; case PitchInterval.Octave: numOfChordSteps++; numOfPerfectConsonants++; break; default: numOfBigLeaps++; break; } } // set total number of intervals totalNumOfIntervals = (ulong)pitches.Length - 1; // calculate diatonic steps ratio metric diatonicStepsMetric = numOfDiatonicSteps / totalNumOfIntervals; // calculate chord steps ratio metric chordStepsMetric = numOfChordSteps / totalNumOfIntervals; // calculate chord steps ratio metric with weighted sum according to interval type intervalTypeMetric = ((numOfPerfectConsonants * 1.0) + (numOfImperfectConsonants * 0.8) + (numOfDissonants * 0.6) + (numOfTritones * 0.3) + (numOfBigLeaps * 0.1)) / totalNumOfIntervals; // calculate total weighted fitness according to the different metrics fitness = (0.50 * diatonicStepsMetric) + (0.30 * chordStepsMetric) + (0.20 * intervalTypeMetric); // return fitness result return(fitness + factor); }
/// <summary> /// Initialize first generation of solution candidates. /// </summary> protected virtual void PopulateFirstGeneration() { MelodyCandidate candidate, reversedCandidate, seedCandidate, reversedSeedCandidate; ICollection <MelodyCandidate> offspringCandidates; List <MelodyCandidate> crossoverParticipants; // initialize basic candidates with generic arpeggeio & scale note sequences foreach (Action <IEnumerable <IBar> > initializer in _initializers) { // create a new empty candidate melody candidate = new MelodyCandidate(_currentGeneration, ChordProgression); // initialize it with current iterated initializer initializer(candidate.Bars); // duplicate the newly created candidate reversedCandidate = new MelodyCandidate(_currentGeneration, candidate.Bars, true); // reverse the duplicated candidate note ReverseAllNotesMutation(reversedCandidate); // add the new candidates to candidate collection _candidates.Add(candidate); _candidates.Add(reversedCandidate); } if (Seed != null) { // encapsulate the bar collection from the seed in a candidate entity seedCandidate = new MelodyCandidate(_currentGeneration, Seed, includeExistingMelody: true); reversedSeedCandidate = new MelodyCandidate(_currentGeneration, Seed, includeExistingMelody: true); // define the number of points for the crossover int n = 1; // Seed.Count / 4; // initialize a list of offspring candidates that would be returned from the crossover List <MelodyCandidate> offSpringCandidatesList = new List <MelodyCandidate>(); // crossover all the existing candidates with the seed candidate & it's reverse foreach (var generatedCandidate in _candidates) { // crossover with seed itself crossoverParticipants = new List <MelodyCandidate> { seedCandidate, generatedCandidate }; offspringCandidates = NPointCrossover(crossoverParticipants, n); offSpringCandidatesList.AddRange(offspringCandidates); // crossover with reversed seed crossoverParticipants = new List <MelodyCandidate> { reversedSeedCandidate, generatedCandidate }; offspringCandidates = NPointCrossover(crossoverParticipants, n); offSpringCandidatesList.AddRange(offspringCandidates); } // add seed and all crossover offsprings to the candidate list _candidates.AddRange(offSpringCandidatesList); _candidates.AddRange(new[] { seedCandidate, reversedSeedCandidate }); } }