Browse Source

First steps to allow more notes per pattern than 8

master
Nils 3 years ago
parent
commit
e25d0d1c17
  1. 73
      engine/api.py
  2. 36
      engine/pattern.py
  3. 1
      engine/track.py
  4. 4
      qtgui/mainwindow.py
  5. 56
      qtgui/pattern_grid.py

73
engine/api.py

@ -14,12 +14,11 @@ import template.engine.api #we need direct access to the module to inject data i
from template.engine.api import * from template.engine.api import *
from template.engine.duration import baseDurationToTraditionalNumber from template.engine.duration import baseDurationToTraditionalNumber
#Our modules #Our own engine Modules
from .pattern import NUMBER_OF_STEPS pass
DEFAULT_FACTOR = 1 #for the GUI. DEFAULT_FACTOR = 1 #for the GUI.
#New callbacks #New callbacks
class ClientCallbacks(Callbacks): #inherits from the templates api callbacks class ClientCallbacks(Callbacks): #inherits from the templates api callbacks
def __init__(self): def __init__(self):
@ -762,24 +761,27 @@ def patternRowChangeVelocity(trackId, pitchindex, delta):
callbacks._patternChanged(track) callbacks._patternChanged(track)
major = [0, 2, 4, 5, 7, 9, 11, 12] #this if sorted by pitch, lowest to highest. Patroneo works in reverse order to accomodate the row/column approach of a grid. We reverse in setScaleToKeyword major = [0, 2, 4, 5, 7, 9, 11] #this is sorted by pitch, lowest to highest. Patroneo works in reverse order to accomodate the row/column approach of a grid. We reverse in setScaleToKeyword
schemesDict = { schemesDict = {
#this if sorted by pitch, lowest to highest. Patroneo works in reverse order to accomodate the row/column approach of a grid. We reverse in setScaleToKeyword #this if sorted by pitch, lowest to highest. Patroneo works in reverse order to accomodate the row/column approach of a grid. We reverse in setScaleToKeyword
"Major": [0,0,0,0,0,0,0,0], #The lowest/first pitch is always 0 because it is just the given root note.
"Minor": [0,0,-1,0,0,-1,-1,0], "Major": [0,0,0,0,0,0,0],
"Dorian": [0,0,-1,0,0,0,-1,0], "Minor": [0,0,-1,0,0,-1,-1],
"Phrygian": [0,-1,-1,0,0,-1,-1,0], "Dorian": [0,0,-1,0,0,0,-1],
"Lydian": [0,0,0,+1,0,0,0,0], "Phrygian": [0,-1,-1,0,0,-1,-1],
"Mixolydian": [0,0,0,0,0,0,-1,0], "Lydian": [0,0,0,+1,0,0,0],
"Locrian": [0,-1,-1,0,-1,-1,-1,0], "Mixolydian": [0,0,0,0,0,0,-1],
"Blues": [0,-2,-1,0,-1,-2,-1,0], "Locrian": [0,-1,-1,0,-1,-1,-1],
"Hollywood": [0,0,0,0,0,-1,-1,0], #The "Hollywood"-Scale. Stargate, Lord of the Rings etc. "Blues": [0,-2,-1,0,-1,-2,-1], #blues is a special case. It has less notes than we offer. Set a full scale and another script will mute/hide those extra steps.
"Chromatic": [0,-1,-2,-2,-3,-4,-5,-5], #not a complete octave, but that is how it goes. "Blues": [0, +1, +1, +1, 0, +1],
"Hollywood": [0,0,0,0,0,-1,-1], #The "Hollywood"-Scale. Stargate, Lord of the Rings etc.
"Chromatic": [0,-1,-2,-2,-3,-4,-5,-5,-6, -7, -7, -8, -9, -10, -10], #crude...
} }
major.reverse() major.reverse()
for l in schemesDict.values(): for l in schemesDict.values():
l.reverse() l.reverse()
#Ordered version
schemes = [ schemes = [
"Major", "Major",
"Minor", "Minor",
@ -794,12 +796,47 @@ schemes = [
] ]
def setScaleToKeyword(trackId, keyword): def setScaleToKeyword(trackId, keyword):
"""Use a builtin base scale and apply to all notes in a pattern. If there are more more ore
fewer notes in the pattern than in the scale we will calculate the rest.
This function is called not often and does not need to be performant.
"""
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
rememberRootNote = track.pattern.scale[-1] #no matter if this is the lowest or not% rememberRootNote = track.pattern.scale[-1] #The last note has a special role by convention. No matter if this is the lowest midi-pitch or not. Most of the time it is the lowest though.
scale = [x + y for x, y in zip(major, schemesDict[keyword])]
difference = rememberRootNote - scale[-1] #Create a modified scalePattern for the tracks numberOfSteps.
result = [midipitch+difference for midipitch in scale] #We technically only need to worry about creating additional steps. less steps is covered by zip(), see below
majorExt = []
schemeExt = []
mrev = list(reversed(major*16)) #pad major to the maximum possible notes. We just need the basis to make it possible long schemes like chromatic fit
srev = list(reversed(schemesDict[keyword]*16))
for i in range(track.pattern.numberOfSteps):
l = len(srev)
octaveOffset = i // l * 12 #starts with 0*12
majorExt.append( mrev[i % l ] + octaveOffset)
schemeExt.append( srev[i % l] ) #this is always the same. it is only the difference to the major scale
majorExt = list(reversed(majorExt))
schemeExt = list(reversed(schemeExt))
scale = [x + y for x, y in zip(majorExt, schemeExt)] #zip just creates pairs until it reached the end of one of its arguments. This is reversed order.
#scale = [x + y for x, y in zip(major, schemesDict[keyword])] #zip just creates pairs until it reached the end of one of its arguments. This is reversed order.
difference = rememberRootNote - scale[-1] #isn't this the same as rootnote since scale[-1] is always 0? Well, we could have hypo-scales in the future.
result = [midipitch+difference for midipitch in scale] #create actual midi pitches from the root note and the scale. This is reversed order because "scale" is.
#Here is a hack because chromatic didn't work with octave wrap-around. We want to make sure we don't fall back to a lower octave
r = reversed(result)
result = []
oldPitch = 0
for p in r:
while p <= oldPitch:
p += 12
result.append(p)
oldPitch = p
result = reversed(result)
#Done. Inform all parties.
track.pattern.scale = result track.pattern.scale = result
track.pattern.buildExportCache() track.pattern.buildExportCache()
track.buildTrack() track.buildTrack()

36
engine/pattern.py

@ -29,23 +29,24 @@ from statistics import median
#Third Party Modules #Third Party Modules
from calfbox import cbox from calfbox import cbox
NUMBER_OF_STEPS = 8 #for exchange with the GUI: one octave of range per pattern. This is unlikely to change and then will not work immediately, but better to keep it as a named "constant".
DEFAULT_VELOCITY = 90 DEFAULT_VELOCITY = 90
class Pattern(object): class Pattern(object):
"""A pattern can be in only one track. """A pattern can be in only one track.
In fact having it as its own object is only for code readability In fact a pattern IS a track.
Having it as its own class is only for code readability
A pattern is an unordered list of dicts. A pattern is an unordered list of dicts.
Each dict is an step, or a note. Each dict is an step, or a note.
{"index": from 0 to parentTrack.parentData.howManyUnits, Only existing steps (Switched On) are in self.data
{"index": from 0 to parentTrack.parentData.howManyUnits * stretchfactor. But can be higher, will just not be played or exported.,
"factor": float, "factor": float,
"pitch": int 0-7, "pitch": int 0-7,
"velocity":int 0-127, "velocity":int 0-127,
} }
The pitch is determined by an external scale, which is a list of len The pitch is determined by an external scale, which is a list of midi pitches.
7 of midi pitches. Our "pitch" is an index in this list. Our "pitch" is an index in this list.
The scale works in screen coordinates(rows and columns). The scale works in screen coordinates(rows and columns).
So usually the highest values comes first. So usually the highest values comes first.
@ -53,20 +54,22 @@ class Pattern(object):
note. note.
The pattern has always its maximum length. The pattern has always its maximum length.
""" """
def __init__(self, parentTrack, data:List[dict]=None, scale:Tuple[int]=None, simpleNoteNames:List[str]=None): def __init__(self, parentTrack, data:List[dict]=None, scale:Tuple[int]=None, simpleNoteNames:List[str]=None):
self._prepareBeforeInit() self._prepareBeforeInit()
self.parentTrack = parentTrack self.parentTrack = parentTrack
#self.scale = scale if scale else (72+ 12, 71+12, 69+12, 67+12, 65+12, 64+12, 62+12, 60+12, 71, 69, 67, 65, 64, 62, 60) #Scale needs to be set first because on init/load data already depends on it, at least the default scale. The scale is part of the track meta callback.
self.scale = scale if scale else (72, 71, 69, 67, 65, 64, 62, 60) #Scale needs to be set first because on init/load data already depends on it, at least the default scale. The scale is part of the track meta callback. self.scale = scale if scale else (72, 71, 69, 67, 65, 64, 62, 60) #Scale needs to be set first because on init/load data already depends on it, at least the default scale. The scale is part of the track meta callback.
self.data = data if data else list() #For content see docstring. this cannot be the default parameter because we would set the same list for all instances. self.data = data if data else list() #For content see docstring. this cannot be the default parameter because we would set the same list for all instances.
self.simpleNoteNames = simpleNoteNames if simpleNoteNames else self.parentTrack.parentData.lastUsedNotenames[:] #This is mostly for the GUI or other kinds of representation instead midi notes self.simpleNoteNames = simpleNoteNames if simpleNoteNames else self.parentTrack.parentData.lastUsedNotenames[:] #This is mostly for the GUI or other kinds of representation instead midi notes
assert self.simpleNoteNames assert self.simpleNoteNames
#Extended Pitch
#2.0 Pitch
#Explanation: Why don't we just do 12 steps per ocatve and then leave steps blank to create a scale? #Explanation: Why don't we just do 12 steps per ocatve and then leave steps blank to create a scale?
#We still want the: User set steps, not pitch idiom. #We still want the "User set steps, not pitch" idiom.
#self.mutedSteps = set()
#self.stepsPerOctave = 7 # This is not supposed to go below 7! e.g. Pentatonic scales are done by leaving steps out with self.blankSteps. #self.stepsPerOctave = 7 # This is not supposed to go below 7! e.g. Pentatonic scales are done by leaving steps out with self.blankSteps.
#self.nrOfSteps = 8 # Needs to be >= stepsPerOctave. stepsPerOctave+1 will, by default, result in a full scale plus its octave. That can of course later be changed. #self.nrOfSteps = 8 # Needs to be >= stepsPerOctave. stepsPerOctave+1 will, by default, result in a full scale plus its octave. That can of course later be changed.
@ -74,12 +77,10 @@ class Pattern(object):
self._processAfterInit() self._processAfterInit()
def _prepareBeforeInit(self): def _prepareBeforeInit(self):
self._cachedTransposedScale = {} self._cachedTransposedScale = {}
def _processAfterInit(self): def _processAfterInit(self):
self._tonalRange = range(-1*NUMBER_OF_STEPS+1, NUMBER_OF_STEPS)
self.averageVelocity = DEFAULT_VELOCITY # cached on each build self.averageVelocity = DEFAULT_VELOCITY # cached on each build
self._exportCacheVersion = 0 # increased each time the cache is renewed. Can be used to check for changes in the pattern itself. self._exportCacheVersion = 0 # increased each time the cache is renewed. Can be used to check for changes in the pattern itself.
@ -103,7 +104,10 @@ class Pattern(object):
@scale.setter @scale.setter
def scale(self, value): def scale(self, value):
"""The scale can never be modified in place! Only replace it with a different list. """The scale can never be modified in place! Only replace it with a different list.
For that reason we keep scale an immutable tuple instead of a list""" For that reason we keep scale an immutable tuple instead of a list.
The number of steps are determined by the scale length
"""
self._scale = tuple(value) self._scale = tuple(value)
self.createCachedTonalRange() self.createCachedTonalRange()
@ -116,6 +120,11 @@ class Pattern(object):
self._simpleNoteNames = tuple(value) #we keep it immutable, this is safer to avoid accidental linked structures when creating a clone. self._simpleNoteNames = tuple(value) #we keep it immutable, this is safer to avoid accidental linked structures when creating a clone.
self.parentTrack.parentData.lastUsedNotenames = self._simpleNoteNames #new default for new tracks self.parentTrack.parentData.lastUsedNotenames = self._simpleNoteNames #new default for new tracks
@property
def numberOfSteps(self):
return len(self.scale)
def fill(self): def fill(self):
"""Create a 2 dimensional array""" """Create a 2 dimensional array"""
l = len(self.scale) l = len(self.scale)
@ -238,7 +247,7 @@ class Pattern(object):
#We create three full octaves because the code is much easier to write and read. no modulo, no divmod. #We create three full octaves because the code is much easier to write and read. no modulo, no divmod.
#We may not present all of them to the user. #We may not present all of them to the user.
self._cachedTransposedScale.clear() self._cachedTransposedScale.clear()
for step in range(NUMBER_OF_STEPS): for step in range(self.numberOfSteps):
self._cachedTransposedScale[step] = self.scale[step] self._cachedTransposedScale[step] = self.scale[step]
if not step+7 in self._cachedTransposedScale: if not step+7 in self._cachedTransposedScale:
self._cachedTransposedScale[step+7] = self.scale[step] - 12 #yes, that is correct. We do top-bottom for our steps. self._cachedTransposedScale[step+7] = self.scale[step] - 12 #yes, that is correct. We do top-bottom for our steps.
@ -248,7 +257,6 @@ class Pattern(object):
def buildExportCache(self): def buildExportCache(self):
"""Called by the api directly and once on init/load""" """Called by the api directly and once on init/load"""
self.exportCache = [] #only used by parentTrack.export() self.exportCache = [] #only used by parentTrack.export()
assert self.scale and len(self.scale) == NUMBER_OF_STEPS #from constants
for pattern in (p for p in self.data if p["index"] < self.parentTrack.parentData.howManyUnits * self.parentTrack.patternLengthMultiplicator): # < and not <= because index counts from 0 but howManyUnits counts from 1 for pattern in (p for p in self.data if p["index"] < self.parentTrack.parentData.howManyUnits * self.parentTrack.patternLengthMultiplicator): # < and not <= because index counts from 0 but howManyUnits counts from 1
note = {} note = {}

1
engine/track.py

@ -139,6 +139,7 @@ class Track(object): #injection at the bottom of this file!
"patternLengthMultiplicator" : self.patternLengthMultiplicator, #int "patternLengthMultiplicator" : self.patternLengthMultiplicator, #int
"pattern": self.pattern.exportCache, "pattern": self.pattern.exportCache,
"scale": self.pattern.scale, "scale": self.pattern.scale,
"numberOfSteps": self.pattern.numberOfSteps,
"simpleNoteNames": self.pattern.simpleNoteNames, "simpleNoteNames": self.pattern.simpleNoteNames,
"numberOfMeasures": self.parentData.numberOfMeasures, "numberOfMeasures": self.parentData.numberOfMeasures,
"whichPatternsAreScaleTransposed": self.whichPatternsAreScaleTransposed, "whichPatternsAreScaleTransposed": self.whichPatternsAreScaleTransposed,

4
qtgui/mainwindow.py

@ -119,6 +119,8 @@ class MainWindow(TemplateMainWindow):
self.ui.centralwidget.addAction(self.ui.actionToStart) #no action without connection to a widget. self.ui.centralwidget.addAction(self.ui.actionToStart) #no action without connection to a widget.
self.ui.actionToStart.triggered.connect(self.ui.toStartButton.click) self.ui.actionToStart.triggered.connect(self.ui.toStartButton.click)
self.currentTrackId = None #this is purely a GUI construct. the engine does not know a current track. On startup there is no active track
##Song Editor ##Song Editor
self.ui.songEditorView.parentMainWindow = self self.ui.songEditorView.parentMainWindow = self
self.songEditor = SongEditor(parentView=self.ui.songEditorView) self.songEditor = SongEditor(parentView=self.ui.songEditorView)
@ -161,7 +163,7 @@ class MainWindow(TemplateMainWindow):
#Toolbar, which needs the widgets above already established #Toolbar, which needs the widgets above already established
self._populateToolbar() self._populateToolbar()
self.currentTrackId = None #this is purely a GUI construct. the engine does not know a current track. On startup there is no active track
self.start() #This shows the GUI, or not, depends on the NSM gui save setting. We need to call that after the menu, otherwise the about dialog will block and then we get new menu entries, which looks strange. self.start() #This shows the GUI, or not, depends on the NSM gui save setting. We need to call that after the menu, otherwise the about dialog will block and then we get new menu entries, which looks strange.
#There is always a track. Forcing that to be active is better than having to hide all the pattern widgets, or to disable them. #There is always a track. Forcing that to be active is better than having to hide all the pattern widgets, or to disable them.
#However, we need the engine to be ready. #However, we need the engine to be ready.

56
qtgui/pattern_grid.py

@ -123,15 +123,18 @@ class PatternGrid(QtWidgets.QGraphicsScene):
if forceId: if forceId:
factor = self._tracks[forceId]["patternLengthMultiplicator"] factor = self._tracks[forceId]["patternLengthMultiplicator"]
numberOfSteps = self._tracks[forceId]["numberOfSteps"]
else: else:
if not self.parentView.parentMainWindow.currentTrackId or not self.parentView.parentMainWindow.currentTrackId in self._tracks: if not self.parentView.parentMainWindow.currentTrackId or not self.parentView.parentMainWindow.currentTrackId in self._tracks:
factor = 1 #program start. Purely internal. Will be overriden by a second callback before the user sees it. factor = 1 #program start. Purely internal. Will be overriden by a second callback before the user sees it.
numberOfSteps = 1
else: else:
factor = self._tracks[self.parentView.parentMainWindow.currentTrackId]["patternLengthMultiplicator"] factor = self._tracks[self.parentView.parentMainWindow.currentTrackId]["patternLengthMultiplicator"]
numberOfSteps = self._tracks[self.parentView.parentMainWindow.currentTrackId]["numberOfSteps"]
#Build a two dimensional grid #Build a two dimensional grid
for column in range(howMany*factor): for column in range(howMany*factor):
for row in range(api.NUMBER_OF_STEPS): for row in range(numberOfSteps):
x = column * SIZE_UNIT + SIZE_RIGHT_OFFSET x = column * SIZE_UNIT + SIZE_RIGHT_OFFSET
y = row * SIZE_UNIT + SIZE_TOP_OFFSET y = row * SIZE_UNIT + SIZE_TOP_OFFSET
@ -200,6 +203,7 @@ class PatternGrid(QtWidgets.QGraphicsScene):
velocityAndFactor = (noteDict["velocity"], noteDict["factor"]) velocityAndFactor = (noteDict["velocity"], noteDict["factor"])
self._steps[(x,y)].on(velocityAndFactor=velocityAndFactor, exceedsPlayback=noteDict["exceedsPlayback"]) self._steps[(x,y)].on(velocityAndFactor=velocityAndFactor, exceedsPlayback=noteDict["exceedsPlayback"])
self.scale.buildScale(exportDict["numberOfSteps"])
self.scale.setScale(exportDict["scale"]) self.scale.setScale(exportDict["scale"])
self.scale.setNoteNames(exportDict["simpleNoteNames"]) self.scale.setNoteNames(exportDict["simpleNoteNames"])
@ -652,28 +656,48 @@ class Step(QtWidgets.QGraphicsRectItem):
class Scale(QtWidgets.QGraphicsRectItem): class Scale(QtWidgets.QGraphicsRectItem):
"""SpinBoxes on the left side of the steps"""
def __init__(self, parentScene): def __init__(self, parentScene):
super().__init__(0,0,0,0) super().__init__(0,0,0,0)
self.parentScene = parentScene self.parentScene = parentScene
self.pitchWidgets = [] #sorted from top to bottom in Step Rect and scene coordinates self.pitchWidgets = [] #sorted from top to bottom in Step Rect and scene coordinates
self.simpleNoteNames = None #list of 128 notes. use index with note name. Can be changed at runtime. Never empty. self.simpleNoteNames = None #list of 128 notes. use index with note name. Can be changed at runtime. Never empty.
api.callbacks.trackMetaDataChanged.append(self.callback_trackMetaDataChanged) api.callbacks.trackMetaDataChanged.append(self.callback_trackMetaDataChanged)
self.buildScale() #also sets the positions of the buttons above #self.buildScale(1) #also sets the positions of the buttons above
def callback_trackMetaDataChanged(self, exportDict): def callback_trackMetaDataChanged(self, exportDict):
#Order matters. We need to set the notenames before the scale. #Order matters. We need to set the notenames before the scale.
self.buildScale(exportDict["numberOfSteps"])
self.setNoteNames(exportDict["simpleNoteNames"]) self.setNoteNames(exportDict["simpleNoteNames"])
self.setScale(exportDict["scale"]) self.setScale(exportDict["scale"])
def buildScale(self):
"""Only executed once per pattern""" def buildScale(self, numberOfSteps):
for i in range(api.NUMBER_OF_STEPS): """This is used for building the GUI as well as switching the active track.
p = PitchWidget(parentItem=self) We only add, show and hide. Never delete steps."""
y = i * SIZE_UNIT
p.setParentItem(self) stepsSoFar = len(self.pitchWidgets)
p.setPos(-65, y+10)
self.pitchWidgets.append(p) if numberOfSteps < stepsSoFar: #reduce only by hiding pitchWidgets
#self.setRect(0,0, SIZE_RIGHT_OFFSET, p.y() + SIZE_UNIT) #p is the last of the 8. for i, pitchWidget in enumerate(self.pitchWidgets):
if i < numberOfSteps:
pitchWidget.show()
else:
pitchWidget.hide()
else: #create new steps, incrementally
for i in range(numberOfSteps):
if i < stepsSoFar: #already exists
self.pitchWidgets[i].show()
else:
p = PitchWidget(parentItem=self)
y = i * SIZE_UNIT
p.setParentItem(self)
p.setPos(-65, y+10)
self.pitchWidgets.append(p)
#self.setRect(0,0, SIZE_RIGHT_OFFSET, p.y() + SIZE_UNIT) #p is the last of the 8.
def setScale(self, scaleList): def setScale(self, scaleList):
"""We receive from top to bottom, in step rect coordinates. This is not sorted after """We receive from top to bottom, in step rect coordinates. This is not sorted after
@ -701,7 +725,9 @@ class Scale(QtWidgets.QGraphicsRectItem):
api.setScale(trackId, scale=result, callback=callback) api.setScale(trackId, scale=result, callback=callback)
class TransposeControls(QtWidgets.QWidget): class TransposeControls(QtWidgets.QWidget):
"""Communication with the scale spinBoxes is done via api callbacks. We just fire and forget""" """
The row of widgets between the track structures and the patterns step grid
Communication with the scale spinBoxes is done via api callbacks. We just fire and forget"""
#Not working. the translation generate works statically. translatedScales = [QtCore.QT_TRANSLATE_NOOP("Scale", scale) for scale in api.schemes] #Not working. the translation generate works statically. translatedScales = [QtCore.QT_TRANSLATE_NOOP("Scale", scale) for scale in api.schemes]
#No choice but to prepare the translations manually here. At least we do not need to watch for the order. #No choice but to prepare the translations manually here. At least we do not need to watch for the order.
@ -794,6 +820,7 @@ class TransposeControls(QtWidgets.QWidget):
api.transposeHalftoneSteps(trackId=self.parentScene.parentView.parentMainWindow.currentTrackId, steps=-12) api.transposeHalftoneSteps(trackId=self.parentScene.parentView.parentMainWindow.currentTrackId, steps=-12)
def transposeToScale(self, index): def transposeToScale(self, index):
"""Index is a shared index, by convention, between our drop-down list and api.schemes"""
if index > 0: if index > 0:
index -= 1 # the backend list obviously has no "Set Scale to" on index [0] index -= 1 # the backend list obviously has no "Set Scale to" on index [0]
api.setScaleToKeyword(trackId=self.parentScene.parentView.parentMainWindow.currentTrackId, keyword=api.schemes[index]) #this schemes must NOT be translated since it is the original key/symbol. api.setScaleToKeyword(trackId=self.parentScene.parentView.parentMainWindow.currentTrackId, keyword=api.schemes[index]) #this schemes must NOT be translated since it is the original key/symbol.
@ -886,7 +913,7 @@ class PitchWidget(QtWidgets.QGraphicsProxyWidget):
class Playhead(QtWidgets.QGraphicsLineItem): class Playhead(QtWidgets.QGraphicsLineItem):
def __init__(self, parentScene): def __init__(self, parentScene):
super().__init__(0, 0, 0, api.NUMBER_OF_STEPS*SIZE_UNIT) # (x1, y1, x2, y2) super().__init__(0, 0, 0, SIZE_UNIT) # (x1, y1, x2, y2) #Program start we are just 1 unit high. Changes with actual data.
self.parentScene = parentScene self.parentScene = parentScene
p = QtGui.QPen() p = QtGui.QPen()
p.setColor(QtGui.QColor("red")) p.setColor(QtGui.QColor("red"))
@ -903,6 +930,9 @@ class Playhead(QtWidgets.QGraphicsLineItem):
x = (tickindex % (self.parentScene.oneMeasureInTicks * factor)) / self.parentScene.ticksToPixelRatio x = (tickindex % (self.parentScene.oneMeasureInTicks * factor)) / self.parentScene.ticksToPixelRatio
x += SIZE_RIGHT_OFFSET x += SIZE_RIGHT_OFFSET
if playbackStatus: # api.duringPlayback: if playbackStatus: # api.duringPlayback:
numberOfSteps = self.parentScene._tracks[self.parentScene.parentView.parentMainWindow.currentTrackId]["numberOfSteps"]
self.setLine(0, 0, 0, numberOfSteps * SIZE_UNIT)
self.show() self.show()
self.setX(x) self.setX(x)
scenePos = self.parentScene.parentView.mapFromScene(self.pos()) scenePos = self.parentScene.parentView.mapFromScene(self.pos())

Loading…
Cancel
Save