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.

468 lines
16KB

  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # JACK-Capture frontend, with freewheel and transport support
  4. # Copyright (C) 2010-2013 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, QProcess, QTime, QTimer, QSettings
  20. from PyQt4.QtGui import QDialog
  21. from time import sleep
  22. # ------------------------------------------------------------------------------------------------------------
  23. # Imports (Custom Stuff)
  24. import ui_render
  25. from shared import *
  26. from jacklib_helpers import *
  27. # ------------------------------------------------------------------------------------------------------------
  28. # Global variables
  29. global gJackCapturePath, gJackClient
  30. gJackCapturePath = ""
  31. gJackClient = None
  32. # ------------------------------------------------------------------------------------------------------------
  33. # Find 'jack_capture'
  34. # Check for cxfreeze
  35. if sys.path[0].rsplit(os.sep, 1)[-1] in ("catia", "claudia", "cadence-render", "render"):
  36. name = sys.path[0].rsplit(os.sep, 1)[-1]
  37. cwd = sys.path[0].rsplit(name, 1)[0]
  38. if os.path.exists(os.path.join(cwd, "jack_capture")):
  39. gJackCapturePath = os.path.join(cwd, "jack_capture")
  40. del cwd, name
  41. # Check in PATH
  42. if not gJackCapturePath:
  43. for iPATH in PATH:
  44. if os.path.exists(os.path.join(iPATH, "jack_capture")):
  45. gJackCapturePath = os.path.join(iPATH, "jack_capture")
  46. break
  47. def canRender():
  48. global gJackCapturePath
  49. return bool(gJackCapturePath)
  50. # ------------------------------------------------------------------------------------------------------------
  51. # Render Window
  52. class RenderW(QDialog):
  53. def __init__(self, parent):
  54. QDialog.__init__(self, parent)
  55. self.ui = ui_render.Ui_RenderW()
  56. self.ui.setupUi(self)
  57. self.fFreewheel = False
  58. self.fLastTime = -1
  59. self.fMaxTime = 180
  60. self.fTimer = QTimer(self)
  61. self.fProcess = QProcess(self)
  62. # -------------------------------------------------------------
  63. # Get JACK client and base information
  64. global gJackClient
  65. if gJackClient:
  66. self.fJackClient = gJackClient
  67. else:
  68. self.fJackClient = jacklib.client_open("Render-Dialog", jacklib.JackNoStartServer, None)
  69. self.fBufferSize = int(jacklib.get_buffer_size(self.fJackClient))
  70. self.fSampleRate = int(jacklib.get_sample_rate(self.fJackClient))
  71. for i in range(self.ui.cb_buffer_size.count()):
  72. if int(self.ui.cb_buffer_size.itemText(i)) == self.fBufferSize:
  73. self.ui.cb_buffer_size.setCurrentIndex(i)
  74. break
  75. else:
  76. self.ui.cb_buffer_size.addItem(str(self.fBufferSize))
  77. self.ui.cb_buffer_size.setCurrentIndex(self.ui.cb_buffer_size.count() - 1)
  78. # -------------------------------------------------------------
  79. # Set-up GUI stuff
  80. # Get List of formats
  81. self.fProcess.start(gJackCapturePath, ["-pf"])
  82. self.fProcess.waitForFinished()
  83. formats = str(self.fProcess.readAllStandardOutput(), encoding="utf-8").split(" ")
  84. formatsList = []
  85. for i in range(len(formats) - 1):
  86. iFormat = formats[i].strip()
  87. if iFormat:
  88. formatsList.append(iFormat)
  89. formatsList.sort()
  90. # Put all formats in combo-box, select 'wav' option
  91. for i in range(len(formatsList)):
  92. self.ui.cb_format.addItem(formatsList[i])
  93. if formatsList[i] == "wav":
  94. self.ui.cb_format.setCurrentIndex(i)
  95. self.ui.cb_depth.setCurrentIndex(4) #Float
  96. self.ui.rb_stereo.setChecked(True)
  97. self.ui.te_end.setTime(QTime(0, 3, 0))
  98. self.ui.progressBar.setFormat("")
  99. self.ui.progressBar.setMinimum(0)
  100. self.ui.progressBar.setMaximum(1)
  101. self.ui.progressBar.setValue(0)
  102. self.ui.b_render.setIcon(getIcon("media-record"))
  103. self.ui.b_stop.setIcon(getIcon("media-playback-stop"))
  104. self.ui.b_close.setIcon(getIcon("window-close"))
  105. self.ui.b_open.setIcon(getIcon("document-open"))
  106. self.ui.b_stop.setVisible(False)
  107. self.ui.le_folder.setText(HOME)
  108. # -------------------------------------------------------------
  109. # Set-up connections
  110. self.connect(self.ui.b_render, SIGNAL("clicked()"), SLOT("slot_renderStart()"))
  111. self.connect(self.ui.b_stop, SIGNAL("clicked()"), SLOT("slot_renderStop()"))
  112. self.connect(self.ui.b_open, SIGNAL("clicked()"), SLOT("slot_getAndSetPath()"))
  113. self.connect(self.ui.b_now_start, SIGNAL("clicked()"), SLOT("slot_setStartNow()"))
  114. self.connect(self.ui.b_now_end, SIGNAL("clicked()"), SLOT("slot_setEndNow()"))
  115. self.connect(self.ui.te_start, SIGNAL("timeChanged(const QTime)"), SLOT("slot_updateStartTime(const QTime)"))
  116. self.connect(self.ui.te_end, SIGNAL("timeChanged(const QTime)"), SLOT("slot_updateEndTime(const QTime)"))
  117. self.connect(self.ui.group_time, SIGNAL("clicked(bool)"), SLOT("slot_transportChecked(bool)"))
  118. self.connect(self.fTimer, SIGNAL("timeout()"), SLOT("slot_updateProgressbar()"))
  119. # -------------------------------------------------------------
  120. self.loadSettings()
  121. @pyqtSlot()
  122. def slot_renderStart(self):
  123. if not os.path.exists(self.ui.le_folder.text()):
  124. QMessageBox.warning(self, self.tr("Warning"), self.tr("The selected directory does not exist. Please choose a valid one."))
  125. return
  126. timeStart = self.ui.te_start.time()
  127. timeEnd = self.ui.te_end.time()
  128. minTime = (timeStart.hour() * 3600) + (timeStart.minute() * 60) + (timeStart.second())
  129. maxTime = (timeEnd.hour() * 3600) + (timeEnd.minute() * 60) + (timeEnd.second())
  130. newBufferSize = int(self.ui.cb_buffer_size.currentText())
  131. useTransport = self.ui.group_time.isChecked()
  132. self.fFreewheel = bool(self.ui.cb_render_mode.currentIndex() == 1)
  133. self.fLastTime = -1
  134. self.fMaxTime = maxTime
  135. if self.fFreewheel:
  136. self.fTimer.setInterval(100)
  137. else:
  138. self.fTimer.setInterval(500)
  139. self.ui.group_render.setEnabled(False)
  140. self.ui.group_time.setEnabled(False)
  141. self.ui.group_encoding.setEnabled(False)
  142. self.ui.b_render.setVisible(False)
  143. self.ui.b_stop.setVisible(True)
  144. self.ui.b_close.setEnabled(False)
  145. if useTransport:
  146. self.ui.progressBar.setFormat("%p%")
  147. self.ui.progressBar.setMinimum(minTime)
  148. self.ui.progressBar.setMaximum(maxTime)
  149. self.ui.progressBar.setValue(minTime)
  150. else:
  151. self.ui.progressBar.setFormat("")
  152. self.ui.progressBar.setMinimum(0)
  153. self.ui.progressBar.setMaximum(0)
  154. self.ui.progressBar.setValue(0)
  155. self.ui.progressBar.update()
  156. arguments = []
  157. # Filename prefix
  158. arguments.append("-fp")
  159. arguments.append(self.ui.le_prefix.text())
  160. # Format
  161. arguments.append("-f")
  162. arguments.append(self.ui.cb_format.currentText())
  163. # Bit depth
  164. arguments.append("-b")
  165. arguments.append(self.ui.cb_depth.currentText())
  166. # Channels
  167. arguments.append("-c")
  168. if self.ui.rb_mono.isChecked():
  169. arguments.append("1")
  170. elif self.ui.rb_stereo.isChecked():
  171. arguments.append("2")
  172. else:
  173. arguments.append(str(self.ui.sb_channels.value()))
  174. # Controlled by transport
  175. if useTransport:
  176. arguments.append("-jt")
  177. # Controlled by freewheel
  178. if self.fFreewheel:
  179. arguments.append("-jf")
  180. # Silent mode
  181. arguments.append("-dc")
  182. arguments.append("-s")
  183. # Change current directory
  184. os.chdir(self.ui.le_folder.text())
  185. if newBufferSize != int(jacklib.get_buffer_size(self.fJackClient)):
  186. print("NOTICE: buffer size changed before render")
  187. jacklib.set_buffer_size(self.fJackClient, newBufferSize)
  188. if useTransport:
  189. if jacklib.transport_query(self.fJackClient, None) > jacklib.JackTransportStopped: # rolling or starting
  190. jacklib.transport_stop(self.fJackClient)
  191. jacklib.transport_locate(self.fJackClient, minTime * self.fSampleRate)
  192. self.fProcess.start(gJackCapturePath, arguments)
  193. self.fProcess.waitForStarted()
  194. if self.fFreewheel:
  195. print("NOTICE: rendering in freewheel mode")
  196. sleep(1)
  197. jacklib.set_freewheel(self.fJackClient, 1)
  198. if useTransport:
  199. self.fTimer.start()
  200. jacklib.transport_start(self.fJackClient)
  201. @pyqtSlot()
  202. def slot_renderStop(self):
  203. useTransport = self.ui.group_time.isChecked()
  204. if useTransport:
  205. jacklib.transport_stop(self.fJackClient)
  206. if self.fFreewheel:
  207. jacklib.set_freewheel(self.fJackClient, 0)
  208. sleep(1)
  209. self.fProcess.close()
  210. if useTransport:
  211. self.fTimer.stop()
  212. self.ui.group_render.setEnabled(True)
  213. self.ui.group_time.setEnabled(True)
  214. self.ui.group_encoding.setEnabled(True)
  215. self.ui.b_render.setVisible(True)
  216. self.ui.b_stop.setVisible(False)
  217. self.ui.b_close.setEnabled(True)
  218. self.ui.progressBar.setFormat("")
  219. self.ui.progressBar.setMinimum(0)
  220. self.ui.progressBar.setMaximum(1)
  221. self.ui.progressBar.setValue(0)
  222. self.ui.progressBar.update()
  223. # Restore buffer size
  224. newBufferSize = int(jacklib.get_buffer_size(self.fJackClient))
  225. if newBufferSize != self.fBufferSize:
  226. jacklib.set_buffer_size(self.fJackClient, newBufferSize)
  227. @pyqtSlot()
  228. def slot_getAndSetPath(self):
  229. getAndSetPath(self, self.ui.le_folder.text(), self.ui.le_folder)
  230. @pyqtSlot()
  231. def slot_setStartNow(self):
  232. time = int(jacklib.get_current_transport_frame(self.fJackClient) / self.fSampleRate)
  233. secs = time % 60
  234. mins = int(time / 60) % 60
  235. hrs = int(time / 3600) % 60
  236. self.ui.te_start.setTime(QTime(hrs, mins, secs))
  237. @pyqtSlot()
  238. def slot_setEndNow(self):
  239. time = int(jacklib.get_current_transport_frame(self.fJackClient) / self.fSampleRate)
  240. secs = time % 60
  241. mins = int(time / 60) % 60
  242. hrs = int(time / 3600) % 60
  243. self.ui.te_end.setTime(QTime(hrs, mins, secs))
  244. @pyqtSlot(QTime)
  245. def slot_updateStartTime(self, time):
  246. if time >= self.ui.te_end.time():
  247. self.ui.te_end.setTime(time)
  248. renderEnabled = False
  249. else:
  250. renderEnabled = True
  251. if self.ui.group_time.isChecked():
  252. self.ui.b_render.setEnabled(renderEnabled)
  253. @pyqtSlot(QTime)
  254. def slot_updateEndTime(self, time):
  255. if time <= self.ui.te_start.time():
  256. self.ui.te_start.setTime(time)
  257. renderEnabled = False
  258. else:
  259. renderEnabled = True
  260. if self.ui.group_time.isChecked():
  261. self.ui.b_render.setEnabled(renderEnabled)
  262. @pyqtSlot(bool)
  263. def slot_transportChecked(self, yesNo):
  264. if yesNo:
  265. renderEnabled = bool(self.ui.te_end.time() > self.ui.te_start.time())
  266. else:
  267. renderEnabled = True
  268. self.ui.b_render.setEnabled(renderEnabled)
  269. @pyqtSlot()
  270. def slot_updateProgressbar(self):
  271. time = int(jacklib.get_current_transport_frame(self.fJackClient)) / self.fSampleRate
  272. self.ui.progressBar.setValue(time)
  273. if time > self.fMaxTime or (self.fLastTime > time and not self.fFreewheel):
  274. self.slot_renderStop()
  275. self.fLastTime = time
  276. def saveSettings(self):
  277. settings = QSettings("Cadence", "Cadence-Render")
  278. if self.ui.rb_mono.isChecked():
  279. channels = 1
  280. elif self.ui.rb_stereo.isChecked():
  281. channels = 2
  282. else:
  283. channels = self.ui.sb_channels.value()
  284. settings.setValue("Geometry", self.saveGeometry())
  285. settings.setValue("OutputFolder", self.ui.le_folder.text())
  286. settings.setValue("FilenamePrefix", self.ui.le_prefix.text())
  287. settings.setValue("EncodingFormat", self.ui.cb_format.currentText())
  288. settings.setValue("EncodingDepth", self.ui.cb_depth.currentText())
  289. settings.setValue("EncodingChannels", channels)
  290. settings.setValue("UseTransport", self.ui.group_time.isChecked())
  291. settings.setValue("StartTime", self.ui.te_start.time())
  292. settings.setValue("EndTime", self.ui.te_end.time())
  293. def loadSettings(self):
  294. settings = QSettings("Cadence", "Cadence-Render")
  295. self.restoreGeometry(settings.value("Geometry", ""))
  296. outputFolder = settings.value("OutputFolder", HOME)
  297. if os.path.exists(outputFolder):
  298. self.ui.le_folder.setText(outputFolder)
  299. self.ui.le_prefix.setText(settings.value("FilenamePrefix", "jack_capture_"))
  300. encFormat = settings.value("EncodingFormat", "Wav", type=str)
  301. for i in range(self.ui.cb_format.count()):
  302. if self.ui.cb_format.itemText(i) == encFormat:
  303. self.ui.cb_format.setCurrentIndex(i)
  304. break
  305. encDepth = settings.value("EncodingDepth", "Float", type=str)
  306. for i in range(self.ui.cb_depth.count()):
  307. if self.ui.cb_depth.itemText(i) == encDepth:
  308. self.ui.cb_depth.setCurrentIndex(i)
  309. break
  310. encChannels = settings.value("EncodingChannels", 2, type=int)
  311. if encChannels == 1:
  312. self.ui.rb_mono.setChecked(True)
  313. elif encChannels == 2:
  314. self.ui.rb_stereo.setChecked(True)
  315. else:
  316. self.ui.rb_outro.setChecked(True)
  317. self.ui.sb_channels.setValue(encChannels)
  318. self.ui.group_time.setChecked(settings.value("UseTransport", False, type=bool))
  319. self.ui.te_start.setTime(settings.value("StartTime", self.ui.te_start.time(), type=QTime))
  320. self.ui.te_end.setTime(settings.value("EndTime", self.ui.te_end.time(), type=QTime))
  321. def closeEvent(self, event):
  322. self.saveSettings()
  323. if self.fJackClient:
  324. jacklib.client_close(self.fJackClient)
  325. QDialog.closeEvent(self, event)
  326. def done(self, r):
  327. QDialog.done(self, r)
  328. self.close()
  329. # ------------------------------------------------------------------------------------------------------------
  330. # Allow to use this as a standalone app
  331. if __name__ == '__main__':
  332. # Additional imports
  333. from PyQt4.QtGui import QApplication
  334. # App initialization
  335. app = QApplication(sys.argv)
  336. app.setApplicationName("Cadence-Render")
  337. app.setApplicationVersion(VERSION)
  338. app.setOrganizationName("Cadence")
  339. app.setWindowIcon(QIcon(":/scalable/cadence.svg"))
  340. if jacklib is None:
  341. QMessageBox.critical(None, app.translate("RenderW", "Error"), app.translate("RenderW",
  342. "JACK is not available in this system, cannot use this application."))
  343. sys.exit(1)
  344. if not gJackCapturePath:
  345. QMessageBox.critical(None, app.translate("RenderW", "Error"), app.translate("RenderW",
  346. "The 'jack_capture' application is not available.\n"
  347. "Is not possible to render without it!"))
  348. sys.exit(2)
  349. jackStatus = jacklib.jack_status_t(0x0)
  350. gJackClient = jacklib.client_open("Render", jacklib.JackNoStartServer, jacklib.pointer(jackStatus))
  351. if not gJackClient:
  352. errorString = get_jack_status_error_string(jackStatus)
  353. QMessageBox.critical(None, app.translate("RenderW", "Error"), app.translate("RenderW",
  354. "Could not connect to JACK, possible reasons:\n"
  355. "%s" % errorString))
  356. sys.exit(1)
  357. # Show GUI
  358. gui = RenderW(None)
  359. gui.setWindowIcon(getIcon("media-record", 48))
  360. gui.show()
  361. # App-Loop
  362. sys.exit(app.exec_())