Browse Source

Rework song editor context menus: Streamline groups, add duplicate group and clean transpositions of group, add replace pattern with other track to track label context menu

master
Nils 3 years ago
parent
commit
531c4e4f16
  1. 3
      CHANGELOG
  2. 45
      engine/api.py
  3. 10
      engine/pattern.py
  4. BIN
      qtgui/resources/translations/de.qm
  5. 50
      qtgui/resources/translations/de.ts
  6. 32
      qtgui/songeditor.py

3
CHANGELOG

@ -1,4 +1,7 @@
2019-10-15 Version 1.4
New context option for track labels: Replace pattern with one from other track
Streamline existing context menu options for measure groups
New context options for measure group: duplicate and clean transpositions
Activate the first pattern for all three tracks on a new project to ease up starting
Set default drums track for new projects to use GM Drum note names and start with a good scale
Use Median Velocity for new notes, not average.

45
engine/api.py

@ -171,7 +171,7 @@ from template.engine.api import callbacks
_templateStartEngine = startEngine
def updatePlayback():
#TODO: use template.sequencer.py internal updates instead
#TODO: use template.sequencer.py internal updates instead
cbox.Document.get_song().update_playback()
def startEngine(nsmClient):
@ -232,7 +232,7 @@ def seek(value):
cbox.Transport.seek_ppqn(value)
##Score
def set_quarterNotesPerMinute(value:int):
def set_quarterNotesPerMinute(value):
if value is None:
session.data.tempoMap.isTransportMaster = False #triggers rebuild
elif value == "on":
@ -242,7 +242,7 @@ def set_quarterNotesPerMinute(value:int):
else:
assert value > 0
session.data.tempoMap.setQuarterNotesPerMinute(value)
session.data.tempoMap.isTransportMaster = True #triggers rebuild
session.data.tempoMap.isTransportMaster = True #triggers rebuild
#Does not need track rebuilding
updatePlayback()
callbacks._quarterNotesPerMinuteChanged()
@ -419,6 +419,18 @@ def trackMergeCopyFrom(sourceTrackId, targetTrackId):
targetTrack.buildTrack()
updatePlayback()
callbacks._trackStructureChanged(targetTrack)
def trackPatternReplaceFrom(sourceTrackId, targetTrackId):
if not sourceTrackId == targetTrackId:
sourceTrack = session.data.trackById(sourceTrackId)
targetTrack = session.data.trackById(targetTrackId)
copyPattern = sourceTrack.pattern.copy(newParentTrack = targetTrack)
targetTrack.pattern = copyPattern
targetTrack.buildTrack()
updatePlayback()
callbacks._patternChanged(targetTrack)
def setSwitchScaleTranspose(trackId, position, transpose):
"""Scale transposition is flipped. lower value means higher pitch"""
@ -448,6 +460,33 @@ def insertSilence(howMany, beforeMeasureNumber):
session.data.buildAllTracks()
updatePlayback()
def duplicateSwitchGroup(startMeasureForGroup:int, endMeasureExclusive:int):
groupSize = endMeasureExclusive-startMeasureForGroup
insertSilence(groupSize, endMeasureExclusive)
for track in session.data.tracks:
for switch in range(startMeasureForGroup+groupSize, endMeasureExclusive+groupSize): #One group after the given one.
if switch-groupSize in track.structure:
track.structure.add(switch)
if switch-groupSize in track.whichPatternsAreScaleTransposed:
track.whichPatternsAreScaleTransposed[switch] = track.whichPatternsAreScaleTransposed[switch-groupSize]
if switch-groupSize in track.whichPatternsAreHalftoneTransposed:
track.whichPatternsAreHalftoneTransposed[switch] = track.whichPatternsAreHalftoneTransposed[switch-groupSize]
callbacks._trackStructureChanged(track)
session.data.buildAllTracks()
updatePlayback()
def clearSwitchGroupTranspositions(startMeasureForGroup:int, endMeasureExclusive:int):
for track in session.data.tracks:
for switch in range(startMeasureForGroup, endMeasureExclusive):
if switch in track.whichPatternsAreScaleTransposed:
del track.whichPatternsAreScaleTransposed[switch]
if switch in track.whichPatternsAreHalftoneTransposed:
del track.whichPatternsAreHalftoneTransposed[switch]
callbacks._trackStructureChanged(track)
session.data.buildAllTracks()
updatePlayback()
def deleteSwitches(howMany, fromMeasureNumber):
for track in session.data.tracks:
new_structure = set()

