Skip to content

Commit 6095d0f

Browse files
committed
Clean up code and UI, add more fitness rules
1 parent 61a5f23 commit 6095d0f

File tree

1 file changed

+91
-50
lines changed

1 file changed

+91
-50
lines changed

examples/genetic_music/sketch.js

Lines changed: 91 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,27 @@ var sloop;
77

88
var validNotes = [...Array(128).keys()];
99
var minValidNote, maxValidNote;
10+
var songLength = 32; // 4 bars * 8th-note resolution
1011

1112
var maxPopulationSize = 40;
12-
var numberOfSurvivors = 5;
13+
var numberOfSurvivors = 10;
1314
var population = [];
14-
var songLength = 32; // 4 bars * 8th-note resolution
15+
var generationCount = 1;
1516

1617
var songIsPlaying = false;
1718
var clickedEarwormIndex;
1819
var notePlaybackIndex;
20+
// Fitness rules
21+
var desiredKeyClasses = [0,2,4,5,7,9,11];
22+
var minGoodPitch = 48;
23+
var maxGoodPitch = 72;
1924

2025
function setup() {
2126
createCanvas(window.innerWidth, window.innerHeight);
2227
colorMode(HSB, 255);
2328
textAlign(CENTER, CENTER);
29+
textSize(16);
30+
frameRate(10);
2431

2532
sloop = new p5.SoundLoop(soundLoop, 0.3); // Loop plays every 0.3s
2633
synth = new p5.PolySynth();
@@ -33,39 +40,18 @@ function setup() {
3340
population.push(song);
3441
}
3542

36-
selectionButton = createButton("Selection");
43+
selectionButton = createButton("Select 10 fittest");
3744
selectionButton.mouseClicked(selectFittest);
3845
selectionButton.position(10, 10);
39-
reproductionButton = createButton("Reproduction");
46+
reproductionButton = createButton("Reproduce");
4047
reproductionButton.mouseClicked(reproducePopulation);
4148
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);
6955
}
7056

7157
function soundLoop(cycleStartTime) {
@@ -87,10 +73,13 @@ function draw() {
8773
for (var i=0; i<population.length; i++) {
8874
population[i].display();
8975
}
76+
fill(255);
9077
if (songIsPlaying) {
91-
fill(255);
9278
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);
9381
}
82+
text("Generation: " + generationCount, width/2, height/6);
9483
}
9584

9685
function mousePressed() {
@@ -112,14 +101,59 @@ function mousePressed() {
112101
}
113102
}
114103

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+
115149
function Earworm(indexNumber) {
116150
this.id = indexNumber;
117151
this.length = songLength;
118152
this.notes = [];
119153
// Visual properties
120154
this.xpos = random(width);
121155
this.ypos = random(height);
122-
this.radius = 30;
156+
this.radius = (width + height) / 50;
123157
this.fitnessScore = 0;
124158
}
125159
Earworm.prototype.initialize = function() {
@@ -130,13 +164,16 @@ Earworm.prototype.initialize = function() {
130164
this.calculateFitness();
131165
};
132166
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+
133170
push();
134171
strokeWeight(1);
135172
angleMode(DEGREES); // Change the mode to DEGREES
136173
var angle = 360 / this.notes.length;
137174
translate(this.xpos, this.ypos);
138175
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;
140177
var length = map(this.notes[i], minValidNote, maxValidNote, this.radius/2, this.radius);
141178
strokeWeight(1);
142179
stroke(color, 180, 250);
@@ -160,30 +197,34 @@ Earworm.prototype.display = function() {
160197
};
161198
Earworm.prototype.calculateFitness = function() {
162199
this.fitnessScore = 0;
163-
// Self-similarity
164200
// 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+
}
168214
// Pitch range
169-
var minGoodPitch = 40;
170-
var maxGoodPitch = 80;
171215
for (var i=0; i<this.notes.length; i++) {
172216
if (this.notes[i] > minGoodPitch) {
173-
this.fitnessScore = this.fitnessScore + 10;
217+
this.fitnessScore = this.fitnessScore + 5;
174218
}
175219
if (this.notes[i] < maxGoodPitch) {
176-
this.fitnessScore = this.fitnessScore + 10;
177-
}
220+
this.fitnessScore = this.fitnessScore + 5;
221+
}
178222
}
179-
// Starting and ending on the same note
180-
// var startAndEndSame = (this.notes[0] == this.notes[this.notes.length - 1]);
181-
182-
// Rhythm
183223
};
184224
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);
187228
var child = new Earworm(0);
188229
child.notes = partA.concat(partB);
189230
child.mutate(); // Add some random variation
@@ -192,7 +233,7 @@ Earworm.prototype.reproduceWith = function(partner) {
192233
};
193234
Earworm.prototype.mutate = function() {
194235
for (var i=0; i<this.notes.length; i++) {
195-
if (random(100) > 90) {
236+
if (random(100) > 80) {
196237
this.notes[i] = random(validNotes);
197238
}
198239
}

0 commit comments

Comments
 (0)