def build_chord_from_roman_numeral(self, chosen_target_degree, previous_chord): """ Returns (built_chord, name_of_chord, generation_method) """ chord_root_note = roman_numeral_to_note( chosen_target_degree, self.parent_scale_allowed_notes) # Perform weighted coin toss use_chord_voicing_from_library = decide_will_event_occur( self.chance_to_use_voicing_from_library) if use_chord_voicing_from_library: # First, lets filter the voicings library down to match the chord size and roman numeral constraints. built_chord, name_of_chord, generation_method = self.build_chord_with_random_good_voicing( chosen_target_degree, chord_root_note, previous_chord) if built_chord != -1: return (built_chord, name_of_chord, generation_method) # If all else fails, build it randomly. built_chord = self.build_chord_with_root_randomly( chord_root_note, chosen_target_degree, previous_chord) generation_method = '\n\t- Built {} by picking chord tones at random.'.format( chosen_target_degree) name_of_chord = None if built_chord != -1: name_of_chord = determine_chord_name( flatten_note_set(built_chord), self.key, constants['scales'][self.scale]) return (built_chord, name_of_chord, generation_method)
def generate_melody(self): result = [] previous_note = None for i in range(self.length): candidate_note = random.choice(self.allowed_notes) # If repeats are disallowed, allow up to 6 retries to find a different noteset. if self.disallow_repeats and previous_note is not None: max_retries = 6 retries = 0 while set(flatten_note_set([previous_note])) == set( flatten_note_set([candidate_note ])) and retries < max_retries: retries += 1 candidate_note = random.choice(self.allowed_notes) result.append([candidate_note]) previous_note = candidate_note return result
def generate_next_chord(self, previous_chord, previous_chord_degree, previous_chord_name): """ Returns (chord, chord_name, chord_degree, generation_method) """ num_notes_in_chord = random.choice(self.allowed_chord_sizes) candidate_chord = None name_of_candidate_chord = None degree_of_candidate_chord = None generation_method = '' # Perform weighted coin toss to use a chord leading chart use_chord_leading = decide_will_event_occur( self.chance_to_use_chord_leading) if use_chord_leading and len( previous_chord) > 0 and previous_chord_name[1] != '': # The chosen scale tells us which chord leading chart to use quality = 'minor' if 'maj' in self.scale: quality = 'major' if previous_chord_degree in chord_leading_chart[quality]: leading_targets = chord_leading_chart[quality][ previous_chord_degree].copy() # Now we attempt to build a chord with one of the leading targets. # This may take multiple tries, as certain scales lack certain leading targets. # If all leading targets fail to produce a chord, we abort and use a different chord creation method. leading_chord = -1 while leading_chord == -1 and len(leading_targets) > 0: chosen_target_degree = random.choice(leading_targets) leading_chord, name_of_chord, __generation_method = self.build_chord_from_roman_numeral( chosen_target_degree, previous_chord) if leading_chord != -1: candidate_chord = leading_chord name_of_candidate_chord = name_of_chord generation_method = '\t- Used {} chord leading chart suggestion {} -> {}. '.format( quality, previous_chord_name[1].split()[0], chosen_target_degree) generation_method += __generation_method leading_targets.remove(chosen_target_degree) if candidate_chord is None: # Pick a random degree of the scale and build a chord on it chosen_target_degree = random.choice(chord_charts[self.scale]) built_chord, name_of_chord, __generation_method = self.build_chord_from_roman_numeral( chosen_target_degree, previous_chord) if built_chord != -1: candidate_chord = built_chord name_of_candidate_chord = name_of_chord generation_method = '\t- Picked scale degree {} randomly.'.format( chosen_target_degree) generation_method += __generation_method if candidate_chord is None: generation_method = '\t- Picked {} scale notes at random.'.format( num_notes_in_chord) candidate_chord = pick_n_random_notes(self.allowed_notes, num_notes_in_chord) # If the candidate chord fails user-applied constraints, regenerate it randomly. # Try to design the previous algorithms so that we avoid having to regenerate from random. fails_repeats_constraint = self.disallow_repeats and chords_are_equal( previous_chord, candidate_chord) fails_topline_distance_constraint = self.chord_fails_topline_distance_constraint( candidate_chord, previous_chord) fails_topline_contour_constraint = self.chord_fails_topline_contour_constraint( candidate_chord, previous_chord) required_notes_in_chord = [] extra_notes = self.allowed_notes if fails_topline_distance_constraint or fails_topline_contour_constraint: try: required_notes_in_chord, extra_notes = self.select_notes_that_satisfy_topline_constraints( self.allowed_notes, previous_chord) except Exception: exc_info = sys.exc_info() traceback.print_exception(*exc_info) max_retries = 20 retries = 0 # If we run out of retries, we'll just have to violate the constraint. There are no other generation algorithms after this one. while (fails_repeats_constraint or fails_topline_distance_constraint or fails_topline_contour_constraint) and retries < max_retries: retries += 1 allowed_chord_sizes = list(self.allowed_chord_sizes).copy() if fails_repeats_constraint and len(allowed_chord_sizes) > 1: # Lets forcibly pick a different number of chord tones. index = allowed_chord_sizes.index(len(previous_chord)) allowed_chord_sizes.pop(index) num_notes_in_chord = random.choice(allowed_chord_sizes) num_notes_to_pick = min( num_notes_in_chord - len(required_notes_in_chord), len(extra_notes)) candidate_chord = pick_n_random_notes( extra_notes, num_notes_to_pick) + required_notes_in_chord generation_method = '\t- Picked {} scale notes at random.'.format( num_notes_in_chord) fails_repeats_constraint = self.disallow_repeats and chords_are_equal( previous_chord, candidate_chord) fails_topline_distance_constraint = self.chord_fails_topline_distance_constraint( candidate_chord, previous_chord) fails_topline_contour_constraint = self.chord_fails_topline_contour_constraint( candidate_chord, previous_chord) # fails_accidentals_constraint = (not self.allow_accidentals) and does_chord_contain_accidentals(candidate_chord, self.allowed_notes) if retries == max_retries and fails_repeats_constraint: ClientLogger.log( 'Error: After {} retries, the disallow repeats constraint could not be satisfied.' .format(retries)) if retries == max_retries and fails_topline_distance_constraint: ClientLogger.log( 'Error: After {} retries, the topline distance constraint could not be satisfied.' .format(retries)) if retries == max_retries and fails_topline_contour_constraint: ClientLogger.log( 'Error: After {} retries, the topline contour constraint could not be satisfied.' .format(retries)) if name_of_candidate_chord is None: name_of_candidate_chord = determine_chord_name( flatten_note_set(candidate_chord), self.key, constants['scales'][self.scale]) try: degree_of_candidate_chord = name_of_candidate_chord[1].split()[0] except Exception as e: degree_of_candidate_chord = '?' print('Failed to name chord. Exception: ', e) exc_info = sys.exc_info() traceback.print_exception(*exc_info) return candidate_chord, name_of_candidate_chord, degree_of_candidate_chord, generation_method
def chords_are_equal(chord_a, chord_b): return set(flatten_note_set(chord_a)) == set(flatten_note_set(chord_b))