Пример #1
0
    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_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.')
Пример #3
0
    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'])
Пример #4
0
    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)
Пример #5
0
    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))
Пример #6
0
 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))
Пример #7
0
    def test_create_interval(self):
        pitch_a = DiatonicPitch(4, 'C')
        pitch_b = DiatonicPitch(5, 'C')
        interval = Interval.create_interval(pitch_a, pitch_b)
        print(interval)
        assert interval.interval_type == IntervalType(IntervalType.Perfect)
        assert interval.diatonic_distance == 7
        
        pitch_a = DiatonicPitch(4, 'C')
        pitch_b = DiatonicPitch(5, 'Cb')
        interval = Interval.create_interval(pitch_a, pitch_b)
        print(interval)
        assert interval.interval_type == IntervalType(IntervalType.Diminished)
        assert interval.diatonic_distance == 7

        pitch_a = DiatonicPitch(4, 'C')
        pitch_b = DiatonicPitch(5, 'Dbb')        
        interval = Interval.create_interval(pitch_a, pitch_b)
        print(interval)
        assert interval.interval_type == IntervalType(IntervalType.Diminished)
        assert interval.diatonic_distance == 8
        
        pitch_a = DiatonicPitch(4, 'C')
        pitch_b = DiatonicPitch(5, 'Cb')        
        interval = Interval.create_interval(pitch_a, pitch_b)
        print(interval)
        assert interval.interval_type == IntervalType(IntervalType.Diminished)
        assert interval.diatonic_distance == 7
        
        pitch_a = DiatonicPitch(4, 'C')
        pitch_b = DiatonicPitch(4, 'B#')        
        interval = Interval.create_interval(pitch_a, pitch_b)
        print(interval)
        assert interval.interval_type == IntervalType(IntervalType.Augmented)
        assert interval.diatonic_distance == 6
        assert not interval.is_negative()

        pitch_a = DiatonicPitch(5, 'C')
        pitch_b = DiatonicPitch(4, 'Bb')
        interval = Interval.create_interval(pitch_a, pitch_b)
        print(interval)
        assert interval.interval_type == IntervalType(IntervalType.Major)
        assert interval.diatonic_distance == -1
        assert interval.is_negative()
        
        interval = Interval(9, IntervalType(IntervalType.Diminished))
        print(interval)
        assert interval.interval_type == IntervalType(IntervalType.Diminished)
        assert interval.diatonic_distance == 8
        
        interval = Interval(3, IntervalType(IntervalType.Minor))
        pitch = interval.get_start_pitch(DiatonicPitch(4, 'E'))
        print(pitch)
        assert str(pitch) == 'C#:4'
Пример #8
0
    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)
Пример #9
0
 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
Пример #10
0
    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)
Пример #11
0
    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)
Пример #12
0
    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)
Пример #13
0
    def test_pentatonic_tonal_function(self):
        t_domain = Tonality.create(ModalityType.MinorPentatonic,
                                   DiatonicTone('C'))
        i = Interval(5, IntervalType.Perfect)
        r = PitchRange.create('E:3', 'E:7')

        f = CrossTonalityShiftPitchFunction(t_domain, r, i)

        print('f={0}'.format(f))

        TestCrossTonalityShiftPitchFunction.print_map(
            'test_pentatonic_tonal_function', f)

        t = f.tonal_function
        print(t)

        assert 'G' == t['C'].diatonic_symbol
        assert 'Bb' == t['Eb'].diatonic_symbol
        assert 'C' == t['F'].diatonic_symbol
        assert 'D' == t['G'].diatonic_symbol
        assert 'F' == t['Bb'].diatonic_symbol

        assert 'G:5' == str(f['C:5'])
        assert 'F:5' == str(f['Bb:4'])
        assert 'D:5' == str(f['G:4'])
        assert 'C:5' == str(f['F:4'])
        assert 'Bb:4' == str(f['Eb:4'])
        assert 'G:4' == str(f['C:4'])

        # test range
        d = f.domain_pitch_range
        print(d)
        assert d.start_index == DiatonicPitch.parse('E:3').chromatic_distance
        assert d.end_index == DiatonicPitch.parse('E:7').chromatic_distance
        r = f.range_pitch_range
        print(r)
        assert r.start_index == DiatonicPitch.parse('B:3').chromatic_distance
        assert r.end_index == DiatonicPitch.parse('B:7').chromatic_distance

        # test chromatics
        assert 'G#:5' == str(f['C#:5'])
        assert 'F#:5' == str(f['B:4'])
        assert 'E#:5' == str(f['A#:4'])
        assert 'E:5' == str(f['A:4'])
        assert 'D#:5' == str(f['G#:4'])
        assert 'C#:5' == str(f['F#:4'])
        assert 'B:4' == str(f['E:4'])
        assert 'A:4' == str(f['D:4'])
        assert 'A#:4' == str(f['D#:4'])
        assert 'G#:4' == str(f['C#:4'])

        assert 'Gb:5' == str(f['Cb:5'])
        assert 'Fb:5' == str(f['Bbb:4'])
        assert 'Eb:5' == str(f['Ab:4'])
        assert 'Db:5' == str(f['Gb:4'])
        assert 'Cb:5' == str(f['Fb:4'])
        assert 'Bbb:4' == str(f['Ebb:4'])
        assert 'Ab:4' == str(f['Db:4'])
        assert 'Gb:4' == str(f['Cb:4'])
    def test_simple_diatonic_test(self):
        logging.debug('Start test_simple_diatonic_test')
        lower_policy_context = TestRelativeDiatonicConstraint.policy_creator(
            ModalityType.Major, DiatonicTone('G'), 'tV', '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,
            Note(DiatonicPitch.parse('F#:5'), Duration(1, 8)))
        lower_note_2 = ContextualNote(lower_policy_context)

        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} == {'D:5', 'E:5', 'F#:5', 'G:5', 'A:5'}

        # test for determining note 1
        logging.debug('Determining note 1')
        lower_note_2.note = lower_note_1.note
        lower_note_1.note = None

        v_result = policy.values(p_map, upper_note_1)

        for note in v_result:
            logging.debug(note)

        pitches = [note.diatonic_pitch for note in v_result]
        assert {str(p) for p in pitches} == {'A:5', 'G:5', 'F#:5', 'E:5'}

        logging.debug('End test_simple_diatonic_test')
Пример #15
0
    def __init__(self, diatonic_basis, scale_degree, chord_type,
                 specified_seconds, inversion):
        """
        Constructor
        
        Args:
          diatonic_basis: DiatonicTone used as root of chord, e.g. C major chord, the C part
          scale_degree: int version of roman numeral
          chord_type: The chord type ala SecundalChordType
          specified_seconds: list of Interval's secondary notes
          inversion: int for which of the chord tones (ordinal) serves as root [origin 1]
        """
        ChordTemplate.__init__(self)
        self.__diatonic_basis = diatonic_basis  # DiatonicTone

        self.__scale_degree = scale_degree

        self.__chord_type = chord_type
        self.__inversion = inversion  # which tone of n is the bass

        self.__base_intervals = list()
        if chord_type:
            self.__base_intervals.extend(
                SecundalChordTemplate.SECUNDAL_CHORD_TYPE_MAP[
                    chord_type.value])
        self.__specified_seconds = specified_seconds
        if specified_seconds:
            intervals = list()
            intervals.append(Interval(1, IntervalType.Perfect))
            for ltr in specified_seconds:
                intervals.append(
                    Interval(
                        2, IntervalType.Major
                        if ltr == 'M' else IntervalType.Minor))
            self.__base_intervals.extend(intervals)

        # Inversion check - only if chord type was given, not for cases like II
        if self.chord_type and self.inversion > len(self.base_intervals):
            raise Exception('Illegal inversion {0} for {1}'.format(
                self.inversion, self.__str__()))
Пример #16
0
    def __init__(self, diatonic_basis, scale_degree, chord_type,
                 specified_fourths, inversion):
        """
        Constructor
        
        Args:
          diatonic_basis: DiatonicTone used as root of chord, e.g. C major chord, the C part
          scale_degree: int version of roman numeral
          chord_type: The chord type ala SecundalChordType
          specified_fourths: list of incremental fourth Interval's comprising the chord, e.g. [p, P, P]
                             usually used in lieu of, or addition to chord_type chord_type
          inversion: int for which of the chord tones (ordinal) serves as root [origin 1]
        """
        ChordTemplate.__init__(self)
        self.__diatonic_basis = diatonic_basis  # DiatonicTone

        self.__scale_degree = scale_degree

        self.__chord_type = chord_type
        self.__inversion = inversion  # which tone of n is the bass

        self.__base_intervals = []
        if chord_type:
            self.__base_intervals.extend(
                QuartalChordTemplate.QUARTAL_CHORD_TYPE_MAP[chord_type.value])
        self.__specified_fourths = specified_fourths
        if specified_fourths:
            intervals = list()
            intervals.append(Interval(1, IntervalType.Perfect))
            for ltr in specified_fourths:
                intervals.append(
                    Interval(
                        4, IntervalType.Perfect if ltr == 'P' or ltr == 'p'
                        else IntervalType.Augmented))
            self.__base_intervals.extend(intervals)

        # Inversion check - only if chord type was given, not for cases like II
        if self.chord_type and self.inversion > len(self.base_intervals):
            raise Exception('Illegal inversion {0} for {1}'.format(
                self.inversion, self.__str__()))
