Browse Source

new row based features for patterns. see changelog

master
Nils 5 years ago
parent
commit
66e12bdd9b
  1. 3
      CHANGELOG
  2. 28
      engine/api.py
  3. 70
      engine/pattern.py
  4. 15
      qtgui/pattern_grid.py

3
CHANGELOG

@ -1,4 +1,7 @@
2019-07-15 Version 1.3
Add "Invert Row" to pattern context menu, if clicked on a step.
Add "Clear Row" for patterns.
Add powerful "Fill row with sub-pattern until this clicked-on step" to quickly repeat short snippets.
Remove strict Non Session Manager enforcement. Instead present a session directory chooser on startup, similar to --save.
Remove videos from git repository for easier download and distribution. Videos are now only hosted on laborejo.org, but could be downloaded by users.
Compilation without executable stack, a security risk.

28
engine/api.py

@ -554,7 +554,35 @@ def patternOffAllSteps(trackId):
track.buildTrack()
updatePlayback()
callbacks._patternChanged(track)
def patternInvertRow(trackId, pitchindex):
"""Pitchindex is the row"""
track = session.data.trackById(trackId)
track.pattern.invertRow(pitchindex)
track.pattern.buildExportCache()
track.buildTrack()
updatePlayback()
callbacks._patternChanged(track)
def patternClearRow(trackId, pitchindex):
"""Pitchindex is the row.
Index is the column"""
track = session.data.trackById(trackId)
track.pattern.clearRow(pitchindex)
track.pattern.buildExportCache()
track.buildTrack()
updatePlayback()
callbacks._patternChanged(track)
def patternRowRepeatFromStep(trackId, pitchindex, index):
"""Pitchindex is the row.
Index is the column"""
track = session.data.trackById(trackId)
track.pattern.repeatFromStep(pitchindex, index)
track.pattern.buildExportCache()
track.buildTrack()
updatePlayback()
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
schemesDict = {

70
engine/pattern.py

@ -34,8 +34,13 @@ class Pattern(object):
"""A pattern can be in only one track.
In fact having it as its own object is only for code readability
A pattern is an unordered list of dicts. Each dicts is an step, or a note.
{"index":int 0-timesigLength, "factor": float, "pitch", int 0-7, "velocity":int 0-127}
A pattern is an unordered list of dicts.
Each dict is an step, or a note.
{"index": from 0 to parentTrack.parentData.howManyUnits,
"factor": float,
"pitch": int 0-7,
"velocity":int 0-127,
}
The pitch is determined is by a scale, which is a list of len 7 of midi pitches. Our "pitch" is
an index in this list.
@ -113,6 +118,67 @@ class Pattern(object):
lst.append({"index":index, "factor": 1, "pitch": pitchindex, "velocity":vel})
self.data = lst
def _getRow(self, pitchindex):
"""Returns a row of steps, sorted by index/column.
Includes the original mutable dictionaries, which can be changed
"""
return sorted([d for d in self.data if d["pitch"] == pitchindex], key = lambda i: i["index"])
def _putRow(self, pitchindex, rowAsListOfSteps):
"""Replace a row with the given one"""
self.clearRow(pitchindex)
self.data.extend(rowAsListOfSteps)
def clearRow(self, pitchindex):
"""pure convenience. This could be done with
repeatFromStep on the first empty step"""
existingSteps = self._getRow(pitchindex)
for step in existingSteps:
self.data.remove(step)
def _rowAsBooleans(self, pitchindex):
"""Existence or not"""
existingSteps = self._getRow(pitchindex)
existingIndices = set(s["index"] for s in existingSteps)
result = [False] * self.parentTrack.parentData.howManyUnits
for i in existingIndices:
result[i] = True
return result
def repeatFromStep(self, pitchindex, stepIndex):
"""Includes the given step.
Uses average velocities
"""
vel = self.averageVelocity
rowAsBools = self._rowAsBooleans(pitchindex)
toRepeatChunk = rowAsBools[:stepIndex+1]
numberOfRepeats, rest = divmod(self.parentTrack.parentData.howManyUnits, stepIndex+1)
index = 0
newRow = []
for i in range(numberOfRepeats):
for b in toRepeatChunk:
if b:
newRow.append({"index":index, "factor": 1, "pitch": pitchindex, "velocity":vel})
index += 1
self._putRow(pitchindex, newRow)
def invertRow(self, pitchindex):
vel = self.averageVelocity
existingSteps = self._getRow(pitchindex)
existingIndices = set(s["index"] for s in existingSteps)
for step in existingSteps:
self.data.remove(step)
for index in range(self.parentTrack.parentData.howManyUnits):
if not index in existingIndices:
self.data.append({"index":index, "factor": 1, "pitch": pitchindex, "velocity":vel})
def stepByIndexAndPitch(self, index, pitch):
for d in self.data:
if d["index"] == index and d["pitch"] == pitch:

15
qtgui/pattern_grid.py

@ -309,11 +309,20 @@ class PatternGrid(QtWidgets.QGraphicsScene):
trackId = self.parentView.parentMainWindow.currentTrackId
listOfLabelsAndFunctions = [
(QtCore.QCoreApplication.translate("EventContextMenu", "Invert Steps"), lambda: api.patternInvertSteps(trackId)),
potentialStep = self.itemAt(event.scenePos().x(), event.scenePos().y(), self.parentView.transform())
if not type(potentialStep) is Step:
potentialStep = None
listOfLabelsAndFunctions = [
(QtCore.QCoreApplication.translate("EventContextMenu", "Invert Steps"), lambda: api.patternInvertSteps(trackId)),
(QtCore.QCoreApplication.translate("EventContextMenu", "All Steps On"), lambda: api.patternOnAllSteps(trackId)),
(QtCore.QCoreApplication.translate("EventContextMenu", "All Steps Off"), lambda: api.patternOffAllSteps(trackId)),
]
if potentialStep:
listOfLabelsAndFunctions.insert(0, (QtCore.QCoreApplication.translate("EventContextMenu", "Repeat to here to fill Row"), lambda: api.patternRowRepeatFromStep(trackId, potentialStep.row, potentialStep.column)))
listOfLabelsAndFunctions.insert(0, (QtCore.QCoreApplication.translate("EventContextMenu", "Clear Row"), lambda: api.patternClearRow(trackId, potentialStep.row)))
listOfLabelsAndFunctions.insert(0, (QtCore.QCoreApplication.translate("EventContextMenu", "Invert Row"), lambda: api.patternInvertRow(trackId, potentialStep.row)))
for text, function in listOfLabelsAndFunctions:
if function is None:
@ -799,7 +808,6 @@ class PitchWidget(QtWidgets.QGraphicsProxyWidget):
else:
QtWidgets.QAbstractSpinBox.stepBy(self.spinBox, n)
class Playhead(QtWidgets.QGraphicsLineItem):
def __init__(self, parentScene):
super().__init__(0, 0, 0, api.NUMBER_OF_STEPS*SIZE_UNIT) # (x1, y1, x2, y2)
@ -828,7 +836,6 @@ class Playhead(QtWidgets.QGraphicsLineItem):
else:
self.hide()
class VelocityControls(QtWidgets.QWidget):
def __init__(self, parentScene):
self.parentScene = parentScene

Loading…
Cancel
Save