def clone(self, time_value, is_offset=True, deferred=False):
        """
        Make a copy of this object.
        args:
            - time_value is in seconds. It's interpretation depends on

            - is_offset.  When is_offset is True time_value is added to this
            object's time position, otherwise it is assigned.

            - deferred controls whether the clone is instantiated in Reaper. Deferring
            instantiation in Reaper allows for some performance improvements.
        returns:
            - the cloned instance.
        
        """
        cloned = TempoTimeSigMarkerWrapper(self.proj, self.ptidx)
        if is_offset:
            cloned.timepos = self.timepos + time_value
            dbg("Cloning sig {} with offset {} to {}".format(self.ptidx,
                                                      time_value, 
                                                      cloned.timepos))
        else:
            dbg("Cloning sig {} without offset at {}".format(self.ptidx, time_value))
            cloned.timepos = time_value

        cloned.timesig_num = self.timesig_num
        cloned.timesig_denom = self.timesig_denom
        cloned.bpm = self.bpm
        cloned.lineartempo = self.lineartempo

        cloned.ptidx = -1
        assert cloned.timepos >= 0.0
        if not deferred:
            cloned.set(use_timepos=True)
        return cloned
 def remove(self):
     """
     Tell reaper to delete this marker. Does not delete this Python object,
     however.
     """
     ret = RPR_DeleteTempoTimeSigMarker(self.proj, self.ptidx)
     if ret:
         dbg("Id {} deleted".format(self.ptidx))
     else:
         dbg("Failed deleting Id {}.".format(self.ptidx))
 def create(self):
     """  Create a new marker with specified timepos and bpm """
     ok = RPR_SetTempoTimeSigMarker(self.proj, -1, self.timepos,
             self.measurepos,
             self.beatpos,
             self.bpm,
             self.timesig_num,
             self.timesig_denom,
             self.lineartempo,
             )
     if not ok:
         raise ValueError("Couldn't create tempo time sig marker")
     else:
         dbg("Created marker ({}/{} {}) at timepos {}".format(self.timesig_num,
                                                              self.timesig_denom,
                                                              self.bpm,
                                                              self.timepos))
def getNonRedundantSigTimes(sigd):
    """
    Return a list of time positions in ascending order that correspond
    to signatures that are not duplicates of their immmediate predecessors.

    args:
        - sigd : a dictionary of TempoTimeSigMarkerWrappers with time
                 positions as keys.

    """
    ## list the keys in reverse order
    sigtimes_r = sorted(sigd.keys(), reverse=True)
    ## Init a list for the non-redundant keys
    nrkeys = []
    ## check each sig for equivalence with its predecessor.
    for i, t in enumerate(sigtimes_r[0:-1]):
        current = sigd[sigtimes_r[i]]
        previous = sigd[sigtimes_r[i+1]]
        if not equivalentSigs(previous, current):
            dbg("Sig at {} is non-redundant".format(t))
            nrkeys.insert(0,t)
    return nrkeys        
