Пример #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 _setup(self, transition_points):
        self.__transition_points = sorted(transition_points,
                                          key=lambda x: x[0])
        self.__domain_start = self.__transition_points[0][0]
        self.__domain_end = self.__transition_points[
            len(self.__transition_points) - 1][0]

        self.ordered_map = OrderedMap(self.transition_points)
Пример #3
0
    def test_remove(self):
        ordered_list = [(10, 100), (5, 20), (7, 70), (2, 50)]
        om = OrderedMap(ordered_list)

        assert 5 in om

        assert om.get(5) == 20
        om.remove_key(5)
        assert 5 not in om
Пример #4
0
 def test_floor(self):
     ordered_list = [(10, 100), (5, 20), (7, 70), (2, 50)]
     om = OrderedMap(ordered_list)
     answers = [2, 2, 2, 5, 5, 7, 7, 7, 10, 10]
     for i in range(2, 12):
         key = om.floor(i)
         self.assertTrue(key == answers[i - 2])
         mapto = om[key]
         print('find {0} is {1} --> {2}'.format(i, key, mapto))
Пример #5
0
 def _reset_hc_list(self, hc_list):
     p = Position(0)
     new_ordered_map = OrderedMap()
     for hc in hc_list:
         hc.position = p
         new_ordered_map.insert(p, hc)
         p += hc.duration
     self.ordered_map = new_ordered_map
     self._wnt_duration = Duration(p.position)
Пример #6
0
 def test_ceil(self):
     ordered_list = [(10, 100), (5, 20), (7, 70), (2, 50)]
     om = OrderedMap(ordered_list)
     answers = [2, 5, 5, 5, 7, 7, 10, 10, 10, None, None]
     for i in range(1, 12):
         key = om.ceil(i)
         self.assertTrue(key == answers[i - 1])
         mapto = None if key is None else om[key]
         print('find {0} is {1} --> {2}'.format(i, key, mapto))
Пример #7
0
 def test_insert(self):
     ordered_list = [(10, 100), (5, 20), (7, 70), (2, 50)]
     om = OrderedMap(ordered_list)
     om.insert(6, 60)
     keys = om.keys()
     last_key = -1
     for k in keys:
         print(k, om[k])
         self.assertTrue(last_key < k)
         last_key = k
Пример #8
0
 def _setup(self, transition_points):
     self.__transition_points = sorted(transition_points, key=lambda x: x[0])
     self.__domain_start = self.__transition_points[0][0]
     self.__domain_end = self.__transition_points[len(self.__transition_points) - 1][0]
     
     lin_segs = []
     for i in range(0, len(self.transition_points) - 1):
         lin_segs.append((self.transition_points[i][0],
                          LinearSegment(self.transition_points[i], self.transition_points[i + 1])))
     
     self.ordered_map = OrderedMap(lin_segs)
Пример #9
0
 def __init__(self, event_list=None):
     """
     Constructor.
     
     Args:
       event_list:  Any of None, a single Event, or a list of Events.
     """
     self.ordered_map = OrderedMap()
     
     self._successor = {}
     self._predecessor = {}
     self.__first = None
     self.__last = None
     
     if event_list:
         self.add(event_list)
Пример #10
0
    def _compute_cue_tone(old_cue_tone, tonality):
        from misc.ordered_map import OrderedMap

        tones = tonality.annotation[:-1]
        s = OrderedMap({v.placement: tones.index(v) for v in tones})

        least_index = s[min(pl for pl in s.keys())]
        nearest_pl = s.floor(old_cue_tone.placement)
        if nearest_pl is None:
            return tones[-1 if least_index == 0 else least_index -
                         1], tones[least_index]

        if nearest_pl == old_cue_tone.placement:
            return tones[s[nearest_pl]], None

        nearest_idx = s[nearest_pl]
        return tones[nearest_idx], tones[nearest_idx +
                                         1 if nearest_idx != len(tones) -
                                         1 else 0]
    def _compute_neighbor_tones(cue_tone, tone_list):
        # Find from tone_list, a tone1, tone2 with tone1<=tone<=tone2 with nearest proximity/
        from misc.ordered_map import OrderedMap

        # s : placement -> index over all tones in tone_list
        s = OrderedMap({v.placement: tone_list.index(v) for v in tone_list})

        # least_index == index of tone in tone_list mapped by least placement.
        least_index = s[min(pl for pl in s.keys())]
        # nearest_pl = s's placement key that is just below cue_tones placement
        nearest_pl = s.floor(cue_tone.placement)
        if nearest_pl is None:
            return tone_list[-1 if least_index == 0 else least_index - 1], tone_list[least_index]

        if nearest_pl == cue_tone.placement:
            return tone_list[s[nearest_pl]], None

        nearest_idx = s[nearest_pl]
        return tone_list[nearest_idx], tone_list[nearest_idx + 1 if nearest_idx != len(tone_list) - 1 else 0]
