diff --git a/Makefile b/Makefile
index 1323064..8cdaff8 100644
--- a/Makefile
+++ b/Makefile
@@ -4,8 +4,8 @@
# Created by falkTX
#
-PYUIC = pyuic4
-PYRCC = pyrcc4
+PYUIC = pyuic4 --pyqt3-wrapper
+PYRCC = pyrcc4 -py3
all: build
@@ -14,21 +14,24 @@ build: UI RES LANG
UI: tools
tools: \
- src/ui_logs.py
+ src/ui_logs.py src/ui_render.py
src/ui_logs.py: src/ui/logs.ui
- $(PYUIC) -w -o src/ui_logs.py $<
+ $(PYUIC) -o src/ui_logs.py $<
+
+src/ui_render.py: src/ui/render.ui
+ $(PYUIC) -o src/ui_render.py $<
RES: src/icons_rc.py
src/icons_rc.py: src/icons/icons.qrc
- $(PYRCC) -py3 -o src/icons_rc.py $<
+ $(PYRCC) -o src/icons_rc.py $<
LANG:
# pylupdate4 -verbose src/lang/lang.pro
# lrelease src/lang/lang.pro
clean:
- rm -f *~ src/*~ src/*.pyc src/*.so src/ui_*.py src/icons_rc.py
+ rm -f *~ src/*~ src/*.pyc src/*.dll src/*.so src/ui_*.py src/icons_rc.py
distclean: clean
diff --git a/src/icons/16x16/document-open.png b/src/icons/16x16/document-open.png
new file mode 100644
index 0000000..530940c
Binary files /dev/null and b/src/icons/16x16/document-open.png differ
diff --git a/src/icons/16x16/media-playback-stop.png b/src/icons/16x16/media-playback-stop.png
new file mode 100644
index 0000000..180280e
Binary files /dev/null and b/src/icons/16x16/media-playback-stop.png differ
diff --git a/src/icons/16x16/media-record.png b/src/icons/16x16/media-record.png
new file mode 100644
index 0000000..4819a0c
Binary files /dev/null and b/src/icons/16x16/media-record.png differ
diff --git a/src/icons/48x48/media-record.png b/src/icons/48x48/media-record.png
new file mode 100644
index 0000000..a6e792f
Binary files /dev/null and b/src/icons/48x48/media-record.png differ
diff --git a/src/icons/icons.qrc b/src/icons/icons.qrc
index fe86639..45b070e 100644
--- a/src/icons/icons.qrc
+++ b/src/icons/icons.qrc
@@ -1,6 +1,10 @@
+ 16x16/document-open.png
16x16/edit-delete.png
+ 16x16/media-record.png
+ 16x16/media-playback-stop.png
16x16/window-close.png
+ 48x48/media-record.png
diff --git a/src/jacklib.py b/src/jacklib.py
index f0ebaa4..9f299a8 100644
--- a/src/jacklib.py
+++ b/src/jacklib.py
@@ -781,7 +781,6 @@ def midi_get_lost_event_count(port_buffer):
# Session
-
def set_timebase_callback(client, session_callback, arg):
jacklib.jack_set_timebase_callback.argtypes = [POINTER(jack_client_t), JackSessionCallback, c_void_p]
jacklib.jack_set_timebase_callback.restype = c_int
diff --git a/src/jacklib_helpers.py b/src/jacklib_helpers.py
new file mode 100644
index 0000000..548807f
--- /dev/null
+++ b/src/jacklib_helpers.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Helper functions for extra jacklib functionality
+# Copyright (C) 2012 Filipe Coelho
+#
+# 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
+
+import jacklib
+
+def get_jack_status_error_string(c_status):
+ status = c_status.value
+ error_string = ""
+
+ if (status & jacklib.JackFailure):
+ error_string += "Overall operation failed;\n"
+ if (status & jacklib.JackInvalidOption):
+ error_string += "The operation contained an invalid or unsupported option;\n"
+ if (status & jacklib.JackNameNotUnique):
+ error_string += "The desired client name was not unique;\n"
+ if (status & jacklib.JackServerStarted):
+ error_string += "The JACK server was started as a result of this operation;\n"
+ if (status & jacklib.JackServerFailed):
+ error_string += "Unable to connect to the JACK server;\n"
+ if (status & jacklib.JackServerError):
+ error_string += "Communication error with the JACK server;\n"
+ if (status & jacklib.JackNoSuchClient):
+ error_string += "Requested client does not exist;\n"
+ if (status & jacklib.JackLoadFailure):
+ error_string += "Unable to load internal client;\n"
+ if (status & jacklib.JackInitFailure):
+ error_string += "Unable to initialize client;\n"
+ if (status & jacklib.JackShmFailure):
+ error_string += "Unable to access shared memory;\n"
+ if (status & jacklib.JackVersionError):
+ error_string += "Client's protocol version does not match;\n"
+ if (status & jacklib.JackBackendError):
+ error_string += "Backend Error;\n"
+ if (status & jacklib.JackClientZombie):
+ error_string += "Client is being shutdown against its will;\n"
+
+ if (error_string):
+ error_string = error_string.strip().rsplit(";", 1)[0]+"."
+
+ return error_string
diff --git a/src/logs.py b/src/logs.py
index 01033b1..6f43fad 100644
--- a/src/logs.py
+++ b/src/logs.py
@@ -215,7 +215,7 @@ class LogsReadThread(QThread):
if (self.LOG_FILE_LADISH):
self.log_ladish_file.close()
-# Class Window
+# Logs Window
class LogsW(QDialog, ui_logs.Ui_LogsW):
LOG_PATH = os.path.join(HOME, ".log")
diff --git a/src/render.py b/src/render.py
new file mode 100644
index 0000000..6b8bb93
--- /dev/null
+++ b/src/render.py
@@ -0,0 +1,305 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# JACK-Capture frontend, with freewheel and transport support
+# Copyright (C) 2012 Filipe Coelho
+#
+# 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, Qt, QProcess, QTime, QTimer, SIGNAL, SLOT
+from PyQt4.QtGui import QDialog
+from time import sleep
+
+# Imports (Custom Stuff)
+import ui_render
+from shared import *
+from jacklib_helpers import *
+
+global jack_client
+jack_client = None
+
+# Render Window
+class RenderW(QDialog, ui_render.Ui_RenderW):
+ def __init__(self, parent, flags):
+ QDialog.__init__(self, parent, flags)
+ self.setupUi(self)
+
+ # -------------------------------------------------------------
+ # Get JACK client and base information
+
+ global jack_client
+ if (jack_client):
+ self.m_jack_client = jack_client
+ self.m_closeClient = False
+ else:
+ self.m_jack_client = jacklib.client_open("Render-Dialog", jacklib.JackNoStartServer, None)
+ self.m_closeClient = True
+
+ self.m_buffer_size = jacklib.get_buffer_size(self.m_jack_client)
+ for i in range(self.cb_buffer_size.count()):
+ if (int(self.cb_buffer_size.itemText(i)) == self.m_buffer_size):
+ self.cb_buffer_size.setCurrentIndex(i)
+
+ self.m_sample_rate = jacklib.get_sample_rate(self.m_jack_client)
+
+ # -------------------------------------------------------------
+ # Internal stuff
+
+ self.m_max_time = 180
+ self.m_last_time = 0
+ self.m_freewheel = False
+
+ self.m_timer = QTimer(self)
+ self.m_process = QProcess(self)
+
+ # -------------------------------------------------------------
+ # Set-up GUI stuff
+
+ # Get List of formats
+ self.m_process.start("jack_capture", ["-pf"])
+ self.m_process.waitForFinished()
+
+ formats = str(self.m_process.readAllStandardOutput(), encoding="ascii").split(" ")
+ for i in range(len(formats)-1):
+ self.cb_format.addItem(formats[i])
+ if (formats[i] == "wav"):
+ self.cb_format.setCurrentIndex(i)
+
+ self.cb_depth.setCurrentIndex(4) #Float
+ self.rb_stereo.setChecked(True)
+
+ self.te_end.setTime(QTime(0, 3, 0))
+ self.progressBar.setMinimum(0)
+ self.progressBar.setMaximum(0)
+ self.progressBar.setValue(0)
+
+ self.b_render.setIcon(getIcon("media-record"))
+ self.b_stop.setIcon(getIcon("media-playback-stop"))
+ self.b_close.setIcon(getIcon("window-close"))
+ self.b_open.setIcon(getIcon("document-open"))
+ self.b_stop.setVisible(False)
+ self.le_folder.setText(HOME)
+
+ # -------------------------------------------------------------
+ # Set-up connections
+
+ self.connect(self.b_render, SIGNAL("clicked()"), SLOT("slot_renderStart()"))
+ self.connect(self.b_stop, SIGNAL("clicked()"), SLOT("slot_renderStop()"))
+ self.connect(self.b_open, SIGNAL("clicked()"), SLOT("slot_getAndSetPath()"))
+ self.connect(self.b_now_start, SIGNAL("clicked()"), SLOT("slot_setStartNow()"))
+ self.connect(self.b_now_end, SIGNAL("clicked()"), SLOT("slot_setEndNow()"))
+ self.connect(self.te_start, SIGNAL("timeChanged(const QTime)"), SLOT("slot_updateStartTime(const QTime)"))
+ self.connect(self.te_end, SIGNAL("timeChanged(const QTime)"), SLOT("slot_updateEndTime(const QTime)"))
+ self.connect(self.m_timer, SIGNAL("timeout()"), SLOT("slot_updateProgressbar()"))
+
+ @pyqtSlot()
+ def slot_renderStart(self):
+ if (os.path.exists(self.le_folder.text()) == False):
+ QMessageBox.warning(self, self.tr("Warning"), self.tr("The selected directory does not exist. Please choose a valid one."))
+ return
+
+ self.group_render.setEnabled(False)
+ self.group_time.setEnabled(False)
+ self.group_encoding.setEnabled(False)
+ self.b_render.setVisible(False)
+ self.b_stop.setVisible(True)
+ self.b_close.setEnabled(False)
+
+ self.m_freewheel = (self.cb_render_mode.currentIndex() == 1)
+ new_buffer_size = int(self.cb_buffer_size.currentText())
+
+ time_start = self.te_start.time()
+ time_end = self.te_end.time()
+ min_time = (time_start.hour()*3600)+(time_start.minute()*60)+(time_start.second())
+ max_time = (time_end.hour()*3600)+(time_end.minute()*60)+(time_end.second())
+ self.m_max_time = max_time
+
+ self.progressBar.setMinimum(min_time)
+ self.progressBar.setMaximum(max_time)
+ self.progressBar.setValue(min_time)
+ self.progressBar.update()
+
+ if (self.m_freewheel):
+ self.m_timer.setInterval(100)
+ else:
+ self.m_timer.setInterval(500)
+
+ arguments = []
+
+ # Bit depth
+ arguments.append("-b")
+ arguments.append(self.cb_depth.currentText())
+
+ # Channels
+ arguments.append("-c")
+ if (self.rb_mono.isChecked()):
+ arguments.append("1")
+ elif (self.rb_stereo.isChecked()):
+ arguments.append("2")
+ else:
+ arguments.append(str(self.sb_channels.value()))
+
+ # Format
+ arguments.append("-f")
+ arguments.append(self.cb_format.currentText())
+
+ # Controlled by transport
+ arguments.append("-jt")
+
+ # Silent mode
+ arguments.append("-dc")
+ arguments.append("-s")
+
+ # Change current directory
+ os.chdir(self.le_folder.text())
+
+ if (new_buffer_size != jacklib.get_buffer_size(self.m_jack_client)):
+ print("NOTICE: buffer size changed before render")
+ jacklib.set_buffer_size(self.m_jack_client, new_buffer_size)
+
+ if (jacklib.transport_query(self.m_jack_client, None) > jacklib.JackTransportStopped): # >TransportStopped is rolling/starting
+ jacklib.transport_stop(self.m_jack_client)
+
+ jacklib.transport_locate(self.m_jack_client, min_time*self.m_sample_rate)
+ self.m_last_time = -1
+
+ self.m_process.start("jack_capture", arguments)
+ self.m_process.waitForStarted()
+
+ if (self.m_freewheel):
+ sleep(1)
+ print("NOTICE: rendering in freewheel mode")
+ jacklib.set_freewheel(jack_client, 1)
+
+ self.m_timer.start()
+ jacklib.transport_start(self.m_jack_client)
+
+ @pyqtSlot()
+ def slot_renderStop(self):
+ jacklib.transport_stop(self.m_jack_client)
+
+ if (self.m_freewheel):
+ jacklib.set_freewheel(self.m_jack_client, 0)
+
+ sleep(1)
+
+ self.m_process.close()
+ self.m_timer.stop()
+
+ self.group_render.setEnabled(True)
+ self.group_time.setEnabled(True)
+ self.group_encoding.setEnabled(True)
+ self.b_render.setVisible(True)
+ self.b_stop.setVisible(False)
+ self.b_close.setEnabled(True)
+
+ self.progressBar.setMinimum(0)
+ self.progressBar.setMaximum(0)
+ self.progressBar.setValue(0)
+ self.progressBar.update()
+
+ # Restore buffer size
+ new_buffer_size = jacklib.get_buffer_size(self.m_jack_client)
+ if (new_buffer_size != self.m_buffer_size):
+ jacklib.set_buffer_size(self.m_jack_client, new_buffer_size)
+
+ @pyqtSlot()
+ def slot_getAndSetPath(self):
+ getAndSetPath(self, self.le_folder.text(), self.le_folder)
+
+ @pyqtSlot()
+ def slot_setStartNow(self):
+ time = jacklib.get_current_transport_frame(self.m_jack_client)/self.m_sample_rate
+ secs = time % 60
+ mins = (time / 60) % 60
+ hrs = (time / 3600) % 60
+ self.te_start.setTime(QTime(hrs, mins, secs))
+
+ @pyqtSlot()
+ def slot_setEndNow(self):
+ time = jacklib.get_current_transport_frame(self.m_jack_client)/self.m_sample_rate
+ secs = time % 60
+ mins = (time / 60) % 60
+ hrs = (time / 3600) % 60
+ self.te_end.setTime(QTime(hrs, mins, secs))
+
+ @pyqtSlot(QTime)
+ def slot_updateStartTime(self, time):
+ if (time >= self.te_end.time()):
+ self.te_end.setTime(time)
+ self.b_render.setEnabled(False)
+ else:
+ self.b_render.setEnabled(True)
+
+ @pyqtSlot(QTime)
+ def slot_updateEndTime(self, time):
+ if (time <= self.te_start.time()):
+ time = self.te_start.setTime(time)
+ self.b_render.setEnabled(False)
+ else:
+ self.b_render.setEnabled(True)
+
+ @pyqtSlot()
+ def slot_updateProgressbar(self):
+ time = jacklib.get_current_transport_frame(self.m_jack_client)/self.m_sample_rate
+ self.progressBar.setValue(time)
+
+ if (time > self.m_max_time or (self.m_last_time > time and self.m_freewheel == False)):
+ self.slot_renderStop()
+
+ self.m_last_time = time
+
+ def closeEvent(self, event):
+ if (self.m_closeClient):
+ jacklib.client_close(self.m_jack_client)
+ QDialog.closeEvent(self, event)
+
+# -------------------------------------------------------------
+# Allow to use this as a standalone app
+if __name__ == '__main__':
+
+ # Additional imports
+ import sys
+ from PyQt4.QtGui import QApplication
+
+ # App initialization
+ app = QApplication(sys.argv)
+
+ for iPATH in PATH:
+ if os.path.exists(os.path.join(iPATH, "jack_capture")):
+ break
+ else:
+ QMessageBox.critical(None, app.translate("RenderW", "Error"), app.translate("RenderW", "The 'jack_capture' application is not available.\nIs not possible to render without it!"))
+ sys.exit(1)
+
+ jack_status = jacklib.jack_status_t(0)
+ jack_client = jacklib.client_open("Render", jacklib.JackNoStartServer, jacklib.pointer(jack_status))
+
+ if not jack_client:
+ QMessageBox.critical(None, app.translate("RenderW", "Error"), app.translate("RenderW", "Could not connect to JACK, possible errors:\n%s" % (get_jack_status_error_string(jack_status))))
+ sys.exit(1)
+
+ # Show GUI
+ gui = RenderW(None, Qt.WindowFlags())
+ gui.setWindowIcon(getIcon("media-record", 48))
+ gui.show()
+
+ # App-Loop
+ ret = app.exec_()
+
+ if (jack_client):
+ jacklib.client_close(jack_client)
+
+ # Exit properly
+ sys.exit(ret)
diff --git a/src/shared.py b/src/shared.py
index 1bb1112..6c671b6 100644
--- a/src/shared.py
+++ b/src/shared.py
@@ -19,7 +19,7 @@
# Imports (Global)
import os
from PyQt4.QtCore import qDebug, qWarning
-from PyQt4.QtGui import QIcon
+from PyQt4.QtGui import QIcon, QMessageBox, QFileDialog
# Small integrity tests
HOME = os.getenv("HOME")
@@ -41,3 +41,10 @@ else:
# Get Icon from user theme, using our own as backup (Oxygen)
def getIcon(icon, size=16):
return QIcon.fromTheme(icon, QIcon(":/%ix%i/%s.png" % (size, size, icon)))
+
+# QLineEdit and QPushButtom combo
+def getAndSetPath(self, currentPath, lineEdit):
+ newPath = QFileDialog.getExistingDirectory(self, self.tr("Set Path"), currentPath, QFileDialog.ShowDirsOnly)
+ if (newPath):
+ lineEdit.setText(newPath)
+ return newPath
diff --git a/src/ui/render.ui b/src/ui/render.ui
new file mode 100644
index 0000000..a4bd62c
--- /dev/null
+++ b/src/ui/render.ui
@@ -0,0 +1,439 @@
+
+
+ RenderW
+
+
+
+ 0
+ 0
+ 585
+ 332
+
+
+
+ Render
+
+
+ -
+
+
-
+
+
+ 24
+
+
+
+ -
+
+
+ &Render
+
+
+
+ :/16x16/media-record.png:/16x16/media-record.png
+
+
+
+ -
+
+
+ &Stop
+
+
+
+ :/16x16/media-playback-stop.png:/16x16/media-playback-stop.png
+
+
+
+ -
+
+
+ &Close
+
+
+
+ :/16x16/window-close.png:/16x16/window-close.png
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ Render Options
+
+
+
-
+
+
+ Render &Mode:
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+ cb_render_mode
+
+
+
+ -
+
+
-
+
+ Realtime
+
+
+ -
+
+ Freewheel
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 275
+ 20
+
+
+
+
+ -
+
+
+ &Buffer Size:
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+ cb_buffer_size
+
+
+
+ -
+
+
+ true
+
+
-
+
+ 32
+
+
+ -
+
+ 64
+
+
+ -
+
+ 128
+
+
+ -
+
+ 256
+
+
+ -
+
+ 512
+
+
+ -
+
+ 1024
+
+
+ -
+
+ 2048
+
+
+ -
+
+ 4096
+
+
+ -
+
+ 8192
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 275
+ 20
+
+
+
+
+ -
+
+
+ Output folder:
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
-
+
+
+ -
+
+
+
+
+
+
+ :/16x16/document-open.png:/16x16/document-open.png
+
+
+
+
+
+
+
+
+ -
+
+
+ Time
+
+
+
-
+
+
+ &Start Time:
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+ te_start
+
+
+
+ -
+
+
+ hh:mm:ss
+
+
+
+ -
+
+
+ &End Time:
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+ te_end
+
+
+
+ -
+
+
+ hh:mm:ss
+
+
+
+ -
+
+
+ now
+
+
+
+ -
+
+
+ now
+
+
+
+
+
+
+ -
+
+
+ Encoding
+
+
+
-
+
+
+ &Format:
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+ cb_format
+
+
+
+ -
+
+
+ -
+
+
+ false
+
+
+ ...
+
+
+
+ -
+
+
+ Bit &Depth:
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+ cb_depth
+
+
+
+ -
+
+
-
+
+ 8
+
+
+ -
+
+ 16
+
+
+ -
+
+ 24
+
+
+ -
+
+ 32
+
+
+ -
+
+ Float
+
+
+
+
+ -
+
+
+ Mono
+
+
+
+ -
+
+
+ Stereo
+
+
+
+ -
+
+
+ Outro:
+
+
+
+ -
+
+
+ false
+
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+ b_close
+ clicked()
+ RenderW
+ close()
+
+
+ 465
+ 343
+
+
+ 258
+ 183
+
+
+
+
+ rb_outro
+ toggled(bool)
+ sb_channels
+ setEnabled(bool)
+
+
+ 405
+ 259
+
+
+ 484
+ 255
+
+
+
+
+