Beispiel #1
0
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
Beispiel #2
0
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
Beispiel #4
0
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
Beispiel #5
0
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)
Beispiel #6
0
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
Beispiel #7
0
    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