Browse Source

update template

master
Nils 4 months ago
parent
commit
16cb1519d7
  1. 9
      template/calfbox/metadata.py
  2. 1
      template/documentation/readme.template
  3. 33
      template/engine/duration.py
  4. 5
      template/engine/input_midi.py
  5. 4
      template/engine/metronome.py
  6. 64
      template/engine/pitch.py
  7. 19
      template/engine/sequencer.py
  8. 8
      template/helper.py
  9. 12
      template/qtgui/debugScript.py
  10. 35
      template/qtgui/helper.py
  11. 28
      template/qtgui/midiinquickwidget.py
  12. 7
      template/qtgui/submenus.py

9
template/calfbox/metadata.py

@ -3,7 +3,7 @@
"""
This file implements the JackIO Python side of Jack Medata as described here:
http://www.jackaudio.org/files/docs/html/group__Metadata.html
https://jackaudio.org/api/metadata_8h.html
"""
import base64 # for icons
@ -64,6 +64,13 @@ class Metadata:
return TypeError("value {} must be int or str but was {}".format(value, type(value)))
do_cmd("/io/client_set_property", None, [key, value, jackPropertyType])
@staticmethod
def client_remove_property(key):
"""
This is directly for our client, which we do not need to provide here.
"""
do_cmd("/io/client_remove_property", None, [key])
@staticmethod
def remove_property(port, key):
"""port is the portname as string System:out_1"""

1
template/documentation/readme.template

@ -36,6 +36,7 @@ It is possible to clone a git repository.
## Dependencies
* Python 3.6 (maybe earlier)
* PyQt5 for Python 3
* PyQt OpenGL and SVG modules, if they are separated in your distribution
* DejaVu Sans Sarif TTF (Font) (recommended, but not technically necessary)
* libcalfbox-lss https://git.laborejo.org/lss/libcalfbox-lss

33
template/engine/duration.py

@ -150,8 +150,33 @@ ticksToLyDurationLogDict = {
D256: 8, #1/256
}
def ticksToLilypond(ticks:int)->int:
if ticks in ticksToLyDurationLogDict:
return ticksToLyDurationLogDict[ticks]
ticksToLilypondDict = {}
for ourticks, lyAsInt in baseDurationToTraditionalNumber.items():
ly = str(lyAsInt)
ticksToLilypondDict[ourticks] = ly
ticksToLilypondDict[ourticks * 1.5] = ly + "." #dot
ticksToLilypondDict[ourticks * 1.75] = ly + ".." #double dot
ticksToLilypondDict[DM] = "\\maxima"
ticksToLilypondDict[DL] = "\\longa"
ticksToLilypondDict[DB] = "\\breve"
ticksToLilypondDict[DM*1.5] = "\\maxima."
ticksToLilypondDict[DL*1.5] = "\\longa."
ticksToLilypondDict[DB*1.5] = "\\breve."
ticksToLilypondDict[DM*1.75] = "\\maxima.."
ticksToLilypondDict[DL*1.75] = "\\longa.."
ticksToLilypondDict[DB*1.75] = "\\breve.."
def ticksToLilypond(ticks:int)->str:
"""Returns a lilypond string with the number 1, 2, 4, 8 etc.
It must be a string and not a number because we have dots and words like maxima and brevis.
This is mostly used for tempo items.
"""
if ticks in ticksToLilypondDict:
return ticksToLilypondDict[ticks]
else:
raise ValueError(f"{ticks} not in duration.py ticksToLyDurationLogDict")
raise ValueError(f"{ticks} not in duration.py ticksToLilypondDict")

5
template/engine/input_midi.py

@ -109,6 +109,9 @@ class MidiInput(object):
for hp in hardwareMidiPorts:
cbox.JackIO.port_connect(hp, cbox.JackIO.status().client_name + ":" + self.portName)
def fullName(self)->str:
return cbox.JackIO.status().client_name + ":" + self.portName
class MidiProcessor(object):
"""
@ -165,7 +168,7 @@ class MidiProcessor(object):
This function gets called very often. So every optimisation is good.
"""
events = cbox.JackIO.get_new_events(self.parentInput.cboxMidiPortUid)
events = cbox.JackIO.get_new_events(self.parentInput.cboxMidiPortUid) #We get the events even if not active. Otherwise they pile up.
if not self.active:
return
if not events:

