|
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
-
- # Common/Shared code related to Canvas and JACK
- # Copyright (C) 2010-2013 Filipe Coelho <falktx@falktx.com>
- #
- # This program 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 2 of the License, or
- # 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.
- #
- # For a full copy of the GNU General Public License see the COPYING file
-
- # ------------------------------------------------------------------------------------------------------------
- # Imports (Global)
-
- from PyQt4.QtCore import pyqtSlot, QTimer
- from PyQt4.QtGui import QCursor, QFontMetrics, QImage, QMainWindow, QMenu, QPainter, QPrinter, QPrintDialog
-
- # ------------------------------------------------------------------------------------------------------------
- # Imports (Custom Stuff)
-
- import patchcanvas
- import jacksettings
- import logs
- import render
- from shared import *
- from jacklib_helpers import *
-
- # ------------------------------------------------------------------------------------------------------------
- # Have JACK2 ?
-
- if DEBUG and jacklib and jacklib.JACK2:
- print("Using JACK2, version %s" % cString(jacklib.get_version_string()))
-
- # ------------------------------------------------------------------------------------------------------------
- # Static Variables
-
- TRANSPORT_VIEW_HMS = 0
- TRANSPORT_VIEW_BBT = 1
- TRANSPORT_VIEW_FRAMES = 2
-
- BUFFER_SIZE_LIST = (16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192)
- SAMPLE_RATE_LIST = (22050, 32000, 44100, 48000, 88200, 96000, 192000)
-
- # ------------------------------------------------------------------------------------------------------------
- # Global DBus object
-
- class DBusObject(object):
- __slots__ = [
- 'loop',
- 'bus',
- 'a2j',
- 'jack',
- 'ladish_control',
- 'ladish_studio',
- 'ladish_room',
- 'ladish_graph',
- 'ladish_manager',
- 'ladish_app_iface',
- 'ladish_app_daemon',
- 'patchbay'
- ]
-
- gDBus = DBusObject()
- gDBus.loop = None
- gDBus.bus = None
- gDBus.a2j = None
- gDBus.jack = None
- gDBus.ladish_control = None
- gDBus.ladish_studio = None
- gDBus.ladish_room = None
- gDBus.ladish_graph = None
- gDBus.ladish_app_iface = None
- gDBus.ladish_app_daemon = None
- gDBus.patchbay = None
-
- # ------------------------------------------------------------------------------------------------------------
- # Global JACK object
-
- class JackObject(object):
- __slots__ = [
- 'client'
- ]
-
- gJack = JackObject()
- gJack.client = None
-
- # ------------------------------------------------------------------------------------------------------------
- # Abstract Canvas and JACK Class
-
- class AbstractCanvasJackClass(QMainWindow):
- def __init__(self, appName, UiClass, parent):
- QMainWindow.__init__(self, parent)
-
- self.ui = UiClass()
- self.ui.setupUi(self)
-
- self.fAppName = appName
- self.fCurTransportView = TRANSPORT_VIEW_HMS
-
- self.fLastBPM = None
- self.fLastTransportState = None
-
- self.fXruns = -1
- self.fBufferSize = 0
- self.fSampleRate = 0.0
- self.fNextSampleRate = 0.0
-
- self.fLogsW = None
- self.scene = None
-
- # -----------------------------------------------------------------
- # Abstract calls
-
- def initPorts(self):
- pass
-
- def jackStopped(self):
- pass
-
- # -----------------------------------------------------------------
- # JACK Property change calls
-
- def jack_setBufferSize(self, bufferSize):
- if self.fBufferSize == bufferSize:
- return
-
- if gJack.client:
- failed = bool(jacklib.set_buffer_size(gJack.client, bufferSize) != 0)
- else:
- failed = bool(jacksettings.setBufferSize(bufferSize))
-
- if failed:
- print("Failed to change buffer-size as %i, reset to %i" % (bufferSize, self.fBufferSize))
- self.ui_setBufferSize(self.fBufferSize, True)
-
- def jack_setSampleRate(self, sampleRate):
- if gJack.client:
- # Show change-in-future dialog
- self.ui_setSampleRate(sampleRate, True)
- else:
- # Try to set sampleRate via dbus now
- if jacksettings.setSampleRate(sampleRate):
- self.ui_setSampleRate(sampleRate)
-
- @pyqtSlot(bool)
- def slot_jackBufferSize_Menu(self, clicked):
- sender = self.sender()
-
- # ignore non-clicks
- if not clicked:
- sender.blockSignals(True)
- sender.setChecked(True)
- sender.blockSignals(False)
- return
-
- text = sender.text()
- if text and text.isdigit():
- self.jack_setBufferSize(int(text))
-
- @pyqtSlot(str)
- def slot_jackBufferSize_ComboBox(self, text):
- if text and text.isdigit():
- self.jack_setBufferSize(int(text))
-
- @pyqtSlot(str)
- def slot_jackSampleRate_ComboBox(self, text):
- if text and text.isdigit():
- self.jack_setSampleRate(int(text))
-
- # -----------------------------------------------------------------
- # JACK Transport calls
-
- def setTransportView(self, view):
- if view == TRANSPORT_VIEW_HMS:
- self.fCurTransportView = TRANSPORT_VIEW_HMS
- self.ui.label_time.setMinimumWidth(QFontMetrics(self.ui.label_time.font()).width("00:00:00") + 3)
- elif view == TRANSPORT_VIEW_BBT:
- self.fCurTransportView = TRANSPORT_VIEW_BBT
- self.ui.label_time.setMinimumWidth(QFontMetrics(self.ui.label_time.font()).width("000|00|0000") + 3)
- elif view == TRANSPORT_VIEW_FRAMES:
- self.fCurTransportView = TRANSPORT_VIEW_FRAMES
- self.ui.label_time.setMinimumWidth(QFontMetrics(self.ui.label_time.font()).width("000'000'000") + 3)
- else:
- self.setTransportView(TRANSPORT_VIEW_HMS)
-
- @pyqtSlot(bool)
- def slot_transportPlayPause(self, play):
- if not gJack.client:
- return
-
- if play:
- jacklib.transport_start(gJack.client)
- else:
- jacklib.transport_stop(gJack.client)
-
- self.refreshTransport()
-
- @pyqtSlot()
- def slot_transportStop(self):
- if not gJack.client:
- return
-
- jacklib.transport_stop(gJack.client)
- jacklib.transport_locate(gJack.client, 0)
-
- self.refreshTransport()
-
- @pyqtSlot()
- def slot_transportBackwards(self):
- if self.fSampleRate == 0.0 or not gJack.client:
- return
-
- curFrame = jacklib.get_current_transport_frame(gJack.client)
-
- if curFrame == 0:
- return
-
- newFrame = curFrame - int(self.fSampleRate*2.5)
-
- if newFrame < 0:
- newFrame = 0
-
- jacklib.transport_locate(gJack.client, newFrame)
-
- @pyqtSlot()
- def slot_transportForwards(self):
- if self.fSampleRate == 0.0 or not gJack.client:
- return
-
- newFrame = jacklib.get_current_transport_frame(gJack.client) + int(self.fSampleRate*2.5)
- jacklib.transport_locate(gJack.client, newFrame)
-
- @pyqtSlot()
- def slot_transportViewMenu(self):
- menu = QMenu(self)
- actHMS = menu.addAction("Hours:Minutes:Seconds")
- actBBT = menu.addAction("Beat:Bar:Tick")
- actFrames = menu.addAction("Frames")
-
- actHMS.setCheckable(True)
- actBBT.setCheckable(True)
- actFrames.setCheckable(True)
-
- if self.fCurTransportView == TRANSPORT_VIEW_HMS:
- actHMS.setChecked(True)
- elif self.fCurTransportView == TRANSPORT_VIEW_BBT:
- actBBT.setChecked(True)
- elif self.fCurTransportView == TRANSPORT_VIEW_FRAMES:
- actFrames.setChecked(True)
-
- actSelected = menu.exec_(QCursor().pos())
-
- if actSelected == actHMS:
- self.setTransportView(TRANSPORT_VIEW_HMS)
- elif actSelected == actBBT:
- self.setTransportView(TRANSPORT_VIEW_BBT)
- elif actSelected == actFrames:
- self.setTransportView(TRANSPORT_VIEW_FRAMES)
-
- # -----------------------------------------------------------------
- # Refresh JACK stuff
-
- def refreshDSPLoad(self):
- if not gJack.client:
- return
-
- self.ui_setDSPLoad(int(jacklib.cpu_load(gJack.client)))
-
- def refreshTransport(self):
- if not gJack.client:
- return
-
- pos = jacklib.jack_position_t()
- pos.valid = 0
-
- state = jacklib.transport_query(gJack.client, jacklib.pointer(pos))
-
- if self.fCurTransportView == TRANSPORT_VIEW_HMS:
- time = pos.frame / int(self.fSampleRate)
- secs = time % 60
- mins = (time / 60) % 60
- hrs = (time / 3600) % 60
- self.ui.label_time.setText("%02i:%02i:%02i" % (hrs, mins, secs))
-
- elif self.fCurTransportView == TRANSPORT_VIEW_BBT:
- if pos.valid & jacklib.JackPositionBBT:
- bar = pos.bar
- beat = pos.beat if bar != 0 else 0
- tick = pos.tick if bar != 0 else 0
- else:
- bar = 0
- beat = 0
- tick = 0
-
- self.ui.label_time.setText("%03i|%02i|%04i" % (bar, beat, tick))
-
- elif self.fCurTransportView == TRANSPORT_VIEW_FRAMES:
- frame1 = pos.frame % 1000
- frame2 = (pos.frame / 1000) % 1000
- frame3 = (pos.frame / 1000000) % 1000
- self.ui.label_time.setText("%03i'%03i'%03i" % (frame3, frame2, frame1))
-
- if pos.valid & jacklib.JackPositionBBT:
- if self.fLastBPM != pos.beats_per_minute:
- self.ui.sb_bpm.setValue(pos.beats_per_minute)
- self.ui.sb_bpm.setStyleSheet("")
- else:
- pos.beats_per_minute = -1.0
- if self.fLastBPM != pos.beats_per_minute:
- self.ui.sb_bpm.setStyleSheet("QDoubleSpinBox { color: palette(mid); }")
-
- self.fLastBPM = pos.beats_per_minute
-
- if state != self.fLastTransportState:
- self.fLastTransportState = state
-
- if state == jacklib.JackTransportStopped:
- icon = getIcon("media-playback-start")
- self.ui.act_transport_play.setChecked(False)
- self.ui.act_transport_play.setText(self.tr("&Play"))
- self.ui.b_transport_play.setChecked(False)
- else:
- icon = getIcon("media-playback-pause")
- self.ui.act_transport_play.setChecked(True)
- self.ui.act_transport_play.setText(self.tr("&Pause"))
- self.ui.b_transport_play.setChecked(True)
-
- self.ui.act_transport_play.setIcon(icon)
- self.ui.b_transport_play.setIcon(icon)
-
- # -----------------------------------------------------------------
- # Set JACK stuff
-
- def ui_setBufferSize(self, bufferSize, forced=False):
- if self.fBufferSize == bufferSize and not forced:
- return
-
- self.fBufferSize = bufferSize
-
- if bufferSize:
- self.ui.cb_buffer_size.blockSignals(True)
-
- if bufferSize == 16:
- self.ui.cb_buffer_size.setCurrentIndex(0)
- elif bufferSize == 32:
- self.ui.cb_buffer_size.setCurrentIndex(1)
- elif bufferSize == 64:
- self.ui.cb_buffer_size.setCurrentIndex(2)
- elif bufferSize == 128:
- self.ui.cb_buffer_size.setCurrentIndex(3)
- elif bufferSize == 256:
- self.ui.cb_buffer_size.setCurrentIndex(4)
- elif bufferSize == 512:
- self.ui.cb_buffer_size.setCurrentIndex(5)
- elif bufferSize == 1024:
- self.ui.cb_buffer_size.setCurrentIndex(6)
- elif bufferSize == 2048:
- self.ui.cb_buffer_size.setCurrentIndex(7)
- elif bufferSize == 4096:
- self.ui.cb_buffer_size.setCurrentIndex(8)
- elif bufferSize == 8192:
- self.ui.cb_buffer_size.setCurrentIndex(9)
- else:
- self.ui.cb_buffer_size.setCurrentIndex(-1)
-
- self.ui.cb_buffer_size.blockSignals(False)
-
- if self.fAppName == "Catia" and bufferSize:
- for actBufSize in self.ui.act_jack_bf_list:
- actBufSize.blockSignals(True)
- actBufSize.setEnabled(True)
-
- if actBufSize.text().replace("&", "") == str(bufferSize):
- actBufSize.setChecked(True)
- else:
- actBufSize.setChecked(False)
-
- actBufSize.blockSignals(False)
-
- def ui_setSampleRate(self, sampleRate, future=False):
- if self.fSampleRate == sampleRate:
- return
-
- if future:
- ask = QMessageBox.question(self, self.tr("Change Sample Rate"),
- self.tr("It's not possible to change Sample Rate while JACK is running.\n"
- "Do you want to change as soon as JACK stops?"), QMessageBox.Ok | QMessageBox.Cancel)
-
- if ask == QMessageBox.Ok:
- self.fNextSampleRate = sampleRate
- else:
- self.fNextSampleRate = 0.0
-
- # not future
- else:
- self.fSampleRate = sampleRate
- self.fNextSampleRate = 0.0
-
- for i in range(len(SAMPLE_RATE_LIST)):
- sampleRateI = SAMPLE_RATE_LIST[i]
-
- if self.fSampleRate == sampleRateI:
- self.ui.cb_sample_rate.blockSignals(True)
- self.ui.cb_sample_rate.setCurrentIndex(i)
- self.ui.cb_sample_rate.blockSignals(False)
- break
-
- def ui_setRealTime(self, isRealtime):
- self.ui.label_realtime.setText(" RT " if isRealtime else " <s>RT</s> ")
- self.ui.label_realtime.setEnabled(isRealtime)
-
- def ui_setDSPLoad(self, dspLoad):
- self.ui.pb_dsp_load.setValue(dspLoad)
-
- def ui_setXruns(self, xruns):
- txt1 = str(xruns) if (xruns >= 0) else "--"
- txt2 = "" if (xruns == 1) else "s"
- self.ui.b_xruns.setText("%s Xrun%s" % (txt1, txt2))
-
- # -----------------------------------------------------------------
- # External Dialogs
-
- @pyqtSlot()
- def slot_showJackSettings(self):
- jacksettingsW = jacksettings.JackSettingsW(self)
- jacksettingsW.exec_()
- del jacksettingsW
-
- # Force update of gui widgets
- if not gJack.client:
- self.jackStopped()
-
- @pyqtSlot()
- def slot_showLogs(self):
- if self.fLogsW is None:
- self.fLogsW = logs.LogsW(self)
- self.fLogsW.show()
-
- @pyqtSlot()
- def slot_showRender(self):
- renderW = render.RenderW(self)
- renderW.exec_()
- del renderW
-
- # -----------------------------------------------------------------
- # Shared Canvas code
-
- @pyqtSlot()
- def slot_canvasArrange(self):
- patchcanvas.arrange()
-
- @pyqtSlot()
- def slot_canvasRefresh(self):
- patchcanvas.clear()
- self.initPorts()
-
- @pyqtSlot()
- def slot_canvasZoomFit(self):
- self.scene.zoom_fit()
-
- @pyqtSlot()
- def slot_canvasZoomIn(self):
- self.scene.zoom_in()
-
- @pyqtSlot()
- def slot_canvasZoomOut(self):
- self.scene.zoom_out()
-
- @pyqtSlot()
- def slot_canvasZoomReset(self):
- self.scene.zoom_reset()
-
- @pyqtSlot()
- def slot_canvasPrint(self):
- self.scene.clearSelection()
- self.fExportPrinter = QPrinter()
- dialog = QPrintDialog(self.fExportPrinter, self)
-
- if dialog.exec_():
- painter = QPainter(self.fExportPrinter)
- painter.save()
- painter.setRenderHint(QPainter.Antialiasing, True)
- painter.setRenderHint(QPainter.TextAntialiasing, True)
- self.scene.render(painter)
- painter.restore()
-
- @pyqtSlot()
- def slot_canvasSaveImage(self):
- newPath = QFileDialog.getSaveFileName(self, self.tr("Save Image"), filter=self.tr("PNG Image (*.png);;JPEG Image (*.jpg)"))
-
- if not newPath:
- return
-
- self.scene.clearSelection()
-
- if newPath.lower().endswith(".jpg"):
- imgFormat = "JPG"
- elif newPath.lower().endswith(".png"):
- imgFormat = "PNG"
- else:
- # File-dialog may not auto-add the extension
- imgFormat = "PNG"
- newPath += ".png"
-
- self.fExportImage = QImage(self.scene.sceneRect().width(), self.scene.sceneRect().height(), QImage.Format_RGB32)
- painter = QPainter(self.fExportImage)
- painter.save()
- painter.setRenderHint(QPainter.Antialiasing, True)
- painter.setRenderHint(QPainter.TextAntialiasing, True)
- self.scene.render(painter)
- self.fExportImage.save(newPath, imgFormat, 100)
- painter.restore()
-
- # -----------------------------------------------------------------
- # Shared Connections
-
- def setCanvasConnections(self):
- self.ui.act_canvas_arrange.setEnabled(False) # TODO, later
- self.connect(self.ui.act_canvas_arrange, SIGNAL("triggered()"), SLOT("slot_canvasArrange()"))
- self.connect(self.ui.act_canvas_refresh, SIGNAL("triggered()"), SLOT("slot_canvasRefresh()"))
- self.connect(self.ui.act_canvas_zoom_fit, SIGNAL("triggered()"), SLOT("slot_canvasZoomFit()"))
- self.connect(self.ui.act_canvas_zoom_in, SIGNAL("triggered()"), SLOT("slot_canvasZoomIn()"))
- self.connect(self.ui.act_canvas_zoom_out, SIGNAL("triggered()"), SLOT("slot_canvasZoomOut()"))
- self.connect(self.ui.act_canvas_zoom_100, SIGNAL("triggered()"), SLOT("slot_canvasZoomReset()"))
- self.connect(self.ui.act_canvas_print, SIGNAL("triggered()"), SLOT("slot_canvasPrint()"))
- self.connect(self.ui.act_canvas_save_image, SIGNAL("triggered()"), SLOT("slot_canvasSaveImage()"))
- self.connect(self.ui.b_canvas_zoom_fit, SIGNAL("clicked()"), SLOT("slot_canvasZoomFit()"))
- self.connect(self.ui.b_canvas_zoom_in, SIGNAL("clicked()"), SLOT("slot_canvasZoomIn()"))
- self.connect(self.ui.b_canvas_zoom_out, SIGNAL("clicked()"), SLOT("slot_canvasZoomOut()"))
- self.connect(self.ui.b_canvas_zoom_100, SIGNAL("clicked()"), SLOT("slot_canvasZoomReset()"))
-
- def setJackConnections(self, modes):
- if "jack" in modes:
- self.connect(self.ui.act_jack_clear_xruns, SIGNAL("triggered()"), SLOT("slot_JackClearXruns()"))
- self.connect(self.ui.act_jack_render, SIGNAL("triggered()"), SLOT("slot_showRender()"))
- self.connect(self.ui.act_jack_configure, SIGNAL("triggered()"), SLOT("slot_showJackSettings()"))
- self.connect(self.ui.b_jack_clear_xruns, SIGNAL("clicked()"), SLOT("slot_JackClearXruns()"))
- self.connect(self.ui.b_jack_configure, SIGNAL("clicked()"), SLOT("slot_showJackSettings()"))
- self.connect(self.ui.b_jack_render, SIGNAL("clicked()"), SLOT("slot_showRender()"))
- self.connect(self.ui.cb_buffer_size, SIGNAL("currentIndexChanged(QString)"), SLOT("slot_jackBufferSize_ComboBox(QString)"))
- self.connect(self.ui.cb_sample_rate, SIGNAL("currentIndexChanged(QString)"), SLOT("slot_jackSampleRate_ComboBox(QString)"))
- self.connect(self.ui.b_xruns, SIGNAL("clicked()"), SLOT("slot_JackClearXruns()"))
-
- if "buffer-size" in modes:
- self.connect(self.ui.act_jack_bf_16, SIGNAL("triggered(bool)"), SLOT("slot_jackBufferSize_Menu(bool)"))
- self.connect(self.ui.act_jack_bf_32, SIGNAL("triggered(bool)"), SLOT("slot_jackBufferSize_Menu(bool)"))
- self.connect(self.ui.act_jack_bf_64, SIGNAL("triggered(bool)"), SLOT("slot_jackBufferSize_Menu(bool)"))
- self.connect(self.ui.act_jack_bf_128, SIGNAL("triggered(bool)"), SLOT("slot_jackBufferSize_Menu(bool)"))
- self.connect(self.ui.act_jack_bf_256, SIGNAL("triggered(bool)"), SLOT("slot_jackBufferSize_Menu(bool)"))
- self.connect(self.ui.act_jack_bf_512, SIGNAL("triggered(bool)"), SLOT("slot_jackBufferSize_Menu(bool)"))
- self.connect(self.ui.act_jack_bf_1024, SIGNAL("triggered(bool)"), SLOT("slot_jackBufferSize_Menu(bool)"))
- self.connect(self.ui.act_jack_bf_2048, SIGNAL("triggered(bool)"), SLOT("slot_jackBufferSize_Menu(bool)"))
- self.connect(self.ui.act_jack_bf_4096, SIGNAL("triggered(bool)"), SLOT("slot_jackBufferSize_Menu(bool)"))
- self.connect(self.ui.act_jack_bf_8192, SIGNAL("triggered(bool)"), SLOT("slot_jackBufferSize_Menu(bool)"))
-
- if "transport" in modes:
- self.connect(self.ui.act_transport_play, SIGNAL("triggered(bool)"), SLOT("slot_transportPlayPause(bool)"))
- self.connect(self.ui.act_transport_stop, SIGNAL("triggered()"), SLOT("slot_transportStop()"))
- self.connect(self.ui.act_transport_backwards, SIGNAL("triggered()"), SLOT("slot_transportBackwards()"))
- self.connect(self.ui.act_transport_forwards, SIGNAL("triggered()"), SLOT("slot_transportForwards()"))
- self.connect(self.ui.b_transport_play, SIGNAL("clicked(bool)"), SLOT("slot_transportPlayPause(bool)"))
- self.connect(self.ui.b_transport_stop, SIGNAL("clicked()"), SLOT("slot_transportStop()"))
- self.connect(self.ui.b_transport_backwards, SIGNAL("clicked()"), SLOT("slot_transportBackwards()"))
- self.connect(self.ui.b_transport_forwards, SIGNAL("clicked()"), SLOT("slot_transportForwards()"))
- self.connect(self.ui.label_time, SIGNAL("customContextMenuRequested(QPoint)"), SLOT("slot_transportViewMenu()"))
-
- if "misc" in modes:
- if LINUX:
- self.connect(self.ui.act_show_logs, SIGNAL("triggered()"), SLOT("slot_showLogs()"))
- else:
- self.ui.act_show_logs.setEnabled(False)
|