Browse Source

mindless copy and paste

master
Nils 6 years ago
parent
commit
7fc1fa6f63
  1. 265
      engine/api.py
  2. 216
      engine/main.py
  3. 127
      engine/track.py
  4. 177
      qtgui/designer/mainwindow.py
  5. 414
      qtgui/designer/mainwindow.ui
  6. 108
      qtgui/mainwindow.py
  7. 882
      qtgui/pattern_grid.py
  8. 793
      qtgui/songeditor.py
  9. 138
      qtgui/timeline.py

265
engine/api.py

@ -4,6 +4,9 @@
import template.engine.api #we need direct access to the module to inject data in the provided structures. but we also need the functions directly. next line:
from template.engine.api import *
DEFAULT_VELOCITY = 90
DEFAULT_FACTOR = 1
NUMBER_OF_STEPS = 8 #for exchange with the GUI: one octave of range per pattern. This is unlikely to change and then will not work immediately, but better to keep it as a named "constant".
#New callbacks
class ClientCallbacks(Callbacks): #inherits from the templates api callbacks
@ -27,15 +30,15 @@ class ClientCallbacks(Callbacks): #inherits from the templates api callbacks
"""There is one tempo for the entire song in quarter notes per mintue.
score.isTransportMaster to False means we do not create our own changes
and leave everything to the default. Negative values are not possible"""
if session.score.isTransportMaster:
export = session.score.quarterNotesPerMinute
if session.data.isTransportMaster:
export = session.data.quarterNotesPerMinute
else:
export = None
for func in self.quarterNotesPerMinuteChanged:
func(export)
def _setPlaybackTicks(self):
ppqn = cbox.Transport.status().pos_ppqn * session.score.subdivisions
ppqn = cbox.Transport.status().pos_ppqn * session.data.subdivisions
status = _playbackStatus()
for func in self.setPlaybackTicks:
func(ppqn, status)
@ -46,13 +49,13 @@ class ClientCallbacks(Callbacks): #inherits from the templates api callbacks
func(export)
def _timeSignatureChanged(self):
nr = session.score.howManyUnits
typ = session.score.whatTypeOfUnit
nr = session.data.howManyUnits
typ = session.data.whatTypeOfUnit
for func in self.timeSignatureChanged:
func(nr, typ)
##All patterns and tracks need updates:
for track in session.score.tracks:
for track in session.data.tracks:
self._patternChanged(track)
self._subdivisionsChanged() #update subdivisions. We do not include them in the score or track export on purpose.
@ -62,7 +65,7 @@ class ClientCallbacks(Callbacks): #inherits from the templates api callbacks
You don't need to redraw anything if you don't want to. One recommendation is to
draw every n step a little more important (bigger, different color).
where n = subdivions"""
export = session.score.subdivisions
export = session.data.subdivisions
for func in self.subdivisionsChanged:
func(export)
@ -72,7 +75,7 @@ class ClientCallbacks(Callbacks): #inherits from the templates api callbacks
so it is its own callback.
Use this for fast and inexpensive updates like drawing a label or adjusting the
GUI that shows your measure groups (a label each 8 measures or so)"""
export = session.score.export()
export = session.data.export()
for func in self.scoreChanged:
func(export)
@ -119,7 +122,7 @@ class ClientCallbacks(Callbacks): #inherits from the templates api callbacks
This is also used when tracks get created or deleted, also on initial load.
This also includes the pattern.
"""
lst = [track.export() for track in session.score.tracks]
lst = [track.export() for track in session.data.tracks]
for func in self.numberOfTracksChanged:
func(lst)
@ -131,7 +134,7 @@ class ClientCallbacks(Callbacks): #inherits from the templates api callbacks
func(export)
def _numberOfMeasuresChanged(self):
export = session.score.export()
export = session.data.export()
for func in self.numberOfMeasuresChanged:
func(export)
@ -144,19 +147,21 @@ _templateStartEngine = startEngine
def startEngine(nsmClient):
_templateStartEngine(nsmClient)
session.inLoopMode = None # remember if we are in loop mode or not. Positive value is None or a tuple with start and end
#Send initial Callbacks to create the first GUI state.
#The order of initial callbacks must not change to avoid GUI problems.
#For example it is important that the tracks get created first and only then the number of measures
callbacksDatabase._numberOfTracksChanged()
callbacksDatabase._timeSignatureChanged()
callbacksDatabase._numberOfMeasuresChanged()
callbacksDatabase._subdivisionsChanged()
callbacksDatabase._quarterNotesPerMinuteChanged()
callbacks._numberOfTracksChanged()
callbacks._timeSignatureChanged()
callbacks._numberOfMeasuresChanged()
callbacks._subdivisionsChanged()
callbacks._quarterNotesPerMinuteChanged()
for track in session.score.tracks:
callbacksDatabase._trackMetaDataChanged(track) #for colors, scale and noteNames
for track in session.data.tracks:
callbacks._trackMetaDataChanged(track) #for colors, scale and noteNames
session.score.buildAllTracks()
session.data.buildAllTracks()
_updatePlayback()
@ -164,13 +169,13 @@ def toggleLoop():
"""Plays the current measure as loop.
Current measure is where the playback cursor is"""
if session.inLoopMode:
session.score.buildSongDuration() #no parameter removes the loop
session.data.buildSongDuration() #no parameter removes the loop
_updatePlayback()
session.inLoopMode = None
callbacksDatabase._loopChanged(None, None, None)
callbacks._loopChanged(None, None, None)
else:
now = loopMeasureAroundPpqn=cbox.Transport.status().pos_ppqn
loopStart, loopEnd = session.score.buildSongDuration(now)
loopStart, loopEnd = session.data.buildSongDuration(now)
_updatePlayback()
session.inLoopMode = (loopStart, loopEnd)
@ -178,10 +183,10 @@ def toggleLoop():
if not _playbackStatus():
cbox.Transport.play()
oneMeasureInTicks = (session.score.howManyUnits * session.score.whatTypeOfUnit) / session.score.subdivisions
oneMeasureInTicks = (session.data.howManyUnits * session.data.whatTypeOfUnit) / session.data.subdivisions
measurenumber, rest = divmod(loopStart, oneMeasureInTicks)
callbacksDatabase._loopChanged(int(measurenumber), loopStart, loopEnd)
callbacks._loopChanged(int(measurenumber), loopStart, loopEnd)
def seek(value):
@ -199,138 +204,138 @@ def toStart():
##Score
def set_quarterNotesPerMinute(value):
if value is None:
session.score.isTransportMaster = False #triggers rebuild
session.data.isTransportMaster = False #triggers rebuild
elif value == "on":
assert not session.score.isTransportMaster
assert not session.data.isTransportMaster
#keep old bpm value
session.score.isTransportMaster = True #triggers rebuild
session.data.isTransportMaster = True #triggers rebuild
else:
assert value > 0
session.score.quarterNotesPerMinute = value #triggers rebuild
session.score.isTransportMaster = True #triggers rebuild
session.data.quarterNotesPerMinute = value #triggers rebuild
session.data.isTransportMaster = True #triggers rebuild
#Does not need track rebuilding
_updatePlayback()
callbacksDatabase._quarterNotesPerMinuteChanged()
callbacks._quarterNotesPerMinuteChanged()
def set_whatTypeOfUnit(ticks):
if session.score.whatTypeOfUnit == ticks: return
session.score.whatTypeOfUnit = ticks
session.score.buildAllTracks()
if session.data.whatTypeOfUnit == ticks: return
session.data.whatTypeOfUnit = ticks
session.data.buildAllTracks()
_updatePlayback()
callbacksDatabase._timeSignatureChanged()
callbacks._timeSignatureChanged()
def set_howManyUnits(value):
if session.score.howManyUnits == value: return
session.score.howManyUnits = value
session.score.buildAllTracks()
if session.data.howManyUnits == value: return
session.data.howManyUnits = value
session.data.buildAllTracks()
_updatePlayback()
callbacksDatabase._timeSignatureChanged()
callbacks._timeSignatureChanged()
def set_subdivisions(value):
if session.score.subdivisions == value: return
session.score.subdivisions = value
session.score.buildAllTracks()
if session.data.subdivisions == value: return
session.data.subdivisions = value
session.data.buildAllTracks()
_updatePlayback()
callbacksDatabase._subdivisionsChanged()
callbacks._subdivisionsChanged()
def convert_subdivisions(value, errorHandling):
""""errorHandling can be fail, delete or merge"""
if session.score.subdivisions == value: return
result = session.score.convertSubdivisions(value, errorHandling)
if session.data.subdivisions == value: return
result = session.data.convertSubdivisions(value, errorHandling)
if result:
session.score.buildAllTracks()
session.data.buildAllTracks()
_updatePlayback()
callbacksDatabase._timeSignatureChanged() #includes subdivisions
for tr in session.score.tracks:
callbacksDatabase._patternChanged(tr)
callbacks._timeSignatureChanged() #includes subdivisions
for tr in session.data.tracks:
callbacks._patternChanged(tr)
else:
callbacksDatabase._subdivisionsChanged() #to reset the GUI value back to the working one.
callbacks._subdivisionsChanged() #to reset the GUI value back to the working one.
return result
def set_numberOfMeasures(value):
if session.score.numberOfMeasures == value: return
session.score.numberOfMeasures = value
session.score.buildSongDuration()
if session.data.numberOfMeasures == value: return
session.data.numberOfMeasures = value
session.data.buildSongDuration()
_updatePlayback()
callbacksDatabase._numberOfMeasuresChanged()
callbacksDatabase._scoreChanged() #redundant but cheap and convenient
callbacks._numberOfMeasuresChanged()
callbacks._scoreChanged() #redundant but cheap and convenient
def set_measuresPerGroup(value):
if session.score.measuresPerGroup == value: return
session.score.measuresPerGroup = value
if session.data.measuresPerGroup == value: return
session.data.measuresPerGroup = value
#No playback change
callbacksDatabase._scoreChanged()
callbacks._scoreChanged()
def changeTrackName(trackId, name):
track = session.score.trackById(trackId)
track = session.data.trackById(trackId)
track.name = " ".join(name.split())
callbacksDatabase._trackMetaDataChanged(track)
callbacks._trackMetaDataChanged(track)
def changeTrackColor(trackId, colorInHex):
"""Expects "#rrggbb"""
track = session.score.trackById(trackId)
track = session.data.trackById(trackId)
assert len(colorInHex) == 7, colorInHex
track.color = colorInHex
callbacksDatabase._trackMetaDataChanged(track)
callbacks._trackMetaDataChanged(track)
def addTrack(scale=None):
if scale:
assert type(scale) == tuple
session.score.addTrack(scale=scale)
callbacksDatabase._numberOfTracksChanged()
session.data.addTrack(scale=scale)
callbacks._numberOfTracksChanged()
def createSiblingTrack(trackId):
"""Create a new track with scale, color and jack midi out the same as the given track.
The jack midi out will be independent after creation, but connected to the same instrument
(if any)"""
track = session.score.trackById(trackId)
track = session.data.trackById(trackId)
assert type(track.pattern.scale) == tuple
newTrack = session.score.addTrack(name=track.name, scale=track.pattern.scale, color=track.color, noteNames=track.pattern.noteNames) #track name increments itself from "Track A" to "Track B" or "Track 1" -> "Track 2"
newTrack = session.data.addTrack(name=track.name, scale=track.pattern.scale, color=track.color, noteNames=track.pattern.noteNames) #track name increments itself from "Track A" to "Track B" or "Track 1" -> "Track 2"
newTrack.pattern.averageVelocity = track.pattern.averageVelocity
jackConnections = cbox.JackIO.get_connected_ports(track.cboxPortName())
for port in jackConnections:
cbox.JackIO.port_connect(newTrack.cboxPortName(), port)
#Move new track to neighbour the old one.
oldIndex = session.score.tracks.index(track)
newIndex = session.score.tracks.index(newTrack)
newTrackAgain = session.score.tracks.pop(newIndex)
oldIndex = session.data.tracks.index(track)
newIndex = session.data.tracks.index(newTrack)
newTrackAgain = session.data.tracks.pop(newIndex)
assert newTrackAgain is newTrack
session.score.tracks.insert(oldIndex+1, newTrackAgain)
callbacksDatabase._numberOfTracksChanged()
session.data.tracks.insert(oldIndex+1, newTrackAgain)
callbacks._numberOfTracksChanged()
return newTrack.export()
def deleteTrack(trackId):
track = session.score.trackById(trackId)
session.score.deleteTrack(track)
if not session.score.tracks: #always keep at least one track
session.score.addTrack()
track = session.data.trackById(trackId)
session.data.deleteTrack(track)
if not session.data.tracks: #always keep at least one track
session.data.addTrack()
_updatePlayback()
callbacksDatabase._numberOfTracksChanged()
callbacks._numberOfTracksChanged()
def moveTrack(trackId, newIndex):
"""index is 0 based"""
track = session.score.trackById(trackId)
oldIndex = session.score.tracks.index(track)
track = session.data.trackById(trackId)
oldIndex = session.data.tracks.index(track)
if not oldIndex == newIndex:
session.score.tracks.pop(oldIndex)
session.score.tracks.insert(newIndex, track)
callbacksDatabase._numberOfTracksChanged()
session.data.tracks.pop(oldIndex)
session.data.tracks.insert(newIndex, track)
callbacks._numberOfTracksChanged()
#Track Switches
def setSwitches(trackId, setOfPositions, newBool):
track = session.score.trackById(trackId)
track = session.data.trackById(trackId)
if newBool:
track.structure = track.structure.union(setOfPositions) #add setOfPositions to the existing one
else:
track.structure = track.structure.difference(setOfPositions) #remove everything from setOfPositions that is in the existing one, ignore entries from setOfPositions not in existing.
track.buildTrack()
_updatePlayback()
callbacksDatabase._trackStructureChanged(track)
callbacks._trackStructureChanged(track)
def setSwitch(trackId, position, newBool):
track = session.score.trackById(trackId)
track = session.data.trackById(trackId)
if newBool:
if position in track.structure: return
track.structure.add(position)
@ -339,79 +344,79 @@ def setSwitch(trackId, position, newBool):
track.structure.remove(position)
track.buildTrack()
_updatePlayback()
callbacksDatabase._trackStructureChanged(track)
callbacks._trackStructureChanged(track)
return True
def trackInvertSwitches(trackId):
track = session.score.trackById(trackId)
track = session.data.trackById(trackId)
"""
if track.structure:
new = set(i for i in range(max(track.structure)))
track.structure = new.difference(track.structure)
else:
track.structure = set(i for i in range(session.score.numberOfMeasures))
track.structure = set(i for i in range(session.data.numberOfMeasures))
"""
new = set(i for i in range(session.score.numberOfMeasures))
new = set(i for i in range(session.data.numberOfMeasures))
track.structure = new.difference(track.structure)
track.buildTrack()
_updatePlayback()
callbacksDatabase._trackStructureChanged(track)
callbacks._trackStructureChanged(track)
def trackOffAllSwitches(trackId):
track = session.score.trackById(trackId)
track = session.data.trackById(trackId)
track.structure = set()
track.buildTrack()
_updatePlayback()
callbacksDatabase._trackStructureChanged(track)
callbacks._trackStructureChanged(track)
def trackOnAllSwitches(trackId):
track = session.score.trackById(trackId)
track.structure = set(i for i in range(session.score.numberOfMeasures))
track = session.data.trackById(trackId)
track.structure = set(i for i in range(session.data.numberOfMeasures))
track.buildTrack()
_updatePlayback()
callbacksDatabase._trackStructureChanged(track)
callbacks._trackStructureChanged(track)
def trackMergeCopyFrom(sourceTrackId, targetTrackId):
if not sourceTrackId == targetTrackId:
sourceTrack = session.score.trackById(sourceTrackId)
targetTrack = session.score.trackById(targetTrackId)
sourceTrack = session.data.trackById(sourceTrackId)
targetTrack = session.data.trackById(targetTrackId)
targetTrack.structure = targetTrack.structure.union(sourceTrack.structure)
targetTrack.whichPatternsAreScaleTransposed.update(sourceTrack.whichPatternsAreScaleTransposed)
targetTrack.whichPatternsAreHalftoneTransposed.update(sourceTrack.whichPatternsAreHalftoneTransposed)
targetTrack.buildTrack()
_updatePlayback()
callbacksDatabase._trackStructureChanged(targetTrack)
callbacks._trackStructureChanged(targetTrack)
def setSwitchScaleTranspose(trackId, position, transpose):
"""Scale transposition is flipped. lower value means higher pitch"""
track = session.score.trackById(trackId)
track = session.data.trackById(trackId)
track.whichPatternsAreScaleTransposed[position] = transpose
track.buildTrack()
_updatePlayback()
callbacksDatabase._trackStructureChanged(track)
callbacks._trackStructureChanged(track)
return True
def setSwitchHalftoneTranspose(trackId, position, transpose):
"""Halftone transposition is not flipped. Higher value means higher pitch"""
track = session.score.trackById(trackId)
track = session.data.trackById(trackId)
track.whichPatternsAreHalftoneTransposed[position] = transpose
track.buildTrack()
_updatePlayback()
callbacksDatabase._trackStructureChanged(track)
callbacks._trackStructureChanged(track)
return True
def insertSilence(howMany, beforeMeasureNumber):
"""Insert empty measures into all tracks"""
for track in session.score.tracks:
for track in session.data.tracks:
track.structure = set( (switch + howMany if switch >= beforeMeasureNumber else switch) for switch in track.structure )
track.whichPatternsAreScaleTransposed = { (k+howMany if k >= beforeMeasureNumber else k):v for k,v in track.whichPatternsAreScaleTransposed.items() }
track.whichPatternsAreHalftoneTransposed = { (k+howMany if k >= beforeMeasureNumber else k):v for k,v in track.whichPatternsAreHalftoneTransposed.items() }
callbacksDatabase._trackStructureChanged(track)
session.score.buildAllTracks()
callbacks._trackStructureChanged(track)
session.data.buildAllTracks()
_updatePlayback()
def deleteSwitches(howMany, fromMeasureNumber):
for track in session.score.tracks:
for track in session.data.tracks:
new_structure = set()
for switch in track.structure:
@ -440,22 +445,22 @@ def deleteSwitches(howMany, fromMeasureNumber):
#else: #discard all in range to delete
track.whichPatternsAreHalftoneTransposed = new_halftoneTransposed
callbacksDatabase._trackStructureChanged(track)
session.score.buildAllTracks()
callbacks._trackStructureChanged(track)
session.data.buildAllTracks()
_updatePlayback()
#Pattern Steps
def setPattern(trackId, patternList):
"""Change the whole pattern, send a callback with the whole pattern"""
track = session.score.trackById(trackId)
track = session.data.trackById(trackId)
track.pattern.data = patternList
track.pattern.buildExportCache()
track.buildTrack()
callbacksDatabase._patternChanged(track)
callbacks._patternChanged(track)
def getAverageVelocity(trackId):
"""If a GUI wants to add a new note and choose a sensible velocity it can use this function"""
return session.score.trackById(trackId).pattern.averageVelocity
return session.data.trackById(trackId).pattern.averageVelocity
def setStep(trackId, stepExportDict):
"""This is an atomic operation that only sets one switch and
@ -463,7 +468,7 @@ def setStep(trackId, stepExportDict):
will most like not listen to that callback since they
already changed the step on their side. Only useful for parallel
views."""
track = session.score.trackById(trackId)
track = session.data.trackById(trackId)
oldNote = track.pattern.stepByIndexAndPitch(index=stepExportDict["index"], pitch=stepExportDict["pitch"])
if oldNote: #modify existing note
oldNoteIndex = track.pattern.data.index(oldNote)
@ -475,66 +480,66 @@ def setStep(trackId, stepExportDict):
track.pattern.buildExportCache()
track.buildTrack()
_updatePlayback()
callbacksDatabase._stepChanged(track, stepExportDict)
callbacks._stepChanged(track, stepExportDict)
def removeStep(trackId, index, pitch):
"""Reverse of setStep"""
track = session.score.trackById(trackId)
track = session.data.trackById(trackId)
oldNote = track.pattern.stepByIndexAndPitch(index, pitch)
track.pattern.data.remove(oldNote)
track.pattern.buildExportCache()
track.buildTrack()
_updatePlayback()
callbacksDatabase._removeStep(track, index, pitch)
callbacks._removeStep(track, index, pitch)
def setScale(trackId, scale):
"""Expects a scale list or tuple from lowest index to highest.
Actual pitches don't matter."""
track = session.score.trackById(trackId)
track = session.data.trackById(trackId)
track.pattern.scale = scale
track.pattern.buildExportCache()
track.buildTrack()
_updatePlayback()
callbacksDatabase._trackMetaDataChanged(track)
callbacks._trackMetaDataChanged(track)
def setNoteNames(trackId, noteNames):
"""note names is a list of strings with length 128. One name for each midi note.
It is saved to file"""
track = session.score.trackById(trackId)
track = session.data.trackById(trackId)
track.pattern.noteNames = noteNames
callbacksDatabase._trackMetaDataChanged(track)
callbacks._trackMetaDataChanged(track)
def transposeHalftoneSteps(trackId, steps):
track = session.score.trackById(trackId)
track = session.data.trackById(trackId)
track.pattern.scale = [midipitch+steps for midipitch in track.pattern.scale]
track.pattern.buildExportCache()
track.buildTrack()
_updatePlayback()
callbacksDatabase._trackMetaDataChanged(track)
callbacks._trackMetaDataChanged(track)
def patternInvertSteps(trackId):
track = session.score.trackById(trackId)
track = session.data.trackById(trackId)
track.pattern.invert()
track.pattern.buildExportCache()
track.buildTrack()
_updatePlayback()
callbacksDatabase._patternChanged(track)
callbacks._patternChanged(track)
def patternOnAllSteps(trackId):
track = session.score.trackById(trackId)
track = session.data.trackById(trackId)
track.pattern.fill()
track.pattern.buildExportCache()
track.buildTrack()
_updatePlayback()
callbacksDatabase._patternChanged(track)
callbacks._patternChanged(track)
def patternOffAllSteps(trackId):
track = session.score.trackById(trackId)
track = session.data.trackById(trackId)
track.pattern.empty()
track.pattern.buildExportCache()
track.buildTrack()
_updatePlayback()
callbacksDatabase._patternChanged(track)
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
@ -569,7 +574,7 @@ schemes = [
]
def setScaleToKeyword(trackId, keyword):
track = session.score.trackById(trackId)
track = session.data.trackById(trackId)
rememberRootNote = track.pattern.scale[-1] #no matter if this is the lowest or not%
scale = [x + y for x, y in zip(major, schemesDict[keyword])]
@ -579,10 +584,10 @@ def setScaleToKeyword(trackId, keyword):
track.pattern.buildExportCache()
track.buildTrack()
_updatePlayback()
callbacksDatabase._trackMetaDataChanged(track)
callbacks._trackMetaDataChanged(track)
def changePatternVelocity(trackId, steps):
track = session.score.trackById(trackId)
track = session.data.trackById(trackId)
for note in track.pattern.data:
new = note["velocity"] + steps
note["velocity"] = min(max(new,0), 127)
@ -590,18 +595,18 @@ def changePatternVelocity(trackId, steps):
track.pattern.buildExportCache()
track.buildTrack()
_updatePlayback()
callbacksDatabase._patternChanged(track)
callbacks._patternChanged(track)
#Other functions
def noteOn(trackId, row):
track = session.score.trackById(trackId)
track = session.data.trackById(trackId)
midipitch = track.pattern.scale[row]
cbox.send_midi_event(0x90, midipitch, track.pattern.averageVelocity, output=track.cboxMidiOutUuid)
def noteOff(trackId, row):
track = session.score.trackById(trackId)
track = session.data.trackById(trackId)
midipitch = track.pattern.scale[row]
cbox.send_midi_event(0x80, midipitch, track.pattern.averageVelocity, output=track.cboxMidiOutUuid)

