예제 #1
0
 def test_book_example(self):
     location = ChromaticScale.parse_notation("4:9")
     print(location)
     index = ChromaticScale.location_to_index(location)
     print(index)
     loc = ChromaticScale.index_to_location(index)
     print(loc)
예제 #2
0
    def values(self, p_map, v_note):
        if v_note != self.actor_note:
            raise Exception('v_note {0} not in ScalarConstraint actors.'.format(v_note.note))

        policy_context = p_map[self.actor_note].policy_context
        tones = list(policy_context.harmonic_context.tonality.annotation)
        tones = tones[:-1]   # remove final note (same as first)
        if len(self.scalar_roles) != 0:
            tones = [tones[i] for i in self.scalar_roles]
        if p_map[v_note].note is not None:
            tone = p_map[v_note].note.diatonic_pitch.diatonic_tone
            return OrderedSet([self.actor_note]) if tone in tones else None

        pitch_range = policy_context.pitch_range
        start_partition = max(ChromaticScale.index_to_location(pitch_range.start_index)[0] - 1, 0)
        end_partition = min(ChromaticScale.index_to_location(pitch_range.end_index)[0] + 1,
                            ChromaticScale.CHROMATIC_END[0])

        valid_set = OrderedSet()

        for tone in tones:
            for j in range(start_partition, end_partition + 1):
                pitch = DiatonicPitch(j, tone)
                if pitch_range.is_pitch_inbounds(pitch):
                    note = Note(pitch, self.actor_note.base_duration, self.actor_note.num_dots)
                    valid_set.add(note)

        return valid_set
예제 #3
0
    def __init__(self,
                 anchor_pitch=DiatonicPitch.parse('A:0'),
                 anchor_value=9,
                 pitch_unit=1):
        """
        Constructor,
        """
        self.__anchor_pitch = anchor_pitch
        if not isinstance(self.anchor_pitch, DiatonicPitch):
            raise Exception('Anchor is not a DiatonicPitch')

        self.__anchor_value = anchor_value
        self.__pitch_unit = pitch_unit

        anchor_index = self.anchor_pitch.chromatic_distance
        base_value = anchor_value - anchor_index * pitch_unit

        self.value_to_pitch = OrderedMap()
        self.pitch_to_value = dict()
        for i in range(ChromaticScale.chromatic_start_index(),
                       ChromaticScale.chromatic_end_index() + 1):
            pitch = DiatonicFoundation.map_to_diatonic_scale(i)[0]
            value = base_value + i * pitch_unit
            self.value_to_pitch.insert(value, pitch)
            self.pitch_to_value[pitch] = value

        PitchRangeInterpreter.__init__(self)
예제 #4
0
    def values(self, p_map, v_note):
        if v_note != self.actor_note:
            raise Exception("Illegal v_note {0} for constraints".format(v_note))
        if p_map[v_note].note is not None:
            if p_map[v_note].note.diatonic_pitch.diatonic_tone == self.tone:
                return {p_map[v_note].note}
            raise Exception('Fixed Tone Policy Violated has {0} should be {1}'.format(
                p_map[v_note].note.diatonic_pitch.diatonic_tone, self.tone))

        contextual_note = p_map[self.actor_note]
        policy_context = contextual_note.policy_context
        pitch_range = contextual_note.policy_context.pitch_range
        start_partition = max(ChromaticScale.index_to_location(pitch_range.start_index)[0] - 1, 0)
        end_partition = min(ChromaticScale.index_to_location(pitch_range.end_index)[0] + 1,
                            ChromaticScale.CHROMATIC_END[0])

        # Try to find that tone in target's tonality/scale.
        tone = self.tone
        for t_str in self.tone.enharmonics():
            t = DiatonicToneCache.get_tone(t_str)
            for scale_tone in PitchScale(policy_context.harmonic_context.tonality,
                                         policy_context.pitch_range).tone_scale:
                if scale_tone == t:
                    tone = t
                    break

        valid_set = OrderedSet()
        for i in range(start_partition, end_partition + 1):
            pitch = DiatonicPitch(i, tone)
            if pitch_range.is_pitch_inbounds(pitch):
                note = Note(pitch, self.actor_note.base_duration, self.actor_note.num_dots)
                valid_set.add(note)

        return valid_set