Пример #12
0
    def __init__(self,
                 tonality,
                 anchor_pitch=None,
                 anchor_value=None,
                 pitch_unit=1):
        """
        Constructor.
        :param tonality: The tonality being mapped to.
        :param anchor_pitch: A DiatonicPitch, in combo with anchor_value is a sample of the mapping.
        :param anchor_value: A numeric value that maps to anchor_pitch.
        :param pitch_unit: In the linear map of value to pitches, pitch_unit is the distance between mapping values.
        """
        self.__tonality = tonality
        self.__pitch_scale = PitchScale(self.tonality,
                                        PitchRange.create('A:0',
                                                          'C:8')).pitch_scale

        self.anchor_pitch = self.pitch_scale[0] if anchor_pitch is None else \
            DiatonicPitch.parse(anchor_pitch) if isinstance(anchor_pitch, str) else anchor_pitch

        anchor_index = self.pitch_scale.index(self.anchor_pitch)
        if anchor_index == -1:
            raise Exception(
                'Anchor pitch \'{0}\' not found in pitch scale for tonality \'{1}\''
                .format(self.anchor_pitch, self.tonality))

        self.__pitch_unit = pitch_unit

        self.anchor_value = anchor_value if anchor_value is not None else anchor_index * self.pitch_unit

        # base value should map to beginning of pitch scale!
        # recall that pitch unit maps to each pitch, making the scalar scale linear in value!
        base_value = anchor_value - anchor_index * pitch_unit

        self.value_to_pitch = OrderedMap()
        self.pitch_to_value = dict()
        for i in range(0, len(self.pitch_scale)):
            pitch = self.pitch_scale[i]
            value = base_value + i * pitch_unit
            self.value_to_pitch.insert(value, pitch)
            self.pitch_to_value[pitch] = value

        PitchRangeInterpreter.__init__(self)
Пример #13
0
    def _build_search_trees(self):
        ts_mt_list = []
        ts_time_list = []
        tempo_mt_list = []
        tempo_time_list = []
        
        for element in self.element_list:
            if element.is_tempo:
                tempo_mt_list.append((element.position, element.element))
                tempo_time_list.append((element.position_time, element.element))
            else:
                ts_mt_list.append((element.position, element.element))
                ts_time_list.append((element.position_time, element.element))
                
        self.ts_mt_map = OrderedMap(ts_mt_list)    # whole note time --> TimeSignature
        self.ts_time_map = OrderedMap(ts_time_list)   # actual time --> TimeSignature

        self.tempo_mt_map = OrderedMap(tempo_mt_list)  # whole note time to Tempo
        self.tempo_time_map = OrderedMap(tempo_time_list)   # actual time to Tempo
        
        # Build an ordered map, mapping BeatPosition --> time signature.
        ts_bp_list = []
        (position, ts) = ts_mt_list[0]
        prior_pickup = 0
        measure_tally = 0
        if self.pickup.duration > 0:
            num_beats = self.pickup.duration / ts.beat_duration.duration
            ts_bp_list.append((BeatPosition(0, ts.beats_per_measure - num_beats), ts))
            prior_pickup = num_beats
        else:
            ts_bp_list.append((BeatPosition(0, Fraction(0, 1)), ts))           
        
        for i in range(1, len(ts_mt_list)):
            (position, ts) = ts_mt_list[i]
            (prior_position, prior_ts) = ts_mt_list[i - 1]
            num_beats = (position - prior_position).duration / prior_ts.beat_duration.duration - prior_pickup
            num_measures = int(num_beats / prior_ts.beats_per_measure) + (1 if prior_pickup > 0 else 0)
            measure_tally += num_measures
            prior_pickup = 0
            ts_bp_list.append((BeatPosition(measure_tally, 0), ts))
        self.ts_bp_map = OrderedMap(ts_bp_list)    # beat position --> TimeSignature    
Пример #14
0
    def class_init():
        if DynamicsHelper.NAME_MAP is not None:
            return

        DynamicsHelper.NAME_MAP = {
            Dynamics.PPPP: 'pianissississimo',
            Dynamics.PPP: 'pianississimo0',
            Dynamics.PP: 'pianissimo',
            Dynamics.P: 'piano',
            Dynamics.MP: 'mezzo piano',
            Dynamics.MF: 'messo forte',
            Dynamics.F: 'forte',
            Dynamics.FF: 'fortissimo',
            Dynamics.FFF: 'fortississimo',
            Dynamics.FFFF: 'fortissississimo',
        }

        DynamicsHelper.DYNAMICS_VALUE_MAP = {
            Dynamics.PPPP: 16,
            Dynamics.PPP: 24,
            Dynamics.PP: 33,
            Dynamics.P: 49,
            Dynamics.MP: 64,
            Dynamics.MF: 80,
            Dynamics.F: 96,
            Dynamics.FF: 112,
            Dynamics.FFF: 120,
            Dynamics.FFFF: 127,
        }

        DynamicsHelper.DYNAMICS_LIST = [
            Dynamics.PPPP,
            Dynamics.PPP,
            Dynamics.PP,
            Dynamics.P,
            Dynamics.MP,
            Dynamics.MF,
            Dynamics.F,
            Dynamics.FF,
            Dynamics.FFF,
            Dynamics.FFFF,
        ]

        DynamicsHelper.DEFAULT_DYNAMICS = Dynamics.MP
        DynamicsHelper.DEFAULT_DYNAMICS_VELOCITY = DynamicsHelper.DYNAMICS_VALUE_MAP[
            DynamicsHelper.DEFAULT_DYNAMICS]
        DynamicsHelper.REVERSE_DYNAMICS_VELOCITY_MAP = OrderedMap({
            value: key
            for (key, value) in DynamicsHelper.DYNAMICS_VALUE_MAP.items()
        })
Пример #15
0
    def test_get(self):
        ordered_list = [(10, 100), (5, 20), (7, 70), (2, 50)]
        om = OrderedMap(ordered_list)

        a = om.get(5)
        assert a == 20

        from structure.time_signature import TimeSignature

        from timemodel.time_signature_event import TimeSignatureEvent
        tse = TimeSignatureEvent(TimeSignature(3, Duration(1, 4)), Position(0))

        d = OrderedMap([(tse.time, tse)])
        x = d.get(0)
        assert x == tse
