def test_semitone_difference(self): assert DiatonicFoundation.get_chromatic_distance(DiatonicPitch.parse('C:4')) == 48 equi_list = DiatonicFoundation.add_semitones(DiatonicPitch.parse('eb:4'), 13) assert equi_list is not None assert len(equi_list) == 3 assert DiatonicPitch.parse('E:5') in equi_list assert DiatonicPitch.parse('D##:5') in equi_list assert DiatonicPitch.parse('Fb:5') in equi_list a = DiatonicPitch.parse('C:5') b = DiatonicPitch.parse('F#:4') assert DiatonicFoundation.semitone_difference(a, b) == 6 assert DiatonicFoundation.semitone_difference(b, a) == -6
def __init__(self, anchor_pitch=DiatonicPitch.parse('A:0'), anchor_value=9, pitch_unit=1): """ Constructor, """ self.__anchor_pitch = anchor_pitch if not isinstance(self.anchor_pitch, DiatonicPitch): raise Exception('Anchor is not a DiatonicPitch') self.__anchor_value = anchor_value self.__pitch_unit = pitch_unit anchor_index = self.anchor_pitch.chromatic_distance base_value = anchor_value - anchor_index * pitch_unit self.value_to_pitch = OrderedMap() self.pitch_to_value = dict() for i in range(ChromaticScale.chromatic_start_index(), ChromaticScale.chromatic_end_index() + 1): pitch = DiatonicFoundation.map_to_diatonic_scale(i)[0] value = base_value + i * pitch_unit self.value_to_pitch.insert(value, pitch) self.pitch_to_value[pitch] = value PitchRangeInterpreter.__init__(self)
def get_end_pitch(self, pitch): """ Given a pitch and this interval, Assuming pitch is the starting pitch of the interval, compute the end pitch. Args: pitch: DiatonicPitch Returns: DiatonicPitch of end tone """ diatonic_dist = pitch.diatonic_distance() + self.diatonic_distance tone_index = diatonic_dist % 7 end_pitch_string = DiatonicTone.get_diatonic_letter(tone_index) end_pitch_octave = diatonic_dist // 7 chromatic_dist = pitch.chromatic_distance + self.chromatic_distance normal_pitch = DiatonicPitch( end_pitch_octave, DiatonicFoundation.get_tone(end_pitch_string)) alteration = chromatic_dist - normal_pitch.chromatic_distance end_pitch_string += DiatonicTone.augmentation(alteration) return DiatonicPitch.parse(end_pitch_string + ':' + str(end_pitch_octave))
def _build_non_tonal_pitch_map(self, pmap): octave_start = self._domain_pitch_range.start_index // 12 octave_end = self._domain_pitch_range.end_index // 12 for octave in range(octave_start, octave_end + 1): for ltr in 'ABCDEFG': for aug in ['bb', 'b', '', '#', "##"]: p = DiatonicPitch(octave, DiatonicFoundation.get_tone(ltr + aug)) if self._domain_pitch_range.is_pitch_inbounds(p) and p not in pmap.keys(): closest_p, closest_distance = self._find_closest_pitch(p) if closest_p not in pmap.keys(): continue closest_p_prime = pmap[closest_p] t = self.tonal_function[p.diatonic_tone] if closest_p > p: o = closest_p_prime.octave + 1 if DiatonicPitch.crosses_c(closest_p_prime.diatonic_tone, t, True) else closest_p_prime.octave else: o = closest_p_prime.octave - 1 if DiatonicPitch.crosses_c(closest_p_prime.diatonic_tone, t, False) else closest_p_prime.octave p_prime = DiatonicPitch(o, t) pmap[p] = p_prime # The mapping is not-symmetrical. # e.g. E-pentatonic reflection_tests on G#, f(A#)->G and f(g)->G## return pmap
def test_major_modality(self): print('Testing Major Modality: D-Major, cue=G:4') domain_tonality = Tonality.create(ModalityType.Major, DiatonicFoundation.get_tone('D')) cue_pitch = DiatonicPitch.parse('G:4') domain_pitch_range = PitchRange.create('D:3', 'C#:6') f = ChromaticPitchReflectionFunction(domain_tonality, cue_pitch, domain_pitch_range, FlipType.CenterTone) TestChromaticPitchReflectionFunction.print_function(f) # Ensure all domain keys are in the domain_pitch_range for pitch in f.domain: assert domain_pitch_range.is_pitch_inbounds(pitch), \ 'Pitch {0} is not in range {1}.'.format(pitch, domain_pitch_range) assert DiatonicPitch.parse('D:3') in f.domain assert DiatonicPitch.parse('C#:6') in f.domain # Test for octave coverage assert 6 == f['D:3'].octave assert 5 == f['C#:4'].octave assert 5 == f['D:4'].octave assert 4 == f['C#:5'].octave assert 4 == f['D:5'].octave assert 3 == f['C#:6'].octave
def create(start_spn, end_spn): """ Create PitchRange based on start and end scientific pitch notation. Args: start_spn: start spn (pitch string). end_spn: end spn (pitch string). Returns: PitchRange based on inputs. """ start = DiatonicFoundation.get_chromatic_distance( DiatonicPitch.parse(start_spn) if isinstance(start_spn, str ) else start_spn) end = DiatonicFoundation.get_chromatic_distance( DiatonicPitch.parse(end_spn) if isinstance(end_spn, str ) else end_spn) return PitchRange(start, end)
def is_pitch_inbounds(self, pitch): """ Determines if given chromatic location is in bounds of range. Args: pitch: spn text for pitch, e.g. 'c:4' or DiatonticPitch object. Returns: boolean indicating if in bounds. """ p = DiatonicPitch.parse(pitch) if isinstance(pitch, str) else pitch return self.is_inbounds(DiatonicFoundation.get_chromatic_distance(p))
def test_low_range(self): ranges = PitchRange.create("A:0", "C:5") for modality_type in SYSTEM_MODALITIES: for validTone in ModalityFactory.create_modality(modality_type).get_valid_root_tones(): tonality = Tonality.create(modality_type, DiatonicFoundation.get_tone(validTone)) pitch_scale = PitchScale(tonality, ranges) print('Scale {0} {1} on {2}: {3}'.format(validTone, modality_type.name, ranges, ','.join(map(get_symbol, pitch_scale.pitch_scale)))) scale_check(pitch_scale, tonality)
def test_map_to_diatonic_scale(self): answers = DiatonicFoundation.map_to_diatonic_scale(46) assert DiatonicPitch.parse('Cbb:4') in answers assert DiatonicPitch.parse('A#:3') in answers assert DiatonicPitch.parse('Bb:3') in answers answers = DiatonicFoundation.map_to_diatonic_scale(47) assert DiatonicPitch.parse('Cb:4') in answers assert DiatonicPitch.parse('A##:3') in answers assert DiatonicPitch.parse('B:3') in answers answers = DiatonicFoundation.map_to_diatonic_scale(48) assert DiatonicPitch.parse('C:4') in answers assert DiatonicPitch.parse('B#:3') in answers assert DiatonicPitch.parse('Dbb:4') in answers answers = DiatonicFoundation.map_to_diatonic_scale(49) assert DiatonicPitch.parse('C#:4') in answers assert DiatonicPitch.parse('B##:3') in answers assert DiatonicPitch.parse('Db:4') in answers
def eval_as_accurate_chromatic_distance(self, v): floor_value = self.value_to_pitch.floor(v) if floor_value is None: raise ChromaticRangeInterpreterException( 'Illegal chromatic pitch range paramger value {0}.'.format(v)) low_pitch = self.value_to_pitch[floor_value] index = low_pitch.chromatic_distance if index >= ChromaticScale.chromatic_end_index() or math.isclose( v, floor_value): return low_pitch.chromatic_distance high_pitch = DiatonicFoundation.map_to_diatonic_scale(index + 1)[0] return low_pitch.chromatic_distance + \ ((v - floor_value) / self.pitch_unit) * \ (high_pitch.chromatic_distance - low_pitch.chromatic_distance)
def test_melodic_minor_modality(self): print('Testing Melodic Minor Modality: C-MelodicMinor, cue=Eb:3') domain_tonality = Tonality.create(ModalityType.MelodicMinor, DiatonicFoundation.get_tone('C')) cue_pitch = DiatonicPitch.parse('Eb:3') domain_pitch_range = PitchRange.create('D:4', 'F:5') f = ChromaticPitchReflectionFunction(domain_tonality, cue_pitch, domain_pitch_range, FlipType.CenterTone) TestChromaticPitchReflectionFunction.print_function(f) assert 2 == f['D:4'].octave assert 2 == f['G:4'].octave assert 1 == f['A:4'].octave assert 1 == f['C:5'].octave assert 1 == f['f:5'].octave
def test_natural_minor_modality(self): print('Testing Natural Minor Modality: C-Major, cue=Eb:3') domain_tonality = Tonality.create(ModalityType.NaturalMinor, DiatonicFoundation.get_tone('C')) cue_pitch = DiatonicPitch.parse('Eb:3') domain_pitch_range = PitchRange.create('D:2', 'F:4') f = ChromaticPitchReflectionFunction(domain_tonality, cue_pitch, domain_pitch_range, FlipType.CenterTone) TestChromaticPitchReflectionFunction.print_function(f) # Test for octave coverage assert 4 == f['D:2'].octave assert 3 == f['Bb:2'].octave assert 3 == f['C:3'].octave assert 2 == f['Bb:3'].octave assert 2 == f['F:4'].octave
def test_book_examples(self): modality = ModalityFactory.create_modality(ModalityType.Major) assert modality is not None modality = ModalityFactory.create_modality( ModalityType.MajorPentatonic, 1) assert modality is not None my_modality = 'my_modality' modality_type = ModalityType(my_modality) incremental_interval_strs = [ 'P:1', 'm:2', 'M:3', 'm:2', 'm:2', 'M:2', 'A:2' ] modality_spec = ModalitySpec(modality_type, incremental_interval_strs) ModalityFactory.register_modality(modality_type, modality_spec) modality = ModalityFactory.create_modality(ModalityType(my_modality)) assert modality is not None tones = modality.get_tonal_scale(DiatonicFoundation.get_tone('Eb')) print('[{0}]'.format(','.join( str(tone.diatonic_symbol) for tone in tones)))
def print_function(f): domain_tonality = f.domain_tonality tones = domain_tonality.annotation[:len(domain_tonality.annotation) - 1] for t in tones: print('{0} --> {1}'.format(t.diatonic_symbol, f[t].diatonic_symbol)) print('------------------------') # print for domain tone letters not in the domain tonality for letter in 'ABCDEFG': found = False for t in tones: if t.diatonic_letter == letter: found = True break if not found: ltr_tone = DiatonicFoundation.get_tone(letter) print('{0} --> {1}'.format(ltr_tone.diatonic_symbol, f[ltr_tone].diatonic_symbol))
def __init__(self, octave, diatonic_tone): """ Constructor Args: octave: integer >=0 diatonic_tone: tone or letter representation of the diatonic tone, e.g. D# Note: The tone is relative to the partition based on tonal_offset. So, Cb:4 is really B:3 - however we retain 4 as the partition as Cb is relative to the 4th. Same with B#4 which is really C:5, we retain the 4. So the partition is not the actual partition, but the relative partition number. """ self.__octave = octave if isinstance(diatonic_tone, DiatonicTone): self.__diatonic_tone = diatonic_tone else: self.__diatonic_tone = DiatonicFoundation.get_tone(diatonic_tone) self.__chromatic_distance = 12 * octave + self.diatonic_tone.tonal_offset
def test_secondary_chord(self): print('----- test_secondary_tonality -----') diatonic_tonality = Tonality.create(ModalityType.Major, DiatonicFoundation.get_tone("C")) chort_t_i = TertianChordTemplate.parse('tI') chord_i = chort_t_i.create_chord(diatonic_tonality) chord_v_ii = SecondaryChordTemplate.parse('V/ii').create_chord( diatonic_tonality) chord_vi_v = SecondaryChordTemplate.parse('vi/V').create_chord( diatonic_tonality) chord_t_ii = TertianChordTemplate.parse('tii') chord_ii = chord_t_ii.create_chord(diatonic_tonality) hc_track = HarmonicContextTrack() hc_track.append( HarmonicContext(diatonic_tonality, chord_i, Duration(1))) hc_track.append( HarmonicContext(diatonic_tonality, chord_v_ii, Duration(1))) hc_track.append( HarmonicContext(diatonic_tonality, chord_vi_v, Duration(1))) hc_track.append( HarmonicContext(diatonic_tonality, chord_ii, Duration(1))) TestTChromaticFlip.print_hct(hc_track) tune = [('C:5', (1, 1)), ('E:5', (1, 1)), ('E:5', (1, 1)), ('G:5', (1, 1))] line = TestTChromaticFlip.build_line(tune) cue = DiatonicPitch(5, 'd') tflip = TChromaticReflection(line, hc_track, cue) score_line, score_hct = tflip.apply() TestTChromaticFlip.print_notes(score_line) TestTChromaticFlip.print_hct(score_hct)
def __str__(self): return 'P-R({0}, {1})'.format( DiatonicFoundation.map_to_diatonic_scale(self.start_index)[0], DiatonicFoundation.map_to_diatonic_scale(self.end_index)[0])
def _find_closest_pitch(self, pitch): """ Given a pitch, find the scale pitch closest to it in chromatic distance. :param pitch: :return: 1) closest in half-steps pitch in tonality. 2) chromatic distance measured from closest pitch to given pitch. Note: algorithm looks for tonal pitches with same letter as pitch first, otherwise nearest non-tonal pitch. """ start_octave = max(pitch.octave - 1, ChromaticScale.CHROMATIC_START[0]) end_octave = min(pitch.octave + 1, ChromaticScale.CHROMATIC_END[0]) # Compute the first and last pitches within the start/end octave range. To build a PitchScale first_pitch = None for t in self.tonality.annotation: first_pitch = DiatonicPitch(start_octave, t) if DiatonicFoundation.get_chromatic_distance(first_pitch) >= ChromaticScale.chromatic_start_index(): break last_pitch = None loop_finished = False for o in range(end_octave, end_octave - 2, -1): if loop_finished: break for t in reversed(self.tonality.annotation): last_pitch = DiatonicPitch(o, t) if DiatonicFoundation.get_chromatic_distance(last_pitch) <= ChromaticScale.chromatic_end_index(): loop_finished = True break scale = PitchScale(self.tonality, PitchRange.create(first_pitch, last_pitch)) # determine if pitch ltr is in tonality, get that tone ll = [t for t in self.tones if t.diatonic_letter == pitch.diatonic_tone.diatonic_letter] if len(ll) == 1: pp = DiatonicPitch(pitch.octave, ll[0]) return pp, pitch.chromatic_distance - pp.chromatic_distance # Do something if len(ll) > 1 elif len(ll) > 1: ll.sort(key=lambda x: abs(x.augmentation_offset - pitch.diatonic_tone.augmentation_offset)) pp = DiatonicPitch(pitch.octave, ll[0]) return pp, pitch.chromatic_distance - pp.chromatic_distance before_pitch = first_pitch after_pitch = last_pitch for p in scale.pitch_scale: if pitch.chromatic_distance <= p.chromatic_distance: after_pitch = p break else: before_pitch = p before_distance = pitch.chromatic_distance - before_pitch.chromatic_distance after_distance = pitch.chromatic_distance - after_pitch.chromatic_distance if pitch.diatonic_tone.diatonic_letter == before_pitch.diatonic_tone.diatonic_letter: closest_distance = before_distance closest_pitch = before_pitch elif pitch.diatonic_tone.diatonic_letter == after_pitch.diatonic_tone.diatonic_letter: closest_distance = after_distance closest_pitch = after_pitch else: if abs(before_distance) < abs(after_distance): closest_distance = before_distance closest_pitch = before_pitch else: closest_distance = after_distance closest_pitch = after_pitch return closest_pitch, closest_distance
def test_get_tones(self): tones = DiatonicFoundation.get_tones() assert tones is not None assert len(tones) > 12 print(len(tones))
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
def _build_pitch_map1(self): pitch_map = dict() lo_reg = self.domain_pitch_range.start_index // 12 hi_reg = self.domain_pitch_range.end_index // 12 domain_start_tone = CrossTonalityShiftPitchFunction.compute_lowest_letter( self.domain_pitch_range.start_index % 12) # self.domain_tonality.annotation[0] range_start_tone = CrossTonalityShiftPitchFunction.compute_highest_letter( self.domain_pitch_range.end_index % 12) # self.range_tonality.annotation[0] # note letter template should start with letter for domain_start_tone note_letters = 'ABCDEFG' i = note_letters.index(domain_start_tone.diatonic_letter) note_letters = note_letters[i:] + note_letters[:i] domain_tones = list() for ltr in note_letters: for aug in ['bb', 'b', '', '#', '##']: domain_tones.append(DiatonicFoundation.get_tone(ltr + aug)) first_domain_pitch = None first_range_pitch = None last_domain_pitch = None last_range_pitch = None for register in range(lo_reg, hi_reg + 1): domain_reg = register target_pitch = self.root_shift_interval.get_end_pitch( DiatonicPitch(domain_reg, domain_start_tone)) range_reg = target_pitch.octave domain_reg_upped = False range_reg_upped = False for tone in domain_tones: if not domain_reg_upped and tone.diatonic_letter == 'C' and domain_start_tone.diatonic_letter != 'C': domain_reg = domain_reg + 1 domain_reg_upped = True domain_pitch = DiatonicPitch(domain_reg, tone) if domain_pitch.chromatic_distance < ChromaticScale.chromatic_start_index() or \ domain_pitch.chromatic_distance > ChromaticScale.chromatic_end_index(): continue if self.domain_pitch_range.is_pitch_inbounds(domain_pitch): range_tone = self.tonal_function[tone] if not range_reg_upped and range_tone.diatonic_letter == 'C' and \ range_start_tone.diatonic_letter != 'C': range_reg = range_reg + 1 range_reg_upped = True target_pitch = DiatonicPitch(range_reg, range_tone) if target_pitch.chromatic_distance < ChromaticScale.chromatic_start_index() or \ target_pitch.chromatic_distance > ChromaticScale.chromatic_end_index(): continue pitch_map[domain_pitch] = target_pitch if first_domain_pitch is None: first_domain_pitch = domain_pitch first_range_pitch = target_pitch last_domain_pitch = domain_pitch last_range_pitch = target_pitch if first_domain_pitch.chromatic_distance > last_domain_pitch.chromatic_distance: self.__domain_pitch_range = PitchRange.create( last_domain_pitch, first_domain_pitch) else: self.__domain_pitch_range = PitchRange.create( first_domain_pitch, last_domain_pitch) if first_range_pitch.chromatic_distance > last_range_pitch.chromatic_distance: self.__range_pitch_range = PitchRange.create( last_range_pitch, first_range_pitch) else: self.__range_pitch_range = PitchRange.create( first_range_pitch, last_range_pitch) return pitch_map
def _build_extension_map(self): extension = dict() # Map domain tone augmentations to similarly augmented range tonality tones. # e.g. if m[Db] == G, then m[D == Db#] == G# key_ltr_map = {d.diatonic_letter: d for d in self.domain_tonality.annotation} for ltr_item, tone_item in key_ltr_map.items(): target_tone = self.constr_primary_map[tone_item] for aug in ['bb', 'b', '', '#', "##"]: tone = DiatonicFoundation.get_tone(ltr_item + aug) if tone not in self.constr_primary_map.keys(): aug_difference = tone.augmentation_offset - tone_item.augmentation_offset new_target = DiatonicTone.alter_tone_by_augmentation(target_tone, aug_difference) extension[tone] = new_target # Map all the other cases, e.g. tones outside pentatonic tonal scale for example. tone_list = self.domain_tonality.annotation[:-1] for ltr_item in 'ABCDEFG': if ltr_item not in key_ltr_map.keys(): t = DiatonicFoundation.get_tone(ltr_item) lo_tone, hi_tone = CrossTonalityShiftTonalFunction._compute_neighbor_tones(t, tone_list) lo_target = self.constr_primary_map[lo_tone] if lo_tone in self.constr_primary_map.keys() else \ extension[lo_tone] hi_target = self.constr_primary_map[hi_tone] if hi_tone in self.constr_primary_map.keys() else \ extension[hi_tone] # d_diff is half-step diff between lo_tone and hi_tone for domain. d_diff = hi_tone.placement - lo_tone.placement if lo_tone.placement <= hi_tone.placement else \ hi_tone.placement - lo_tone.placement + 12 # r_diff is half-step diff between lo_tone and hi_tone for target. r_diff = hi_target.placement - lo_target.placement if lo_target.placement <= hi_target.placement else\ hi_target.placement - lo_target.placement + 12 range_factor = r_diff / d_diff # tone_domain_distance is chromatic distance from lo_tone to t (domain). tone_domain_distance = t.placement - lo_tone.placement if lo_tone.placement <= t.placement else \ t.placement - lo_tone.placement + 12 # range_chrom_dist is conversion of tone_domain_distance to range chromatic distance (interpolation). # i.e. distance from lo_target to ideal target (corresponding to t in domain). range_chrom_dist = int(round(tone_domain_distance * range_factor)) # Tonal interpolation: # 1) Need to use a diatonic dist from lo_target # 2) Adjusted so that from lo_target is range_chrom_dist diatonic_distance = DiatonicTone.calculate_diatonic_distance(lo_tone, t) # target_tone is end tone of interval based on diatonic_distance and range_chrom_dist target_tone = Interval.end_tone_from_pure_distance(lo_target, diatonic_distance, range_chrom_dist) extension[t] = target_tone tone_list.insert(tone_list.index(lo_tone) + 1, t) key_ltr_map[t] = target_tone # Now that t is mappec, map all the augmentations. for aug in ['bb', 'b', '#', "##"]: aug_tone = DiatonicFoundation.get_tone(ltr_item + aug) if aug_tone not in self.constr_primary_map.keys() and aug_tone not in extension.keys(): aug_difference = aug_tone.augmentation_offset - t.augmentation_offset new_target = DiatonicTone.alter_tone_by_augmentation(target_tone, aug_difference) extension[aug_tone] = new_target return extension
def enharmonics(self): return DiatonicFoundation.map_to_diatonic_scale( self.chromatic_distance)