Browse Source

add option to change midi channel of tracks

master
Nils 4 years ago
parent
commit
5a0ac31d2b
  1. 3
      CHANGELOG
  2. 21
      engine/api.py
  3. 4
      engine/pattern.py
  4. 12
      engine/track.py
  5. 10
      qtgui/songeditor.py
  6. 1
      template/documentation/readme.template

3
CHANGELOG

@ -1,3 +1,6 @@
2021-04-15 Version 2.1.0
Add option to change the midi channel for a track in the tracks context menu.
2021-01-15 Version 2.0.0 2021-01-15 Version 2.0.0
Big new features increase the MAJOR version. Old save files can still be loaded. Big new features increase the MAJOR version. Old save files can still be loaded.
The new features integrate seamlessly into the existing workflow: The new features integrate seamlessly into the existing workflow:

21
engine/api.py

@ -440,6 +440,19 @@ def changeTrackColor(trackId, colorInHex):
track.color = colorInHex track.color = colorInHex
callbacks._trackMetaDataChanged(track) callbacks._trackMetaDataChanged(track)
def changeTrackMidiChannel(trackId, newChannel:int):
"""newChannel is 1-16, we convert here to internal format 0-15.
Callbacks export data sends 1-16 again"""
if newChannel < 1 or newChannel > 16:
logger.warning(f"Midi Channel must be between 1-16 for this function, was: {newChannel}. Doing nothing.")
return
track = session.data.trackById(trackId)
track.midiChannel = newChannel-1
track.pattern.buildExportCache()
track.buildTrack()
updatePlayback()
callbacks._trackMetaDataChanged(track)
def addTrack(scale=None): def addTrack(scale=None):
if scale: if scale:
assert type(scale) == tuple assert type(scale) == tuple
@ -455,6 +468,7 @@ def createSiblingTrack(trackId):
newTrack = session.data.addTrack(name=track.sequencerInterface.name, scale=track.pattern.scale, color=track.color, simpleNoteNames=track.pattern.simpleNoteNames) #track name increments itself from "Track A" to "Track B" or "Track 1" -> "Track 2" newTrack = session.data.addTrack(name=track.sequencerInterface.name, scale=track.pattern.scale, color=track.color, simpleNoteNames=track.pattern.simpleNoteNames) #track name increments itself from "Track A" to "Track B" or "Track 1" -> "Track 2"
newTrack.pattern.averageVelocity = track.pattern.averageVelocity newTrack.pattern.averageVelocity = track.pattern.averageVelocity
newTrack.patternLengthMultiplicator = track.patternLengthMultiplicator newTrack.patternLengthMultiplicator = track.patternLengthMultiplicator
newTrack.midiChannel = track.midiChannel
jackConnections = cbox.JackIO.get_connected_ports(track.sequencerInterface.cboxPortName()) jackConnections = cbox.JackIO.get_connected_ports(track.sequencerInterface.cboxPortName())
for port in jackConnections: for port in jackConnections:
cbox.JackIO.port_connect(newTrack.sequencerInterface.cboxPortName(), port) cbox.JackIO.port_connect(newTrack.sequencerInterface.cboxPortName(), port)
@ -498,6 +512,9 @@ def setTrackPatternLengthMultiplicator(trackId, newMultiplicator:int):
callbacks._patternLengthMultiplicatorChanged(track) callbacks._patternLengthMultiplicatorChanged(track)
callbacks._patternChanged(track) callbacks._patternChanged(track)
#Track Switches #Track Switches
def setSwitches(trackId, setOfPositions, newBool): def setSwitches(trackId, setOfPositions, newBool):
@ -956,11 +973,11 @@ def resizePatternWithoutScale(trackId, steps):
def noteOn(trackId, row): def noteOn(trackId, row):
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
midipitch = track.pattern.scale[row] midipitch = track.pattern.scale[row]
cbox.send_midi_event(0x90, midipitch, track.pattern.averageVelocity, output=track.sequencerInterface.cboxMidiOutUuid) cbox.send_midi_event(0x90+track.midiChannel, midipitch, track.pattern.averageVelocity, output=track.sequencerInterface.cboxMidiOutUuid)
def noteOff(trackId, row): def noteOff(trackId, row):
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
midipitch = track.pattern.scale[row] midipitch = track.pattern.scale[row]
cbox.send_midi_event(0x80, midipitch, track.pattern.averageVelocity, output=track.sequencerInterface.cboxMidiOutUuid) cbox.send_midi_event(0x80+track.midiChannel, midipitch, track.pattern.averageVelocity, output=track.sequencerInterface.cboxMidiOutUuid)

