def test_unnested_climaxes(obj): section1 = Voice.merge_lists(*obj.nested_scale_degrees[:4]) section2 = Voice.merge_lists(*obj.nested_scale_degrees[4:8]) section3 = Voice.merge_lists(*obj.nested_scale_degrees[8:12]) section4 = Voice.merge_lists(*obj.nested_scale_degrees[12:]) if max(section1) == max(section2): return False if max(section3) <= max(section4): return False return True
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 test_bounds(obj): if min(obj.unnested_scale_degrees) < -3: return False if max(obj.unnested_scale_degrees) > 7: return False if max(obj.unnested_scale_degrees) < 5: return False section1 = Voice.merge_lists(*obj.nested_scale_degrees[:3]) if min(section1) < 0: return False return True
def has_melody_figure(self): """Check specific melody against figuration options""" last_rhythm_symbol = self.rhythm_symbols[self.chord_index - 1] if last_rhythm_symbol == -1: if self.chord_index == 15: section3 = Voice.merge_lists(*self.nested_scale_degrees[8:12]) section4 = Voice.merge_lists(*self.nested_scale_degrees[12:14]) if max(section3) <= max(section4): return False self.nested_scale_degrees[self.chord_index - 1] = [self.previous_degree_choice] return True if last_rhythm_symbol == -2: self.melody_figure_options[self.chord_index - 1] = (self.get_pickup_sequences( self.current_degree_choice)) return self.add_valid_figure() remaining_figures = self.melody_figure_options[self.chord_index - 1] if remaining_figures: return self.add_valid_figure() degree_mvmt = self.current_degree_choice - self.previous_degree_choice melody_slope = Voice.calculate_slope(degree_mvmt) degree_mvmt = abs(degree_mvmt) embellish_amount = len(self.finalized_rhythms[self.chord_index - 1]) if embellish_amount == 2: all_figurations = self.all_single_figurations elif embellish_amount == 3: all_figurations = self.all_double_figurations possible_scale_degrees = all_figurations[degree_mvmt]( self.previous_degree_choice, self.current_degree_choice, melody_slope) self.melody_figure_options[self.chord_index - 1] = possible_scale_degrees return self.add_valid_figure()
def add_valid_figure(self): """Find and add specific figuration of base melody using idioms""" valid_figure = None remaining_figures = self.melody_figure_options[self.chord_index - 1] random.shuffle(remaining_figures) # alias has side effect but allows easier referencing while remaining_figures: inbetween, fig_type = remaining_figures.pop() if min(inbetween) < -3 or max(inbetween) > 7: continue if self.chord_index - 1 < 3 and min(inbetween) < 0: continue unnested_scalar_melody = self.unnested_scale_degrees[:] unnested_scalar_melody.extend(inbetween) """ chord 8 and 12 are short-circuited only need to evaluate once going forward so, use the next indices for testing at divisible checkpoints """ if self.chord_index == 13 and max(unnested_scalar_melody) < 5: continue if self.chord_index == 9: section1 = Voice.merge_lists(*self.nested_scale_degrees[:4]) section2 = Voice.merge_lists(*self.nested_scale_degrees[4:8]) if max(section1) == max(section2): continue valid_figure = inbetween break if valid_figure is None: return False self.nested_scale_degrees[self.chord_index - 1] = [ self.previous_degree_choice, *valid_figure ] self.chosen_figurations[self.chord_index - 1] = fig_type return True
def test_list_merger(self): self.assertEqual(Voice.merge_lists([]), []) self.assertEqual(Voice.merge_lists([], [], []), []) self.assertEqual(Voice.merge_lists([], [0], []), [0]) self.assertEqual(Voice.merge_lists([5], [], []), [5]) self.assertEqual(Voice.merge_lists([], [], [2, 3]), [2, 3]) self.assertEqual(Voice.merge_lists([4], [], [9, 1, 3], []), [4, 9, 1, 3]) self.assertEqual(Voice.merge_lists([1, 2], [3], [4, 5]), [1, 2, 3, 4, 5]) list1 = [-5, -4] list2 = [-3, -2] list3 = Voice.merge_lists(list1, list2) list3.append(0) self.assertFalse(list1[-1] == 0) self.assertFalse(list2[-1] == 0) list4 = Voice.merge_lists(list1, []) self.assertTrue(list1 is not list4)
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