A genetic algorithm is an optimization algorithm used to efficiently generate solutions, especially when dealing with problems with large search spaces. This is often useful in creative contexts, like music, because it has the capability to generate multiple different solutions due to its random nature. GA works in a process similar to evolution - parents of a population breed, their children mutate, and those with the highest fitness score are selected. In this computational essay, I use a GA to generate melodies.

Introduction

In this computational essay, I dive into the use of a genetic algorithm to generate melodies. My algorithm can be broken down into a couple sections, abstractly defined below:
1. Initialization of all necessary variables. A random sample of 16-measure notes are generated.
2. crossover. Two parents are chosen at random, to which they are merged together based on a “turning point.”
3. mutation. During each generation, there is a possibility that the melodies can mutate - which includes being able to shuffle, copy, reverse, and tweak pitches and rhythms. Every child is passed through the “mutation” function.
4. fitnessFunction. After the new generation is created, we use a “survival of the fittest” approach, taking those only with the highest fitness function values. This function is based on the contour of the melody, the number of steps and leaps, how well it fits into the given harmonic progression, and others.
In[]:=
geneticAlg[fitness_,population_,crossover_,mutation_,popsize_,inversionprob_,popsurvive_,degreevar_,maxpat_,maxgap_,notes_,probtweak_,probshuffle_,probmutate_,probaddnotes_,probremovenotes_,probinversion_,probcopy_,beatval_,chords_,mainkey_]:=​​Module[​​{children,babies=Table[crossover@@RandomSample[population,2],popsize]},​​children=Table[​​mutation[x,notes,probtweak,probshuffle,probmutate,probaddnotes,probremovenotes,probinversion,probcopy,beatval],​​{x,babies}];​​TakeLargestBy[children,fitnessFunction[#,degreevar,maxpat,maxgap,chords,mainkey]&,popsurvive]​​]

Crossover

This function combines two melodies (melody1 and melody2), by splitting them based on a random turning point. They are then joined together.
In[]:=
crossover[melody1_,melody2_]:=Module[{​​min=TakeSmallestBy[{melody1,melody2},Min[#[[1]]]&,1][[1]],​​max=TakeLargestBy[{melody1,melody2},Max[#[[1]]]&,1][[1]],​​turningpt},​​​​turningpt=RandomInteger[{2,Min[Length/@{max,min}]}]-1;​​{Join[​​min[[1]][[;;turningpt]],​​max[[1]][[turningpt+1;;]]],​​Join[​​min[[2]][[;;turningpt]],​​max[[2]][[turningpt+1;;]]]}​​]

Mutation

I coded a helper function called flat, which redistributes the number of beats per note such that the total sum of note lengths is equal to a user-specified value.
In[]:=
flat[melody_]:={melody[[1]],Nest[Switch[Total[melody[[2]]],n_/;n>32,ReplaceAt[#,n_:>n-1,RandomChoice[Position[#,n_/;(n>0)]]],n_/;n<32,ReplaceAt[#,n_:>n+1,RandomChoice[Position[#,n_/;(n<4)]]],_,#]&,melody[[2]],Abs[Total[melody[[2]]]-32]]}
I also added probability, which, given weights, generates a number (either 0 or 1) every time it is called.
In[]:=
prob[probability_]:=RandomChoice[{1-probability,probability}->{False,True}]
I added a couple of mutations, as follows.
​
​tweak changes one note to a different pitch.
In[]:=
tweak[melody_,notes_,beatval_]:=​​Module[{x=RandomInteger[{1,Length[melody[[1]]]}]},​​{ReplacePart[melody[[1]],x->RandomChoice[notes]],​​ReplacePart[melody[[2]],x->RandomChoice[beatval]]}​​]
shufflePitch shuffles the pitches of the melody.
In[]:=
shufflePitch[melody_]:={RandomSample[melody],melody[[2]]}
mutate applies “tweak” a random number of times from 1 to 10.
In[]:=
mutate[melody_,notes_,beatval_]:=​​{Nest[tweak[#,notes,beatval]&,melody[[1]],RandomInteger[{1,10}]],melody[[2]]}
addNotes adds another random note with a random number of beats to the end of a phrase.
In[]:=
addNotes[melody_,notes_,beatval_]:=flat[With[{newNotes=RandomInteger[{1,10}]},{Join[melody[[1]],RandomChoice[notes,newNotes]],Join[melody[[2]],RandomChoice[beatval,newNotes]]}]]
removeNotes removes the last note from the end of the phrase.
In[]:=
removeNotes[melody_]:=With[{index=RandomInteger[{1,Length[melody[[1]]]}]},{ReplacePart[melody[[1]],{RandomInteger[{1,Length[melody[[1]]]-1}]->melody[[1,index]],index->Nothing}],ReplaceAt[melody[[2]],n_:>n+melody[[2,index]],RandomInteger[{1,Length[melody[[2]]-1]}]]}]
removeNotes inverts the notes of the melody but keeps the beat the same.
In[]:=
inversion[melody_]:={Reverse[melody],melody[[2]]}
invWithBeat inverts both the beat and the melody.
In[]:=
invWithBeat[melody_]:={inversion[melody[[1]]],inversion[melody[[2]]]}
copy “copies” a section of the phrase (both beat and melody) onto another section.
In[]:=
copy[melody_]:=Module[{choices=RandomInteger[{1,Length[melody[[1]]]-1},2]},flat[{ReplacePart[melody[[1]],{choices[[1]]->melody[[1]][[choices[[2]]]],choices[[1]]+1->melody[[1]][[choices[[2]]+1]]}],ReplacePart[melody[[2]],{choices[[1]]->melody[[2]][[choices[[2]]]],choices[[1]]+1->melody[[2]][[choices[[2]]+1]]}]}]]
This is the function that puts everything together and uses them based on a given probability.
mutation[melody_,notes_,probtweak_,probshuffle_,probmutate_,probaddnotes_,probremovenotes_,probinversion_,probcopy_,beatval_]:=Fold[Replace[#1,#2]&,melody,​​{n_/;prob[probtweak]:>tweak[n,notes,beatval],​​n_/;prob[probshuffle]:>shufflePitch[n],​​n_/;prob[probmutate]:>mutate[n,notes,beatval],​​n_/;prob[probaddnotes]:>addNotes[n,notes,beatval],​​n_/;(prob[probremovenotes]&&Length[n]>4):>removeNotes[n],​​n_/;prob[probinversion]:>inversion[n],​​n_/;prob[probcopy]:>copy[n]}​​]
Note: All functions that modify the length or beat of the melody also include a “redistribution” of beats such that it stays constant.

Fitness Function

This was, by far, the trickiest part. It is often agreed that the quality of a melody is subjective to the listener, so mimicking the human experience posed a huge challenge.
One of the elements that I included in my fitness function is the idea of repetition. This makes the melody catchier and more memorable. I can’t use any built-in Wolfram function because I wanted to add a degree of variance, so some notes don’t have to match “perfectly.”
I coded two different types of patterns. The first one, offBeatPatterns, checks for patterns that do not need to have the same beats.
In[]:=
offBeatPatterns[melody_,degreevar_,minpat_]:=​​Length[Table[​​Select[​​Permutations[Partition[melody,len,1]],​​Length[DeleteElements[#[[1]]-#[[2]],0]]>degreevar&​​]​​,{len,Range[minpat,Round[Length[melody]/2]]}​​]]
onBeatPatterns, on the other hand, checks for patterns that match both the beat and the melody.
In[]:=
onBeatPatterns[melody_,degreevar_,minpat_,beat_]:=​​Length[Table[​​Select[​​Permutations[Partition[melody,len,1]],​​Length[DeleteElements[#[[1]]-#[[2]],0]]<degreevar&​​],{len,Range[minpat,Round[Length[melody]/2]]}​​]+​​Table[​​Select[​​Permutations[Partition[beat,len,1]],​​Length[DeleteElements[#[[1]]-#[[2]],0]]<degreevar&​​],{len,Range[minpat,Round[Length[melody]/2]]}​​]]
I also added another sub-function to assess the contour of a piece of music. The use of a “smooth” contour often shows up in classical music, especially with the use of scales and arpeggios, compared to jagged and “random” notes.
In[]:=
contour[melody_]:=Count[Select[GatherBy[DeleteElements[Ratios[Differences[melody]],0],Sign],Length[#]<2&]]
chord generates the notes in a given chord, represented by whether it is major or minor (0 vs 1, respectively) and its relationship to the home key.
In[]:=
chord[mainkey_,relativeinterval_,majormin_]:=Module[{x=mainkey+relativeinterval-1},If[majormin==0,{x,x+4,x+7},{x,x+3,x+7}]]
breaks checks for large gaps in the pitch. Although these gaps in music are often used for dramatic effect, for the purposes of this program, the music should sound better without a lot of large breaks.
In[]:=
breaks[melody_,maxgap_]:=Length[Select[Differences[melody],#>maxgap&]]
nct makes sure there are no more than two non-chord tones in a row. Having to many non-chord tones strays from the original key of the measure.
In[]:=
nct[bar_,mainkey_,chord_,majormin_]:=​​If[MatchQ[​​Table[If[MemberQ[chord[mainkey,chord,majormin],note],0,1],{note,bar}],{__,1,__}|{__,1,1,__}],1]
beatCheck assesses how good a given melody is on staying on beat.
beatCheck[beats_,div_]:=​​Module[​​{counter=0,temp,return,messups},​​Table[​​Append[temp,beat];​​counter+=beat;​​If[counter==div,counter=0];​​If[counter>div,counter=Mod[counter,4],messups+=1],​​{beat,beats}​​];​​messups​​]
Here’s the main fitness function! I take each of the sub-functions and weight them based on their degree of importance (and whether they are “good” or “bad”). For example, nct has the highest weighting because of how important it is to the overall structure of the melody.
In[]:=
fitnessFunction[melody_,degreevar_,maxpat_,maxgap_,beat_,chords_,mainkey_]:=​​breaks[melody[[1]],maxgap]*-20+​​contour[melody[[1]]]*-20+​​offBeatPatterns[melody[[1]],degreevar,maxpat]*20+​​onBeatPatterns[melody[[1]],degreevar,maxpat,melody[[2]]]*30+​​beatCheck[melody[[2]],4]*-100+​​Total[Table[nct[x,mainkey,chords[[1]],chords[[2]]],{x,beatDiv[melody]}]]*200

Main Function

We first define an initial population:
randomPop[notes_,beatval_]:={Table[RandomChoice[notes],20],Table[RandomChoice[beatval],20]}​​​​initialPop[notes_,beatval_]:=Table[randomPop[notes,beatval],200]
beatDiv is a helper function that can break a melody into 4 beat measures.
beatDiv[melody_]:=​​Module[{indiv=Partition[Table[melody[[1,x]],{melody[[2,x]]},{x,Length[melody]}],4]},Table[Gather[x],{x,indiv}]]
This is the main function. It calls the GA recursively with all the parameters specified by the user, and then plays the sound.
main[popsize_,popsurvive_,probabilityOfAll_,inputChords_,maxgap_,mainkey_,speed_,generations_]:=​​Module[{​​population=NestList[​​geneticAlg[​​fitnessFunction,#,crossover,mutation,popsize,probabilityOfAll,popsurvive,2,4,maxgap,​​{0,1,2,3,4,5,6,7,8,9,10,11,12},probabilityOfAll,probabilityOfAll,probabilityOfAll,probabilityOfAll,probabilityOfAll,probabilityOfAll,probabilityOfAll,{0,1,2,3,4},{inputChords[;;;;2],inputChords[2;;;;2]},mainkey]&,initialPop[{0,1,2,3,4,5,6,7,8,9,10,11,12},{0,1,2,3,4}],generations]},​​population[[-1]][[All,2]]/=speed;Sound[SoundNote@@@Transpose[population[[-1,-1]]]]​​]
Here’s the dynamic module so that the user can specify their input.
Note: A sample input for “chord” is as follows: {chord, maj/min, chord, maj/min, etc...}
Chord: Number representing the starting pitch of the chord relative to the home key. “2” with a home key in C would be D.
Maj/min: 0 for a major chord, 1 for a minor chord.
DynamicModule[{popSize=100,popSurvive=40,probabilityOfAll=0.1,inputChords={1,1,5,0,2,1,1,1},maxGap=5,mainKey=1,result="waiting...",speed=1,generations=10},Column[{​​Row[{"Number of Parents: ",InputField[Dynamic[popSize],Number]}],​​Row[{"Number of Survivors: ",InputField[Dynamic[popSurvive],Number]}],Row[{"Mutation Probability: ",InputField[Dynamic[probabilityOfAll],Number]}],​​Row[{"Chords: ",InputField[Dynamic[inputChords]]}],​​Row[{"Maximum gap between notes: ",InputField[Dynamic[maxGap],Number]}],​​Row[{"Main Key: ",InputField[Dynamic[mainKey],Number]}],​​Row[{"Speed: ",Slider[Dynamic[speed],{1,20}]}],​​Row[{"Generations: ",InputField[Dynamic[generations],Number]}],​​Spacer[20],Button["Submit",result=main[popSize,popSurvive,probabilityOfAll,inputChords,maxGap,mainKey,speed,generations];​​],Spacer[20],Panel[Column[{TextCell["Melody:","Text"],Dynamic[result]}]]}],SaveDefinitions->True]
Out[]=
Number of Parents:
100
Number of Survivors:
40
Mutation Probability:
0.1`
Chords:
{1,1,5,0,2,1,1,1}
Maximum gap between notes:
5
Main Key:
1
Speed:
Generations:
10
Submit
Melody:
waiting...

Conclusion & Future Direction

This exploration also reveals a paradigm for music generation: we have effectively demonstrated how an algorithm, by mimicking the process of biological evolution, can iteratively refine randomness into (somewhat) coherent phrases. This raises some clear questions: can this music rival the complexity of human-crafted melodies? Can we ever define objectively “good” music, like we tried to do in our fitness function? Can creativity and nuance be dumbed down to algorithms and code from a computer screen?
Looking ahead, the potential for algorithms in music generation is largely untapped. My project is only a mere template for further explorations in procedurally generated music. Future work could explore more sophisticated fitness functions with more advanced music theory concepts, user feedback, or even machine learning models that can guide the evolution towards specific genres or aesthetics.

Acknowledgements

I would like to thank my mentor, Logan Gilbert, for guiding me through this entire process, putting up with me, and helping debug my code; Bertie Bennett for coming up with my project and all the support; my mentor group and roommates for distractions from the stress of my project; and Kairav Banerjee for waking me up every time I fall asleep during project time.

CITE THIS NOTEBOOK

Genetic algorithm to generate melodies​
by Kirana Widha​
Wolfram Community, STAFF PICKS, July 10, 2025
​https://community.wolfram.com/groups/-/m/t/3501634