Browse Source

Prepare two new measure modifiers: step-delay and augment/diminish. Engine and save/load done but nothing in the GUI yet

master
Nils 3 years ago
parent
commit
e8fd18bf10
  1. 6
      CHANGELOG
  2. 127
      engine/api.py
  3. 38
      engine/pattern.py
  4. 51
      engine/track.py
  5. 2
      qtgui/songeditor.py

6
CHANGELOG

@ -1,7 +1,13 @@
2021-04-15 Version 2.1.1 2021-04-15 Version 2.1.1
Add status bar to explain possible user actions (like "use shift + mousewheel to transpose measure") 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. 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 wrong playback cursor speed.
Fix small GUI drawing issues
2021-02-15 Version 2.1.0 2021-02-15 Version 2.1.0
Full Undo/Redo Full Undo/Redo

127
engine/api.py

@ -783,6 +783,8 @@ def trackMergeCopyFrom(sourceTrackId, targetTrackId):
targetTrack.structure = targetTrack.structure.union(sourceTrack.structure) targetTrack.structure = targetTrack.structure.union(sourceTrack.structure)
targetTrack.whichPatternsAreScaleTransposed.update(sourceTrack.whichPatternsAreScaleTransposed) targetTrack.whichPatternsAreScaleTransposed.update(sourceTrack.whichPatternsAreScaleTransposed)
targetTrack.whichPatternsAreHalftoneTransposed.update(sourceTrack.whichPatternsAreHalftoneTransposed) targetTrack.whichPatternsAreHalftoneTransposed.update(sourceTrack.whichPatternsAreHalftoneTransposed)
targetTrack.whichPatternsAreStepDelayed.update(sourceTrack.whichPatternsAreStepDelayed)
targetTrack.whichPatternsHaveAugmentationFactor.update(sourceTrack.whichPatternsHaveAugmentationFactor)
targetTrack.buildTrack() targetTrack.buildTrack()
updatePlayback() updatePlayback()
callbacks._trackStructureChanged(targetTrack) callbacks._trackStructureChanged(targetTrack)
@ -801,6 +803,7 @@ def trackPatternReplaceFrom(sourceTrackId, targetTrackId):
callbacks._patternChanged(targetTrack) callbacks._patternChanged(targetTrack)
#Transpositions and Modal Shifts #Transpositions and Modal Shifts
#StepDelay and AugmentationFactor
def _setSwitchesScaleTranspose(trackId, whichPatternsAreScaleTransposed): def _setSwitchesScaleTranspose(trackId, whichPatternsAreScaleTransposed):
"""For undo. Used by all functions as entry point, then calls itself for undo/redo. """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) callbacks._trackStructureChanged(track)
def setSwitchScaleTranspose(trackId, position, transpose): def setSwitchScaleTranspose(trackId, position:int, transpose:int):
"""Scale transposition is flipped. lower value means higher pitch""" """Scale transposition is flipped. lower value means higher pitch.
Default value is 0."""
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return if not track: return
session.history.register(lambda tr=trackId, v=track.whichPatternsAreScaleTransposed.copy(): _setSwitchesScaleTranspose(trackId, v), descriptionString="Set Modal Shift") 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() updatePlayback()
callbacks._trackStructureChanged(track) callbacks._trackStructureChanged(track)
def setSwitchHalftoneTranspose(trackId, position, transpose): def setSwitchHalftoneTranspose(trackId, position:int, transpose:int):
"""Halftone transposition is not flipped. Higher value means higher pitch""" """Halftone transposition is not flipped. Higher value means higher pitch
Default value is 0.
"""
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return if not track: return
session.history.register(lambda tr=trackId, v=track.whichPatternsAreHalftoneTransposed.copy(): _setSwitchHalftoneTranspose(trackId, v), descriptionString="Set Half Tone Shift") 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) callbacks._trackStructureChanged(track)
return True 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): def _registerHistoryWholeTrackSwitches(track):
"""This is used by insertSilence, clearSwitchGroupTranspositions etc. """This is used by insertSilence, clearSwitchGroupModifications etc.
It assumes to run inside the context: It assumes to run inside the context:
with session.history.sequence(""): 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.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.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.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): 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.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.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.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._trackStructureChanged(track)
callbacks._dataChanged() #register undo callbacks._dataChanged() #register undo
@ -906,6 +969,10 @@ def duplicateSwitchGroup(startMeasureForGroup:int, endMeasureExclusive:int):
track.whichPatternsAreScaleTransposed[switch] = track.whichPatternsAreScaleTransposed[switch-thisGroupSize] track.whichPatternsAreScaleTransposed[switch] = track.whichPatternsAreScaleTransposed[switch-thisGroupSize]
if switch-thisGroupSize in track.whichPatternsAreHalftoneTransposed: if switch-thisGroupSize in track.whichPatternsAreHalftoneTransposed:
track.whichPatternsAreHalftoneTransposed[switch] = track.whichPatternsAreHalftoneTransposed[switch-thisGroupSize] 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) callbacks._trackStructureChanged(track)
session.data.buildAllTracks() session.data.buildAllTracks()
@ -931,11 +998,14 @@ def exchangeSwitchGroupWithGroupToTheRight(startMeasureForGroup:int, endMeasureE
tempStructure = set() #integers tempStructure = set() #integers
tempScaleTransposed = dict() #position:integers tempScaleTransposed = dict() #position:integers
tempHalfToneTransposed = dict() #position:integers tempHalfToneTransposed = dict() #position:integers
tempStepDelayed = dict() #position:integers
tempAugmentedFactor = dict() #position:floats
#Remember for later testing #Remember for later testing
lenStructure = len(track.structure) lenStructure = len(track.structure)
lenScaleTransposed = len(track.whichPatternsAreScaleTransposed.keys()) 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 #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 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: if switch in track.whichPatternsAreHalftoneTransposed:
tempHalfToneTransposed[switch] = track.whichPatternsAreHalftoneTransposed[switch] tempHalfToneTransposed[switch] = track.whichPatternsAreHalftoneTransposed[switch]
del 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. #Now move current group to the right, which is now empty.
for switch in range(thisTrackStartMeasure, thisTrackEndMeasure): #switch is a number for switch in range(thisTrackStartMeasure, thisTrackEndMeasure): #switch is a number
@ -960,6 +1036,14 @@ def exchangeSwitchGroupWithGroupToTheRight(startMeasureForGroup:int, endMeasureE
if switch in track.whichPatternsAreHalftoneTransposed: if switch in track.whichPatternsAreHalftoneTransposed:
track.whichPatternsAreHalftoneTransposed[switch+groupSize] = track.whichPatternsAreHalftoneTransposed[switch] track.whichPatternsAreHalftoneTransposed[switch+groupSize] = track.whichPatternsAreHalftoneTransposed[switch]
del 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 #Move old right-group into its new place
for sw in tempStructure: for sw in tempStructure:
@ -968,6 +1052,10 @@ def exchangeSwitchGroupWithGroupToTheRight(startMeasureForGroup:int, endMeasureE
track.whichPatternsAreScaleTransposed[stPos-groupSize] = stVal track.whichPatternsAreScaleTransposed[stPos-groupSize] = stVal
for htPos, htVal in tempScaleTransposed.items(): for htPos, htVal in tempScaleTransposed.items():
track.whichPatternsAreHalftoneTransposed[hPos-groupSize] = htVal 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) callbacks._trackStructureChanged(track)
@ -975,13 +1063,15 @@ def exchangeSwitchGroupWithGroupToTheRight(startMeasureForGroup:int, endMeasureE
assert lenStructure == len(track.structure), (lenStructure, len(track.structure)) assert lenStructure == len(track.structure), (lenStructure, len(track.structure))
assert lenScaleTransposed == len(track.whichPatternsAreScaleTransposed.keys()), (lenScaleTransposed, len(track.whichPatternsAreScaleTransposed.keys())) assert lenScaleTransposed == len(track.whichPatternsAreScaleTransposed.keys()), (lenScaleTransposed, len(track.whichPatternsAreScaleTransposed.keys()))
assert lenHalfToneTransposed == len(track.whichPatternsAreHalftoneTransposed.keys()), (lenHalfToneTransposed, len(track.whichPatternsAreHalftoneTransposed.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 callbacks._dataChanged() #register undo
session.data.buildAllTracks() session.data.buildAllTracks()
updatePlayback() updatePlayback()
def clearSwitchGroupTranspositions(startMeasureForGroup:int, endMeasureExclusive:int): def clearSwitchGroupModifications(startMeasureForGroup:int, endMeasureExclusive:int):
"""startMeasureForGroup and endMeasureExclusive are in the global, un-multiplied measure counting """startMeasureForGroup and endMeasureExclusive are in the global, un-multiplied measure counting
format.""" format."""
with session.history.sequence("Clear all Group Transpositions"): with session.history.sequence("Clear all Group Transpositions"):
@ -994,6 +1084,10 @@ def clearSwitchGroupTranspositions(startMeasureForGroup:int, endMeasureExclusive
del track.whichPatternsAreScaleTransposed[switch] del track.whichPatternsAreScaleTransposed[switch]
if switch in track.whichPatternsAreHalftoneTransposed: if switch in track.whichPatternsAreHalftoneTransposed:
del track.whichPatternsAreHalftoneTransposed[switch] 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._trackStructureChanged(track)
callbacks._dataChanged() #register undo callbacks._dataChanged() #register undo
session.data.buildAllTracks() session.data.buildAllTracks()
@ -1037,6 +1131,25 @@ def deleteSwitches(howMany, fromMeasureNumber):
#else: #discard all in range to delete #else: #discard all in range to delete
track.whichPatternsAreHalftoneTransposed = new_halftoneTransposed 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._trackStructureChanged(track)
callbacks._dataChanged() #register undo callbacks._dataChanged() #register undo

38
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.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 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. """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 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. 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 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 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. 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) scale (as tuple so it is hashable)
self._exportCacheVersion self._exportCacheVersion
patternLengthMultiplicator patternLengthMultiplicator
stepDelay
augmentationFactor
_cachedTransposedScale is updated with self.scale changes and therefore already covered. _cachedTransposedScale is updated with self.scale changes and therefore already covered.
@ -321,12 +326,14 @@ class Pattern(object):
api.swingPercentToAbsolute 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: try:
return self._builtPatternCache[cacheHash] return self._builtPatternCache[cacheHash]
except KeyError: except KeyError:
pass 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 = 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 /= 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 oneMeasureInTicks = int(oneMeasureInTicks) * patternLengthMultiplicator
@ -336,7 +343,14 @@ class Pattern(object):
shuffle = int(self.parentTrack.parentData.swing * whatTypeOfUnit) shuffle = int(self.parentTrack.parentData.swing * whatTypeOfUnit)
for noteDict in self.exportCache: 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 startTick = index * whatTypeOfUnit
endTick = startTick + noteDict["factor"] * whatTypeOfUnit endTick = startTick + noteDict["factor"] * whatTypeOfUnit
startTick /= subdivisions startTick /= subdivisions
@ -349,12 +363,20 @@ class Pattern(object):
else: #off beats else: #off beats
startTick += shuffle startTick += shuffle
startTick = int(startTick) startTick = int(startTick * augmentationFactor)
endTick = int(endTick) 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: if endTick > oneMeasureInTicks:
endTick = oneMeasureInTicks #all note off must end at the end of the pattern 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: else:
noteDict["exceedsPlayback"] = False noteDict["exceedsPlayback"] = False

51
engine/track.py

@ -45,7 +45,12 @@ class Track(object): #injection at the bottom of this file!
color:str=None, color:str=None,
whichPatternsAreScaleTransposed:Dict[int,int]=None, whichPatternsAreScaleTransposed:Dict[int,int]=None,
whichPatternsAreHalftoneTransposed: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.parentData = parentData
self.sequencerInterface = template.engine.sequencer.SequencerInterface(parentTrack=self, name=name) #needs 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 #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.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.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.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.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.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.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() self._processAfterInit()
@property @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) 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.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.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 = (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 oneMeasureInTicks = int(oneMeasureInTicks) * self.patternLengthMultiplicator
@ -118,7 +131,9 @@ class Track(object): #injection at the bottom of this file!
for index in filteredStructure: for index in filteredStructure:
scaleTransposition = self.whichPatternsAreScaleTransposed[index] if index in self.whichPatternsAreScaleTransposed else 0 scaleTransposition = self.whichPatternsAreScaleTransposed[index] if index in self.whichPatternsAreScaleTransposed else 0
halftoneTransposition = self.whichPatternsAreHalftoneTransposed[index] if index in self.whichPatternsAreHalftoneTransposed 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. r = ourCalfboxTrack.add_clip(globalOffset + index*oneMeasureInTicks, 0, oneMeasureInTicks, cboxPattern) #pos, pattern-internal offset, length, pattern.
######Old optimisations. Keep for later#### ######Old optimisations. Keep for later####
@ -140,9 +155,13 @@ class Track(object): #injection at the bottom of this file!
"whichPatternsAreScaleTransposed" : self.whichPatternsAreScaleTransposed, "whichPatternsAreScaleTransposed" : self.whichPatternsAreScaleTransposed,
"whichPatternsAreHalftoneTransposed" : self.whichPatternsAreHalftoneTransposed, "whichPatternsAreHalftoneTransposed" : self.whichPatternsAreHalftoneTransposed,
"patternLengthMultiplicator" : self.patternLengthMultiplicator, "patternLengthMultiplicator" : self.patternLengthMultiplicator,
"whichPatternsAreStepDelayed" : self.whichPatternsAreStepDelayed,
"whichPatternsHaveAugmentationFactor" : self.whichPatternsHaveAugmentationFactor,
"midiChannel" : self.midiChannel, "midiChannel" : self.midiChannel,
"group" : self.group, "group" : self.group,
"visible" : self.visible, "visible" : self.visible,
"stepDelayWrapAround" : self.stepDelayWrapAround,
"repeatDiminishedPatternInItself" : self.repeatDiminishedPatternInItself,
} }
@classmethod @classmethod
@ -172,6 +191,28 @@ class Track(object): #injection at the bottom of this file!
else: else:
self.visible = True 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.color = serializedData["color"]
self.structure = set(serializedData["structure"]) self.structure = set(serializedData["structure"])
self.whichPatternsAreHalftoneTransposed = {int(k):int(v) for k,v in serializedData["whichPatternsAreHalftoneTransposed"].items()} #json saves dict keys as strings 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, "numberOfMeasures": self.parentData.numberOfMeasures,
"whichPatternsAreScaleTransposed": self.whichPatternsAreScaleTransposed, "whichPatternsAreScaleTransposed": self.whichPatternsAreScaleTransposed,
"whichPatternsAreHalftoneTransposed": self.whichPatternsAreHalftoneTransposed, "whichPatternsAreHalftoneTransposed": self.whichPatternsAreHalftoneTransposed,
"whichPatternsAreStepDelayed": self.whichPatternsAreStepDelayed,
"whichPatternsHaveAugmentationFactor": self.whichPatternsHaveAugmentationFactor,
"midiChannel" : self.midiChannel+1, #1-16 "midiChannel" : self.midiChannel+1, #1-16
"group" : self.group, #string "group" : self.group, #string
"visible" : self.visible, #bool. Always True for standalone tracks "visible" : self.visible, #bool. Always True for standalone tracks
"stepDelayWrapAround" : self.stepDelayWrapAround, #bool.
"repeatDiminishedPatternInItself" : self.repeatDiminishedPatternInItself, #bool
} }
#Dependency Injections. #Dependency Injections.

2
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", "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", "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", "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: for text, function in listOfLabelsAndFunctions:

Loading…
Cancel
Save