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.

413 lines
15KB

  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # Common/Shared code related to JACK
  4. # Copyright (C) 2012 Filipe Coelho <falktx@gmail.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. # Imports (Global)
  18. from PyQt4.QtCore import pyqtSlot, QTimer
  19. from PyQt4.QtGui import QCursor, QFontMetrics, QMenu
  20. # Imports (Custom Stuff)
  21. import jacksettings, logs, render
  22. from shared import *
  23. from jacklib_helpers import *
  24. # Have JACK2 ?
  25. try:
  26. JACK2 = True
  27. version_str = str(jacklib.get_version_string(), encoding="ascii")
  28. print("Using JACK2, version %s" % (version_str))
  29. except:
  30. JACK2 = False
  31. # Can Render ?
  32. for iPATH in PATH:
  33. if (os.path.exists(os.path.join(iPATH, "jack_capture"))):
  34. canRender = True
  35. break
  36. else:
  37. canRender = False
  38. # Variables
  39. TRANSPORT_VIEW_HMS = 0
  40. TRANSPORT_VIEW_BBT = 1
  41. TRANSPORT_VIEW_FRAMES = 2
  42. buffer_sizes = (16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192)
  43. sample_rates = (22050, 32000, 44100, 48000, 88200, 96000, 192000)
  44. # DBus object
  45. class DBusObject(object):
  46. __slots__ = [
  47. 'loop',
  48. 'bus',
  49. 'a2j',
  50. 'jack',
  51. 'ladish_control',
  52. 'ladish_studio',
  53. 'ladish_room',
  54. 'ladish_graph',
  55. 'ladish_app_iface',
  56. 'ladish_app_daemon',
  57. 'patchbay'
  58. ]
  59. DBus = DBusObject()
  60. # Jack object
  61. class JackObject(object):
  62. __slots__ = [
  63. 'client'
  64. ]
  65. jack = JackObject()
  66. # Init objects
  67. DBus.loop = None
  68. DBus.bus = None
  69. DBus.a2j = None
  70. DBus.jack = None
  71. DBus.ladish_control = None
  72. DBus.ladish_studio = None
  73. DBus.ladish_room = None
  74. DBus.ladish_graph = None
  75. DBus.ladish_app_iface = None
  76. DBus.ladish_app_daemon = None
  77. DBus.patchbay = None
  78. jack.client = None
  79. # -------------------------------------------------------------
  80. # Property change calls
  81. def jack_buffer_size(self, buffer_size):
  82. if (self.m_buffer_size != buffer_size):
  83. if (jack.client):
  84. failed = bool(jacklib.set_buffer_size(jack.client, buffer_size) != 0)
  85. else:
  86. failed = bool(jacksettings.setBufferSize(buffer_size))
  87. if (failed):
  88. print("Failed to change buffer-size as %i, reset to %i" % (buffer_size, self.m_buffer_size))
  89. setBufferSize(self, self.m_buffer_size, True)
  90. def jack_sample_rate(self, sample_rate):
  91. if (jack.client):
  92. setSampleRate(self, sample_rate, True)
  93. else:
  94. if (jacksettings.setSampleRate(sample_rate)):
  95. setSampleRate(self, sample_rate)
  96. @pyqtSlot(str)
  97. def slot_jackBufferSize_ComboBox(self, text):
  98. if (not text or text.isdigit() == False):
  99. return
  100. jack_buffer_size(self, int(text))
  101. @pyqtSlot(int)
  102. def slot_jackBufferSize_Menu(self, buffer_size):
  103. jack_buffer_size(self, buffer_size)
  104. @pyqtSlot(str)
  105. def slot_jackSampleRate_ComboBox(self, text):
  106. if (not text or text.isdigit() == False):
  107. return
  108. jack_sample_rate(self, int(text))
  109. # -------------------------------------------------------------
  110. # Transport calls
  111. def setTransportView(self, view):
  112. if (view == TRANSPORT_VIEW_HMS):
  113. self.m_selected_transport_view = TRANSPORT_VIEW_HMS
  114. self.label_time.setMinimumWidth(QFontMetrics(self.label_time.font()).width("00:00:00")+3)
  115. elif (view == TRANSPORT_VIEW_BBT):
  116. self.m_selected_transport_view = TRANSPORT_VIEW_BBT
  117. self.label_time.setMinimumWidth(QFontMetrics(self.label_time.font()).width("000|00|0000")+3)
  118. elif (view == TRANSPORT_VIEW_FRAMES):
  119. self.m_selected_transport_view = TRANSPORT_VIEW_FRAMES
  120. self.label_time.setMinimumWidth(QFontMetrics(self.label_time.font()).width("000'000'000")+3)
  121. else:
  122. self.m_selected_transport_view = None
  123. @pyqtSlot(bool)
  124. def slot_transportPlayPause(self, play):
  125. if (not jack.client): return
  126. if (play):
  127. jacklib.transport_start(jack.client)
  128. else:
  129. jacklib.transport_stop(jack.client)
  130. refreshTransport(self)
  131. @pyqtSlot()
  132. def slot_transportStop(self):
  133. if (not jack.client): return
  134. jacklib.transport_stop(jack.client)
  135. jacklib.transport_locate(jack.client, 0)
  136. refreshTransport(self)
  137. @pyqtSlot()
  138. def slot_transportBackwards(self):
  139. if (not jack.client): return
  140. new_frame = jacklib.get_current_transport_frame(jack.client)-100000
  141. if (new_frame < 0): new_frame = 0
  142. jacklib.transport_locate(jack.client, new_frame)
  143. @pyqtSlot()
  144. def slot_transportForwards(self):
  145. if (not jack.client): return
  146. new_frame = jacklib.get_current_transport_frame(jack.client)+100000
  147. jacklib.transport_locate(jack.client, new_frame)
  148. @pyqtSlot()
  149. def slot_transportViewMenu(self):
  150. menu = QMenu(self)
  151. act_t_hms = menu.addAction("Hours:Minutes:Seconds")
  152. act_t_bbt = menu.addAction("Beat:Bar:Tick")
  153. act_t_fr = menu.addAction("Frames")
  154. act_t_hms.setCheckable(True)
  155. act_t_bbt.setCheckable(True)
  156. act_t_fr.setCheckable(True)
  157. if (self.m_selected_transport_view == TRANSPORT_VIEW_HMS):
  158. act_t_hms.setChecked(True)
  159. elif (self.m_selected_transport_view == TRANSPORT_VIEW_BBT):
  160. act_t_bbt.setChecked(True)
  161. elif (self.m_selected_transport_view == TRANSPORT_VIEW_FRAMES):
  162. act_t_fr.setChecked(True)
  163. act_selected = menu.exec_(QCursor().pos())
  164. if (act_selected == act_t_hms):
  165. setTransportView(self, TRANSPORT_VIEW_HMS)
  166. elif (act_selected == act_t_bbt):
  167. setTransportView(self, TRANSPORT_VIEW_BBT)
  168. elif (act_selected == act_t_fr):
  169. setTransportView(self, TRANSPORT_VIEW_FRAMES)
  170. # -------------------------------------------------------------
  171. # Refresh GUI stuff
  172. def refreshDSPLoad(self):
  173. if (not jack.client): return
  174. setDSPLoad(self, int(jacklib.cpu_load(jack.client)))
  175. def refreshTransport(self):
  176. if (not jack.client): return
  177. pos = jacklib.jack_position_t()
  178. pos.valid = 0
  179. state = jacklib.transport_query(jack.client, jacklib.pointer(pos))
  180. if (self.m_selected_transport_view == TRANSPORT_VIEW_HMS):
  181. frame = pos.frame
  182. time = frame / self.m_sample_rate
  183. secs = time % 60
  184. mins = (time / 60) % 60
  185. hrs = (time / 3600) % 60
  186. self.label_time.setText("%02i:%02i:%02i" % (hrs, mins, secs))
  187. elif (self.m_selected_transport_view == TRANSPORT_VIEW_BBT):
  188. if (pos.valid & jacklib.JackPositionBBT):
  189. bar = pos.bar
  190. beat = pos.beat
  191. tick = pos.tick
  192. if (bar == 0):
  193. beat = 0
  194. tick = 0
  195. self.label_time.setText("%03i|%02i|%04i" % (bar, beat, tick))
  196. else:
  197. self.label_time.setText("000|00|0000")
  198. elif (self.m_selected_transport_view == TRANSPORT_VIEW_FRAMES):
  199. frame = pos.frame
  200. frame1 = pos.frame % 1000
  201. frame2 = (pos.frame / 1000) % 1000
  202. frame3 = (pos.frame / 1000000) % 1000
  203. self.label_time.setText("%03i'%03i'%03i" % (frame3, frame2, frame1))
  204. if (pos.valid & jacklib.JackPositionBBT):
  205. if (self.m_last_bpm != pos.beats_per_minute):
  206. self.sb_bpm.setValue(pos.beats_per_minute)
  207. self.sb_bpm.setStyleSheet("")
  208. else:
  209. pos.beats_per_minute = -1
  210. if (self.m_last_bpm != pos.beats_per_minute):
  211. self.sb_bpm.setStyleSheet("QDoubleSpinBox { color: palette(mid); }")
  212. self.m_last_bpm = pos.beats_per_minute
  213. if (state != self.m_last_transport_state):
  214. if (state == jacklib.JackTransportStopped):
  215. icon = getIcon("media-playback-start")
  216. self.act_transport_play.setChecked(False)
  217. self.act_transport_play.setIcon(icon)
  218. self.act_transport_play.setText(self.tr("&Play"))
  219. self.b_transport_play.setChecked(False)
  220. self.b_transport_play.setIcon(icon)
  221. else:
  222. icon = getIcon("media-playback-pause")
  223. self.act_transport_play.setChecked(True)
  224. self.act_transport_play.setIcon(icon)
  225. self.act_transport_play.setText(self.tr("&Pause"))
  226. self.b_transport_play.setChecked(True)
  227. self.b_transport_play.setIcon(icon)
  228. self.m_last_transport_state = state
  229. # -------------------------------------------------------------
  230. # Set GUI stuff
  231. def setBufferSize(self, buffer_size, forced=False):
  232. if (self.m_buffer_size == buffer_size and not forced):
  233. return
  234. self.m_buffer_size = buffer_size
  235. if (buffer_size):
  236. if (buffer_size == 16):
  237. self.cb_buffer_size.setCurrentIndex(0)
  238. elif (buffer_size == 32):
  239. self.cb_buffer_size.setCurrentIndex(1)
  240. elif (buffer_size == 64):
  241. self.cb_buffer_size.setCurrentIndex(2)
  242. elif (buffer_size == 128):
  243. self.cb_buffer_size.setCurrentIndex(3)
  244. elif (buffer_size == 256):
  245. self.cb_buffer_size.setCurrentIndex(4)
  246. elif (buffer_size == 512):
  247. self.cb_buffer_size.setCurrentIndex(5)
  248. elif (buffer_size == 1024):
  249. self.cb_buffer_size.setCurrentIndex(6)
  250. elif (buffer_size == 2048):
  251. self.cb_buffer_size.setCurrentIndex(7)
  252. elif (buffer_size == 4096):
  253. self.cb_buffer_size.setCurrentIndex(8)
  254. elif (buffer_size == 8192):
  255. self.cb_buffer_size.setCurrentIndex(9)
  256. else:
  257. QMessageBox.warning(self, self.tr("Warning"), self.tr("Invalid JACK buffer-size requested"))
  258. #if ("act_jack_bf_list" in dir(self)):
  259. if (buffer_size):
  260. for act_bf in self.act_jack_bf_list:
  261. act_bf.setEnabled(True)
  262. if (act_bf.text().replace("&","") == str(buffer_size)):
  263. if (act_bf.isChecked() == False):
  264. act_bf.setChecked(True)
  265. else:
  266. if (act_bf.isChecked()):
  267. act_bf.setChecked(False)
  268. #else:
  269. #for i in range(len(self.act_jack_bf_list)):
  270. #self.act_jack_bf_list[i].setEnabled(False)
  271. #if (self.act_jack_bf_list[i].isChecked()):
  272. #self.act_jack_bf_list[i].setChecked(False)
  273. def setSampleRate(self, sample_rate, future=False):
  274. if (self.m_sample_rate == sample_rate):
  275. return
  276. if (future):
  277. pass
  278. if (self.sender() == self.cb_sample_rate): # Changed using GUI
  279. ask = QMessageBox.question(self, self.tr("Change Sample Rate"), self.tr("It's not possible to change Sample Rate while JACK is running.\n"
  280. "Do you want to change as soon as JACK stops?"), QMessageBox.Ok|QMessageBox.Cancel)
  281. if (ask == QMessageBox.Ok):
  282. self.m_next_sample_rate = sample_rate
  283. else:
  284. self.m_next_sample_rate = 0
  285. # not future
  286. else:
  287. self.m_sample_rate = sample_rate
  288. self.m_next_sample_rate = 0
  289. for i in range(len(sample_rates)):
  290. sample_rate = sample_rates[i]
  291. sample_rate_str = str(sample_rate)
  292. self.cb_sample_rate.setItemText(i, sample_rate_str)
  293. if (self.m_sample_rate == sample_rate):
  294. self.cb_sample_rate.setCurrentIndex(i)
  295. def setRealTime(self, realtime):
  296. self.label_realtime.setText(" RT " if realtime else " <s>RT</s> ")
  297. self.label_realtime.setEnabled(realtime)
  298. def setDSPLoad(self, dsp_load):
  299. self.pb_dsp_load.setValue(dsp_load)
  300. def setXruns(self, xruns):
  301. self.b_xruns.setText("%s Xrun%s" % (str(xruns) if (xruns >= 0) else "--", "" if (xruns == 1) else "s"))
  302. # -------------------------------------------------------------
  303. # External Dialogs
  304. @pyqtSlot()
  305. def slot_showJackSettings(self):
  306. jacksettings.JackSettingsW(self).exec_()
  307. @pyqtSlot()
  308. def slot_showLogs(self):
  309. logs.LogsW(self).show()
  310. @pyqtSlot()
  311. def slot_showRender(self):
  312. render.RenderW(self).exec_()
  313. # -------------------------------------------------------------
  314. # Shared Connections
  315. def setJackConnections(self, modes):
  316. pass
  317. if ("jack" in modes):
  318. self.connect(self.act_jack_clear_xruns, SIGNAL("triggered()"), SLOT("slot_JackClearXruns()"))
  319. self.connect(self.act_jack_render, SIGNAL("triggered()"), lambda: slot_showRender(self))
  320. self.connect(self.act_jack_configure, SIGNAL("triggered()"), lambda: slot_showJackSettings(self))
  321. self.connect(self.b_jack_clear_xruns, SIGNAL("clicked()"), SLOT("slot_JackClearXruns()"))
  322. self.connect(self.b_jack_configure, SIGNAL("clicked()"), lambda: slot_showJackSettings(self))
  323. self.connect(self.b_jack_render, SIGNAL("clicked()"), lambda: slot_showRender(self))
  324. self.connect(self.cb_buffer_size, SIGNAL("currentIndexChanged(QString)"), lambda: slot_jackBufferSize_ComboBox(self, self.cb_buffer_size.currentText()))
  325. self.connect(self.cb_sample_rate, SIGNAL("currentIndexChanged(QString)"), lambda: slot_jackSampleRate_ComboBox(self, self.cb_sample_rate.currentText()))
  326. self.connect(self.b_xruns, SIGNAL("clicked()"), SLOT("slot_JackClearXruns()"))
  327. if ("buffer-size" in modes):
  328. self.connect(self.act_jack_bf_16, SIGNAL("triggered(bool)"), lambda: slot_jackBufferSize_Menu(self, 16))
  329. self.connect(self.act_jack_bf_32, SIGNAL("triggered(bool)"), lambda: slot_jackBufferSize_Menu(self, 32))
  330. self.connect(self.act_jack_bf_64, SIGNAL("triggered(bool)"), lambda: slot_jackBufferSize_Menu(self, 64))
  331. self.connect(self.act_jack_bf_128, SIGNAL("triggered(bool)"), lambda: slot_jackBufferSize_Menu(self, 128))
  332. self.connect(self.act_jack_bf_256, SIGNAL("triggered(bool)"), lambda: slot_jackBufferSize_Menu(self, 256))
  333. self.connect(self.act_jack_bf_512, SIGNAL("triggered(bool)"), lambda: slot_jackBufferSize_Menu(self, 512))
  334. self.connect(self.act_jack_bf_1024, SIGNAL("triggered(bool)"), lambda: slot_jackBufferSize_Menu(self, 1024))
  335. self.connect(self.act_jack_bf_2048, SIGNAL("triggered(bool)"), lambda: slot_jackBufferSize_Menu(self, 2048))
  336. self.connect(self.act_jack_bf_4096, SIGNAL("triggered(bool)"), lambda: slot_jackBufferSize_Menu(self, 4096))
  337. self.connect(self.act_jack_bf_8192, SIGNAL("triggered(bool)"), lambda: slot_jackBufferSize_Menu(self, 8192))
  338. if ("transport" in modes):
  339. self.connect(self.act_transport_play, SIGNAL("triggered(bool)"), lambda: slot_transportPlayPause(self, self.act_transport_play.isChecked()))
  340. self.connect(self.act_transport_stop, SIGNAL("triggered()"), lambda: slot_transportStop(self))
  341. self.connect(self.act_transport_backwards, SIGNAL("triggered()"), lambda: slot_transportBackwards(self))
  342. self.connect(self.act_transport_forwards, SIGNAL("triggered()"), lambda: slot_transportForwards(self))
  343. self.connect(self.b_transport_play, SIGNAL("clicked(bool)"), lambda: slot_transportPlayPause(self, self.b_transport_play.isChecked()))
  344. self.connect(self.b_transport_stop, SIGNAL("clicked()"), lambda: slot_transportStop(self))
  345. self.connect(self.b_transport_backwards, SIGNAL("clicked()"), lambda: slot_transportBackwards(self))
  346. self.connect(self.b_transport_forwards, SIGNAL("clicked()"), lambda: slot_transportForwards(self))
  347. self.connect(self.label_time, SIGNAL("customContextMenuRequested(QPoint)"), lambda: slot_transportViewMenu(self))
  348. if ("misc" in modes):
  349. if (LINUX):
  350. self.connect(self.act_show_logs, SIGNAL("triggered()"), lambda: slot_showLogs(self))
  351. else:
  352. self.act_show_logs.setEnabled(False)