Пример #17
0
    def apply(self,
              target_hct,
              window_anchor_pitch,
              tag_map=None,
              window_height=None,
              num_solutions=-1,
              tunnel_half_interval=Interval(5, IntervalType.Perfect)):
        """
        Apply method for transformation.
        :param target_hct: Target hct for new target line.
        :param window_anchor_pitch: Pitch specifying the lowest pitch for the target line window.
        :param tag_map: map index of source/target note to specified pitch.
        :param window_height: Height of target pitch window (in semi-tones) - use source line height if None specified.
        :param num_solutions: Maximum number of solutions to return, -1 == unbounded.
        :param tunnel_half_interval: half-interval for pitch range on each target tone.
        :return: MCSResults
        """
        if self.source_hct.duration != target_hct.duration:
            raise Exception(
                'Target hct duration {0} does not match source hct duration {1}.'
                .format(target_hct.duration, self.source_hct.duration))

        window_anchor_pitch = DiatonicPitch.parse(window_anchor_pitch) if isinstance(window_anchor_pitch, str) \
            else window_anchor_pitch

        target_line = self._build_target_line()

        self.__tunnel_half_interval = tunnel_half_interval

        source_notes = self.source_line.get_all_notes()
        target_notes = target_line.get_all_notes()
        source_to_target = {
            source_note: target_note
            for source_note, target_note in zip(source_notes, target_notes)
        }

        constraints = self._build_constraints(source_to_target, tag_map)
        ts_seq, tempo_seq = THarmonicTranscription._build_default_time_sig_tempo(
        )

        height = window_height if window_height else self.height
        pitch_range = PitchRange(
            window_anchor_pitch.chromatic_distance,
            window_anchor_pitch.chromatic_distance + height)

        solver = MelodicConstraintSolver(target_line, tempo_seq, ts_seq,
                                         target_hct, pitch_range, constraints)

        initial_map = {target_notes[k]: v
                       for k, v in tag_map.items()} if tag_map else None
        results = solver.solve(initial_map, num_solutions)
        return results
Пример #18
0
    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
Пример #19
0
    def test_shift_create(self):
        t_domain = Tonality.create(ModalityType.MelodicMinor,
                                   DiatonicTone('C'))
        i = Interval(5, IntervalType.Perfect)
        r = PitchRange.create('E:3', 'E:7')

        f = CrossTonalityShiftPitchFunction(t_domain, r, i)

        assert 'P:5' == str(f.root_shift_interval)

        assert 'G:4' == str(f['C:4'])
        assert 'A:4' == str(f['D:4'])
        assert 'B:4' == str(f['E:4'])
        assert 'C:5' == str(f['F:4'])
Пример #20
0
 def test_upper_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
             interval = Interval(i, interval_type)
             p = interval.get_end_pitch(pitch)
             print(p)
             assert p.diatonic_distance() == 4 * 7 + 0 + (i - 1)
Пример #21
0
 def test_upper_tone_non_C(self):
     pitch = DiatonicPitch(4, 'E')
     for i in range(1, 13):
         for interval_type in TestInterval.INTERVAL_TYPES:
             if i == 1 or i == 4 or i == 5 or i == 8 or i == 11 or i == 12:
                 if i == 1 and interval_type == IntervalType(IntervalType.Diminished):
                     continue
                 if interval_type == IntervalType(IntervalType.Minor) or \
                    interval_type == IntervalType(IntervalType.Major):
                     continue 
             else:
                 if interval_type == IntervalType(IntervalType.Perfect):
                     continue
             interval = Interval(i, interval_type)
             print(interval)
             p = interval.get_end_pitch(pitch)
             print(p)
             assert p.diatonic_distance() == 4 * 7 + 2 + (i - 1), \
                 'dist {0} does not match computation {1} on {2}'.format(p.diatonic_distance(), 4 * 7 + 0 + (i - 1),
                                                                         interval)
