Collection of tools useful for audio production
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.

525 lines
21KB

  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # Common/Shared code related to Canvas and JACK
  4. # Copyright (C) 2010-2012 Filipe Coelho <falktx@falktx.com>
  5. #
  6. # This program is free software; you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation; either version 2 of the License, or
  9. # any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # For a full copy of the GNU General Public License see the COPYING file
  17. # ------------------------------------------------------------------------------------------------------------
  18. # Imports (Global)
  19. from PyQt4.QtCore import pyqtSlot, QSettings, QTimer
  20. from PyQt4.QtGui import QCursor, QFontMetrics, QImage, QMainWindow, QMenu, QPainter, QPrinter, QPrintDialog
  21. # ------------------------------------------------------------------------------------------------------------
  22. # Imports (Custom Stuff)
  23. import patchcanvas
  24. import jacksettings, logs, render
  25. from shared import *
  26. from jacklib_helpers import *
  27. # ------------------------------------------------------------------------------------------------------------
  28. # Have JACK2 ?
  29. if DEBUG and jacklib and jacklib.JACK2:
  30. print("Using JACK2, version %s" % cString(jacklib.get_version_string()))
  31. # ------------------------------------------------------------------------------------------------------------
  32. # Static Variables
  33. TRANSPORT_VIEW_HMS = 0
  34. TRANSPORT_VIEW_BBT = 1
  35. TRANSPORT_VIEW_FRAMES = 2
  36. BUFFER_SIZE_LIST = (16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192)
  37. SAMPLE_RATE_LIST = (22050, 32000, 44100, 48000, 88200, 96000, 192000)
  38. # ------------------------------------------------------------------------------------------------------------
  39. # Global DBus object
  40. class DBusObject(object):
  41. __slots__ = [
  42. 'loop',
  43. 'bus',
  44. 'a2j',
  45. 'jack',
  46. 'ladish_control',
  47. 'ladish_studio',
  48. 'ladish_room',
  49. 'ladish_graph',
  50. 'ladish_manager',
  51. 'ladish_app_iface',
  52. 'ladish_app_daemon',
  53. 'patchbay'
  54. ]
  55. DBus = DBusObject()
  56. DBus.loop = None
  57. DBus.bus = None
  58. DBus.a2j = None
  59. DBus.jack = None
  60. DBus.ladish_control = None
  61. DBus.ladish_studio = None
  62. DBus.ladish_room = None
  63. DBus.ladish_graph = None
  64. DBus.ladish_app_iface = None
  65. DBus.ladish_app_daemon = None
  66. DBus.patchbay = None
  67. # ------------------------------------------------------------------------------------------------------------
  68. # Global JACK object
  69. class JackObject(object):
  70. __slots__ = [
  71. 'client'
  72. ]
  73. jack = JackObject()
  74. jack.client = None
  75. # ------------------------------------------------------------------------------------------------------------
  76. # Abstract Canvas and JACK Class
  77. class AbstractCanvasJackClass(QMainWindow):
  78. def __init__(self, parent, appName):
  79. QMainWindow.__init__(self, parent)
  80. self.m_appName = appName
  81. self.m_curTransportView = TRANSPORT_VIEW_HMS
  82. self.m_lastBPM = None
  83. self.m_lastTransportState = None
  84. self.m_xruns = -1
  85. self.m_bufferSize = 0
  86. self.m_sampleRate = 0
  87. self.m_nextSampleRate = 0
  88. self.m_logsW = None
  89. self.settings = QSettings("Cadence", appName)
  90. # -----------------------------------------------------------------
  91. # JACK Property change calls
  92. def jack_setBufferSize(self, bufferSize):
  93. if self.m_bufferSize == bufferSize:
  94. return
  95. if jack.client:
  96. failed = bool(jacklib.set_buffer_size(jack.client, bufferSize) != 0)
  97. else:
  98. failed = bool(jacksettings.setBufferSize(bufferSize))
  99. if failed:
  100. print("Failed to change buffer-size as %i, reset to %i" % (bufferSize, self.m_bufferSize))
  101. self.setBufferSize(self.m_bufferSize, True)
  102. def jack_setSampleRate(self, sampleRate):
  103. if jack.client:
  104. # Show change-in-future dialog
  105. self.setSampleRate(sampleRate, True)
  106. else:
  107. # Try to set sampleRate via dbus now
  108. if jacksettings.setSampleRate(sampleRate):
  109. self.setSampleRate(sampleRate)
  110. @pyqtSlot()
  111. def slot_jackBufferSize_Menu(self):
  112. bufferSize = int(self.sender().text())
  113. self.jack_setBufferSize(bufferSize)
  114. @pyqtSlot(str)
  115. def slot_jackBufferSize_ComboBox(self, text):
  116. if text and text.isdigit():
  117. self.jack_setBufferSize(int(text))
  118. @pyqtSlot(str)
  119. def slot_jackSampleRate_ComboBox(self, text):
  120. if text and text.isdigit():
  121. self.jack_setSampleRate(int(text))
  122. # -----------------------------------------------------------------
  123. # JACK Transport calls
  124. def setTransportView(self, view):
  125. if view == TRANSPORT_VIEW_HMS:
  126. self.m_curTransportView = TRANSPORT_VIEW_HMS
  127. self.label_time.setMinimumWidth(QFontMetrics(self.label_time.font()).width("00:00:00") + 3)
  128. elif view == TRANSPORT_VIEW_BBT:
  129. self.m_curTransportView = TRANSPORT_VIEW_BBT
  130. self.label_time.setMinimumWidth(QFontMetrics(self.label_time.font()).width("000|00|0000") + 3)
  131. elif view == TRANSPORT_VIEW_FRAMES:
  132. self.m_curTransportView = TRANSPORT_VIEW_FRAMES
  133. self.label_time.setMinimumWidth(QFontMetrics(self.label_time.font()).width("000'000'000") + 3)
  134. else:
  135. self.setTransportView(TRANSPORT_VIEW_HMS)
  136. @pyqtSlot(bool)
  137. def slot_transportPlayPause(self, play):
  138. if not jack.client: return
  139. if play:
  140. jacklib.transport_start(jack.client)
  141. else:
  142. jacklib.transport_stop(jack.client)
  143. self.refreshTransport()
  144. @pyqtSlot()
  145. def slot_transportStop(self):
  146. if not jack.client: return
  147. jacklib.transport_stop(jack.client)
  148. jacklib.transport_locate(jack.client, 0)
  149. self.refreshTransport()
  150. @pyqtSlot()
  151. def slot_transportBackwards(self):
  152. if not jack.client: return
  153. newFrame = jacklib.get_current_transport_frame(jack.client) - 100000
  154. if newFrame < 0:
  155. newFrame = 0
  156. jacklib.transport_locate(jack.client, newFrame)
  157. @pyqtSlot()
  158. def slot_transportForwards(self):
  159. if not jack.client: return
  160. newFrame = jacklib.get_current_transport_frame(jack.client) + 100000
  161. jacklib.transport_locate(jack.client, newFrame)
  162. @pyqtSlot()
  163. def slot_transportViewMenu(self):
  164. menu = QMenu(self)
  165. act_t_hms = menu.addAction("Hours:Minutes:Seconds")
  166. act_t_bbt = menu.addAction("Beat:Bar:Tick")
  167. act_t_fr = menu.addAction("Frames")
  168. act_t_hms.setCheckable(True)
  169. act_t_bbt.setCheckable(True)
  170. act_t_fr.setCheckable(True)
  171. if self.m_curTransportView == TRANSPORT_VIEW_HMS:
  172. act_t_hms.setChecked(True)
  173. elif self.m_curTransportView == TRANSPORT_VIEW_BBT:
  174. act_t_bbt.setChecked(True)
  175. elif self.m_curTransportView == TRANSPORT_VIEW_FRAMES:
  176. act_t_fr.setChecked(True)
  177. act_selected = menu.exec_(QCursor().pos())
  178. if act_selected == act_t_hms:
  179. self.setTransportView(TRANSPORT_VIEW_HMS)
  180. elif act_selected == act_t_bbt:
  181. self.setTransportView(TRANSPORT_VIEW_BBT)
  182. elif act_selected == act_t_fr:
  183. self.setTransportView(TRANSPORT_VIEW_FRAMES)
  184. # -----------------------------------------------------------------
  185. # Refresh JACK stuff
  186. def refreshDSPLoad(self):
  187. if not jack.client: return
  188. self.setDSPLoad(int(jacklib.cpu_load(jack.client)))
  189. def refreshTransport(self):
  190. if not jack.client: return
  191. pos = jacklib.jack_position_t()
  192. pos.valid = 0
  193. state = jacklib.transport_query(jack.client, jacklib.pointer(pos))
  194. if self.m_curTransportView == TRANSPORT_VIEW_HMS:
  195. time = pos.frame / self.m_sampleRate
  196. secs = time % 60
  197. mins = (time / 60) % 60
  198. hrs = (time / 3600) % 60
  199. self.label_time.setText("%02i:%02i:%02i" % (hrs, mins, secs))
  200. elif self.m_curTransportView == TRANSPORT_VIEW_BBT:
  201. if pos.valid & jacklib.JackPositionBBT:
  202. bar = pos.bar
  203. beat = pos.beat if bar != 0 else 0
  204. tick = pos.tick if bar != 0 else 0
  205. self.label_time.setText("%03i|%02i|%04i" % (bar, beat, tick))
  206. else:
  207. self.label_time.setText("%03i|%02i|%04i" % (0, 0, 0))
  208. elif self.m_curTransportView == TRANSPORT_VIEW_FRAMES:
  209. frame1 = pos.frame % 1000
  210. frame2 = (pos.frame / 1000) % 1000
  211. frame3 = (pos.frame / 1000000) % 1000
  212. self.label_time.setText("%03i'%03i'%03i" % (frame3, frame2, frame1))
  213. if pos.valid & jacklib.JackPositionBBT:
  214. if self.m_lastBPM != pos.beats_per_minute:
  215. self.sb_bpm.setValue(pos.beats_per_minute)
  216. self.sb_bpm.setStyleSheet("")
  217. else:
  218. pos.beats_per_minute = -1
  219. if self.m_lastBPM != pos.beats_per_minute:
  220. self.sb_bpm.setStyleSheet("QDoubleSpinBox { color: palette(mid); }")
  221. self.m_lastBPM = pos.beats_per_minute
  222. if state != self.m_lastTransportState:
  223. if state == jacklib.JackTransportStopped:
  224. icon = getIcon("media-playback-start")
  225. self.act_transport_play.setChecked(False)
  226. self.act_transport_play.setIcon(icon)
  227. self.act_transport_play.setText(self.tr("&Play"))
  228. self.b_transport_play.setChecked(False)
  229. self.b_transport_play.setIcon(icon)
  230. else:
  231. icon = getIcon("media-playback-pause")
  232. self.act_transport_play.setChecked(True)
  233. self.act_transport_play.setIcon(icon)
  234. self.act_transport_play.setText(self.tr("&Pause"))
  235. self.b_transport_play.setChecked(True)
  236. self.b_transport_play.setIcon(icon)
  237. self.m_lastTransportState = state
  238. # -----------------------------------------------------------------
  239. # Set JACK stuff
  240. def setBufferSize(self, bufferSize, forced=False):
  241. if self.m_bufferSize == bufferSize and not forced:
  242. return
  243. self.m_bufferSize = bufferSize
  244. if bufferSize:
  245. if bufferSize == 16:
  246. self.cb_buffer_size.setCurrentIndex(0)
  247. elif bufferSize == 32:
  248. self.cb_buffer_size.setCurrentIndex(1)
  249. elif bufferSize == 64:
  250. self.cb_buffer_size.setCurrentIndex(2)
  251. elif bufferSize == 128:
  252. self.cb_buffer_size.setCurrentIndex(3)
  253. elif bufferSize == 256:
  254. self.cb_buffer_size.setCurrentIndex(4)
  255. elif bufferSize == 512:
  256. self.cb_buffer_size.setCurrentIndex(5)
  257. elif bufferSize == 1024:
  258. self.cb_buffer_size.setCurrentIndex(6)
  259. elif bufferSize == 2048:
  260. self.cb_buffer_size.setCurrentIndex(7)
  261. elif bufferSize == 4096:
  262. self.cb_buffer_size.setCurrentIndex(8)
  263. elif bufferSize == 8192:
  264. self.cb_buffer_size.setCurrentIndex(9)
  265. else:
  266. QMessageBox.warning(self, self.tr("Warning"), self.tr("Invalid JACK buffer-size requested: %i" % bufferSize))
  267. if self.m_appName == "Catia":
  268. if bufferSize:
  269. for act_bf in self.act_jack_bf_list:
  270. act_bf.setEnabled(True)
  271. if act_bf.text().replace("&", "") == str(bufferSize):
  272. if not act_bf.isChecked():
  273. act_bf.setChecked(True)
  274. else:
  275. if act_bf.isChecked():
  276. act_bf.setChecked(False)
  277. #else:
  278. #for act_bf in self.act_jack_bf_list:
  279. #act_bf.setEnabled(False)
  280. #if act_bf.isChecked():
  281. #act_bf.setChecked(False)
  282. def setSampleRate(self, sampleRate, future=False):
  283. if self.m_sampleRate == sampleRate:
  284. return
  285. if future:
  286. #if self.sender() == self.cb_sample_rate: # Changed using GUI
  287. ask = QMessageBox.question(self, self.tr("Change Sample Rate"),
  288. self.tr("It's not possible to change Sample Rate while JACK is running.\n"
  289. "Do you want to change as soon as JACK stops?"), QMessageBox.Ok | QMessageBox.Cancel)
  290. if ask == QMessageBox.Ok:
  291. self.m_nextSampleRate = sampleRate
  292. else:
  293. self.m_nextSampleRate = 0
  294. # not future
  295. else:
  296. self.m_sampleRate = sampleRate
  297. self.m_nextSampleRate = 0
  298. for i in range(len(SAMPLE_RATE_LIST)):
  299. sampleRate = SAMPLE_RATE_LIST[i]
  300. #sampleRateStr = str(sampleRate)
  301. #self.cb_sample_rate.setItemText(i, sampleRateStr)
  302. if self.m_sampleRate == sampleRate:
  303. self.cb_sample_rate.setCurrentIndex(i)
  304. def setRealTime(self, realtime):
  305. self.label_realtime.setText(" RT " if realtime else " <s>RT</s> ")
  306. self.label_realtime.setEnabled(realtime)
  307. def setDSPLoad(self, dsp_load):
  308. self.pb_dsp_load.setValue(dsp_load)
  309. def setXruns(self, xruns):
  310. txt1 = str(xruns) if (xruns >= 0) else "--"
  311. txt2 = "" if (xruns == 1) else "s"
  312. self.b_xruns.setText("%s Xrun%s" % (txt1, txt2))
  313. # -----------------------------------------------------------------
  314. # External Dialogs
  315. @pyqtSlot()
  316. def slot_showJackSettings(self):
  317. jacksettingsW = jacksettings.JackSettingsW(self)
  318. jacksettingsW.exec_()
  319. del jacksettingsW
  320. # Force update of gui widgets
  321. if not jack.client:
  322. self.jackStopped()
  323. @pyqtSlot()
  324. def slot_showLogs(self):
  325. if self.m_logsW is None:
  326. self.m_logsW = logs.LogsW(self)
  327. self.m_logsW.show()
  328. @pyqtSlot()
  329. def slot_showRender(self):
  330. renderW = render.RenderW(self)
  331. renderW.exec_()
  332. del renderW
  333. # -----------------------------------------------------------------
  334. # Shared Canvas code
  335. @pyqtSlot()
  336. def slot_canvasArrange(self):
  337. patchcanvas.arrange()
  338. @pyqtSlot()
  339. def slot_canvasRefresh(self):
  340. patchcanvas.clear()
  341. self.init_ports()
  342. @pyqtSlot()
  343. def slot_canvasZoomFit(self):
  344. self.scene.zoom_fit()
  345. @pyqtSlot()
  346. def slot_canvasZoomIn(self):
  347. self.scene.zoom_in()
  348. @pyqtSlot()
  349. def slot_canvasZoomOut(self):
  350. self.scene.zoom_out()
  351. @pyqtSlot()
  352. def slot_canvasZoomReset(self):
  353. self.scene.zoom_reset()
  354. @pyqtSlot()
  355. def slot_canvasPrint(self):
  356. self.scene.clearSelection()
  357. self.m_exportPrinter = QPrinter()
  358. dialog = QPrintDialog(self.m_exportPrinter, self)
  359. if dialog.exec_():
  360. painter = QPainter(self.m_exportPrinter)
  361. painter.setRenderHint(QPainter.Antialiasing)
  362. painter.setRenderHint(QPainter.TextAntialiasing)
  363. self.scene.render(painter)
  364. @pyqtSlot()
  365. def slot_canvasSaveImage(self):
  366. newPath = QFileDialog.getSaveFileName(self, self.tr("Save Image"), filter=self.tr("PNG Image (*.png);;JPEG Image (*.jpg)"))
  367. if newPath:
  368. self.scene.clearSelection()
  369. if newPath.endswith((".jpg", ".jpG", ".jPG", ".JPG", ".JPg", ".Jpg")):
  370. imgFormat = "JPG"
  371. elif newPath.endswith((".png", ".pnG", ".pNG", ".PNG", ".PNg", ".Png")):
  372. imgFormat = "PNG"
  373. else:
  374. # File-dialog may not auto-add the extension
  375. imgFormat = "PNG"
  376. newPath += ".png"
  377. self.m_exportImage = QImage(self.scene.sceneRect().width(), self.scene.sceneRect().height(), QImage.Format_RGB32)
  378. painter = QPainter(self.m_exportImage)
  379. painter.setRenderHint(QPainter.Antialiasing) # TODO - set true, cleanup this
  380. painter.setRenderHint(QPainter.TextAntialiasing)
  381. self.scene.render(painter)
  382. self.m_exportImage.save(newPath, imgFormat, 100)
  383. # -----------------------------------------------------------------
  384. # Shared Connections
  385. def setCanvasConnections(self):
  386. self.act_canvas_arrange.setEnabled(False) # TODO, later
  387. self.connect(self.act_canvas_arrange, SIGNAL("triggered()"), SLOT("slot_canvasArrange()"))
  388. self.connect(self.act_canvas_refresh, SIGNAL("triggered()"), SLOT("slot_canvasRefresh()"))
  389. self.connect(self.act_canvas_zoom_fit, SIGNAL("triggered()"), SLOT("slot_canvasZoomFit()"))
  390. self.connect(self.act_canvas_zoom_in, SIGNAL("triggered()"), SLOT("slot_canvasZoomIn()"))
  391. self.connect(self.act_canvas_zoom_out, SIGNAL("triggered()"), SLOT("slot_canvasZoomOut()"))
  392. self.connect(self.act_canvas_zoom_100, SIGNAL("triggered()"), SLOT("slot_canvasZoomReset()"))
  393. self.connect(self.act_canvas_print, SIGNAL("triggered()"), SLOT("slot_canvasPrint()"))
  394. self.connect(self.act_canvas_save_image, SIGNAL("triggered()"), SLOT("slot_canvasSaveImage()"))
  395. self.connect(self.b_canvas_zoom_fit, SIGNAL("clicked()"), SLOT("slot_canvasZoomFit()"))
  396. self.connect(self.b_canvas_zoom_in, SIGNAL("clicked()"), SLOT("slot_canvasZoomIn()"))
  397. self.connect(self.b_canvas_zoom_out, SIGNAL("clicked()"), SLOT("slot_canvasZoomOut()"))
  398. self.connect(self.b_canvas_zoom_100, SIGNAL("clicked()"), SLOT("slot_canvasZoomReset()"))
  399. def setJackConnections(self, modes):
  400. if "jack" in modes:
  401. self.connect(self.act_jack_clear_xruns, SIGNAL("triggered()"), SLOT("slot_JackClearXruns()"))
  402. self.connect(self.act_jack_render, SIGNAL("triggered()"), SLOT("slot_showRender()"))
  403. self.connect(self.act_jack_configure, SIGNAL("triggered()"), SLOT("slot_showJackSettings()"))
  404. self.connect(self.b_jack_clear_xruns, SIGNAL("clicked()"), SLOT("slot_JackClearXruns()"))
  405. self.connect(self.b_jack_configure, SIGNAL("clicked()"), SLOT("slot_showJackSettings()"))
  406. self.connect(self.b_jack_render, SIGNAL("clicked()"), SLOT("slot_showRender()"))
  407. self.connect(self.cb_buffer_size, SIGNAL("currentIndexChanged(QString)"), SLOT("slot_jackBufferSize_ComboBox(QString)"))
  408. self.connect(self.cb_sample_rate, SIGNAL("currentIndexChanged(QString)"), SLOT("slot_jackSampleRate_ComboBox(QString)"))
  409. self.connect(self.b_xruns, SIGNAL("clicked()"), SLOT("slot_JackClearXruns()"))
  410. if "buffer-size" in modes:
  411. self.connect(self.act_jack_bf_16, SIGNAL("triggered(bool)"), SLOT("slot_jackBufferSize_Menu()"))
  412. self.connect(self.act_jack_bf_32, SIGNAL("triggered(bool)"), SLOT("slot_jackBufferSize_Menu()"))
  413. self.connect(self.act_jack_bf_64, SIGNAL("triggered(bool)"), SLOT("slot_jackBufferSize_Menu()"))
  414. self.connect(self.act_jack_bf_128, SIGNAL("triggered(bool)"), SLOT("slot_jackBufferSize_Menu()"))
  415. self.connect(self.act_jack_bf_256, SIGNAL("triggered(bool)"), SLOT("slot_jackBufferSize_Menu()"))
  416. self.connect(self.act_jack_bf_512, SIGNAL("triggered(bool)"), SLOT("slot_jackBufferSize_Menu()"))
  417. self.connect(self.act_jack_bf_1024, SIGNAL("triggered(bool)"), SLOT("slot_jackBufferSize_Menu()"))
  418. self.connect(self.act_jack_bf_2048, SIGNAL("triggered(bool)"), SLOT("slot_jackBufferSize_Menu()"))
  419. self.connect(self.act_jack_bf_4096, SIGNAL("triggered(bool)"), SLOT("slot_jackBufferSize_Menu()"))
  420. self.connect(self.act_jack_bf_8192, SIGNAL("triggered(bool)"), SLOT("slot_jackBufferSize_Menu()"))
  421. if "transport" in modes:
  422. self.connect(self.act_transport_play, SIGNAL("triggered(bool)"), SLOT("slot_transportPlayPause(bool)"))
  423. self.connect(self.act_transport_stop, SIGNAL("triggered()"), SLOT("slot_transportStop()"))
  424. self.connect(self.act_transport_backwards, SIGNAL("triggered()"), SLOT("slot_transportBackwards()"))
  425. self.connect(self.act_transport_forwards, SIGNAL("triggered()"), SLOT("slot_transportForwards()"))
  426. self.connect(self.b_transport_play, SIGNAL("clicked(bool)"), SLOT("slot_transportPlayPause(bool)"))
  427. self.connect(self.b_transport_stop, SIGNAL("clicked()"), SLOT("slot_transportStop()"))
  428. self.connect(self.b_transport_backwards, SIGNAL("clicked()"), SLOT("slot_transportBackwards()"))
  429. self.connect(self.b_transport_forwards, SIGNAL("clicked()"), SLOT("slot_transportForwards()"))
  430. self.connect(self.label_time, SIGNAL("customContextMenuRequested(QPoint)"), SLOT("slot_transportViewMenu()"))
  431. if "misc" in modes:
  432. if LINUX:
  433. self.connect(self.act_show_logs, SIGNAL("triggered()"), SLOT("slot_showLogs()"))
  434. else:
  435. self.act_show_logs.setEnabled(False)