def _check_starting_pitch(self, note: Note) -> bool: if note.get_accidental() == ScaleOption.REST: return True if self._cantus[0].get_scale_degree_interval(note) not in [ -8, 1, 5, 8 ]: return False return note.get_accidental() == ScaleOption.NATURAL
def _valid_adjacent(self, note1: Note, note2: Note) -> bool: chro_interval = note1.get_chromatic_interval(note2) sdg_interval = note1.get_scale_degree_interval(note2) if chro_interval in VALID_MELODIC_INTERVALS_CHROMATIC and ( abs(sdg_interval), abs(chro_interval)) not in FORBIDDEN_INTERVAL_COMBINATIONS: if note1.get_accidental( ) == ScaleOption.NATURAL or note2.get_accidental( ) == ScaleOption.NATURAL or abs(chro_interval) == 2: return True return False
def _handles_antipenultimate_bar(self, note: Note, index: tuple) -> bool: if index == (self._length - 3, 2): if note.get_accidental( ) != ScaleOption.NATURAL or note.get_scale_degree( ) != self._mr.get_final(): return False return True
def _handles_final_leading_tone(self, note: Note, index: tuple, line: int) -> bool: if index != (self._length - 2, 0): return True final = self._mr[line].get_final() if line != 0 and self._counterpoint_lst[0][ self._length - 1].get_scale_degree() != final: return True if (note.get_scale_degree() + 1) % 7 != final: return True if (final in [2, 5, 6] and note.get_accidental() != ScaleOption.SHARP ) or (final in [1, 3, 4] and note.get_accidental() != ScaleOption.NATURAL): return False if final == 3 and note.get_scale_degree() == 4 and note.get_accidental( ) == ScaleOption.SHARP: return False return True
def _no_cross_relations_with_cantus_firmus(self, note: Note, index: tuple) -> bool: (i, j) = index cf_note = self._cantus[i - 1 if j == 0 else i + 1] if abs(cf_note.get_scale_degree_interval(note)) in [1, 8]: return cf_note.get_accidental() == note.get_accidental() return True
def _check_for_highest_and_second_note(self, note: Note, index: tuple) -> None: if self._mr.is_unison(note, self._attempt_params["highest"]): self._attempt_params["highest_has_been_placed"] = True if note.get_accidental( ) == ScaleOption.NATURAL and note.get_scale_degree( ) == self._attempt_params["second_note"]: self._attempt_params["second_note_has_been_placed"] = True
def _map_solution_onto_counterpoint_dict(self, solution: list[Note]) -> None: for i, note in enumerate(solution): (measure, beat) = self._all_indices[i] if measure in self._divided_measures: note = Note(note.get_scale_degree(), note.get_octave(), 4, note.get_accidental()) self._counterpoint[(measure, beat)] = note
def _check_last_pitch(self, note: Note) -> bool: if self._mr.get_final() != note.get_scale_degree( ) or note.get_accidental() != ScaleOption.NATURAL: return False if self._counterpoint_lst[-1].get_scale_degree_interval(note) == 2: return self._mr.is_unison(self._mr.get_leading_tone_of_note(note), self._counterpoint_lst[-1]) if self._counterpoint_lst[-1] != -2: return False return self._mr.is_unison(self._counterpoint_lst[-1], self._mr.get_leading_tone_of_note(note))
def _get_valid_durations(self, note: Note, index: tuple) -> set: (bar, beat) = index if bar == self._length - 1: return {16} if note.get_accidental() == ScaleOption.REST: return {4} if bar == 0 and beat == 0: return {12, 8, 6, 4} durs = self._get_durations_from_beat(beat) prev_length = len(durs) for check in self._rhythm_filters: durs = check(note, index, durs) if len(durs) == 0: break return durs
def _is_valid_adjacent(self, note1: Note, note2: Note) -> bool: sdg_interval = note1.get_scale_degree_interval(note2) if (note1.get_accidental() == ScaleOption.SHARP or note2.get_accidental() == ScaleOption.SHARP) and abs(sdg_interval) > 3: return False #if a sharp is not followed by a step up, we'll give it an arbitrary 50% chance of passing is_leading_tone = note1.get_accidental == ScaleOption.SHARP or ( note1.get_scale_degree() == 7 and self._mode in [ModeOption.DORIAN, ModeOption.LYDIAN]) if sdg_interval != 2 and is_leading_tone and random() > .5: return False chro_interval = note1.get_chromatic_interval(note2) if (sdg_interval in LegalIntervals["adjacent_melodic_scalar"] and chro_interval in LegalIntervals["adjacent_melodic_chromatic"] and (sdg_interval, chro_interval) not in LegalIntervals["forbidden_combinations"]): return True return False
def _backtrack(self) -> None: if (self._num_backtracks > 2000 and self._solutions_this_attempt == 0) or self._num_backtracks > 20000: return if self._solutions_this_attempt >= 100: return self._num_backtracks += 1 if len(self._remaining_indices) == 0: # print("found possible solution!") sol = [] for i in range(len(self._all_indices)): note_to_add = self._counterpoint[self._all_indices[i]] note_to_add_copy = Note(note_to_add.get_scale_degree(), note_to_add.get_octave(), note_to_add.get_duration(), note_to_add.get_accidental()) sol.append(note_to_add_copy) if self._passes_final_checks(sol): # print("FOUND SOLUTION!") self._solutions.append(sol) self._solutions_this_attempt += 1 return (bar, beat) = self._remaining_indices.pop() candidates = None if bar == 0 and (beat == 0 or self._counterpoint[(0, 0)].get_accidental() == ScaleOption.REST): candidates = list(filter(lambda n: self._cantus[0].get_chromatic_interval(n) in [-12, 0, 7, 12], self._valid_pitches)) else: candidates = list(filter(lambda n: self._passes_insertion_checks(n, (bar, beat)), self._valid_pitches)) if bar == 0 and beat == 0: candidates.append(Note(1, 0, 4, accidental = ScaleOption.REST)) shuffle(candidates) if bar == self._length - 1: candidates = list(filter(lambda n: self._valid_last_note(n), candidates)) for candidate in candidates: #start by making a copy of the note candidate = Note(candidate.get_scale_degree(), candidate.get_octave(), 8, candidate.get_accidental()) temp_highest_placed, temp_lowest_placed = self._highest_has_been_placed, self._lowest_has_been_placed if self._mr.is_unison(candidate, self._highest): self._highest_has_been_placed = True if self._mr.is_unison(candidate, self._lowest): self._lowest_has_been_placed = True (may_be_tied, must_be_tied) = self._get_tied_options(candidate, (bar, beat)) if not must_be_tied: candidate.set_duration(4 if bar != self._length - 1 else 16) self._counterpoint[(bar, beat)] = candidate if self._current_chain_is_legal((bar, beat)): self._backtrack() if may_be_tied or (bar == self._length - 2 and beat == 0): candidate.set_duration(8) index_to_remove = (bar, 2) if beat == 0 else (bar + 1, 0) index_position_all, index_position_remaining = self._all_indices.index(index_to_remove), self._remaining_indices.index(index_to_remove) self._all_indices.remove(index_to_remove) self._remaining_indices.remove(index_to_remove) del self._counterpoint[index_to_remove] self._counterpoint[(bar, beat)] = candidate if self._current_chain_is_legal((bar, beat)): self._backtrack() self._all_indices.insert(index_position_all, index_to_remove) self._remaining_indices.insert(index_position_remaining, index_to_remove) self._counterpoint[index_to_remove] = None self._highest_has_been_placed, self._lowest_has_been_placed = temp_highest_placed, temp_lowest_placed self._counterpoint[(bar, beat)] = None self._remaining_indices.append((bar, beat))
def _check_starting_pitch(self, note: Note) -> bool: if note.get_accidental() != ScaleOption.NATURAL: return False if note.get_scale_degree() != self._attempt_params["first_note"]: return False return True
def _check_starting_pitch(self, note: Note) -> bool: if note.get_accidental() == ScaleOption.REST: return True if note.get_scale_degree() != self._mr.get_final(): return False return note.get_accidental() == ScaleOption.NATURAL