Пример #16
0
class PiecewiseLinearFunction(UnivariateFunction):
    """
    Piecewise linear function, steps defined by a set of transition points, where values between the ordinates
    of adjacent transitions points, are based on a linear interpolation of the transition points' values.
    
    For example, (3, 5), (7, 10), (10, 14), (12, 2)  has the following steps:
       (-3, 5
       (3-7, 5),
       (7-10, 10),
       (10-12, 14),
       (12-, 2)
       
       if restrict_domain is specified (True), evaluation points must be within domain bounds.
    """

    def __init__(self, transition_points=list(), restrict_domain=False):
        """
        Constructor.
        
        Args:
        transition_points: non-empty list of ordered pairs (x, y), x is the domain, y the range.
        restrict_domain: boolean indicating if evaluation points must be in defined domain of transition points.
                         default is False.
        """
        if transition_points is None or not isinstance(transition_points, list):
            assert Exception('Illegal argument to SetwiseLinearFunction {0}'.format(transition_points))
        self.__restrict_domain = restrict_domain
        self._setup(transition_points)
        
    def _setup(self, transition_points):
        self.__transition_points = sorted(transition_points, key=lambda x: x[0])
        self.__domain_start = self.__transition_points[0][0]
        self.__domain_end = self.__transition_points[len(self.__transition_points) - 1][0]
        
        lin_segs = []
        for i in range(0, len(self.transition_points) - 1):
            lin_segs.append((self.transition_points[i][0],
                             LinearSegment(self.transition_points[i], self.transition_points[i + 1])))
        
        self.ordered_map = OrderedMap(lin_segs)
        
    @property
    def transition_points(self):
        return self.__transition_points
    
    @property
    def restrict_domain(self):
        return self.__restrict_domain
    
    @property
    def domain_start(self):
        return self.__domain_start
    
    @property
    def domain_end(self):
        return self.__domain_end

    def __call__(self, x):
        return self.eval(x)
        
    def eval(self, x):
        if len(self.transition_points) == 0:
            raise Exception("The function is undefined due to lack of transition points.")
        if self.restrict_domain:
            if x < self.domain_start or x > self.domain_end:
                raise Exception('Input {0} out of range [{1}, {2}]'.format(x, self.domain_start, self.domain_end))

        if x <= self.domain_start:
            return self.transition_points[0][1]
        if x >= self.domain_end:
            return self.transition_points[len(self.transition_points) - 1][1]
        key = self.ordered_map.floor(x)
        lin_seg = self.ordered_map.get(key)
        return lin_seg.eval(x)
    
    def add(self, transition_point):
        """
        Add a transition point to the piecewise function.
        
        Args:
          transition_point: Pair (x, y)  x, y are numerics.
        """
        new_points = list(self.transition_points)
        new_points.append(transition_point)
        self._setup(new_points)
        
    def add_and_clear_forward(self, transition_point):
        """
        Add a transition point to the piecewise function AND clear out higher (domain value) transition points.
        
        Args:
          transition_point: Pair (x, y)  x, y are numerics.
        """
        new_points = []
        elimination_value = transition_point[0]
    
        for p in self.transition_points:
            if p[0] < elimination_value:
                new_points.append(p) 
        new_points.append(transition_point)
    
        self._setup(new_points)
Пример #17
0
 def __init__(self):
     """
     Constructor.
     """
     self.ordered_map = OrderedMap()
     self._wnt_duration = Duration(0)
Пример #18
0
class HarmonicContextTrack(object):
    def __init__(self):
        """
        Constructor.
        """
        self.ordered_map = OrderedMap()
        self._wnt_duration = Duration(0)

    def __getitem__(self, position):
        """
        Get the harmonic context based on the floor of the given position.

        :param position:
        :return:
        """
        floor_position = self.ordered_map.floor(position)
        return None if floor_position is None else self.ordered_map[
            floor_position]

    def __len__(self):
        return len(self.ordered_map)

    @property
    def duration(self):
        return self._wnt_duration

    def hc_list(self):
        return [self.ordered_map[hc] for hc in self.ordered_map.keys()]

    def reset(self):
        self._reset_hc_list(self.ordered_map.value_items())

    def append(self, harmonic_context):
        """
        Append a harmonic context to the end of the track.
        It is recommended to use this call to build the track, as the key data members are not rebuilt.

        :param harmonic_context:
        :return:
        """
        last_hc = None if self.ordered_map.is_empty(
        ) else self.ordered_map.value_items()[-1]

        harmonic_context.position = Position(
            0) if last_hc is None else last_hc.position + last_hc.duration
        self.ordered_map.insert(harmonic_context.position, harmonic_context)

        self._wnt_duration += harmonic_context.duration.duration

    def append_first(self, harmonic_context):
        """
        Append a harmonic context to the beginning of the track, and shove right all existing HC's
        by the duration of the added HC.
        :param harmonic_context:
        :return:
        """
        hc_list = self.ordered_map.value_items()
        hc_list.insert(0, harmonic_context)
        self._reset_hc_list(hc_list)

    def insert(self, position_key, harmonic_context):
        """
        Insert a harmonic context at a given position.  The insertion happens after the HC that is floor(position_key).
        :param position_key:
        :param harmonic_context:
        :return:
        """
        hc_list = self.ordered_map.value_items()
        hc_target_index = self.ordered_map.floor(position_key)
        index = 0 if hc_target_index is None else hc_list.index(
            self.ordered_map[hc_target_index])
        hc_list.insert(index, harmonic_context)
        harmonic_context.position = position_key
        self._reset_hc_list(hc_list)

    def get_hc_by_position(self, position):
        return self.ordered_map[self.ordered_map.floor(position)]

    def replace(self, position_key, harmonic_context):
        if position_key not in self.ordered_map:
            raise Exception(
                'Attempt to replace a harmonic context with invalid key')
        harmonic_context.position = position_key
        self.ordered_map.insert(position_key, harmonic_context)
        self._reset_hc_list(self.ordered_map.value_items())

    def remove(self, harmonic_context):
        """
        Remove the given harmonic content.  Error if it does not exist.
        :param harmonic_context:
        :return:
        """
        if harmonic_context.position not in self.ordered_map or \
                self.ordered_map[harmonic_context.position] != harmonic_context:
            raise Exception(
                'Attempt of remove harmonic context that is not in list')
        self.ordered_map.remove_key(harmonic_context.position)
        self._reset_hc_list(self.ordered_map.value_items())

    def _reset_hc_list(self, hc_list):
        p = Position(0)
        new_ordered_map = OrderedMap()
        for hc in hc_list:
            hc.position = p
            new_ordered_map.insert(p, hc)
            p += hc.duration
        self.ordered_map = new_ordered_map
        self._wnt_duration = Duration(p.position)

    def clear(self):
        self.ordered_map = OrderedMap()
        self._wnt_duration = Duration(0)

    def __str__(self):
        l = self.hc_list()
        return '\n'.join('[{0}] {1}'.format(i, str(l[i]))
                         for i in range(0, len(self)))

    def sub_hct(self, sub_track_interval=None):
        """
        Take a sub_track of this hct.
        :param sub_track_interval: NmericInterval.  If none, the entire hct.
        :return:
        """
        sub_track_interval = NumericInterval(Fraction(0), self.duration.duration) if sub_track_interval is None else \
            sub_track_interval

        new_track = HarmonicContextTrack()
        for hc in self.hc_list():
            hc_interval = NumericInterval(
                hc.position.position,
                hc.position.position + hc.duration.duration)
            hc_intersect = hc_interval.intersection(sub_track_interval)
            if hc_intersect is not None:
                new_hc = HarmonicContext(hc.tonality, hc.chord,
                                         Duration(hc_intersect.length()),
                                         Position(hc_intersect.lower))
                new_track.append(new_hc)

        return new_track

    def reverse(self):
        new_track = HarmonicContextTrack()

        offset = Position(0)
        for hc in reversed(self.hc_list()):
            hs_prime = HarmonicContext(hc.tonality, hc.chord, hc.duration,
                                       offset)
            new_track.append(hs_prime)
            offset = offset + hc.duration

        return new_track
