def topline_note_passes_topline_distance_constraint( note, previous_chord_topline_position, max_topline_distance): note_position = note if not isinstance(note, int): note_position = midi_tools.note_to_numeral(note) return abs(note_position - previous_chord_topline_position) <= max_topline_distance
def chord_to_topline_int(chord): """ Covert chord topline to an integer for the purpose of comparing topline height of adjacent chords. We just convert MIDI numeral here, as it has comparison built-in, assigning higher numerals to higher notes. """ as_numerals = [] for note in chord: numeral = midi_tools.note_to_numeral(note) as_numerals.append(numeral) return max(as_numerals)
def select_notes_that_satisfy_topline_constraints(self, allowed_notes, previous_chord): required_notes_in_chord = [] previous_chord_topline_position = music_theory.chord_to_topline_int( previous_chord) note_choices_to_add_to_chord = allowed_notes.copy() # Pre-select candidates for topline notes and require the chord to have one of them. allowed_highest_notes = [] for note in allowed_notes: note_pos = midi_tools.note_to_numeral(note) if topline_note_passes_topline_constraints( self.get_current_topline_direction(), note, previous_chord_topline_position, self.max_topline_distance): allowed_highest_notes.append(note) highest_note_choice = random.choice(allowed_highest_notes) highest_note_index = note_choices_to_add_to_chord.index( highest_note_choice) # Remove that note from the remaining choices. note_choices_to_add_to_chord = note_choices_to_add_to_chord[:highest_note_index] + note_choices_to_add_to_chord[ highest_note_index + 1:] required_notes_in_chord.append(highest_note_choice) # Remove notes from candidates that would violate the topline constraints from above note_choices_to_add_to_chord_filtered = [] for note in note_choices_to_add_to_chord: passes_topline_constraints = non_topline_note_meets_topline_constraints( self.get_current_topline_direction(), note, previous_chord_topline_position, self.max_topline_distance) if self.get_current_topline_direction( ) == 'unequal' and midi_tools.note_to_numeral( note) == previous_chord_topline_position: continue if passes_topline_constraints: note_choices_to_add_to_chord_filtered.append(note) note_choices_to_add_to_chord = note_choices_to_add_to_chord_filtered return required_notes_in_chord, note_choices_to_add_to_chord
def topline_note_passes_topline_constraints(current_topline_direction, candidate_note, previous_chord_topline_position, max_topline_distance): if previous_chord_topline_position is None: return True candidate_topline_note_position = candidate_note if not isinstance(candidate_topline_note_position, int): candidate_topline_note_position = midi_tools.note_to_numeral(candidate_topline_note_position) passes_topline_distance_constraint = topline_note_passes_topline_distance_constraint(candidate_topline_note_position, previous_chord_topline_position, max_topline_distance) if current_topline_direction == "any": passes_contour_constraint = True else: passes_contour_constraint = chord_passes_topline_contour_constraint(current_topline_direction, candidate_topline_note_position, previous_chord_topline_position) return passes_topline_distance_constraint and passes_contour_constraint
def calculate_number_of_note_changes(chord_a, chord_b): to_int = lambda x: midi_tools.note_to_numeral(x) midi_int_a = map(to_int, chord_a) midi_int_b = map(to_int, chord_b) deletions = 0 for numeral_a in midi_int_a: if numeral_a not in midi_int_b: deletions += 1 additions = 0 for numeral_b in midi_int_b: if numeral_b not in midi_int_a: additions += 1 return max(additions, deletions)
def non_topline_note_meets_topline_constraints(current_topline_direction, non_topline_note, previous_chord_topline_position, max_topline_distance): """ If the contour is "down" and the topline distance is 3 semitones, any non-topline note above the previous chord's topline will be rejected. If the contour is "strictdown" and the topline distance is 3 semitones, any non-topline note above the previous chord's topline will be rejected. """ if previous_chord_topline_position is None: return True note_position = non_topline_note if not isinstance(non_topline_note, int): note_position = midi_tools.note_to_numeral(non_topline_note) passes_topline_distance_constraint = non_topline_note_passes_topline_distance_constraint(note_position, previous_chord_topline_position, max_topline_distance) if current_topline_direction == "strictdown" and note_position >= previous_chord_topline_position: return False if current_topline_direction == "down" and note_position > previous_chord_topline_position: return False if current_topline_direction == "equal" and note_position > previous_chord_topline_position: return False return passes_topline_distance_constraint
def generate_chords(self): ClientLogger.log('Generating new chord progression in {} {}.'.format( self.key.upper(), self.scale_name)) result_chord_progression = [] result_chord_names = [] previous_chord = [] previous_chord_degree = '?' previous_chord_name = '', '' while len(result_chord_progression) < self.length: # We measure "beats" in whole notes. Each chord is a whole note. self.current_beat = len(result_chord_progression) user_progression_applied = False for item in self.user_progression: if item != 'any' and item is not None: user_progression_applied = True # Perform weighted coin toss to use a pre-selected chord progression use_chord_progression_from_library = decide_will_event_occur( self.chance_to_use_common_progression) if user_progression_applied: use_chord_progression_from_library = False # Which chord progressions fit in the remaining space? allowed_progressions = [] good_progressions = [] if self.parent_scale_code in good_chord_progressions: good_progressions = good_chord_progressions[ self.parent_scale_code] else: good_progressions = good_chord_progressions[ self.contains_scale] for progression in good_progressions: if len(progression['roman_numerals']) + len( result_chord_progression) < self.length: allowed_progressions.append(progression) preselected_progression = None if use_chord_progression_from_library: preselected_progression = random.choice(allowed_progressions) elif user_progression_applied: preselected_progression = { 'roman_numerals': self.user_progression, 'name': 'User-supplied progression' } if (use_chord_progression_from_library and len(allowed_progressions) > 0) or user_progression_applied: progression_chords, progression_chord_names, lines_to_log = self.materialize_chord_progression( preselected_progression, previous_chord, previous_chord_degree) if progression_chords is None: continue result_chord_progression += progression_chords result_chord_names += progression_chord_names previous_chord = result_chord_progression[-1] previous_chord_name = result_chord_names[-1] previous_chord_degree = preselected_progression[ 'roman_numerals'][-1] for line in lines_to_log: ClientLogger.log(line) progression_str = pretty_print_progression( preselected_progression['roman_numerals']) ClientLogger.log( '{} progression complete.'.format(progression_str)) else: chord, chord_name, chord_degree, generation_method = self.generate_next_chord( previous_chord, previous_chord_degree, previous_chord_name) result_chord_progression.append(chord) result_chord_names.append(chord_name) previous_chord = chord previous_chord_name = chord_name previous_chord_degree = chord_degree ClientLogger.log( 'Added {} ( {} ). Generation pathway: \n{}'.format( previous_chord_name[0], previous_chord_degree, generation_method)) result_chord_progression_labeled_with_midi_numerals = [] for i in range(len(result_chord_progression)): chord = result_chord_progression[i] labeled_chord = [] for note in chord: note['numeral'] = midi_tools.note_to_numeral(note) labeled_chord.append(note) result_chord_progression_labeled_with_midi_numerals.append( labeled_chord) cache_key = convert_chord_to_cache_key(chord) chord_knowledge.chord_name_caches[ 'key-scale'] = self.key + self.scale chord_knowledge.chord_name_caches[cache_key] = result_chord_names[ i] chord_knowledge.chord_name_caches['key-scale'] = self.key.lower( ) + self.scale prettified_progression = [item[1] for item in result_chord_names] prettified_progression = ' -> '.join(prettified_progression) ClientLogger.log('\n') ClientLogger.log('The progression in numerals:') ClientLogger.log(prettified_progression) t = result_chord_progression_labeled_with_midi_numerals flattened = [item for sublist in t for item in sublist] flattened = [n['note'].upper() for n in flattened] notes_used = list(set(flattened)) notes_used = music_theory.sort_relative_to_tonic( notes_used, self.key.upper()) ClientLogger.log('\n') ClientLogger.log('Notes used: {}'.format(notes_used)) ClientLogger.log('--------') ClientLogger.log('Generation settings: {}'.format(self.all_settings)) return result_chord_progression_labeled_with_midi_numerals, result_chord_names