Browse Source

Fixy fixy

master
Nils 5 years ago
parent
commit
0a64e0f487
  1. 85
      engine/api.py
  2. 3
      engine/block.py
  3. 2
      engine/config.py
  4. 7
      engine/graphtracks.py
  5. 9
      engine/items.py
  6. 23
      engine/main.py
  7. 2
      engine/resources/metronome.sfz
  8. 0
      engine/resources/samples/normal.wav
  9. 0
      engine/resources/samples/stressed.wav
  10. 19
      engine/track.py
  11. 10
      qtgui/menu.py
  12. 29
      qtgui/scoreview.py
  13. 41
      qtgui/structures.py

85
engine/api.py

@ -58,7 +58,6 @@ class ClientCallbacks(Callbacks): #inherits from the templates api callbacks
#self.playbackStart = []
#self.playbackStop = []
self.playbackSpeedChanged = []
self._cachedTickIndex = -1
self.updateGraphTrackCC = []
@ -68,9 +67,9 @@ class ClientCallbacks(Callbacks): #inherits from the templates api callbacks
self.updateTempoTrack = []
self.updateTempoTrackBlocks = []
self.updateTempoTrackMeta = []
self.tempoScalingChanged = []
self.prevailingBaseDurationChanged = []
self.metronomeChanged = []
#self.recordingStreamNoteOn = []
#self.recordingStreamNoteOff = []
@ -115,6 +114,8 @@ class ClientCallbacks(Callbacks): #inherits from the templates api callbacks
changedTracks, currentItem = session.data.currentContentLinkAndItsBlocksInAllTracks()
for track in changedTracks:
self._updateTrack(id(track))
if session.data.currentMetronomeTrack is track:
setMetronome(track.asMetronomeData, label=track.name) #template api
def _updateTrack(self, trId):
"""The most important function. Create a static representation of the music data which
@ -179,6 +180,7 @@ class ClientCallbacks(Callbacks): #inherits from the templates api callbacks
"""Track deleted, added or moved. Or toggled double/non-double.
This callback is relatively cheap because it does not generate
any item data."""
#TODO: does NOT call template.api._numberOfTracksChanged
session.data.updateJackMetadataSorting()
if self.tracksChanged:
@ -226,6 +228,10 @@ class ClientCallbacks(Callbacks): #inherits from the templates api callbacks
#TODO: but this also leads to centerOn cursor in the gui after every mouse click in the conductor.
#self._setCursor(destroySelection = False)
def _tempoScalingChanged(self, newValue):
for func in self.tempoScalingChanged:
func(newValue)
def _playbackStart(self):
for func in self.playbackStart:
func()
@ -234,9 +240,6 @@ class ClientCallbacks(Callbacks): #inherits from the templates api callbacks
for func in self.playbackStop:
func()
def _playbackSpeedChanged(self, newValue):
for func in self.playbackSpeedChanged:
func(newValue)
def _prevailingBaseDurationChanged(self, newPrevailingBaseDuration):
for func in self.prevailingBaseDurationChanged:
@ -252,18 +255,6 @@ class ClientCallbacks(Callbacks): #inherits from the templates api callbacks
for func in self.historyChanged:
func(undoHistory, redoHistory)
def _metronomeChanged(self):
"""returns a dictionary with meta data such as the mute-state and the track name"""
#Check if the actual track exists. This handles track delete of any kind.
#session.data.metronome.currentMetronomeTrack is not a calf box track but our Laborejo track that HOLDS the current cbox metronome track
return #TODO
if not session.data.metronome.currentMetronomeTrack or not (session.data.metronome.currentMetronomeTrack in session.data.tracks or session.data.metronome.currentMetronomeTrack in session.data.hiddenTracks):
session.data.metronome.currentMetronomeTrack = None
session.data.metronome.useTrack(session.data.currentTrack())
for func in self.metronomeChanged:
func(session.data.metronome.staticRepresentation())
def _recordingStreamNoteOn(self, liveChord):
"""One dict at a time"""
trId = id(session.data.currentTrack())
@ -302,6 +293,8 @@ def startEngine(nsmClient):
for track in session.data.hiddenTracks: #After loading a file some tracks could be hidden. We want midi for them as well.
track.staticRepresentation() #that generates the calfbox data as a side effect. discard the other data.
useCurrentTrackAsMetronome()
callbacks._setCursor()
global laborejoEngineStarted #makes for a convenient check. stepMidiInput uses it, which needs to know that the gui already started the api.
@ -414,21 +407,6 @@ def _updateCallbackAllTracks():
updateCallbackAllTracks = _updateCallbackAllTracks
def useCurrentTrackAsMetronome():
track = session.data.currentTrack()
session.data.metronome.useTrack(track)
callbacks._metronomeChanged() #updates playback as well
def enableMetronome(value):
session.data.metronome.enabled = value #has side effects
callbacks._metronomeChanged() #updates playback as well
def isMetronomeEnabled():
return session.data.metronome.enabled
def toggleMetronome():
enableMetronome(not session.data.metronome.enabled) #handles callback etc.
def _changeBlockAndItemOrder(dictWithTrackIDsAndDataLists):
"""A helper function for deleteSelection, paste and other...
This makes it possible to undo/redo properly. It registers
@ -472,7 +450,7 @@ def copyObjects(): #ctrl+c
#The score doesn't change at all. No callback.
#no undo.
def pasteObjects(customBuffer = None, keepCursorState = False, overwriteSelection = True): #ctrl+v
def pasteObjects(customBuffer = None, updateCursor = True, overwriteSelection = True): #ctrl+v
"""api.duplicate overrides default paste behaviour by providing its own copyBuffer
and not destroying the selection/keep the cursor at its origin position
"""
@ -489,30 +467,30 @@ def pasteObjects(customBuffer = None, keepCursorState = False, overwriteSelectio
for trackId in listOfChangedTrackIDs:
callbacks._updateTrack(trackId)
if keepCursorState:
goTo()
callbacks._setCursor(destroySelection = False)
else:
if updateCursor:
callbacks._setCursor()
def duplicate(): #ctrl+d
"""Duplicate a single object and put it right of the original. Duplicate the entire selection
and put the copy right of the last selected note.
"""Duplicate a single object and put it right of the original. The cursor moves with it
to enable follow up insertion.
Duplicate the entire selection and put the copy right of the last selected note.
The cursor moves to selection start. deal with it.
Basically a special case of copy and paste that does not touch the clipboard."""
if session.data.cursorWhenSelectionStarted:
#startPos = session.data.where() #we cannot use where() since we don't know if a content linked block before our current position gets new items in the duplication process
#TODO: when duplicating with content links or at least inside a content link the selection gets lost completely. Maybe this cannot be avoided, but review that in the future.
customBuffer = session.data.copyObjects(writeInSessionBuffer = False)
if customBuffer:
if customBuffer: #success
session.data.goToSelectionStart()
pasteObjects(customBuffer = customBuffer, keepCursorState = True, overwriteSelection = False)
pos = session.data.where() #where even keeps the local position if a content linked block inserts items before our position
pasteObjects(customBuffer = customBuffer, updateCursor = False, overwriteSelection = False)
session.data.goTo(*pos)
callbacks._setCursor(destroySelection=False)
else:
item = session.data.currentItem()
if item:
insertItem(item.copy())
callbacks._setCursor()
#Score
def transposeScore(rootPitch, targetPitch):
"""Based on automatic transpose. The interval is caculated from two pitches.
@ -521,6 +499,11 @@ def transposeScore(rootPitch, targetPitch):
session.history.register(lambda r=rootPitch,t=targetPitch: transposeScore(t,r), descriptionString="transpose score")
_updateCallbackAllTracks()
def useCurrentTrackAsMetronome():
"""This is called once after loading/creating a session in startEngine"""
session.data.currentMetronomeTrack = session.data.currentTrack()
setMetronome(session.data.currentMetronomeTrack.asMetronomeData, label=session.data.currentMetronomeTrack.name) #template api. has callbacks
#Tracks
def insertTrack(atIndex, trackObject):
moveFunction = _createLambdaMoveToForCurrentPosition()
@ -541,6 +524,7 @@ def newEmptyTrack():
insertTrack(newIndex, newTrack) #handles callbacks and undo
return (id(newTrack))
def deleteTrack(trId):
"""Can not delete hidden tracks because these don't implement undo.
A hidden track is already considered "deleted" by the program.
@ -2135,6 +2119,15 @@ def insertTempoChangeDuringDuration(percentageUnitsPerMinuteAsFloat):
session.history.clear() #TODO: This registers two times undo
def currentTempoScalingFactor():
return session.data.tempoTrack.factor
def changeTempoScaling(factor:float):
"""The factor is always a factor from x1, not from the previous
value"""
session.data.tempoTrack.setFactor(float(factor))
callbacks._tempoScalingChanged(session.data.tempoTrack.factor)
#Toolbox
#High Level commands

3
engine/block.py

@ -152,7 +152,8 @@ class Block(object):
copyItem = item.copy()
new.data.append(copyItem)
copyItem.parentBlocks.add(new) #parentBlock was empty until now
assert not copyItem.parentBlocks #parentBlock was empty until now
copyItem.parentBlocks.add(new)
if self in copyItem.parentBlocks: #TODO: investigate
copyItem.parentBlocks.remove(self)

2
engine/config.py

@ -36,7 +36,7 @@ METADATA={
#How many audio outputs do you want? must be pairs. These are just unconnected jack outputs
#that need to be connected internally to instrument outputs like fluidsynth
"cboxOutputs" : 2 * 0,
"cboxOutputs" : 2 * 1, #metronome
#Various strings for the README
#Extra whitespace will be stripped so we don't need to worry about docstring indentation

7
engine/graphtracks.py

@ -1313,6 +1313,13 @@ class TempoTrack(GraphTrackCC):
assert self.blocks
@property
def factor(self):
return self.parentData.tempoMap.factor
def setFactor(self, factor:float):
self.parentData.tempoMap.setFactor(factor)
def lilypond(self):
"""Based on the static export"""
def _ly(tempoItem, nextTempoItem):

9
engine/items.py

@ -745,7 +745,12 @@ class Item(object):
@property
def parentTracks(self):
return [block.parentTrack for block in self.parentBlocks]
#return (block.parentTrack for block in self.parentBlocks[0])
return (block.parentTrack for block in list(self.parentBlocks)[0].linkedContentBlocksInScore())
@property
def parentTrackIds(self):
return (id(tr) for tr in self.parentTracks)
def _copy(self):
"""return an independent copy of self"""
@ -758,7 +763,7 @@ class Item(object):
def copyParentBlocks(self, oldItem):
"""Move the oldItems parentBlocks to the new item"""
self.parentBlocks = oldItem.parentBlocks
#self.parentBlocks = oldItem.parentBlocks #TODO. We took that out when pasting after deleting a track and recreating failed. Why do we to copy the parentBlocks when a parentBlock is added during block.insert anyway? Wild guess: we don't.
def logicalDuration(self):
return 0

23
engine/main.py

@ -23,12 +23,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging; logging.info("import {}".format(__file__))
#Standar Library
import sys
#3rd Party
#Template
import template.engine.sequencer
from template.engine.metronome import Metronome
#Our modules
from .block import Block
@ -56,11 +56,12 @@ class Data(template.engine.sequencer.Score):
self.cursorWhenSelectionStarted = None #A cursor dict, from self.cursorExport(). Is None when there is no selection. Can be used for "if selection:" questions. Gets changed quite often.
self.copyObjectsBuffer = [] #for copy and paste. obviously empty after load file. Also not saved.
self.cachedTrackDurations = {} #updated after every track export
self.metronome = Metronome(parentData=self) #Purely dynamic structure. No save/load. No undo/redo
self.currentMetronomeTrack = None #A Laborejo Track, indepedent of currentTrack. The metronome is in self.metronome, set by the template Score.
def duration(self):
"""Return the duration of the whole score, in ticks"""
#TODO: use cached duration? How often is this used?
#TODO: use cached duration? How often is this used? Pretty often. 3 Times for a single track note update.
#TODO: Measure before trying to improve performance.
result = []
for track in self.tracks:
result.append(track.duration())
@ -580,10 +581,11 @@ class Data(template.engine.sequencer.Score):
if self.cursorWhenSelectionStarted and overwriteSelection:
listOfChangedTrackIdsFromDelete = self.deleteSelection()
startPosition = self.where()
startItem = self.currentItem()
#TODO: check if the cursor is still in the correct position after delete selection
listOfChangedTrackIds = set()
for number, track in enumerate(workBuffer):
curTrack = self.currentTrack()
#Here comes an additional condition. Most of the time pastes are single-track. If so, or we simply are in the starting track, we want to paste at the actual position, not the tickindex. This circumvents problems with block boundaries and zero-duration items.
@ -597,8 +599,8 @@ class Data(template.engine.sequencer.Score):
for item in track:
newItem = item.copy()
curTrack.insert(newItem) #eventhough copyObjectsBuffer is already a copy of the original notes we don't want every paste to be the same instances.
for parentBlock in newItem.parentBlocks: #TODO: just be naive and add them all for now. Performance can be improved later.
listOfChangedTrackIds.add(id(parentBlock.parentTrack))
#newItem has now a parentBlock and a parentTrack. We add this parent track to the list of changed Tracks
listOfChangedTrackIds.update(newItem.parentTrackIds) #TODO: profiling. All items in a block have the same parentTrack. This is highly redundant BUT can be different for items which originated from block boundaries
if not number+1 == len(workBuffer): #enumerate from 0, len from 1.
#we have to prevent the track going down one too far in the last step and messing with the tick index though.
self.trackDown()
@ -609,10 +611,13 @@ class Data(template.engine.sequencer.Score):
self.trackUp()
assert self.trackIndex == trackIndexStart == startPosition[0]
assert self.currentTrack().state.tickindex == finalTickIndex #this is not enough to pinpoint the real location.
#Return to the item where pasting starting. Pasting counts as insert, and insert sticks to the item right of cursor.
#Therefore we go to the starting track and block, but not to the starting localCursorIndexInBlock. Instead we search for the specific item in a second step
self.goTo(trackIndex=startPosition[0], blockindex=startPosition[1], localCursorIndexInBlock=0)
self.goToItemInCurrentBlock(startItem) #we actually want to be at the same item where we started, not just the tick index which gets confused with block boundaries and zero duration items
try: #overwrite?
try: #overwrite? #TODO: What is that? assert with AssertionError pass?
assert listOfChangedTrackIds == listOfChangedTrackIdsFromDelete
except AssertionError:
pass
@ -811,12 +816,14 @@ class Data(template.engine.sequencer.Score):
raise ValueError("graphItem with this id not in any track")
def goTo(self, trackIndex, blockindex, localCursorIndexInBlock):
"""Handles even shifting positions in a content link situation"""
self.trackIndex = trackIndex
self.currentTrack().toBlockAndLocalCursorIndex(blockindex, localCursorIndexInBlock)
assert self.where() == (trackIndex, blockindex, localCursorIndexInBlock)
def where(self):
"""return the data needed by self.goto"""
"""return the data needed by self.goto
where even keeps the local position if a content linked block inserts items before our position """
return self.trackIndex, self.currentTrack().state.blockindex, self.currentTrack().currentBlock().localCursorIndex
def goToItemInCurrentBlock(self, itemInstance):

2
engine/resources/metronome.sfz

@ -0,0 +1,2 @@
<region> key=77 sample=samples/normal.wav count=1 //normal metronome tick
<region> key=76 sample=samples/stressed.wav count=1 //stressed metronome tick

0
engine/resources/normal.wav → engine/resources/samples/normal.wav

0
engine/resources/stressed.wav → engine/resources/samples/stressed.wav

19
engine/track.py

@ -369,8 +369,8 @@ class TrackState(object):
class Track(object):
allTracks = WeakValueDictionary() #key is the trackId, value is the weak reference to the Track. Deleted tracks (from the current session) and hidden tracks are in here as well.
def __repr__(self) -> str:
return f"Laborejo Track: {self.sequencerInterface.name}"
#def __repr__(self) -> str:
# return f"Laborejo Track: {self.sequencerInterface.name}"
def __init__(self, parentData, name=None):
self.parentData = parentData
@ -396,6 +396,8 @@ class Track(object):
self.initialInstrumentName = "" #different than the track name. e.g. "Violin"
self.initialShortInstrumentName = "" # e.g. "vl"
self.asMetronomeData = None #This track as metronome version. Is always up to date through export.
self._processAfterInit()
def _processAfterInit(self):
@ -1060,10 +1062,10 @@ class Track(object):
#########
#Metronome start
#The metronome cannot be calculated by simply looking at metricalInstructions. We need to look at the barlines. Two instructions in a row at the same tick are wrong, but technically possible. These are not two full measures of metronome
#TODO: metronome activate
#if self is self.parentData.currentMetronomeTrack:
# self.parentData.metronome.generate(((pos, m.isMetrical, m.treeOfInstructions) for pos, m in barlines.items()))
#The metronome cannot be calculated by simply looking at metricalInstructions.
#We need to look at the barlines. Two instructions in a row at the same tick are wrong,
#but technically possible. These are not two full measures of metronome
self.asMetronomeData = tuple((pos, m.isMetrical, m.treeOfInstructions) for pos, m in barlines.items())
#Metronome end. Nothing below is connected with the metronome subtrack.
#Calculate the beam positions for the static groups.
@ -1152,10 +1154,5 @@ class Track(object):
self.toPosition(originalPosition, strict = False) #has head() in it
return result
#Dependency Injections.
template.engine.sequencer.Score.TrackClass = Track #Score will look for Track in its module.

10
qtgui/menu.py

@ -539,10 +539,11 @@ class ToolBarMetronome(QtWidgets.QCheckBox):
return False
def updateFromCallback(self, exportDict):
"""Gets the metronome dict"""
self.blockSignals(True)
self.setStatus()
assert self.isChecked() == exportDict["enabled"], (self.isChecked(), exportDict["enabled"])
self.setText("Metronome: " + exportDict["trackName"])
self.setText("Metronome: " + exportDict["label"])
self.blockSignals(False)
def changed(self, value):
@ -554,10 +555,10 @@ class ToolBarPlaybackSpeed(QtWidgets.QDoubleSpinBox):
super().__init__()
self.setRange(0.1,3.0)
self.setDecimals(1)
self.setValue(1.0)
self.setValue(api.currentTempoScalingFactor())
self.setSingleStep(0.1)
self.setPrefix("Tempo ×") #yes, this is the unicode multiplication sign × and not an x
api.callbacks.playbackSpeedChanged.append(self.updateFromCallback)
api.callbacks.tempoScalingChanged.append(self.updateFromCallback)
self.valueChanged.connect(self.changed)
def updateFromCallback(self, newValue):
@ -566,5 +567,4 @@ class ToolBarPlaybackSpeed(QtWidgets.QDoubleSpinBox):
self.blockSignals(False)
def changed(self):
api.playbackChangeSpeed(self.value())
constantsAndConfigs.ccViewValue = self.value() #this sends a call back to all CC spinboxes in the whole program
api.changeTempoScaling(self.value())

29
qtgui/scoreview.py

@ -23,7 +23,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#Third Party
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5 import QtCore, QtGui, QtWidgets, QtOpenGL
#from PyQt5 import QtOpenGL
#Template Modules
@ -40,21 +40,20 @@ class ScoreView(QtWidgets.QGraphicsView):
super().__init__()
self.mainWindow = mainWindow
#OpenGL has a huge positive impact on performance
if True: #for testing
viewport = QtOpenGL.QGLWidget(QtOpenGL.QGLFormat(QtOpenGL.QGL.SampleBuffers))
viewport.format().setSwapInterval(0) #disable VSync.
viewport.setAutoFillBackground(False)
viewport = QtWidgets.QOpenGLWidget()
viewportFormat = QtGui.QSurfaceFormat()
viewportFormat.setSwapInterval(0) #disable VSync
#viewportFormat.setSamples(2**8) #By default, the highest number of samples available is used.
viewportFormat.setDefaultFormat(viewportFormat)
viewport.setFormat(viewportFormat)
self.setViewport(viewport)
#TODO: Measure OpenGL performance impact. Make sure fallback works
"""
viewport = QtOpenGL.QGLWidget(QtOpenGL.QGLFormat(QtOpenGL.QGL.SampleBuffers))
viewport.format().setSwapInterval(0) #disable VSync.
viewport.setAutoFillBackground(False)
viewport = QtWidgets.QOpenGLWidget()
viewportFormat = QtGui.QSurfaceFormat()
viewportFormat.setSwapInterval(0) #disable VSync
viewportFormat.setSamples(2**8)
viewportFormat.setDefaultFormat(viewportFormat)
viewport.setFormat(viewportFormat)
self.setViewport(viewport)
"""
self.setAlignment(QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
#self.setDragMode(QtWidgets.QGraphicsView.RubberBandDrag)

41
qtgui/structures.py

@ -22,7 +22,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
from math import log
from PyQt5 import QtCore, QtGui, QtWidgets
from .items import staticItem2Item, GuiTieCurveGraphicsItem, GuiLiveNote
from .items import staticItem2Item, GuiTieCurveGraphicsItem
from .constantsAndConfigs import constantsAndConfigs
from .cursor import Cursor, Playhead, Selection
from .conductor import Conductor, ConductorTransparentBlock
@ -517,16 +517,6 @@ class GuiTrack(QtWidgets.QGraphicsItem):
backgroundColor.setOpacity(1)
for tbh in self.transparentBlockHandles:
tbh.blockMode()
def recordingStreamNoteOn(self, liveNoteData):
guiLiveNote = GuiLiveNote(parent=self, liveNoteData=liveNoteData)
guiLiveNote.setX(liveNoteData["tickindex"] / constantsAndConfigs.ticksToPixelRatio)
self.parentScore.liveMidiWaitingForNoteOff[liveNoteData["midipitch"]] = guiLiveNote
def recordingStreamNoteOff(self, liveNoteData):
guiLiveNote = self.parentScore.liveMidiWaitingForNoteOff[liveNoteData["midipitch"]]
guiLiveNote.update(liveNoteData["duration"])
del self.parentScore.liveMidiWaitingForNoteOff[liveNoteData["midipitch"]]
class GuiScore(QtWidgets.QGraphicsScene):
def __init__(self, parentView):
@ -541,8 +531,6 @@ class GuiScore(QtWidgets.QGraphicsScene):
self._deleteOnIdleLoop.start(0) #0 means "if there is time"
self._deleteOnIdleLoop.timeout.connect(self._deleteOnIdle) #processes deleteOnIdleStack
self.liveMidiWaitingForNoteOff = {} #all unfinished midi notes (key is currently held down). We use one for all tracks because we have a callback in the cursor update function that iterates over all live notes. We don't want that to iterate over all tracks just to find the current live notes.
self.duringTrackDragAndDrop = None #switched to a QGraphicsItem (e.g. GuiTrack) while a track is moved around by the mouse
self.duringBlockDragAndDrop = None #switched to a QGraphicsItem (e.g. GuiTrack) while a block is moved around by the mouse
@ -584,11 +572,6 @@ class GuiScore(QtWidgets.QGraphicsScene):
api.callbacks.updateGraphBlockTrack.append(self.updateGraphBlockTrack)
api.callbacks.graphCCTracksChanged.append(self.syncCCsToBackend)
#api.callbacks.recordingStreamNoteOn.append(self.recordingStreamNoteOn)
#api.callbacks.recordingStreamNoteOff.append(self.recordingStreamNoteOff)
#api.callbacks.setPlaybackTicks.append(self._drawLiveMidiRecording)
#api.callbacks.recordingStreamClear.append(self.recordingStreamClear)
def updateMode(self, nameAsString):
assert nameAsString in constantsAndConfigs.availableEditModes
@ -665,24 +648,6 @@ class GuiScore(QtWidgets.QGraphicsScene):
self.removeWhenIdle(self.tracks[trackId].ccPaths[cc])
del self.tracks[trackId].ccPaths[cc]
def recordingStreamNoteOn(self, trackId, liveChord):
if trackId in self.tracks:
self.tracks[trackId].recordingStreamNoteOn(liveChord)
#else:
#hidden track.
#self.parentView.updateMode()
#self.updateSceneRect()
def recordingStreamNoteOff(self, trackId, liveChord):
if trackId in self.tracks:
self.tracks[trackId].recordingStreamNoteOff(liveChord)
#else:
#hidden track.
#self.parentView.updateMode()
#self.updateSceneRect()
def recordingStreamClear(self):
removeInstancesFromScene(GuiLiveNote)
def redraw(self, listOfStaticTrackRepresentations):
"""The order of guiTracks depends on the backend index.
@ -762,10 +727,6 @@ class GuiScore(QtWidgets.QGraphicsScene):
self.removeItem(deleteMe) #This is the only line in the program that should call scene.removeItem
del deleteMe
def _drawLiveMidiRecording(self, tickPosition):
for midiPitch, guiLiveNote in self.liveMidiWaitingForNoteOff.items():
guiLiveNote.update(tickPosition - guiLiveNote.liveNoteData["tickindex"])
def trackAt(self, qScenePosition):
"""trackAt always returns the full GuiTrack, even if in ccEdit mode."""

Loading…
Cancel
Save