Randomness is all well and good but you still need a editor's guiding hand on the rudder as you explore chancespace. A more deterministic approach to programming a composition involves using something like Bytebeat to introduce chaotic variation that is repeatable.
Bytebeat is a genre of 8-bit music that involves evaluating a single expression at a fixed frequency to generate the next sample of audio. The end result is kind of like looking at a cellular automata through a one byte window; the aliasing effects generate interesting patterns that repeat over different periods depending on the complexity and values of the expression.
For example, if you take the expression:
v = t
and evaluate it at 8000 times a second and take just the low byte you'll get a triangle wave at about 31 Hz. A more complicated and difficult-to-predict expression might be:
v = (t>>6|t|t>>(t>>16))*10+((t>>11)&7)
In Bytebeat the expressions are used to generate waveforms, but you can use the same idea to generate repeating unpredictable sequences of bits for visuals or (in the case of the example below) generating noteOns and offs.
This script sets up 24 channels with piano instruments, each assigned a different note. The bytebeat expressions are evaluated at a frequency determined by the step variable. The low 3 bytes are used to toggle each of the 24 channels, and a timer changes the state and controls the score.
Each performance is the same, but you can play with the step and increment variables to explore alternative compositions. These particular values were chosen and tweaked to make something inspired by Julius Eastman.
// deterministic Bytebeat rule-based
#include <Fluxamasynth.h>
#define maxInstruments 24
#define totalLength 60000 // should be 120000
int numInstruments = 8;
Fluxamasynth synth;
boolean noteIsOn[maxInstruments];
int note[maxInstruments];
int step = 1000;
int increment = 1000;
int tempo = 10;
long startTime;
long v = 0;
long t = 0;
int choice = 0;
void setup() {
startTime = millis();
Serial.begin(9600);
synth.setMasterVolume(75);
// Set up the individual instruments
for (int i=0; i<numInstruments; i++) {
synth.programChange(0, i, 1);
note[i] = 64+i*3;
synth.pan(i,127/numInstruments*i);
synth.setReverb(i, 3, 127, 25);
}
for (int i=0; i < numInstruments; i++) {
noteIsOn[i] = false;
}
synth.setChannelVolume(9,0); // Turn off the percussion channel
startTime = millis();
}
void loop() {
iterate();
for (int i=0; i < numInstruments/8; i++) {
iterate();
for (int j = 0; j < 8; j++) {
if (((v>>j) & 0x01) == 1) {
if (!noteIsOn[i]) {
noteIsOn[i] = true;
synth.noteOn(j*i, note[j*i], 127);
} else {
if (noteIsOn[i]) {
noteIsOn[i] = false;
synth.noteOff(j*i, note[j*i]);
}
}
}
}
}
delay(step/8);
if (millis() > startTime+5000) {
startTime = millis();
numInstruments = 4 + (numInstruments + 4) % maxInstruments;
Serial.print(numInstruments);
choice = (choice + 1) % 6;
if (choice == 5) {
for (int i=0; i < maxInstruments; i++) {
note[i] = (40 + i*7) % 100; // fifths
}
}
if (choice == 3) {
for (int i=0; i < maxInstruments; i++) {
note[i] = (64+i*4) % 100; // thirds
}
}
}
if (millis() > totalLength) {
while (1) { ; }
}
}
void iterate() {
t += increment;
// Bytebeat expressions
switch (choice) {
case 0:
v = t;
break;
case 1:
v = ((t<<1)^((t<<1)+(t>>7)&t>>12))|t>>(4-(1^7&(t>>19)))|t>>7;
break;
case 2:
v = t/32;
break;
case 3:
v = ((-t&4095)*(255&t*(t&(t>>13)))>>12)+(127&t*(234&t>>8&t>>3)>>(3&t>>14));
break;
case 4:
v=(t>>6|t|t>>(t>>16))*10+((t>>11)&7);
break;
case 5:
v=(t>>7|t|t>>6)*10+4*(t&t>>13|t>>6);
break;
}
}
Here's what it sounds like for step and increment = 1000:
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.