Пример #22
0
class TertianChordTemplate(ChordTemplate):
    """
    Template for tertian chords.  We have a regular expression syntax to cover these cases that roughly goes:

    (T|t)?((I|II|...)|A-G)(Maj|Min| ...)?(+?(b|#)?[2-15])*(@[1-7])?
    
    Examples:
      IIMaj7+b9@3
      CDom7
      TIVDim7Flat5#3      The third is sharped
      
    Note: The idea of modifiying scale degree ala:
              (+|-)?(I|II|...)
          was considered.  The notation traces back to -ii being used as a shorthand for Neopolian Six chords.
          The reference:
              https://en.wikipedia.org/wiki/Neapolitan_chord
          provides an interesting argument of using Phrygian scales to provide an underpinning for Neopolican.
          However, to take the notation to the next level, cases such as +iv and -vi need similar underpinning,
          which at this point cannot be found.  So we are not allowing this notation unless a solid theoretical
          solution appears.
    """

    TERTIAN_CHORD_TYPE_MAP = {
        TertianChordType.Maj: [Interval(1, IntervalType.Perfect),
                               Interval(3, IntervalType.Major),
                               Interval(5, IntervalType.Perfect)],
        TertianChordType.MajSus2: [Interval(1, IntervalType.Perfect),
                                   Interval(2, IntervalType.Major),
                                   Interval(5, IntervalType.Perfect)],
        TertianChordType.MajSus4: [Interval(1, IntervalType.Perfect),
                                   Interval(4, IntervalType.Perfect),
                                   Interval(5, IntervalType.Perfect)],
        TertianChordType.MajSus: [Interval(1, IntervalType.Perfect),
                                  Interval(4, IntervalType.Perfect),
                                  Interval(5, IntervalType.Perfect)],
        TertianChordType.Min: [Interval(1, IntervalType.Perfect),
                               Interval(3, IntervalType.Minor),
                               Interval(5, IntervalType.Perfect)],
        TertianChordType.Dim: [Interval(1, IntervalType.Perfect),
                               Interval(3, IntervalType.Minor),
                               Interval(5, IntervalType.Diminished)],
        TertianChordType.Aug: [Interval(1, IntervalType.Perfect),
                               Interval(3, IntervalType.Major),
                               Interval(5, IntervalType.Augmented)],
        TertianChordType.Maj7: [Interval(1, IntervalType.Perfect),
                                Interval(3, IntervalType.Major),
                                Interval(5, IntervalType.Perfect),
                                Interval(7, IntervalType.Major)],
        TertianChordType.Maj7Sus2: [Interval(1, IntervalType.Perfect),
                                    Interval(2, IntervalType.Major),
                                    Interval(5, IntervalType.Perfect),
                                    Interval(7, IntervalType.Major)],
        TertianChordType.Maj7Sus4: [Interval(1, IntervalType.Perfect),
                                    Interval(4, IntervalType.Perfect),
                                    Interval(5, IntervalType.Perfect),
                                    Interval(7, IntervalType.Major)],
        TertianChordType.Maj7Sus: [Interval(1, IntervalType.Perfect),
                                   Interval(4, IntervalType.Perfect),
                                   Interval(5, IntervalType.Perfect),
                                   Interval(7, IntervalType.Major)],
        TertianChordType.Min7: [Interval(1, IntervalType.Perfect),
                                Interval(3, IntervalType.Minor),
                                Interval(5, IntervalType.Perfect),
                                Interval(7, IntervalType.Minor)],
        TertianChordType.Dom7: [Interval(1, IntervalType.Perfect),
                                Interval(3, IntervalType.Major),
                                Interval(5, IntervalType.Perfect),
                                Interval(7, IntervalType.Minor)],
        TertianChordType.Dom7Sus2: [Interval(1, IntervalType.Perfect),
                                    Interval(2, IntervalType.Major),
                                    Interval(5, IntervalType.Perfect),
                                    Interval(7, IntervalType.Minor)],
        TertianChordType.Dom7Sus4: [Interval(1, IntervalType.Perfect),
                                    Interval(4, IntervalType.Perfect),
                                    Interval(5, IntervalType.Perfect),
                                    Interval(7, IntervalType.Minor)],
        TertianChordType.Dom7Sus: [Interval(1, IntervalType.Perfect),
                                   Interval(4, IntervalType.Perfect),
                                   Interval(5, IntervalType.Perfect),
                                   Interval(7, IntervalType.Minor)],
        TertianChordType.Dim7: [Interval(1, IntervalType.Perfect),
                                Interval(3, IntervalType.Minor),
                                Interval(5, IntervalType.Diminished),
                                Interval(7, IntervalType.Diminished)],
        TertianChordType.HalfDim7: [Interval(1, IntervalType.Perfect),
                                    Interval(3, IntervalType.Minor),
                                    Interval(5, IntervalType.Diminished),
                                    Interval(7, IntervalType.Minor)],
        TertianChordType.MinMaj7: [Interval(1, IntervalType.Perfect),
                                   Interval(3, IntervalType.Minor),
                                   Interval(5, IntervalType.Perfect),
                                   Interval(7, IntervalType.Major)],
        TertianChordType.AugMaj7: [Interval(1, IntervalType.Perfect),
                                   Interval(3, IntervalType.Major),
                                   Interval(5, IntervalType.Augmented),
                                   Interval(7, IntervalType.Major)],
        TertianChordType.Aug7: [Interval(1, IntervalType.Perfect),
                                Interval(3, IntervalType.Major),
                                Interval(5, IntervalType.Augmented),
                                Interval(7, IntervalType.Minor)],
        TertianChordType.DimMaj7: [Interval(1, IntervalType.Perfect),
                                   Interval(3, IntervalType.Minor),
                                   Interval(5, IntervalType.Diminished),
                                   Interval(7, IntervalType.Major)],
        TertianChordType.Dom7Flat5: [Interval(1, IntervalType.Perfect),
                                     Interval(3, IntervalType.Major),
                                     Interval(5, IntervalType.Diminished),
                                     Interval(7, IntervalType.Minor)],
        TertianChordType.Maj6: [Interval(1, IntervalType.Perfect),
                                Interval(3, IntervalType.Major),
                                Interval(5, IntervalType.Perfect),
                                Interval(6, IntervalType.Major)],
        TertianChordType.Min6: [Interval(1, IntervalType.Perfect),
                                Interval(3, IntervalType.Minor),
                                Interval(5, IntervalType.Perfect),
                                Interval(6, IntervalType.Major)],
        TertianChordType.Fr: [Interval(6, IntervalType.Augmented),
                              Interval(1, IntervalType.Perfect),
                              Interval(2, IntervalType.Major),
                              Interval(4, IntervalType.Augmented)],
        TertianChordType.Ger: [Interval(6, IntervalType.Augmented),
                               Interval(1, IntervalType.Perfect),
                               Interval(3, IntervalType.Minor),
                               Interval(4, IntervalType.Augmented)],
        TertianChordType.It: [Interval(6, IntervalType.Minor),
                              Interval(1, IntervalType.Perfect),
                              Interval(4, IntervalType.Augmented)],
        TertianChordType.N6: [Interval(6, IntervalType.Minor),
                              Interval(2, IntervalType.Minor),
                              Interval(4, IntervalType.Perfect)],
    }

    # Note that augmented 6th chords and the neopolitan have the sixth as the root.  This is the normal position.
    # And inversions specified alter that order.  So, root position would be inversion == 2.

    GROUP_BASIS = 'Basis'
    GROUP_BASIS_TAG = '?P<' + GROUP_BASIS + '>'
    P1_BASIS = '(' + GROUP_BASIS_TAG + 'T|t)?'

    SCALE_DEGREE = 'III|II|IV|VII|VI|V|I|iii|ii|iv|vii|vi|v|i'
    GROUP_SCALE_DEGREE = 'ScaleDegree'
    GROUP_SCALE_DEGREE_TAG = '?P<' + GROUP_SCALE_DEGREE + '>'

    GROUP_DIATONIC_TONE = 'DiatonicTone'
    GROUP_DIATONIC_TONE_NAME = '?P<' + GROUP_DIATONIC_TONE + '>'
    ROOT = '((' + GROUP_DIATONIC_TONE_NAME + DiatonicTone.DIATONIC_PATTERN_STRING + ')|' + \
           '(' + GROUP_SCALE_DEGREE_TAG + SCALE_DEGREE + '))'

    TENSION_RANGE = '(10|11|12|13|14|15|9|8|7|6|5|4|3|2|1)'
    TENSION = '((\\+)' + '(bb|b|##|#)?' + TENSION_RANGE + ')'
    GROUP_TENSIONS = 'Tensions'
    GROUP_TENSIONS_TAG = '?P<' + GROUP_TENSIONS + '>'
    TERTIAN_TENSIONS = '(' + GROUP_TENSIONS_TAG + TENSION + '*)'

    CHORD_NAMES = 'Maj7Sus4|Maj7Sus2|Maj7Sus|Maj7|MajSus4|MajSus2|MajSus|Maj6|Maj|Min7|MinMaj7|Min6|Min|DimMaj7|' \
                  'Dom7Flat5|Dim7|Dim|AugMaj7|Aug7|Aug|Dom7Sus4|Dom7Sus2|Dom7Sus|Dom7|HalfDim7|Fr|Ger|It|N6'

    GROUP_CHORD = 'Chord'
    GROUP_CHORD_TAG = '?P<' + GROUP_CHORD + '>'
    CHORDS = '(' + GROUP_CHORD_TAG + CHORD_NAMES + ')?'

    INVERSION = '[1-7]'
    GROUP_INVERSION = 'Inversion'
    GROUP_INVERSION_TAG = '?P<' + GROUP_INVERSION + '>'
    # INVERSIONS = '(\@(' + GROUP_INVERSION_TAG + INVERSION + '))?'

    INVERSION_TENSION = 'InvTension'
    INVERSION_TENSION_TAG = '?P<' + INVERSION_TENSION + '>'
    INVERSION_TENSION_STRUCT = '\\(' + '(bb|b|##|#)?' + TENSION_RANGE + '\\)'
    INVERSION_TENSION_PATTERN = '(' + INVERSION_TENSION_TAG + INVERSION_TENSION_STRUCT + ')'
    INVERSIONS = '(\\@(' + GROUP_INVERSION_TAG + INVERSION + '|' + INVERSION_TENSION_PATTERN + '))?'

    # full parse string and accompanying pattern for the tertian chord grammar.
    TERTIAN_PARSE_STRING = P1_BASIS + ROOT + CHORDS + TERTIAN_TENSIONS + INVERSIONS + '$'
    TERTIAN_PATTERN = re.compile(TERTIAN_PARSE_STRING)

    TENSION_PATTERN = re.compile(TENSION)
    INVERSE_TENSION_PATTERN = re.compile(INVERSION_TENSION_STRUCT)

    def __init__(self, diatonic_basis, scale_degree, chord_type, tension_intervals, inversion, inversion_interval=None):
        """
        Constructor
        
        Args:
          diatonic_basis: DiatonicTone used as root of chord, e.g. C major chord, the C part
          scale_degree: int version of roman numeral
          chord_type: The chord type ala TertianChordType
          tension_intervals: list of Interval's comprising the tensions
          inversion: int for which of the chord tones (ordinal) serves as root [origin 1]
          inversion_interval: if specified, indicates which interval should be the base.
          (both this in interval cannot be non-null.)
        """
        ChordTemplate.__init__(self)
        self.__diatonic_basis = diatonic_basis  # DiatonicTone

        self.__scale_degree = scale_degree

        self.__chord_type = chord_type
        self.__tension_intervals = tension_intervals  # list of [number, augmentation] representing intervals
        self.__inversion = inversion  # which tone of n is the bass
        self.__inversion_interval = inversion_interval

        self.__base_intervals = []
        if chord_type:
            self.__base_intervals.extend(TertianChordTemplate.TERTIAN_CHORD_TYPE_MAP[self.chord_type.value])

        # Remove duplicate tensions
        seen = set()
        seen_add = seen.add
        deduped_tension_intervals = [tension for tension in self.tension_intervals
                                     if not (tension.semitones() in seen or seen_add(tension.semitones()))]
        self.__tension_intervals = deduped_tension_intervals

        # Inversion check - only if chord type was given, not for cases like II
        if self.chord_type and (self.inversion is not None) and \
                self.inversion > len(self.base_intervals) + len(self.tension_intervals):
            raise Exception('Illegal inversion {0} for {1}'.format(self.inversion, self.__str__()))

        if self.inversion_interval is not None and \
                self.inversion_interval not in self.base_intervals and \
                self.inversion_interval not in self.tension_intervals:
            raise Exception('Illegal inversion_interval {0}'.format(self.inversion_interval))

    @property
    def diatonic_basis(self):
        return self.__diatonic_basis

    @property
    def scale_degree(self):
        return self.__scale_degree

    @property
    def chord_type(self):
        return self.__chord_type

    @property
    def base_intervals(self):
        return self.__base_intervals

    @property
    def tension_intervals(self):
        return self.__tension_intervals

    @property
    def inversion(self):
        return self.__inversion

    @property
    def inversion_interval(self):
        return self.__inversion_interval

    @staticmethod
    def get_chord_type(interval_list):
        for k, v in list(TertianChordTemplate.TERTIAN_CHORD_TYPE_MAP.items()):
            if len(interval_list) == len(v):
                same = True
                for i in range(0, len(v)):
                    if not interval_list[i].is_same(v[i]):
                        same = False
                        break
                if same:
                    return TertianChordType(k)
        return None

    @staticmethod
    def get_triad(diatonic_tonality, scale_degree):
        return TertianChordTemplate.parse('t{0}'.format(
            ChordTemplate.SCALE_DEGREE_REVERSE_MAP[scale_degree])).create_chord(diatonic_tonality)

    def create_chord(self, diatonic_tonality=None):
        return TertianChord(self, diatonic_tonality)

    def __str__(self):
        inv = ''
        if self.inversion is not None and self.inversion != 1:
            inv = '@' + str(self.inversion)
        elif self.inversion_interval is not None:
            inv = '@(' + str(self.inversion_interval) + ')'
        return 'T{0}{1}{2}{3}'.format(
            self.diatonic_basis.diatonic_symbol if self.diatonic_basis else
            (str(ChordTemplate.SCALE_DEGREE_REVERSE_MAP[self.scale_degree])),
            self.chord_type if self.chord_type else '',
            ' '.join(str(w) for w in self.tension_intervals),
            inv)

    @staticmethod
    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)