예제 #5
0
    def compute_closest_scale_tones(tonality, pitch):
        """
        Returns either the pitch if in tonality, or lower/upper pitches in scale to pitch.
        :param tonality:
        :param pitch:
        :return: an array with 1 element if exact match, otherwise closest lower and upper bound pitches
                 in given tonality.
        """
        from tonalmodel.pitch_range import PitchRange
        chromatic_index = pitch.chromatic_distance
        pitch_range = PitchRange(
            max(chromatic_index - 12, ChromaticScale.chromatic_start_index()),
            min(chromatic_index + 12, ChromaticScale.chromatic_end_index()))
        pitch_scale = PitchScale(tonality, pitch_range)

        for i in range(0, len(pitch_scale.pitch_scale)):
            p = pitch_scale.pitch_scale[i]
            if p.chromatic_distance < chromatic_index:
                continue
            if p.chromatic_distance == chromatic_index:
                return [p]
            if i == 0:
                raise Exception(
                    'unexpected logic issue in compute_closest_pitch_range {0}, {1]'
                    .format(tonality, pitch))
            return [pitch_scale.pitch_scale[i - 1], p]
        raise Exception(
            'unexpected logic fail in compute_closest_pitch_range {0}, {1]'.
            format(tonality, pitch))
예제 #6
0
    def test_basic_plf(self):
        array = [(0, 'A:0'), (Fraction(1, 2), 'C:5'), (Position(3, 4), 'G:4'),
                 (1, 'A:5')]
        f = PiecewiseLinearPitchFunction(array)

        assert DiatonicPitch.parse('A:0').chromatic_distance == f.eval(0)
        assert DiatonicPitch.parse('C:5').chromatic_distance == f.eval(0.5)
        assert DiatonicPitch.parse('G:4').chromatic_distance == f.eval(
            Fraction(3, 4))
        assert DiatonicPitch.parse('A:5').chromatic_distance == f.eval(
            Position(1))

        assert DiatonicPitch.parse(
            'A:0').chromatic_distance == f.eval_as_chromatic_distance(0)
        assert DiatonicPitch.parse(
            'C:5').chromatic_distance == f.eval_as_chromatic_distance(0.5)
        assert DiatonicPitch.parse(
            'G:4').chromatic_distance == f.eval_as_chromatic_distance(
                Fraction(3, 4))
        assert DiatonicPitch.parse(
            'A:5').chromatic_distance == f.eval_as_chromatic_distance(
                Position(1))

        print(f.eval_as_frequency(0))
        assert ChromaticScale.A0 == f.eval_as_frequency(0)

        print(
            ChromaticScale.index_to_location(
                DiatonicPitch.parse('C:5').chromatic_distance))
        print(
            ChromaticScale.get_frequency(
                ChromaticScale.index_to_location(
                    DiatonicPitch.parse('C:5').chromatic_distance)))
        print(f.eval_as_frequency(0.5))
        assert math.isclose(
            ChromaticScale.get_frequency(
                ChromaticScale.index_to_location(
                    DiatonicPitch.parse('C:5').chromatic_distance)),
            f.eval_as_frequency(0.5))

        print(f.eval(0.25))  # 34.5
        print(f.eval_as_nearest_pitch(0.25))
        assert math.isclose(
            ChromaticScale.A0 * math.pow(ChromaticScale.SEMITONE_RATIO,
                                         f.eval(0.25) - 9),
            f.eval_as_frequency(0.25))

        assert 'A#:2' == str(f.eval_as_nearest_pitch(0.25))

        pitches = f.eval_as_pitch(0.25)
        assert 'A#:2' == str(pitches[0])
        assert 'B:2' == str(pitches[1])
예제 #7
0
    def test_scale(self):
        scale = ChromaticScale.get_chromatic_scale(
            ChromaticScale.parse_notation("0:9"),
            ChromaticScale.parse_notation("8:0"))
        start = ChromaticScale.location_to_index((0, 9))
        end = ChromaticScale.location_to_index((8, 0)) + 1

        for i in range(start, end):
            logging.info('{0}{1}   {1}'.format(
                i, ChromaticScale.index_to_location(i), scale[i - start]))

        assert is_close(scale[ChromaticScale.location_to_index((4, 9)) - start], 440.0), \
            "Error A:4 = {0} should be 440.0".format(scale[ChromaticScale.location_to_index((4, 9)) - start])
        assert is_close(scale[ChromaticScale.location_to_index((4, 0)) - start], 261.625565301), \
            "Error C:4 = {0} should be 261.625565301".format(scale[ChromaticScale.location_to_index((4, 0)) - start])
