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.")