Пример #23
0
class THarmonicTranscription(Transformation):
    """
    THarmonicTranscription: Construct a pattern with similar melodic structure to a source pattern, but given
        a specific hct to follow.
    """

    TUNNEL_HALF_INTERVAL = Interval(5, IntervalType.Perfect)

    def __init__(self, source_line, source_hct, source_melodic_form=None):
        """
        Constructor
        :param source_line: Source line.
        :param source_hct: Source line's hct.
        :param source_melodic_form: MelodicForm for the source line (optional).
        """
        self.__source_line = source_line
        self.__source_hct = source_hct
        self.__source_melodic_form = source_melodic_form

        self.__source_analysis = MelodicSearchAnalysis(self.source_line,
                                                       self.source_hct)

        min_pitch, max_pitch = THarmonicTranscription.compute_min_max_pitches(
            self.source_line.get_all_notes())
        self.__height = max_pitch.chromatic_distance - min_pitch.chromatic_distance
        self.__tunnel_half_interval = THarmonicTranscription.TUNNEL_HALF_INTERVAL

        Transformation.__init__(self)

    @staticmethod
    def create(source_expression):
        lge = LineGrammarExecutor()

        source_line, source_hct = lge.parse(source_expression)
        return THarmonicTranscription(source_line, source_hct)

    @property
    def source_line(self):
        return self.__source_line

    @property
    def source_hct(self):
        return self.__source_hct

    @property
    def source_analysis(self):
        return self.__source_analysis

    @property
    def source_melodic_form(self):
        return self.__source_melodic_form

    @property
    def height(self):
        return self.__height

    @property
    def tunnel_half_interval(self):
        return self.__tunnel_half_interval

    def apply(self,
              target_hct,
              window_anchor_pitch,
              tag_map=None,
              window_height=None,
              num_solutions=-1,
              tunnel_half_interval=Interval(5, IntervalType.Perfect)):
        """
        Apply method for transformation.
        :param target_hct: Target hct for new target line.
        :param window_anchor_pitch: Pitch specifying the lowest pitch for the target line window.
        :param tag_map: map index of source/target note to specified pitch.
        :param window_height: Height of target pitch window (in semi-tones) - use source line height if None specified.
        :param num_solutions: Maximum number of solutions to return, -1 == unbounded.
        :param tunnel_half_interval: half-interval for pitch range on each target tone.
        :return: MCSResults
        """
        if self.source_hct.duration != target_hct.duration:
            raise Exception(
                'Target hct duration {0} does not match source hct duration {1}.'
                .format(target_hct.duration, self.source_hct.duration))

        window_anchor_pitch = DiatonicPitch.parse(window_anchor_pitch) if isinstance(window_anchor_pitch, str) \
            else window_anchor_pitch

        target_line = self._build_target_line()

        self.__tunnel_half_interval = tunnel_half_interval

        source_notes = self.source_line.get_all_notes()
        target_notes = target_line.get_all_notes()
        source_to_target = {
            source_note: target_note
            for source_note, target_note in zip(source_notes, target_notes)
        }

        constraints = self._build_constraints(source_to_target, tag_map)
        ts_seq, tempo_seq = THarmonicTranscription._build_default_time_sig_tempo(
        )

        height = window_height if window_height else self.height
        pitch_range = PitchRange(
            window_anchor_pitch.chromatic_distance,
            window_anchor_pitch.chromatic_distance + height)

        solver = MelodicConstraintSolver(target_line, tempo_seq, ts_seq,
                                         target_hct, pitch_range, constraints)

        initial_map = {target_notes[k]: v
                       for k, v in tag_map.items()} if tag_map else None
        results = solver.solve(initial_map, num_solutions)
        return results

    def _build_target_line(self):
        # Build a target line, all notes C:4 with onsets/durations of original line.
        target_line = Line()
        initial_pitch = DiatonicPitch.parse('C:4')
        source_notes = self.source_line.get_all_notes()
        for note in source_notes:
            t_note = note.clone()
            t_note.diatonic_pitch = initial_pitch
            target_line.append(t_note)

        return target_line

    def _build_constraints(self, source_to_target, tag_map):
        # Constraints:
        #    contour based on pair analysis
        #    chordal if original note is chordal
        #    melodic form constraints
        #    Tunnel: for diatonic notes, a pitch range constraint based on the specified "tunnel" over target notes.
        pair_annotations = self.source_analysis.note_pair_annotation
        note_annotations = self.source_analysis.note_annotation

        constraints = list()

        for pair_annotation in pair_annotations:
            t1 = source_to_target[pair_annotation.first_note]
            t2 = source_to_target[pair_annotation.second_note]
            if pair_annotation.relationship == NotePairInformation.Relationship.LT:
                rel = ComparativePitchConstraint.LESS_THAN
            elif pair_annotation.relationship == NotePairInformation.Relationship.GT:
                rel = ComparativePitchConstraint.GREATER_THAN
            else:
                rel = ComparativePitchConstraint.EQUAL
            constraint = ComparativePitchConstraint(t1, t2, rel)
            constraints.append(constraint)

        for annotation in note_annotations:
            if annotation.is_chordal:
                constraint = ChordalPitchConstraint(
                    source_to_target[annotation.note])
                constraints.append(constraint)

        # Get the constraints off the motifs
        if self.source_melodic_form:
            form_constraints = self.source_melodic_form.constraints
            for c in form_constraints:
                c_prime = c.clone([source_to_target[n] for n in c.actors])
                constraints.append(c_prime)

        tunnel_constraints = self._build_tunnel_constraints(
            source_to_target, tag_map)
        constraints.extend(tunnel_constraints)

        return constraints

    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

    @staticmethod
    def _build_default_time_sig_tempo():
        tempo_seq = TempoEventSequence()
        ts_seq = EventSequence()
        tempo_seq.add(TempoEvent(Tempo(60, Duration(1, 4)), Position(0)))
        ts_seq.add(
            TimeSignatureEvent(TimeSignature(3, Duration(1, 4), 'sww'),
                               Position(0)))
        return ts_seq, tempo_seq

    @staticmethod
    def compute_min_max_pitches(notes):
        min_pitch = None
        max_pitch = None
        for n in notes:
            p = n.diatonic_pitch
            if p is None:
                continue
            min_pitch = p if min_pitch is None else p if p.chromatic_distance < min_pitch.chromatic_distance else \
                min_pitch
            max_pitch = p if max_pitch is None else p if p.chromatic_distance > max_pitch.chromatic_distance else \
                max_pitch

        return min_pitch, max_pitch
Пример #24
0
    def apply(self,
              reverse_harmony=True,
              time_interval=None,
              transcription=True,
              results_sample_size=200):
        """
        Extract and reverse a melodic segment of the score.
        :param reverse_harmony: Boolean indicating if harmony should be reversed.
        :param time_interval: Interval (numeric) bounds of the melody to be reversed
        :param transcription: True means apply harmonic transcription, only whenever_harmony==False.
        :param results_sample_size: Number of results from which to generate a best.
        :return: reversed line, hct
        Note: if reverse_harmony is False, a Harmonic Transcription is applied to the line.
        Note: as to assist when reverse_harmony is False, we make 2 optimization on harmonic transcription:
              1) We take the first note to the closest chord tone.
              2) We increase the height by 6 tones (half octave)
        """
        from tonalmodel.interval import Interval, IntervalType

        self.__reverse_harmony = reverse_harmony
        self.__time_interval = time_interval if time_interval is not None else \
            NumericInterval(Fraction(0), self.score.line.duration.duration)

        reduced_line, first_position, duration = self.score.line.sub_line(
            self.time_interval)
        reduced_reversed_line = reduced_line.clone()
        reduced_reversed_line.reverse()

        reduced_hct = self.score.hct.sub_hct(
            NumericInterval(first_position.position,
                            first_position.position + duration.duration))

        reduced_reversed_hct = reduced_hct.reverse()

        # If reversing harmony OR if reversing harmony and not doing transposition
        # These cases do not require transcription:
        #  a) reverse harmony and no transcription - return reversed line + reversed harmony
        #  b) no reverse harmony and no transcription - return reversed_line + original hct
        #  c) reverse harmony and transcription - return reversed_line and reversed harmony.
        if reverse_harmony or not transcription:
            return reduced_reversed_line, reduced_reversed_hct if reverse_harmony else reduced_hct

        # Case: not reversing harmony AND requiring a transposition
        # Must specify reversed line here, but ANALYZE against reversed_hct, which is the original harmony
        # making the correct analysis of the original melody.
        t_ht = THarmonicTranscription(reduced_reversed_line,
                                      reduced_reversed_hct)

        # Find lowest tone:
        notes = reduced_reversed_line.get_all_notes()
        note_index, _ = min(
            enumerate(notes),
            key=lambda n: n[1].diatonic_pitch.chromatic_distance)
        lowest_pitch = notes[note_index].diatonic_pitch

        # Adapt reversed melody to original harmony.
        tag_map = gen_tag_map(notes[0], reduced_hct.hc_list()[0])
        results = t_ht.apply(reduced_hct,
                             lowest_pitch,
                             tag_map,
                             t_ht.height + 6,
                             results_sample_size,
                             tunnel_half_interval=Interval(
                                 4, IntervalType.Perfect))

        filtered_results = MinContourFilter(reduced_reversed_line,
                                            results.pitch_results)
        scored_filtered_results = filtered_results.scored_results

        if len(scored_filtered_results) == 0:
            return None, None

        return scored_filtered_results[0][0], reduced_hct