예제 #8
0
    def __compute_pitch_scale(self):
        (tone_index, pitch_index) = self.__find_lowest_tone(
        )  # Determine the lowest tone in the range
        if tone_index == -1:
            return []
        scale = [
            DiatonicPitch(
                ChromaticScale.index_to_location(pitch_index)[0],
                self.tone_scale[tone_index].diatonic_symbol)
        ]

        # Given the first pitch, sync up with the incremental intervals on the tonality, and move forward, computing
        # each scale pitch until we are out of range.
        # Note: be sure to skip the first incremental interval which should be P:1
        prior_pitch = scale[0]
        while True:
            tone_index += 1
            if tone_index > len(self.tone_scale) - 1:
                tone_index = 1  # skip 0 as that should be P:1
            incremental_interval = self.tonality.modality.incremental_intervals[
                tone_index]
            current_pitch = incremental_interval.get_end_pitch(prior_pitch)
            if current_pitch.chromatic_distance > self.pitch_range.end_index:
                break
            scale.append(current_pitch)
            prior_pitch = current_pitch

        return scale
예제 #9
0
 def __init__(self, start_index, end_index):
     """
     Constructor
     
     Args:
       start_index: integer start chromatic pitch index
       end_index: integer end chromatic pitch index
     Exceptions: is start, end out of range of absolute chromatic range, plus those from Range.
     """
     Range.__init__(self, start_index, end_index)
     if start_index < ChromaticScale.chromatic_start_index():
         raise Exception(
             "Start index {0} lower than chromatic start {1}".format(
                 start_index, ChromaticScale.chromatic_start_index()))
     if end_index > ChromaticScale.chromatic_end_index():
         raise Exception(
             "end index {0} higher than chromatic end {1}".format(
                 end_index, ChromaticScale.chromatic_end_index()))
예제 #10
0
 def find_lowest_placement_in_range(self, placement):
     """
     For a given chromatic placement (0, ..., 11) find the lowest chromatic index 
     in the range for it.
     """
     if placement < 0 or placement >= 12:
         raise Exception(
             'Illegal placement value {0} must be between 0 and 11'.format(
                 placement))
     start_partition = ChromaticScale.index_to_location(self.start_index)[0]
     end_partition = ChromaticScale.index_to_location(self.end_index)[0]
     lowest_index = -1
     for partition in range(start_partition, end_partition + 1):
         if self.is_location_inbounds((partition, placement)):
             lowest_index = ChromaticScale.location_to_index(
                 (partition, placement))
             break
     return lowest_index
예제 #11
0
 def is_location_inbounds(self, location):
     """
     Determines if given chromatic location is in bounds of range.
     
     Args:
       location: chromatic location
     Returns:
       boolean indicating if in bounds.
     """
     return self.is_inbounds(ChromaticScale.location_to_index(location))
예제 #12
0
    def values(self, p_map, v_note):
        """
        Return a set of possible pitches that v_note can take that must be in p_map target's chord.
        :param p_map: 
        :param v_note: 
        :return: 
        """
        if v_note != self.actor_note:
            raise Exception(
                'v_note {0} not in ChordalToneConstraint actors.'.format(
                    v_note.note))

        policy_context = p_map[self.actor_note].policy_context
        tones = policy_context.harmonic_context.chord.tones
        if p_map[v_note].note is not None:
            note = p_map[v_note].note
            if note.diatonic_pitch.diatonic_tone in tones:
                return {note}
            raise Exception(
                'Chordal Pitch Policy Violated has {0} should be member of chord {1}'
                .format(p_map[v_note].note.diatonic_pitch,
                        policy_context.harmonic_context.chord))

        pitch_range = policy_context.pitch_range
        start_partition = max(
            ChromaticScale.index_to_location(pitch_range.start_index)[0] - 1,
            0)
        end_partition = min(
            ChromaticScale.index_to_location(pitch_range.end_index)[0] + 1,
            ChromaticScale.CHROMATIC_END[0])

        valid_set = OrderedSet()
        for tone in tones:
            for i in range(start_partition, end_partition + 1):
                pitch = DiatonicPitch(i, tone[0])
                if pitch_range.is_pitch_inbounds(str(pitch)):
                    note = Note(pitch, self.actor_note.base_duration,
                                self.actor_note.num_dots)
                    valid_set.add(note)

        return valid_set
