예제 #1
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)
예제 #2
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))
예제 #3
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()))
    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
예제 #7
0
 def create_default(tonality):
     return PitchScale(
         tonality,
         PitchRange(ChromaticScale.chromatic_start_index(),
                    ChromaticScale.chromatic_end_index()))
예제 #8
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]