Browse Source

Fixy fixy

master
Nils 5 years ago
parent
commit
0a64e0f487
  1. 103
      engine/api.py
  2. 7
      engine/block.py
  3. 2
      engine/config.py
  4. 7
      engine/graphtracks.py
  5. 13
      engine/items.py
  6. 27
      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. 33
      qtgui/scoreview.py
  13. 43
      qtgui/structures.py

103
engine/api.py

@ -58,7 +58,6 @@ class ClientCallbacks(Callbacks): #inherits from the templates api callbacks
#self.playbackStart = [] #self.playbackStart = []
#self.playbackStop = [] #self.playbackStop = []
self.playbackSpeedChanged = []
self._cachedTickIndex = -1 self._cachedTickIndex = -1
self.updateGraphTrackCC = [] self.updateGraphTrackCC = []
@ -68,9 +67,9 @@ class ClientCallbacks(Callbacks): #inherits from the templates api callbacks
self.updateTempoTrack = [] self.updateTempoTrack = []
self.updateTempoTrackBlocks = [] self.updateTempoTrackBlocks = []
self.updateTempoTrackMeta = [] self.updateTempoTrackMeta = []
self.tempoScalingChanged = []
self.prevailingBaseDurationChanged = [] self.prevailingBaseDurationChanged = []
self.metronomeChanged = []
#self.recordingStreamNoteOn = [] #self.recordingStreamNoteOn = []
#self.recordingStreamNoteOff = [] #self.recordingStreamNoteOff = []
@ -115,6 +114,8 @@ class ClientCallbacks(Callbacks): #inherits from the templates api callbacks
changedTracks, currentItem = session.data.currentContentLinkAndItsBlocksInAllTracks() changedTracks, currentItem = session.data.currentContentLinkAndItsBlocksInAllTracks()
for track in changedTracks: for track in changedTracks:
self._updateTrack(id(track)) self._updateTrack(id(track))
if session.data.currentMetronomeTrack is track:
setMetronome(track.asMetronomeData, label=track.name) #template api
def _updateTrack(self, trId): def _updateTrack(self, trId):
"""The most important function. Create a static representation of the music data which """The most important function. Create a static representation of the music data which
@ -178,7 +179,8 @@ class ClientCallbacks(Callbacks): #inherits from the templates api callbacks
def _tracksChanged(self): def _tracksChanged(self):
"""Track deleted, added or moved. Or toggled double/non-double. """Track deleted, added or moved. Or toggled double/non-double.
This callback is relatively cheap because it does not generate This callback is relatively cheap because it does not generate
any item data.""" any item data."""
#TODO: does NOT call template.api._numberOfTracksChanged
session.data.updateJackMetadataSorting() session.data.updateJackMetadataSorting()
if self.tracksChanged: 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. #TODO: but this also leads to centerOn cursor in the gui after every mouse click in the conductor.
#self._setCursor(destroySelection = False) #self._setCursor(destroySelection = False)
def _tempoScalingChanged(self, newValue):
for func in self.tempoScalingChanged:
func(newValue)
def _playbackStart(self): def _playbackStart(self):
for func in self.playbackStart: for func in self.playbackStart:
func() func()
@ -234,9 +240,6 @@ class ClientCallbacks(Callbacks): #inherits from the templates api callbacks
for func in self.playbackStop: for func in self.playbackStop:
func() func()
def _playbackSpeedChanged(self, newValue):
for func in self.playbackSpeedChanged:
func(newValue)
def _prevailingBaseDurationChanged(self, newPrevailingBaseDuration): def _prevailingBaseDurationChanged(self, newPrevailingBaseDuration):
for func in self.prevailingBaseDurationChanged: for func in self.prevailingBaseDurationChanged:
@ -252,18 +255,6 @@ class ClientCallbacks(Callbacks): #inherits from the templates api callbacks
for func in self.historyChanged: for func in self.historyChanged:
func(undoHistory, redoHistory) 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): def _recordingStreamNoteOn(self, liveChord):
"""One dict at a time""" """One dict at a time"""
trId = id(session.data.currentTrack()) trId = id(session.data.currentTrack())
@ -290,7 +281,7 @@ def startEngine(nsmClient):
_templateStartEngine(nsmClient) _templateStartEngine(nsmClient)
#Send initial Data etc. #Send initial Data etc.
session.data.tempoMap.isTransportMaster = True #always true for Laborejo. session.data.tempoMap.isTransportMaster = True #always true for Laborejo.
callbacks._tracksChanged() # This creates the frontend/GUI tracks with access through track ids. From now on we can send the GUI a trId and it knows which track needs change. callbacks._tracksChanged() # This creates the frontend/GUI tracks with access through track ids. From now on we can send the GUI a trId and it knows which track needs change.
for trId in session.data.listOfTrackIds(): for trId in session.data.listOfTrackIds():
callbacks._updateTrack(trId) #create content: music items callbacks._updateTrack(trId) #create content: music items
@ -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. 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. track.staticRepresentation() #that generates the calfbox data as a side effect. discard the other data.
useCurrentTrackAsMetronome()
callbacks._setCursor() callbacks._setCursor()
global laborejoEngineStarted #makes for a convenient check. stepMidiInput uses it, which needs to know that the gui already started the api. 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 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): def _changeBlockAndItemOrder(dictWithTrackIDsAndDataLists):
"""A helper function for deleteSelection, paste and other... """A helper function for deleteSelection, paste and other...
This makes it possible to undo/redo properly. It registers This makes it possible to undo/redo properly. It registers
@ -472,10 +450,10 @@ def copyObjects(): #ctrl+c
#The score doesn't change at all. No callback. #The score doesn't change at all. No callback.
#no undo. #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 """api.duplicate overrides default paste behaviour by providing its own copyBuffer
and not destroying the selection/keep the cursor at its origin position and not destroying the selection/keep the cursor at its origin position
""" """
dataBefore = session.data.getBlockAndItemOrder() dataBefore = session.data.getBlockAndItemOrder()
moveFunction = _createLambdaMoveToForCurrentPosition() moveFunction = _createLambdaMoveToForCurrentPosition()
listOfChangedTrackIDs = session.data.pasteObjects(customBuffer, overwriteSelection) listOfChangedTrackIDs = session.data.pasteObjects(customBuffer, overwriteSelection)
@ -488,31 +466,31 @@ def pasteObjects(customBuffer = None, keepCursorState = False, overwriteSelectio
#Make changes visible in the GUI #Make changes visible in the GUI
for trackId in listOfChangedTrackIDs: for trackId in listOfChangedTrackIDs:
callbacks._updateTrack(trackId) callbacks._updateTrack(trackId)
if keepCursorState: if updateCursor:
goTo()
callbacks._setCursor(destroySelection = False)
else:
callbacks._setCursor() callbacks._setCursor()
def duplicate(): #ctrl+d def duplicate(): #ctrl+d
"""Duplicate a single object and put it right of the original. Duplicate the entire selection """Duplicate a single object and put it right of the original. The cursor moves with it
and put the copy right of the last selected note. to enable follow up insertion.
Basically a special case of copy and paste that does not touch the clipboard."""
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: 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) customBuffer = session.data.copyObjects(writeInSessionBuffer = False)
if customBuffer: if customBuffer: #success
session.data.goToSelectionStart() 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
else: pasteObjects(customBuffer = customBuffer, updateCursor = False, overwriteSelection = False)
session.data.goTo(*pos)
callbacks._setCursor(destroySelection=False)
else:
item = session.data.currentItem() item = session.data.currentItem()
if item: if item:
insertItem(item.copy()) insertItem(item.copy())
callbacks._setCursor()
#Score #Score
def transposeScore(rootPitch, targetPitch): def transposeScore(rootPitch, targetPitch):
"""Based on automatic transpose. The interval is caculated from two pitches. """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") session.history.register(lambda r=rootPitch,t=targetPitch: transposeScore(t,r), descriptionString="transpose score")
_updateCallbackAllTracks() _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 #Tracks
def insertTrack(atIndex, trackObject): def insertTrack(atIndex, trackObject):
moveFunction = _createLambdaMoveToForCurrentPosition() moveFunction = _createLambdaMoveToForCurrentPosition()
@ -540,6 +523,7 @@ def newEmptyTrack():
newTrack = Track(session.data) newTrack = Track(session.data)
insertTrack(newIndex, newTrack) #handles callbacks and undo insertTrack(newIndex, newTrack) #handles callbacks and undo
return (id(newTrack)) return (id(newTrack))
def deleteTrack(trId): def deleteTrack(trId):
"""Can not delete hidden tracks because these don't implement undo. """Can not delete hidden tracks because these don't implement undo.
@ -2135,6 +2119,15 @@ def insertTempoChangeDuringDuration(percentageUnitsPerMinuteAsFloat):
session.history.clear() #TODO: This registers two times undo 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 #Toolbox
#High Level commands #High Level commands

7
engine/block.py

@ -151,8 +151,9 @@ class Block(object):
for item in self.data: for item in self.data:
copyItem = item.copy() copyItem = item.copy()
new.data.append(copyItem) 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 if self in copyItem.parentBlocks: #TODO: investigate
copyItem.parentBlocks.remove(self) copyItem.parentBlocks.remove(self)
@ -301,7 +302,7 @@ class Block(object):
def insert(self, item): def insert(self, item):
self.data.insert(self.localCursorIndex, item) #we do not need to check if appending or not. list.insert appends if the index is higher then len() self.data.insert(self.localCursorIndex, item) #we do not need to check if appending or not. list.insert appends if the index is higher then len()
#self.localCursorIndex += 1 #we don't need to go right here because track.insert() is calling its own right() directly after insert, which triggers block.right() #self.localCursorIndex += 1 #we don't need to go right here because track.insert() is calling its own right() directly after insert, which triggers block.right()
item.parentBlocks.add(self) item.parentBlocks.add(self)
def delete(self): def delete(self):
"""The commented out is the immediate garbage collector which """The commented out is the immediate garbage collector which

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 #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 #that need to be connected internally to instrument outputs like fluidsynth
"cboxOutputs" : 2 * 0, "cboxOutputs" : 2 * 1, #metronome
#Various strings for the README #Various strings for the README
#Extra whitespace will be stripped so we don't need to worry about docstring indentation #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 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): def lilypond(self):
"""Based on the static export""" """Based on the static export"""
def _ly(tempoItem, nextTempoItem): def _ly(tempoItem, nextTempoItem):

13
engine/items.py

@ -744,8 +744,13 @@ class Item(object):
@property @property
def parentTracks(self): 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): def _copy(self):
"""return an independent copy of self""" """return an independent copy of self"""
@ -757,8 +762,8 @@ class Item(object):
return new return new
def copyParentBlocks(self, oldItem): def copyParentBlocks(self, oldItem):
"""Move the oldItems parentBlocks to the new item""" """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): def logicalDuration(self):
return 0 return 0

27
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__)) import logging; logging.info("import {}".format(__file__))
#Standar Library #Standar Library
import sys
#3rd Party #3rd Party
#Template #Template
import template.engine.sequencer import template.engine.sequencer
from template.engine.metronome import Metronome
#Our modules #Our modules
from .block import Block 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.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.copyObjectsBuffer = [] #for copy and paste. obviously empty after load file. Also not saved.
self.cachedTrackDurations = {} #updated after every track export 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): def duration(self):
"""Return the duration of the whole score, in ticks""" """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 = [] result = []
for track in self.tracks: for track in self.tracks:
result.append(track.duration()) result.append(track.duration())
@ -506,7 +507,7 @@ class Data(template.engine.sequencer.Score):
result = [] result = []
if validSelection: if validSelection:
for track in selectedTracksAndItems: for track in selectedTracksAndItems:
result.append([item.copy() for item, cachedTrackState in track]) result.append([item.copy() for item, cachedTrackState in track])
if writeInSessionBuffer: if writeInSessionBuffer:
self.copyObjectsBuffer = result self.copyObjectsBuffer = result
return result return result
@ -580,10 +581,11 @@ class Data(template.engine.sequencer.Score):
if self.cursorWhenSelectionStarted and overwriteSelection: if self.cursorWhenSelectionStarted and overwriteSelection:
listOfChangedTrackIdsFromDelete = self.deleteSelection() listOfChangedTrackIdsFromDelete = self.deleteSelection()
startPosition = self.where()
startItem = self.currentItem()
#TODO: check if the cursor is still in the correct position after delete selection #TODO: check if the cursor is still in the correct position after delete selection
listOfChangedTrackIds = set() listOfChangedTrackIds = set()
for number, track in enumerate(workBuffer): for number, track in enumerate(workBuffer):
curTrack = self.currentTrack() 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. #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: for item in track:
newItem = item.copy() 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. 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. #newItem has now a parentBlock and a parentTrack. We add this parent track to the list of changed Tracks
listOfChangedTrackIds.add(id(parentBlock.parentTrack)) 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. 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. #we have to prevent the track going down one too far in the last step and messing with the tick index though.
self.trackDown() self.trackDown()
@ -609,10 +611,13 @@ class Data(template.engine.sequencer.Score):
self.trackUp() self.trackUp()
assert self.trackIndex == trackIndexStart == startPosition[0] assert self.trackIndex == trackIndexStart == startPosition[0]
assert self.currentTrack().state.tickindex == finalTickIndex #this is not enough to pinpoint the real location. assert self.currentTrack().state.tickindex == finalTickIndex #this is not enough to pinpoint the real location.
self.goTo(trackIndex=startPosition[0], blockindex=startPosition[1], localCursorIndexInBlock=0)
#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 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 assert listOfChangedTrackIds == listOfChangedTrackIdsFromDelete
except AssertionError: except AssertionError:
pass pass
@ -811,12 +816,14 @@ class Data(template.engine.sequencer.Score):
raise ValueError("graphItem with this id not in any track") raise ValueError("graphItem with this id not in any track")
def goTo(self, trackIndex, blockindex, localCursorIndexInBlock): def goTo(self, trackIndex, blockindex, localCursorIndexInBlock):
"""Handles even shifting positions in a content link situation"""
self.trackIndex = trackIndex self.trackIndex = trackIndex
self.currentTrack().toBlockAndLocalCursorIndex(blockindex, localCursorIndexInBlock) self.currentTrack().toBlockAndLocalCursorIndex(blockindex, localCursorIndexInBlock)
assert self.where() == (trackIndex, blockindex, localCursorIndexInBlock) assert self.where() == (trackIndex, blockindex, localCursorIndexInBlock)
def where(self): 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 return self.trackIndex, self.currentTrack().state.blockindex, self.currentTrack().currentBlock().localCursorIndex
def goToItemInCurrentBlock(self, itemInstance): 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): 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. 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: #def __repr__(self) -> str:
return f"Laborejo Track: {self.sequencerInterface.name}" # return f"Laborejo Track: {self.sequencerInterface.name}"
def __init__(self, parentData, name=None): def __init__(self, parentData, name=None):
self.parentData = parentData self.parentData = parentData
@ -395,6 +395,8 @@ class Track(object):
#The instrument names are also handled by the trackState so that the cursor knows about instrument changes #The instrument names are also handled by the trackState so that the cursor knows about instrument changes
self.initialInstrumentName = "" #different than the track name. e.g. "Violin" self.initialInstrumentName = "" #different than the track name. e.g. "Violin"
self.initialShortInstrumentName = "" # e.g. "vl" self.initialShortInstrumentName = "" # e.g. "vl"
self.asMetronomeData = None #This track as metronome version. Is always up to date through export.
self._processAfterInit() self._processAfterInit()
@ -1060,10 +1062,10 @@ class Track(object):
######### #########
#Metronome start #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 #The metronome cannot be calculated by simply looking at metricalInstructions.
#TODO: metronome activate #We need to look at the barlines. Two instructions in a row at the same tick are wrong,
#if self is self.parentData.currentMetronomeTrack: #but technically possible. These are not two full measures of metronome
# self.parentData.metronome.generate(((pos, m.isMetrical, m.treeOfInstructions) for pos, m in barlines.items())) self.asMetronomeData = tuple((pos, m.isMetrical, m.treeOfInstructions) for pos, m in barlines.items())
#Metronome end. Nothing below is connected with the metronome subtrack. #Metronome end. Nothing below is connected with the metronome subtrack.
#Calculate the beam positions for the static groups. #Calculate the beam positions for the static groups.
@ -1152,10 +1154,5 @@ class Track(object):
self.toPosition(originalPosition, strict = False) #has head() in it self.toPosition(originalPosition, strict = False) #has head() in it
return result return result
#Dependency Injections. #Dependency Injections.
template.engine.sequencer.Score.TrackClass = Track #Score will look for Track in its module. 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 return False
def updateFromCallback(self, exportDict): def updateFromCallback(self, exportDict):
"""Gets the metronome dict"""
self.blockSignals(True) self.blockSignals(True)
self.setStatus() self.setStatus()
assert self.isChecked() == exportDict["enabled"], (self.isChecked(), exportDict["enabled"]) assert self.isChecked() == exportDict["enabled"], (self.isChecked(), exportDict["enabled"])
self.setText("Metronome: " + exportDict["trackName"]) self.setText("Metronome: " + exportDict["label"])
self.blockSignals(False) self.blockSignals(False)
def changed(self, value): def changed(self, value):
@ -554,10 +555,10 @@ class ToolBarPlaybackSpeed(QtWidgets.QDoubleSpinBox):
super().__init__() super().__init__()
self.setRange(0.1,3.0) self.setRange(0.1,3.0)
self.setDecimals(1) self.setDecimals(1)
self.setValue(1.0) self.setValue(api.currentTempoScalingFactor())
self.setSingleStep(0.1) self.setSingleStep(0.1)
self.setPrefix("Tempo ×") #yes, this is the unicode multiplication sign × and not an x 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) self.valueChanged.connect(self.changed)
def updateFromCallback(self, newValue): def updateFromCallback(self, newValue):
@ -566,5 +567,4 @@ class ToolBarPlaybackSpeed(QtWidgets.QDoubleSpinBox):
self.blockSignals(False) self.blockSignals(False)
def changed(self): def changed(self):
api.playbackChangeSpeed(self.value()) api.changeTempoScaling(self.value())
constantsAndConfigs.ccViewValue = self.value() #this sends a call back to all CC spinboxes in the whole program