def removeRedundantSigs():
    """
    Remove all tempo time sigs that are duplicates of  the sig immediately
    preceding.
    """
     ## TempoTimeSigMarkers
    nsig = RPR_CountTempoTimeSigMarkers(0)
    dbg("Entering removeRedundantSigs()")
    dbg("{} time signatures in project".format(nsig))
    sigids = range(nsig)
    proj = 0  ## current project

    siglist = []
    for sigid in sigids:
        sig = TempoTimeSigMarkerWrapper(proj, sigid)
        siglist.append(sig)

    ilast = len(siglist) - 1
    while ilast > 0:
        dbg("Checking sig {}".format(ilast))
        if equivalentSigs(siglist[ilast], siglist[ilast - 1]):
            dbg("Removing sig {}".format(ilast))
            siglist[ilast].remove()
        ilast -= 1
 def message(self, obj):
     elapsed = time.time() - self.start
     msg = "{}: {}".format(elapsed, obj)
     dbg(msg)
     return msg
    def replicate(self, t0, ndups, nbetween=0):
        """
        Make 0 or more copies of an item and preserve the surrounding meter
        positions and tempi.

        Details of what this method does:

            Move item to t0 and follow it with ndups copies. Include outtime
            after original and all copies.  Prepend nbetween full measures +
            intime to all copies.

            Replicate all tempo time signature marker in original and copies.
            Make sure that the moved original begins with the correct tempo and
            time sig.  Ditto for each copy starting with the incount.

            The resulting sequence for each item looks like: betweentime intime
            orig outtime [ [ betweentime intime dup outtime] ... ]

            Return t0 + sum of all time added such that the returned time
            corresponds to the end of the last outtime.

        """


        '''
        First, find all the tempo time sig markers in the item.item and locate
        the marker for the sig in effect at the beginning of the item. The
        latter will be used for the lead-in count.
        '''

        itemsigs = []
        insig = self.tempotimesiglist[0] # earliest possible
        for sig in self.tempotimesiglist:
            if self.pos <= sig.timepos < (self.pos + self.length):
                dbg("Sig {} is in this item".format(sig.ptidx))
                itemsigs.append(sig)
                #sig.dump()
            if sig.timepos <= self.pos + .001:
                insig = sig
        incountsigd = {}

        #dbg("\nIncount sig info:")
        #incountsig.dump()
        #dbg("")

        # select the item (so we can use ApplyNudge())
        RPR_SetMediaItemSelected(self.iid, True)

        # Caculate destination time position
        t = t0
        dbg("Entering replicate() with t={}".format(t))
        incountsigd[t] = (TempoTimeSigMarkerWrapper(self.proj, None,
                timepos = t,
                bpm = insig.bpm,
                num = insig.timesig_num,
                denom = insig.timesig_denom))


        betweentime = nbetween * self.poscml * 60./self.posbpm
        dbg("betweentime = {}".format(betweentime))
        t += betweentime + self.intime

        # Move the item if need be
        if t > self.pos:
            RPR_SetMediaItemInfo_Value(self.iid, "D_POSITION", t)
            dbg('Item moved to {}'.format(t))

        # copy the tempo time markers to the new location.
        for sig in itemsigs:
            newpos = t
            incountsigd[t] = sig.clone(newpos - self.pos, deferred=True)

        # Advance to end of item
        t += self.length
        t += self.outtime

        # Apppend the requested number of duplicates.
        for _ in range(ndups):
            # Insert a tempo time at start of incount measure.
            incountsigd[t] = (TempoTimeSigMarkerWrapper(self.proj, None,
                timepos = t,
                bpm = insig.bpm,
                num = insig.timesig_num,
                denom = insig.timesig_denom))

            dbg("incount marker position = {}".format(t))

            # Advance to beginning of new item position.
            t += betweentime + self.intime

            # Clone the tempo time sigs
            for sig in itemsigs:
                newpos = t
                incountsigd[t] = sig.clone(newpos - self.pos, deferred=True)

            # Compute the offset for duplication
            nudge = self.length 
            nudge += self.outtime
            nudge += betweentime
            nudge += self.intime

            # Duplicate the item using ApplyNudge and the flags
            # assigned below. See API doc for more info about args
            # to ApplyNudge().
            fbyvalue = 0
            fduplicate = 5
            fseconds = 1
            freverse = False
            RPR_ApplyNudge(self.proj, fbyvalue, fduplicate, fseconds,
                           nudge, freverse, 1)
            
            dbg("Item duped offset by {}".format(nudge))

            # Advance to end of last measure in item
            t += self.length + self.outtime

        dbg("")  # blank line in console log

        # Unselect the item
        RPR_SetMediaItemSelected(self.iid, False)

        # Return end time for last dup so we can use it as
        # the start of the next item to be processed.
        return t, incountsigd
 def dump(self):
     """ Neatly print attributes """
     dbg("proj = {}".format(self.proj))
     dbg("iid = {}".format(self.iid))
     dbg("pos = {}".format(self.pos))
     dbg("length = {}".format(self.length))
     dbg("posbeats = {}".format(self.posbeats))
     dbg("poscml = {}".format(self.poscml))
     dbg("poscdenom = {}".format(self.poscdenom))
     dbg("posbpm = {}".format(self.posbpm))
     dbg("endbeats = {}".format(self.endbeats))
     dbg("endcml = {}".format(self.endcml))
     dbg("endcdenom = {}".format(self.endcdenom))
     dbg("endbpm = {}".format(self.endbpm))
     dbg("intime = {}".format(self.intime))
     dbg("outtime = {}".format(self.outtime))
 def dump(self):
     """ Print neatly to console """
     dbg("retval = {}".format(self.retval))
     dbg("proj = {}".format(self.proj))
     dbg("ptidx = {}".format(self.ptidx))
     dbg("timepos = {}".format(self.timepos))
     dbg("measurepos = {}".format(self.measurepos))
     dbg("beatpos = {}".format(self.beatpos))
     dbg("bpm = {}".format(self.bpm))
     dbg("timesig_num = {}".format(self.timesig_num))
     dbg("timesig_denom = {}".format(self.timesig_denom))
     dbg("lineartempo = {}".format(self.lineartempo))
