def _doesnt_create_hiddens_or_parallels(self, note: Note, index: int) -> bool: chro_interval = note.get_chromatic_interval(self._cf.get_note(index)) if abs(chro_interval) not in [7, 12]: return True prev_note = self._counterpoint[index - 1] next_note = self._counterpoint[index + 1] if prev_note is not None: prev_interval = prev_note.get_scale_degree_interval(note) cf_prev_interval = self._cf.get_note(index - 1).get_scale_degree_interval( self._cf.get_note(index)) if (prev_interval > 0 and cf_prev_interval > 0) or (prev_interval < 0 and cf_prev_interval < 0): return False if next_note is not None: next_interval = note.get_scale_degree_interval(next_note) cf_next_interval = self._cf.get_note( index).get_scale_degree_interval(self._cf.get_note(index + 1)) if (next_interval > 0 and cf_next_interval > 0) or (next_interval < 0 and cf_next_interval < 0): next_chro_interval = next_note.get_chromatic_interval( self._cf.get_note(index + 1)) if abs(next_chro_interval) in [7, 12]: return False return True
def _valid_harmonic_insertion(self, note: Note, index: tuple) -> bool: (i, j) = index cf_note = self._cantus[i] if self._is_valid_harmonically(note, cf_note): if j == 0: prev_note, cf_prev = self._counterpoint[(i - 1, 2)], self._cantus[i - 1] if prev_note is not None and not self._is_valid_harmonically( prev_note, cf_prev): if (i - 1, 0) in self._counterpoint and self._counterpoint[ (i - 1, 0)].get_scale_degree_interval( prev_note) != prev_note.get_scale_degree_interval( note): return False return True if j == 0: return False if cf_note.get_chromatic_interval(note) == 0: return True prev_note = self._counterpoint[(i, 0)] if prev_note is None: return False #the highest or lowest note cannot be a passing tone if abs(prev_note.get_scale_degree_interval(note)) != 2: return False next_note = self._counterpoint[(i + 1, 0)] if next_note is None: return True return note.get_scale_degree_interval( next_note) == prev_note.get_scale_degree_interval(note)
def _add_highest(self, note: Note) -> None: def remove_center_position(index: int) -> bool: if self._length % 2 == 1 and index == math.floor(self._length / 2): return False return True possible_indices = list(filter(remove_center_position, [num for num in self._remaining_indices])) shuffle(possible_indices) correct_index = None for index in possible_indices: correct_index = index prev_note = self._cf.get_note(index - 1) next_note = self._cf.get_note(index + 1) if prev_note is None and next_note is None: break if prev_note is not None and self._valid_melodic_interval(prev_note, note): break if prev_note is not None and not self._valid_melodic_interval(prev_note, note): continue if next_note is not None and not self._valid_melodic_interval(note, next_note): continue if next_note is not None and self._valid_melodic_interval(note, next_note): #we need to check if the final three notes are handled correctly if note.get_scale_degree_interval(next_note) == -2: break #if we leap down, this can't be followed by a downward step final = self._cf.get_note(index + 2) if next_note.get_scale_degree_interval(final) == -2: continue #otherwise we're legal break self._cf.insert_note(note, correct_index) self._remaining_indices.remove(correct_index)
def _no_large_parallel_leaps(self, note: Note, index: tuple) -> bool: (i, j) = index cf_prev, cf_note, cf_next = self._cantus[ i - 1], self._cantus[i], self._cantus[i + 1] if j == 2: next_note = self._counterpoint[(i + 1, 0)] if next_note is not None: next_interval, cf_next_interval = note.get_scale_degree_interval( next_note), cf_note.get_scale_degree_interval(cf_next) if ((abs(next_interval) > 2 and abs(cf_next_interval) > 2 and (abs(next_interval) > 4 or abs(cf_next_interval) > 4) and ((next_interval > 0 and cf_next_interval > 0) or (next_interval < 0 and cf_next_interval < 0)))): return False else: prev_note = self._counterpoint[( i - 1, 2)] #this index will always exist when we check this if prev_note is not None: prev_interval, cf_prev_interval = prev_note.get_scale_degree_interval( note), cf_prev.get_scale_degree_interval(cf_note) if ((abs(prev_interval) > 2 and abs(cf_prev_interval) > 2 and (abs(prev_interval) > 4 or abs(cf_prev_interval) > 4) and ((prev_interval > 0 and cf_prev_interval > 0) or (prev_interval < 0 and cf_prev_interval < 0)))): return False return True
def _valid_outline(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 CONSONANT_MELODIC_INTERVALS_CHROMATIC and ( abs(sdg_interval), abs(chro_interval)) not in FORBIDDEN_INTERVAL_COMBINATIONS: return True return False
def _is_valid_harmonically(self, note1: Note, note2: Note) -> bool: sdg_interval = note1.get_scale_degree_interval(note2) chro_interval = note1.get_chromatic_interval(note2) if ( sdg_interval in LegalIntervalsFourthSpecies["harmonic_scalar"] and chro_interval in LegalIntervalsFourthSpecies["harmonic_chromatic"] and (sdg_interval, chro_interval) not in LegalIntervalsFourthSpecies["forbidden_combinations"] ): return True return False
def _is_valid_outline(self, note1: Note, note2: Note) -> bool: sdg_interval = note1.get_scale_degree_interval(note2) chro_interval = note1.get_chromatic_interval(note2) if (sdg_interval in LegalIntervals["outline_melodic_scalar"] and chro_interval in LegalIntervals["outline_melodic_chromatic"] and (sdg_interval, chro_interval) not in LegalIntervals["forbidden_combinations"]): return True return False
def _is_valid_adjacent(self, note1: Note, note2: Note) -> bool: sdg_interval = note1.get_scale_degree_interval(note2) chro_interval = note1.get_chromatic_interval(note2) if (self._mr.is_leading_tone(note1) or self._mr.is_leading_tone(note2)) and abs(sdg_interval) > 3: return False if ( sdg_interval in LegalIntervalsFourthSpecies["adjacent_melodic_scalar"] and chro_interval in LegalIntervalsFourthSpecies["adjacent_melodic_chromatic"] and (sdg_interval, chro_interval) not in LegalIntervalsFourthSpecies["forbidden_combinations"] ): return True return False
def _valid_harmonically(self, note1: Note, note2: Note) -> bool: chro_interval = note1.get_chromatic_interval(note2) if chro_interval == 0: return False sdg_interval = note1.get_scale_degree_interval(note2) if sdg_interval in CONSONANT_HARMONIC_INTERVALS_SCALE_DEGREES and abs( chro_interval) % 12 in CONSONANT_HARMONIC_INTERVALS_CHROMATIC: combo = (abs(sdg_interval if sdg_interval <= 8 else sdg_interval - 7), abs(chro_interval) % 12) if combo not in FORBIDDEN_INTERVAL_COMBINATIONS: return True return False
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 _doesnt_create_parallels(self, note: Note, index: tuple) -> bool: (i, j) = index cf_note, next_note, cf_next = self._cantus[i], self._counterpoint[( i + 1, 0)], self._cantus[i + 1] if next_note is not None and abs( next_note.get_chromatic_interval(cf_next)) in [0, 7, 12, 19]: #next measure is a perfect interval. check for parallels first if note.get_chromatic_interval( cf_note) == next_note.get_chromatic_interval(cf_next): return False #check for hidden intervals if (j == 2 and ((note.get_scale_degree_interval(next_note) > 0 and cf_note.get_scale_degree_interval(cf_next) > 0) or (note.get_scale_degree_interval(next_note) < 0 and cf_note.get_scale_degree_interval(cf_next) < 0))): return False #if j is 2 we don't have to check what comes before if j == 0 and abs( note.get_chromatic_interval(cf_note)) in [0, 7, 12, 19]: cf_prev = self._cantus[i - 1] #check previous downbeat if it exists if i - 1 != 0 or self._start_on_beat: prev_downbeat = self._counterpoint[(i - 1, 0)] if prev_downbeat is not None and note.get_chromatic_interval( cf_note) == prev_downbeat.get_chromatic_interval( cf_prev): return False #previous weak beat will always exist when we check an insertion prev_note = self._counterpoint[(i - 1, 2)] if prev_note is not None and note.get_chromatic_interval( cf_note) == prev_note.get_chromatic_interval(cf_prev): return False #check for hiddens if (prev_note is not None and ((prev_note.get_scale_degree_interval(note) > 0 and cf_prev.get_scale_degree_interval(cf_note) > 0) or (prev_note.get_scale_degree_interval(note) < 0 and cf_prev.get_scale_degree_interval(cf_note) < 0))): return False return True
def _no_octave_leap_with_perfect_harmonic_interval(self, note: Note, index: tuple) -> bool: (i, j) = index if i not in self._divided_measures or abs( self._cantus[i].get_scale_degree_interval(note)) not in [ 1, 5, 8, 12 ]: return True other_note = self._counterpoint[(i, 0 if j == 2 else 2)] if other_note is not None and abs( note.get_scale_degree_interval(other_note)) == 8: return False return True
def _add_lowest(self, note: Note) -> None: possible_indices = [num for num in self._remaining_indices] shuffle(possible_indices) correct_index = None for index in possible_indices: #for each index, there are several scenarios: #index is preceded by a note (either the highest or the lowest) #index is followed by a note (either the highest followed by a blank, the penultimate, or the highest followed by penultimate) correct_index = index prev_note = self._cf.get_note(index - 1) next_note = self._cf.get_note(index + 1) note_after_next = self._cf.get_note(index + 2) if prev_note is None and next_note is None: break if prev_note is not None and not self._valid_melodic_interval(prev_note, note): continue if next_note is not None and not self._valid_melodic_interval(note, next_note): continue if next_note is None or note_after_next is None: break if next_note is not None and note_after_next is not None: #we are in the third to last or fourth to last position if index == self._length - 3: #if index is the antipenultimate position, there are no scenarios in which #lowest note -> penultimate -> final have an improper sequence of intervals break #otherwise we need to make sure that a leap to the highest note is handled properly final = self._cf.get_note(index + 3) lowest_to_highest = note.get_scale_degree_interval(next_note) lowest_to_penult = note.get_scale_degree_interval(note_after_next) lowest_to_final = note.get_scale_degree_interval(final) if lowest_to_highest > 3 and lowest_to_penult != lowest_to_highest - 1 and lowest_to_final != lowest_to_highest - 1: continue else: break self._cf.insert_note(note, correct_index) self._remaining_indices.remove(correct_index)
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 _no_large_parallel_leaps(self, note: Note, index: int) -> bool: prev_note = self._counterpoint[index - 1] next_note = self._counterpoint[index + 1] cf_note = self._cf.get_note(index) cf_prev_note = self._cf.get_note(index - 1) cf_next_note = self._cf.get_note(index + 1) if prev_note is not None: prev_interval = prev_note.get_scale_degree_interval(note) cf_prev_interval = cf_prev_note.get_scale_degree_interval(cf_note) if (prev_interval > 2 and cf_prev_interval > 2) or (prev_interval < -2 and cf_prev_interval < -2): if abs(prev_interval) > 4 or abs(cf_prev_interval) > 4: return False if next_note is not None: next_interval = note.get_scale_degree_interval(next_note) cf_next_interval = cf_note.get_scale_degree_interval(cf_next_note) if (next_interval > 2 and cf_next_interval > 2) or (next_interval < -2 and cf_next_interval < -2): if abs(next_interval) > 4 or abs(cf_next_interval) > 4: return False return True
def _place_highest(self, note: Note) -> bool: possible_indices = self._remaining_indices[:] if self._length % 2 == 1: possible_indices.remove((math.floor(self._length / 2), 0)) shuffle(possible_indices) index = None while len(possible_indices) > 0: index = possible_indices.pop() if not self._passes_insertion_check(note, index): continue #if it passes insertion checks, make sure last two intervals are not invalid next_note = self._get_next_note(index) if next_note is not None: last_note = self._counterpoint[(self._length - 1, 0)] if next_note.get_scale_degree_interval( last_note ) == -2 and note.get_scale_degree_interval(next_note) < -2: continue break if len(possible_indices) == 0: return False self._counterpoint[index] = note self._remaining_indices.remove(index) return True
def _is_unison(self, note1: Note, note2: Note) -> bool: return note1.get_scale_degree_interval( note2) == 1 and note1.get_chromatic_interval(note2) == 0
def _valid_range(self, note1: Note, note2: Note) -> bool: if self._valid_outline(note1, note2): return True if note1.get_scale_degree_interval(note2) == 7: return True return False
def _valid_melodic_interval(self, first_note: Note, second_note: Note) -> bool: scale_interval = first_note.get_scale_degree_interval(second_note) chro_interval = first_note.get_chromatic_interval(second_note) if scale_interval not in VALID_MELODIC_INTERVALS_SCALE_DEGREES: return False if chro_interval not in VALID_MELODIC_INTERVALS_CHROMATIC: return False return True