def test_proper_leaps(obj): # breaking this rule is too advanced for you unnested_antecedent = Voice.merge_lists(*obj.nested_scale_degrees[:8]) unnested_consequent = Voice.merge_lists(*obj.nested_scale_degrees[8:]) proper_antecedent_leaps = Voice.has_proper_leaps(unnested_antecedent) proper_consequent_leaps = Voice.has_proper_leaps(unnested_consequent) if not (proper_antecedent_leaps and proper_consequent_leaps): return False ante_cons_transition = Voice.merge_lists(*obj.nested_scale_degrees[5:10]) if (not Voice.has_proper_leaps(ante_cons_transition) and obj.nested_scale_degrees[0] != obj.nested_scale_degrees[8]): return False return True
def validate_base_melody(self): """Check current base melody with idioms""" melodic_mvmt = "".join( str(slope) for slope in self.melodic_direction[:self.chord_index + 1]) if "_" * 3 in melodic_mvmt: # Avoid long rests return False if "_" * 2 in melodic_mvmt: all_rest_indices = set() start_index = 0 while True: rest_index = melodic_mvmt.find("__", start_index) if rest_index == -1: break all_rest_indices.add(rest_index) start_index = rest_index + 1 if all_rest_indices - self.good_double_rest_indices: # Avoid triple repeats only between phrases") return False start_index = 0 while True: rest_index = melodic_mvmt.find("_", start_index) if rest_index in self.bad_single_rest_indices: return False if rest_index == -1: break start_index = rest_index + 1 relevant_melodic_mvt = melodic_mvmt[1:] current_move_distance = self.current_degree_choice - self.previous_degree_choice abs_current_move_distance = abs(current_move_distance) if abs_current_move_distance > 7: # Keep leaps within octave return False if self.chord_index == 14: if abs_current_move_distance > 4: # Don't end with a large leap return False if relevant_melodic_mvt.count('>') > relevant_melodic_mvt.count( '<'): # Descending motion should predominate return False if abs_current_move_distance > 4 and self.chord_index not in self.valid_leap_indices: # Large leap can only occur halfway through return False if len(self.unnested_scale_degrees) >= 3: if self.chord_index < 9: nested_part_half = self.nested_scale_degrees[:8] else: nested_part_half = self.nested_scale_degrees[8:] unnested_part_half = Voice.merge_lists(*nested_part_half) if not Voice.has_proper_leaps(unnested_part_half): # "Leap should be followed by contrary stepwise motion (full melody)" return False if self.chord_index == 11: ante_cons_transition = Voice.merge_lists( *self.nested_scale_degrees[5:10]) if (not Voice.has_proper_leaps(ante_cons_transition) and self.nested_scale_degrees[0] != self.nested_scale_degrees[8]): return False # score divides into 4 sections, 16 items # first 2 sections: antecedent # last 2 sections: consequent current_section = self.chord_index // 4 section_start_index = current_section * 4 section_scale_degrees = ( self.chosen_scale_degrees[section_start_index:section_start_index + 4]) end_degree = section_scale_degrees[-1] while end_degree is None: section_scale_degrees.pop() end_degree = section_scale_degrees[-1] section_max_degree = max(section_scale_degrees) if current_section <= 2: if section_scale_degrees.count(section_max_degree) > 2: return False if section_scale_degrees.count(section_max_degree) == 2: for scale_degree0, scale_degree1 in zip( section_scale_degrees, section_scale_degrees[1:]): if (scale_degree0 == section_max_degree and scale_degree0 == scale_degree1): break else: return False if self.chord_index == 2: if self.chosen_figurations[0] != "IPT": return False if self.chord_index >= 3: previous_melody_note = self.chosen_scale_degrees[self.chord_index - 3] for chord_index, melody_group in enumerate( self.nested_scale_degrees[self.chord_index - 3:self.chord_index - 1], self.chord_index - 3): for fig_index, current_melody_note in enumerate(melody_group): pitch_diff = current_melody_note - previous_melody_note if (abs(pitch_diff) > 4 and pitch_diff < 0 and (fig_index != 0 or chord_index not in self.valid_leap_indices)): return False previous_melody_note = current_melody_note if (self.chord_index not in self.quick_turn_indices and melodic_mvmt[self.chord_index - 2:] in {"><>", "<><"}): # No late melodic jukes return False if self.chord_index == 8: section1 = self.chosen_scale_degrees[:4] section2 = self.chosen_scale_degrees[4:8] if max(section1) == max(section2): return False elif self.chord_index == 15: section3 = self.chosen_scale_degrees[8:12] section4 = self.chosen_scale_degrees[12:] if max(section3) <= max(section4): return False if abs(self.nested_scale_degrees[-3][-1]) > 1: return False if (self.chosen_figurations.count("OPT") > 2 and self.nested_scale_degrees[0:4] != self.nested_scale_degrees[8:12]): return False num_still_figures = self.chosen_figurations.count("CN") num_still_figures += self.chosen_figurations.count("DN") num_still_figures += self.chosen_figurations.count("DCN") if num_still_figures > 2: return False if self.chosen_figurations.count("OPT") > 4: return False if self.chosen_figurations.count("ANT") > 1: return False return True
def test_leaps(self): self.assertFalse(Voice.has_proper_leaps([7, 6, 2, 1])) self.assertFalse(Voice.has_proper_leaps([0, 5, 6, 5])) self.assertFalse(Voice.has_proper_leaps([2, 6, 2, 3])) self.assertFalse(Voice.has_proper_leaps([4, 0, 5, 1])) self.assertFalse(Voice.has_proper_leaps([0, 5, 10, 9])) self.assertFalse(Voice.has_proper_leaps([9, 5, 0, 1])) self.assertFalse(Voice.has_proper_leaps([6, 7, 2, 2, 1])) self.assertFalse(Voice.has_proper_leaps([0, 5, 5, 5, 6])) self.assertTrue(Voice.has_proper_leaps([1, 4, 4, 3, 2])) self.assertTrue(Voice.has_proper_leaps([10, 6, 6, 7, 6])) self.assertTrue(Voice.has_proper_leaps([])) self.assertTrue(Voice.has_proper_leaps([1, 2, 3, 4])) self.assertTrue(Voice.has_proper_leaps([0, 5, 4, 3])) self.assertTrue(Voice.has_proper_leaps([5, 2, 3, 1])) self.assertTrue(Voice.has_proper_leaps([8, 6, 4, 2])) self.assertTrue(Voice.has_proper_leaps([1, 3, 5, 7]))