diff --git a/gen_alg.py b/gen_alg.py index 54fc0bb..db84491 100644 --- a/gen_alg.py +++ b/gen_alg.py @@ -1,21 +1,19 @@ -# Genetic Algorithm file +# gen_alg.py # By Danny Noe +# Evolutionary Composition +# gen_alg.py contains the code for the genetic algorithm data and functions. import random - import numpy - from deap import algorithms, base, creator, tools - from representation import representation, save_best_melodies, Melody - class algorithm_args: """ The algorithm_args object contains all of the algorithm arguments recieved on the command-line. algorithm_args simplifies number of arguments that need to be sent to run_program() """ - def __init__(self, algorithm: str, pop_size: int, ngen: int, mu: int, lambda_: int, cxpb: float, mutpb: float): + def __init__(self, algorithm: str, pop_size: int, ngen: int, mu: int, lambda_: int, cxpb: float, mutpb: float) -> None: """ Initializes the algorithm_args object with arguments given on the command-line Input: algorithm: str, the name of the genetic algorithm | pop_size: int, the size of the initial population @@ -33,7 +31,7 @@ def __init__(self, algorithm: str, pop_size: int, ngen: int, mu: int, lambda_: i self.mutpb = mutpb return -def cx_music(input_mel1: Melody, input_mel2: Melody): +def cx_music(input_mel1: Melody, input_mel2: Melody) -> tuple: """ cx_music() performs a crossover operation on two given melodies Input: input_mel1: Melody, the first melody | input_mel2: Melody, the second melody @@ -50,7 +48,7 @@ def cx_music(input_mel1: Melody, input_mel2: Melody): return input_mel1, input_mel2 -def mut_melody(input_mel: Melody): +def mut_melody(input_mel: Melody) -> tuple: """ mut_melody() mutates a given melody. The function iterates over the whole melody performing a coin flip on each note determining if it should be pitch shifted up or down @@ -68,7 +66,7 @@ def mut_melody(input_mel: Melody): return input_mel, -def load_midi(population: list, toolbox, key: str): +def load_midi(population: list, toolbox, key: str) -> int: """ The load_midi() function reads MIDI files entered by the user from the ./midi_out/ directory and adds them to the population @@ -96,7 +94,7 @@ def load_midi(population: list, toolbox, key: str): return len(population) -def run_genetic_algorithm(rep_obj, alg_args): +def run_genetic_algorithm(rep_obj, alg_args) -> tuple: """ The run_genetic_algorithm is the main method of the evolutionary composition project. The method takes in all arguments, then runs the selected genetic algorithm. diff --git a/main.py b/main.py index 3af02f2..4a0b8ae 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,7 @@ -# Main file for Danny Noe's Evolutionary Music Project +# main.py +# By Danny Noe +# Evolutionary Composition +# main.py is responsible for initializing the program, as well as handling the command-line arguments import argparse from representation import representation, available_outports @@ -11,7 +14,6 @@ parser.add_argument('-k', '--key_signature', type=str, help="Sets the key signature for the program", choices=key_sig, default="C") parser.add_argument('-t', '--tempo', type=int, help="Sets the tempo (in BPM) for the program", choices=range(1, 301), metavar="[0,300]", default=120) - def str2bool(v): if isinstance(v, bool): return v diff --git a/music_data.py b/music_data.py index a9494f9..b3155d5 100644 --- a/music_data.py +++ b/music_data.py @@ -1,5 +1,6 @@ # music_date.py # Danny Noe +# Evolutionary Composition # music_data.py houses constant data used by representation.py. This file has no executable code ############################## diff --git a/representation.py b/representation.py index 07c314f..01c25fe 100644 --- a/representation.py +++ b/representation.py @@ -1,5 +1,7 @@ -# Representation.py +# representation.py # By Danny Noe +# Evolutionary Composition +# representation.py is responsible for the musical representation data. import random import mido @@ -368,9 +370,9 @@ def shift_scale(key: str) -> list: return shifted_scale -def get_new_pitch(prev_pitch: int, interval: int, ascend_or_descend: int) -> int: +def calculate_pitch(prev_pitch: int, interval: int, ascend_or_descend: int) -> int: """ - Helper function for get_new_note_pitch. Returns a new_pitch based off the prev_pitch + Helper function for get_new_pitch(). Returns a new_pitch based off the prev_pitch Input: prev_pitch: int, the previous pitch | interval: int, the interval to shift the new_pitch | ascend_or_descend: int, dictates if the pitch is shifted up or down Output: new_pitch: int, the pitch of the new note """ @@ -391,10 +393,9 @@ def get_new_pitch(prev_pitch: int, interval: int, ascend_or_descend: int) -> int return new_pitch - -def get_new_note_pitch(prev_pitch: int, ascend_or_descend: int, scale: list) -> int: +def get_new_pitch(prev_pitch: int, ascend_or_descend: int, scale: list) -> int: """ - Helper function for next_note. Uses the previous note's pitch to dictate the pitch of the pitch it generates. + Helper function for get_new_note_pitch(). Uses the previous note's pitch to dictate the pitch of the pitch it generates. Pitchs could also be a rest or a completely random new pitch in the scale, not associated with the previous pitch. Input: prev_pitch: int, the previous pitch value | ascend_or_descend: int, dictates if the pitch is shifted up or down (0 = descend, 1 = ascend) scale: list, the note pitches available in the current scale @@ -413,29 +414,30 @@ def get_new_note_pitch(prev_pitch: int, ascend_or_descend: int, scale: list) -> new_pitch = prev_pitch elif option == 1: # step - new_pitch = get_new_pitch(prev_pitch, 2, ascend_or_descend) + new_pitch = calculate_pitch(prev_pitch, 2, ascend_or_descend) elif option == 2: # third - new_pitch = get_new_pitch(prev_pitch, 3, ascend_or_descend) + new_pitch = calculate_pitch(prev_pitch, 3, ascend_or_descend) elif option == 3: # skip skip = random.randint(1, 4) - new_pitch = get_new_pitch(prev_pitch, skip, ascend_or_descend) + new_pitch = calculate_pitch(prev_pitch, skip, ascend_or_descend) elif option == 4: # Octave - new_pitch = get_new_pitch(prev_pitch, 12, ascend_or_descend) + new_pitch = calculate_pitch(prev_pitch, 12, ascend_or_descend) elif option == 5: # jump jump = random.randint(4, 14) - new_pitch = get_new_pitch(prev_pitch, jump, ascend_or_descend) + new_pitch = calculate_pitch(prev_pitch, jump, ascend_or_descend) elif option == 6: # random - new_pitch = random.choice(scale) + pitch_str = random.choice(scale) + new_pitch = NOTE_TO_MIDI[pitch_str] else: # rest @@ -443,6 +445,15 @@ def get_new_note_pitch(prev_pitch: int, ascend_or_descend: int, scale: list) -> return new_pitch + +def get_new_note_pitch(prev_pitch: int, ascend_or_descend: int, scale: list) -> int: + + new_pitch = get_new_pitch(prev_pitch, ascend_or_descend, scale) + while MIDI_TO_NOTE[new_pitch] not in NOTE_RANGE: + new_pitch = get_new_pitch(prev_pitch, ascend_or_descend, scale) + + return new_pitch + def get_new_beat(prev_beats: float) -> float: """ Helper function for get_new_note_beat(). Returns a float for the new note's beat value.