Пример #19
0
 def clear(self):
     self.ordered_map = OrderedMap()
     self._wnt_duration = Duration(0)
Пример #20
0
class TimeConversion(object):
    """
    Time conversion algorithms.
    1) Whole Time --> actual time
    2) actual time --> Wholec Time
    """

    def __init__(self, tempo_line, ts_line, max_position, pickup=Duration(0, 1)):
        """
        Constructor.
        
        Args:
          tempo_line: (EventSequence) of TempoEvent's
          ts_line: (EventSequence) of TimeSignatureEvent's
          max_position: Position of end of whole note time
          pickup: whole note time for a partial initial measure
          
        Assumption:
          tempo_line and ts_line cover position 0
          
        Exceptions:
          If pickup exceeds whole note time of the first time signature.
        """
        self.tempo_line = tempo_line
        self.ts_line = ts_line
        self.__max_position = max_position
        self.__pickup = pickup

        if not isinstance(max_position, Position):
            raise Exception('max_position argument must be Position not \'{0}\'.'.format(type(max_position)))
        
        # check if the pickup exceeds the first TS
        if self.ts_line is None or self.ts_line.is_empty or self.tempo_line is None or self.tempo_line.is_empty:
            raise Exception('Time Signature and Tempo sequences must be non-empty for time conversions.')
        if pickup.duration >= self.ts_line.event(0).object.beats_per_measure * \
                self.ts_line.event(0).object.beat_duration.duration:
            raise Exception(
                'pickup exceeds timing based on first time signature {0}'.format(self.ts_line.event(0).object))
        
        self._build_uniform_element_list()
        self._build_lines()
        self._build_search_trees()
        
        self.__max_time = self.position_to_actual_time(self.max_position)
        
    @property
    def max_position(self):
        return self.__max_position
    
    @property
    def pickup(self):
        return self.__pickup
        
    @property
    def max_time(self):
        return self.__max_time
        
    def _build_uniform_element_list(self):
        self.element_list = [Element(x.object, x.time) for x in self.tempo_line.sequence_list] + \
                            [Element(x.object, x.time) for x in self.ts_line.sequence_list]
        self.element_list.sort(key=lambda p: p.position)
        
    def _build_lines(self):
        """
        Compute the actual time for the tempo and time signature elements.
        """
        current_ts = None
        current_tempo = None
        current_at = 0
        last_position = None
        
        for element in self.element_list:
            if current_ts and current_tempo:
                translated_tempo = current_tempo.effective_tempo(current_ts.beat_duration)
                current_at += (element.position - last_position).duration / \
                              (current_ts.beat_duration.duration * translated_tempo) * 60 * 1000
                element.position_time = current_at
                
            if element.is_tempo:
                current_tempo = element.element
            else:
                current_ts = element.element
            last_position = element.position
            
    def _build_search_trees(self):
        ts_mt_list = []
        ts_time_list = []
        tempo_mt_list = []
        tempo_time_list = []
        
        for element in self.element_list:
            if element.is_tempo:
                tempo_mt_list.append((element.position, element.element))
                tempo_time_list.append((element.position_time, element.element))
            else:
                ts_mt_list.append((element.position, element.element))
                ts_time_list.append((element.position_time, element.element))
                
        self.ts_mt_map = OrderedMap(ts_mt_list)    # whole note time --> TimeSignature
        self.ts_time_map = OrderedMap(ts_time_list)   # actual time --> TimeSignature

        self.tempo_mt_map = OrderedMap(tempo_mt_list)  # whole note time to Tempo
        self.tempo_time_map = OrderedMap(tempo_time_list)   # actual time to Tempo
        
        # Build an ordered map, mapping BeatPosition --> time signature.
        ts_bp_list = []
        (position, ts) = ts_mt_list[0]
        prior_pickup = 0
        measure_tally = 0
        if self.pickup.duration > 0:
            num_beats = self.pickup.duration / ts.beat_duration.duration
            ts_bp_list.append((BeatPosition(0, ts.beats_per_measure - num_beats), ts))
            prior_pickup = num_beats
        else:
            ts_bp_list.append((BeatPosition(0, Fraction(0, 1)), ts))           
        
        for i in range(1, len(ts_mt_list)):
            (position, ts) = ts_mt_list[i]
            (prior_position, prior_ts) = ts_mt_list[i - 1]
            num_beats = (position - prior_position).duration / prior_ts.beat_duration.duration - prior_pickup
            num_measures = int(num_beats / prior_ts.beats_per_measure) + (1 if prior_pickup > 0 else 0)
            measure_tally += num_measures
            prior_pickup = 0
            ts_bp_list.append((BeatPosition(measure_tally, 0), ts))
        self.ts_bp_map = OrderedMap(ts_bp_list)    # beat position --> TimeSignature    

    def position_to_actual_time(self, position):
        """
        Convert a whole time position to it's actual time (in ms) from the beginning.
        
        Args:
          position: a Position in whole time.
        Returns:
          The actual time in ms for the position relative to the beginning.
          
        Note: if the position exceeds max_position, we use max_position
        """
        (tempo_mt_floor, tempo_element) = self.tempo_mt_map.floor_entry(position)
        tempo_time = self.tempo_time_map.reverse_get(tempo_element)
        
        (ts_mt_floor, ts_element) = self.ts_mt_map.floor_entry(position)
        ts_time = self.ts_time_map.reverse_get(ts_element)
        
        start_mt = max(tempo_mt_floor, ts_mt_floor)
        start_time = max(tempo_time, ts_time)
        # at this point, we have:
        #  start_mt: a whole time to start from
        #  start_time: the actual time to start from
        #  tempo_element: the current Tempo
        #  ts_element: the current TimeSignature
        
        delta_mt = min(position, self.max_position) - start_mt
        translated_tempo = tempo_element.effective_tempo(ts_element.beat_duration)
        # time = music_time / (beat_duration * tempo)
        delta_time = (delta_mt.duration / (ts_element.beat_duration.duration * translated_tempo)
                      if delta_mt > 0 else 0) * 60 * 1000
      
        return start_time + delta_time
     
    def actual_time_to_position(self, actual_time):  
        """
        Convert from an actual time (ms) position to a whole time Position
        
        Args:
          actual_time: the actual time (ms) of a position in the music
        Returns:
          the Position corresponding to the actual time.
          
        Note: if actual_time exceeds max_time, we use max_time.
        """
        (tempo_time_floor, tempo_element) = self.tempo_time_map.floor_entry(actual_time)
        tempo_mt = self.tempo_mt_map.reverse_get(tempo_element)
        (ts_time_floor, ts_element) = self.ts_time_map.floor_entry(actual_time)
        ts_mt = self.ts_mt_map.reverse_get(ts_element)
        
        start_mt = max(tempo_mt, ts_mt)
        start_time = max(tempo_time_floor, ts_time_floor)
        # at this point, we have:
        #  start_mt: a whole note time to start from
        #  start_time: the actual time to measure from
        #  tempo_element: the current Tempo
        #  ts_element: the current TimeSignature
        
        delta_time = min(actual_time, self.max_time) - start_time
        if not isinstance(delta_time, Fraction):
            delta_time = Fraction.from_float(delta_time)
        # musicTime = time * tempo * beat_duration
        # Translate tempo using the time signature beat.
        translated_tempo = tempo_element.effective_tempo(ts_element.beat_duration)
        delta_mt = (delta_time * translated_tempo * ts_element.beat_duration.duration / (60 * 1000)) \
            if delta_time > 0 else 0
        
        return start_mt + delta_mt
    
    def bp_to_position(self, beat_position):
        """
        Method to convert a beat position to a whole note time position.
        
        Args:
          beat_position: BeatPosition object given measure, beat number
        Returns:
          the whole note time position for beat_position.
          
        Exceptions:
          for improper beat_position values
        """
        (beginning_bp, ts_element) = self.ts_bp_map.floor_entry(beat_position)
        if beat_position.beat_number >= ts_element.beats_per_measure:
            raise Exception(
                'Illegal beat asked for {0}, ts has 0-{1} beats per measure.'.format(beat_position.beat_number,
                                                                                     ts_element.beats_per_measure - 1))
        
        ts_mt_floor = self.ts_mt_map.reverse_get(ts_element)
        
        delta_mt = ((beat_position.measure_number - beginning_bp.measure_number) * ts_element.beats_per_measure +
                    beat_position.beat_number - beginning_bp.beat_number) * ts_element.beat_duration.duration
        return Position(ts_mt_floor.position + delta_mt)
    
    def position_to_bp(self, position):
        """
        Method to convert a whole note time position to a beat position
        
        Args:
          position: the whole note time position
        Returns:
          the BeatPosition corresponding to the given position
        """
        (ts_mt_floor, ts_element) = self.ts_mt_map.floor_entry(position)
        
        ts_bp = self.ts_bp_map.reverse_get(ts_element)
        
        num_beats = (position - ts_mt_floor).duration / ts_element.beat_duration.duration   # - prior_pickup.duration
        num_measures = int(num_beats / ts_element.beats_per_measure)   # + (1 if prior_pickup.duration > 0 else 0)
        residual_beats = num_beats - num_measures * ts_element.beats_per_measure
        
        # add the measures and beats  to ts_bp
        beats = ts_bp.beat_number + residual_beats
        measures = ts_bp.measure_number + num_measures
        if beats >= ts_element.beats_per_measure:
            beats -= ts_element.beats_per_measure
            measures += 1
                    
        return BeatPosition(measures, beats)
