Spend a month making one beautiful thing per day, given a bunch of prompts. A month late, but as they say, ’the second best time is now'.
Let’s do it!
10) Generative music
Wikipedia: Generative music
NOTE: Because of limitations in JavaScript autoplaying sound, you must click to start.
NOTE: This currently doesn’t work in Safari. Something funny with the p5.sound addon. I’m working on it.
let gui;
let params = {
width: 32, widthMin: 4, widthMax: 64,
height: 4, heightMin: 1, heightMax: 8,
bpm: 140, bpmMin: 30, bpmMax: 280,
randomFill: 0.2, randomFillMin: 0.0, randomFillMax: 1.0, randomFillStep: 0.01,
};
const MAX_VALUE = 8;
const COLORS = [
"black",
"red",
"green",
"blue",
"cyan",
"magenta",
"yellow",
"white",
];
const NOTES = [
'A4',
'B4',
'C4',
'D4',
'E4',
'F4',
'G4',
];
class CellValues {
constructor() {
this.data = {};
}
get(x, y) {
if ([x, y] in this.data) {
return this.data[[x, y]];
} else {
return 0;
}
}
set(x, y, v) {
if ([x, y] in this.data && v == 0) {
delete this.data[[x, y]];
} else {
this.data[[x, y]] = v;
}
}
}
let cell_values;
let start_millis = 0;
let last_beat = -1;
let synth;
function setup() {
let canvas = createCanvas(400, 400);
cell_values = new CellValues();
cell_values.set(3, 5, 1);
// Toggle squares
canvas.mousePressed(() => {
if (!synth) {
start_millis = millis();
userStartAudio();
synth = new p5.PolySynth();
}
let cx = parseInt(params.width * mouseX / width);
let cy = parseInt(params.height * mouseY / height);
let key = JSON.stringify([cx, cy]);
cell_values.set(
cx,
cy,
(
cell_values.get(cx, cy)
+ (mouseButton == LEFT ? 1 : -1)
+ MAX_VALUE
) % MAX_VALUE
);
});
gui = createGuiPanel('params');
gui.addObject(params);
gui.setPosition(420, 0);
let randomize = () => {
for (let cx = 0; cx < params.width; cx++) {
for (let cy = 0; cy < params.height; cy++) {
if (random() < params.randomFill) {
cell_values.set(cx, cy, parseInt(random() * MAX_VALUE));
} else {
cell_values.set(cx, cy, 0);
}
}
}
};
createButton('randomize').mousePressed(randomize);
randomize();
}
function draw() {
background(0);
let elapsed_millis = 0;
let beat = -1;
if (synth) {
elapsed_millis = millis() - start_millis;
beat = parseInt(elapsed_millis / 1000.0 * params.bpm / 60.0);
}
let cell_width = width / params.width;
let cell_height = height / params.height;
// Update display
for (let cx = 0; cx < params.width; cx++) {
for (let cy = 0; cy < params.height; cy++) {
if (beat % params.width == cx) {
stroke("yellow");
} else {
noStroke();
}
fill(COLORS[cell_values.get(cx, cy)]);
rect(
2 + cx * cell_width,
2 + cy * cell_height,
cell_width - 4,
cell_height - 4,
8
);
}
}
// Play music
if (synth) {
if (beat != last_beat) {
let cx = beat % params.width;
for (let cy = 0; cy < params.height; cy++) {
let cell_value = cell_values.get(cx, cy);
if (cell_value == 0) continue;
synth.play(
NOTES[cell_value - 1],
1.0, // velocity [0, 1]
0, // time start
60.0 / params.bpm - 0.05, // duration (seconds)
);
}
last_beat = beat;
}
}
}
Posts in Genuary 2023:
- Genuary 2023.01: Perfect loop
- Genuary 2023.02: Made in 10 minutes
- Genuary 2023.03: Glitch art
- Genuary 2023.04: Intersections
- Genuary 2023.05: Debug view
- Genuary 2023.06: Steal like an artist
- Genuary 2023.07: Sample a color palette
- Genuary 2023.08: Signed Distance Functions
- Genuary 2023.09: Plants
- Genuary 2023.10: Generative Music
- Genuary 2023.11: Suprematism
- Genuary 2023.12: Tessellation
- Genuary 2023.13: Something you've always wanted to learn
- Genuary 2023.14: Asemic Writing
- Genuary 2023.15: Sine Waves
- Genuary 2023.16: Reflections of a Reflection
- Genuary 2023.17: A grid inside a grid inside a grid
- Genuary 2023.18: Definitely not a grid
- Genuary 2023.19: Black and white
- Genuary 2023.20: Art Deco
- Genuary 2023.21: Persian Carpet
- Genuary 2023.22: Shadows
- Genuary 2023.23: Moiré
- Genuary 2023.24: Textile
- Genuary 2023.25: Yayoi Kusama
- Genuary 2023.26: My kid could have made that
- Genuary 2023.27: In the style of Hilma Af Klint
- Genuary 2023.28: Generative poetry
- Genuary 2023.29: Maximalism
- Genuary 2023.30: Minimalism
- Genuary 2023.31: Break a previous image