10
engine/pattern.py

@ -59,7 +59,7 @@ class Pattern(object):
self.parentTrack = parentTrack
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.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
self._processAfterInit()
def _prepareBeforeInit(self):
@ -75,6 +75,14 @@ class Pattern(object):
self._builtPatternCache = {} #holds a ready cbox pattern for a clip as value. Key is a tuple of hashable parameters. see self.buildPattern
def copy(self, newParentTrack):
"""Return an independent copy of this pattern"""
data = [note.copy() for note in self.data] #list of mutable dicts. Dicts have only primitve data types inside
scale = self.scale #it is immutable so there is no risk of changing it in place for both patterns at once
simpleNoteNames = self.simpleNoteNames[:] #this mutable list always gets replaced completely by setting a new list, but we don't want to take any chances and create a slice copy.
result = Pattern(newParentTrack, data, scale, simpleNoteNames)
return result
@property
def scale(self):
return self._scale

BIN
qtgui/resources/translations/de.qm

Binary file not shown.

50
qtgui/resources/translations/de.ts

@ -194,12 +194,32 @@
<message>
<location filename="../../songeditor.py" line="325"/>
<source>Insert {} empty measures before no. {}</source>
<translation>{} leere Takte vor Takt {} einfügen</translation>
<translation type="obsolete">{} leere Takte vor Takt {} einfügen</translation>
</message>
<message>
<location filename="../../songeditor.py" line="326"/>
<source>Delete {} measures from no. {} on</source>
<translation>Lösche {} Takte von Takt {} beginnend</translation>
<translation type="obsolete">Lösche {} Takte von Takt {} beginnend</translation>
</message>
<message>
<location filename="../../songeditor.py" line="328"/>
<source>Insert empty group before this one</source>
<translation>Leere Taktgruppe vor dieser einfügen</translation>
</message>
<message>
<location filename="../../songeditor.py" line="329"/>
<source>Delete whole group</source>
<translation>Lösche diese Taktgruppe</translation>
</message>
<message>
<location filename="../../songeditor.py" line="330"/>
<source>Duplicate whole group including measures</source>
<translation>Verdopple diese Taktgruppe inkl. Struktur</translation>
</message>
<message>
<location filename="../../songeditor.py" line="331"/>
<source>Clear all group transpositions</source>
<translation>Setze alle Transpositionen dieser Taktgruppe zurück</translation>
</message>
</context>
<context>
@ -324,12 +344,12 @@
<context>
<name>TrackLabel</name>
<message>
<location filename="../../songeditor.py" line="623"/>
<location filename="../../songeditor.py" line="643"/>
<source>grab and move to reorder tracks</source>
<translation>mit der maus halten und ziehen um Spuren anzuordnen</translation>
</message>
<message>
<location filename="../../songeditor.py" line="628"/>
<location filename="../../songeditor.py" line="648"/>
<source>change track color</source>
<translation>setze Farbe der Spur</translation>
</message>
@ -337,34 +357,44 @@
<context>
<name>TrackLabelContext</name>
<message>
<location filename="../../songeditor.py" line="575"/>
<location filename="../../songeditor.py" line="580"/>
<source>Invert Measures</source>
<translation>Taktauswahl umdrehen</translation>
</message>
<message>
<location filename="../../songeditor.py" line="576"/>
<location filename="../../songeditor.py" line="581"/>
<source>All Measures On</source>
<translation>Alle Takte anschalten</translation>
</message>
<message>
<location filename="../../songeditor.py" line="577"/>
<location filename="../../songeditor.py" line="582"/>
<source>All Measures Off</source>
<translation>Alle Takte ausschalten</translation>
</message>
<message>
<location filename="../../songeditor.py" line="578"/>
<location filename="../../songeditor.py" line="583"/>
<source>Clone this Track</source>
<translation>Spur klonen</translation>
</message>
<message>
<location filename="../../songeditor.py" line="579"/>
<location filename="../../songeditor.py" line="584"/>
<source>Delete Track</source>
<translation>Spur löschen</translation>
</message>
<message>
<location filename="../../songeditor.py" line="595"/>
<source>Merge/Copy from</source>
<translation>Übernimm Struktur von</translation>
<translation type="obsolete">Übernimm Struktur von</translation>
</message>
<message>
<location filename="../../songeditor.py" line="600"/>
<source>Merge/Copy Measure-Structure from</source>
<translation>Übernimm und ergänze Struktur von</translation>
</message>
<message>
<location filename="../../songeditor.py" line="615"/>
<source>Replace Pattern with</source>
<translation>Ersetze Noten des Taktes durch</translation>
</message>
</context>
<context>

