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
+
11
+ var maxPopulationSize = 40 ;
12
+ var numberOfSurvivors = 5 ;
13
+ var population = [ ] ;
14
+ var songLength = 32 ; // 4 bars * 8th-note resolution
15
+
16
+ var songIsPlaying = false ;
17
+ var clickedEarwormIndex ;
18
+ var notePlaybackIndex ;
19
+
20
+ function setup ( ) {
21
+ createCanvas ( window . innerWidth , window . innerHeight ) ;
22
+ colorMode ( HSB , 255 ) ;
23
+ textAlign ( CENTER , CENTER ) ;
24
+
25
+ sloop = new p5 . SoundLoop ( soundLoop , 0.3 ) ; // Loop plays every 0.3s
26
+ synth = new p5 . PolySynth ( ) ;
27
+
28
+ minValidNote = min ( validNotes ) ;
29
+ maxValidNote = max ( validNotes ) ;
30
+ for ( var i = 0 ; i < maxPopulationSize ; i ++ ) {
31
+ var song = new Earworm ( i ) ;
32
+ song . initialize ( ) ;
33
+ population . push ( song ) ;
34
+ }
35
+
36
+ selectionButton = createButton ( "Selection" ) ;
37
+ selectionButton . mouseClicked ( selectFittest ) ;
38
+ selectionButton . position ( 10 , 10 ) ;
39
+ reproductionButton = createButton ( "Reproduction" ) ;
40
+ reproductionButton . mouseClicked ( reproducePopulation ) ;
41
+ 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
+ }
69
+ }
70
+
71
+ function soundLoop ( cycleStartTime ) {
72
+ var duration = this . interval ;
73
+ var velocity = 0.7 ;
74
+ var midiNote = population [ clickedEarwormIndex ] . notes [ notePlaybackIndex ] ;
75
+ var noteFreq = midiToFreq ( midiNote ) ;
76
+ synth . play ( noteFreq , velocity , 0 , duration ) ;
77
+ // Move forward the index, and stop if we've reached the end
78
+ notePlaybackIndex ++ ;
79
+ if ( notePlaybackIndex >= population [ clickedEarwormIndex ] . notes . length ) {
80
+ this . stop ( ) ;
81
+ songIsPlaying = false ;
82
+ }
83
+ }
84
+
85
+ function draw ( ) {
86
+ background ( 30 ) ;
87
+ for ( var i = 0 ; i < population . length ; i ++ ) {
88
+ population [ i ] . display ( ) ;
89
+ }
90
+ if ( songIsPlaying ) {
91
+ fill ( 255 ) ;
92
+ text ( "Song playing... Click to stop." , width / 2 , height / 2 ) ;
93
+ }
94
+ }
95
+
96
+ function mousePressed ( ) {
97
+ if ( songIsPlaying ) {
98
+ // Stop a song
99
+ sloop . stop ( ) ;
100
+ songIsPlaying = false ;
101
+ } else {
102
+ // Start a song
103
+ for ( var i = 0 ; i < population . length ; i ++ ) {
104
+ var clickToEarwormDistance = dist ( mouseX , mouseY , population [ i ] . xpos , population [ i ] . ypos ) ;
105
+ if ( clickToEarwormDistance < population [ i ] . radius ) {
106
+ clickedEarwormIndex = i ;
107
+ notePlaybackIndex = 0 ;
108
+ songIsPlaying = true ;
109
+ sloop . start ( ) ;
110
+ }
111
+ }
112
+ }
113
+ }
114
+
115
+ function Earworm ( indexNumber ) {
116
+ this . id = indexNumber ;
117
+ this . length = songLength ;
118
+ this . notes = [ ] ;
119
+ // Visual properties
120
+ this . xpos = random ( width ) ;
121
+ this . ypos = random ( height ) ;
122
+ this . radius = 30 ;
123
+ this . fitnessScore = 0 ;
124
+ }
125
+ Earworm . prototype . initialize = function ( ) {
126
+ this . notes = [ ] ;
127
+ for ( var i = 0 ; i < this . length ; i ++ ) {
128
+ this . notes . push ( random ( validNotes ) ) ;
129
+ }
130
+ this . calculateFitness ( ) ;
131
+ } ;
132
+ Earworm . prototype . display = function ( ) {
133
+ push ( ) ;
134
+ strokeWeight ( 1 ) ;
135
+ angleMode ( DEGREES ) ; // Change the mode to DEGREES
136
+ var angle = 360 / this . notes . length ;
137
+ translate ( this . xpos , this . ypos ) ;
138
+ for ( var i = 0 ; i < this . notes . length ; i ++ ) {
139
+ var color = map ( this . notes [ i ] , minValidNote , maxValidNote , 250 , 100 ) ;
140
+ var length = map ( this . notes [ i ] , minValidNote , maxValidNote , this . radius / 2 , this . radius ) ;
141
+ strokeWeight ( 1 ) ;
142
+ stroke ( color , 180 , 250 ) ;
143
+ if ( songIsPlaying ) {
144
+ if ( this . id == clickedEarwormIndex ) {
145
+ stroke ( color , 180 , 250 ) ;
146
+ if ( i == notePlaybackIndex ) {
147
+ strokeWeight ( 2 ) ;
148
+ length = this . radius ;
149
+ } else {
150
+ strokeWeight ( 1 ) ;
151
+ }
152
+ } else {
153
+ stroke ( color , 100 , 100 ) ;
154
+ }
155
+ }
156
+ rotate ( angle ) ;
157
+ line ( 0 , 0 , length , 0 ) ;
158
+ }
159
+ pop ( ) ;
160
+ } ;
161
+ Earworm . prototype . calculateFitness = function ( ) {
162
+ this . fitnessScore = 0 ;
163
+ // Self-similarity
164
+ // Key
165
+ // setA = subset(this.notes, 0, this.notes.length/2);
166
+ // setB = subset(this.notes, this.notes.length/2, this.notes.length);
167
+
168
+ // Pitch range
169
+ var minGoodPitch = 40 ;
170
+ var maxGoodPitch = 80 ;
171
+ for ( var i = 0 ; i < this . notes . length ; i ++ ) {
172
+ if ( this . notes [ i ] > minGoodPitch ) {
173
+ this . fitnessScore = this . fitnessScore + 10 ;
174
+ }
175
+ if ( this . notes [ i ] < maxGoodPitch ) {
176
+ this . fitnessScore = this . fitnessScore + 10 ;
177
+ }
178
+ }
179
+ // Starting and ending on the same note
180
+ // var startAndEndSame = (this.notes[0] == this.notes[this.notes.length - 1]);
181
+
182
+ // Rhythm
183
+ } ;
184
+ 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 ) ;
187
+ var child = new Earworm ( 0 ) ;
188
+ child . notes = partA . concat ( partB ) ;
189
+ child . mutate ( ) ; // Add some random variation
190
+ child . calculateFitness ( ) ;
191
+ return child ;
192
+ } ;
193
+ Earworm . prototype . mutate = function ( ) {
194
+ for ( var i = 0 ; i < this . notes . length ; i ++ ) {
195
+ if ( random ( 100 ) > 90 ) {
196
+ this . notes [ i ] = random ( validNotes ) ;
197
+ }
198
+ }
199
+ } ;
0 commit comments