def run():
    """
    The toplevel function for this script. Performs the following actions:
    1.  Gather info about the selected media items and tempo/time markers in
        the project.
    2.  Disable UI updates while operating.
    3.  Remove existing tempo/time markers.
    4.  Unselect media items (so we can select each one sequentially).
    5.  Start at the beginning time of the leftmost selected item.
    6.  Replicate each item with ndups copies after it.
    7.  Enable UI updates and update the Arrange window.

    NOTE: This script will not work correctly unless the timebase is set to
    "time" for  items AND tempo time sig markers.  See the File: Project Settings
    dialog to control these items.
    """
    uin = userInputs("Parameters", ndups=1, nbetween=1)
    if uin is None:
        dbg("Cancelled")
        return
    elif uin is False:
        # Bad input
        return
    elif uin.ndups < 0:
        dbg("Can't have negative number of duplicates!")
        return
    elif uin.nbetween < 0:
        dbg("Can't have negative number of bars between items!")
        return
    else:
        ndups = uin.ndups
        nbetween = uin.nbetween

    '''
    Initialize a run timer so we can see how long various parts
    of the processing require.
    '''
    tmr = RunTimer().message
    tmr("Starting run ...")


    '''
    Gather a list of Tempo Time Signature markers in the project
    and create wrappers for them.
    '''
    nsig = RPR_CountTempoTimeSigMarkers(0)
    dbg("{} time signatures in project".format(nsig))
    sigids = range(nsig)
    proj = 0  ## current project

    siglist = []
    for sigid in sigids:
        sig = TempoTimeSigMarkerWrapper(proj, sigid)
        siglist.append(sig)
    tmr("Finished getting siglist")

    '''
    Make a list of selected media items.  We begin with a count of
    the number of selected items.
    '''
    nitems = RPR_CountSelectedMediaItems(0)
    dbg("{} media items selected".format(nitems))
    itemids = range(nitems)
    '''
    Note: the above approach works because the Reaper function
    GetSelectedMediaItem() takes a zero-based index into the set of currently
    selected items as one of its argumemts.  
    See MediaItemRreplicator.__init__() to see how this is used.
    '''

    '''
    Create a list of the selected media items wrapped in MediaItemReplicator instances.
    See below in this file for the class definition.
    '''
    items = []
    for itemid in itemids:
        item = MediaItemReplicator(proj, itemid, siglist)
        #item.dump()
        items.append(item)
    
    '''
    Make a list of the Reaper MediaItem references for each item in
    the user's selections.  
    '''
    selectediids = [item.iid for item in items]

    '''
    We now have a list of current MediaItemReplicator instances for each
    selected media item.  We don't currently handle multiple tracks, so check
    and continue only if all items are in the same track.
    '''

    tracks = [ RPR_GetMediaItem_Track(i.iid) for i in items ]
    dbg(tracks)
    if len(set(tracks)) != 1:
        dbg("Usage Error: All selected items must be in the same track.")
        return
    else:
        track = tracks[0] 

    tmr("Finished setting up selected media item list.")    

    '''
    Now create a list of MediaItemReplicators for ALL items in the track. We'll
    use this to make decisions about how to handle items in the track that are
    not selected.  The ones to the left of the first selection will be left unchanged
    and any that occur after the first selection will be shifted right as needed.
    '''
    ntrackitems = RPR_CountTrackMediaItems(track)
    trackitems = []
    for titemid in range(ntrackitems):
        itemref = RPR_GetTrackMediaItem(track, titemid)
        item = MediaItemReplicator(proj, itemref, siglist, id_is_index=False)
        trackitems.append(item)

    tmr("Finished getting list of ALL media items.")

    '''
    We're ready to start moving and replicating items. Begin by freezing the 
    UI. It's not strictly necessary, but gives better performance.
    '''
    RPR_PreventUIRefresh(1)

    '''
    Delete the existing tempo time sig markers.  We're going to recreate them
    as we go in new locations.
    '''
    for sig in reversed(siglist):
        sig.remove()
    tmr("Finished removing tempo markers.")

    '''
    Unselect all the media items. Our replicator method uses RPR_ApplyNudge()
    which operates on selected items.  We want to have only one item selected
    at a time.
    '''
    RPR_SelectAllMediaItems(0, False)

    '''
    The variable endt represents the ending time position of the item just
    processed. It may or may not include some outtime to finish on a measure
    end.  The important thing is that it represents the earliest time allowed
    for the start of the next item to be processed.
    '''
    endt = 0.0

    '''
    Init a dictionary of tempo time sigs with time position as keys.
    '''
    incountsigd = {}
    for item in trackitems:
        item.dump()
        if item.iid in selectediids:
            endt, _incountsigd = item.replicate(endt, 
                                   ndups, 
                                   nbetween=nbetween)
        else:
            endt, _incountsigd = item.replicate(endt, 
                                   0, 
                                   nbetween = nbetween)

        incountsigd.update(_incountsigd)

    tmr("Finished item processing")

    '''
    Create the incount signatures. These are tempo time signature markers
    that start the count-in before each item. These could have been created
    during the processing, but are deferred until now.  Deferral seemed necessary
    at one point during development, but that turned out not to be the cause of
    the problem I was trying to fix.  Eventually, it may be worthwhile to 
    move the creation back into the replicate() method.
    '''
    sigtimes = getNonRedundantSigTimes(incountsigd)
    tmr("Finished getting non-redundant sig times.")

    for t in sigtimes:
        incountsigd[t].create()

    tmr("Finished creating incount sigs.")

    # clean up the sigs
    #removeRedundantSigs()

    #tmr("Finished removing redundant sigs.")

    # Unfreeze the UI
    RPR_PreventUIRefresh(-1)
    RPR_UpdateArrange()

    tmr("Run completed.")