-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathApp.js
154 lines (128 loc) · 5.2 KB
/
App.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
// Choose one from the option
preparedSongs = [
// C Major scale
[[0, 1], [2, 1], [4, 1], [5, 1], [7, 1], [9, 1], [11, 1], [12, 1], [11, 1], [9, 1], [7, 1], [5, 1], [4, 1], [2, 1], [0, 0], [4, 0], [7, 0], [4, 0], [0, 1]],
// 학교 종이 땡땡땡 - same speed
[[7, 1], [7, 1], [9, 1], [9, 1], [7, 1], [7, 1], [4, 1], [36, 1], [7, 1], [7, 1], [4, 1], [4, 1], [2, 1]],
// 쇼팽 Nocturne Op.9 No.2 - different speed
[[10, 1], [19, 2], [36, 1], [36, 1], [17, 1], [19, 1], [17, 2], [36, 1], [15, 2], [10, 1], [19, 2], [12, 1], [24, 2], [19, 1], [22, 2], [36, 1], [20, 2], [19, 1], [17, 2], [36, 1], [19, 2], [14, 1], [15, 2], [36, 1], [12, 2], [36, 1], [10, 1], [26, 1], [24, 1], [22, 0], [20, 0], [19, 0], [20, 0], [12, 0], [14, 0], [15, 2], [36, 0]]
]; // only notes are given
// Core variables for the main program
let target;
let population;
let isLoopRunning = false;
let minFitness = false; // true if optimize happens to minimize the fitness
let slowMode = false;
let bestSong;
// UI
let generation = 0;
let musicSheet;
// Hyperparameters
let popsize = 100, mutation_rate = 0.03; // default
let fitnessChoice = 0;
let parentSelectionChoice = 0;
let crossoverChoice = 0;
let mutationChoice = 0;
let generationSelectionChoice = 0;
// Other adjustable constants
const beatsPerSheet = 10;
// Initialize program (UI and variables)
function setup() {
noLoop();
target = preparedSongs[2];
hypeUI = textInit(20, 20); // hyperparameter input + text UI
// Get user input for hyperparameters
hypeUI.startButton.mousePressed(() => {
if (isLoopRunning) {
hypeUI.startButton.html("resume");
isLoopRunning = false;
noLoop();
}
else {
hypeUI.startButton.html("pause");
isLoopRunning = true;
// Set hyperparameters (only once in the beginning)
if (generation <= 1) {
if (hypeUI.popSizeInp.value()) popsize = parseInt(popSizeInp.value(), 10);
if (hypeUI.mutRateInp.value()) mutation_rate = parseInt(mutRateInp.value()) / 100;
fitnessChoice = document.querySelector('#fitSel').selectedIndex;
parentSelectionChoice = document.querySelector('#selSel').selectedIndex;
crossoverChoice = document.querySelector('#crossSel').selectedIndex;
mutationChoice = document.querySelector('#mutSel').selectedIndex;
generationSelectionChoice = document.querySelector('#genSel').selectedIndex;
slowMode = document.querySelector('.slow input[type="checkbox"]').checked;
// Reverse optimization direction (fitness maximize -> minimize)
if (fitnessChoice > 0)
minFitness = true;
let songChoice = document.querySelector('#songSel').selectedIndex;
target = preparedSongs[songChoice];
// UI for music notes
initMusicSheet();
// Init population
population = new Population();
}
loop();
}
});
population = new Population(); // dummy for initial UI rendering
initMusicSheet();
}
// Main program loop - Evolutionary cycle
function draw() {
// Step 1. Calculate each individual's fitness
population.calPopulationFitness();
// Step 2. Select parents
switch (parentSelectionChoice) {
case 0:
// select by roulette
population.selectParents.roulette.call(population);
break;
case 1:
// select by tournament (slower)
population.selectParents.tournament.call(population, popsize / 2);
}
// Step 3. cross-over, mutation
population.produceOffspring();
// Step 4. Select next generation from current parent+offspring population pool
switch (generationSelectionChoice) {
case 0:
// total replacement
population.formNextGeneration.gradual_replacement.call(population, popsize);
break;
case 1:
// gradual replacement (control replace size here)
population.formNextGeneration.gradual_replacement.call(population, floor(popsize / 2));
break;
case 2:
// elitism (control preserve size here)
population.formNextGeneration.elitism.call(population, floor(popsize / 20));
}
// Show individual with best fitness
population.findBest();
generation++;
// Update UI
textUpdate();
sheetUpdate();
// Stopping condition - target song is found
if (isArrEqual(target, bestSong.notes)) {
isLoopRunning = false;
noLoop();
alert('Simulation over - reload page to try again');
}
// Slow mode - stops at every generation
if (slowMode) {
hypeUI.startButton.html("resume");
isLoopRunning = false;
noLoop();
}
}
function isArrEqual(arr1, arr2) {
if (arr1.length != arr2.length) return false;
return arr1.every((elem, idx) => elem[NOTE] == arr2[idx][NOTE] && elem[SPD] == arr2[idx][SPD]);
}
function initMusicSheet() {
const sheetX = 100, sheetY = 180, sheetH = 8 * h;
let canvas = createCanvas(linelen * (beatsPerSheet + 1), sheetH * (ceil(target.length / beatsPerSheet) + 1));
canvas.parent('sketch-holder');
musicSheet = new Sheet(sheetX, sheetY, sheetH);
}