예제 #13
0
    def _build_pitch_map(self):
        ltrs = 'CDEFGAB'
        index = ltrs.index(self.domain_tonality.diatonic_tone.diatonic_letter)
        key_ltrs = list(ltrs[index:] + ltrs[:index])
        c_index = key_ltrs.index('C')
        low_octave = self.cue_pitch.octave if key_ltrs.index(self.cue_pitch.diatonic_tone.diatonic_letter) < c_index \
            else self.cue_pitch.octave - 1
        high_octave = low_octave + 1

        range_ltrs = 'CBAGFED'
        index = range_ltrs.index(self.range_tonality.diatonic_tone.diatonic_letter)
        range_key_ltrs = list(range_ltrs[index:] + range_ltrs[:index])
        range_c_index = range_key_ltrs.index('C')
        range_low_octave = self.cue_pitch.octave if range_key_ltrs.index(self.cue_pitch.diatonic_tone.diatonic_letter) \
            > range_c_index else self.cue_pitch.octave - 1
        range_high_octave = range_low_octave + 1

        imap = dict()
        for tone in self.tonal_function.domain:
            domain_octave = low_octave if key_ltrs.index(tone.diatonic_letter) < c_index else high_octave
            value = self.tonal_function[tone]
            range_octave = range_high_octave if range_key_ltrs.index(value.diatonic_letter) <= range_c_index \
                else range_low_octave
            imap[DiatonicPitch(domain_octave, tone)] = DiatonicPitch(range_octave, value)

        full_map = dict()
        start = ChromaticScale.index_to_location(self.domain_pitch_range.start_index)[0]
        end = ChromaticScale.index_to_location(self.domain_pitch_range.end_index)[0] + 1

        for octave in range(start, end):
            if octave < ChromaticScale.CHROMATIC_START[0] or octave > ChromaticScale.CHROMATIC_END[0]:
                continue
            octave_delta = self.cue_pitch.octave - octave
            for pitch in imap.keys():
                rrange = imap[pitch]
                new_pitch = DiatonicPitch(pitch.octave - octave_delta, pitch.diatonic_tone)
                if self.domain_pitch_range.is_pitch_inbounds(new_pitch):
                    full_map[new_pitch] = DiatonicPitch(rrange.octave + octave_delta, rrange.diatonic_tone)

        return full_map
예제 #14
0
    def test_lowest_placement(self):
        pr = PitchRange.create('G:3', 'G:5')
        answers = [4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3]
        for i in range(0, 12):
            lowest = pr.find_lowest_placement_in_range(i)
            partition = ChromaticScale.index_to_location(lowest)[0]
            self.assertTrue(partition == answers[i], 'Assert failure {0} != {1}'.format(partition, answers[i]))
            print(i, partition)
            
        with self.assertRaises(Exception):
            pr.find_lowest_placement_in_range(-1)

        with self.assertRaises(Exception):
            pr.find_lowest_placement_in_range(12)
예제 #15
0
    def eval_as_accurate_chromatic_distance(self, v):
        floor_value = self.value_to_pitch.floor(v)
        if floor_value is None:
            raise ChromaticRangeInterpreterException(
                'Illegal chromatic pitch range paramger value {0}.'.format(v))
        low_pitch = self.value_to_pitch[floor_value]
        index = low_pitch.chromatic_distance

        if index >= ChromaticScale.chromatic_end_index() or math.isclose(
                v, floor_value):
            return low_pitch.chromatic_distance
        high_pitch = DiatonicFoundation.map_to_diatonic_scale(index + 1)[0]
        return low_pitch.chromatic_distance + \
            ((v - floor_value) / self.pitch_unit) * \
            (high_pitch.chromatic_distance - low_pitch.chromatic_distance)