Пример #25
0
    def test_for_book_example_1(self):
        print('----- test for book example 1 -----')

        source_instance_expression = '{<C-Major:IV> [sC:5 B:4 A G] qF:4 [sA:4 B C:5 D] qD:5}'
        target_instance_expression = '{<G-Major:V> wA:4}'

        lge = LineGrammarExecutor()

        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.create('C:2', 'C:8')

        p_map = PMap.create(source_instance_expression, pitch_range,
                            [('G-Major:V', 1)])

        actors = p_map.actors
        for a in actors:
            print("{0}".format(a))

        policies = set()
        policies.add(
            PitchStepConstraint(actors[0], actors[1], 1,
                                PitchStepConstraint.Down))
        policies.add(
            PitchStepConstraint(actors[1], actors[2], 1,
                                PitchStepConstraint.Down))
        policies.add(
            PitchStepConstraint(actors[2], actors[3], 1,
                                PitchStepConstraint.Down))
        policies.add(
            PitchStepConstraint(actors[5], actors[6], 1,
                                PitchStepConstraint.UP))
        policies.add(
            PitchStepConstraint(actors[6], actors[7], 1,
                                PitchStepConstraint.UP))
        policies.add(
            PitchStepConstraint(actors[7], actors[8], 1,
                                PitchStepConstraint.UP))
        policies.add(EqualPitchConstraint([actors[3], actors[4]]))
        policies.add(EqualPitchConstraint([actors[8], actors[9]]))
        policies.add(
            RelativeDiatonicConstraint(actors[4], actors[5],
                                       Interval(3, IntervalType.Major),
                                       Interval(1, IntervalType.Perfect)))
        policies.add(
            RelativeDiatonicConstraint(actors[3], actors[8],
                                       Interval(5, IntervalType.Perfect),
                                       Interval(1, IntervalType.Perfect)))
        policies.add(ChordalPitchConstraint(actors[4]))
        policies.add(ChordalPitchConstraint(actors[9]))

        solver = PitchConstraintSolver(policies)

        full_results, _ = solver.solve(p_map)
        print('Results has {0} results.'.format(len(full_results)))

        for pm in full_results:
            print("{0}".format(pm))
Пример #26
0
class TestPitchConstraintSolver(unittest.TestCase):
    logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)

    tone_cache = DiatonicToneCache.get_cache()

    def setUp(self):
        pass

    def tearDown(self):
        pass

    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_for_book_example_1(self):
        print('----- test for book example 1 -----')

        source_instance_expression = '{<C-Major:IV> [sC:5 B:4 A G] qF:4 [sA:4 B C:5 D] qD:5}'
        target_instance_expression = '{<G-Major:V> wA:4}'

        lge = LineGrammarExecutor()

        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.create('C:2', 'C:8')

        p_map = PMap.create(source_instance_expression, pitch_range,
                            [('G-Major:V', 1)])

        actors = p_map.actors
        for a in actors:
            print("{0}".format(a))

        policies = set()
        policies.add(
            PitchStepConstraint(actors[0], actors[1], 1,
                                PitchStepConstraint.Down))
        policies.add(
            PitchStepConstraint(actors[1], actors[2], 1,
                                PitchStepConstraint.Down))
        policies.add(
            PitchStepConstraint(actors[2], actors[3], 1,
                                PitchStepConstraint.Down))
        policies.add(
            PitchStepConstraint(actors[5], actors[6], 1,
                                PitchStepConstraint.UP))
        policies.add(
            PitchStepConstraint(actors[6], actors[7], 1,
                                PitchStepConstraint.UP))
        policies.add(
            PitchStepConstraint(actors[7], actors[8], 1,
                                PitchStepConstraint.UP))
        policies.add(EqualPitchConstraint([actors[3], actors[4]]))
        policies.add(EqualPitchConstraint([actors[8], actors[9]]))
        policies.add(
            RelativeDiatonicConstraint(actors[4], actors[5],
                                       Interval(3, IntervalType.Major),
                                       Interval(1, IntervalType.Perfect)))
        policies.add(
            RelativeDiatonicConstraint(actors[3], actors[8],
                                       Interval(5, IntervalType.Perfect),
                                       Interval(1, IntervalType.Perfect)))
        policies.add(ChordalPitchConstraint(actors[4]))
        policies.add(ChordalPitchConstraint(actors[9]))

        solver = PitchConstraintSolver(policies)

        full_results, _ = solver.solve(p_map)
        print('Results has {0} results.'.format(len(full_results)))

        for pm in full_results:
            print("{0}".format(pm))

    def test_for_debugging(self):
        logging.debug('Start test_for_debugging')

        p_map, policies = TestPitchConstraintSolver.generate_generic_sample(
            TestPitchConstraintSolver.example_2)

        solver = PitchConstraintSolver(policies)
        try:
            full_results, partial_results = solver.solve(p_map)
            if full_results is None:
                print("Full Results is None")
            else:
                print("Full Results is not None")
                if len(full_results) == 0:
                    print("Results is empty")
                else:
                    print('Results has {0} results.'.format(len(full_results)))

                    # verify
                    for pm in full_results:
                        for p in policies:
                            if not p.verify(pm.p_map):
                                print('Policy failure: {0}'.format(
                                    type(p).__name__))
                                print(pm)
                                continue

                    for pm in full_results:
                        print(pm)
        except Exception as e:
            print(e)
            # print >> sys.stderr, traceback.format_exc()
            traceback.print_exc()

        logging.debug('End test_for_debugging')

    example_1 = [[
        ModalityType.Major,
        tone_cache.get_tone('C'), 'tIV', 'C:2', 'C:8'
    ], [ModalityType.Major,
        tone_cache.get_tone('G'), 'tV', 'C:2', 'C:8'],
                 [['C:6', 'B:5', 'A:5', 'G:5'], ['e', 'e', 'e', 'e']],
                 [[PitchStepConstraint, [0, 1], 1, PitchStepConstraint.Down],
                  [PitchStepConstraint, [1, 2], 1, PitchStepConstraint.Down],
                  [PitchStepConstraint, [2, 3], 1, PitchStepConstraint.Down],
                  [PitchRangeConstraint, {3},
                   PitchRange.create('C:4', 'F:4')]]]

    example_2 = [[
        ModalityType.Major,
        tone_cache.get_tone('C'), 'tIV', 'C:2', 'C:8'
    ], [ModalityType.Major,
        tone_cache.get_tone('G'), 'tV', 'C:2', 'C:8'],
                 [[
                     'C:5', 'B:4', 'A:4', 'G:4', 'F:4', 'A:4', 'B:4', 'C:5',
                     'D:5', 'D:5'
                 ], ['s', 's', 's', 's', 'q', 's', 's', 's', 's', 'q']],
                 [[PitchStepConstraint, [0, 1], 1, PitchStepConstraint.Down],
                  [PitchStepConstraint, [1, 2], 1, PitchStepConstraint.Down],
                  [PitchStepConstraint, [2, 3], 1, PitchStepConstraint.Down],
                  [PitchStepConstraint, [5, 6], 1, PitchStepConstraint.UP],
                  [PitchStepConstraint, [6, 7], 1, PitchStepConstraint.UP],
                  [PitchStepConstraint, [7, 8], 1, PitchStepConstraint.UP],
                  [EqualPitchConstraint, {3, 4}],
                  [EqualPitchConstraint, {8, 9}],
                  [
                      RelativeDiatonicConstraint, [4, 5],
                      Interval(3, IntervalType.Major),
                      Interval(1, IntervalType.Perfect)
                  ],
                  [
                      RelativeDiatonicConstraint, [3, 8],
                      Interval(5, IntervalType.Perfect),
                      Interval(1, IntervalType.Perfect)
                  ], [ChordalPitchConstraint, [4]],
                  [ChordalPitchConstraint, [9]]]]

    @staticmethod
    def generate_generic_sample(sample):
        lower_policy_context = TestPitchConstraintSolver.policy_creator(
            sample[1][0], sample[1][1], sample[1][2], sample[1][3],
            sample[0][4])

        upper_notes = TestPitchConstraintSolver.create_plain_notes(
            sample[2][0], sample[2][1])

        p_map = TestPitchConstraintSolver.create_pmap(upper_notes,
                                                      lower_policy_context)

        policies = set()
        for i in range(0, len(sample[3])):
            spec = sample[3][i]
            args = []
            if isinstance(spec[1], list):
                for j in range(0, len(spec[1])):
                    args.append(upper_notes[spec[1][j]])
            elif isinstance(spec[1], set):
                actors = list()
                for j in spec[1]:
                    actors.append(upper_notes[j])
                args.append(actors)

            for j in range(2, len(spec)):
                args.append(spec[j])

            # args = [upper_notes[spec[1][0]], upper_notes[spec[1][1]], spec[2], spec[3]]
            policy = (spec[0])(*tuple(args))
            policies.add(policy)

        return p_map, policies

    @staticmethod
    def generate_sample():
        lower_policy_context = \
            TestPitchConstraintSolver.policy_creator(ModalityType.Major,
                                                     TestPitchConstraintSolver.tone_cache.get_tone('G'), 'tV',
                                                     'C:2', 'C:8')

        upper_notes = TestPitchConstraintSolver.create_plain_notes(
            ['C:6', 'B:5', 'A:5', 'G:5'], ['e', 'e', 'e', 'e'])

        p_map = TestPitchConstraintSolver.create_pmap(upper_notes,
                                                      lower_policy_context)

        policies = set()
        for i in range(0, len(upper_notes) - 1):
            policies.add(
                PitchStepConstraint(upper_notes[i], upper_notes[i + 1], 1,
                                    PitchStepConstraint.Down))

        return p_map, policies

    @staticmethod
    def create_pmap(upper_notes, lower_policy_context):
        p_map = OrderedDict()
        for s in upper_notes:
            lower_cn = ContextualNote(lower_policy_context)
            p_map[s] = lower_cn
        return p_map

    @staticmethod
    def create_contextual_notes(pitch_list, duration_list, policy_context):
        assert len(pitch_list) == len(duration_list)
        assert len(pitch_list) > 0

        result = []
        for pitch_str, duration_str in zip(pitch_list, duration_list):
            pitch = DiatonicPitch.parse(pitch_str)
            duration = TestPitchConstraintSolver.parse_duration(duration_str)
            note = Note(pitch, duration)
            cn = ContextualNote(policy_context, note)
            result.append(cn)

        return result

    @staticmethod
    def create_plain_notes(pitch_list, duration_list):
        assert len(pitch_list) == len(duration_list)
        assert len(pitch_list) > 0

        result = []
        for pitch_str, duration_str in zip(pitch_list, duration_list):
            pitch = DiatonicPitch.parse(pitch_str)
            duration = TestPitchConstraintSolver.parse_duration(duration_str)
            note = Note(pitch, duration)
            result.append(note)

        return result

    @staticmethod
    def parse_duration(txt):
        cap_txt = txt.upper()
        if cap_txt == 'W':
            return Duration(1)
        if cap_txt == 'H':
            return Duration(1, 2)
        if cap_txt == 'Q':
            return Duration(1, 4)
        if cap_txt == 'E':
            return Duration(1, 8)
        if cap_txt == 'S':
            return Duration(1, 16)
        raise Exception('Illegal duration text: {0}'.format(txt))

    @staticmethod
    def policy_creator(modality_type, modality_tone, tertian_chord_txt,
                       low_pitch_txt, hi_pitch_txt):
        diatonic_tonality = Tonality.create(modality_type, modality_tone)
        chord = TertianChordTemplate.parse(tertian_chord_txt).create_chord(
            diatonic_tonality)
        hc = HarmonicContext(diatonic_tonality, chord, Duration(1, 2))

        pitch_range = PitchRange(
            DiatonicPitch.parse(low_pitch_txt).chromatic_distance,
            DiatonicPitch.parse(hi_pitch_txt).chromatic_distance)
        return PolicyContext(hc, pitch_range)