Пример #21
0
class ChromaticRangeInterpreter(PitchRangeInterpreter):
    """
    Class that interprets a number as being in the chromatic range A:0-C:8,
        and computes pitches that relate to that range.
    """
    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)

    @property
    def anchor_pitch(self):
        return self.__anchor_pitch

    @property
    def anchor_value(self):
        return self.__anchor_value

    @property
    def pitch_unit(self):
        return self.__pitch_unit

    def eval_as_nearest_pitch(self, v):
        candidates = self.eval_as_pitch(v)
        if len(candidates) == 1:
            return candidates[0]
        v1 = self.pitch_to_value[candidates[0]]
        v2 = self.pitch_to_value[candidates[1]]
        if v <= (v1 + v2) / 2:
            return candidates[0]
        return candidates[1]

    def value_for(self, diatonic_pitch):
        if isinstance(diatonic_pitch, str):
            diatonic_pitch = DiatonicPitch.parse(diatonic_pitch)
            if diatonic_pitch is None:
                return None
        if diatonic_pitch not in self.pitch_to_value:
            enharmonics = diatonic_pitch.enharmonics()
            found = False
            for p in enharmonics:
                if p in self.pitch_to_value:
                    diatonic_pitch = p
                    found = True
                    break
            if not found:
                return None
        return self.pitch_to_value[
            diatonic_pitch] if diatonic_pitch in self.pitch_to_value else None

    def eval_as_pitch(self, v):
        floor_value = self.value_to_pitch.floor(v)
        ceil_value = self.value_to_pitch.ceil(v)
        if floor_value is None or ceil_value is None:
            raise ChromaticRangeInterpreterException(
                'Illegal chromatic pitch range paramger value {0}.'.format(v))
        if math.isclose(v, self.value_for(self.value_to_pitch[floor_value])):
            return [self.value_to_pitch[floor_value]]
        else:
            p1 = self.value_to_pitch[floor_value]
            p2 = self.value_to_pitch[ceil_value]
            return [p1, p2]

    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)