예제 #16
0
 def map_to_diatonic_scale(chromatic_index):
     """
     Convert a chromatic index (int) to a diatonic pitch in string format.
     
     Args:
       chromatic_index: the chromatic index of the pitch (int)
     Return:
       all enharmonic diatonic pitches
     """
     from tonalmodel.diatonic_pitch import DiatonicPitch
     location = ChromaticScale.index_to_location(chromatic_index)
     enharmonics = DiatonicTone.DIATONIC_OFFSET_ENHARMONIC_MAPPING[
         location[1]]
     octave_adjustments = DiatonicFoundation.ENHARMONIC_OCTAVE_ADJUSTMENT_MAPPING[
         location[1]]
     answers = []
     for i in range(0, len(enharmonics)):
         enharmonic = enharmonics[i]
         answers.append(
             DiatonicPitch.parse(enharmonic + ':' +
                                 str(location[0] + octave_adjustments[i])))
     return answers
    def _find_closest_pitch(self, pitch):
        """
        Given a pitch, find the scale pitch closest to it in chromatic distance.
        :param pitch:
        :return:
           1) closest in half-steps pitch in tonality.
           2) chromatic distance measured from closest pitch to given pitch.
        Note: algorithm looks for tonal pitches with same letter as pitch first, otherwise nearest non-tonal pitch.
        """
        start_octave = max(pitch.octave - 1, ChromaticScale.CHROMATIC_START[0])
        end_octave = min(pitch.octave + 1, ChromaticScale.CHROMATIC_END[0])

        # Compute the first and last pitches within the start/end octave range. To build a PitchScale
        first_pitch = None
        for t in self.tonality.annotation:
            first_pitch = DiatonicPitch(start_octave, t)
            if DiatonicFoundation.get_chromatic_distance(first_pitch) >= ChromaticScale.chromatic_start_index():
                break

        last_pitch = None
        loop_finished = False
        for o in range(end_octave, end_octave - 2, -1):
            if loop_finished:
                break
            for t in reversed(self.tonality.annotation):
                last_pitch = DiatonicPitch(o, t)
                if DiatonicFoundation.get_chromatic_distance(last_pitch) <= ChromaticScale.chromatic_end_index():
                    loop_finished = True
                    break

        scale = PitchScale(self.tonality, PitchRange.create(first_pitch, last_pitch))

        # determine if pitch ltr is in tonality, get that tone
        ll = [t for t in self.tones if t.diatonic_letter == pitch.diatonic_tone.diatonic_letter]
        if len(ll) == 1:
            pp = DiatonicPitch(pitch.octave, ll[0])
            return pp, pitch.chromatic_distance - pp.chromatic_distance

        # Do something if len(ll) > 1
        elif len(ll) > 1:
            ll.sort(key=lambda x: abs(x.augmentation_offset - pitch.diatonic_tone.augmentation_offset))
            pp = DiatonicPitch(pitch.octave, ll[0])
            return pp, pitch.chromatic_distance - pp.chromatic_distance

        before_pitch = first_pitch
        after_pitch = last_pitch
        for p in scale.pitch_scale:
            if pitch.chromatic_distance <= p.chromatic_distance:
                after_pitch = p
                break
            else:
                before_pitch = p

        before_distance = pitch.chromatic_distance - before_pitch.chromatic_distance
        after_distance = pitch.chromatic_distance - after_pitch.chromatic_distance

        if pitch.diatonic_tone.diatonic_letter == before_pitch.diatonic_tone.diatonic_letter:
            closest_distance = before_distance
            closest_pitch = before_pitch
        elif pitch.diatonic_tone.diatonic_letter == after_pitch.diatonic_tone.diatonic_letter:
            closest_distance = after_distance
            closest_pitch = after_pitch
        else:
            if abs(before_distance) < abs(after_distance):
                closest_distance = before_distance
                closest_pitch = before_pitch
            else:
                closest_distance = after_distance
                closest_pitch = after_pitch

        return closest_pitch, closest_distance
    def _build_pitch_map(self):
        imap = dict()
        n_tones = len(self.tones)
        if self.flip_type == FlipType.CenterTone:
            upper = lower = self._tone_index
            upper_octave = lower_octave = self._scale_origin_octave
            upper_tone = self.cue_tone
            lower_tone = self.tonal_function[self.cue_tone]
            imap[DiatonicPitch(lower_octave, lower_tone)] = DiatonicPitch(upper_octave, upper_tone)
        else:
            if self.flip_type == FlipType.LowerNeighborOfPair:
                lower = self._tone_index
                upper = (lower + 1) % n_tones
                lower_tone = self.tones[lower]
                upper_tone = self.tonal_function[lower_tone]  # self.tones[upper]
                lower_octave = self._scale_origin_octave
                upper_octave = self._scale_origin_octave + 1 if DiatonicPitch.crosses_c(lower_tone, upper_tone, True)\
                    else self._scale_origin_octave
            else:
                upper = self._tone_index
                lower = (upper - 1) % n_tones
                lower_tone = self.tones[lower]
                upper_tone = self.tonal_function[lower_tone]  # self.tones[upper]
                upper_octave = self._scale_origin_octave
                lower_octave = self._scale_origin_octave if not DiatonicPitch.crosses_c(lower_tone, upper_tone, True) \
                    else self._scale_origin_octave - 1

            lower_pitch = DiatonicPitch(lower_octave, lower_tone)
            upper_pitch = DiatonicPitch(upper_octave, upper_tone)
            imap[lower_pitch] = upper_pitch
            imap[upper_pitch] = lower_pitch

        entered_range = False
        while True:
            lower = (lower - 1) % n_tones
            upper = (upper + 1) % n_tones
            if DiatonicPitch.crosses_c(lower_tone, self._tones[lower], False):
                lower_octave = lower_octave - 1
            if DiatonicPitch.crosses_c(upper_tone, self._tones[upper], True):
                upper_octave = upper_octave + 1
            lower_tone = self._tones[lower]
            upper_tone = self.tonal_function[lower_tone]

            lower_pitch = DiatonicPitch(lower_octave, lower_tone)
            upper_pitch = DiatonicPitch(upper_octave, upper_tone)
            if upper_pitch.chromatic_distance < ChromaticScale.chromatic_start_index() or \
                    upper_pitch.chromatic_distance > ChromaticScale.chromatic_end_index():
                break
            if lower_pitch.chromatic_distance < ChromaticScale.chromatic_start_index() or \
                    lower_pitch.chromatic_distance > ChromaticScale.chromatic_end_index():
                break
            if entered_range:
                if not self._domain_pitch_range.is_pitch_inbounds(lower_pitch) and \
                        not self._domain_pitch_range.is_pitch_inbounds(upper_pitch):
                    break
            elif self._domain_pitch_range.is_pitch_inbounds(lower_pitch) or \
                    self._domain_pitch_range.is_pitch_inbounds(upper_pitch):
                entered_range = True
            else:
                continue
            imap[lower_pitch] = upper_pitch
            imap[upper_pitch] = lower_pitch

        return self._build_non_tonal_pitch_map(imap)
    def _build_pitch_map1(self):
        pitch_map = dict()

        lo_reg = self.domain_pitch_range.start_index // 12
        hi_reg = self.domain_pitch_range.end_index // 12

        domain_start_tone = CrossTonalityShiftPitchFunction.compute_lowest_letter(
            self.domain_pitch_range.start_index %
            12)  # self.domain_tonality.annotation[0]
        range_start_tone = CrossTonalityShiftPitchFunction.compute_highest_letter(
            self.domain_pitch_range.end_index %
            12)  # self.range_tonality.annotation[0]

        # note letter template should start with letter for domain_start_tone
        note_letters = 'ABCDEFG'
        i = note_letters.index(domain_start_tone.diatonic_letter)
        note_letters = note_letters[i:] + note_letters[:i]

        domain_tones = list()
        for ltr in note_letters:
            for aug in ['bb', 'b', '', '#', '##']:
                domain_tones.append(DiatonicFoundation.get_tone(ltr + aug))

        first_domain_pitch = None
        first_range_pitch = None
        last_domain_pitch = None
        last_range_pitch = None
        for register in range(lo_reg, hi_reg + 1):
            domain_reg = register
            target_pitch = self.root_shift_interval.get_end_pitch(
                DiatonicPitch(domain_reg, domain_start_tone))
            range_reg = target_pitch.octave
            domain_reg_upped = False
            range_reg_upped = False
            for tone in domain_tones:
                if not domain_reg_upped and tone.diatonic_letter == 'C' and domain_start_tone.diatonic_letter != 'C':
                    domain_reg = domain_reg + 1
                    domain_reg_upped = True
                domain_pitch = DiatonicPitch(domain_reg, tone)
                if domain_pitch.chromatic_distance < ChromaticScale.chromatic_start_index() or \
                        domain_pitch.chromatic_distance > ChromaticScale.chromatic_end_index():
                    continue
                if self.domain_pitch_range.is_pitch_inbounds(domain_pitch):
                    range_tone = self.tonal_function[tone]
                    if not range_reg_upped and range_tone.diatonic_letter == 'C' and \
                            range_start_tone.diatonic_letter != 'C':
                        range_reg = range_reg + 1
                        range_reg_upped = True
                    target_pitch = DiatonicPitch(range_reg, range_tone)
                    if target_pitch.chromatic_distance < ChromaticScale.chromatic_start_index() or \
                            target_pitch.chromatic_distance > ChromaticScale.chromatic_end_index():
                        continue
                    pitch_map[domain_pitch] = target_pitch
                    if first_domain_pitch is None:
                        first_domain_pitch = domain_pitch
                        first_range_pitch = target_pitch
                    last_domain_pitch = domain_pitch
                    last_range_pitch = target_pitch

        if first_domain_pitch.chromatic_distance > last_domain_pitch.chromatic_distance:
            self.__domain_pitch_range = PitchRange.create(
                last_domain_pitch, first_domain_pitch)
        else:
            self.__domain_pitch_range = PitchRange.create(
                first_domain_pitch, last_domain_pitch)

        if first_range_pitch.chromatic_distance > last_range_pitch.chromatic_distance:
            self.__range_pitch_range = PitchRange.create(
                last_range_pitch, first_range_pitch)
        else:
            self.__range_pitch_range = PitchRange.create(
                first_range_pitch, last_range_pitch)
        return pitch_map
