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, 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 |
def directValues(self,text): |
---|
99 |
#log.debug("MixerNode.directValues( '%s' )" % text) |
---|
100 |
text = text.split(" ")[0].replace(",",".") |
---|
101 |
n = pow(10, (float(text)/20)) * pow(2,14) |
---|
102 |
#log.debug("%g" % n) |
---|
103 |
self.setValue(n) |
---|
104 |
|
---|
105 |
def mousePressEvent(self, ev): |
---|
106 |
if ev.buttons() & Qt.Qt.LeftButton: |
---|
107 |
self.pos = ev.posF() |
---|
108 |
self.tmpvalue = self.value() |
---|
109 |
ev.accept() |
---|
110 |
#log.debug("MixerNode.mousePressEvent() %s" % str(self.pos)) |
---|
111 |
|
---|
112 |
def mouseMoveEvent(self, ev): |
---|
113 |
if hasattr(self, "tmpvalue") and self.pos is not QtCore.QPointF(0, 0): |
---|
114 |
newpos = ev.posF() |
---|
115 |
change = newpos.y() - self.pos.y() |
---|
116 |
#log.debug("MixerNode.mouseReleaseEvent() change %s" % (str(change))) |
---|
117 |
self.setValue( self.tmpvalue - math.copysign(pow(abs(change), 2), change) ) |
---|
118 |
ev.accept() |
---|
119 |
|
---|
120 |
def mouseReleaseEvent(self, ev): |
---|
121 |
if hasattr(self, "tmpvalue") and self.pos is not QtCore.QPointF(0, 0): |
---|
122 |
newpos = ev.posF() |
---|
123 |
change = newpos.y() - self.pos.y() |
---|
124 |
#log.debug("MixerNode.mouseReleaseEvent() change %s" % (str(change))) |
---|
125 |
self.setValue( self.tmpvalue - math.copysign(pow(abs(change), 2), change) ) |
---|
126 |
self.pos = QtCore.QPointF(0, 0) |
---|
127 |
del self.tmpvalue |
---|
128 |
ev.accept() |
---|
129 |
|
---|
130 |
def paintEvent(self, ev): |
---|
131 |
p = QtGui.QPainter(self) |
---|
132 |
rect = self.rect() |
---|
133 |
v = self.value() |
---|
134 |
color = self.bgcolors.getColor(v) |
---|
135 |
p.fillRect(rect, color) |
---|
136 |
|
---|
137 |
if self.small: |
---|
138 |
return |
---|
139 |
|
---|
140 |
if color.valueF() < 0.6: |
---|
141 |
p.setPen(QtGui.QColor(255, 255, 255)) |
---|
142 |
else: |
---|
143 |
p.setPen(QtGui.QColor(0, 0, 0)) |
---|
144 |
lv=decimal.Decimal('-Infinity') |
---|
145 |
if v != 0: |
---|
146 |
lv = 20 * math.log10(v * 1.0 / math.pow(2,14)) |
---|
147 |
#log.debug("new value is %g dB" % lv) |
---|
148 |
text = "%.2g dB" % lv |
---|
149 |
if v == 0: |
---|
150 |
text = "-ꝏ dB" |
---|
151 |
p.drawText(rect, Qt.Qt.AlignCenter, QtCore.QString.fromUtf8(text)) |
---|
152 |
|
---|
153 |
def internalValueChanged(self, value): |
---|
154 |
#log.debug("MixerNode.internalValueChanged( %i )" % value) |
---|
155 |
if value is not 0: |
---|
156 |
dB = 20 * math.log10(value / math.pow(2,14)) |
---|
157 |
if self.spinbox.value() is not dB: |
---|
158 |
self.spinbox.setValue(dB) |
---|
159 |
self.emit(QtCore.SIGNAL("valueChanged"), (self.input, self.output, value) ) |
---|
160 |
self.update() |
---|
161 |
|
---|
162 |
def setSmall(self, small): |
---|
163 |
self.small = small |
---|
164 |
if small: |
---|
165 |
self.setMinimumSize(10, 10) |
---|
166 |
else: |
---|
167 |
fontmetrics = self.fontMetrics() |
---|
168 |
self.setMinimumSize(fontmetrics.boundingRect("-0.0 dB").size()*1.1) |
---|
169 |
self.update() |
---|
170 |
|
---|
171 |
|
---|
172 |
class MixerChannel(QtGui.QWidget): |
---|
173 |
def __init__(self, number, parent=None, name=""): |
---|
174 |
QtGui.QWidget.__init__(self, parent) |
---|
175 |
layout = QtGui.QGridLayout(self) |
---|
176 |
self.number = number |
---|
177 |
if name != "": |
---|
178 |
name = "\n(%s)" % name |
---|
179 |
self.name = name |
---|
180 |
self.lbl = QtGui.QLabel(self) |
---|
181 |
self.lbl.setAlignment(Qt.Qt.AlignCenter) |
---|
182 |
layout.addWidget(self.lbl, 0, 0, 1, 2) |
---|
183 |
self.hideChannel(False) |
---|
184 |
|
---|
185 |
self.setContextMenuPolicy(Qt.Qt.ActionsContextMenu) |
---|
186 |
|
---|
187 |
action = QtGui.QAction("Make this channel small", self) |
---|
188 |
action.setCheckable(True) |
---|
189 |
self.connect(action, QtCore.SIGNAL("triggered(bool)"), self.hideChannel) |
---|
190 |
self.addAction(action) |
---|
191 |
|
---|
192 |
def hideChannel(self, hide): |
---|
193 |
if hide: |
---|
194 |
self.lbl.setText("%i" % self.number); |
---|
195 |
else: |
---|
196 |
self.lbl.setText("Ch. %i%s" % (self.number, self.name)) |
---|
197 |
self.emit(QtCore.SIGNAL("hide"), self.number, hide) |
---|
198 |
self.update() |
---|
199 |
|
---|
200 |
|
---|
201 |
|
---|
202 |
class MatrixMixer(QtGui.QWidget): |
---|
203 |
def __init__(self, servername, basepath, parent=None, sliderMaxValue=-1): |
---|
204 |
QtGui.QWidget.__init__(self, parent) |
---|
205 |
self.bus = dbus.SessionBus() |
---|
206 |
self.dev = self.bus.get_object(servername, basepath) |
---|
207 |
self.interface = dbus.Interface(self.dev, dbus_interface="org.ffado.Control.Element.MatrixMixer") |
---|
208 |
|
---|
209 |
#palette = self.palette() |
---|
210 |
#palette.setColor(QtGui.QPalette.Window, palette.color(QtGui.QPalette.Window).darker()); |
---|
211 |
#self.setPalette(palette) |
---|
212 |
|
---|
213 |
rows = self.interface.getColCount() |
---|
214 |
cols = self.interface.getRowCount() |
---|
215 |
log.debug("Mixer has %i rows and %i columns" % (rows, cols)) |
---|
216 |
|
---|
217 |
layout = QtGui.QGridLayout(self) |
---|
218 |
self.setLayout(layout) |
---|
219 |
|
---|
220 |
self.rowHeaders = [] |
---|
221 |
self.columnHeaders = [] |
---|
222 |
self.items = [] |
---|
223 |
|
---|
224 |
# Add row/column headers |
---|
225 |
for i in range(cols): |
---|
226 |
ch = MixerChannel(i, self, self.interface.getColName(i)) |
---|
227 |
self.connect(ch, QtCore.SIGNAL("hide"), self.hideColumn) |
---|
228 |
layout.addWidget(ch, 0, i+1) |
---|
229 |
self.columnHeaders.append( ch ) |
---|
230 |
for i in range(rows): |
---|
231 |
ch = MixerChannel(i, self, self.interface.getRowName(i)) |
---|
232 |
self.connect(ch, QtCore.SIGNAL("hide"), self.hideRow) |
---|
233 |
layout.addWidget(ch, i+1, 0) |
---|
234 |
self.rowHeaders.append( ch ) |
---|
235 |
|
---|
236 |
# Add node-widgets |
---|
237 |
for i in range(rows): |
---|
238 |
self.items.append([]) |
---|
239 |
for j in range(cols): |
---|
240 |
node = MixerNode(j, i, self.interface.getValue(i,j), sliderMaxValue, self) |
---|
241 |
self.connect(node, QtCore.SIGNAL("valueChanged"), self.valueChanged) |
---|
242 |
layout.addWidget(node, i+1, j+1) |
---|
243 |
self.items[i].append(node) |
---|
244 |
|
---|
245 |
self.hiddenRows = [] |
---|
246 |
self.hiddenCols = [] |
---|
247 |
|
---|
248 |
|
---|
249 |
def checkVisibilities(self): |
---|
250 |
for x in range(len(self.items)): |
---|
251 |
for y in range(len(self.items[x])): |
---|
252 |
self.items[x][y].setSmall( |
---|
253 |
(x in self.hiddenRows) |
---|
254 |
| (y in self.hiddenCols) |
---|
255 |
) |
---|
256 |
|
---|
257 |
|
---|
258 |
def hideColumn(self, column, hide): |
---|
259 |
if hide: |
---|
260 |
self.hiddenCols.append(column) |
---|
261 |
else: |
---|
262 |
self.hiddenCols.remove(column) |
---|
263 |
self.checkVisibilities() |
---|
264 |
def hideRow(self, row, hide): |
---|
265 |
if hide: |
---|
266 |
self.hiddenRows.append(row) |
---|
267 |
else: |
---|
268 |
self.hiddenRows.remove(row) |
---|
269 |
self.checkVisibilities() |
---|
270 |
|
---|
271 |
def valueChanged(self, n): |
---|
272 |
#log.debug("MatrixNode.valueChanged( %s )" % str(n)) |
---|
273 |
self.interface.setValue(n[1], n[0], n[2]) |
---|
274 |
|
---|
275 |
# |
---|
276 |
# vim: et ts=4 sw=4 fileencoding=utf8 |
---|