Browse Source

clean up aborted vico integration

master
Nils 3 years ago
parent
commit
6084a6529a
  1. 5
      engine/api.py
  2. 24
      engine/vico/__init__.py
  3. 28
      engine/vico/api.py
  4. 74
      engine/vico/event.py
  5. 183
      engine/vico/track.py
  6. 3
      qtgui/designer/mainwindow.py
  7. 1
      qtgui/designer/mainwindow.ui
  8. 1
      qtgui/mainwindow.py

5
engine/api.py

@ -34,11 +34,6 @@ from template.engine.api import *
from template.engine.duration import baseDurationToTraditionalNumber
from template.helper import compress
#Our own engine Modules
#for readability import vico/pianoroll tracks from their own file
from engine.vico import api as vico
DEFAULT_FACTOR = 1 #for the GUI.

24
engine/vico/__init__.py

@ -1,24 +0,0 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Copyright 2021, Nils Hilbricht, Germany ( https://www.hilbricht.net )
This file is part of the Laborejo Software Suite ( https://www.laborejo.org ),
This 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/>.
"""
import logging; logger = logging.getLogger(__name__); logger.info("import")
#This file only exists as a reminder to _not_ create it again wrongly in the future.

28
engine/vico/api.py

@ -1,28 +0,0 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Copyright 2021, Nils Hilbricht, Germany ( https://www.hilbricht.net )
This file is part of the Laborejo Software Suite ( https://www.laborejo.org ),
This 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/>.
"""
import logging; logger = logging.getLogger(__name__); logger.info("import")
from template.engine.input_midi import MidiInput
def addTrack():
print ("vico track")

74
engine/vico/event.py

@ -1,74 +0,0 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Copyright 2021, Nils Hilbricht, Germany ( https://www.hilbricht.net )
This file is part of the Laborejo Software Suite ( https://www.laborejo.org ),
This 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/>.
"""
import logging; logger = logging.getLogger(__name__); logger.info("import")
class Event:
"""
[0x90, 60, 100] for a loud middle c on channel 0
[0xE0, 4, 85] pitchbend to 85*128 + 4 = 10884 steps
There is no duration value. Connections, e.g. note rectangles in the GUI, have to be calculated.
Byte2 can be None, for Program Change or Channel Pressure.
Vico events have no channel. The recorded channel is converted to channel 0.
"""
def __init__(self, position:int, status:int, byte1:int, byte2:int, layer:int, freeText:str=""):
self.position = position
self.status = status # e.g. 0x90 for note-on or 0xE0 for pitchbend
self.byte1 = byte1 # e.g. 60 for middle c
self.byte2 = byte2 #eg. 100 for a rather loud note. Can be None for Program Changes.
self.layer = layer #0-9 incl. . Events need to know their layer for undo.
self.freeText = freeText
#self.parentLayer = Injected and changed by the Layer itself.
def serialize(self)->tuple:
return (int(self.position), self.status, self.byte1, self.byte2, self.freeText)
def export(self)->dict:
return {
"id": id(self),
"position": self.position,
"status" : self.status,
"byte1" : self.byte1,
"byte2" : self.byte2,
"layer" : self.layer,
"freeText" : self.freeText,
}
def toCboxBytes(self)->bytes:
byte1 = pitch.midiPitchLimiter(self.byte1, 0)
byte2 = pitch.midiPitchLimiter(self.byte2, 0)
status = self.status + self.parentLayer.midiChannel - 1 #we index channels from 1 to 16, so -1 here because status is itself already chan 1
if self.position >= 0:
return cbox.Pattern.serialize_event(self.position, status, byte1, byte2)
else:
logger.warning(f"Event {self.byte1},{self.byte2} has position less than 0. Limiting to 0 in midi output. Please fix manually")
return cbox.Pattern.serialize_event(0, status, byte1, byte2)
def __repr__(self):
return str(self.export())
def copy(self):
"""Returns a standalone copy of self. Needs to be inserted into a layer."""
#return Event(*self.serialize(), layer=None)
return Event(position=self.position, status=self.status, byte1=self.byte1, byte2=self.byte2, layer=None, freeText=self.freeText)

183
engine/vico/track.py

@ -1,183 +0,0 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Copyright 2021, Nils Hilbricht, Germany ( https://www.hilbricht.net )
This file is part of the Laborejo Software Suite ( https://www.laborejo.org ),
This 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/>.
"""
import logging; logger = logging.getLogger(__name__); logger.info("import")
#Python Standard Library
from collections import defaultdict
from statistics import median
#Third Party Modules
from calfbox import cbox
#Template Modules
import template.engine.sequencer
import template.engine.pitch as pitch
from template.engine.duration import DB, DL, D1, D2, D4, D8, D16, D32, D64, D128, D256, D512, D1024
class Track(object):
"""
Main data structure is self.events which holds tickspositions and tuples.
"""
def __init__(self, parentData,
name:str="",
color:str=None,
simpleNoteNames:List[str]=None):
logger.info("Creating empty Vico Track instance")
self.parentData = parentData
self.sequencerInterface = template.engine.sequencer.SequencerInterface(parentTrack=self, name=name) #needs parentData
self.color = color if color else "#00FFFF" # "#rrggbb" in hex. no alpha. a convenience slot for the GUI to save a color.
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.group = "" # "" is a standalone track. Using a name here will group these tracks together. A GUI can use this information. Also all tracks in a group share a single jack out port.
self.visible = True #only used together with groups. the api and our Datas setGroup function take care that standalone tracks are never hidden.
self.events = defaultdict(list) # statusType: [Event]
self.dirty = False # indicates wether this needs re-export. Obviously not saved.
self._processAfterInit()
def __init__(self, parentTrack, index:int):
self.parentTrack = parentTrack
self.index = index
self.color = "cyan"
self.events = defaultdict(list) # statusType: [Event]
self.dirty = False # indicates wether this needs re-export. Obviously not saved.
self.midiChannel = 1 #1-16 inclusive.
self._processAfterInit()
def _processAfterInit(self):
"""Call this after either init or instanceFromSerializedData"""
self.cachedMedianVelocity = 64
self.cachedLastEventPosition = 0 #set in generateCalfboxMidi. Used by the track
self.cachedFirstEventPosition = 0 #set in generateCalfboxMidi. Used by the track
def newEvent(self, tickindex:int, statusType:int, byte1:int, byte2:int, freeText:str):
"""The only place in the program where events get created, except Score.getCopyBufferCopy
and where self.parentTrack.parentData.allEventsById gets populated. """
ev = Event(tickindex, statusType, byte1, byte2, self.index, freeText)
ev.parentLayer = self
self.parentTrack.parentData.allEventsById[id(ev)] = ev
self.events[statusType].append(ev)
self.dirty = True
return ev
def insertEvent(self, event):
"""Insert an existing Event object, that was at one time created through newEvent"""
assert not event in self.events[event.status]
self.events[event.status].append(event)
event.parentLayer = self
self.dirty = True
def deleteEvent(self, event):
"""Don't be surprised. This gets called by api.createNote to temporarily remove
notes and later add them with insertEvent again"""
try:
self.events[event.status].remove(event)
except ValueError:
logger.error(f"Event was not in our list. Layer{self.index}. Event: {event}")
self.dirty = True
#Save / Load / Export
def serialize(self)->dict:
events = []
for statusType, eventlist in self.events.items():
for event in eventlist:
events.append(event.serialize())
return {
"index" : self.index,
"color" : self.color,
"events": events,
"midiChannel": self.midiChannel,
}
@classmethod
def instanceFromSerializedData(cls, parentTrack, serializedData):
self = cls.__new__(cls)
self.parentTrack = parentTrack
self.color = serializedData["color"]
self.index = serializedData["index"]
self.midiChannel = serializedData["midiChannel"]
self.events = defaultdict(list) # statusType: [Event]
for seriEvent in serializedData["events"]: #tuples or list
self.newEvent(*seriEvent)
self.dirty = True
self._processAfterInit()
return self
def generateCalfboxMidi(self):
"""We use subtracks, therefore they do not change the cachedDuration of the song.
Instead we save our own version of cached ticks of the actual song length.
Useful for the GUI so it doesn not have to draw MAX_DURATION"""
if self.dirty:
velocities = []
blob = bytes()
maxPos = 0
minPos = template.engine.sequencer.MAXIMUM_TICK_DURATION
for status, eventlist in self.events.items():
for event in eventlist:
if event.position > maxPos:
maxPos = event.position
if event.position < minPos:
minPos = event.position
if event.status == 0x90:
velocities.append(event.byte2)
blob += event.toCboxBytes()
if velocities:
self.cachedMedianVelocity = int(median(velocities))
self.parentTrack.sequencerInterface.setSubtrack(self.index, [(blob, 0, maxPos+1)]) #(bytes-blob, position, length)
self.dirty = False
self.cachedLastEventPosition = maxPos
self.cachedFirstEventPosition = minPos
def _exportEvents(self) -> list:
"""Includes generating midi.
This is not called after every change!
In fact it is only triggered by api._layerChanged which happens once on program start
and on very extensive operations on the whole layer (like transpose)
"""
result = []
for status, eventlist in self.events.items():
for event in eventlist:
result.append(event.export())
self.generateCalfboxMidi()
return sorted(result, key=lambda e: e["position"])
def export(self)->dict:
return {
"index" : self.index,
"color" : self.color,
"events" : self._exportEvents(), #side effect: generate midi
}

3
qtgui/designer/mainwindow.py

@ -2,7 +2,7 @@
# Form implementation generated from reading ui file 'mainwindow.ui'
#
# Created by: PyQt5 UI code generator 5.15.2
# Created by: PyQt5 UI code generator 5.15.4
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
@ -187,7 +187,6 @@ class Ui_MainWindow(object):
self.actionAddPianoRoll.setObjectName("actionAddPianoRoll")
self.toolBar.addAction(self.actionClone_Selected_Track)
self.toolBar.addAction(self.actionAddPattern)
self.toolBar.addAction(self.actionAddPianoRoll)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)

1
qtgui/designer/mainwindow.ui

@ -376,7 +376,6 @@
</attribute>
<addaction name="actionClone_Selected_Track"/>
<addaction name="actionAddPattern"/>
<addaction name="actionAddPianoRoll"/>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">

1
qtgui/mainwindow.py

@ -306,7 +306,6 @@ class MainWindow(TemplateMainWindow):
#Designer Actions
self.ui.actionAddPattern.triggered.connect(self.addPatternTrack)
self.ui.actionClone_Selected_Track.triggered.connect(self.cloneSelectedTrack)
self.ui.actionAddPianoRoll.triggered.connect(api.vico.addTrack) #no need for our own function, these track types don't have a scale
#New Widgets. Toolbar in Designer can only have QActions, while in reality it can hold any widget. So we need to add them in code:
#We first define then, then add them below.

Loading…
Cancel
Save