Пример #27
0
class QuartalChordTemplate(ChordTemplate):
    """
    Template for quartal chords.  Quartal chords are based on incremental intervals of perfect and augmented intervals.
    This follows along the lines of Persechetti.  We do not used diminished 4th as they identify more with major 3rds.  
    
    We have a regular expression syntax to cover these cases that roughly goes:

    (Q|q)((I|II|...)|A-G)((PerPer|PerAug|AugPer| ...)|(m|M)+))?(@([1-9]([0-9]*)))?
    
    Examples:
      QII
      qCpapa
    """

    QUARTAL_CHORD_TYPE_MAP = {
        QuartalChordType.PerPer: [
            Interval(1, IntervalType.Perfect),
            Interval(4, IntervalType.Perfect),
            Interval(4, IntervalType.Perfect)
        ],
        QuartalChordType.PerAug: [
            Interval(1, IntervalType.Perfect),
            Interval(4, IntervalType.Perfect),
            Interval(4, IntervalType.Augmented)
        ],
        QuartalChordType.AugPer: [
            Interval(1, IntervalType.Perfect),
            Interval(4, IntervalType.Augmented),
            Interval(4, IntervalType.Perfect)
        ],
    }

    GROUP_BASIS = 'Basis'
    GROUP_BASIS_TAG = '?P<' + GROUP_BASIS + '>'
    P1_BASIS = '(' + GROUP_BASIS_TAG + 'Q|q)?'

    SCALE_DEGREE = 'III|II|IV|VII|VI|V|I|iii|ii|iv|vii|vi|v|i'
    GROUP_SCALE_DEGREE = 'ScaleDegree'
    GROUP_SCALE_DEGREE_TAG = '?P<' + GROUP_SCALE_DEGREE + '>'

    GROUP_DIATONIC_TONE = 'DiatonicTone'
    GROUP_DIATONIC_TONE_NAME = '?P<' + GROUP_DIATONIC_TONE + '>'
    ROOT = '((' + GROUP_DIATONIC_TONE_NAME + DiatonicTone.DIATONIC_PATTERN_STRING + ')|' + \
           '(' + GROUP_SCALE_DEGREE_TAG + SCALE_DEGREE + '))'

    CHORD_NAMES = 'PerPer|PerAug|AugPer'
    GROUP_CHORD = 'Chord'
    GROUP_CHORD_TAG = '?P<' + GROUP_CHORD + '>'

    SECONDS = 'Seconds'
    SECONDS_SPECIFICATION_TAG = '?P<' + SECONDS + '>'
    CHORDS = '((' + GROUP_CHORD_TAG + CHORD_NAMES + ')|(' + SECONDS_SPECIFICATION_TAG + '(p|P|a|A)+))?'

    INVERSION = '[1-9]([0-9]*)'
    GROUP_INVERSION = 'Inversion'
    GROUP_INVERSION_TAG = '?P<' + GROUP_INVERSION + '>'
    INVERSIONS = '(\\@(' + GROUP_INVERSION_TAG + INVERSION + '))?'

    # full parse string and accompanying pattern for the secundal chord grammar.
    QUARTAL_PARSE_STRING = P1_BASIS + ROOT + CHORDS + INVERSIONS + '$'
    QUARTAL_PATTERN = re.compile(QUARTAL_PARSE_STRING)

    def __init__(self, diatonic_basis, scale_degree, chord_type,
                 specified_fourths, inversion):
        """
        Constructor
        
        Args:
          diatonic_basis: DiatonicTone used as root of chord, e.g. C major chord, the C part
          scale_degree: int version of roman numeral
          chord_type: The chord type ala SecundalChordType
          specified_fourths: list of incremental fourth Interval's comprising the chord, e.g. [p, P, P]
                             usually used in lieu of, or addition to chord_type chord_type
          inversion: int for which of the chord tones (ordinal) serves as root [origin 1]
        """
        ChordTemplate.__init__(self)
        self.__diatonic_basis = diatonic_basis  # DiatonicTone

        self.__scale_degree = scale_degree

        self.__chord_type = chord_type
        self.__inversion = inversion  # which tone of n is the bass

        self.__base_intervals = []
        if chord_type:
            self.__base_intervals.extend(
                QuartalChordTemplate.QUARTAL_CHORD_TYPE_MAP[chord_type.value])
        self.__specified_fourths = specified_fourths
        if specified_fourths:
            intervals = list()
            intervals.append(Interval(1, IntervalType.Perfect))
            for ltr in specified_fourths:
                intervals.append(
                    Interval(
                        4, IntervalType.Perfect if ltr == 'P' or ltr == 'p'
                        else IntervalType.Augmented))
            self.__base_intervals.extend(intervals)

        # Inversion check - only if chord type was given, not for cases like II
        if self.chord_type and self.inversion > len(self.base_intervals):
            raise Exception('Illegal inversion {0} for {1}'.format(
                self.inversion, self.__str__()))

    @property
    def diatonic_basis(self):
        return self.__diatonic_basis

    @property
    def scale_degree(self):
        return self.__scale_degree

    @property
    def chord_type(self):
        return self.__chord_type

    @property
    def base_intervals(self):
        return self.__base_intervals

    @property
    def inversion(self):
        return self.__inversion

    @property
    def specified_fourths(self):
        return self.__specified_fourths

    def create_chord(self, diatonic_tonality=None):
        return QuartalChord(self, diatonic_tonality)

    @staticmethod
    def get_chord_type(interval_list):
        for k, v in list(QuartalChordTemplate.QUARTAL_CHORD_TYPE_MAP.items()):
            if len(interval_list) == len(v):
                same = True
                for i in range(0, len(v)):
                    if not interval_list[i].is_same(v[i]):
                        same = False
                        break
                if same:
                    return QuartalChordType(k)

        # Build a M/m string
        t = ''
        for interval in interval_list[1:]:
            if interval.interval_type == IntervalType.Perfect:
                t += 'P'
            elif interval.interval_type == IntervalType.Augmented:
                t += 'A'
            else:
                raise Exception(
                    'Illegal interval type for quartal {0}'.format(interval))
        return t

    def __str__(self):
        return 'Q{0}{1}{2}{3}'.format(
            self.diatonic_basis.diatonic_symbol if self.diatonic_basis else
            (str(ChordTemplate.SCALE_DEGREE_REVERSE_MAP[self.scale_degree])),
            self.chord_type if self.chord_type else
            (self.specified_fourths if self.specified_fourths else ''),
            '@' + str(self.inversion) if self.inversion != 1 else '',
            ' --> ' + (' '.join(str(w) for w in self.base_intervals)),
        )

    @staticmethod
    def parse(chord_string):
        """
        Parse an input string into a QuartalChordTemplate.
        
        Args:
          chord_string: string input representing chord
        Returns:
          QuartalChordTemplate       
        """
        if not chord_string:
            raise QuartalChordException(
                'Unable to parse chord string to completion: {0}'.format(
                    chord_string))
        m = QuartalChordTemplate.QUARTAL_PATTERN.match(chord_string)
        if not m:
            raise QuartalChordException(
                'Unable to parse chord string to completion: {0}'.format(
                    chord_string))

        scale_degree = m.group(QuartalChordTemplate.GROUP_SCALE_DEGREE)
        if scale_degree:
            scale_degree = ChordTemplate.SCALE_DEGREE_MAP[scale_degree]
        if m.group(QuartalChordTemplate.GROUP_DIATONIC_TONE) is not None:
            diatonic_basis = DiatonicTone(
                m.group(QuartalChordTemplate.GROUP_DIATONIC_TONE))
        else:
            diatonic_basis = None
        chord_name = m.group(QuartalChordTemplate.GROUP_CHORD)
        chord_type = None
        if chord_name:
            chord_type = QuartalChordType.to_type(chord_name)

        fourths = m.group(QuartalChordTemplate.SECONDS)
        inversion_text = m.group(QuartalChordTemplate.GROUP_INVERSION)
        inversion = int(inversion_text) if inversion_text else 1

        logging.info('{0}, {1}, {2}, {3}'.format(
            diatonic_basis if scale_degree is None else str(scale_degree),
            str(chord_type) if chord_type else '', fourths if fourths else '',
            inversion))
        return QuartalChordTemplate(diatonic_basis, scale_degree, chord_type,
                                    fourths, inversion)