4
engine/pattern.py

@ -354,8 +354,8 @@ class Pattern(object):
velocity = noteDict["velocity"] velocity = noteDict["velocity"]
pitch = self._cachedTransposedScale[noteDict["pitch"] + scaleTransposition] + halftoneTransposition pitch = self._cachedTransposedScale[noteDict["pitch"] + scaleTransposition] + halftoneTransposition
exportPattern += cbox.Pattern.serialize_event(startTick, 0x90, pitch, velocity) # note on exportPattern += cbox.Pattern.serialize_event(startTick, 0x90 + self.parentTrack.midiChannel, pitch, velocity) # note on
exportPattern += cbox.Pattern.serialize_event(endTick-1, 0x80, pitch, velocity) # note off #-1 ticks to create a small logical gap. Does not affect next note on. exportPattern += cbox.Pattern.serialize_event(endTick-1, 0x80 + self.parentTrack.midiChannel, pitch, velocity) # note off #-1 ticks to create a small logical gap. Does not affect next note on.
pattern = cbox.Document.get_song().pattern_from_blob(exportPattern, oneMeasureInTicks) pattern = cbox.Document.get_song().pattern_from_blob(exportPattern, oneMeasureInTicks)
self._builtPatternCache[cacheHash] = pattern self._builtPatternCache[cacheHash] = pattern

12
engine/track.py