예제 #20
0
 def create_default(tonality):
     return PitchScale(
         tonality,
         PitchRange(ChromaticScale.chromatic_start_index(),
                    ChromaticScale.chromatic_end_index()))
예제 #21
0
 def test_location_to_index(self):
     for i in range(1, 4):
         for j in range(0, 12):
             index = ChromaticScale.location_to_index((i, j))
             assert index == 12 * i + j
예제 #22
0
    def compute_tonal_pitch_range(tonality, pitch, lower_index, upper_index):
        """
        Find all pitches within range of tonality based on an arbitrary pitch given as starting point.
        In all cases, look at the closest pitches (1 or 2) as origin 0, and the lower/upper as counting indices
        below or up from them.
        :param tonality:
        :param pitch:
        :param lower_index:
        :param upper_index:
        :return:
        """
        import math
        from tonalmodel.pitch_range import PitchRange
        starting_points = PitchScale.compute_closest_scale_tones(
            tonality, pitch)

        # Determine the number of octaves that will cover the given range.
        up_chrom = max(
            0,
            int(
                math.ceil(float(abs(upper_index)) / len(tonality.annotation)) *
                12) * (-1 if upper_index < 0 else 1))
        down_chrom = min(
            0,
            int(
                math.ceil(float(abs(lower_index)) / len(tonality.annotation)) *
                12) * (-1 if lower_index < 0 else 1))

        # Compute all pitches within that range
        low = max(starting_points[0].chromatic_distance + down_chrom,
                  ChromaticScale.chromatic_start_index())
        high = min(
            (starting_points[0].chromatic_distance if len(starting_points) == 1
             else starting_points[1].chromatic_distance) + up_chrom,
            ChromaticScale.chromatic_end_index())

        pitch_range = PitchRange(low, high)
        pitch_scale = PitchScale(tonality, pitch_range).pitch_scale

        # The first starting point is either the enharmonic equivalent to pitch, or the lower scale pitch to the pitch.
        # lower_starting_index is the index in pitch_scale for that pitch.
        lower_starting_index = [
            index for index in range(0, len(pitch_scale))
            if pitch_scale[index].chromatic_distance ==
            starting_points[0].chromatic_distance
        ][0]

        if len(starting_points) == 1:
            full_range = range(
                lower_starting_index + lower_index,
                min(lower_starting_index + upper_index + 1, len(pitch_scale)))
            return [pitch_scale[i] for i in full_range]
        else:
            upper_starting_index = [
                index for index in range(0, len(pitch_scale))
                if pitch_scale[index].chromatic_distance ==
                starting_points[1].chromatic_distance
            ][0]
            lo = lower_index + (lower_starting_index
                                if lower_index <= 0 else upper_starting_index)
            hi = upper_index + (lower_starting_index
                                if upper_index < 0 else upper_starting_index)
            full_range = range(lo, hi + 1)
            return [pitch_scale[i] for i in full_range]
