Exemple #1
0
        /// <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);
        }
Exemple #2
0
        /// <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);
        }
Exemple #3
0
        /// <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);
        }
Exemple #4
0
        /// <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());
        }
Exemple #5
0
 /// <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);
     }
 }
Exemple #6
0
        /// <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);
        }
Exemple #7
0
        /// <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));
        }
Exemple #8
0
        /// <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));
        }
Exemple #9
0
        /// <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());
        }
Exemple #10
0
        /// <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);
        }
Exemple #11
0
        /// <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);
        }
Exemple #12
0
        /// <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);
        }
Exemple #13
0
        /// <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);
        }
Exemple #14
0
 /// <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);
 }
Exemple #15
0
        /// <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);
        }
Exemple #16
0
        /// <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);
        }
Exemple #17
0
 /// <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);
 }
Exemple #18
0
        /// <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);
        }
Exemple #19
0
        /// <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);
        }
Exemple #20
0
        /// <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);
        }
Exemple #21
0
        /// <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);
        }
Exemple #22
0
        /// <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);
        }
Exemple #23
0
        /// <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 });
            }
        }