Skip to content

Commit 61a5f23

Browse files
committed
Add genetic music example, proof-of-concept works but still in progress
1 parent 3e5c085 commit 61a5f23

File tree

3 files changed

+216
-0
lines changed

3 files changed

+216
-0
lines changed

examples/genetic_music/index.html

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<head>
2+
<script language="javascript" type="text/javascript" src="../../lib/p5.js"></script>
3+
4+
<script language="javascript" type="text/javascript" src="../../lib/addons/p5.dom.js"></script>
5+
6+
<script language="javascript" type="text/javascript" src="../../lib/p5.sound.js"></script>
7+
8+
<script language="javascript" type="text/javascript" src="sketch.js"></script>
9+
10+
<style>
11+
body {
12+
margin: 0;
13+
}
14+
</style>
15+
</head>

examples/genetic_music/sketch.js

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
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+
};

index.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ <h2>p5.sound
3434
<div><a href="examples/FFT_scaleOneThirdOctave">FFT_scaleOneThirdOctave</a></div>
3535
<div><a href="examples/Filter_BandPass">Filter_BandPass</a></div>
3636
<div><a href="examples/Filter_LowPass">Filter_LowPass</a></div>
37+
<div><a href="examples/genetic_music">genetic_music</a></div>
3738
<div><a href="examples/granular_sampler">granular_sampler</a></div>
3839
<div><a href="examples/granular_sampler_psynth">granular_sampler_psynth</a></div>
3940
<div><a href="examples/graphical_eq">graphical_eq</a></div>
@@ -72,6 +73,7 @@ <h2>p5.sound
7273
<div><a href="examples/Reverb_convolve">Reverb_convolve</a></div>
7374
<div><a href="examples/Reverb_convolve_FFT">Reverb_convolve_FFT</a></div>
7475
<div><a href="examples/soundfile_playMode">soundfile_playMode</a></div>
76+
<div><a href="examples/soundfile_remove_cue">soundfile_remove_cue</a></div>
7577
<div><a href="examples/soundfileMod_AM">soundfileMod_AM</a></div>
7678
<div><a href="examples/soundFormats">soundFormats</a></div>
7779
<div><a href="examples/spatial_panning">spatial_panning</a></div>

0 commit comments

Comments
 (0)