32
qtgui/songeditor.py

@ -172,7 +172,7 @@ class SongEditor(QtWidgets.QGraphicsScene):
barline.setPen(self.normalPen)
class TrackStructure(QtWidgets.QGraphicsRectItem):
"""From left to right. Holds two lines to show the "stafflinen" and a number of switches,
"""From left to right. Holds two lines to show the "staffline" and a number of switches,
colored rectangles to indicate where a pattern is activated on the timeline"""
def __init__(self, parentScene):
@ -320,10 +320,15 @@ class TrackStructure(QtWidgets.QGraphicsRectItem):
position = self.scenePos2switchPosition(event.scenePos().x()) #measure number 0 based
measuresPerGroup = self.parentScene.measuresPerGroupCache
offset = position % measuresPerGroup
startMeasureForGroup = position - offset
endMeasureExclusive = startMeasureForGroup + measuresPerGroup
listOfLabelsAndFunctions = [
(QtCore.QCoreApplication.translate("SongStructure", "Insert {} empty measures before no. {}").format(measuresPerGroup, position+1), lambda: api.insertSilence(howMany=measuresPerGroup, beforeMeasureNumber=position)),
(QtCore.QCoreApplication.translate("SongStructure", "Delete {} measures from no. {} on").format(measuresPerGroup, position+1), lambda: api.deleteSwitches(howMany=measuresPerGroup, fromMeasureNumber=position)),
(QtCore.QCoreApplication.translate("SongStructure", "Insert empty group before this one").format(measuresPerGroup), lambda: api.insertSilence(howMany=measuresPerGroup, beforeMeasureNumber=startMeasureForGroup)),
(QtCore.QCoreApplication.translate("SongStructure", "Delete whole group").format(measuresPerGroup), lambda: api.deleteSwitches(howMany=measuresPerGroup, fromMeasureNumber=startMeasureForGroup)),
(QtCore.QCoreApplication.translate("SongStructure", "Duplicate whole group including measures"), lambda: api.duplicateSwitchGroup(startMeasureForGroup, endMeasureExclusive)),
(QtCore.QCoreApplication.translate("SongStructure", "Clear all group transpositions"), lambda: api.clearSwitchGroupTranspositions(startMeasureForGroup, endMeasureExclusive)),
]
for text, function in listOfLabelsAndFunctions:
@ -592,8 +597,8 @@ class TrackLabelEditor(QtWidgets.QGraphicsScene):
a.triggered.connect(function)
#Add a submenu for merge/copy
mergeMenu = menu.addMenu(QtCore.QCoreApplication.translate("TrackLabelContext", "Merge/Copy from"))
mergeMenu = menu.addMenu(QtCore.QCoreApplication.translate("TrackLabelContext", "Merge/Copy Measure-Structure from"))
def createCopyMergeLambda(srcId):
return lambda: api.trackMergeCopyFrom(srcId, exportDict["id"])
@ -606,6 +611,21 @@ class TrackLabelEditor(QtWidgets.QGraphicsScene):
a.setEnabled(False)
a.triggered.connect(mergeCommand)
#Add a submenu for pattern merge/copy
copyMenu = menu.addMenu(QtCore.QCoreApplication.translate("TrackLabelContext", "Replace Pattern with"))
def replacePatternWithLambda(srcId):
return lambda: api.trackPatternReplaceFrom(srcId, exportDict["id"])
for track in self.tracks.values():
sourceDict = track.exportDict
a = QtWidgets.QAction(sourceDict["sequencerInterface"]["name"], copyMenu)
copyMenu.addAction(a)
mergeCommand = replacePatternWithLambda(sourceDict["id"])
if sourceDict["id"] == exportDict["id"]:
a.setEnabled(False)
a.triggered.connect(mergeCommand)
pos = QtGui.QCursor.pos()
pos.setY(pos.y() + 5)
self.parentView.parentMainWindow.setFocus()

Loading…
Cancel
Save