def test_pentatonic_tonal_function(self): t_domain = Tonality.create(ModalityType.MajorPentatonic, DiatonicTone('C')) interval = Interval(3, IntervalType.Major) f = CrossTonalityShiftTonalFunction.create_shift(t_domain, interval) # C, D, E, G. A ==> E, F#, G#, B, C# assert 'E' == f['C'].diatonic_symbol assert 'F#' == f['D'].diatonic_symbol assert 'G#' == f['E'].diatonic_symbol assert 'B' == f['G'].diatonic_symbol assert 'C#' == f['A'].diatonic_symbol assert 'A' == f['F'].diatonic_symbol assert 'D#' == f['B'].diatonic_symbol assert 'A#' == f['F#'].diatonic_symbol assert 'D##' == f['B#'].diatonic_symbol assert 'Ab' == f['Fb'].diatonic_symbol assert 'D' == f['Bb'].diatonic_symbol t_range = Tonality.create(ModalityType.MajorPentatonic, interval.get_end_tone(DiatonicTone('C'))) f = CrossTonalityShiftTonalFunction(t_domain, t_range.annotation[2], 2) # C, D, E, G. A ==> G#, B, C#, E, F# assert 'G#' == f['C'].diatonic_symbol assert 'B' == f['D'].diatonic_symbol assert 'C#' == f['E'].diatonic_symbol assert 'E' == f['G'].diatonic_symbol assert 'F#' == f['A'].diatonic_symbol TestCrossTonalityShiftTonalFunction.print_function(f)
def test_book_examples(self): # Interval Creation interval = Interval(5, IntervalType.Perfect) print(interval) interval = Interval.parse("m:10") print(interval) interval = Interval.create_interval(DiatonicPitch.parse("a:3"), DiatonicPitch.parse("f#:4")) print(interval) # Interval Addition/Subtraction i1 = Interval(5, IntervalType.Perfect) i2 = Interval.parse("M:3") interval = i1 + i2 print(interval) interval = i1 - i2 print(interval) interval += i2 print(interval) interval -= i2 print(interval) # compute end and start interval = Interval(5, IntervalType.Perfect) pitch = interval.get_end_pitch(DiatonicPitch.parse("F#:5")) print(pitch) pitch = interval.get_start_pitch(DiatonicPitch.parse("C:5")) print(pitch)
def test_additional_octaves(self): t_domain = Tonality.create(ModalityType.MinorPentatonic, DiatonicTone('C')) i = Interval(12, IntervalType.Perfect) r = PitchRange.create('E:3', 'E:7') f = CrossTonalityShiftPitchFunction(t_domain, r, i) print('f={0}'.format(f)) assert 'P:12' == str(f.root_shift_interval) assert 'G:5' == str(f['C:4']) assert 'A:5' == str(f['D:4']) assert 'B:5' == str(f['E:4']) assert 'C:6' == str(f['F:4']) i = Interval(11, IntervalType.Perfect).negation() f = CrossTonalityShiftPitchFunction(t_domain, r, i) print('f={0}'.format(f)) assert '-P:11' == str(f.root_shift_interval) assert 'G:2' == str(f['C:4']) assert 'A:2' == str(f['D:4']) assert 'B:2' == str(f['E:4']) assert 'C:3' == str(f['F:4'])
def __create_chord_on_diatonic_tonality(self, diatonic_tone, diatonic_tonality): if not diatonic_tonality: raise Exception( "Cannot base secundal chord on tone {0} without tonality.". format(diatonic_tone.diatonic_symbol)) # The tonality must include this tone. tone_scale = diatonic_tonality.annotation found_index = -1 for i in range(0, len(tone_scale)): if diatonic_tone == tone_scale[i]: found_index = i break if found_index == -1: raise Exception( "For secundal chord based on tone {0}, tone must be in given tonality {1}" .format(diatonic_tone.diatonic_symbol, diatonic_tonality)) self.chord_basis = [] basis_tone = tone_scale[found_index] for i in range(0, 3): tone = tone_scale[(found_index + i) % (len(tone_scale) - 1)] pitch_a = DiatonicPitch(1, basis_tone.diatonic_symbol) b_octave = 2 if basis_tone.diatonic_index > tone.diatonic_index else 1 pitch_b = DiatonicPitch(b_octave, tone.diatonic_symbol) interval = Interval.create_interval(pitch_a, pitch_b) # If for any reason, the interval is not perfect or augmented (we know it is a 4th), just adjust tone upward # It is unknown if this can happen in a diatonic scale in practice. if interval.interval_type.value == IntervalType.Diminished: tone = DiatonicTone.alter_tone_by_augmentation(tone, 1) pitch_b = DiatonicPitch(b_octave, tone.diatonic_symbol) interval = Interval.create_interval(pitch_a, pitch_b) self.chord_basis.append(interval) self.__tones.append((tone, interval)) basis_tone = tone
def test_inversion_pattern(self): ctxt = 'CMaj+9@(9)' template = TertianChordTemplate.parse(ctxt) if template: print('succeeded') chord = template.create_chord() print(chord) else: print("failed") # Another way to do the same from tonalmodel.interval import Interval, IntervalType from tonalmodel.diatonic_tone_cache import DiatonicToneCache template = TertianChordTemplate(DiatonicToneCache.get_cache().get_tone('C'), None, TertianChordType.to_type('Maj'), [Interval(9, IntervalType.Major)], None, Interval(9, IntervalType.Major)) chord = template.create_chord() print(chord) # reuse template = TertianChordTemplate.parse('IVMin') diatonic_tonality = Tonality.create(ModalityType.Major, DiatonicToneCache.get_cache().get_tone("Db")) chord = template.create_chord(diatonic_tonality) print(chord) diatonic_tonality = Tonality.create(ModalityType.Major, DiatonicToneCache.get_cache().get_tone("F")) chord = template.create_chord(diatonic_tonality) print(chord) diatonic_tonality = Tonality.create(ModalityType.HarmonicMinor, DiatonicToneCache.get_cache().get_tone("C")) chord = template.create_chord(diatonic_tonality) print(chord)
def test_across_tonalities(self): logging.debug('Start test_across_tonalities.') lower_policy_context_1 = TestRelativeDiatonicConstraint.policy_creator( ModalityType.Major, DiatonicTone('G'), 'tV', 'C:2', 'C:8') lower_policy_context_2 = TestRelativeDiatonicConstraint.policy_creator( ModalityType.Major, DiatonicTone('Ab'), 'tI', 'C:2', 'C:8') upper_note_1 = Note(DiatonicPitch.parse('C:5'), Duration(1, 8)) upper_note_2 = Note(DiatonicPitch.parse('D:5'), Duration(1, 8)) lower_note_1 = ContextualNote( lower_policy_context_1, Note(DiatonicPitch.parse('F#:5'), Duration(1, 8))) lower_note_2 = ContextualNote(lower_policy_context_2) p_map = dict([(upper_note_1, lower_note_1), (upper_note_2, lower_note_2)]) policy = RelativeDiatonicConstraint(upper_note_1, upper_note_2, Interval(3, IntervalType.Minor), Interval(3, IntervalType.Major)) v_result = policy.values(p_map, upper_note_2) for note in v_result: logging.debug(note) pitches = [note.diatonic_pitch for note in v_result] assert {str(p) for p in pitches} == {'Eb:5', 'F:5', 'G:5', 'Ab:5'} logging.debug('End test_across_tonalities.')
def shift_change_modal_index_modality_and_shift(): print('----- Shift Change Modal Index, modality, shift Example -----') source_expression = '{<C-Major: I> iC:4 C qD E <:IV> iF G hA <:V> ig b qf g <:VI> ie e qd ic d <:i> h@c}' t_shift = TShift.create(source_expression) print('Shift examples based on:') print_line(t_shift.source_line) print() print('Shift to modal index 1 (dorian)') target_line, target_hct = t_shift.apply(root_shift_interval=TonalInterval.parse('M:2'), modal_index=1) print_line(target_line) print_hct(target_hct) print() t_shift = TShift(target_line, target_hct) print('Shift P:4 to modal index 2 (phrygian) of MelodicMinor') target_line, target_hct = t_shift.apply(root_shift_interval=TonalInterval.parse('P:4'), range_modality_type=ModalityType.MelodicMinor, modal_index=2) print_line(target_line) print_hct(target_hct) print()
def test_lower_pitch(self): pitch = DiatonicPitch(4, 'C') for i in range(1, 13): for interval_type in TestInterval.INTERVAL_TYPES: if i == 1 and interval_type == IntervalType(IntervalType.Diminished): continue if i == 1 or i == 4 or i == 5 or i == 8 or i == 11 or i == 12: if interval_type == IntervalType(IntervalType.Minor) or \ interval_type == IntervalType(IntervalType.Major): continue else: if interval_type == IntervalType(IntervalType.Perfect): continue try: interval = Interval(i, interval_type) p = interval.get_start_pitch(pitch) except Exception: e = sys.exc_info()[0] print('exception {0} for interval i={1} interval={2} pitch={3}'.format(e, i, interval_type, pitch)) raise Exception('exception {0} for creating interval i={1} type={2} pitch={3}'.format(e, i, interval_type, pitch)) print(p) assert p.diatonic_distance() == 4 * 7 + 0 - (i - 1)
def test_for_book_example_2(self): print('----- test for book example 2 -----') source_instance_expression = '{<A-Major:i> [sA:4 A A A] qA:4 [iA:4 A] <:iv> qA:4 [sA:4 A A A] qA:4}' target_instance_expression = '{<G-Major:i> wA:4 <:iv> wA:4}' lge = LineGrammarExecutor() source_instance_line, source_instance_hct = lge.parse( source_instance_expression) actors = source_instance_line.get_all_notes() for a in actors: print("{0}".format(a)) target_instance_line, target_instance_hct = lge.parse( target_instance_expression) target_hcs = target_instance_hct.hc_list() for hc in target_hcs: print("{0}".format(hc)) pitch_range = PitchRange( DiatonicPitch.parse('C:4').chromatic_distance, DiatonicPitch.parse('C:6').chromatic_distance) p_map = PMap.create(source_instance_expression, pitch_range, [('G-Major:I', Duration(3, 4)), ('G-Major:IV', Duration(3, 4))]) actors = p_map.actors policies = OrderedSet() policies.add( StepSequenceConstraint( [actors[0], actors[1], actors[2], actors[3]], [1, 1, 1])) policies.add(ChordalPitchConstraint(actors[0])) policies.add(ChordalPitchConstraint(actors[4])) policies.add(ChordalPitchConstraint(actors[8])) policies.add( StepSequenceConstraint( [actors[8], actors[9], actors[10], actors[11]], [1, -1, -1])) policies.add(EqualPitchConstraint([actors[0], actors[12]])) policies.add(EqualPitchConstraint([actors[4], actors[7]])) policies.add( RelativeDiatonicConstraint(actors[4], actors[5], Interval(3, IntervalType.Major), Interval(1, IntervalType.Perfect))) policies.add(StepSequenceConstraint([actors[5], actors[6]], [-1])) # policies.add(ChordalPitchConstraint(actors[7])) solver = PitchConstraintSolver(policies) full_results, partial_results = solver.solve(p_map) print('Results has {0} results.'.format(len(full_results))) for pm in full_results: if str(pm[actors[7]].note.diatonic_pitch) == 'D:4' and str( pm[actors[0]].note.diatonic_pitch) == 'G:4': print("{0}".format(pm))
def test_inversion(self): interval_strs = ['d:1', 'P:1', 'A:1', 'd:2', 'm:2', 'M:2', 'A:2', 'd:3', 'm:3', 'M:3', 'A:3', 'd:4', 'P:4', 'A:4', 'd:5', 'P:5', 'A:5', 'd:6', 'm:6', 'M:6', 'A:6', 'd:7', 'm:7', 'M:7', 'A:7', 'd:8', 'P:8', 'A:8'] answers = ['A:8', 'P:8', 'd:8', 'A:7', 'M:7', 'm:7', 'd:7', 'A:6', 'M:6', 'm:6', 'd:6', 'A:5', 'P:5', 'd:5', 'A:4', 'P:4', 'd:4', 'A:3', 'M:3', 'm:3', 'd:3', 'A:2', 'M:2', 'm:2', 'd:2', 'A:1', 'P:1', 'd:1'] intervals = [Interval.parse(s) for s in interval_strs] print('+++++') for interval, answer in zip(intervals, answers): print('{0} --> {1}'.format(interval, interval.inversion())) assert str(interval.inversion()) == answer print('-----') interval = Interval.parse('A:12') inversion = interval.inversion() print('int={0} inv={1}'.format(interval, inversion)) # Test augmented and negative intervals interval_strs = ['A:15', 'P:15', 'd:15', 'A:14', 'M:14', 'm:14', 'd:14', 'A:13', 'M:13', 'm:13', 'd:13', 'A:12', 'P:12', 'd:12', 'A:11', 'P:11', 'd:11', 'A:10', 'M:10', 'm:10', 'd:10', 'A:9', 'M:9', 'm:9', 'd:9', ] answers = ['d:1', 'P:1', 'A:1', 'd:2', 'm:2', 'M:2', 'A:2', 'd:3', 'm:3', 'M:3', 'A:3', 'd:4', 'P:4', 'A:4', 'd:5', 'P:5', 'A:5', 'd:6', 'm:6', 'M:6', 'A:6', 'd:7', 'm:7', 'M:7', 'A:7'] intervals = [Interval.parse(s) for s in interval_strs] print('+++++') for interval, answer in zip(intervals, answers): print('{0} --> {1} {2}'.format(interval, interval.inversion(), answer)) assert str(interval.inversion()) == answer print('-----') interval_strs = ['-d:1', '-P:1', '-A:1', '-d:2', '-m:2', '-M:2', '-A:2', '-d:3', '-m:3', '-M:3', '-A:3', '-d:4', '-P:4', '-A:4', '-d:5', '-P:5', '-A:5', '-d:6', '-m:6', '-M:6', '-A:6', '-d:7', '-m:7', '-M:7', '-A:7', '-d:8', '-P:8', '-A:8'] answers = ['d:8', 'P:8', 'A:8', '-A:7', '-M:7', '-m:7', '-d:7', '-A:6', '-M:6', '-m:6', '-d:6', '-A:5', '-P:5', '-d:5', '-A:4', '-P:4', '-d:4', '-A:3', '-M:3', '-m:3', '-d:3', '-A:2', '-M:2', '-m:2', '-d:2', 'd:1', 'P:1', 'A:1'] intervals = [Interval.parse(s) for s in interval_strs] print('+++++') for interval, answer in zip(intervals, answers): print('{0} --> {1}'.format(interval, interval.inversion())) assert str(interval.inversion()) == answer print('-----') interval_strs = ['-A:15', '-P:15', '-d:15', '-A:14', '-M:14', '-m:14', '-d:14', '-A:13', '-M:13', '-m:13', '-d:13', '-A:12', '-P:12', '-d:12', '-A:11', '-P:11', '-d:11', '-A:10', '-M:10', '-m:10', '-d:10', '-A:9', '-M:9', '-m:9', '-d:9', ] answers = ['A:1', 'P:1', 'd:1', 'd:2', 'm:2', 'M:2', 'A:2', 'd:3', 'm:3', 'M:3', 'A:3', 'd:4', 'P:4', 'A:4', 'd:5', 'P:5', 'A:5', 'd:6', 'm:6', 'M:6', 'A:6', 'd:7', 'm:7', 'M:7', 'A:7'] intervals = [Interval.parse(s) for s in interval_strs] print('+++++') for interval, answer in zip(intervals, answers): print('{0} --> {1} {2}'.format(interval, interval.inversion(), answer)) print('-----')
def test_plus_equals(self): a = Interval(3, IntervalType(IntervalType.Major)) a += Interval(3, IntervalType(IntervalType.Minor)) assert a == (Interval(5, IntervalType(IntervalType.Perfect))) a = Interval(2, IntervalType(IntervalType.Diminished)) with self.assertRaises(Exception): a += Interval(3, IntervalType(IntervalType.Minor))
def test_interval_exception(self): i1 = Interval.parse('d:4') i2 = Interval.parse('d:3') try: i = i1 + i2 except IntervalException as e: print('caught exception ' + str(e)) assert e else: assert i is not None
def parse(chord_string): """ Parse an input string into a TertialChordTemplate. Args: chord_string: string input representing chord Returns: TertianChordTemplate """ if not chord_string: raise TertianChordException('Unable to parse chord string to completion: {0}'.format(chord_string)) m = TertianChordTemplate.TERTIAN_PATTERN.match(chord_string) if not m: raise TertianChordException('Unable to parse chord string to completion: {0}'.format(chord_string)) scale_degree = m.group(TertianChordTemplate.GROUP_SCALE_DEGREE) if scale_degree: scale_degree = ChordTemplate.SCALE_DEGREE_MAP[scale_degree] if m.group(TertianChordTemplate.GROUP_DIATONIC_TONE) is not None: diatonic_basis = DiatonicTone(m.group(TertianChordTemplate.GROUP_DIATONIC_TONE)) else: diatonic_basis = None chord_name = m.group(TertianChordTemplate.GROUP_CHORD) chord_type = None if chord_name: chord_type = TertianChordType.to_type(chord_name) inversion_text = m.group(TertianChordTemplate.GROUP_INVERSION) inversion_tension = m.group(TertianChordTemplate.INVERSION_TENSION) inversion_interval = None inversion = None if inversion_tension: tensions_parse = TertianChordTemplate.INVERSE_TENSION_PATTERN.findall(inversion_tension) for tension in tensions_parse: # should only be 1 aug = DiatonicTone.AUGMENTATION_OFFSET_MAPPING[tension[0]] interval_type = Interval.available_types(int(tension[1]))[aug] inversion_interval = Interval(int(tension[1]), interval_type) logging.info('inversion_interval = {0}'.format(str(inversion_interval))) elif inversion_text: inversion = int(inversion_text) else: inversion = 1 tensions = [] if m.group(TertianChordTemplate.GROUP_TENSIONS): tensions_parse = TertianChordTemplate.TENSION_PATTERN.findall(m.group(TertianChordTemplate.GROUP_TENSIONS)) for tension in tensions_parse: aug = DiatonicTone.AUGMENTATION_OFFSET_MAPPING[tension[2]] if aug not in Interval.available_types(int(tension[3])): raise TertianChordException('Invalid interval specification for tension \'{0}\''.format(tension[0])) interval_type = Interval.available_types(int(tension[3]))[aug] interval = Interval(int(tension[3]), interval_type) tensions.append(interval) return TertianChordTemplate(diatonic_basis, scale_degree, chord_type, tensions, inversion, inversion_interval)
def test_various(self): dta = DiatonicPitch(2, 'B#') dtb = DiatonicPitch(3, 'C') interval = Interval.create_interval(dta, dtb) semitones = interval.semitones() print('Interval "{0}" based on {1} and {2} has {3} semitones'.format(interval, dta, dtb, semitones)) # get a major 3rd pitch interval = Interval.create_interval(DiatonicPitch(2, 'E'), DiatonicPitch(2, 'G#')) end_pitch = interval.get_end_pitch(DiatonicPitch(4, 'E')) print(end_pitch) assert str(end_pitch) == 'G#:4'
def find_tertian_chords(self): results = list() for chord_type, interval_list in TertianChordTemplate.TERTIAN_CHORD_TYPE_MAP.items( ): chord_tones = list() for interval in interval_list: chord_tones.append(interval.get_end_tone(self.root_tone)) if set(chord_tones) <= set(self.chord_tones): if self.chord_tones[0] not in chord_tones: continue ct = TertianChordType(chord_type) results.append((ct, chord_tones)) results.sort(key=lambda x: len(x[1]), reverse=True) rr = [x for x in results if len(x[1]) == len(results[0][1])] chords = list() if len(rr): for answer in rr: # inversion computed - must be a chordal tone # if self.chord_tones[0] not in answer[1]: # raise Exception('Inversion tone \'{0}\' must be a chord tone in [{1}]'.format( # self.chord_tones[0], ', '.join(v.diatonic_symbol for v in answer[1]))) inversion = answer[1].index(self.chord_tones[0]) + 1 tensions = list() remainder = set(self.chord_tones) - set(answer[1]) for r in remainder: p1 = DiatonicPitch(4, self.root_tone) p2 = DiatonicPitch( 5 if DiatonicPitch.crosses_c(self.root_tone, r) else 4, r) interval = Interval.create_interval(p1, p2) if interval.diatonic_distance < 5: # We don't want M:13 nor M:14 interval = Interval(interval.diatonic_distance + 8, interval.interval_type) tensions.append(interval) if self.tonality is not None: index = self.tonality.annotation.index(self.root_tone) \ if self.root_tone in self.tonality.annotation else None if index is None: continue # raise Exception('Root tone {0} is not in tonality {1}'.format(self.root_tone, self.tonality)) template = TertianChordTemplate(None, index + 1, answer[0], tensions, inversion) else: template = TertianChordTemplate(self.root_tone, None, answer[0], tensions, inversion) chords.append(TertianChord(template, self.tonality)) return chords
def __create_chord_on_root_no_base_intervals(self, diatonic_tone): # Assume MM or MajMaj self.chord_basis = [] current_tone = diatonic_tone intervals = [ Interval(1, IntervalType.Perfect), Interval(4, IntervalType.Perfect), Interval(4, IntervalType.Perfect) ] for i in range(0, 3): tone = intervals[i].get_end_tone(current_tone) self.__tones.append((tone, intervals[i])) self.chord_basis.append(intervals[i]) current_tone = tone
def create_adapted_function(self, domain_tonality, range_tonality): """ Generate a tonal function from this template using: :param domain_tonality: Replacement domain tonality. :param range_tonality: Replacement range tnality. :return: """ # Tonalities must match original domain and range in cardinality. if domain_tonality.cardinality != self.domain_cardinality: raise Exception( 'Cardinalities of domains don\'t match: given {0} versus {1}.'. format(domain_tonality.cardinality, self.domain_cardinality)) if range_tonality.cardinality != self.range_cardinality: raise Exception( 'Cardinalities of ranges don\'t match: given {0} versus {1}.'. format(range_tonality.cardinality, self.range_cardinality)) domain_tones = domain_tonality.annotation[:len(domain_tonality. annotation) - 1] range_tones = range_tonality.annotation[:len(range_tonality.annotation ) - 1] primary_map = dict() for t, i in zip(domain_tones, self.tonal_order): primary_map[t] = None if i is None else range_tones[i] # Build the extension map extension_map = dict() for k, v in self.extension_interval_map.items(): key_tone = Interval.end_tone_from_pure_distance( domain_tones[0], k[0], k[1]) if key_tone not in domain_tones: value_tone = Interval.end_tone_from_pure_distance( range_tones[0], v[0], v[1]) extension_map[key_tone] = value_tone # Examine the primary intervals, as some may not be primary due to modality change. for k, v in self.tonal_interval_map.items(): key_tone = Interval.end_tone_from_pure_distance( domain_tones[0], k[0], k[1]) if key_tone not in domain_tones: value_tone = Interval.end_tone_from_pure_distance( range_tones[0], v[0], v[1]) extension_map[key_tone] = value_tone return TonalFunction(domain_tonality, range_tonality, primary_map, extension_map)
def test_parse(self): interval = Interval.parse('P:5') assert str(interval) == 'P:5' assert str(Interval.parse('A:8')) == 'A:8' assert str(Interval.parse('d:8')) == 'd:8' assert str(Interval.parse('M:3')) == 'M:3' assert str(Interval.parse('m:6')) == 'm:6' assert Interval.parse('-d:1') == Interval.parse('A:1') assert Interval.parse('-A:1') == Interval.parse('d:1')
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 create_instrument(inst_node, parent): low = high = '' up_down = None transpose_interval = None articulations = [] for c in inst_node: if c.tag == 'Range': for lh in c: if lh.tag == 'Low': low = lh.text elif lh.tag == 'High': high = lh.text elif c.tag == 'Transpose': updown_txt = c.get('direction') if updown_txt != 'up' and updown_txt != 'down': raise Exception( 'Illegal transpose up/down must be \'up\' or \'down\' now \'{0}\'' .format(updown_txt)) up_down = updown_txt == 'up' transpose_interval = Interval.parse(c.get('interval')) elif c.tag == 'Articulations': articulations = InstrumentCatalog._parse_articulations(c) instrument = Instrument(inst_node.get('name'), inst_node.get('key'), low, high, up_down, transpose_interval, parent) instrument.extend_articulations(articulations) return instrument
def __create_chord_on_diatonic_without_type(self, diatonic_tone): from tonalmodel.tonality import Tonality from tonalmodel.modality import ModalityType from harmonicmodel.tertian_chord_template import TertianChordTemplate diatonic_tonality = Tonality.create(ModalityType.Major, diatonic_tone) tone_scale = diatonic_tonality.annotation self.chord_basis = [] base_tone = None for i in range(0, 3): tone = tone_scale[(2 * i) % (len(tone_scale) - 1)] if i == 0: base_tone = tone pitch_a = DiatonicPitch(1, diatonic_tone) b_octave = 2 if base_tone.diatonic_index > tone.diatonic_index else 1 pitch_b = DiatonicPitch(b_octave, tone.diatonic_symbol) interval = Interval.create_interval(pitch_a, pitch_b) self.chord_basis.append(interval) self.__tones.append((tone, interval)) self.__set_inversion() self.__chord_type = TertianChordTemplate.get_chord_type( self.chord_basis)
def cue_examples(): print('-------------- Cue examples --------------------------') source_expression = '{<Bb-Major: I> sBb:4 A G F qEb D sF g iA i@Bb sF <:IVMaj7> ' \ 'ir Eb sEb F G A iBb sEb:5 F i@Eb C ' \ '<:IIIMin7> sR F:5 Eb D C Bb:4 C:5 D i@Eb sC sr G:4 A G <:I> sG:5 F Eb D D C Bb:4 A ir q@G}' t_flip = TDiatonicReflection.create(source_expression, DiatonicPitch.parse('Eb:4')) print('Flip examples based on:') print_line(t_flip.source_line) print_hct(t_flip.source_hct) print() print('Flip on Eb:4 (Figure 16.12)') target_line, target_hct = t_flip.apply() print_line(target_line) print_hct(target_hct) print() print('Shift up an octave (Figure 16.13)') t_shift = TShift(target_line, target_hct, TonalInterval.parse('P:8')) final_line, final_hct = t_shift.apply() print_line(final_line) print_hct(final_hct)
def calculate_interval(tone_1, tone_2, near_interval): """ The purpose of this method is to find a transform interval close to 'near_interval'. This is used to determine the transform interval for secondary chords, wherein the obvious jump does not match transform interval (e.g. major to minor scale), and as well must be adjusted for the number and sign of octaves nearest interval may have. See test cases: test_modal_secondary_tonality where best_interval == d:4 and near_interval == P:4 E-MM to Ab-MM using V/III chord. :param tone_1: :param tone_2: :param near_interval: :return: """ sign = TShift._sign(near_interval.chromatic_distance) start, increment = (1, 1) if sign == 1 else (7, -1) p1 = DiatonicPitch(start, tone_1) oct_2 = start best = 100000 best_interval = None while abs(oct_2) < 7: p2 = DiatonicPitch(oct_2, tone_2) i = TonalInterval.create_interval(p1, p2) if TShift._sign(i.chromatic_distance) != sign: oct_2 = oct_2 + sign continue diff = abs(near_interval.chromatic_distance - i.chromatic_distance) if diff < best: best = diff best_interval = i else: break oct_2 = oct_2 + sign return best_interval
def test_hct_simple_shift(self): print('----- test_hct_simple_shift -----') line_str = '{<C-Major: I> C:4 E F D <:IV> F A <:V> G D <:VI> a c b a}' lge = LineGrammarExecutor() target_line, target_hct = lge.parse(line_str) root_shift_interval = TonalInterval.create_interval('C:4', 'G:4') tshift = TShift(target_line, target_hct, root_shift_interval) temporal_extent = Interval(Fraction(1, 1), Fraction(2, 1)) tshift.apply(temporal_extent, as_copy=False) TestTShift.print_notes(target_line) TestTShift.print_hct(target_hct) notes = target_line.get_all_notes() assert 12 == len(notes) assert 'C:5' == str(notes[4].diatonic_pitch) assert 'E:5' == str(notes[5].diatonic_pitch) assert 'D:5' == str(notes[6].diatonic_pitch) assert 'A:4' == str(notes[7].diatonic_pitch) hc_list = target_hct.hc_list() assert len(hc_list) == 4 assert hc_list[1].chord.chord_template.scale_degree == 4 assert {t[0].diatonic_symbol for t in hc_list[1].chord.tones} == {'C', 'E', 'G'} assert hc_list[1].chord.chord_template.inversion == 1 assert hc_list[1].tonality.modal_index == 0 assert hc_list[1].tonality.basis_tone.diatonic_symbol == 'G' assert hc_list[1].tonality.root_tone.diatonic_symbol == 'G' assert hc_list[1].tonality.modality_type == ModalityType.Major assert hc_list[1].chord.chord_type.value == TertianChordType.Maj
def test_diatonic_modal_indexed_function(self): t_domain = Tonality.create(ModalityType.Major, DiatonicTone('C')) i = Interval(2, IntervalType.Major) r = PitchRange.create('E:3', 'E:7') f = CrossTonalityShiftPitchFunction(t_domain, r, i, modal_index=4) print('f={0}'.format(f)) TestCrossTonalityShiftPitchFunction.print_map('test_diatonic_function', f) t = f.tonal_function print(t) # test diatonic maps assert 'D:4' == str(f['C:4']) assert 'E:4' == str(f['D:4']) assert 'F#:4' == str(f['E:4']) assert 'G:4' == str(f['F:4']) assert 'A:4' == str(f['G:4']) assert 'B:4' == str(f['A:4']) assert 'C:5' == str(f['B:4']) assert 'D:5' == str(f['C:5']) assert 'M:2' == str(f.root_shift_interval)
def test_all_g_same_octave_itervals(self): pitches = list('GABCDEF') octaves = [5, 5, 5, 6, 6, 6, 6] augs = ('bb', 'b', '', '#', '##') example_count = 0 exception_items = (1, 2, 5, 10, 15, 16, 20, 21, 25, 30, 31, 35) for i in range(0, 7): pitch = pitches[i] octave = octaves[i] for aug in augs: example_count += 1 a = DiatonicPitch(5, 'G') b = DiatonicPitch(octave, pitch + aug) try: interval = Interval.create_interval(a, b) print('({0}): {1}, {2}) --> {3}'.format(example_count, a, b, interval)) dist = b.diatonic_tone.diatonic_index - a.diatonic_tone.diatonic_index if dist < 0: dist += 7 assert dist == interval.diatonic_distance assert b.chromatic_distance - a.chromatic_distance == interval.chromatic_distance except Exception as e: print('Exception ({0}): ({1}, {2}) : {3}'.format(example_count, a, b, e)) assert example_count in exception_items, \ 'ASSERT ERROR ({0}): ({1}, {2}) : {3}'.format(example_count, a, b, e)
def __create_chord_on_diatonic_tonality(self, diatonic_tone, diatonic_tonality): if not diatonic_tonality: raise Exception( "Cannot base quartal chord on tone {0} without tonality.". format(diatonic_tone.diatonic_symbol)) # The tonality must include this tone. tone_scale = diatonic_tonality.annotation found_index = -1 for i in range(0, len(tone_scale)): if diatonic_tone == tone_scale[i]: found_index = i break if found_index == -1: raise Exception( "For quartal chord based on tone {0}, tone must be in given tonality {1}" .format(diatonic_tone.diatonic_symbol, diatonic_tonality)) self.chord_basis = [] basis_tone = tone_scale[found_index] for i in range(0, 3): tone = tone_scale[(found_index + 3 * i) % (len(tone_scale) - 1)] pitch_a = DiatonicPitch(1, basis_tone.diatonic_symbol) b_octave = 2 if basis_tone.diatonic_index > tone.diatonic_index else 1 pitch_b = DiatonicPitch(b_octave, tone.diatonic_symbol) interval = Interval.create_interval(pitch_a, pitch_b) self.chord_basis.append(interval) self.__tones.append((tone, interval)) basis_tone = tone
def example2(): print('----- Debug meaning of modal index change and hct -----') # example tonality with modal index # Create a harmonic minor tonality of some basis root which as Mixolydian has F as the root. # The answer is Bb-HarmonicMinor F(4) source_expression = '{<C-Major: I> iC:4}' t_shift = TShift.create(source_expression) print('Shift examples based on:') print_line(t_shift.source_line) print() print('Shift to modal index 4 (Mixolydian)') # This makes C the Mixolydian of F-Major target_line, target_hct = t_shift.apply(modal_index=4) print_line(target_line) print_hct(target_hct) print() # if you wanted G Mixolydian based on C print('Shift as if moving to modal index 4 (Mixolydian) in C') target_line, target_hct = t_shift.apply( root_shift_interval=TonalInterval.parse('P:5'), modal_index=4) print_line(target_line) print_hct(target_hct) print()
def _build_tunnel_constraints(self, source_to_target, tag_map): if tag_map is None or len(tag_map) == 0: return [] one_id = next(iter(tag_map.keys())) source_note = self.source_line.get_all_notes()[one_id] target_pitch = tag_map[one_id] mvmt_interval = Interval.create_interval(source_note.diatonic_pitch, target_pitch) constraints = list() note_annotations = self.source_analysis.note_annotation for annotation in note_annotations: if annotation.note.diatonic_pitch is None: continue target_note = source_to_target[annotation.note] dest_ctr_pitch = mvmt_interval.get_end_pitch( annotation.note.diatonic_pitch) low_pitch = self.tunnel_half_interval.get_start_pitch( dest_ctr_pitch) high_pitch = self.tunnel_half_interval.get_end_pitch( dest_ctr_pitch) p_range = PitchRange.create(low_pitch, high_pitch) constraint = PitchRangeConstraint([target_note], p_range) constraints.append(constraint) return constraints
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