4
template/engine/metronome.py

@ -73,6 +73,10 @@ class Metronome(object):
self.label = "" #E.g. current Track Name, but can be anything.
self.setEnabled(False) #TODO: save load
def getPortNames(self)->(str,str):
"""Return two client:port , for left and right channel"""
return (self.sfzInstrumentSequencerInterface.portnameL, self.sfzInstrumentSequencerInterface.portnameR)
def soundStresses(self, value:bool):
self._soundStresses = value
self.generate(self._cachedData, self.label)

64
template/engine/pitch.py

@ -1006,7 +1006,7 @@ sortedNoteNameList = [
"bisis'''''" ,
]
baseNotesToBaseNames = {
baseNotesToBaseLyNames = {
20 : "C",
70 : "D",
120 : "E",
@ -1021,7 +1021,15 @@ baseNotesToBaseNames = {
170-10 : "Fes",
220-10 : "Ges",
270-10 : "Aes",
320-10 : "Bes/Bb",
320-10 : "Bes",
20-20 : "Ceses",
70-20 : "Deses",
120-20 : "Eeses",
170-20 : "Feses",
220-20 : "Geses",
270-20 : "Aeses",
320-20 : "Beses",
20+10 : "Cis",
70+10 : "Dis",
@ -1029,7 +1037,57 @@ baseNotesToBaseNames = {
170+10 : "Fis",
220+10 : "Gis",
270+10 : "Ais",
320+10 : "Bis/His",
320+10 : "Bis",
20+20 : "Cisis",
70+20 : "Disis",
120+20 : "Eisis",
170+20 : "Fisis",
220+20 : "Gisis",
270+20 : "Aisis",
320+20 : "Bisis",
}
baseNotesToAccidentalNames = {
20 : "C",
70 : "D",
120 : "E",
170 : "F",
220 : "G",
270 : "A",
320 : "B♮/H",
20-10 : "C♭",
70-10 : "D♭",
120-10 : "E♭",
170-10 : "F♭",
220-10 : "G♭",
270-10 : "A♭",
320-10 : "B♭/H♭",
20-20 : "C𝄫",
70-20 : "D𝄫",
120-20 : "E𝄫",
170-20 : "F𝄫",
220-20 : "G𝄫",
270-20 : "A𝄫",
320-20 : "B𝄫/H𝄫",
20+10 : "C♯",
70+10 : "D♯",
120+10 : "E♯",
170+10 : "F♯",
220+10 : "G♯",
270+10 : "A♯",
320+10 : "B♯/H♯",
20+20 : "C𝄪",
70+20 : "D𝄪",
120+20 : "E𝄪",
170+20 : "F𝄪",
220+20 : "G𝄪",
270+20 : "A𝄪",
320+20 : "B𝄪/H𝄪",
}
orderedBaseNotes = ["C", "D", "E", "F", "G", "A", "H/B"]

19
template/engine/sequencer.py

@ -503,10 +503,21 @@ class SfzInstrumentSequencerInterface(_Interface):
self.calfboxTrack.set_external_output("")
#Metadata
portnameL = f"{cbox.JackIO.status().client_name}:out_1"
portnameR = f"{cbox.JackIO.status().client_name}:out_2"
cbox.JackIO.Metadata.set_pretty_name(portnameL, name.title() + "-L")
cbox.JackIO.Metadata.set_pretty_name(portnameR, name.title() + "-R")
l = f"{cbox.JackIO.status().client_name}:out_1"
r = f"{cbox.JackIO.status().client_name}:out_2"
self.portnameL = cbox.JackIO.status().client_name + ":" + name.title() + "-L"
self.portnameR = cbox.JackIO.status().client_name + ":" + name.title() + "-R"
luuid = cbox.JackIO.create_audio_output(name.title() + "-L")
ruuid = cbox.JackIO.create_audio_output(name.title() + "-R")
cbox.JackIO.Metadata.set_pretty_name(self.portnameL, name.title() + "-L")
cbox.JackIO.Metadata.set_pretty_name(self.portnameR, name.title() + "-R")
self.outputMergerRouter = cbox.JackIO.create_audio_output_router(luuid, ruuid)
self.outputMergerRouter.set_gain(-1.0)
self.instrumentLayer.get_output_slot(0).rec_wet.attach(self.outputMergerRouter) #output_slot is 0 based and means a pair.
def enable(self, enabled):
if enabled:

8
template/helper.py

@ -27,6 +27,14 @@ from functools import lru_cache #https://docs.python.org/3.4/library/functools.h
cache_unlimited = lru_cache(maxsize=None)
#use as @cache_unlimited decorator
def dictdiff(a, b):
"""We use dicts for internal export packages. Compare two of them for changes"""
result = {}
for key, value in a.items():
if not b[key] == value:
result[key] = (value, b[key])
return result
import itertools
def pairwise(iterable):
"s -> (s0,s1), (s1,s2), (s2, s3), ..."

12
template/qtgui/debugScript.py

@ -39,9 +39,11 @@ class DebugScriptRunner(object):
assert nsmClient
self.nsmClient = nsmClient
self.absoluteScriptFilePath = os.path.join(self.nsmClient.ourPath, "debugscript.py")
logger.info(f"{self.nsmClient.ourClientNameUnderNSM}: Using script file: {self.absoluteScriptFilePath}")
def _createEmptyDebugScript(self):
assert self.absoluteScriptFilePath
print(f"{self.nsmClient.ourClientNameUnderNSM}: Script file not found. Initializing: {self.absoluteScriptFilePath}")
logger.info(f"{self.nsmClient.ourClientNameUnderNSM}: Script file not found. Initializing: {self.absoluteScriptFilePath}")
text = ("""#! /usr/bin/env python3"""
"\n"
@ -54,14 +56,14 @@ class DebugScriptRunner(object):
)
with open(self.absoluteScriptFilePath, "w", encoding="utf-8") as f:
f.write(text)
f.write(text)
def run(self):
if not os.path.exists(self.absoluteScriptFilePath):
if not os.path.exists(self.nsmClient.ourPath):
os.makedirs(self.nsmClient.ourPath)
os.makedirs(self.nsmClient.ourPath)
self._createEmptyDebugScript()
try:
exec(compile(open(self.absoluteScriptFilePath).read(), filename=self.absoluteScriptFilePath, mode="exec"), globals(), self.apilocals)
except Exception as e:

35
template/qtgui/helper.py

@ -26,6 +26,41 @@ from PyQt5 import QtCore, QtGui, QtWidgets
from hashlib import md5
def makeValueWidget(value:any):
"""Create a widget just from a value. Needs an external label e.g. in a formLayout.
First usecase was laborejo lilypond metadata and properties where it edited an engine-dict
directly and inplace.
Use with getValueFromWidget"""
types = {
str : QtWidgets.QLineEdit,
int : QtWidgets.QSpinBox,
float : QtWidgets.QDoubleSpinBox,
bool : QtWidgets.QCheckBox,
}
typ = type(value)
widget = types[typ]()
if typ == str:
widget.setText(value)
elif typ == int or typ == float:
widget.setValue(value)
elif typ == bool:
widget.setChecked(value)
return widget
def getValueFromWidget(widget):
"""Use with makeValueWidget"""
typ = type(widget)
if typ == QtWidgets.QLineEdit:
return widget.text()
elif typ == QtWidgets.QSpinBox or typ == QtWidgets.QDoubleSpinBox:
return widget.value()
elif typ == QtWidgets.QCheckBox:
return widget.isChecked()
def iconFromString(st, size=128):
px = QtGui.QPixmap(size,size)
color = stringToColor(st)

28
template/qtgui/midiinquickwidget.py

@ -34,9 +34,11 @@ import engine.api as api
class QuickMidiInputComboController(QtWidgets.QWidget):
"""This widget breaks a bit with the convention of engine/gui. However, it is a pure convenience
fire-and-forget function with no callbacks."""
fire-and-forget function with no callbacks.
def __init__(self, parentWidget):
If you give a text it must already be translated."""
def __init__(self, parentWidget, text=None, stretch=True):
super().__init__(parentWidget)
self.parentWidget = parentWidget
self.layout = QtWidgets.QHBoxLayout()
@ -54,7 +56,10 @@ class QuickMidiInputComboController(QtWidgets.QWidget):
self.wholePanel = parentWidget.ui.auditionerWidget
"""
self.label = QtWidgets.QLabel(self)
self.label.setText(QtCore.QCoreApplication.translate("QuickMidiInputComboController", "Midi Input. Use JACK to connect multiple inputs."))
if text:
self.label.setText(text) #already translated by parent.
else:
self.label.setText(QtCore.QCoreApplication.translate("QuickMidiInputComboController", "Midi Input. Use JACK to connect multiple inputs."))
self.layout.addWidget(self.label)
#if not api.isStandaloneMode():
@ -66,9 +71,8 @@ class QuickMidiInputComboController(QtWidgets.QWidget):
self.comboBox.showPopup = self.showPopup
self.comboBox.activated.connect(self._newPortChosen)
self.cboxPortname, self.cboxMidiPortUid = api.getMidiInputNameAndUuid() #these don't change during runtime.
self.layout.addStretch()
if stretch:
self.layout.addStretch()
#api.callbacks.startLoadingAuditionerInstrument.append(self.callback_startLoadingAuditionerInstrument)
#api.callbacks.auditionerInstrumentChanged.append(self.callback_auditionerInstrumentChanged)
#api.callbacks.auditionerVolumeChanged.append(self.callback__auditionerVolumeChanged)
@ -130,17 +134,23 @@ class QuickMidiInputComboController(QtWidgets.QWidget):
"""externalPort is in the Client:Port JACK format
If "" False or None disconnect all ports."""
cboxPortname, cboxMidiPortUid = api.getMidiInputNameAndUuid() #these don't change during runtime. But if the system it not ready yet it returns None, None
if cboxPortname is None and cboxMidiPortUid is None:
logging.info("engine is not ready yet to deliver midi input name and port id. This is normal during startup but a problem during normal runtime.")
return #startup delay
try:
currentConnectedList = cbox.JackIO.get_connected_ports(self.cboxMidiPortUid)
currentConnectedList = cbox.JackIO.get_connected_ports(cboxMidiPortUid)
except: #port not found.
currentConnectedList = []
for port in currentConnectedList:
cbox.JackIO.port_disconnect(port, self.cboxPortname)
cbox.JackIO.port_disconnect(port, cboxPortname)
if externalPort:
availablePorts = self.getAvailablePorts()
if not (externalPort in availablePorts["hardware"] or externalPort in availablePorts["software"]):
raise RuntimeError(f"QuickMidiInput was instructed to connect to port {externalPort}, which does not exist")
cbox.JackIO.port_connect(externalPort, self.cboxPortname)
cbox.JackIO.port_connect(externalPort, cboxPortname)

7
template/qtgui/submenus.py

@ -50,6 +50,10 @@ class Submenu(QtWidgets.QDialog):
label = QtWidgets.QLabel(labelString) #"Choose a clef" or so.
self.layout.addWidget(label)
#Second Label that can be changed on every call with self.dynamicLabel.setText()
self.dynamicLabel = QtWidgets.QLabel("")
self.layout.addWidget(self.dynamicLabel)
#self.setFocus(); #self.grabKeyboard(); #redundant for a proper modal dialog. Leave here for documentation reasons.
if hasOkCancelButtons == 1: #or true
@ -100,7 +104,8 @@ class Submenu(QtWidgets.QDialog):
self.done(True)
def __call__(self):
"""This instance can be called like a function"""
"""This instance can be called like a function.
Subclasses can subclass this as well to create non-static functionality."""
if self.buttonBox:
self.layout.addWidget(self.buttonBox)
self.setFixedSize(self.layout.geometry().size())

Loading…
Cancel
Save