music_generator.py
To reuse some basic code we will create a helper file music_generator.py
, containing multiple functions how to convert our genomes to a playable sound with pyo.
The functions are based on the example from Kie Codes that can be found here.
First we need to import pyo.
from pyo import *
We define a few variables we will use across our network regarding number of bars, number of notes and bits per note in our generated melodies.
BITS_PER_NOTE = 4
NUM_BARS = 8
NUM_NOTES = 4
We define a function that converts our genome containing bits (0s and 1s) to integers.
def int_from_bits(bits) -> int:
return int(sum([bit*pow(2, index) for index, bit in enumerate(bits)]))
This function converts our genome to a melody dictionary containing all necessary information.
def genome_to_melody(genome, num_bars: int, num_notes: int, num_steps: int,
pauses: int, key: str, scale: str, root: int):
notes = [genome[i * BITS_PER_NOTE:i * BITS_PER_NOTE + BITS_PER_NOTE] for i in range(num_bars * num_notes)]
note_length = 4 / float(num_notes)
scl = EventScale(root=key, scale=scale, first=root)
melody = {
"notes": [],
"velocity": [],
"beat": []
}
for note in notes:
integer = int_from_bits(note)
if not pauses:
integer = int(integer % pow(2, BITS_PER_NOTE - 1))
if integer >= pow(2, BITS_PER_NOTE - 1):
melody["notes"] += [0]
melody["velocity"] += [0]
melody["beat"] += [note_length]
else:
if len(melody["notes"]) > 0 and melody["notes"][-1] == integer:
melody["beat"][-1] += note_length
else:
melody["notes"] += [integer]
melody["velocity"] += [127]
melody["beat"] += [note_length]
steps = []
for step in range(num_steps):
steps.append([scl[(note+step*2) % len(scl)] for note in melody["notes"]])
melody["notes"] = steps
return melody
At last we define a function that convert the genome to a list of pyo events, which we can play and listen to.
def genome_to_events(genome, num_bars: int, num_notes: int, num_steps: int,
pauses: bool, key: str, scale: str, root: int, bpm: int):
melody = genome_to_melody(genome, num_bars, num_notes, num_steps, pauses, key, scale, root)
return [
Events(
midinote=EventSeq(step, occurrences=1),
midivel=EventSeq(melody["velocity"], occurrences=1),
beat=EventSeq(melody["beat"], occurrences=1),
attack=0.001,
decay=0.05,
sustain=0.5,
release=0.005,
bpm=bpm
) for step in melody["notes"]
]