@ -56,6 +56,7 @@ class Track(object): #injection at the bottom of this file!
#we take inspiration from the GUI that presents the Track on its own. #we take inspiration from the GUI that presents the Track on its own.
#The following setting is most likely to be found in the track sub-window: #The following setting is most likely to be found in the track sub-window:
self.patternLengthMultiplicator = 1 #int. >= 1 the multiplicator is added after all other calculations, like subdivions. We can't integrate this into howManyUnits because that is the global score value self.patternLengthMultiplicator = 1 #int. >= 1 the multiplicator is added after all other calculations, like subdivions. We can't integrate this into howManyUnits because that is the global score value
self.midiChannel = 0 # 0-15 midi channel is always set.
self.pattern = Pattern(parentTrack=self, scale=scale, simpleNoteNames=simpleNoteNames) self.pattern = Pattern(parentTrack=self, scale=scale, simpleNoteNames=simpleNoteNames)
self.structure = structure if structure else set() #see buildTrack(). This is the main track data structure besides the pattern. Just integers (starts at 0) as switches which are positions where to play the patterns. In between are automatic rests. self.structure = structure if structure else set() #see buildTrack(). This is the main track data structure besides the pattern. Just integers (starts at 0) as switches which are positions where to play the patterns. In between are automatic rests.
@ -109,6 +110,7 @@ class Track(object): #injection at the bottom of this file!
"whichPatternsAreScaleTransposed" : self.whichPatternsAreScaleTransposed, "whichPatternsAreScaleTransposed" : self.whichPatternsAreScaleTransposed,
"whichPatternsAreHalftoneTransposed" : self.whichPatternsAreHalftoneTransposed, "whichPatternsAreHalftoneTransposed" : self.whichPatternsAreHalftoneTransposed,
"patternLengthMultiplicator" : self.patternLengthMultiplicator, "patternLengthMultiplicator" : self.patternLengthMultiplicator,
"midiChannel" : self.midiChannel,
} }
@classmethod @classmethod
@ -116,11 +118,18 @@ class Track(object): #injection at the bottom of this file!
self = cls.__new__(cls) self = cls.__new__(cls)
self.parentData = parentData self.parentData = parentData
self.sequencerInterface = template.engine.sequencer.SequencerInterface.instanceFromSerializedData(self, serializedData["sequencerInterface"]) self.sequencerInterface = template.engine.sequencer.SequencerInterface.instanceFromSerializedData(self, serializedData["sequencerInterface"])
if "patternLengthMultiplicator" in serializedData: #Version 1.8
#Version 2.0+ changes
if "patternLengthMultiplicator" in serializedData:
self.patternLengthMultiplicator = serializedData["patternLengthMultiplicator"] self.patternLengthMultiplicator = serializedData["patternLengthMultiplicator"]
else: else:
self.patternLengthMultiplicator = 1 self.patternLengthMultiplicator = 1
if "midiChannel" in serializedData:
self.midiChannel = serializedData["midiChannel"]
else:
self.midiChannel = 0
self.color = serializedData["color"] self.color = serializedData["color"]
self.structure = set(serializedData["structure"]) self.structure = set(serializedData["structure"])
self.whichPatternsAreHalftoneTransposed = {int(k):int(v) for k,v in serializedData["whichPatternsAreHalftoneTransposed"].items()} #json saves dict keys as strings self.whichPatternsAreHalftoneTransposed = {int(k):int(v) for k,v in serializedData["whichPatternsAreHalftoneTransposed"].items()} #json saves dict keys as strings
@ -144,6 +153,7 @@ class Track(object): #injection at the bottom of this file!
"numberOfMeasures": self.parentData.numberOfMeasures, "numberOfMeasures": self.parentData.numberOfMeasures,
"whichPatternsAreScaleTransposed": self.whichPatternsAreScaleTransposed, "whichPatternsAreScaleTransposed": self.whichPatternsAreScaleTransposed,
"whichPatternsAreHalftoneTransposed": self.whichPatternsAreHalftoneTransposed, "whichPatternsAreHalftoneTransposed": self.whichPatternsAreHalftoneTransposed,
"midiChannel" : self.midiChannel+1, #1-16
} }
#Dependency Injections. #Dependency Injections.

10
qtgui/songeditor.py

@ -762,6 +762,16 @@ class TrackLabelEditor(QtWidgets.QGraphicsScene):
a.setEnabled(False) a.setEnabled(False)
a.triggered.connect(mergeCommand) a.triggered.connect(mergeCommand)
#Add a submenu to set the midi channel of this track. Highlight the current one
midiChannelMenu = menu.addMenu(QtCore.QCoreApplication.translate("TrackLabelContext", "Send on MIDI Channel"))
for mch in range(1, 17):
mchAction = QtWidgets.QAction(str(mch), midiChannelMenu)
midiChannelMenu.addAction(mchAction)
midiChannelCommand = lambda discard, chArg=mch: api.changeTrackMidiChannel(exportDict["id"], chArg) #discard parameter given by QAction
if exportDict["midiChannel"] == mch:
mchAction.setEnabled(False)
mchAction.triggered.connect(midiChannelCommand)
pos = QtGui.QCursor.pos() pos = QtGui.QCursor.pos()
pos.setY(pos.y() + 5) pos.setY(pos.y() + 5)
self.parentView.parentMainWindow.setFocus() self.parentView.parentMainWindow.setFocus()

1
template/documentation/readme.template

@ -4,6 +4,7 @@
#<name> #<name>
Program version <version> Program version <version>
![Screenshot](https://git.laborejo.org/lss/<name>/raw/branch/master/documentation/screenshot.png "Screenshot") ![Screenshot](https://git.laborejo.org/lss/<name>/raw/branch/master/documentation/screenshot.png "Screenshot")

Loading…
Cancel
Save