1
+ /**
2
+ * Example: Genetic music
3
+ */
4
+
5
+ var synth ;
6
+ var sloop ;
7
+
8
+ var validNotes = [ ...Array ( 128 ) . keys ( ) ] ;
9
+ var minValidNote , maxValidNote ;
10
+ var songLength = 32 ; // 4 bars * 8th-note resolution
11
+
12
+ var maxPopulationSize = 40 ;
13
+ var numberOfSurvivors = 10 ;
14
+ var population = [ ] ;
15
+ var generationCount = 1 ;
16
+
17
+ var songIsPlaying = false ;
18
+ var clickedEarwormIndex ;
19
+ var notePlaybackIndex ;
20
+ // Fitness rules
21
+ var desiredKeyClasses = [ 0 , 2 , 4 , 5 , 7 , 9 , 11 ] ;
22
+ var minGoodPitch = 48 ;
23
+ var maxGoodPitch = 72 ;
24
+
25
+ function setup ( ) {
26
+ createCanvas ( window . innerWidth , window . innerHeight ) ;
27
+ colorMode ( HSB , 255 ) ;
28
+ textAlign ( CENTER , CENTER ) ;
29
+ textSize ( 16 ) ;
30
+ frameRate ( 10 ) ;
31
+
32
+ sloop = new p5 . SoundLoop ( soundLoop , 0.3 ) ; // Loop plays every 0.3s
33
+ synth = new p5 . PolySynth ( ) ;
34
+
35
+ minValidNote = min ( validNotes ) ;
36
+ maxValidNote = max ( validNotes ) ;
37
+ for ( var i = 0 ; i < maxPopulationSize ; i ++ ) {
38
+ var song = new Earworm ( i ) ;
39
+ song . initialize ( ) ;
40
+ population . push ( song ) ;
41
+ }
42
+
43
+ selectionButton = createButton ( "Select 10 fittest" ) ;
44
+ selectionButton . mouseClicked ( selectFittest ) ;
45
+ selectionButton . position ( 10 , 10 ) ;
46
+ reproductionButton = createButton ( "Reproduce" ) ;
47
+ reproductionButton . mouseClicked ( reproducePopulation ) ;
48
+ reproductionButton . position ( 10 , 40 ) ;
49
+ fastForwardButton = createButton ( "Fast-forward 10 generations" ) ;
50
+ fastForwardButton . mouseClicked ( fastForward ) ;
51
+ fastForwardButton . position ( 10 , 70 ) ;
52
+ resetButton = createButton ( "Reset population" ) ;
53
+ resetButton . mouseClicked ( resetPopulation ) ;
54
+ resetButton . position ( 10 , 100 ) ;
55
+ }
56
+
57
+ function soundLoop ( cycleStartTime ) {
58
+ var duration = this . interval ;
59
+ var velocity = 0.7 ;
60
+ var midiNote = population [ clickedEarwormIndex ] . notes [ notePlaybackIndex ] ;
61
+ var noteFreq = midiToFreq ( midiNote ) ;
62
+ synth . play ( noteFreq , velocity , 0 , duration ) ;
63
+ // Move forward the index, and stop if we've reached the end
64
+ notePlaybackIndex ++ ;
65
+ if ( notePlaybackIndex >= population [ clickedEarwormIndex ] . notes . length ) {
66
+ this . stop ( ) ;
67
+ songIsPlaying = false ;
68
+ }
69
+ }
70
+
71
+ function draw ( ) {
72
+ background ( 30 ) ;
73
+ for ( var i = 0 ; i < population . length ; i ++ ) {
74
+ population [ i ] . display ( ) ;
75
+ }
76
+ fill ( 255 ) ;
77
+ if ( songIsPlaying ) {
78
+ text ( "Song playing... Click to stop." , width / 2 , height / 2 ) ;
79
+ } else {
80
+ text ( "Click on an Earworm to hear it sing!" , width / 2 , height / 2 ) ;
81
+ }
82
+ text ( "Generation: " + generationCount , width / 2 , height / 6 ) ;
83
+ }
84
+
85
+ function mousePressed ( ) {
86
+ if ( songIsPlaying ) {
87
+ // Stop a song
88
+ sloop . stop ( ) ;
89
+ songIsPlaying = false ;
90
+ } else {
91
+ // Start a song
92
+ for ( var i = 0 ; i < population . length ; i ++ ) {
93
+ var clickToEarwormDistance = dist ( mouseX , mouseY , population [ i ] . xpos , population [ i ] . ypos ) ;
94
+ if ( clickToEarwormDistance < population [ i ] . radius ) {
95
+ clickedEarwormIndex = i ;
96
+ notePlaybackIndex = 0 ;
97
+ songIsPlaying = true ;
98
+ sloop . start ( ) ;
99
+ }
100
+ }
101
+ }
102
+ }
103
+
104
+ function selectFittest ( ) {
105
+ // Sort in descending order of fitness
106
+ population . sort ( ( a , b ) => b . fitnessScore - a . fitnessScore ) ;
107
+ // Keep only the N fittest
108
+ population = subset ( population , 0 , numberOfSurvivors ) ;
109
+ // Re-assign ID numbers
110
+ for ( var i = 0 ; i < population . length ; i ++ ) {
111
+ population [ i ] . id = i ;
112
+ }
113
+ }
114
+
115
+ function reproducePopulation ( ) {
116
+ var newPopulation = [ ] ;
117
+ while ( newPopulation . length < maxPopulationSize - numberOfSurvivors ) {
118
+ var parentA = random ( population ) ;
119
+ var parentB = random ( population ) ;
120
+ var child = parentA . reproduceWith ( parentB ) ;
121
+ newPopulation . push ( child ) ;
122
+ }
123
+ // Add new generation to the survivors
124
+ population = population . concat ( newPopulation ) ;
125
+ // Re-assign ID numbers
126
+ for ( var i = 0 ; i < population . length ; i ++ ) {
127
+ population [ i ] . id = i ;
128
+ }
129
+ generationCount ++ ;
130
+ }
131
+
132
+ function fastForward ( ) {
133
+ var fastForwardNum = 10 ;
134
+ for ( var i = 0 ; i < fastForwardNum ; i ++ ) {
135
+ selectFittest ( ) ;
136
+ reproducePopulation ( ) ;
137
+ }
138
+ }
139
+
140
+ function resetPopulation ( ) {
141
+ generationCount = 1 ;
142
+ for ( var i = 0 ; i < maxPopulationSize ; i ++ ) {
143
+ var song = new Earworm ( i ) ;
144
+ song . initialize ( ) ;
145
+ population [ i ] = song ;
146
+ }
147
+ }
148
+
149
+ function Earworm ( indexNumber ) {
150
+ this . id = indexNumber ;
151
+ this . length = songLength ;
152
+ this . notes = [ ] ;
153
+ // Visual properties
154
+ this . xpos = random ( width ) ;
155
+ this . ypos = random ( height ) ;
156
+ this . radius = ( width + height ) / 50 ;
157
+ this . fitnessScore = 0 ;
158
+ }
159
+ Earworm . prototype . initialize = function ( ) {
160
+ this . notes = [ ] ;
161
+ for ( var i = 0 ; i < this . length ; i ++ ) {
162
+ this . notes . push ( random ( validNotes ) ) ;
163
+ }
164
+ this . calculateFitness ( ) ;
165
+ } ;
166
+ Earworm . prototype . display = function ( ) {
167
+ this . xpos = constrain ( this . xpos + random ( - 1 , 1 ) , 0 , width ) ;
168
+ this . ypos = constrain ( this . ypos + random ( - 1 , 1 ) , 0 , height ) ;
169
+
170
+ push ( ) ;
171
+ strokeWeight ( 1 ) ;
172
+ angleMode ( DEGREES ) ; // Change the mode to DEGREES
173
+ var angle = 360 / this . notes . length ;
174
+ translate ( this . xpos , this . ypos ) ;
175
+ for ( var i = 0 ; i < this . notes . length ; i ++ ) {
176
+ var color = map ( this . notes [ i ] , minValidNote , maxValidNote , 280 , 120 ) % 255 ;
177
+ var length = map ( this . notes [ i ] , minValidNote , maxValidNote , this . radius / 2 , this . radius ) ;
178
+ strokeWeight ( 1 ) ;
179
+ stroke ( color , 180 , 250 ) ;
180
+ if ( songIsPlaying ) {
181
+ if ( this . id == clickedEarwormIndex ) {
182
+ stroke ( color , 180 , 250 ) ;
183
+ if ( i == notePlaybackIndex ) {
184
+ strokeWeight ( 2 ) ;
185
+ length = this . radius ;
186
+ } else {
187
+ strokeWeight ( 1 ) ;
188
+ }
189
+ } else {
190
+ stroke ( color , 100 , 100 ) ;
191
+ }
192
+ }
193
+ rotate ( angle ) ;
194
+ line ( 0 , 0 , length , 0 ) ;
195
+ }
196
+ pop ( ) ;
197
+ } ;
198
+ Earworm . prototype . calculateFitness = function ( ) {
199
+ this . fitnessScore = 0 ;
200
+ // Key
201
+ for ( var i = 0 ; i < this . notes . length ; i ++ ) {
202
+ var keyClass = this . notes [ i ] % 12 ;
203
+ if ( desiredKeyClasses . indexOf ( keyClass ) >= 0 ) {
204
+ this . fitnessScore = this . fitnessScore + 10 ;
205
+ }
206
+ }
207
+ // Prefer smaller intervals
208
+ for ( var i = 0 ; i < this . notes . length - 1 ; i ++ ) {
209
+ var currentNote = this . notes [ i ] ;
210
+ var nextNote = this . notes [ i + 1 ] ;
211
+ var interval = abs ( nextNote - currentNote ) ;
212
+ this . fitnessScore = this . fitnessScore - interval ;
213
+ }
214
+ // Pitch range
215
+ for ( var i = 0 ; i < this . notes . length ; i ++ ) {
216
+ if ( this . notes [ i ] > minGoodPitch ) {
217
+ this . fitnessScore = this . fitnessScore + 5 ;
218
+ }
219
+ if ( this . notes [ i ] < maxGoodPitch ) {
220
+ this . fitnessScore = this . fitnessScore + 5 ;
221
+ }
222
+ }
223
+ } ;
224
+ Earworm . prototype . reproduceWith = function ( partner ) {
225
+ var partitionIndex = round ( random ( this . notes . length ) ) ;
226
+ var partA = subset ( this . notes , 0 , partitionIndex ) ;
227
+ var partB = subset ( partner . notes , partitionIndex , partner . notes . length ) ;
228
+ var child = new Earworm ( 0 ) ;
229
+ child . notes = partA . concat ( partB ) ;
230
+ child . mutate ( ) ; // Add some random variation
231
+ child . calculateFitness ( ) ;
232
+ return child ;
233
+ } ;
234
+ Earworm . prototype . mutate = function ( ) {
235
+ for ( var i = 0 ; i < this . notes . length ; i ++ ) {
236
+ if ( random ( 100 ) > 80 ) {
237
+ this . notes [ i ] = random ( validNotes ) ;
238
+ }
239
+ }
240
+ } ;
0 commit comments