33
qtgui/scoreview.py

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

43
qtgui/structures.py

@ -22,7 +22,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
from math import log from math import log
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
from .items import staticItem2Item, GuiTieCurveGraphicsItem, GuiLiveNote from .items import staticItem2Item, GuiTieCurveGraphicsItem
from .constantsAndConfigs import constantsAndConfigs from .constantsAndConfigs import constantsAndConfigs
from .cursor import Cursor, Playhead, Selection from .cursor import Cursor, Playhead, Selection
from .conductor import Conductor, ConductorTransparentBlock from .conductor import Conductor, ConductorTransparentBlock
@ -517,16 +517,6 @@ class GuiTrack(QtWidgets.QGraphicsItem):
backgroundColor.setOpacity(1) backgroundColor.setOpacity(1)
for tbh in self.transparentBlockHandles: for tbh in self.transparentBlockHandles:
tbh.blockMode() 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): class GuiScore(QtWidgets.QGraphicsScene):
def __init__(self, parentView): def __init__(self, parentView):
@ -541,8 +531,6 @@ class GuiScore(QtWidgets.QGraphicsScene):
self._deleteOnIdleLoop.start(0) #0 means "if there is time" self._deleteOnIdleLoop.start(0) #0 means "if there is time"
self._deleteOnIdleLoop.timeout.connect(self._deleteOnIdle) #processes deleteOnIdleStack 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.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 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.updateGraphBlockTrack.append(self.updateGraphBlockTrack)
api.callbacks.graphCCTracksChanged.append(self.syncCCsToBackend) 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): def updateMode(self, nameAsString):
assert nameAsString in constantsAndConfigs.availableEditModes assert nameAsString in constantsAndConfigs.availableEditModes
@ -665,25 +648,7 @@ class GuiScore(QtWidgets.QGraphicsScene):
self.removeWhenIdle(self.tracks[trackId].ccPaths[cc]) self.removeWhenIdle(self.tracks[trackId].ccPaths[cc])
del 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): def redraw(self, listOfStaticTrackRepresentations):
"""The order of guiTracks depends on the backend index. """The order of guiTracks depends on the backend index.
This way it is a no-brainer, we don't need to maintain our own This way it is a no-brainer, we don't need to maintain our own
@ -762,10 +727,6 @@ class GuiScore(QtWidgets.QGraphicsScene):
self.removeItem(deleteMe) #This is the only line in the program that should call scene.removeItem self.removeItem(deleteMe) #This is the only line in the program that should call scene.removeItem
del deleteMe del deleteMe
def _drawLiveMidiRecording(self, tickPosition):
for midiPitch, guiLiveNote in self.liveMidiWaitingForNoteOff.items():
guiLiveNote.update(tickPosition - guiLiveNote.liveNoteData["tickindex"])
def trackAt(self, qScenePosition): def trackAt(self, qScenePosition):
"""trackAt always returns the full GuiTrack, even if in ccEdit mode.""" """trackAt always returns the full GuiTrack, even if in ccEdit mode."""

Loading…
Cancel
Save