diff --git a/CHANGELOG b/CHANGELOG index fc693d3..ee4b2ee 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,13 @@ 2021-04-15 Version 2.1.1 Add status bar to explain possible user actions (like "use shift + mousewheel to transpose measure") Streamline mousewheel behaviour in song editor. It now always scrolls without shift / alt key, no more accidental transpositions. +Add non-destructive step delay for individual measures. Shift an individual measures forwards or backwards in time, within the boundaries of the measure. +Add track-switch to choose step-delay wrap-around behaviour, if delayed notes end up outside the pattern +Add non-destructiv pattern augmentation/scaling. Make an individual measure shorter or longer overall, with the same GUI as transpositions. +Add track-switch to choose repeat-to-fill behaviour if scaling a pattern shorter, which creates empty space. + Fix wrong playback cursor speed. +Fix small GUI drawing issues 2021-02-15 Version 2.1.0 Full Undo/Redo diff --git a/engine/api.py b/engine/api.py index bdad910..a6099bf 100644 --- a/engine/api.py +++ b/engine/api.py @@ -783,6 +783,8 @@ def trackMergeCopyFrom(sourceTrackId, targetTrackId): targetTrack.structure = targetTrack.structure.union(sourceTrack.structure) targetTrack.whichPatternsAreScaleTransposed.update(sourceTrack.whichPatternsAreScaleTransposed) targetTrack.whichPatternsAreHalftoneTransposed.update(sourceTrack.whichPatternsAreHalftoneTransposed) + targetTrack.whichPatternsAreStepDelayed.update(sourceTrack.whichPatternsAreStepDelayed) + targetTrack.whichPatternsHaveAugmentationFactor.update(sourceTrack.whichPatternsHaveAugmentationFactor) targetTrack.buildTrack() updatePlayback() callbacks._trackStructureChanged(targetTrack) @@ -801,6 +803,7 @@ def trackPatternReplaceFrom(sourceTrackId, targetTrackId): callbacks._patternChanged(targetTrack) #Transpositions and Modal Shifts +#StepDelay and AugmentationFactor def _setSwitchesScaleTranspose(trackId, whichPatternsAreScaleTransposed): """For undo. Used by all functions as entry point, then calls itself for undo/redo. @@ -816,8 +819,9 @@ def _setSwitchesScaleTranspose(trackId, whichPatternsAreScaleTransposed): callbacks._trackStructureChanged(track) -def setSwitchScaleTranspose(trackId, position, transpose): - """Scale transposition is flipped. lower value means higher pitch""" +def setSwitchScaleTranspose(trackId, position:int, transpose:int): + """Scale transposition is flipped. lower value means higher pitch. + Default value is 0.""" track = session.data.trackById(trackId) if not track: return session.history.register(lambda tr=trackId, v=track.whichPatternsAreScaleTransposed.copy(): _setSwitchesScaleTranspose(trackId, v), descriptionString="Set Modal Shift") @@ -841,8 +845,10 @@ def _setSwitchHalftoneTranspose(trackId, whichPatternsAreHalftoneTransposed): updatePlayback() callbacks._trackStructureChanged(track) -def setSwitchHalftoneTranspose(trackId, position, transpose): - """Halftone transposition is not flipped. Higher value means higher pitch""" +def setSwitchHalftoneTranspose(trackId, position:int, transpose:int): + """Halftone transposition is not flipped. Higher value means higher pitch + Default value is 0. + """ track = session.data.trackById(trackId) if not track: return session.history.register(lambda tr=trackId, v=track.whichPatternsAreHalftoneTransposed.copy(): _setSwitchHalftoneTranspose(trackId, v), descriptionString="Set Half Tone Shift") @@ -852,8 +858,61 @@ def setSwitchHalftoneTranspose(trackId, position, transpose): callbacks._trackStructureChanged(track) return True +def _setSwitchStepDelay(trackId, whichPatternsAreStepDelayed): + """For undo. Used by all functions as entry point, then calls itself for undo/redo. + whichPatternsAreStepDelayed is a dicts of int:int which we can copy with .copy()""" + track = session.data.trackById(trackId) + if not track: return + + session.history.register(lambda tr=trackId, v=track.whichPatternsAreStepDelayed.copy(): _setSwitchStepDelay(trackId, v), descriptionString="Set Step Delay") + + track.whichPatternsAreStepDelayed = whichPatternsAreStepDelayed #restore data + track.buildTrack() + updatePlayback() + callbacks._trackStructureChanged(track) + +def setSwitchStepDelay(trackId, position:int, delay:int): + """Public entry function for _setSwitchStepDelay. + Default value is 0""" + track = session.data.trackById(trackId) + if not track: return + session.history.register(lambda tr=trackId, v=track.whichPatternsAreStepDelayed.copy(): _setSwitchStepDelay(trackId, v), descriptionString="Set Step Delay") + track.whichPatternsAreStepDelayed[position] = delay + track.buildTrack() + updatePlayback() + callbacks._trackStructureChanged(track) + return True + + +def _setSwitchAugmentationsFactor(trackId, whichPatternsAreStepDelayed): + """For undo. Used by all functions as entry point, then calls itself for undo/redo. + whichPatternsHaveAugmentationFactor is a dicts of int:float which we can copy with .copy()""" + track = session.data.trackById(trackId) + if not track: return + + session.history.register(lambda tr=trackId, v=track.whichPatternsHaveAugmentationFactor.copy(): _setSwitchAugmentationsFactor(trackId, v), descriptionString="Set Augmentation Factor") + + track.whichPatternsHaveAugmentationFactor = whichPatternsHaveAugmentationFactor #restore data + track.buildTrack() + updatePlayback() + callbacks._trackStructureChanged(track) + +def setSwitchAugmentationsFactor(trackId, position:int, factor:float): + """Public entry function for _setSwitchAugmentationsFactor. + Default value is 1.0""" + track = session.data.trackById(trackId) + if not track: return + session.history.register(lambda tr=trackId, v=track.whichPatternsHaveAugmentationFactor.copy(): _setSwitchAugmentationsFactor(trackId, v), descriptionString="Set Augmentation Factor") + track.whichPatternsHaveAugmentationFactor[position] = factor + track.buildTrack() + updatePlayback() + callbacks._trackStructureChanged(track) + return True + + + def _registerHistoryWholeTrackSwitches(track): - """This is used by insertSilence, clearSwitchGroupTranspositions etc. + """This is used by insertSilence, clearSwitchGroupModifications etc. It assumes to run inside the context: with session.history.sequence(""): """ @@ -862,6 +921,8 @@ def _registerHistoryWholeTrackSwitches(track): session.history.register(lambda trId=trackId, v=track.structure.copy(): _setTrackStructure(trId, v, "Change Group"), descriptionString="Change Group") session.history.register(lambda trId=trackId, v=track.whichPatternsAreScaleTransposed.copy(): _setSwitchesScaleTranspose(trId, v), descriptionString="Set Modal Shift") session.history.register(lambda trId=trackId, v=track.whichPatternsAreHalftoneTransposed.copy(): _setSwitchHalftoneTranspose(trId, v), descriptionString="Set Half Tone Shift") + session.history.register(lambda trId=trackId, v=track.whichPatternsAreStepDelayed.copy(): _setSwitchStepDelay(trId, v), descriptionString="Set Step Delay") + session.history.register(lambda trId=trackId, v=track.whichPatternsHaveAugmentationFactor.copy(): _setSwitchAugmentationsFactor(trId, v), descriptionString="Set Augmentation Factor") def insertSilence(howMany:int, beforeMeasureNumber:int): @@ -880,6 +941,8 @@ def insertSilence(howMany:int, beforeMeasureNumber:int): track.structure = set( (switch + thisTrackHowMany if switch >= thisTrackWhere else switch) for switch in track.structure ) track.whichPatternsAreScaleTransposed = { (k+thisTrackHowMany if k >= thisTrackWhere else k):v for k,v in track.whichPatternsAreScaleTransposed.items() } track.whichPatternsAreHalftoneTransposed = { (k+thisTrackHowMany if k >= thisTrackWhere else k):v for k,v in track.whichPatternsAreHalftoneTransposed.items() } + track.whichPatternsAreStepDelayed = { (k+thisTrackHowMany if k >= thisTrackWhere else k):v for k,v in track.whichPatternsAreStepDelayed.items() } + track.whichPatternsHaveAugmentationFactor = { (k+thisTrackHowMany if k >= thisTrackWhere else k):v for k,v in track.whichPatternsHaveAugmentationFactor.items() } callbacks._trackStructureChanged(track) callbacks._dataChanged() #register undo @@ -906,6 +969,10 @@ def duplicateSwitchGroup(startMeasureForGroup:int, endMeasureExclusive:int): track.whichPatternsAreScaleTransposed[switch] = track.whichPatternsAreScaleTransposed[switch-thisGroupSize] if switch-thisGroupSize in track.whichPatternsAreHalftoneTransposed: track.whichPatternsAreHalftoneTransposed[switch] = track.whichPatternsAreHalftoneTransposed[switch-thisGroupSize] + if switch-thisGroupSize in track.whichPatternsAreStepDelayed: + track.whichPatternsAreStepDelayed[switch] = track.whichPatternsAreStepDelayed[switch-thisGroupSize] + if switch-thisGroupSize in track.whichPatternsHaveAugmentationFactor: + track.whichPatternsHaveAugmentationFactor[switch] = track.whichPatternsHaveAugmentationFactor[switch-thisGroupSize] callbacks._trackStructureChanged(track) session.data.buildAllTracks() @@ -931,11 +998,14 @@ def exchangeSwitchGroupWithGroupToTheRight(startMeasureForGroup:int, endMeasureE tempStructure = set() #integers tempScaleTransposed = dict() #position:integers tempHalfToneTransposed = dict() #position:integers + tempStepDelayed = dict() #position:integers + tempAugmentedFactor = dict() #position:floats #Remember for later testing lenStructure = len(track.structure) lenScaleTransposed = len(track.whichPatternsAreScaleTransposed.keys()) - lenHalfToneTransposed = len(track.whichPatternsAreHalftoneTransposed.keys()) + lenStepDelayed = len(track.whichPatternsAreStepDelayed.keys()) + lenAugmentedFactor = len(track.whichPatternsHaveAugmentationFactor.keys()) #First move right group into a temporary buffer to have it out of the way for switch in range(thisTrackStartMeasure+groupSize, thisTrackEndMeasure+groupSize): #switch is a number @@ -948,6 +1018,12 @@ def exchangeSwitchGroupWithGroupToTheRight(startMeasureForGroup:int, endMeasureE if switch in track.whichPatternsAreHalftoneTransposed: tempHalfToneTransposed[switch] = track.whichPatternsAreHalftoneTransposed[switch] del track.whichPatternsAreHalftoneTransposed[switch] + if switch in track.whichPatternsAreStepDelayed: + tempStepDelayed[switch] = track.whichPatternsAreStepDelayed[switch] + del track.whichPatternsAreStepDelayed[switch] + if switch in track.whichPatternsHaveAugmentationFactor: + tempAugmentedFactor[switch] = track.whichPatternsHaveAugmentationFactor[switch] + del track.whichPatternsHaveAugmentationFactor[switch] #Now move current group to the right, which is now empty. for switch in range(thisTrackStartMeasure, thisTrackEndMeasure): #switch is a number @@ -960,6 +1036,14 @@ def exchangeSwitchGroupWithGroupToTheRight(startMeasureForGroup:int, endMeasureE if switch in track.whichPatternsAreHalftoneTransposed: track.whichPatternsAreHalftoneTransposed[switch+groupSize] = track.whichPatternsAreHalftoneTransposed[switch] del track.whichPatternsAreHalftoneTransposed[switch] + if switch in track.whichPatternsAreStepDelayed: + track.whichPatternsAreStepDelayed[switch+groupSize] = track.whichPatternsAreStepDelayed[switch] + del track.whichPatternsAreStepDelayed[switch] + if switch in track.whichPatternsHaveAugmentationFactor: + track.whichPatternsHaveAugmentationFactor[switch+groupSize] = track.whichPatternsHaveAugmentationFactor[switch] + del track.whichPatternsHaveAugmentationFactor[switch] + + #Move old right-group into its new place for sw in tempStructure: @@ -968,6 +1052,10 @@ def exchangeSwitchGroupWithGroupToTheRight(startMeasureForGroup:int, endMeasureE track.whichPatternsAreScaleTransposed[stPos-groupSize] = stVal for htPos, htVal in tempScaleTransposed.items(): track.whichPatternsAreHalftoneTransposed[hPos-groupSize] = htVal + for sDPos, sDVal in tempStepDelayed.items(): + track.whichPatternsAreStepDelayed[sDPos-groupSize] = sDVal + for aFPos, aFVal in tempAugmentedFactor.items(): + track.whichPatternsHaveAugmentationFactor[aFPos-groupSize] = aFVal callbacks._trackStructureChanged(track) @@ -975,13 +1063,15 @@ def exchangeSwitchGroupWithGroupToTheRight(startMeasureForGroup:int, endMeasureE assert lenStructure == len(track.structure), (lenStructure, len(track.structure)) assert lenScaleTransposed == len(track.whichPatternsAreScaleTransposed.keys()), (lenScaleTransposed, len(track.whichPatternsAreScaleTransposed.keys())) assert lenHalfToneTransposed == len(track.whichPatternsAreHalftoneTransposed.keys()), (lenHalfToneTransposed, len(track.whichPatternsAreHalftoneTransposed.keys())) + assert lenStepDelayed == len(track.whichPatternsAreStepDelayed.keys()), (lenStepDelayed, len(track.whichPatternsAreStepDelayed.keys())) + assert lenAugmentedFactor == len(track.whichPatternsHaveAugmentationFactor.keys()), (lenAugmentedFactor, len(track.whichPatternsHaveAugmentationFactor.keys())) callbacks._dataChanged() #register undo session.data.buildAllTracks() updatePlayback() -def clearSwitchGroupTranspositions(startMeasureForGroup:int, endMeasureExclusive:int): +def clearSwitchGroupModifications(startMeasureForGroup:int, endMeasureExclusive:int): """startMeasureForGroup and endMeasureExclusive are in the global, un-multiplied measure counting format.""" with session.history.sequence("Clear all Group Transpositions"): @@ -994,6 +1084,10 @@ def clearSwitchGroupTranspositions(startMeasureForGroup:int, endMeasureExclusive del track.whichPatternsAreScaleTransposed[switch] if switch in track.whichPatternsAreHalftoneTransposed: del track.whichPatternsAreHalftoneTransposed[switch] + if switch in track.whichPatternsAreStepDelayed: + del track.whichPatternsAreStepDelayed[switch] + if switch in track.whichPatternsHaveAugmentationFactor: + del track.whichPatternsHaveAugmentationFactor[switch] callbacks._trackStructureChanged(track) callbacks._dataChanged() #register undo session.data.buildAllTracks() @@ -1037,6 +1131,25 @@ def deleteSwitches(howMany, fromMeasureNumber): #else: #discard all in range to delete track.whichPatternsAreHalftoneTransposed = new_halftoneTransposed + new_stepDelayed = dict() + for k,v in track.whichPatternsAreStepDelayed.items(): + if k < thisTrackWhere: + new_stepDelayed[k] = v + elif k >= thisTrackWhere+thisTrackHowMany: #like a text editor let gravitate left into the hole left by the deleted range + new_stepDelayed[k-thisTrackHowMany] = v + #else: #discard all in range to delete + track.whichPatternsAreStepDelayed = new_stepDelayed + + new_augmentFactor = dict() + for k,v in track.whichPatternsHaveAugmentationFactor.items(): + if k < thisTrackWhere: + new_augmentFactor[k] = v + elif k >= thisTrackWhere+thisTrackHowMany: #like a text editor let gravitate left into the hole left by the deleted range + new_augmentFactor[k-thisTrackHowMany] = v + #else: #discard all in range to delete + track.whichPatternsHaveAugmentationFactor = new_augmentFactor + + callbacks._trackStructureChanged(track) callbacks._dataChanged() #register undo diff --git a/engine/pattern.py b/engine/pattern.py index 65f2124..32cc8e1 100644 --- a/engine/pattern.py +++ b/engine/pattern.py @@ -270,15 +270,18 @@ class Pattern(object): self.averageVelocity = int(median(n["velocity"] for n in self.data)) if self.data else DEFAULT_VELOCITY self._exportCacheVersion += 1 #used by build pattern for its cache hash - def buildPattern(self, scaleTransposition, halftoneTransposition, howManyUnits, whatTypeOfUnit, subdivisions, patternLengthMultiplicator): + def buildPattern(self, scaleTransposition, halftoneTransposition, howManyUnits, whatTypeOfUnit, subdivisions, patternLengthMultiplicator, stepDelay, augmentationFactor): """return a cbox pattern ready to insert into a cbox clip. - This is the function to communicate with the outside, e.g. the track. - All ticks are relativ to the pattern start + This is called for every measure in a track. If you change the pattern it is called + for each existing modification once (transposition, stepDelay, AugmentFactor) + This is the function to communicate with the outside, e.g. the track. We cache internally, but for the outside it looks like we generate a new pattern each time on each little note update or transposition change. + All ticks are relative to the pattern start + The cache restores the cbox-pattern, not the note configuration in our python data. It is used for placing the same pattern into multiple measures. But _not_ to return to a note configuration we already had once. If you change the pattern itself the cache is cleared. @@ -295,6 +298,8 @@ class Pattern(object): scale (as tuple so it is hashable) self._exportCacheVersion patternLengthMultiplicator + stepDelay + augmentationFactor _cachedTransposedScale is updated with self.scale changes and therefore already covered. @@ -321,12 +326,14 @@ class Pattern(object): api.swingPercentToAbsolute """ - cacheHash = (scaleTransposition, halftoneTransposition, howManyUnits, whatTypeOfUnit, subdivisions, tuple(self.scale), self._exportCacheVersion, patternLengthMultiplicator) + cacheHash = (scaleTransposition, halftoneTransposition, howManyUnits, whatTypeOfUnit, subdivisions, tuple(self.scale), self._exportCacheVersion, patternLengthMultiplicator, stepDelay, augmentationFactor) try: return self._builtPatternCache[cacheHash] except KeyError: pass + #If uncertain if the cache works print cacheHash to see what is really different. This function is called more than once per pattern sometimes, which is correct. + oneMeasureInTicks = howManyUnits * whatTypeOfUnit oneMeasureInTicks /= subdivisions #subdivisions is 1 by default. bigger values mean shorter durations, which is compensated by the user setting bigger howManyUnits manually. oneMeasureInTicks = int(oneMeasureInTicks) * patternLengthMultiplicator @@ -336,7 +343,14 @@ class Pattern(object): shuffle = int(self.parentTrack.parentData.swing * whatTypeOfUnit) for noteDict in self.exportCache: - index = noteDict["index"] + if self.parentTrack.stepDelayWrapAround: + index = (noteDict["index"] + stepDelay) % howManyUnits + assert index >= 0, index + else: + index = noteDict["index"] + stepDelay + if index < 0 or index >= howManyUnits: + continue #skip lost step + startTick = index * whatTypeOfUnit endTick = startTick + noteDict["factor"] * whatTypeOfUnit startTick /= subdivisions @@ -349,12 +363,20 @@ class Pattern(object): else: #off beats startTick += shuffle - startTick = int(startTick) - endTick = int(endTick) + startTick = int(startTick * augmentationFactor) + endTick = int(endTick * augmentationFactor) + + #Prevent augmented notes to start and hang when exceeding the pattern-length + if startTick >= oneMeasureInTicks-1: #-1 is important!!! Without it we will get hanging notes with factor 1.333 + assert augmentationFactor > 1.0, augmentationFactor #can only happen for augmented patterns + continue #do not create a note, at all if endTick > oneMeasureInTicks: endTick = oneMeasureInTicks #all note off must end at the end of the pattern - noteDict["exceedsPlayback"] = True + if endTick / augmentationFactor > oneMeasureInTicks: #this is only for the visuals, the GUI. For them it did not exceed, it displays the original step. + noteDict["exceedsPlayback"] = True + else: + noteDict["exceedsPlayback"] = False else: noteDict["exceedsPlayback"] = False diff --git a/engine/track.py b/engine/track.py index 7681078..c98c051 100644 --- a/engine/track.py +++ b/engine/track.py @@ -45,7 +45,12 @@ class Track(object): #injection at the bottom of this file! color:str=None, whichPatternsAreScaleTransposed:Dict[int,int]=None, whichPatternsAreHalftoneTransposed:Dict[int,int]=None, - simpleNoteNames:List[str]=None): + simpleNoteNames:List[str]=None, + whichPatternsAreStepDelayed:Dict[int,int]=None, + whichPatternsHaveAugmentationFactor:Dict[int,float]=None, + stepDelayWrapAround:bool=False, + repeatDiminishedPatternInItself:bool=False, + ): self.parentData = parentData self.sequencerInterface = template.engine.sequencer.SequencerInterface(parentTrack=self, name=name) #needs parentData @@ -61,11 +66,15 @@ class Track(object): #injection at the bottom of this file! #2.1 self.group = "" # "" is a standalone track, the normal one which existed since version 1.0. Using a name here will group these tracks together. A GUI can use this information. Also all tracks in a group share a single jack out port. self.visible = True #only used together with groups. the api and our Datas setGroup function take care that standalone tracks are never hidden. + self.stepDelayWrapAround = False #every note that falls down on the "right side" of the pattern will wrap around to the beginning. + self.repeatDiminishedPatternInItself = False # if augmentationFactor < 1: repeat the pattern multiple times, as many as fit into the measure. self.pattern = Pattern(parentTrack=self, scale=scale, simpleNoteNames=simpleNoteNames) self.structure = structure if structure else set() #see buildTrack(). This is the main track data structure besides the pattern. Just integers (starts at 0) as switches which are positions where to play the patterns. In between are automatic rests. self.whichPatternsAreScaleTransposed = whichPatternsAreScaleTransposed if whichPatternsAreScaleTransposed else {} #position:integers between -7 and 7. Reversed pitch, row based: -7 is higher than 7!! self.whichPatternsAreHalftoneTransposed = whichPatternsAreHalftoneTransposed if whichPatternsAreHalftoneTransposed else {} #position:integers between -7 and 7. Reversed pitch, row based: -7 is higher than 7!! + self.whichPatternsAreStepDelayed = whichPatternsAreStepDelayed if whichPatternsAreStepDelayed else {} #position:signed integer + self.whichPatternsHaveAugmentationFactor = whichPatternsHaveAugmentationFactor if whichPatternsHaveAugmentationFactor else {} #position:float > 0 self._processAfterInit() @property @@ -101,9 +110,13 @@ class Track(object): #injection at the bottom of this file! assert ourCalfboxTrack, (ourCalfboxTrack, self, self.group, self.parentData, self.sequencerInterface) - #First clean the transpositions of zeroes + #First clean all modifications from the default value. + #We test for k in self.structure to not have modifications for measures that will be switched on later + #TODO: Can this possibly lead to a race condition where we load modifications first and then load the structure, which results in the mods getting perma-deleted here. e.g. we could not set default value in init, even for testing purposes. self.whichPatternsAreScaleTransposed = {k:v for k,v in self.whichPatternsAreScaleTransposed.items() if v!=0 and k in self.structure} self.whichPatternsAreHalftoneTransposed = {k:v for k,v in self.whichPatternsAreHalftoneTransposed.items() if v!=0 and k in self.structure} + self.whichPatternsAreStepDelayed = {k:v for k,v in self.whichPatternsAreStepDelayed.items() if v!=0 and k in self.structure} + self.whichPatternsHaveAugmentationFactor = {k:v for k,v in self.whichPatternsHaveAugmentationFactor.items() if v!=1.0 and k in self.structure} #default is 1.0 oneMeasureInTicks = (self.parentData.howManyUnits * self.parentData.whatTypeOfUnit) / self.parentData.subdivisions #subdivisions is 1 by default. bigger values mean shorter values, which is compensated by the user setting bigger howManyUnits manually. oneMeasureInTicks = int(oneMeasureInTicks) * self.patternLengthMultiplicator @@ -118,7 +131,9 @@ class Track(object): #injection at the bottom of this file! for index in filteredStructure: scaleTransposition = self.whichPatternsAreScaleTransposed[index] if index in self.whichPatternsAreScaleTransposed else 0 halftoneTransposition = self.whichPatternsAreHalftoneTransposed[index] if index in self.whichPatternsAreHalftoneTransposed else 0 - cboxPattern = self.pattern.buildPattern(scaleTransposition, halftoneTransposition, self.parentData.howManyUnits, self.parentData.whatTypeOfUnit, self.parentData.subdivisions, self.patternLengthMultiplicator) + stepDelay = self.whichPatternsAreStepDelayed[index] if index in self.whichPatternsAreStepDelayed else 0 + augmentationFactor = self.whichPatternsHaveAugmentationFactor[index] if index in self.whichPatternsHaveAugmentationFactor else 1 + cboxPattern = self.pattern.buildPattern(scaleTransposition, halftoneTransposition, self.parentData.howManyUnits, self.parentData.whatTypeOfUnit, self.parentData.subdivisions, self.patternLengthMultiplicator, stepDelay, augmentationFactor) r = ourCalfboxTrack.add_clip(globalOffset + index*oneMeasureInTicks, 0, oneMeasureInTicks, cboxPattern) #pos, pattern-internal offset, length, pattern. ######Old optimisations. Keep for later#### @@ -140,9 +155,13 @@ class Track(object): #injection at the bottom of this file! "whichPatternsAreScaleTransposed" : self.whichPatternsAreScaleTransposed, "whichPatternsAreHalftoneTransposed" : self.whichPatternsAreHalftoneTransposed, "patternLengthMultiplicator" : self.patternLengthMultiplicator, + "whichPatternsAreStepDelayed" : self.whichPatternsAreStepDelayed, + "whichPatternsHaveAugmentationFactor" : self.whichPatternsHaveAugmentationFactor, "midiChannel" : self.midiChannel, "group" : self.group, "visible" : self.visible, + "stepDelayWrapAround" : self.stepDelayWrapAround, + "repeatDiminishedPatternInItself" : self.repeatDiminishedPatternInItself, } @classmethod @@ -172,6 +191,28 @@ class Track(object): #injection at the bottom of this file! else: self.visible = True + if "stepDelayWrapAround" in serializedData: + self.stepDelayWrapAround = bool(serializedData["stepDelayWrapAround"]) + else: + self.stepDelayWrapAround = False + + if "repeatDiminishedPatternInItself" in serializedData: + self.repeatDiminishedPatternInItself = bool(serializedData["repeatDiminishedPatternInItself"]) + else: + self.repeatDiminishedPatternInItself = False + + if "whichPatternsAreStepDelayed" in serializedData: + self.whichPatternsAreStepDelayed = {int(k):int(v) for k,v in serializedData["whichPatternsAreStepDelayed"].items()} #json saves dict keys as strings + else: + self.whichPatternsAreStepDelayed = {} + + if "whichPatternsHaveAugmentationFactor" in serializedData: + self.whichPatternsHaveAugmentationFactor = {int(k):float(v) for k,v in serializedData["whichPatternsHaveAugmentationFactor"].items()} #json saves dict keys as strings + else: + self.whichPatternsHaveAugmentationFactor = {} + + + self.color = serializedData["color"] self.structure = set(serializedData["structure"]) self.whichPatternsAreHalftoneTransposed = {int(k):int(v) for k,v in serializedData["whichPatternsAreHalftoneTransposed"].items()} #json saves dict keys as strings @@ -195,9 +236,13 @@ class Track(object): #injection at the bottom of this file! "numberOfMeasures": self.parentData.numberOfMeasures, "whichPatternsAreScaleTransposed": self.whichPatternsAreScaleTransposed, "whichPatternsAreHalftoneTransposed": self.whichPatternsAreHalftoneTransposed, + "whichPatternsAreStepDelayed": self.whichPatternsAreStepDelayed, + "whichPatternsHaveAugmentationFactor": self.whichPatternsHaveAugmentationFactor, "midiChannel" : self.midiChannel+1, #1-16 "group" : self.group, #string "visible" : self.visible, #bool. Always True for standalone tracks + "stepDelayWrapAround" : self.stepDelayWrapAround, #bool. + "repeatDiminishedPatternInItself" : self.repeatDiminishedPatternInItself, #bool } #Dependency Injections. diff --git a/qtgui/songeditor.py b/qtgui/songeditor.py index fbb6689..a6fa90b 100644 --- a/qtgui/songeditor.py +++ b/qtgui/songeditor.py @@ -434,7 +434,7 @@ class TrackStructure(QtWidgets.QGraphicsRectItem): (QtCore.QCoreApplication.translate("SongStructure", "Exchange group with right neigbour"), lambda: api.exchangeSwitchGroupWithGroupToTheRight(startMeasureForGroup, endMeasureExclusive)), (QtCore.QCoreApplication.translate("SongStructure", "Delete whole group"), lambda: api.deleteSwitches(howMany=measuresPerGroup, fromMeasureNumber=startMeasureForGroup)), (QtCore.QCoreApplication.translate("SongStructure", "Duplicate whole group including measures"), lambda: api.duplicateSwitchGroup(startMeasureForGroup, endMeasureExclusive)), - (QtCore.QCoreApplication.translate("SongStructure", "Clear all group transpositions"), lambda: api.clearSwitchGroupTranspositions(startMeasureForGroup, endMeasureExclusive)), + (QtCore.QCoreApplication.translate("SongStructure", "Clear all group transpositions"), lambda: api.clearSwitchGroupModifications(startMeasureForGroup, endMeasureExclusive)), ] for text, function in listOfLabelsAndFunctions: