You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

147 lines
6.6 KiB

#! /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; logging.info("import {}".format(__file__))
#Standard Library Modules
#Third Party Modules
from PyQt5 import QtWidgets, QtCore, QtGui
from calfbox import cbox
#Template Modules
#Our modules
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."""
def __init__(self, parentWidget):
super().__init__(parentWidget)
self.parentWidget = parentWidget
self.layout = QtWidgets.QHBoxLayout()
self.setLayout(self.layout)
self.comboBox = QtWidgets.QComboBox(self)
self.layout.addWidget(self.comboBox)
"""
self.volumeDial = parentWidget.ui.auditionerVolumeDial
self.volumeDial.setMaximum(0)
self.volumeDial.setMinimum(-21)
self.volumeDial.valueChanged.connect(self._sendVolumeChangeToEngine)
self.volumeDial.enterEvent = lambda ev: self.parentWidget.statusBar().showMessage((QtCore.QCoreApplication.translate("Auditioner", "Use mousewheel to change the Auditioner Volume")))
self.volumeDial.leaveEvent = lambda ev: self.parentWidget.statusBar().showMessage("")
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."))
self.layout.addWidget(self.label)
#if not api.isStandaloneMode():
#self.wholePanel.hide()
#return
##self.wholePanel.show() #explicit is better than implicit
self.originalShowPopup = self.comboBox.showPopup
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()
#api.callbacks.startLoadingAuditionerInstrument.append(self.callback_startLoadingAuditionerInstrument)
#api.callbacks.auditionerInstrumentChanged.append(self.callback_auditionerInstrumentChanged)
#api.callbacks.auditionerVolumeChanged.append(self.callback__auditionerVolumeChanged)
def callback_startLoadingAuditionerInstrument(self, idkey):
self.parentWidget.qtApp.setOverrideCursor(QtCore.Qt.WaitCursor) #reset in self.callback_auditionerInstrumentChanged
self.label.setText(QtCore.QCoreApplication.translate("QuickMidiInputComboController", "…loading…"))
self.parentWidget.qtApp.processEvents() #actually show the label and cursor
def callback_auditionerInstrumentChanged(self, exportMetadata:dict):
self.parentWidget.qtApp.restoreOverrideCursor() #We assume the cursor was set to a loading animation
key = exportMetadata["id-key"]
t = f"➜ [{key[0]}-{key[1]}] {exportMetadata['name']}"
self.label.setText(t)
def callback__auditionerVolumeChanged(self, value:float):
self.volumeDial.setValue(value)
self.parentWidget.statusBar().showMessage(QtCore.QCoreApplication.translate("QuickMidiInputComboController", "Volume: {}").format(value))
def _sendVolumeChangeToEngine(self, newValue):
self.volumeDial.blockSignals(True)
api.setAuditionerVolume(newValue)
self.volumeDial.blockSignals(False)
def _newPortChosen(self, index:int):
assert self.comboBox.currentIndex() == index
self.connectMidiInputPort(self.comboBox.currentText())
def showPopup(self):
"""When the combobox is opened quickly update the port list before showing it"""
self._fill()
self.originalShowPopup()
def _fill(self):
self.comboBox.clear()
self.comboBox.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents)
availablePorts = self.getAvailablePorts()
self.comboBox.addItem("") # Not only a more visible seaparator than the Qt one, but also doubles as "disconnect"
self.comboBox.addItems(availablePorts["hardware"])
#self.comboBox.insertSeparator(len(availablePorts["hardware"])+1)
self.comboBox.addItem("") # Not only a more visible seaparator than the Qt one, but also doubles as "disconnect"
self.comboBox.addItems(availablePorts["software"])
def getAvailablePorts(self)->dict:
"""This function queries JACK each time it is called.
It returns a dict with two lists.
Keys "hardware" and "software" for the type of port.
"""
result = {}
hardware = set(cbox.JackIO.get_ports(".*", cbox.JackIO.MIDI_TYPE, cbox.JackIO.PORT_IS_SOURCE | cbox.JackIO.PORT_IS_PHYSICAL))
allPorts = set(cbox.JackIO.get_ports(".*", cbox.JackIO.MIDI_TYPE, cbox.JackIO.PORT_IS_SOURCE))
software = allPorts.difference(hardware)
result["hardware"] = sorted(list(hardware))
result["software"] = sorted(list(software))
return result
def connectMidiInputPort(self, externalPort:str):
"""externalPort is in the Client:Port JACK format
If "" False or None disconnect all ports."""
try:
currentConnectedList = cbox.JackIO.get_connected_ports(self.cboxMidiPortUid)
except: #port not found.
currentConnectedList = []
for port in currentConnectedList:
cbox.JackIO.port_disconnect(port, self.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)