@@ -7,20 +7,27 @@ var sloop;
7
7
8
8
var validNotes = [ ...Array ( 128 ) . keys ( ) ] ;
9
9
var minValidNote , maxValidNote ;
10
+ var songLength = 32 ; // 4 bars * 8th-note resolution
10
11
11
12
var maxPopulationSize = 40 ;
12
- var numberOfSurvivors = 5 ;
13
+ var numberOfSurvivors = 10 ;
13
14
var population = [ ] ;
14
- var songLength = 32 ; // 4 bars * 8th-note resolution
15
+ var generationCount = 1 ;
15
16
16
17
var songIsPlaying = false ;
17
18
var clickedEarwormIndex ;
18
19
var notePlaybackIndex ;
20
+ // Fitness rules
21
+ var desiredKeyClasses = [ 0 , 2 , 4 , 5 , 7 , 9 , 11 ] ;
22
+ var minGoodPitch = 48 ;
23
+ var maxGoodPitch = 72 ;
19
24
20
25
function setup ( ) {
21
26
createCanvas ( window . innerWidth , window . innerHeight ) ;
22
27
colorMode ( HSB , 255 ) ;
23
28
textAlign ( CENTER , CENTER ) ;
29
+ textSize ( 16 ) ;
30
+ frameRate ( 10 ) ;
24
31
25
32
sloop = new p5 . SoundLoop ( soundLoop , 0.3 ) ; // Loop plays every 0.3s
26
33
synth = new p5 . PolySynth ( ) ;
@@ -33,39 +40,18 @@ function setup() {
33
40
population . push ( song ) ;
34
41
}
35
42
36
- selectionButton = createButton ( "Selection " ) ;
43
+ selectionButton = createButton ( "Select 10 fittest " ) ;
37
44
selectionButton . mouseClicked ( selectFittest ) ;
38
45
selectionButton . position ( 10 , 10 ) ;
39
- reproductionButton = createButton ( "Reproduction " ) ;
46
+ reproductionButton = createButton ( "Reproduce " ) ;
40
47
reproductionButton . mouseClicked ( reproducePopulation ) ;
41
48
reproductionButton . position ( 10 , 40 ) ;
42
- }
43
-
44
- function selectFittest ( ) {
45
- // Sort in descending order of fitness
46
- population . sort ( ( a , b ) => b . fitnessScore - a . fitnessScore ) ;
47
- // Keep only the N fittest
48
- population = subset ( population , 0 , numberOfSurvivors ) ;
49
- // Re-assign ID numbers
50
- for ( var i = 0 ; i < population . length ; i ++ ) {
51
- population [ i ] . id = i ;
52
- }
53
- }
54
-
55
- function reproducePopulation ( ) {
56
- var newPopulation = [ ] ;
57
- while ( newPopulation . length < maxPopulationSize - numberOfSurvivors ) {
58
- var parentA = random ( population ) ;
59
- var parentB = random ( population ) ;
60
- var child = parentA . reproduceWith ( parentB ) ;
61
- newPopulation . push ( child ) ;
62
- }
63
- // Add new generation to the survivors
64
- population = population . concat ( newPopulation ) ;
65
- // Re-assign ID numbers
66
- for ( var i = 0 ; i < population . length ; i ++ ) {
67
- population [ i ] . id = i ;
68
- }
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 ) ;
69
55
}
70
56
71
57
function soundLoop ( cycleStartTime ) {
@@ -87,10 +73,13 @@ function draw() {
87
73
for ( var i = 0 ; i < population . length ; i ++ ) {
88
74
population [ i ] . display ( ) ;
89
75
}
76
+ fill ( 255 ) ;
90
77
if ( songIsPlaying ) {
91
- fill ( 255 ) ;
92
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 ) ;
93
81
}
82
+ text ( "Generation: " + generationCount , width / 2 , height / 6 ) ;
94
83
}
95
84
96
85
function mousePressed ( ) {
@@ -112,14 +101,59 @@ function mousePressed() {
112
101
}
113
102
}
114
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
+
115
149
function Earworm ( indexNumber ) {
116
150
this . id = indexNumber ;
117
151
this . length = songLength ;
118
152
this . notes = [ ] ;
119
153
// Visual properties
120
154
this . xpos = random ( width ) ;
121
155
this . ypos = random ( height ) ;
122
- this . radius = 30 ;
156
+ this . radius = ( width + height ) / 50 ;
123
157
this . fitnessScore = 0 ;
124
158
}
125
159
Earworm . prototype . initialize = function ( ) {
@@ -130,13 +164,16 @@ Earworm.prototype.initialize = function() {
130
164
this . calculateFitness ( ) ;
131
165
} ;
132
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
+
133
170
push ( ) ;
134
171
strokeWeight ( 1 ) ;
135
172
angleMode ( DEGREES ) ; // Change the mode to DEGREES
136
173
var angle = 360 / this . notes . length ;
137
174
translate ( this . xpos , this . ypos ) ;
138
175
for ( var i = 0 ; i < this . notes . length ; i ++ ) {
139
- var color = map ( this . notes [ i ] , minValidNote , maxValidNote , 250 , 100 ) ;
176
+ var color = map ( this . notes [ i ] , minValidNote , maxValidNote , 280 , 120 ) % 255 ;
140
177
var length = map ( this . notes [ i ] , minValidNote , maxValidNote , this . radius / 2 , this . radius ) ;
141
178
strokeWeight ( 1 ) ;
142
179
stroke ( color , 180 , 250 ) ;
@@ -160,30 +197,34 @@ Earworm.prototype.display = function() {
160
197
} ;
161
198
Earworm . prototype . calculateFitness = function ( ) {
162
199
this . fitnessScore = 0 ;
163
- // Self-similarity
164
200
// Key
165
- // setA = subset(this.notes, 0, this.notes.length/2);
166
- // setB = subset(this.notes, this.notes.length/2, this.notes.length);
167
-
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
+ }
168
214
// Pitch range
169
- var minGoodPitch = 40 ;
170
- var maxGoodPitch = 80 ;
171
215
for ( var i = 0 ; i < this . notes . length ; i ++ ) {
172
216
if ( this . notes [ i ] > minGoodPitch ) {
173
- this . fitnessScore = this . fitnessScore + 10 ;
217
+ this . fitnessScore = this . fitnessScore + 5 ;
174
218
}
175
219
if ( this . notes [ i ] < maxGoodPitch ) {
176
- this . fitnessScore = this . fitnessScore + 10 ;
177
- }
220
+ this . fitnessScore = this . fitnessScore + 5 ;
221
+ }
178
222
}
179
- // Starting and ending on the same note
180
- // var startAndEndSame = (this.notes[0] == this.notes[this.notes.length - 1]);
181
-
182
- // Rhythm
183
223
} ;
184
224
Earworm . prototype . reproduceWith = function ( partner ) {
185
- var partA = subset ( this . notes , 0 , this . notes . length / 2 ) ;
186
- var partB = subset ( partner . notes , partner . notes . length / 2 , partner . notes . length ) ;
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 ) ;
187
228
var child = new Earworm ( 0 ) ;
188
229
child . notes = partA . concat ( partB ) ;
189
230
child . mutate ( ) ; // Add some random variation
@@ -192,7 +233,7 @@ Earworm.prototype.reproduceWith = function(partner) {
192
233
} ;
193
234
Earworm . prototype . mutate = function ( ) {
194
235
for ( var i = 0 ; i < this . notes . length ; i ++ ) {
195
- if ( random ( 100 ) > 90 ) {
236
+ if ( random ( 100 ) > 80 ) {
196
237
this . notes [ i ] = random ( validNotes ) ;
197
238
}
198
239
}
0 commit comments