Пример #28
0
    def test_negative_intervals(self):
        interval = Interval(-3, IntervalType.Major)
        assert interval.diatonic_distance == -2
        assert interval.chromatic_distance == -4
        assert str(interval) == '-M:3'
        print(interval)
        
        interval = Interval.parse('-P:5')
        assert interval.diatonic_distance == -4
        assert interval.chromatic_distance == -7
        assert str(interval) == '-P:5'
        print(interval)
        
        pitch_a = DiatonicPitch(5, 'C')
        pitch_b = DiatonicPitch(4, 'C')
        interval = Interval.create_interval(pitch_a, pitch_b)
        print(interval)
        assert interval.interval_type == IntervalType(IntervalType.Perfect)
        assert interval.diatonic_distance == -7
        
        pitch_a = DiatonicPitch(5, 'C')
        pitch_b = DiatonicPitch(4, 'Cb')
        interval = Interval.create_interval(pitch_a, pitch_b)
        print(interval)
        assert interval.interval_type == IntervalType(IntervalType.Augmented)
        assert interval.diatonic_distance == -7
        assert str(interval) == '-A:8'
        
        pitch_a = DiatonicPitch(5, 'C')
        pitch_b = DiatonicPitch(4, 'C#')
        interval = Interval.create_interval(pitch_a, pitch_b)
        print(interval)
        assert interval.interval_type == IntervalType(IntervalType.Diminished)
        assert interval.diatonic_distance == -7
        assert str(interval) == '-d:8'

        pitch_a = DiatonicPitch(5, 'C')
        lower_pitches = [DiatonicPitch(4, i) for i in list('CDEFGAB')]
        answers = ['-P:8', '-m:7', '-m:6', '-P:5', '-P:4', '-m:3', '-m:2']
        for (p, a) in zip(lower_pitches, answers):
            interval = Interval.create_interval(pitch_a, p)
            assert str(interval) == a
          
        lower_pitches = [DiatonicPitch(4, i) for i in 'Cb,Db,Eb,Fb,Gb,Ab,Bb'.split(',')]
        answers = ['-A:8', '-M:7', '-M:6', '-A:5', '-A:4', '-M:3', '-M:2']
        for (p, a) in zip(lower_pitches, answers):
            interval = Interval.create_interval(pitch_a, p)
            assert str(interval) == a

        lower_pitches = [DiatonicPitch(4, i) for i in 'C#,D#,E#,F#,G#,A#,B#'.split(',')]
        answers = ['-d:8', '-d:7', '-d:6', '-d:5', '-d:4', '-d:3', '-d:2']
        for (p, a) in zip(lower_pitches, answers):
            interval = Interval.create_interval(pitch_a, p)
            assert str(interval) == a

        interval = Interval.parse('-M:3')
        p = DiatonicPitch(4, 'Ab')
        end_p = interval.get_end_pitch(p)
        print(end_p)
        assert str(end_p) == 'Fb:4'
        
        interval = Interval.parse('-P:5')
        p = DiatonicPitch(4, 'D')
        end_p = interval.get_end_pitch(p)
        print(end_p)
        assert str(end_p) == 'G:3'

        interval_strs = ['-P:1', '-M:2', '-M:3', '-P:4', '-P:5', '-M:6', '-M:7', '-P:8', '-M:9', '-M:10',
                         '-P:11', '-P:12', '-M:13', '-M:14', '-P:15']
        intervals = [Interval.parse(i) for i in interval_strs]
        p = DiatonicPitch(4, 'G')
        answers = ['G:4', 'F:4', 'Eb:4', 'D:4', 'C:4', 'Bb:3', 'Ab:3', 
                   'G:3', 'F:3', 'Eb:3', 'D:3', 'C:3', 'Bb:2', 'Ab:2',
                   'G:2'
                   ]
        end_ps = []
        for interval in intervals:
            end_p = interval.get_end_pitch(p)
            print(end_p)
            end_ps.append(end_p)
        for end_p, answer in zip(end_ps, answers):
            assert str(end_p) == answer
            
        # Negation tests
        interval_strs = ['P:1', 'M:2', 'M:3', 'P:4', 'P:5', 'M:6', 'M:7', 'P:8', 'M:9', 'M:10', 'P:11',
                         'P:12', 'M:13', 'M:14', 'P:15',
                         '-P:1', '-M:2', '-M:3', '-P:4', '-P:5', '-M:6', '-M:7', '-P:8', '-M:9', '-M:10', '-P:11',
                         '-P:12', '-M:13', '-M:14', '-P:15']
        intervals = [Interval.parse(i) for i in interval_strs]
        count = 1
        for interval, i_str in zip(intervals, interval_strs):
            neg_interval = interval.negation()
            if count <= 15:
                assert str(neg_interval) == ('-' if count > 1 else '') + i_str
            else:
                assert str(neg_interval) == i_str[1:]
            count += 1
            
        interval_strs = ['-P:1', '-M:2', '-M:3', '-P:4', '-P:5', '-M:6', '-M:7', '-P:8', '-M:9', '-M:10', '-P:11',
                         '-P:12', '-M:13', '-M:14', '-P:15']
        intervals = [Interval.parse(i) for i in interval_strs]
        p = DiatonicPitch(4, 'G')
        answers = ['G:4', 'A:4', 'B:4', 'C:5', 'D:5', 'E:5', 'F#:5', 
                   'G:5', 'A:5', 'B:5', 'C:6', 'D:6', 'E:6', 'F#:6',
                   'G:6'
                   ]
        end_ps = []
        print('+++++')
        for interval in intervals:
            end_p = interval.get_start_pitch(p)
            print(end_p)
            end_ps.append(end_p)
        print('-----')
        for end_p, answer in zip(end_ps, answers):
            assert str(end_p) == answer