예제 #23
0
    def __init__(self, tonal_function, scale_origins, domain_pitch_range, reversal=False):
        """
        Constructor.
        :param tonal_function: TonalFunction upon which this function is based.
        :param scale_origins: Duplet of origin pitches for the domain and range respectively.
        :param domain_pitch_range: Domain pitch range (PitchRange)
        :param reversal: If during construction, domain and range octaves follow together forward, or if the range
                         follow backwards.
        """
        self.domain_tonality = tonal_function.domain_tonality
        self.range_tonality = tonal_function.range_tonality

        if domain_pitch_range is None or not isinstance(domain_pitch_range, PitchRange):
            raise Exception('domain pitch range cannot be None and must be PitchRange')

        dlex, dcom = TonalityPitchFunction._build_lex_dcom_maps(self.domain_tonality, scale_origins[0])
        rlex, rcom = TonalityPitchFunction._build_lex_dcom_maps(self.range_tonality, scale_origins[1])

        # Needed for getitem()
        self._dlex = dlex

        # These define how the mapping starts on the domain, in terms of octaves octaves
        # d_reg: The octave for which the domain tonality is centered.
        # d_min: The minimal octave in the octave coverage of the domain.
        # d_max: The maximal octave in the octave coverage of the domain.
        d_reg = DiatonicPitch.parse(scale_origins[0]).octave
        d_min = max(ChromaticScale.CHROMATIC_START[0],
                    ChromaticScale.index_to_location(domain_pitch_range.start_index)[0]) - 1
        d_max = min(ChromaticScale.CHROMATIC_END[0],
                    ChromaticScale.index_to_location(domain_pitch_range.start_index)[1]) + 1

        # Some aspects of building pitch_map.
        # Goal: pitch_map: (key from largest range of (tone, octave) in domain) --> (range tone, octave)
        # The following help:
        # d_lex: maps enharmonic representations of domain tone to the domain tones.
        #        the range is called the normalized range.
        # d_com: for domain pitch scale centered at scale_origin[0], maps normalized tone to octave.
        # r_lex and r_com are similar but for the range.
        #
        # The pitch map is based on the following dynamics:
        # For d_key in pitch domain, d_key --(d_lex)-->d_n_key--(d_com)-->d_octave
        #     d_key --(tonal_function)--> r_key
        #     r_key --(r_lex)-->r_n_key --(r_com)--> r_octave
        # then:
        #     pitch_map(DP(d_octave, d_key) = DP(r_octave, r_key)
        # with adjustments to d_octave, r_octave based on sliding over the octave ranges.
        pitch_map = OrderedDict()
        for d_octave in range(d_min, d_max + 1):
            for d_tone in tonal_function.map.keys():
                source_pitch = DiatonicPitch(d_octave + (dcom[dlex[d_tone]] - d_reg), d_tone)
                r_tone = tonal_function[d_tone]
                if r_tone is not None:
                    target_pitch = DiatonicPitch(rcom[rlex[r_tone]] -
                                                 (-1 if reversal else 1) * (d_reg - d_octave), r_tone)
                    if domain_pitch_range.is_pitch_inbounds(source_pitch) and \
                            TonalityPitchFunction.FULL_PITCH_RANGE.is_pitch_inbounds(target_pitch):
                        pitch_map[source_pitch] = target_pitch
                else:
                    pitch_map[source_pitch] = None

        GeneralPitchFunction.__init__(self, pitch_map)
예제 #24
0
 def test_parse_chromatic_location(self):
     for i in range(0, 12):
         s = str(4) + ':' + str(i)
         location = ChromaticScale.parse_notation(s)
         assert location[0] == 4 and location[1] == i
예제 #25
0
 def test_frequencies(self):
     assert is_close(ChromaticScale.get_frequency((4, 9)), 440.0), \
         "Error A:4 = {0} should be 440.0".format(ChromaticScale.get_frequency((4, 9)))
     assert is_close(ChromaticScale.get_frequency((4, 0)), 261.625565301), \
         "Error C:4 = {0} should be 261.625565301".format(ChromaticScale.get_frequency((4, 0)))
예제 #26
0
 def test_index_to_location(self):
     for i in range(12, 47):
         location = ChromaticScale.index_to_location(i)
         logging.info(location)
         assert location[0] == i // 12 and location[1] == i % 12