Пример #22
0
class EventSequence(object):
    """
    A class to collect a sequence of Event's ordered (increasing) by the Event's time value.
    The class contains the following event accounting structures:
    1) OrderedMap: ordering the events by time in a map that provides a floor() function.
    2) successor: a dict that maps events to successors.
    3) predecessor: a dict that maps events to predecessors.
    4) first: first event in the event sequence.
    5) last: last event in the event sequence.
    """

    def __init__(self, event_list=None):
        """
        Constructor.
        
        Args:
          event_list:  Any of None, a single Event, or a list of Events.
        """
        self.ordered_map = OrderedMap()
        
        self._successor = {}
        self._predecessor = {}
        self.__first = None
        self.__last = None
        
        if event_list:
            self.add(event_list)
    
    @property       
    def sequence_list(self):
        return list(self.ordered_map.get(x) for x in self.ordered_map.keys())
    
    @property
    def is_empty(self):
        return self.ordered_map.is_empty()
    
    def floor(self, time):
        return self.ordered_map.floor(time)
    
    def event(self, index):
        return self.ordered_map.get(index)
    
    def floor_event(self, time):
        floor_position = self.floor(time)
        return self.event(floor_position) if floor_position else None
    
    @property
    def first(self):
        return self.__first
    
    @property
    def last(self):
        return self.__last
    
    def add(self, new_members):
        """
        Add any of a single Event or a list of Events.
        
        Args:
          new_members: Any of a single Event or a list of events
        """
                
        if isinstance(new_members, list):
            mem_set = new_members
            inputt = [(e.time, e) for e in new_members]

        else:
            mem_set = [new_members]
            inputt = [(new_members.time, new_members)]
           
        for m in mem_set:
            if self.ordered_map.has_reverse(m):
                raise Exception('{0} already a member of sequence.'.format(m))  
            if not isinstance(m, Event):
                raise Exception('{0} is not an event.'.format(m)) 
            
        for i in inputt:
            if i[1].time not in self.ordered_map:
                self._add_successor_predecessor_maps(i[1])
            else:
                self._update_successor_predecessor_maps(i[1])
            self.ordered_map.insert(i[0], i[1])                  
        
    def remove(self, members): 
        """
        Remove any of a single Event or a list of Events already in the sequence.
        
        Args:
          members: Any of a single Event or a list of Events already in the sequence.
        """
        if isinstance(members, list):
            for member in members:
                self.remove(member)
        else:
            if not self.ordered_map.has_reverse(members):
                raise Exception('{0} not a member of sequence'.format(members))            
            self._remove_successor_predecessor_maps(members)
            self.ordered_map.remove_key(self.ordered_map.reverse_get(members))  
            
    def move_event(self, event, new_time):
        """
        Method to move event in sequence to a new time.
        
        Args:
          event: (Event) to move
          new_time: the new time setting for the event
        """
        if self.event(event.time) != event:
            raise Exception('Given event at time {0} not in sequence'.format(event.time))
        self.remove(event)
        event.time = new_time
        self.add(event)
            
    def _add_successor_predecessor_maps(self, event):
        fl_key = self.floor(event.time)
        if fl_key:
            a = self.event(fl_key)
            b = self._successor[a]  # could be None  event is between a and b
            self._successor[a] = event
            self._successor[event] = b
            self._predecessor[event] = a
            if b:
                self._predecessor[b] = event
            else:
                self.__last = event
        else:  # this event has to come first
            if self.__first:
                self._successor[event] = self.__first
                self._predecessor[self.__first] = event
                self._predecessor[event] = None
                self.__first = event
            else:
                self.__first = self.__last = event
                self._successor[event] = None
                self._predecessor[event] = None
            
    def _update_successor_predecessor_maps(self, event):
        e = self.event(event.time)
        self.remove(e)
        self._add_successor_predecessor_maps(event)
        pass
    
    def _remove_successor_predecessor_maps(self, event):
        a = self._predecessor[event]
        b = self._successor[event]
        del self._successor[event]
        del self._predecessor[event]
        if a:
            self._successor[a] = b
        else:
            self.__first = b
        if b:
            self._predecessor[b] = a
        else:
            self.__last = a
        
    def clear(self):
        self.ordered_map.clear()
        self._successor.clear()
        self._predecessor.clear()
        
    def successor(self, event):
        return self._successor[event] if event in self._successor else None
    
    def predecessor(self, event):
        return self._predecessor[event] if event in self._predecessor else None
        
    def __str__(self):
        return ', '.join(str(x) for x in self.sequence_list)
    
    def print_maps(self):
        print('---------')
        if self.__first:
            print('first={0}'.format(self.__first))
        else:
            print('first=None')
        if self.__first:
            print('last={0}'.format(self.__last))
        else:
            print('last=None')
        
        print('Successor:')
        for i in self._successor.items():
            print('   {0} --> {1}'.format(i[0].object if i[0] else 'None', i[1].object if i[1] else 'None'))

        print('Predecessor:')
        for i in self._predecessor.items():
            print('   {0} --> {1}'.format(i[0].object if i[0] else 'None', i[1].object if i[1] else 'None'))
