def remap_chord(self, hc): from tonalmodel.interval import Interval as TonalInterval chord = hc.chord if not isinstance(chord, SecondaryChord): f = self.__hc_flip_map[hc] if hc in self.__hc_flip_map.keys() else \ ChromaticPitchReflectionFunction(hc.tonality, self.cue_pitch, self.domain_pitch_range) # FlipOnTonality(hc.tonality, self.cue_pitch, self.domain_pitch_range) new_chord_tones = [f.tonal_function[t[0]] for t in chord.tones] chords = ChordClassifier.classify_all_roots( new_chord_tones, f.range_tonality) if chords is not None and len(chords) > 0: return chords[0] else: raise Exception( 'Cannot remap/classify chord {0} based on chord.'.format( ', '.join( str(t.diatonic_symbol) for t in new_chord_tones))) else: if hc in self.__hc_flip_map.keys(): secondary_function = self.__hc_flip_map[hc].tonal_function else: secondary_function = self._build_secondary_flip_function( hc).tonal_function base_f = self._build_chromatic_reflection(hc) root_mapped_tonality = base_f.range_tonality mapped_denominator = TonalInterval.calculate_tone_interval( root_mapped_tonality.root_tone, secondary_function. range_tonality.root_tone).diatonic_distance + 1 # Alternatively, in the else part, we could have done: # secondary_function = f.tonal_function.create_adapted_function(secondary_tonality, secondary_tonality) # but to be consistent within the logic, we go for the reflection_tests constructiobn of # the secondary function # as embodied in tFlip._build_secondary_flip_function() new_chord_tones = [secondary_function[t[0]] for t in chord.tones] secondary_tonality = secondary_function.range_tonality chords = ChordClassifier.classify_all_roots( new_chord_tones, secondary_tonality) if chords is not None and len(chords) > 0: new_chord = chords[0] else: raise Exception( 'Cannot remap/classify chord {0} based on chord.'.format( ', '.join( str(t.diatonic_symbol) for t in new_chord_tones))) # mapped_numerator = TonalInterval.calculate_tone_interval( # new_chord.root_tone, # secondary_function.range_tonality.root_tone).diatonic_distance + 1 secondary_chord_template = SecondaryChordTemplate( new_chord.chord_template, mapped_denominator, secondary_tonality.modality.modality_type) secondary_chord = SecondaryChord(secondary_chord_template, root_mapped_tonality, secondary_function.range_tonality) return secondary_chord
def _build_primary_map(self): domain_scale = self.domain_tonality.annotation[:-1] tonal_map = dict() if self.reflect_type == FlipType.CenterTone: for tone in domain_scale: interval = Interval.calculate_tone_interval(tone, self.cue_tone) end_tone = interval.get_end_tone(self.cue_tone) tonal_map[tone] = end_tone else: if self.reflect_type == FlipType.LowerNeighborOfPair: lower_index = domain_scale.index(self.cue_tone) upper_index = (lower_index + 1) % len(domain_scale) else: upper_index = domain_scale.index(self.cue_tone) lower_index = (upper_index - 1) % len(domain_scale) tonal_map[domain_scale[upper_index]] = domain_scale[lower_index] tonal_map[domain_scale[lower_index]] = domain_scale[upper_index] last_lower = domain_scale[lower_index] last_upper = domain_scale[upper_index] for i in list(reversed(range(0, lower_index))): new_lower = domain_scale[i] interval = Interval.calculate_tone_interval(new_lower, last_lower) new_upper = interval.get_end_tone(last_upper) tonal_map[new_lower] = new_upper last_lower = new_lower last_upper = new_upper last_lower = domain_scale[lower_index] last_upper = domain_scale[upper_index] for i in list(range((upper_index + 1), len(domain_scale))): new_upper = domain_scale[i] interval = Interval.calculate_tone_interval(last_upper, new_upper) new_lower = interval.negation().get_end_tone(last_lower) tonal_map[new_upper] = new_lower last_lower = new_lower last_upper = new_upper range_tones = list(reversed([tonal_map[tone] for tone in domain_scale])) first_tone = range_tones[-1] range_tones = [first_tone] + range_tones[:-1] # Determine the tonality of the range range_tonality = Tonality.find_tonality(range_tones) return tonal_map, range_tonality
def compute_chord_degree(hc): if hc.chord.chord_template.diatonic_basis is None: return hc.chord.chord_template.scale_degree else: interval = Interval.calculate_tone_interval( hc.tonality.diatonic_tone, hc.chord.chord_template.diatonic_basis) return interval.diatonic_distance + 1
def _build_shift_function(self, hc): if hc in self.hc_pitch_function_map.keys(): # reuse return self.hc_pitch_function_map[hc] if not isinstance(hc.chord, SecondaryChord): f = CrossTonalityShiftPitchFunction( hc.tonality, self.domain_pitch_range, self.root_shift_interval, self.range_modality_type, self.modal_index if self.modal_index is not None else hc.tonality.modal_index) range_tonality = f.range_tonality else: if self.root_shift_interval: range_tonality = Tonality.create( self.range_modality_type if self.range_modality_type is not None else hc.tonality.modality_type, self.root_shift_interval.get_end_tone( hc.tonality.diatonic_tone), self.modal_index if self.modal_index is not None else hc.tonality.modal_index) else: range_tonality = hc.tonality # Range tone is the tone from the denominator, e.g. the ii in V/ii. range_tone = range_tonality.annotation[ hc.chord.chord_template.secondary_scale_degree - 1] root_tone_interval = TonalInterval.calculate_tone_interval(hc.chord.secondary_tonality.root_tone, range_tone) \ if not TonalInterval.is_negative(self.root_shift_interval) else \ -TonalInterval.calculate_tone_interval(range_tone, hc.chord.secondary_tonality.root_tone) f = CrossTonalityShiftPitchFunction( hc.chord.secondary_tonality, self.domain_pitch_range, root_tone_interval, hc.chord.secondary_tonality.modality_type, hc.chord.secondary_tonality.modal_index) self.hc_pitch_function_map[hc] = (f, range_tonality) return f, range_tonality
def find_modality(tones): answers = list() if len(tones) == 5: for t in [ModalityType.MajorPentatonic]: modality_spec = PentatonicModality.MODALITY_DEFINITION_MAP[t] p1 = Interval.parse('P:1') for scale_start in range(0, 5): intervals = [p1] + [ Interval.calculate_tone_interval( tones[(scale_start + i) % 5], tones[(scale_start + i + 1) % 5]) for i in range(0, len(tones)) ] if intervals == modality_spec.incremental_intervals: answers.append( PentatonicModality.create(t, (-scale_start) % len(tones))) return answers
def notes_compare(pattern_annotation, note, hc, search_options): pattern_note = pattern_annotation.note if pattern_note.duration != note.duration: return False if pattern_note.diatonic_pitch is None and note.diatonic_pitch is None: return True if (pattern_note.diatonic_pitch is None and note.diatonic_pitch is not None) or \ (pattern_note.diatonic_pitch is not None and note.diatonic_pitch is None): return False target_pitch_scalar_degree = MelodicSearch.compute_scale_degree( note.diatonic_pitch, hc) target_chord_interval = MelodicSearch.compute_chord_interval(hc, note) if search_options.note_match_chordal: if pattern_annotation.is_chordal: if target_chord_interval is None: return False if search_options.note_match_chordal_precision: return pattern_annotation.chord_interval.diatonic_distance == \ target_chord_interval.diatonic_distance return True if pattern_annotation.is_scalar: if target_pitch_scalar_degree is not None: if search_options.note_match_scalar_precision: return pattern_annotation.scale_degree == target_pitch_scalar_degree return True return False # Pattern note is non-scalar if target_pitch_scalar_degree is not None: # Target is scalar return search_options.note_match_non_scalar_to_scalar # Target pitch is non-scalar if not search_options.note_match_non_scalar_precision: return True target_note_interval = \ Interval.calculate_tone_interval(hc.tonality.root_tone, note.diatonic_pitch.diatonic_tone) if target_note_interval.diatonic_distance != pattern_annotation.root_based_interval.diatonic_distance: return False return True
def find_modality(tones): answers = list() if len(tones) == 7: for t in [ ModalityType.Major, ModalityType.NaturalMinor, ModalityType.MelodicMinor, ModalityType.HarmonicMinor, ModalityType.HarmonicMajor ]: modality_spec = DiatonicModality.MODALITY_DEFINITION_MAP[t] p1 = Interval.parse('P:1') for scale_start in range(0, 7): intervals = [p1] + [ Interval.calculate_tone_interval( tones[(scale_start + i) % 7], tones[(scale_start + i + 1) % 7]) for i in range(0, len(tones)) ] if intervals == modality_spec.incremental_intervals: answers.append( DiatonicModality.create(t, (-scale_start) % len(tones))) return answers
def _build_extension_map(self): ltrs = 'CDEFGAB' extension = dict() domain_scale = self.domain_tonality.annotation[:-1] domain_start_index = ltrs.index(domain_scale[0].diatonic_letter) domain_index_list = list(ltrs[domain_start_index:] + ltrs[:domain_start_index]) # One time calculations based on lower upper if self.reflect_type != FlipType.CenterTone: if self.reflect_type == FlipType.LowerNeighborOfPair: lower_domain_index = domain_scale.index(self.cue_tone) upper_domain_index = (lower_domain_index + 1) % len(domain_scale) else: upper_domain_index = domain_scale.index(self.cue_tone) lower_domain_index = (upper_domain_index - 1) % len(domain_scale) lower_tone = domain_scale[lower_domain_index] upper_tone = domain_scale[upper_domain_index] lower_ltr_index = domain_index_list.index(lower_tone.diatonic_letter) lower_augmentation = lower_tone.augmentation_offset upper_ltr_index = domain_index_list.index(upper_tone.diatonic_letter) upper_augmentation = upper_tone.augmentation_offset else: lower_tone = None upper_tone = None lower_ltr_index = None lower_augmentation = None upper_ltr_index = None upper_augmentation = None for ltr in 'CDEFGAB': for aug in ['bb', 'b', '', '#', "##"]: tone = DiatonicFoundation.get_tone(ltr + aug) if tone not in self.tonal_map.keys(): if self.reflect_type == FlipType.CenterTone: interval = Interval.calculate_tone_interval(tone, self.cue_tone) if interval: # Some intervals are illegal, eg Cbb --> C, for now ignore end_tone = interval.get_end_tone(self.cue_tone) extension[tone] = end_tone else: tone_ltr_index = domain_index_list.index(tone.diatonic_letter) tone_augmentation = tone.augmentation_offset if tone_ltr_index >= 0 and (tone_ltr_index < lower_ltr_index or (tone_ltr_index == lower_ltr_index and tone_augmentation <= lower_augmentation)): interval = Interval.calculate_tone_interval(tone, lower_tone) if interval: upper = interval.get_end_tone(upper_tone) extension[tone] = upper elif tone_ltr_index < len(domain_index_list) and (tone_ltr_index > upper_ltr_index or (tone_ltr_index == upper_ltr_index and tone_augmentation >= upper_augmentation)): interval = Interval.calculate_tone_interval(tone, upper_tone) if interval: new_lower = interval.get_end_tone(lower_tone) extension[tone] = new_lower else: # Between the two limits upper_interval = Interval.calculate_tone_interval(tone, upper_tone) lower_interval = Interval.calculate_tone_interval(lower_tone, tone) if upper_interval is None and lower_interval is None: continue elif upper_interval is None: extension[tone] = upper_tone elif lower_interval is None: extension[tone] = lower_tone else: if abs(lower_interval.chromatic_distance) <= abs(upper_interval.chromatic_distance): extension[tone] = lower_interval.negation().get_end_tone(upper_tone) else: extension[tone] = upper_interval.negation().get_end_tone(lower_tone) return extension