-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathautomata.js
148 lines (120 loc) · 4.17 KB
/
automata.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
/*
Generative Automata w/ Web MIDI
==============================
- Built for the Novation LaunchPad MK2 by George Mandis (george.mand.is)
- Generative code taken from Tero Parviainen's (teropa.info) slides as seen at Fullstack London 2017 (teropa.info/generative-music-slides/)
- Additional inspiration from Chris Wilsons's (cwilso.com) Conway's Game of Life demo for Web MIDI and Novation Launchpad (webaudiodemos.appspot.com/conway/index.html)
- For more information visit: midi.mand.is
*/
const generations = [
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0]
],
rules = {},
colorOffset = Math.floor(Math.random() * 60)
const updateRules = () => {
document.querySelectorAll("input").forEach((rule, index) => {
let key = rule.name.replace(/\D/g, '');
rules[key] = parseInt(rule.value);
});
}
const nextGeneration = (last) => {
if (last) {
const result = new Array(last.length);
for (let idx = 0; idx < last.length; idx++) {
const leftIdx = idx > 0 ? idx - 1 : last.length - 1;
const rightIdx = idx < last.length - 1 ? idx + 1 : 0;
const key = `${last[leftIdx]}${last[idx]}${last[rightIdx]}`;
result[idx] = rules[key];
}
return result;
}
}
const last = (arr) => {
return arr[arr.length - 1];
}
const startGenerativeAutomata = () => {
generations.push(nextGeneration(last(generations)));
setInterval(time => {
generations.push(nextGeneration(last(generations)));
if (generations.length > 8) generations.splice(0, 1);
const generation = last(generations);
// update on-screen mirror of what's happening on launchpad
document.getElementById('generated').innerHTML = generations.join("<br>");;
for (let y = 0; y < generations.length; y++) {
for (let x = 0; x < generations[y].length; x++) {
if (midiOutput) midiOutput.send([144, coordinateToMIDINote(x, y), generations[y][x] * (x + colorOffset)]); // Select
}
}
}, 750);
}
var midiInput = null,
midiOutput = null,
grid = new Array(8);
// Create empty grid for Launchpad
for (let x = 0; x < 8; x++) {
grid[x] = new Array(8);
}
// convert LaunchPad coordinate to MIDI note
const coordinateToMIDINote = (x, y) => {
value = 88 - 7 + (x - (y * 10));
return value;
}
// Clear the LaunchPad board
const resetBoard = () => {
for (let x = 0; x < 8; x++) {
grid[x] = [0, 0, 0, 0, 0, 0, 0, 0]
}
for (let i = 0; i < 127; i++) {
if (midiOutput) midiOutput.send([144, i, 0]);
}
}
const onMIDIInit = (midi) => {
for (let input of midi.inputs.values()) {
if (input.name === "Launchpad MK2") midiInput = input;
}
for (let output of midi.outputs.values()) {
if (output.name === "Launchpad MK2") midiOutput = output;
}
if (midiInput) midiInput.onmidimessage = midiProc;
resetBoard();
if (midiInput) {
midiOutput.send([0xB0, 0x00, 0x00]); // Reset Launchpad
midiOutput.send([0xB0, 0x00, 0x01]); // Select XY mode
}
startGenerativeAutomata();
}
const onMIDIFail = () => {
console.log("Could not load MIDI");
}
// Mess with the generative patterns
const midiProc = (event) => {
const data = event.data,
cmd = data[0] >> 4,
noteNumber = data[1],
parse = (noteNumber) / 10,
// Map to X/Y values for LaunchPad MKII
x = Math.abs((parseInt(parse.toString().split(".")[0]) - 1) - 7),
y = parseInt(parse.toString().split(".")[1]) - 1;
// clear row when pressing bottom right, off-grid button
if (y === 8 && data[2] === 127) {
generations[x].splice(0, 8, 0, 0, 0, 0, 0, 0, 0, 0);
} else if ((generations[x][y] === undefined || generations[x][y] === 0) && data[2] == 127) {
// toggle cell on
generations[x].splice(y, 1, 1);
} else if (generations[x][y] > 0 && data[2] == 127) {
// toggle cell off
generations[x].splice(y, 1, 0);
}
midiOutput.send([144, coordinateToMIDINote(y, x), generations[x][y] * (y + colorOffset)]); // Select
}
navigator.requestMIDIAccess({}).then(onMIDIInit, onMIDIFail);
document.querySelectorAll("input").forEach((rule) => {
rule.addEventListener('change', updateRules);
});
updateRules();