Пример #23
0
    def test_order(self):
        ordered_list = [(10, 100), (5, 20), (7, 70), (2, 50)]
        om = OrderedMap(ordered_list)
        keys = om.keys()
        last_key = -1
        for k in keys:
            print(k, om[k])
            self.assertTrue(last_key < k)
            last_key = k

        ordered_list = [(Fraction(2, 3), 200), (Fraction(1, 10), 10),
                        (Fraction(1, 2), 50), (Fraction(1, 8), 20)]
        om = OrderedMap(ordered_list)
        keys = om.keys()
        last_key = -1
        for k in keys:
            print(k, om[k])
            self.assertTrue(last_key < k)
            last_key = k

        ordered_list = [(Position(2, 3), 200), (Position(1, 10), 10),
                        (Position(1, 2), 50), (Position(1, 8), 20)]
        om = OrderedMap(ordered_list)
        keys = om.keys()
        last_key = -Fraction(1, 1)
        for k in keys:
            print(k, om[k])
            self.assertTrue(last_key < k.position)
            last_key = k.position

        ordered_list = [(Duration(2, 3), 200), (Duration(1, 10), 10),
                        (Duration(1, 2), 50), (Duration(1, 8), 20)]
        om = OrderedMap(ordered_list)
        keys = om.keys()
        last_key = -Fraction(1, 1)
        for k in keys:
            print(k, om[k])
            self.assertTrue(last_key < k.duration)
            last_key = k.duration
Пример #24
0
class ScalarRangeInterpreter(PitchRangeInterpreter):
    """
    ScalarRangeInterpreter maps numeric values to pitches, exposed as a PitchRangeInterpreter.  The mapping is
    linear in v.
    """
    def __init__(self,
                 tonality,
                 anchor_pitch=None,
                 anchor_value=None,
                 pitch_unit=1):
        """
        Constructor.
        :param tonality: The tonality being mapped to.
        :param anchor_pitch: A DiatonicPitch, in combo with anchor_value is a sample of the mapping.
        :param anchor_value: A numeric value that maps to anchor_pitch.
        :param pitch_unit: In the linear map of value to pitches, pitch_unit is the distance between mapping values.
        """
        self.__tonality = tonality
        self.__pitch_scale = PitchScale(self.tonality,
                                        PitchRange.create('A:0',
                                                          'C:8')).pitch_scale

        self.anchor_pitch = self.pitch_scale[0] if anchor_pitch is None else \
            DiatonicPitch.parse(anchor_pitch) if isinstance(anchor_pitch, str) else anchor_pitch

        anchor_index = self.pitch_scale.index(self.anchor_pitch)
        if anchor_index == -1:
            raise Exception(
                'Anchor pitch \'{0}\' not found in pitch scale for tonality \'{1}\''
                .format(self.anchor_pitch, self.tonality))

        self.__pitch_unit = pitch_unit

        self.anchor_value = anchor_value if anchor_value is not None else anchor_index * self.pitch_unit

        # base value should map to beginning of pitch scale!
        # recall that pitch unit maps to each pitch, making the scalar scale linear in value!
        base_value = anchor_value - anchor_index * pitch_unit

        self.value_to_pitch = OrderedMap()
        self.pitch_to_value = dict()
        for i in range(0, len(self.pitch_scale)):
            pitch = self.pitch_scale[i]
            value = base_value + i * pitch_unit
            self.value_to_pitch.insert(value, pitch)
            self.pitch_to_value[pitch] = value

        PitchRangeInterpreter.__init__(self)

    @property
    def tonality(self):
        return self.__tonality

    @property
    def pitch_scale(self):
        return self.__pitch_scale

    @property
    def pitch_unit(self):
        return self.__pitch_unit

    def eval_as_nearest_pitch(self, v):
        candidates = self.eval_as_pitch(v)
        if len(candidates) == 1:
            return candidates[0]
        v1 = self.pitch_to_value[candidates[0]]
        v2 = self.pitch_to_value[candidates[1]]
        if v <= (v1 + v2) / 2:
            return candidates[0]
        return candidates[1]

    def value_for(self, diatonic_pitch):
        if isinstance(diatonic_pitch, str):
            diatonic_pitch = DiatonicPitch.parse(diatonic_pitch)
            if diatonic_pitch is None:
                return None
        return self.pitch_to_value[
            diatonic_pitch] if diatonic_pitch in self.pitch_to_value else None

    def eval_as_pitch(self, v):
        floor_value = self.value_to_pitch.floor(v)
        low_pitch = self.value_to_pitch[floor_value]
        index = self.pitch_scale.index(low_pitch)

        if index >= len(self.pitch_scale) - 1 or math.isclose(v, floor_value):
            return [low_pitch]
        return [low_pitch, self.pitch_scale[index + 1]]

    def eval_as_accurate_chromatic_distance(self, v):
        floor_value = self.value_to_pitch.floor(v)
        low_pitch = self.value_to_pitch[floor_value]
        index = self.pitch_scale.index(low_pitch)

        if index >= len(self.pitch_scale) - 1 or math.isclose(v, floor_value):
            return low_pitch.chromatic_distance
        high_pitch = self.pitch_scale[index + 1]
        return low_pitch.chromatic_distance + \
               ((v - floor_value) / (self.pitch_unit)) * \
               (high_pitch.chromatic_distance - low_pitch.chromatic_distance)
