root/trunk/libffado/support/mixer-qt4/ffado/widgets/matrixmixer.py

Revision 2064, 13.0 kB (checked in by jwoithe, 9 years ago)

matrixmixer: only label rows/columns if there is more than one row/column; a single row/column label is pointless.

  • Property svn:mergeinfo set to
Line 
1 # coding=utf8
2 #
3 # Copyright (C) 2009 by Arnold Krille
4 #
5 # This file is part of FFADO
6 # FFADO = Free Firewire (pro-)audio drivers for linux
7 #
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 2 of the License, or
11 # (at your option) version 3 of the License.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #
21
22 from PyQt4 import QtGui, QtCore, Qt
23 import dbus, math, decimal
24
25 import logging
26 log = logging.getLogger("matrixmixer")
27
28 class ColorForNumber:
29     def __init__(self):
30         self.colors = dict()
31
32     def addColor(self, n, color):
33         self.colors[n] = color
34
35     def getColor(self, n):
36         #print "ColorForNumber.getColor( %g )" % (n)
37         keys = self.colors.keys()
38         keys.sort()
39         low = keys[-1]
40         high = keys[-1]
41         for i in range(len(keys)-1):
42             if keys[i] <= n and keys[i+1] > n:
43                 low = keys[i]
44                 high = keys[i+1]
45         #print "%g is between %g and %g" % (n, low, high)
46         f = 0
47         if high != low:
48             f = (n-low) / (high-low)
49         lc = self.colors[low]
50         hc = self.colors[high]
51         return QtGui.QColor(
52                 (1-f)*lc.red()   + f*hc.red(),
53                 (1-f)*lc.green() + f*hc.green(),
54                 (1-f)*lc.blue()  + f*hc.blue() )
55
56 class MixerNode(QtGui.QAbstractSlider):
57     def __init__(self, input, output, value, max, muted, inverted, parent):
58         QtGui.QAbstractSlider.__init__(self, parent)
59         #log.debug("MixerNode.__init__( %i, %i, %i, %i, %s )" % (input, output, value, max, str(parent)) )
60
61         self.pos = QtCore.QPointF(0, 0)
62         self.input = input
63         self.output = output
64         self.setOrientation(Qt.Qt.Vertical)
65         if max == -1:
66             max = pow(2, 16)-1
67         self.setRange(0, max)
68         self.setValue(value)
69         self.connect(self, QtCore.SIGNAL("valueChanged(int)"), self.internalValueChanged)
70
71         self.setSmall(False)
72
73         self.bgcolors = ColorForNumber()
74         self.bgcolors.addColor(             0.0, QtGui.QColor(  0,   0,   0))
75         self.bgcolors.addColor(             1.0, QtGui.QColor(  0,   0, 128))
76         self.bgcolors.addColor(   math.pow(2,6), QtGui.QColor(  0, 255,   0))
77         self.bgcolors.addColor(  math.pow(2,14), QtGui.QColor(255, 255,   0))
78         self.bgcolors.addColor(math.pow(2,16)-1, QtGui.QColor(255,   0,   0))
79
80         self.setContextMenuPolicy(Qt.Qt.ActionsContextMenu)
81         self.mapper = QtCore.QSignalMapper(self)
82         self.connect(self.mapper, QtCore.SIGNAL("mapped(const QString&)"), self.directValues)
83
84         self.spinbox = QtGui.QDoubleSpinBox(self)
85         self.spinbox.setRange(-40, 12)
86         self.spinbox.setSuffix(" dB")
87         self.connect(self.spinbox, QtCore.SIGNAL("valueChanged(const QString&)"), self.directValues)
88         action = QtGui.QWidgetAction(self)
89         action.setDefaultWidget(self.spinbox)
90         self.addAction(action)
91
92         for text in ["3 dB", "0 dB", "-3 dB", "-20 dB", "-inf dB"]:
93             action = QtGui.QAction(text, self)
94             self.connect(action, QtCore.SIGNAL("triggered()"), self.mapper, QtCore.SLOT("map()"))
95             self.mapper.setMapping(action, text)
96             self.addAction(action)
97
98         # Only show the mute menu item if a value has been supplied
99         self.mute_action = None
100         if (muted != None):
101             action = QtGui.QAction(text, self)
102             action.setSeparator(True)
103             self.addAction(action)
104             self.mute_action = QtGui.QAction("Mute", self)
105             self.mute_action.setCheckable(True)
106             self.mute_action.setChecked(muted)
107             self.connect(self.mute_action, QtCore.SIGNAL("triggered()"), self.mapper, QtCore.SLOT("map()"))
108             self.mapper.setMapping(self.mute_action, "Mute")
109             self.addAction(self.mute_action)
110
111         # Similarly, only show a phase inversion menu item if in use
112         self.inv_action = None
113         if (inverted != None):
114             if (muted == None):
115                 action = QtGui.QAction(text, self)
116                 action.setSeparator(True)
117                 self.addAction(action)
118             self.inv_action = QtGui.QAction("Invert", self)
119             self.inv_action.setCheckable(True)
120             self.inv_action.setChecked(inverted)
121             self.connect(self.inv_action, QtCore.SIGNAL("triggered()"), self.mapper, QtCore.SLOT("map()"))
122             self.mapper.setMapping(self.inv_action, "Invert")
123             self.addAction(self.inv_action)
124
125     def directValues(self,text):
126         #log.debug("MixerNode.directValues( '%s' )" % text)
127         if text == "Mute":
128             #log.debug("Mute %d" % self.mute_action.isChecked())
129             self.update()
130             self.parent().mutes_interface.setValue(self.output, self.input, self.mute_action.isChecked())
131         elif text == "Invert":
132             #log.debug("Invert %d" % self.inv_action.isChecked())
133             self.update()
134             self.parent().inverts_interface.setValue(self.output, self.input, self.inv_action.isChecked())
135         else:
136             text = text.split(" ")[0].replace(",",".")
137             n = pow(10, (float(text)/20)) * pow(2,14)
138             #log.debug("%g" % n)
139             self.setValue(n)
140
141     def mousePressEvent(self, ev):
142         if ev.buttons() & Qt.Qt.LeftButton:
143             self.pos = ev.posF()
144             self.tmpvalue = self.value()
145             ev.accept()
146             #log.debug("MixerNode.mousePressEvent() %s" % str(self.pos))
147
148     def mouseMoveEvent(self, ev):
149         if hasattr(self, "tmpvalue") and self.pos is not QtCore.QPointF(0, 0):
150             newpos = ev.posF()
151             change = newpos.y() - self.pos.y()
152             #log.debug("MixerNode.mouseReleaseEvent() change %s" % (str(change)))
153             self.setValue( self.tmpvalue - math.copysign(pow(abs(change), 2), change) )
154             ev.accept()
155
156     def mouseReleaseEvent(self, ev):
157         if hasattr(self, "tmpvalue") and self.pos is not QtCore.QPointF(0, 0):
158             newpos = ev.posF()
159             change = newpos.y() - self.pos.y()
160             #log.debug("MixerNode.mouseReleaseEvent() change %s" % (str(change)))
161             self.setValue( self.tmpvalue - math.copysign(pow(abs(change), 2), change) )
162             self.pos = QtCore.QPointF(0, 0)
163             del self.tmpvalue
164             ev.accept()
165
166     def paintEvent(self, ev):
167         p = QtGui.QPainter(self)
168         rect = self.rect()
169         v = self.value()
170         if (self.mute_action!=None and self.mute_action.isChecked()):
171             color = QtGui.QColor(64, 64, 64)
172         else:
173             color = self.bgcolors.getColor(v)
174         p.fillRect(rect, color)
175
176         if self.small:
177             return
178
179         if color.valueF() < 0.6:
180             p.setPen(QtGui.QColor(255, 255, 255))
181         else:
182             p.setPen(QtGui.QColor(0, 0, 0))
183         lv=decimal.Decimal('-Infinity')
184         if v != 0:
185             lv = 20 * math.log10(v * 1.0 / math.pow(2,14))
186             #log.debug("new value is %g dB" % lv)
187         text = "%.2g dB" % lv
188         if v == 0:
189             text = "-ꝏ dB"
190         p.drawText(rect, Qt.Qt.AlignCenter, QtCore.QString.fromUtf8(text))
191         if (self.inv_action!=None and self.inv_action.isChecked()):
192             p.drawText(rect, Qt.Qt.AlignLeft|Qt.Qt.AlignTop, QtCore.QString.fromUtf8(" ϕ"))
193
194     def internalValueChanged(self, value):
195         #log.debug("MixerNode.internalValueChanged( %i )" % value)
196         if value is not 0:
197             dB = 20 * math.log10(value / math.pow(2,14))
198             if self.spinbox.value() is not dB:
199                 self.spinbox.setValue(dB)
200         self.emit(QtCore.SIGNAL("valueChanged"), (self.input, self.output, value) )
201         self.update()
202
203     def setSmall(self, small):
204         self.small = small
205         if small:
206             self.setMinimumSize(10, 10)
207         else:
208             fontmetrics = self.fontMetrics()
209             self.setMinimumSize(fontmetrics.boundingRect("-0.0 dB").size()*1.1)
210         self.update()
211
212
213 class MixerChannel(QtGui.QWidget):
214     def __init__(self, number, parent=None, name=""):
215         QtGui.QWidget.__init__(self, parent)
216         layout = QtGui.QGridLayout(self)
217         self.number = number
218         if name != "":
219             name = "\n(%s)" % name
220         self.name = name
221         self.lbl = QtGui.QLabel(self)
222         self.lbl.setAlignment(Qt.Qt.AlignCenter)
223         layout.addWidget(self.lbl, 0, 0, 1, 2)
224         self.hideChannel(False)
225
226         self.setContextMenuPolicy(Qt.Qt.ActionsContextMenu)
227
228         action = QtGui.QAction("Make this channel small", self)
229         action.setCheckable(True)
230         self.connect(action, QtCore.SIGNAL("triggered(bool)"), self.hideChannel)
231         self.addAction(action)
232
233     def hideChannel(self, hide):
234         if hide:
235             self.lbl.setText("%i" % self.number);
236         else:
237             self.lbl.setText("Ch. %i%s" % (self.number+1, self.name))
238         self.emit(QtCore.SIGNAL("hide"), self.number, hide)
239         self.update()
240
241
242
243 class MatrixMixer(QtGui.QWidget):
244     def __init__(self, servername, basepath, parent=None, sliderMaxValue=-1, mutespath=None, invertspath=None):
245         QtGui.QWidget.__init__(self, parent)
246         self.bus = dbus.SessionBus()
247         self.dev = self.bus.get_object(servername, basepath)
248         self.interface = dbus.Interface(self.dev, dbus_interface="org.ffado.Control.Element.MatrixMixer")
249
250         self.mutes_dev = None
251         self.mutes_interface = None
252         if (mutespath != None):
253             self.mutes_dev = self.bus.get_object(servername, mutespath)
254             self.mutes_interface = dbus.Interface(self.mutes_dev, dbus_interface="org.ffado.Control.Element.MatrixMixer")
255
256         self.inverts_dev = None
257         self.inverts_interface = None
258         if (invertspath != None):
259             self.inverts_dev = self.bus.get_object(servername, invertspath)
260             self.inverts_interface = dbus.Interface(self.inverts_dev, dbus_interface="org.ffado.Control.Element.MatrixMixer")
261
262         #palette = self.palette()
263         #palette.setColor(QtGui.QPalette.Window, palette.color(QtGui.QPalette.Window).darker());
264         #self.setPalette(palette)
265
266         cols = self.interface.getColCount()
267         rows = self.interface.getRowCount()
268         log.debug("Mixer has %i rows and %i columns" % (rows, cols))
269
270         layout = QtGui.QGridLayout(self)
271         self.setLayout(layout)
272
273         self.rowHeaders = []
274         self.columnHeaders = []
275         self.items = []
276
277         # Add row/column headers, but only if there's more than one
278         # row/column
279         if (cols > 1):
280             for i in range(cols):
281                 ch = MixerChannel(i, self, self.interface.getColName(i))
282                 self.connect(ch, QtCore.SIGNAL("hide"), self.hideColumn)
283                 layout.addWidget(ch, 0, i+1)
284                 self.columnHeaders.append( ch )
285             layout.setRowStretch(0, 0)
286             layout.setRowStretch(1, 10)
287         if (rows > 1):
288             for i in range(rows):
289                 ch = MixerChannel(i, self, self.interface.getRowName(i))
290                 self.connect(ch, QtCore.SIGNAL("hide"), self.hideRow)
291                 layout.addWidget(ch, i+1, 0)
292                 self.rowHeaders.append( ch )
293
294         # Add node-widgets
295         for i in range(rows):
296             self.items.append([])
297             for j in range(cols):
298                 mute_value = None
299                 if (self.mutes_interface != None):
300                     mute_value = self.mutes_interface.getValue(i,j)
301                 inv_value = None
302                 if (self.inverts_interface != None):
303                     inv_value = self.inverts_interface.getValue(i,j)
304                 node = MixerNode(j, i, self.interface.getValue(i,j), sliderMaxValue, mute_value, inv_value, self)
305                 self.connect(node, QtCore.SIGNAL("valueChanged"), self.valueChanged)
306                 layout.addWidget(node, i+1, j+1)
307                 self.items[i].append(node)
308
309         self.hiddenRows = []
310         self.hiddenCols = []
311
312
313     def checkVisibilities(self):
314         for x in range(len(self.items)):
315             for y in range(len(self.items[x])):
316                 self.items[x][y].setSmall(
317                         (x in self.hiddenRows)
318                         | (y in self.hiddenCols)
319                         )
320
321
322     def hideColumn(self, column, hide):
323         if hide:
324             self.hiddenCols.append(column)
325         else:
326             self.hiddenCols.remove(column)
327         self.checkVisibilities()
328     def hideRow(self, row, hide):
329         if hide:
330             self.hiddenRows.append(row)
331         else:
332             self.hiddenRows.remove(row)
333         self.checkVisibilities()
334
335     def valueChanged(self, n):
336         #log.debug("MatrixNode.valueChanged( %s )" % str(n))
337         self.interface.setValue(n[1], n[0], n[2])
338
339 #
340 # vim: et ts=4 sw=4 fileencoding=utf8
Note: See TracBrowser for help on using the browser.