Пример #29
0
class SecundalChordTemplate(ChordTemplate):
    """
    Template for secundal chords.  Secundal chords are based on incremental intervals of major and minor 2nd intervals.  
    
    We have a regular expression syntax to cover these cases that roughly goes:

    (S|s)((I|II|...)|A-G)((MinMin|MinMaj|MajMin|MajMaj| ...)|(m|M)+))?(@([1-9]([0-9]*)))?
    
    Examples:
      sII
      sCMmM
      
    Note: Along the lines of tertian, the idea of modifiying scale degree ala:
              (+|-)?(I|II|...)
          was considered.  The notation traces back to -ii being used as a shorthand for Neopolian Six chords.
          The reference:
              https://en.wikipedia.org/wiki/Neapolitan_chord
          provides an interesting argument of using Phrygian scales to provide an underpinning for Neopolican.
          However, to take the notation to the next level, cases such as +iv and -vi need similar underpinning,
          which at this point cannot be found.  So we are not allowing this notation unless a solid theoretical
          solution appears.
          
    """

    SECUNDAL_CHORD_TYPE_MAP = {
        SecundalChordType.MinMin: [
            Interval(1, IntervalType.Perfect),
            Interval(2, IntervalType.Minor),
            Interval(2, IntervalType.Minor)
        ],
        SecundalChordType.MajMin: [
            Interval(1, IntervalType.Perfect),
            Interval(2, IntervalType.Major),
            Interval(2, IntervalType.Minor)
        ],
        SecundalChordType.MinMaj: [
            Interval(1, IntervalType.Perfect),
            Interval(2, IntervalType.Minor),
            Interval(2, IntervalType.Major)
        ],
        SecundalChordType.MajMaj: [
            Interval(1, IntervalType.Perfect),
            Interval(2, IntervalType.Major),
            Interval(2, IntervalType.Major)
        ],
    }

    GROUP_BASIS = 'Basis'
    GROUP_BASIS_TAG = '?P<' + GROUP_BASIS + '>'
    P1_BASIS = '(' + GROUP_BASIS_TAG + 'S|s)?'

    SCALE_DEGREE = 'III|II|IV|VII|VI|V|I|iii|ii|iv|vii|vi|v|i'
    GROUP_SCALE_DEGREE = 'ScaleDegree'
    GROUP_SCALE_DEGREE_TAG = '?P<' + GROUP_SCALE_DEGREE + '>'

    GROUP_DIATONIC_TONE = 'DiatonicTone'
    GROUP_DIATONIC_TONE_NAME = '?P<' + GROUP_DIATONIC_TONE + '>'
    ROOT = '((' + GROUP_DIATONIC_TONE_NAME + DiatonicTone.DIATONIC_PATTERN_STRING + ')|' + \
           '(' + GROUP_SCALE_DEGREE_TAG + SCALE_DEGREE + '))'

    CHORD_NAMES = 'MinMin|MinMaj|MajMin|MajMaj'
    GROUP_CHORD = 'Chord'
    GROUP_CHORD_TAG = '?P<' + GROUP_CHORD + '>'

    SECONDS = 'Seconds'
    SECONDS_SPECIFICATION_TAG = '?P<' + SECONDS + '>'
    CHORDS = '((' + GROUP_CHORD_TAG + CHORD_NAMES + ')|(' + SECONDS_SPECIFICATION_TAG + '(m|M)+))?'

    INVERSION = '[1-9]([0-9]*)'
    GROUP_INVERSION = 'Inversion'
    GROUP_INVERSION_TAG = '?P<' + GROUP_INVERSION + '>'
    INVERSIONS = '(\\@(' + GROUP_INVERSION_TAG + INVERSION + '))?'

    # full parse string and accompanying pattern for the secundal chord grammar.
    SECUNDAL_PARSE_STRING = P1_BASIS + ROOT + CHORDS + INVERSIONS + '$'
    SECUNDAL_PATTERN = re.compile(SECUNDAL_PARSE_STRING)

    def __init__(self, diatonic_basis, scale_degree, chord_type,
                 specified_seconds, inversion):
        """
        Constructor
        
        Args:
          diatonic_basis: DiatonicTone used as root of chord, e.g. C major chord, the C part
          scale_degree: int version of roman numeral
          chord_type: The chord type ala SecundalChordType
          specified_seconds: list of Interval's secondary notes
          inversion: int for which of the chord tones (ordinal) serves as root [origin 1]
        """
        ChordTemplate.__init__(self)
        self.__diatonic_basis = diatonic_basis  # DiatonicTone

        self.__scale_degree = scale_degree

        self.__chord_type = chord_type
        self.__inversion = inversion  # which tone of n is the bass

        self.__base_intervals = list()
        if chord_type:
            self.__base_intervals.extend(
                SecundalChordTemplate.SECUNDAL_CHORD_TYPE_MAP[
                    chord_type.value])
        self.__specified_seconds = specified_seconds
        if specified_seconds:
            intervals = list()
            intervals.append(Interval(1, IntervalType.Perfect))
            for ltr in specified_seconds:
                intervals.append(
                    Interval(
                        2, IntervalType.Major
                        if ltr == 'M' else IntervalType.Minor))
            self.__base_intervals.extend(intervals)

        # Inversion check - only if chord type was given, not for cases like II
        if self.chord_type and self.inversion > len(self.base_intervals):
            raise Exception('Illegal inversion {0} for {1}'.format(
                self.inversion, self.__str__()))

    @property
    def diatonic_basis(self):
        return self.__diatonic_basis

    @property
    def scale_degree(self):
        return self.__scale_degree

    @property
    def chord_type(self):
        return self.__chord_type

    @property
    def base_intervals(self):
        return self.__base_intervals

    @property
    def inversion(self):
        return self.__inversion

    @property
    def specified_seconds(self):
        return self.__specified_seconds

    def create_chord(self, diatonic_tonality=None):
        return SecundalChord(self, diatonic_tonality)

    @staticmethod
    def get_chord_type(interval_list):
        for k, v in list(
                SecundalChordTemplate.SECUNDAL_CHORD_TYPE_MAP.items()):
            if len(interval_list) == len(v):
                same = True
                for i in range(0, len(v)):
                    if not interval_list[i].is_same(v[i]):
                        same = False
                        break
                if same:
                    return SecundalChordType(k)

        # Build a M/m string
        t = ''
        for interval in interval_list[1:]:
            if interval.interval_type == IntervalType.Major:
                t += 'M'
            elif interval.interval_type == IntervalType.Minor:
                t += 'm'
            else:
                raise Exception(
                    'Illegal interval type for secundal {0}'.format(interval))
        return t

    def __str__(self):
        return 'S{0}{1}{2}{3}'.format(
            self.diatonic_basis.diatonic_symbol if self.diatonic_basis else
            (str(ChordTemplate.SCALE_DEGREE_REVERSE_MAP[self.scale_degree])),
            self.chord_type if self.chord_type else
            (self.specified_seconds if self.specified_seconds else ''),
            '@' + str(self.inversion) if self.inversion != 1 else '',
            ' --> ' + (' '.join(str(w) for w in self.base_intervals)),
        )

    @staticmethod
    def parse(chord_string):
        """
        Parse an input string into a SecundalChordTemplate.
        
        Args:
          chord_string: string input representing chord
        Returns:
          SecundalChordTemplate       
        """
        if not chord_string:
            raise SecundalChordException(
                'Unable to parse chord string to completion: {0}'.format(
                    chord_string))
        m = SecundalChordTemplate.SECUNDAL_PATTERN.match(chord_string)
        if not m:
            raise SecundalChordException(
                'Unable to parse chord string to completion: {0}'.format(
                    chord_string))

        scale_degree = m.group(SecundalChordTemplate.GROUP_SCALE_DEGREE)
        if scale_degree:
            scale_degree = ChordTemplate.SCALE_DEGREE_MAP[scale_degree]
        if m.group(SecundalChordTemplate.GROUP_DIATONIC_TONE) is not None:
            diatonic_basis = DiatonicTone(
                m.group(SecundalChordTemplate.GROUP_DIATONIC_TONE))
        else:
            diatonic_basis = None
        chord_name = m.group(SecundalChordTemplate.GROUP_CHORD)
        chord_type = None
        if chord_name:
            chord_type = SecundalChordType.to_type(chord_name)

        seconds = m.group(SecundalChordTemplate.SECONDS)
        inversion_text = m.group(SecundalChordTemplate.GROUP_INVERSION)
        inversion = int(inversion_text) if inversion_text else 1

        logging.info('{0}, {1}, {2}, {3}'.format(
            diatonic_basis if scale_degree is None else str(scale_degree),
            str(chord_type) if chord_type else '', seconds if seconds else '',
            inversion))
        return SecundalChordTemplate(diatonic_basis, scale_degree, chord_type,
                                     seconds, inversion)
Пример #30
0
 def test_semitones(self):
     answers = (
                 0,
                 1,
                 0,
                 1,
                 2,
                 3,
                 2,
                 3,
                 4,
                 5,
                 4,
                 5,
                 6,
                 6,
                 7,
                 8,
                 7,
                 8,
                 9,
                 10,
                 9,
                 10,
                 11,
                 12,
                 11,
                 12,
                 13,
                 12,
                 13,
                 14,
                 15,
                 14,
                 15,
                 16,
                 17,
                 16,
                 17,
                 18,
                 18,
                 19,
                 20)
     
     test_index = 0
     for i in range(1, 13):
         for interval_type in TestInterval.INTERVAL_TYPES:
             if i == 1 or i == 4 or i == 5 or i == 8 or i == 11 or i == 12:
                 if i == 1 and interval_type == IntervalType(IntervalType.Diminished):
                     continue
                 if interval_type == IntervalType(IntervalType.Minor) or interval_type == \
                    IntervalType(IntervalType.Major):
                     continue 
             else:
                 if interval_type == IntervalType(IntervalType.Perfect):
                     continue
         
             interval = Interval(i, interval_type)
             semitones = interval.semitones()
             print('{0} has {1} semitones'.format(interval, semitones))
             assert semitones == answers[test_index], \
                 'semitones {0} != {1}  interval {2}'.format(semitones, answers[test_index], interval)
             test_index += 1