Пример #25
0
    def test_merge(self):
        ordered_list = [(10, 100), (5, 20), (7, 70), (2, 50)]
        om = OrderedMap(ordered_list)
        sup = [(3, 60), (15, 2)]
        om.merge(sup)
        self.assertTrue(3 in om.keys())
        self.assertTrue(om.get(3) == 60)
        self.assertTrue(15 in om.keys())
        self.assertTrue(om.get(15) == 2)
        self.assertTrue(5 in om.keys())

        sup = {3: 60, 15: 2}
        om.merge(sup)
        self.assertTrue(3 in om.keys())
        self.assertTrue(om.get(3) == 60)
        self.assertTrue(15 in om.keys())
        self.assertTrue(om.get(15) == 2)
        self.assertTrue(5 in om.keys())

        sup = OrderedMap({3: 60, 15: 2})
        om.merge(sup)
        self.assertTrue(3 in om.keys())
        self.assertTrue(om.get(3) == 60)
        self.assertTrue(15 in om.keys())
        self.assertTrue(om.get(15) == 2)
        self.assertTrue(5 in om.keys())
Пример #26
0
class StepwiseFunction(UnivariateFunction):
    """
    Stepwise function, steps defined by a set of transition points
    
    For example, (5, 1), (7, 3), (10, 6), (12, 8)  has the following linear segments:
    (5, 1) to (7, 3)
    (7, 3) to (10, 6)
    (10, 6) to 12, 8)
             
    if restrict_domain is specified (True), evaluation points must be within domain bounds.
    """
    def __init__(self, transition_points=None, restrict_domain=False):
        """
        Constructor.
        
        Args:
        transition_points: non-empty list of ordered pairs (x, y)
        restrict_domain: boolean indicating if evaluation points must be in defined domain of transition points.
                         default is False.
        """
        if transition_points is None:
            transition_points = list()
        if transition_points is None or not isinstance(transition_points,
                                                       list):
            assert Exception(
                'Illegal argument to SetwiseLinearFunction {0}'.format(
                    transition_points))
        self.__restrict_domain = restrict_domain
        self._setup(transition_points)

    def _setup(self, transition_points):
        self.__transition_points = sorted(transition_points,
                                          key=lambda x: x[0])
        self.__domain_start = self.__transition_points[0][0]
        self.__domain_end = self.__transition_points[
            len(self.__transition_points) - 1][0]

        self.ordered_map = OrderedMap(self.transition_points)

    @property
    def transition_points(self):
        return self.__transition_points

    @property
    def domain_start(self):
        return self.__domain_start

    @property
    def domain_end(self):
        return self.__domain_end

    @property
    def restrict_domain(self):
        return self.__restrict_domain

    def eval(self, x):
        if len(self.transition_points) == 0:
            raise Exception(
                "The function is undefined due to lack of transition points.")
        if self.restrict_domain:
            if x < self.domain_start or x > self.domain_end:
                raise Exception('Input {0} out of range [{1}, {2}]'.format(
                    x, self.domain_start, self.domain_end))
        key = self.ordered_map.floor(x)
        if key is None:
            return self.ordered_map.get(self.domain_start)
        if key == self.domain_end:
            return self.ordered_map.get(self.domain_end)
        else:
            return self.ordered_map.get(key)

    def add(self, transition_point):
        """
        Add a transition point to the stepwise function.
        
        Args:
          transition_point: Pair (x, y)  x, y are numerics.
        """
        new_points = list(self.transition_points)
        new_points.append(transition_point)
        self._setup(new_points)

    def add_and_clear_forward(self, transition_point):
        """
        Add a transition point to the stepwise function AND clear out higher (domain value) transition points.
        
        Args:
          transition_point: Pair (x, y)  x, y are numerics.
        """
        new_points = []
        elimination_value = transition_point[0]

        for p in self.transition_points:
            if p[0] < elimination_value:
                new_points.append(p)
        new_points.append(transition_point)

        self._setup(new_points)