216
engine/main.py

@ -22,29 +22,207 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging; logging.info("import {}".format(__file__))
#Standard Library Modules
#Third Party Modules
from calfbox import cbox
#Template Modules
from template.engine.data import Data as TemplateData
import template.engine.sequencer
#Our modules
from .track import Track
class Data(template.engine.sequencer.Score):
"""There must always be a Data class in a file main.py.
Simply inheriting from engine.data.Data is easiest.
You need to match the init parameters of your parent class. They vary from class to class
You need to match the init parameters of your parent class. They vary from class to class
of course. Simply copy and paste them from your Data parent class
There is also engine.sequencer.Score and engine.sampler_sf2.Sf2"""
def __init__(self, parentSession, tracks=None, tempoMap=None):
super().__init__(parentSession, tracks, tempoMap)
self.addTrack("Welt")
class Track(template.engine.sequencer.TemplateTrack):
def __repr__(self) -> str:
return f"Client Example Track: {self.name}"
def __init__(self, parentScore, name=None):
super().__init__(parentScore, name)
print (self)
#Dependency Injections.
template.engine.sequencer.Track = Track #Score will look for Track in its module.
Pattern is our measure. Since Patroneo is not a metrical program we use the simple
traditional time signatures.
"""
def __init__(self, parentSession, howManyUnits=8, whatTypeOfUnit=D4, tracks = None, numberOfMeasures = 64, measuresPerGroup=8, subdivisions=1, isTransportMaster=False, quarterNotesPerMinute=120.0):
super().__init__(parentSession, tracks, tempoMap=None)
self.howManyUnits = howManyUnits
self.whatTypeOfUnit = whatTypeOfUnit
self.numberOfMeasures = numberOfMeasures
self.measuresPerGroup = measuresPerGroup # meta data, has no effect on playback.
self.subdivisions = subdivisions
self.lastUsedNotenames = noteNames["English"] #The default value for new tracks/patterns. Changed each time the user picks a new representation via api.setNoteNames . noteNames are saved with the patterns.
if not tracks: #Empty / New project
self.tracks = []
self.addTrack(name="Melody A", color="#ffff00")
self.tracks[0].structure=set((0,)) #Already have the first pattern activated, so 'play' after startup already produces sounding notes. This is less confusing for a new user.
self.addTrack(name="Bass A", color="#00ff00")
self.addTrack(name="Drums A", color="#ff5500")
def trackById(self, trackId):
for track in self.tracks:
if trackId == id(track):
return track
raise ValueError(f"Track {trackId} not found. Current Tracks: {[id(tr) for tr in self.tracks]}")
def addTrack(self, name="", scale=None, color=None, noteNames=None):
track = Track(parentScore=self, name=name, scale=scale, color=color, noteNames=noteNames)
self.tracks.append(track)
return track
def deleteTrack(self, track):
track.prepareForDeletion()
self.tracks.remove(track)
def convertSubdivisions(self, value, errorHandling):
"""Not only setting the subdivisions but also trying to scale existing notes up or down
proportinally. But only if possible."""
assert errorHandling in ("fail", "delete", "merge")
scaleFactor = value / self.subdivisions
inverseScaleFactor = self.subdivisions / value
#the easiest case. New value is bigger and a multiple of the old one. 1->everything, 2->4.
#We do need not check if the old notes have a place in the new grid because there are more new places than before
if int(scaleFactor) == scaleFactor:
assert int(scaleFactor * self.howManyUnits) == scaleFactor * self.howManyUnits
self.howManyUnits = int(scaleFactor * self.howManyUnits)
for track in self.tracks:
for step in track.pattern.data:
step["index"] = int(scaleFactor * step["index"])
step["factor"] = scaleFactor * step["factor"]
#Possible case, but needs checking.
elif int(inverseScaleFactor) == inverseScaleFactor:
assert int(scaleFactor * self.howManyUnits) == scaleFactor * self.howManyUnits
#Test, if in "fail" mode
if errorHandling == "fail":
if not int(scaleFactor * self.howManyUnits) == scaleFactor * self.howManyUnits:
return False
for track in self.tracks:
for step in track.pattern.data:
if not int(scaleFactor * step["index"]) == scaleFactor * step["index"]: #yes, not inverse.
return False
#Then apply
todelete = []
self.howManyUnits = int(scaleFactor * self.howManyUnits)
for track in self.tracks:
for step in track.pattern.data:
if errorHandling == "delete" and not int(scaleFactor * step["index"]) == scaleFactor * step["index"]:
todelete.append(step)
else: # if error handling was "merge" then impossible conversions will lead to step positions that can't be undone by restoring the old subdivision value.
step["index"] = int(scaleFactor * step["index"]) #yes, not inverse.
step["factor"] = scaleFactor * step["factor"]
track.pattern.data = [d for d in track.pattern.data if not d in todelete]
else: #anything involving a 3.
#test if a conversion is possible. It is possible if you could first convert to 1 manually and then back up to the target number.
#Or in other words: if only the main positions are set as steps.
if errorHandling == "fail":
if not int(scaleFactor * self.howManyUnits) == scaleFactor * self.howManyUnits:
return False
for track in self.tracks:
for step in track.pattern.data:
if step["index"] % self.subdivisions: #not on a main position.
return False
#Test without error. Go!
self.howManyUnits = int(scaleFactor * self.howManyUnits)
todelete = []
for track in self.tracks:
for step in track.pattern.data:
if errorHandling == "delete" and not int(scaleFactor * step["index"]) == scaleFactor * step["index"]:
todelete.append(step)
step["index"] = int(scaleFactor * step["index"]) #yes, not inverse.
step["factor"] = scaleFactor * step["factor"]
track.pattern.data = [d for d in track.pattern.data if not d in todelete]
self.subdivisions = value
return True
def buildAllTracks(self):
"""Includes all patterns.
This still needs to be followed by buildSongDuration. Explicit is better than implicit."""
for track in self.tracks:
track.pattern.buildExportCache()
track.buildTrack()
self.buildSongDuration()
def buildSongDuration(self, loopMeasureAroundPpqn=None):
oneMeasureInTicks = (self.howManyUnits * self.whatTypeOfUnit) / self.subdivisions
oneMeasureInTicks = int(oneMeasureInTicks)
maxTrackDuration = self.numberOfMeasures * oneMeasureInTicks
if loopMeasureAroundPpqn is None: #could be 0
cbox.Document.get_song().set_loop(maxTrackDuration, maxTrackDuration) #set playback length for the entire score. Why is the first value not zero? That would create an actual loop from the start to end. We want the song to play only once. The cbox way of doing that is to set the loop range to zero at the end of the track. Zero length is stop.
else:
loopMeasure = int(loopMeasureAroundPpqn / oneMeasureInTicks) #0 based
start = loopMeasure * oneMeasureInTicks
end = start + oneMeasureInTicks
cbox.Document.get_song().set_loop(start, end) #set playback length for the entire score. Why is the first value not zero? That would create an actual loop from the start to end. We want the song to play only once. The cbox way of doing that is to set the loop range to zero at the end of the track. Zero length is stop.
return start, end
#Save / Load / Export
def serialize(self)->dict:
dictionary = super().serialize()
dictionary.update( { #update in place
"howManyUnits" : self.howManyUnits,
"whatTypeOfUnit" : self.whatTypeOfUnit,
"numberOfMeasures" : self.numberOfMeasures,
"measuresPerGroup" : self.measuresPerGroup,
"subdivisions" : self.subdivisions,
})
return dictionary
@classmethod
def instanceFromSerializedData(cls, parentSession, serializedData):
self = cls(parentSession=parentSession,
howManyUnits=serializedData["howManyUnits"],
whatTypeOfUnit=serializedData["whatTypeOfUnit"],
tracks=True,
numberOfMeasures=serializedData["numberOfMeasures"],
measuresPerGroup=serializedData["measuresPerGroup"],
subdivisions=serializedData["subdivisions"],
isTransportMaster=serializedData["isTransportMaster"],
quarterNotesPerMinute=serializedData["quarterNotesPerMinute"],
)
self.tracks = []
for trackSrzData in serializedData["tracks"]:
track = Track(
parentScore=self,
name=trackSrzData["name"],
structure=set(trackSrzData["structure"]),
pattern= True, #fake. Filled in right after Track got created
color=trackSrzData["color"],
whichPatternsAreScaleTransposed=trackSrzData["whichPatternsAreScaleTransposed"],
whichPatternsAreHalftoneTransposed=trackSrzData["whichPatternsAreHalftoneTransposed"],
)
track.pattern = Pattern(parentTrack=track, data=trackSrzData["data"], scale=trackSrzData["scale"], noteNames=trackSrzData["noteNames"],)
self.tracks.append(track)
return self
@classmethod
def instanceFromSerializedData(cls, parentSession, serializedData):
self = super().instanceFromSerializedData(cls, parentSession, serializedData)
def export(self):
return {
"numberOfTracks" : len(self.tracks),
"howManyUnits" : self.howManyUnits,
"whatTypeOfUnit" : self.whatTypeOfUnit,
"numberOfMeasures" : self.numberOfMeasures,
"measuresPerGroup" : self.measuresPerGroup,
"isTransportMaster" : self.tempoMap.export()["isTransportMaster"],
}

127
engine/track.py

@ -0,0 +1,127 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Copyright 2018, Nils Hilbricht, Germany ( https://www.hilbricht.net )
This file is part of the Laborejo Software Suite ( https://www.laborejo.org ),
more specifically its template base application.
The Template Base Application is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
#Standard Library Modules
#Third Party Modules
#Template Modules
import template.engine.sequencer
#Our modules
class Track(object): #injection at the bottom of this file!
"""The pattern is same as the track, even if the GUI does not represent it that way"""
def __repr__(self) -> str:
return f"Patroneo Track: {self.name}"
#def __init__(self, parentScore, name="", structure=None, pattern=None, scale=None, color=None, whichPatternsAreScaleTransposed=None, whichPatternsAreHalftoneTransposed=None, noteNames=None):
def __init__(self, parentScore, name="", structure=None, pattern=None, scale=None, color=None, whichPatternsAreScaleTransposed=None, whichPatternsAreHalftoneTransposed=None, noteNames=None):
print (self)
self.color = color if color else "#00FFFF" # "#rrggbb" in hex. no alpha. a convenience slot for the GUI to save a color.
#User data:
self.pattern = pattern if pattern else Pattern(parentTrack = self, scale=scale, noteNames=noteNames)
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.
if whichPatternsAreScaleTransposed:
self.whichPatternsAreScaleTransposed = {int(k):int(v) for k,v in whichPatternsAreScaleTransposed.items()} #json saves dict keys as strings
else:
self.whichPatternsAreScaleTransposed = {} #position:integers between -7 and 7. Reversed pitch, row based: -7 is higher than 7!!
if whichPatternsAreHalftoneTransposed:
self.whichPatternsAreHalftoneTransposed = {int(k):int(v) for k,v in whichPatternsAreHalftoneTransposed.items()} #json saves dict keys as strings
else:
self.whichPatternsAreHalftoneTransposed = {} #position:integers between -7 and 7. Reversed pitch, row based: -7 is higher than 7!!
def buildTrack(self):
"""The goal is to create a cbox-track, consisting of cbox-clips which hold cbox-pattern,
generated with our own note data. The latter happens in structures_pattern.
"""
#First clean the transpositions of zeroes
self.whichPatternsAreScaleTransposed = {k:v for k,v in self.whichPatternsAreScaleTransposed.items() if v!=0 and k in self.structure}
self.whichPatternsAreHalftoneTransposed = {k:v for k,v in self.whichPatternsAreHalftoneTransposed.items() if v!=0 and k in self.structure}
oneMeasureInTicks = (self.parentScore.howManyUnits * self.parentScore.whatTypeOfUnit) / self.parentScore.subdivisions #subdivisions is 1 by default. bigger values mean shorter values, which is compensated by the user setting bigger howManyUnits manually.
oneMeasureInTicks = int(oneMeasureInTicks)
filteredStructure = [index for index in sorted(self.structure) if index < self.parentScore.numberOfMeasures] #not <= because we compare count with range
cboxclips = [o.clip for o in self.calfboxTrack.status().clips]
for cboxclip in cboxclips:
cboxclip.delete() #removes itself from the track
for index in filteredStructure:
scaleTransposition = self.whichPatternsAreScaleTransposed[index] if index in self.whichPatternsAreScaleTransposed else 0
halftoneTransposition = self.whichPatternsAreHalftoneTransposed[index] if index in self.whichPatternsAreHalftoneTransposed else 0
cboxPattern = self.pattern.buildPattern(scaleTransposition, halftoneTransposition, self.parentScore.howManyUnits, self.parentScore.whatTypeOfUnit, self.parentScore.subdivisions)
r = self.calfboxTrack.add_clip(index*oneMeasureInTicks, 0, oneMeasureInTicks, cboxPattern) #pos, pattern-internal offset, length, pattern.
######Old optimisations. Keep for later####
##########################################
#if changeClipsInPlace: #no need for track.buildTrack. Very cheap pattern exchange.
# cboxclips = [o.clip for o in self.parentTrack.calfboxTrack.status().clips]
# for cboxclip in cboxclips:
# cboxclip.set_pattern(self.cboxPattern[cboxclip.patroneoScaleTransposed])
def serialize(self)->dict:
dictionary = super().serialize()
dictionary.update( { #update in place
"color" : self.color,
"structure" : list(self.structure),
"data" : self.pattern.data,
"scale" : self.pattern.scale, #The scale is part of the track meta callback.
"noteNames" : self.pattern.noteNames, #The noteNames are part of the track meta callback.
"whichPatternsAreScaleTransposed" : self.whichPatternsAreScaleTransposed,
"whichPatternsAreHalftoneTransposed" : self.whichPatternsAreHalftoneTransposed,
})
return dictionary
@classmethod
def instanceFromSerializedData(cls, parentTrack, serializedData):
self = cls.__new__(cls)
self._name = serializedData["name"]
self.parentTrack = parentTrack
self.parentScore = parentTrack.parentScore
self._processAfterInit()
return self
def export(self)->dict:
dictionary = super().export()
dictionary.update({
"color" : self.color,
"structure" : sorted(self.structure),
"pattern": self.pattern.exportCache,
"scale": self.pattern.scale,
"noteNames": self.pattern.noteNames,
"numberOfMeasures": self.parentScore.numberOfMeasures,
"whichPatternsAreScaleTransposed": self.whichPatternsAreScaleTransposed,
"whichPatternsAreHalftoneTransposed": self.whichPatternsAreHalftoneTransposed,
})
#Dependency Injections.
template.engine.sequencer.Score.TrackClass = Track #Score will look for Track in its module.

177
qtgui/designer/mainwindow.py

@ -11,29 +11,182 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 504)
MainWindow.resize(800, 600)
MainWindow.setWindowTitle("Patroneo")
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.verticalLayout.setSpacing(0)
self.verticalLayout.setObjectName("verticalLayout")
self.splitter = QtWidgets.QSplitter(self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.splitter.sizePolicy().hasHeightForWidth())
self.splitter.setSizePolicy(sizePolicy)
self.splitter.setFrameShape(QtWidgets.QFrame.NoFrame)
self.splitter.setLineWidth(0)
self.splitter.setOrientation(QtCore.Qt.Vertical)
self.splitter.setHandleWidth(6)
self.splitter.setObjectName("splitter")
self.songArea = QtWidgets.QWidget(self.splitter)
self.songArea.setObjectName("songArea")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.songArea)
self.horizontalLayout.setContentsMargins(0, 0, 0, 5)
self.horizontalLayout.setSpacing(0)
self.horizontalLayout.setObjectName("horizontalLayout")
self.widget_2 = QtWidgets.QWidget(self.songArea)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.widget_2.sizePolicy().hasHeightForWidth())
self.widget_2.setSizePolicy(sizePolicy)
self.widget_2.setMinimumSize(QtCore.QSize(200, 0))
self.widget_2.setMaximumSize(QtCore.QSize(200, 16777215))
self.widget_2.setObjectName("widget_2")
self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.widget_2)
self.verticalLayout_4.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_4.setSpacing(0)
self.verticalLayout_4.setObjectName("verticalLayout_4")
self.widget_3 = QtWidgets.QWidget(self.widget_2)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.widget_3.sizePolicy().hasHeightForWidth())
self.widget_3.setSizePolicy(sizePolicy)
self.widget_3.setMinimumSize(QtCore.QSize(200, 30))
self.widget_3.setMaximumSize(QtCore.QSize(200, 30))
self.widget_3.setObjectName("widget_3")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.widget_3)
self.horizontalLayout_2.setContentsMargins(1, 1, 1, 1)
self.horizontalLayout_2.setSpacing(2)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.playPauseButton = QtWidgets.QPushButton(self.widget_3)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.playPauseButton.sizePolicy().hasHeightForWidth())
self.playPauseButton.setSizePolicy(sizePolicy)
self.playPauseButton.setText("play")
self.playPauseButton.setShortcut("Space")
self.playPauseButton.setFlat(False)
self.playPauseButton.setObjectName("playPauseButton")
self.horizontalLayout_2.addWidget(self.playPauseButton)
self.loopButton = QtWidgets.QPushButton(self.widget_3)
self.loopButton.setText("loop")
self.loopButton.setShortcut("")
self.loopButton.setObjectName("loopButton")
self.horizontalLayout_2.addWidget(self.loopButton)
self.toStartButton = QtWidgets.QPushButton(self.widget_3)
self.toStartButton.setText("first")
self.toStartButton.setShortcut("")
self.toStartButton.setObjectName("toStartButton")
self.horizontalLayout_2.addWidget(self.toStartButton)
self.verticalLayout_4.addWidget(self.widget_3)
self.trackEditorView = QtWidgets.QGraphicsView(self.widget_2)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.trackEditorView.sizePolicy().hasHeightForWidth())
self.trackEditorView.setSizePolicy(sizePolicy)
self.trackEditorView.setMinimumSize(QtCore.QSize(200, 0))
self.trackEditorView.setMaximumSize(QtCore.QSize(200, 16777215))
self.trackEditorView.setFrameShape(QtWidgets.QFrame.NoFrame)
self.trackEditorView.setFrameShadow(QtWidgets.QFrame.Plain)
self.trackEditorView.setLineWidth(0)
self.trackEditorView.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.trackEditorView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.trackEditorView.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
self.trackEditorView.setObjectName("trackEditorView")
self.verticalLayout_4.addWidget(self.trackEditorView)
self.horizontalLayout.addWidget(self.widget_2)
self.widget = QtWidgets.QWidget(self.songArea)
self.widget.setObjectName("widget")
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.widget)
self.verticalLayout_3.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_3.setSpacing(0)
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.timelineView = QtWidgets.QGraphicsView(self.widget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.timelineView.sizePolicy().hasHeightForWidth())
self.timelineView.setSizePolicy(sizePolicy)
self.timelineView.setMinimumSize(QtCore.QSize(0, 30))
self.timelineView.setMaximumSize(QtCore.QSize(16777215, 30))
self.timelineView.setFrameShape(QtWidgets.QFrame.NoFrame)
self.timelineView.setFrameShadow(QtWidgets.QFrame.Plain)
self.timelineView.setLineWidth(0)
self.timelineView.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.timelineView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.timelineView.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
self.timelineView.setObjectName("timelineView")
self.verticalLayout_3.addWidget(self.timelineView)
self.songEditorView = QtWidgets.QGraphicsView(self.widget)
self.songEditorView.setFrameShape(QtWidgets.QFrame.NoFrame)
self.songEditorView.setFrameShadow(QtWidgets.QFrame.Plain)
self.songEditorView.setLineWidth(0)
self.songEditorView.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
self.songEditorView.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
self.songEditorView.setObjectName("songEditorView")
self.verticalLayout_3.addWidget(self.songEditorView)
self.horizontalLayout.addWidget(self.widget)
self.patternArea = QtWidgets.QWidget(self.splitter)
self.patternArea.setObjectName("patternArea")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.patternArea)
self.verticalLayout_2.setContentsMargins(0, 5, 0, 0)
self.verticalLayout_2.setSpacing(0)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.gridView = QtWidgets.QGraphicsView(self.patternArea)
self.gridView.setFrameShape(QtWidgets.QFrame.NoFrame)
self.gridView.setFrameShadow(QtWidgets.QFrame.Plain)
self.gridView.setLineWidth(0)
self.gridView.setAlignment(QtCore.Qt.AlignHCenter|QtCore.Qt.AlignTop)
self.gridView.setObjectName("gridView")
self.verticalLayout_2.addWidget(self.gridView)
self.verticalLayout.addWidget(self.splitter)
MainWindow.setCentralWidget(self.centralwidget)
self.toolBar = QtWidgets.QToolBar(MainWindow)
self.toolBar.setWindowTitle("toolBar")
self.toolBar.setToolTip("")
self.toolBar.setMovable(False)
self.toolBar.setFloatable(False)
self.toolBar.setObjectName("toolBar")
MainWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.toolBar)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 20))
self.menubar.setObjectName("menubar")
self.menuInsert_Item = QtWidgets.QMenu(self.menubar)
self.menuInsert_Item.setObjectName("menuInsert_Item")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.actionRofl = QtWidgets.QAction(MainWindow)
self.actionRofl.setObjectName("actionRofl")
self.menubar.addAction(self.menuInsert_Item.menuAction())
self.actionAddTrack = QtWidgets.QAction(MainWindow)
self.actionAddTrack.setText("Add Track")
self.actionAddTrack.setToolTip("Add a new Track")
self.actionAddTrack.setShortcut("")
self.actionAddTrack.setObjectName("actionAddTrack")
self.actionPlayPause = QtWidgets.QAction(MainWindow)
self.actionPlayPause.setText("PlayPause")
self.actionPlayPause.setShortcut("Space")
self.actionPlayPause.setObjectName("actionPlayPause")
self.actionLoop = QtWidgets.QAction(MainWindow)
self.actionLoop.setText("Loop")
self.actionLoop.setShortcut("L")
self.actionLoop.setObjectName("actionLoop")
self.actionToStart = QtWidgets.QAction(MainWindow)
self.actionToStart.setText("To Start")
self.actionToStart.setShortcut("Backspace")
self.actionToStart.setObjectName("actionToStart")
self.actionClone_Selected_Track = QtWidgets.QAction(MainWindow)
self.actionClone_Selected_Track.setText("Clone selected Track")
self.actionClone_Selected_Track.setShortcut("")
self.actionClone_Selected_Track.setObjectName("actionClone_Selected_Track")
self.toolBar.addAction(self.actionClone_Selected_Track)
self.toolBar.addAction(self.actionAddTrack)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.menuInsert_Item.setTitle(_translate("MainWindow", "Insert Item"))
self.actionRofl.setText(_translate("MainWindow", "Rofl!"))
self.actionAddTrack.setIconText(_translate("MainWindow", "Add Track"))
self.actionClone_Selected_Track.setIconText(_translate("MainWindow", "Clone selected Track"))

414
qtgui/designer/mainwindow.ui

@ -7,13 +7,366 @@
<x>0</x>
<y>0</y>
<width>800</width>
<height>504</height>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
<string notr="true">Patroneo</string>
</property>
<widget class="QWidget" name="centralwidget"/>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QSplitter" name="splitter">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="handleWidth">
<number>6</number>
</property>
<widget class="QWidget" name="songArea" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<widget class="QWidget" name="widget_2" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>16777215</height>
</size>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="widget_3" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>30</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>30</height>
</size>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>1</number>
</property>
<property name="topMargin">
<number>1</number>
</property>
<property name="rightMargin">
<number>1</number>
</property>
<property name="bottomMargin">
<number>1</number>
</property>
<item>
<widget class="QPushButton" name="playPauseButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">play</string>
</property>
<property name="shortcut">
<string notr="true">Space</string>
</property>
<property name="flat">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="loopButton">
<property name="text">
<string notr="true">loop</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="toStartButton">
<property name="text">
<string notr="true">first</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGraphicsView" name="trackEditorView">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>16777215</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="widget" native="true">
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGraphicsView" name="timelineView">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>30</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>30</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
</widget>
</item>
<item>
<widget class="QGraphicsView" name="songEditorView">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<