diff --git a/src/digitalpeakmeter.py b/src/digitalpeakmeter.py new file mode 100644 index 0000000..ceddadc --- /dev/null +++ b/src/digitalpeakmeter.py @@ -0,0 +1,231 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Digital Peak Meter, a custom Qt4 widget +# 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 qCritical, Qt, QRectF, QTimer, QSize +from PyQt4.QtGui import QColor, QLinearGradient, QPainter, QWidget + +# Widget Class +class DigitalPeakMeter(QWidget): + HORIZONTAL = 1 + VERTICAL = 2 + + GREEN = 1 + BLUE = 2 + + def __init__(self, parent): + QWidget.__init__(self, parent) + + self.n_channels = 0 + self.bg_color = QColor("#111111") + + self.base_color = QColor("#5DE73D") + self.base_colorT = QColor(15, 110, 15, 100) + self.orientation = self.VERTICAL + + self.meter_gradient = QLinearGradient(0, 0, 1, 1) + self.smooth_multiplier = 1 + + self.setOrientation(self.VERTICAL) + self.setChannels(2) + + self.paint_timer = QTimer() + self.paint_timer.setInterval(60) + self.paint_timer.timeout.connect(self.update) + self.paint_timer.start() + + def minimumSizeHint(self): + return QSize(30, 30) + + def sizeHint(self): + return QSize(self.width_, self.height_) + + def setChannels(self, channels): + self.n_channels = channels + self.channels_data = [] + self.last_max_data = [] + + if (channels > 0): + for i in range(channels): + self.channels_data.append(0.0) + self.last_max_data.append(0.0) #self.height_ + + def setColor(self, color): + if (color == self.GREEN): + self.base_color = QColor("#5DE73D") + self.base_colorT = QColor(15, 110, 15, 100) + elif (color == self.BLUE): + self.base_color = QColor("#52EEF8") + self.base_colorT = QColor(15, 15, 110, 100) + else: + return + + self.setOrientation(self.orientation) + + def setOrientation(self, orientation): + self.orientation = orientation + + if (self.orientation == self.HORIZONTAL): + self.meter_gradient.setColorAt(0.0, self.base_color) + self.meter_gradient.setColorAt(0.2, self.base_color) + self.meter_gradient.setColorAt(0.4, self.base_color) + self.meter_gradient.setColorAt(0.6, self.base_color) + self.meter_gradient.setColorAt(0.8, Qt.yellow) + self.meter_gradient.setColorAt(1.0, Qt.red) + + elif (self.orientation == self.VERTICAL): + self.meter_gradient.setColorAt(0.0, Qt.red) + self.meter_gradient.setColorAt(0.2, Qt.yellow) + self.meter_gradient.setColorAt(0.4, self.base_color) + self.meter_gradient.setColorAt(0.6, self.base_color) + self.meter_gradient.setColorAt(0.8, self.base_color) + self.meter_gradient.setColorAt(1.0, self.base_color) + + self.checkSizes() + + def setRefreshRate(self, rate): + self.paint_timer.stop() + self.paint_timer.setInterval(rate) + self.paint_timer.start() + + def setSmoothRelease(self, value): + if (value < 0): + value = 0 + elif (value > 5): + value = 5 + self.smooth_multiplier = value + + def displayMeter(self, meter_n, level): + if (meter_n > self.n_channels): + qCritical("DigitalPeakMeter::displayMeter(%i, %f) - Invalid meter number", meter_n, level) + return + + if (level < 0.0): + level = -level + if (level > 1.0): + level = 1.0 + + self.channels_data[meter_n-1] = level + + def checkSizes(self): + self.width_ = self.width() + self.height_ = self.height() + self.meter_size = 0 + + if (self.orientation == self.HORIZONTAL): + self.meter_gradient.setFinalStop(self.width_, 0) + if (self.n_channels > 0): + self.meter_size = self.height_/self.n_channels + + elif (self.orientation == self.VERTICAL): + self.meter_gradient.setFinalStop(0, self.height_) + if (self.n_channels > 0): + self.meter_size = self.width_/self.n_channels + + def paintEvent(self, event): + painter = QPainter(self) + + painter.setPen(Qt.black) + painter.setBrush(Qt.black) + painter.drawRect(0, 0, self.width_, self.height_) + + meter_x = 0 + + for i in range(self.n_channels): + level = self.channels_data[i] + + if (level == self.last_max_data[i]): + continue + + if (self.orientation == self.HORIZONTAL): + value = self.width_*level + elif (self.orientation == self.VERTICAL): + value = self.height_-(self.height_*level) + else: + value = 0 + + # Don't bounce the meter so much + if (self.smooth_multiplier > 0): + value = (self.last_max_data[i]*self.smooth_multiplier + value)/(self.smooth_multiplier+1) + + if (value < 0): + value = 0 + + painter.setPen(self.bg_color) + painter.setBrush(self.meter_gradient) + + if (self.orientation == self.HORIZONTAL): + painter.drawRect(0, meter_x, value, self.meter_size) + elif (self.orientation == self.VERTICAL): + painter.drawRect(meter_x, value, self.meter_size, self.height_) + + meter_x += self.meter_size + self.last_max_data[i] = value + + if (self.orientation == self.HORIZONTAL): + lsmall = self.width_ + lfull = self.height_-1 + elif (self.orientation == self.VERTICAL): + lsmall = self.height_ + lfull = self.width_-1 + else: + return + + painter.setBrush(QColor(0, 0, 0, 0)) + + if (self.orientation == self.HORIZONTAL): + # Base + painter.setPen(self.base_colorT) + painter.drawLine(lsmall/4, 1, lsmall/4, lfull) + painter.drawLine(lsmall/2, 1, lsmall/2, lfull) + + # Yellow + painter.setPen(QColor(110, 110, 15, 100)) + painter.drawLine(lsmall/1.4, 1, lsmall/1.4, lfull) + painter.drawLine(lsmall/1.2, 1, lsmall/1.2, lfull) + + # Orange + painter.setPen(QColor(180, 110, 15, 100)) + painter.drawLine(lsmall/1.1, 1, lsmall/1.1, lfull) + + # Red + painter.setPen(QColor(110, 15, 15, 100)) + painter.drawLine(lsmall/1.04, 1, lsmall/1.04, lfull) + + elif (self.orientation == self.VERTICAL): + # Base + painter.setPen(self.base_colorT) + painter.drawLine(1, lsmall-(lsmall/4), lfull, lsmall-(lsmall/4)) + painter.drawLine(1, lsmall-(lsmall/2), lfull, lsmall-(lsmall/2)) + + # Yellow + painter.setPen(QColor(110, 110, 15, 100)) + painter.drawLine(1, lsmall-(lsmall/1.4), lfull, lsmall-(lsmall/1.4)) + painter.drawLine(1, lsmall-(lsmall/1.2), lfull, lsmall-(lsmall/1.2)) + + # Orange + painter.setPen(QColor(180, 110, 15, 100)) + painter.drawLine(1, lsmall-(lsmall/1.1), lfull, lsmall-(lsmall/1.1)) + + # Red + painter.setPen(QColor(110, 15, 15, 100)) + painter.drawLine(1, lsmall-(lsmall/1.04), lfull, lsmall-(lsmall/1.04)) + + def resizeEvent(self, event): + QTimer.singleShot(0, self.checkSizes) + return QWidget.resizeEvent(self, event) diff --git a/src/jacklib_helpers.py b/src/jacklib_helpers.py index 548807f..196b6db 100644 --- a/src/jacklib_helpers.py +++ b/src/jacklib_helpers.py @@ -53,3 +53,26 @@ def get_jack_status_error_string(c_status): error_string = error_string.strip().rsplit(";", 1)[0]+"." return error_string + +# C char** -> Python list conversion +def c_char_p_p_to_list(c_char_p_p): + i = 0 + final_list = [] + + if (not c_char_p_p): + return final_list + + while (True): + new_char_p = c_char_p_p[i] + if (new_char_p): + final_list.append(str(new_char_p, encoding="ascii")) + else: + break + i += 1 + + jacklib.free(c_char_p_p) + return final_list + +# C cast void* -> jack_default_audio_sample_t* +def translate_audio_port_buffer(void_p): + return jacklib.cast(void_p, jacklib.POINTER(jacklib.jack_default_audio_sample_t)) diff --git a/src/jackmeter.py b/src/jackmeter.py new file mode 100644 index 0000000..95a647e --- /dev/null +++ b/src/jackmeter.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Simple JACK Audio Meter +# 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 Qt +from PyQt4.QtGui import QApplication, QWidget + +# Imports (Custom Stuff) +from digitalpeakmeter import DigitalPeakMeter +from jacklib_helpers import * +from shared import * + +global x_port1, x_port2, need_reconnect +x_port1 = 0.0 +x_port2 = 0.0 +need_reconnect = False +client = None + +def process_callback(nframes, arg): + global x_port1, x_port2 + + p_out1 = translate_audio_port_buffer(jacklib.port_get_buffer(port_1, nframes)) + p_out2 = translate_audio_port_buffer(jacklib.port_get_buffer(port_2, nframes)) + + for i in range(nframes): + if (abs(p_out1[i]) > x_port1): + x_port1 = abs(p_out1[i]) + + if (abs(p_out2[i]) > x_port2): + x_port2 = abs(p_out2[i]) + + return 0 + +def port_callback(port_a, port_b, connect_yesno, arg): + global need_reconnect + need_reconnect = True + return 0 + +def reconnect_inputs(): + play_port_1 = jacklib.port_by_name(client, "system:playback_1") + play_port_2 = jacklib.port_by_name(client, "system:playback_2") + list_port_1 = c_char_p_p_to_list(jacklib.port_get_all_connections(client, play_port_1)) + list_port_2 = c_char_p_p_to_list(jacklib.port_get_all_connections(client, play_port_2)) + client_name = str(jacklib.get_client_name(client), encoding="ascii") + + for port in list_port_1: + this_port = jacklib.port_by_name(client, port) + if not jacklib.port_is_mine(client, this_port): + jacklib.connect(client, port, "%s:in1" % (client_name)) + + for port in list_port_2: + this_port = jacklib.port_by_name(client, port) + if not jacklib.port_is_mine(client, this_port): + jacklib.connect(client, port, "%s:in2" % (client_name)) + + global need_reconnect + need_reconnect = False + +class MeterW(DigitalPeakMeter): + def __init__(self, parent): + DigitalPeakMeter.__init__(self, parent) + + client_name = str(jacklib.get_client_name(client), encoding="ascii") + + self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint); + self.setWindowTitle(client_name) + self.setChannels(2) + self.setOrientation(self.VERTICAL) + self.setSmoothRelease(1) + + self.displayMeter(1, x_port1) + self.displayMeter(2, x_port2) + + self.setRefreshRate(30) + self.peakTimer = self.startTimer(60) + + def timerEvent(self, event): + if (event.timerId() == self.peakTimer): + global x_port1, x_port2, need_reconnect + self.displayMeter(1, x_port1) + self.displayMeter(2, x_port2) + x_port1 = 0.0 + x_port2 = 0.0 + + if (need_reconnect): + reconnect_inputs() + + QWidget.timerEvent(self, event) + +#--------------- main ------------------ +if __name__ == '__main__': + + # App initialization + app = QApplication(sys.argv) + + # JACK initialization + client = jacklib.client_open("M", jacklib.JackNullOption, None) + + port_1 = jacklib.port_register(client, "in1", jacklib.JACK_DEFAULT_AUDIO_TYPE, jacklib.JackPortIsInput, 0) + port_2 = jacklib.port_register(client, "in2", jacklib.JACK_DEFAULT_AUDIO_TYPE, jacklib.JackPortIsInput, 0) + + jacklib.set_process_callback(client, process_callback, None) + jacklib.set_port_connect_callback(client, port_callback, None) + jacklib.activate(client) + + reconnect_inputs() + + # Show GUI + gui = MeterW(None) + gui.resize(70, 600) + gui.show() + + set_up_signals(gui) + + # App-Loop + ret = app.exec_() + + jacklib.deactivate(client) + jacklib.client_close(client) + + sys.exit(ret) diff --git a/src/logs.py b/src/logs.py index 6f43fad..cf9be95 100644 --- a/src/logs.py +++ b/src/logs.py @@ -17,7 +17,7 @@ # For a full copy of the GNU General Public License see the COPYING file # Imports (Global) -from PyQt4.QtCore import pyqtSlot, Qt, QFile, QIODevice, QTextStream, QThread, SIGNAL, SLOT +from PyQt4.QtCore import pyqtSlot, Qt, QFile, QIODevice, QTextStream, QThread from PyQt4.QtGui import QDialog, QPalette, QSyntaxHighlighter # Imports (Custom Stuff) @@ -352,7 +352,6 @@ class LogsW(QDialog, ui_logs.Ui_LogsW): if __name__ == '__main__': # Additional imports - import sys from PyQt4.QtGui import QApplication # App initialization @@ -362,5 +361,7 @@ if __name__ == '__main__': gui = LogsW(None, Qt.WindowFlags()) gui.show() + set_up_signals(gui) + # App-Loop sys.exit(app.exec_()) diff --git a/src/render.py b/src/render.py index 6b8bb93..ff8ad12 100644 --- a/src/render.py +++ b/src/render.py @@ -17,7 +17,7 @@ # 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.QtCore import pyqtSlot, Qt, QProcess, QTime, QTimer from PyQt4.QtGui import QDialog from time import sleep @@ -270,7 +270,6 @@ class RenderW(QDialog, ui_render.Ui_RenderW): if __name__ == '__main__': # Additional imports - import sys from PyQt4.QtGui import QApplication # App initialization diff --git a/src/shared.py b/src/shared.py index 6c671b6..80119ce 100644 --- a/src/shared.py +++ b/src/shared.py @@ -17,10 +17,24 @@ # For a full copy of the GNU General Public License see the COPYING file # Imports (Global) -import os -from PyQt4.QtCore import qDebug, qWarning +import os, sys +from PyQt4.QtCore import pyqtSlot, qWarning, SIGNAL, SLOT from PyQt4.QtGui import QIcon, QMessageBox, QFileDialog +# Set Platform +if ("linux" in sys.platform): + LINUX = True + WINDOWS = False +elif ("win" in sys.platform): + LINUX = False + WINDOWS = True +else: + LINUX = False + WINDOWS = False + +if (WINDOWS == False): + from signal import signal, SIGINT, SIGTERM, SIGUSR1, SIGUSR2 + # Small integrity tests HOME = os.getenv("HOME") if (HOME == None): @@ -48,3 +62,43 @@ def getAndSetPath(self, currentPath, lineEdit): if (newPath): lineEdit.setText(newPath) return newPath + +# Custom MessageBox +def CustomMessageBox(self, icon, title, text, extra_text="", buttons=QMessageBox.Yes|QMessageBox.No, defButton=QMessageBox.No): + msgBox = QMessageBox(self) + msgBox.setIcon(icon) + msgBox.setWindowTitle(title) + msgBox.setText(text) + msgBox.setInformativeText(extra_text) + msgBox.setStandardButtons(buttons) + msgBox.setDefaultButton(defButton) + return msgBox.exec_() + +# signal handler for unix systems +def set_up_signals(_gui): + if (WINDOWS == False): + from signal import signal, SIGINT, SIGTERM, SIGUSR1, SIGUSR2 + global x_gui + x_gui = _gui + signal(SIGINT, signal_handler) + signal(SIGTERM, signal_handler) + signal(SIGUSR1, signal_handler) + signal(SIGUSR2, signal_handler) + + x_gui.connect(x_gui, SIGNAL("SIGUSR2()"), lambda gui=x_gui: showWindow(gui)) + x_gui.connect(x_gui, SIGNAL("SIGTERM()"), SLOT("close()")) + +def signal_handler(sig=0, frame=0): + global x_gui + if (sig in (SIGINT, SIGTERM)): + x_gui.emit(SIGNAL("SIGTERM()")) + elif (sig == SIGUSR1): + x_gui.emit(SIGNAL("SIGUSR1()")) + elif (sig == SIGUSR2): + x_gui.emit(SIGNAL("SIGUSR2()")) + +def showWindow(self): + if (self.isMaximized()): + self.showMaximized() + else: + self.showNormal()