1 |
# coding=utf8 |
---|
2 |
# |
---|
3 |
# Copyright (C) 2009 by Arnold Krille |
---|
4 |
# 2013 by Philippe Carriere |
---|
5 |
# |
---|
6 |
# This file is part of FFADO |
---|
7 |
# FFADO = Free FireWire (pro-)audio drivers for Linux |
---|
8 |
# |
---|
9 |
# This program is free software: you can redistribute it and/or modify |
---|
10 |
# it under the terms of the GNU General Public License as published by |
---|
11 |
# the Free Software Foundation, either version 2 of the License, or |
---|
12 |
# (at your option) version 3 of the License. |
---|
13 |
# |
---|
14 |
# This program is distributed in the hope that it will be useful, |
---|
15 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
16 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
---|
17 |
# GNU General Public License for more details. |
---|
18 |
# |
---|
19 |
# You should have received a copy of the GNU General Public License |
---|
20 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
---|
21 |
# |
---|
22 |
|
---|
23 |
# from PyQt4 import QtGui, QtCore, Qt |
---|
24 |
# from PyQt4.QtCore import pyqtSignal |
---|
25 |
# from PyQt4.QtGui import QColor, QAbstractSlider, QDoubleSpinBox, QWidgetAction |
---|
26 |
# from PyQt4.QtGui import QAction, QPainter, QWidget, QGridLayout, QLabel |
---|
27 |
# from PyQt4.QtGui import QLayout, QSlider, QLineEdit, QPalette |
---|
28 |
# from PyQt4.QtGui import QVBoxLayout, QHBoxLayout, QTabWidget, QToolBar |
---|
29 |
# from PyQt4.QtGui import QComboBox, QScrollArea, QPushButton, QSizePolicy |
---|
30 |
from ffado.import_pyqt import * |
---|
31 |
|
---|
32 |
import dbus, math, decimal |
---|
33 |
|
---|
34 |
import ffado.config |
---|
35 |
|
---|
36 |
import logging |
---|
37 |
log = logging.getLogger("matrixmixer") |
---|
38 |
|
---|
39 |
def toDBvalue(value): |
---|
40 |
n = int(value) |
---|
41 |
c2p14 = 16384.0 |
---|
42 |
if n > 164: |
---|
43 |
return round(20.0*math.log10(float(n)/c2p14), 2) |
---|
44 |
else: |
---|
45 |
return -40.0 |
---|
46 |
|
---|
47 |
def fromDBvalue(value): |
---|
48 |
v = float(value) |
---|
49 |
c2p14 = 16384.0 |
---|
50 |
if (v > -40): |
---|
51 |
return int(round(math.pow(10.0, (value/20.0))*c2p14, 0)) |
---|
52 |
else: |
---|
53 |
return 0 |
---|
54 |
|
---|
55 |
# v, vl, vr in linear scale |
---|
56 |
# b range in [-1:1] |
---|
57 |
def getVolumeLeft(v, b): |
---|
58 |
return int(round(0.5*v*(1.0-b),0)) |
---|
59 |
def getVolumeRight(v, b): |
---|
60 |
return v-int(round(0.5*v*(1.0-b),0)) |
---|
61 |
def getStereoVolume(vl, vr): |
---|
62 |
return int(round(vl+vr,0)) |
---|
63 |
def getStereoBalance(vl, vr): |
---|
64 |
if ((vl+vr) == 0): |
---|
65 |
return 0 |
---|
66 |
else: |
---|
67 |
return round(float(vr-vl)/float(vr+vl),2) |
---|
68 |
|
---|
69 |
class ColorForNumber: |
---|
70 |
def __init__(self): |
---|
71 |
self.colors = dict() |
---|
72 |
|
---|
73 |
def addColor(self, n, color): |
---|
74 |
self.colors[n] = color |
---|
75 |
|
---|
76 |
def getColor(self, n): |
---|
77 |
#print( "ColorForNumber.getColor( %g )" % (n) ) |
---|
78 |
keys = sorted(self.colors.keys()) |
---|
79 |
low = keys[-1] |
---|
80 |
high = keys[-1] |
---|
81 |
for i in range(len(keys)-1): |
---|
82 |
if keys[i] <= n and keys[i+1] > n: |
---|
83 |
low = keys[i] |
---|
84 |
high = keys[i+1] |
---|
85 |
#print( "%g is between %g and %g" % (n, low, high) ) |
---|
86 |
f = 0 |
---|
87 |
if high != low: |
---|
88 |
f = (n-low) / (high-low) |
---|
89 |
lc = self.colors[low] |
---|
90 |
hc = self.colors[high] |
---|
91 |
return QColor( |
---|
92 |
int((1-f)*lc.red() + f*hc.red()), |
---|
93 |
int((1-f)*lc.green() + f*hc.green()), |
---|
94 |
int((1-f)*lc.blue() + f*hc.blue()) ) |
---|
95 |
|
---|
96 |
class BckgrdColorForNumber(ColorForNumber): |
---|
97 |
def __init__(self): |
---|
98 |
ColorForNumber.__init__(self) |
---|
99 |
self.addColor( 0.0, QColor( 0, 0, 0)) |
---|
100 |
self.addColor( 1.0, QColor( 0, 0, 128)) |
---|
101 |
self.addColor( math.pow(2,6), QColor( 0, 255, 0)) |
---|
102 |
self.addColor( math.pow(2,14), QColor(255, 255, 0)) |
---|
103 |
self.addColor(math.pow(2,16)-1, QColor(255, 0, 0)) |
---|
104 |
|
---|
105 |
def getFrgdColor(self, color): |
---|
106 |
if color.valueF() < 0.6: |
---|
107 |
return QColor(255, 255, 255) |
---|
108 |
else: |
---|
109 |
return QColor(0, 0, 0) |
---|
110 |
|
---|
111 |
class MixerNode(QAbstractSlider): |
---|
112 |
nodeValueChanged = pyqtSignal(tuple) |
---|
113 |
def __init__(self, input, output, value, max, muted, inverted, parent, matrix_obj): |
---|
114 |
QAbstractSlider.__init__(self, parent) |
---|
115 |
#log.debug("MixerNode.__init__( %i, %i, %i, %i, %s )" % (input, output, value, max, str(parent)) ) |
---|
116 |
|
---|
117 |
# Store a direct link back to the underlying matrix object so the mute |
---|
118 |
# and invert interfaces can be easily found. By the time the matrix |
---|
119 |
# has been set into the full widget hierarchy, its parent is unlikely |
---|
120 |
# to still be the top-level matrix object. |
---|
121 |
self.matrix_obj = matrix_obj; |
---|
122 |
|
---|
123 |
self.pos = QtCore.QPointF(0, 0) |
---|
124 |
self.input = input |
---|
125 |
self.output = output |
---|
126 |
self.setOrientation(Qt.Vertical) |
---|
127 |
if max == -1: |
---|
128 |
max = pow(2, 16)-1 |
---|
129 |
self.setRange(0, max) |
---|
130 |
self.setValue(int(value)) |
---|
131 |
self.valueChanged.connect(self.internalValueChanged) |
---|
132 |
|
---|
133 |
self.setSmall(False) |
---|
134 |
|
---|
135 |
self.bgcolors = BckgrdColorForNumber() |
---|
136 |
|
---|
137 |
self.setContextMenuPolicy(Qt.ActionsContextMenu) |
---|
138 |
self.mapper = QtCore.QSignalMapper(self) |
---|
139 |
self.mapper.mapped['QString'].connect(self.directValues) |
---|
140 |
|
---|
141 |
self.spinbox = QDoubleSpinBox(self) |
---|
142 |
self.spinbox.setRange(-40, 12) |
---|
143 |
self.spinbox.setSuffix(" dB") |
---|
144 |
if value != 0: |
---|
145 |
self.spinbox.setValue(toDBvalue(value)) |
---|
146 |
|
---|
147 |
self.spinbox.valueChanged.connect(self.directValues) |
---|
148 |
action = QWidgetAction(self) |
---|
149 |
action.setDefaultWidget(self.spinbox) |
---|
150 |
self.addAction(action) |
---|
151 |
|
---|
152 |
for text in ["3 dB", "0 dB", "-3 dB", "-20 dB", "-inf dB"]: |
---|
153 |
action = QAction(text, self) |
---|
154 |
action.triggered.connect(self.mapper.map) |
---|
155 |
self.mapper.setMapping(action, text) |
---|
156 |
self.addAction(action) |
---|
157 |
|
---|
158 |
# Only show the mute menu item if a value has been supplied |
---|
159 |
self.mute_action = None |
---|
160 |
if (muted != None): |
---|
161 |
action = QAction(text, self) |
---|
162 |
action.setSeparator(True) |
---|
163 |
self.addAction(action) |
---|
164 |
self.mute_action = QAction("Mute", self) |
---|
165 |
self.mute_action.setCheckable(True) |
---|
166 |
self.mute_action.setChecked(bool(muted)) |
---|
167 |
self.mute_action.triggered.connect(self.mapper.map) |
---|
168 |
self.mapper.setMapping(self.mute_action, "Mute") |
---|
169 |
self.addAction(self.mute_action) |
---|
170 |
|
---|
171 |
# Similarly, only show a phase inversion menu item if in use |
---|
172 |
self.inv_action = None |
---|
173 |
if (inverted != None): |
---|
174 |
if (muted == None): |
---|
175 |
action = QAction(text, self) |
---|
176 |
action.setSeparator(True) |
---|
177 |
self.addAction(action) |
---|
178 |
self.inv_action = QAction("Invert", self) |
---|
179 |
self.inv_action.setCheckable(True) |
---|
180 |
self.inv_action.setChecked(bool(inverted)) |
---|
181 |
self.inv_action.triggered.connect(self.mapper.map) |
---|
182 |
self.mapper.setMapping(self.inv_action, "Invert") |
---|
183 |
self.addAction(self.inv_action) |
---|
184 |
|
---|
185 |
def directValues(self,text): |
---|
186 |
#log.debug("MixerNode.directValues( '%s' )" % text) |
---|
187 |
if text == "Mute": |
---|
188 |
#log.debug("Mute %d" % self.mute_action.isChecked()) |
---|
189 |
self.update() |
---|
190 |
self.matrix_obj.mutes_interface.setValue(self.output, self.input, self.mute_action.isChecked()) |
---|
191 |
elif text == "Invert": |
---|
192 |
#log.debug("Invert %d" % self.inv_action.isChecked()) |
---|
193 |
self.update() |
---|
194 |
self.matrix_obj.inverts_interface.setValue(self.output, self.input, self.inv_action.isChecked()) |
---|
195 |
else: |
---|
196 |
text = str(text).split(" ")[0].replace(",",".") |
---|
197 |
n = fromDBvalue(float(text)) |
---|
198 |
#log.debug(" linear value: %g" % n) |
---|
199 |
self.setValue(n) |
---|
200 |
|
---|
201 |
def mousePressEvent(self, ev): |
---|
202 |
if ev.buttons() & Qt.LeftButton: |
---|
203 |
self.pos = ev.posF() if ffado_pyqt_version == 4 else ev.localPos() |
---|
204 |
self.tmpvalue = self.value() |
---|
205 |
ev.accept() |
---|
206 |
#log.debug("MixerNode.mousePressEvent() %s" % str(self.pos)) |
---|
207 |
|
---|
208 |
def mouseMoveEvent(self, ev): |
---|
209 |
if hasattr(self, "tmpvalue") and self.pos is not QtCore.QPointF(0, 0): |
---|
210 |
newpos = ev.posF() if ffado_pyqt_version == 4 else ev.localPos() |
---|
211 |
change = newpos.y() - self.pos.y() |
---|
212 |
#log.debug("MixerNode.mouseReleaseEvent() change %s" % (str(change))) |
---|
213 |
self.setValue( |
---|
214 |
int(self.tmpvalue - math.copysign(pow(abs(change), 2), change)) |
---|
215 |
) |
---|
216 |
ev.accept() |
---|
217 |
|
---|
218 |
def mouseReleaseEvent(self, ev): |
---|
219 |
if hasattr(self, "tmpvalue") and self.pos is not QtCore.QPointF(0, 0): |
---|
220 |
newpos = ev.posF() if ffado_pyqt_version == 4 else ev.localPos() |
---|
221 |
change = newpos.y() - self.pos.y() |
---|
222 |
#log.debug("MixerNode.mouseReleaseEvent() change %s" % (str(change))) |
---|
223 |
self.setValue( |
---|
224 |
int(self.tmpvalue - math.copysign(pow(abs(change), 2), change)) |
---|
225 |
) |
---|
226 |
self.pos = QtCore.QPointF(0, 0) |
---|
227 |
del self.tmpvalue |
---|
228 |
ev.accept() |
---|
229 |
|
---|
230 |
# Wheel event is mainly for scrolling inside the mixer window |
---|
231 |
# Additionnaly press Control key for wheel controling the values |
---|
232 |
def wheelEvent (self, ev): |
---|
233 |
if (ev.modifiers() & Qt.ControlModifier): |
---|
234 |
tmpvalue = self.value() |
---|
235 |
change = ev.delta()/8 |
---|
236 |
self.setValue( |
---|
237 |
int(tmpvalue + math.copysign(pow(abs(change), 2), change)) |
---|
238 |
) |
---|
239 |
ev.accept() |
---|
240 |
else: |
---|
241 |
ev.ignore() |
---|
242 |
|
---|
243 |
def paintEvent(self, ev): |
---|
244 |
p = QPainter(self) |
---|
245 |
rect = self.rect() |
---|
246 |
v = self.value() |
---|
247 |
if (self.mute_action!=None and self.mute_action.isChecked()): |
---|
248 |
color = QColor(64, 64, 64) |
---|
249 |
else: |
---|
250 |
color = self.bgcolors.getColor(v) |
---|
251 |
p.fillRect(rect, color) |
---|
252 |
|
---|
253 |
if self.small: |
---|
254 |
return |
---|
255 |
|
---|
256 |
p.setPen(self.bgcolors.getFrgdColor(color)) |
---|
257 |
|
---|
258 |
lv=decimal.Decimal('-Infinity') |
---|
259 |
if v != 0: |
---|
260 |
lv = toDBvalue(v) |
---|
261 |
#log.debug("new value is %g dB" % lv) |
---|
262 |
text = "%.2g dB" % lv |
---|
263 |
if v == 0: |
---|
264 |
symb_inf = u"\u221E" |
---|
265 |
text = "-" + symb_inf + " dB" |
---|
266 |
if ffado_python3 or ffado_pyqt_version == 5: |
---|
267 |
# Python3 uses native python UTF strings rather than QString. |
---|
268 |
# This therefore appears to be the correct way to display this |
---|
269 |
# UTF8 string, but testing may prove otherwise. |
---|
270 |
p.drawText(rect, Qt.AlignCenter, text) |
---|
271 |
else: |
---|
272 |
p.drawText(rect, Qt.AlignCenter, QString.fromUtf8(text)) |
---|
273 |
if (self.inv_action!=None and self.inv_action.isChecked()): |
---|
274 |
if ffado_python3 or ffado_pyqt_version == 5: |
---|
275 |
# Refer to the comment about about Python UTF8 strings. |
---|
276 |
p.drawText(rect, Qt.AlignLeft|Qt.AlignTop, " ϕ") |
---|
277 |
else: |
---|
278 |
p.drawText(rect, Qt.AlignLeft|Qt.AlignTop, QString.fromUtf8(" ϕ")) |
---|
279 |
|
---|
280 |
def internalValueChanged(self, value): |
---|
281 |
#log.debug("MixerNode.internalValueChanged( %i )" % value) |
---|
282 |
if value != 0: |
---|
283 |
dB = toDBvalue(value) |
---|
284 |
if self.spinbox.value() is not dB: |
---|
285 |
self.spinbox.setValue(dB) |
---|
286 |
self.nodeValueChanged.emit((self.input, self.output, value)) |
---|
287 |
self.update() |
---|
288 |
|
---|
289 |
def setSmall(self, small): |
---|
290 |
self.small = small |
---|
291 |
if small: |
---|
292 |
self.setMinimumSize(10, 10) |
---|
293 |
else: |
---|
294 |
fontmetrics = self.fontMetrics() |
---|
295 |
self.setMinimumSize(fontmetrics.boundingRect("-0.0 dB").size()*1.1) |
---|
296 |
self.update() |
---|
297 |
|
---|
298 |
class MixerChannel(QWidget): |
---|
299 |
hide = pyqtSignal(int, bool, name='hide') |
---|
300 |
def __init__(self, number, parent=None, name="", smallFont=False): |
---|
301 |
QWidget.__init__(self, parent) |
---|
302 |
layout = QGridLayout(self) |
---|
303 |
self.number = number |
---|
304 |
self.name = name |
---|
305 |
self.lbl = QLabel(self) |
---|
306 |
self.lbl.setAlignment(Qt.AlignCenter) |
---|
307 |
if (smallFont): |
---|
308 |
font = self.lbl.font() |
---|
309 |
font.setPointSize(int(font.pointSize()/1.5 + 0.5)) |
---|
310 |
self.lbl.setFont(font) |
---|
311 |
layout.addWidget(self.lbl, 0, 0, 1, 2) |
---|
312 |
self.hideChannel(False) |
---|
313 |
|
---|
314 |
self.setContextMenuPolicy(Qt.ActionsContextMenu) |
---|
315 |
|
---|
316 |
action = QAction("Make this channel small", self) |
---|
317 |
action.setCheckable(True) |
---|
318 |
action.triggered.connect(self.hideChannel) |
---|
319 |
self.addAction(action) |
---|
320 |
|
---|
321 |
def hideChannel(self, hide): |
---|
322 |
if hide: |
---|
323 |
self.lbl.setText("%i" % (self.number+1)); |
---|
324 |
else: |
---|
325 |
self.lbl.setText(self.name) |
---|
326 |
self.hide.emit(self.number, hide) |
---|
327 |
self.update() |
---|
328 |
|
---|
329 |
# Matrix view widget |
---|
330 |
class MatrixControlView(QWidget): |
---|
331 |
valueChanged = pyqtSignal([tuple]) |
---|
332 |
def __init__(self, servername, basepath, parent=None, sliderMaxValue=-1, mutespath=None, invertspath=None, smallFont=False, shortname=False, shortcolname="Ch", shortrowname="Ch", transpose=False): |
---|
333 |
QWidget.__init__(self, parent) |
---|
334 |
|
---|
335 |
if not ffado.config.bypassdbus: |
---|
336 |
self.bus = dbus.SessionBus() |
---|
337 |
self.dev = self.bus.get_object(servername, basepath) |
---|
338 |
self.interface = dbus.Interface(self.dev, dbus_interface="org.ffado.Control.Element.MatrixMixer") |
---|
339 |
|
---|
340 |
self.transpose = transpose |
---|
341 |
if (transpose): |
---|
342 |
self.shortcolname = shortrowname |
---|
343 |
self.shortrowname = shortcolname |
---|
344 |
if ffado.config.bypassdbus: |
---|
345 |
self.cols = 2 |
---|
346 |
self.rows = 2 |
---|
347 |
else: |
---|
348 |
self.cols = self.interface.getRowCount() |
---|
349 |
self.rows = self.interface.getColCount() |
---|
350 |
else: |
---|
351 |
self.shortcolname = shortcolname |
---|
352 |
self.shortrowname = shortrowname |
---|
353 |
if ffado.config.bypassdbus: |
---|
354 |
self.cols = 2 |
---|
355 |
self.rows = 2 |
---|
356 |
else: |
---|
357 |
self.cols = self.interface.getColCount() |
---|
358 |
self.rows = self.interface.getRowCount() |
---|
359 |
|
---|
360 |
log.debug("Mixer has %i rows and %i columns" % (self.rows, self.cols)) |
---|
361 |
|
---|
362 |
self.mutes_dev = None |
---|
363 |
self.mutes_interface = None |
---|
364 |
if not ffado.config.bypassdbus and (mutespath != None): |
---|
365 |
self.mutes_dev = self.bus.get_object(servername, mutespath) |
---|
366 |
self.mutes_interface = dbus.Interface(self.mutes_dev, dbus_interface="org.ffado.Control.Element.MatrixMixer") |
---|
367 |
|
---|
368 |
self.inverts_dev = None |
---|
369 |
self.inverts_interface = None |
---|
370 |
if not ffado.config.bypassdbus and (invertspath != None): |
---|
371 |
self.inverts_dev = self.bus.get_object(servername, invertspath) |
---|
372 |
self.inverts_interface = dbus.Interface(self.inverts_dev, dbus_interface="org.ffado.Control.Element.MatrixMixer") |
---|
373 |
|
---|
374 |
layout = QGridLayout(self) |
---|
375 |
layout.setSizeConstraint(QLayout.SetNoConstraint); |
---|
376 |
self.setLayout(layout) |
---|
377 |
|
---|
378 |
self.rowHeaders = [] |
---|
379 |
self.columnHeaders = [] |
---|
380 |
self.items = [] |
---|
381 |
self.shortname = shortname |
---|
382 |
|
---|
383 |
# Add row/column headers, but only if there's more than one |
---|
384 |
# row/column |
---|
385 |
if (self.cols > 1): |
---|
386 |
for i in range(self.cols): |
---|
387 |
ch = MixerChannel(i, self, self.getColName(i, self.shortname), smallFont) |
---|
388 |
ch.hide.connect(self.hideColumn) |
---|
389 |
layout.addWidget(ch, 0, i+1) |
---|
390 |
self.columnHeaders.append( ch ) |
---|
391 |
layout.setRowStretch(0, 0) |
---|
392 |
layout.setRowStretch(1, 10) |
---|
393 |
if (self.rows > 1): |
---|
394 |
for i in range(self.rows): |
---|
395 |
ch = MixerChannel(i, self, self.getRowName(i, self.shortname), smallFont) |
---|
396 |
ch.hide.connect(self.hideRow) |
---|
397 |
layout.addWidget(ch, i+1, 0) |
---|
398 |
self.rowHeaders.append( ch ) |
---|
399 |
|
---|
400 |
# Add node-widgets |
---|
401 |
for i in range(self.rows): |
---|
402 |
self.items.append([]) |
---|
403 |
for j in range(self.cols): |
---|
404 |
if (transpose): |
---|
405 |
mute_value = None |
---|
406 |
if (self.mutes_interface != None): |
---|
407 |
mute_value = self.mutes_interface.getValue(j,i) |
---|
408 |
inv_value = None |
---|
409 |
if (self.inverts_interface != None): |
---|
410 |
inv_value = self.inverts_interface.getValue(j,i) |
---|
411 |
if ffado.config.bypassdbus: |
---|
412 |
val = 0 |
---|
413 |
else: |
---|
414 |
val = self.interface.getValue(j,i) |
---|
415 |
node = MixerNode(i, j, val, sliderMaxValue, mute_value, inv_value, self, self) |
---|
416 |
else: |
---|
417 |
mute_value = None |
---|
418 |
if (self.mutes_interface != None): |
---|
419 |
mute_value = self.mutes_interface.getValue(i,j) |
---|
420 |
inv_value = None |
---|
421 |
if (self.inverts_interface != None): |
---|
422 |
inv_value = self.inverts_interface.getValue(i,j) |
---|
423 |
if ffado.config.bypassdbus: |
---|
424 |
val = 0 |
---|
425 |
else: |
---|
426 |
val = self.interface.getValue(i,j) |
---|
427 |
node = MixerNode(j, i, val, sliderMaxValue, mute_value, inv_value, self, self) |
---|
428 |
if (smallFont): |
---|
429 |
font = node.font() |
---|
430 |
font.setPointSize(int(font.pointSize()/1.5 + 0.5)) |
---|
431 |
node.setFont(font) |
---|
432 |
self.nodeConnect(node) |
---|
433 |
layout.addWidget(node, i+1, j+1) |
---|
434 |
self.items[i].append(node) |
---|
435 |
|
---|
436 |
self.hiddenRows = [] |
---|
437 |
self.hiddenCols = [] |
---|
438 |
|
---|
439 |
def nodeConnect(self, node): |
---|
440 |
node.nodeValueChanged.connect(self.valueChangedFn) |
---|
441 |
|
---|
442 |
def nodeDisconnect(self, node): |
---|
443 |
node.nodeValueChanged.disconnect(self.valueChangedFn) |
---|
444 |
|
---|
445 |
def checkVisibilities(self): |
---|
446 |
for x in range(len(self.items)): |
---|
447 |
for y in range(len(self.items[x])): |
---|
448 |
self.items[x][y].setSmall( |
---|
449 |
(x in self.hiddenRows) |
---|
450 |
| (y in self.hiddenCols) |
---|
451 |
) |
---|
452 |
|
---|
453 |
def hideColumn(self, column, hide): |
---|
454 |
if hide: |
---|
455 |
self.hiddenCols.append(column) |
---|
456 |
else: |
---|
457 |
self.hiddenCols.remove(column) |
---|
458 |
self.checkVisibilities() |
---|
459 |
|
---|
460 |
def hideRow(self, row, hide): |
---|
461 |
if hide: |
---|
462 |
self.hiddenRows.append(row) |
---|
463 |
else: |
---|
464 |
self.hiddenRows.remove(row) |
---|
465 |
self.checkVisibilities() |
---|
466 |
|
---|
467 |
# Columns and rows |
---|
468 |
def getColName(self, i, shortname): |
---|
469 |
if ffado.config.bypassdbus: |
---|
470 |
return 'col ' + str(i) |
---|
471 |
if (self.transpose): |
---|
472 |
name = self.interface.getRowName(i) |
---|
473 |
else: |
---|
474 |
name = self.interface.getColName(i) |
---|
475 |
self.shortname = shortname |
---|
476 |
if (shortname or (name == '')): |
---|
477 |
number = " %d" % (i+1) |
---|
478 |
name = self.shortcolname + number |
---|
479 |
return name |
---|
480 |
|
---|
481 |
def getRowName(self, j, shortname): |
---|
482 |
if ffado.config.bypassdbus: |
---|
483 |
return 'row ' + str(j) |
---|
484 |
if (self.transpose): |
---|
485 |
name = self.interface.getColName(j) |
---|
486 |
else: |
---|
487 |
name = self.interface.getRowName(j) |
---|
488 |
self.shortname = shortname |
---|
489 |
if (shortname or (name == '')): |
---|
490 |
number = " %d" % (j+1) |
---|
491 |
name = self.shortrowname + number |
---|
492 |
return name |
---|
493 |
|
---|
494 |
def valueChangedFn(self, n): |
---|
495 |
#log.debug("MatrixNode.valueChangedFn( %s )" % str(n)) |
---|
496 |
if not ffado.config.bypassdbus: |
---|
497 |
self.interface.setValue(n[1], n[0], n[2]) |
---|
498 |
self.valueChanged.emit(n) |
---|
499 |
|
---|
500 |
# Update when routing is modified |
---|
501 |
def updateRouting(self): |
---|
502 |
if (self.cols > 1): |
---|
503 |
for i in range(self.cols): |
---|
504 |
last_name = self.columnHeaders[i].lbl.text() |
---|
505 |
col_name = self.getColName(i, self.shortname) |
---|
506 |
if last_name != col_name: |
---|
507 |
#log.debug("MatrixControlView.updateRouting( %s )" % str(col_name)) |
---|
508 |
self.columnHeaders[i].name = col_name |
---|
509 |
self.columnHeaders[i].lbl.setText(col_name) |
---|
510 |
|
---|
511 |
if (self.rows > 1): |
---|
512 |
for j in range(self.rows): |
---|
513 |
last_name = self.rowHeaders[j].lbl.text() |
---|
514 |
row_name = self.getRowName(j, self.shortname) |
---|
515 |
if last_name != row_name: |
---|
516 |
#log.debug("MatrixControlView.updateRouting( %s )" % str(row_name)) |
---|
517 |
self.rowHeaders[j].name = row_name |
---|
518 |
self.rowHeaders[j].lbl.setText(row_name) |
---|
519 |
|
---|
520 |
def updateValues(self, n): |
---|
521 |
nbitems = len(n) // 3 |
---|
522 |
for i in range(nbitems): |
---|
523 |
n_0 = n[3*i] |
---|
524 |
n_1 = n[3*i+1] |
---|
525 |
n_2 = n[3*i+2] |
---|
526 |
self.nodeDisconnect(self.items[n_0][n_1]) |
---|
527 |
self.items[n_0][n_1].setValue(n_2) |
---|
528 |
self.nodeConnect(self.items[n_0][n_1]) |
---|
529 |
|
---|
530 |
def refreshValues(self): |
---|
531 |
if ffado.config.bypassdbus: |
---|
532 |
return |
---|
533 |
for x in range(len(self.items)): |
---|
534 |
for y in range(len(self.items[x])): |
---|
535 |
val = self.interface.getValue(x,y) |
---|
536 |
if (self.transpose): |
---|
537 |
self.items[y][x].setValue(int(val)) |
---|
538 |
self.items[y][x].internalValueChanged(val) |
---|
539 |
else: |
---|
540 |
self.items[x][y].setValue(int(val)) |
---|
541 |
self.items[x][y].internalValueChanged(val) |
---|
542 |
|
---|
543 |
def saveSettings(self, indent): |
---|
544 |
matrixSaveString = [] |
---|
545 |
matrixSaveString.append('%s <row_number>\n' % indent) |
---|
546 |
matrixSaveString.append('%s %d\n' % (indent, self.rows)) |
---|
547 |
matrixSaveString.append('%s </row_number>\n' % indent) |
---|
548 |
matrixSaveString.append('%s <col_number>\n' % indent) |
---|
549 |
matrixSaveString.append('%s %d\n' % (indent, self.cols)) |
---|
550 |
matrixSaveString.append('%s </col_number>\n' % indent) |
---|
551 |
matrixSaveString.append('%s <coefficients>\n' % indent) |
---|
552 |
for i in range(self.rows): |
---|
553 |
line = '%s ' % indent |
---|
554 |
for j in range(self.cols): |
---|
555 |
line += '%d ' % self.interface.getValue(i,j) |
---|
556 |
line += '\n' |
---|
557 |
matrixSaveString.append(line) |
---|
558 |
matrixSaveString.append('%s </coefficients>\n' % indent) |
---|
559 |
if (self.mutes_interface != None): |
---|
560 |
matrixSaveString.append('%s <mutes>\n' % indent) |
---|
561 |
for i in range(self.rows): |
---|
562 |
line = '%s ' % indent |
---|
563 |
for j in range(self.cols): |
---|
564 |
line += '%d ' % self.mutes_interface.getValue(i,j) |
---|
565 |
line += '\n' |
---|
566 |
matrixSaveString.append(line) |
---|
567 |
matrixSaveString.append('%s </mutes>\n' % indent) |
---|
568 |
|
---|
569 |
if (self.inverts_interface != None): |
---|
570 |
matrixSaveString.append('%s <inverts>\n' % indent) |
---|
571 |
for i in range(self.rows): |
---|
572 |
line = '%s ' % indent |
---|
573 |
for j in range(self.cols): |
---|
574 |
line += '%d ' % self.inverts_interface.getValue(i,j) |
---|
575 |
line += '\n' |
---|
576 |
matrixSaveString.append(line) |
---|
577 |
matrixSaveString.append('%s </inverts>\n' % indent) |
---|
578 |
|
---|
579 |
return matrixSaveString |
---|
580 |
|
---|
581 |
def readSettings(self, readMatrixString, transpose_coeff): |
---|
582 |
if readMatrixString[0].find("<row_number>") == -1: |
---|
583 |
log.debug("Number of matrix rows must be specified") |
---|
584 |
return False |
---|
585 |
if readMatrixString[2].find("</row_number>") == -1: |
---|
586 |
log.debug("Non-conformal xml file") |
---|
587 |
return False |
---|
588 |
n_rows = int(readMatrixString[1]) |
---|
589 |
|
---|
590 |
if readMatrixString[3].find("<col_number>") == -1: |
---|
591 |
log.debug("Number of matrix columns must be specified") |
---|
592 |
return False |
---|
593 |
if readMatrixString[5].find("</col_number>") == -1: |
---|
594 |
log.debug("Non-conformal xml file") |
---|
595 |
return False |
---|
596 |
n_cols = int(readMatrixString[4]) |
---|
597 |
|
---|
598 |
if transpose_coeff: |
---|
599 |
if n_rows > self.cols: |
---|
600 |
n_rows = self.cols |
---|
601 |
if n_cols > self.rows: |
---|
602 |
n_cols = self.rows |
---|
603 |
else: |
---|
604 |
if n_rows > self.rows: |
---|
605 |
n_rows = self.rows |
---|
606 |
if n_cols > self.cols: |
---|
607 |
n_cols = self.cols |
---|
608 |
log.debug("Setting %d rows and %d columns coefficients" % (n_rows, n_cols)) |
---|
609 |
|
---|
610 |
try: |
---|
611 |
idxb = readMatrixString.index('<coefficients>') |
---|
612 |
idxe = readMatrixString.index('</coefficients>') |
---|
613 |
except Exception: |
---|
614 |
log.debug("No mixer matrix coefficients specified") |
---|
615 |
idxb = -1 |
---|
616 |
idxe = -1 |
---|
617 |
if idxb >= 0: |
---|
618 |
if idxe < idxb + n_rows + 1: |
---|
619 |
log.debug("Incoherent number of rows in coefficients") |
---|
620 |
return False |
---|
621 |
i = 0 |
---|
622 |
for s in readMatrixString[idxb+1:idxb + n_rows + 1]: |
---|
623 |
coeffs = s.split() |
---|
624 |
if len(coeffs) < n_cols: |
---|
625 |
log.debug("Incoherent number of columns in coefficients") |
---|
626 |
return False |
---|
627 |
j = 0 |
---|
628 |
for c in coeffs[0:n_cols]: |
---|
629 |
if transpose_coeff: |
---|
630 |
self.interface.setValue(j, i, int(c)) |
---|
631 |
else: |
---|
632 |
self.interface.setValue(i, j, int(c)) |
---|
633 |
j += 1 |
---|
634 |
i += 1 |
---|
635 |
del coeffs |
---|
636 |
|
---|
637 |
try: |
---|
638 |
idxb = readMatrixString.index('<mutes>') |
---|
639 |
idxe = readMatrixString.index('</mutes>') |
---|
640 |
except Exception: |
---|
641 |
log.debug("No mixer mute coefficients specified") |
---|
642 |
idxb = -1 |
---|
643 |
idxe = -1 |
---|
644 |
if idxb >= 0: |
---|
645 |
if idxe < idxb + n_rows + 1: |
---|
646 |
log.debug("Incoherent number of rows in mute") |
---|
647 |
return false |
---|
648 |
i = 0 |
---|
649 |
for s in readMatrixString[idxb+1:idxb + n_rows + 1]: |
---|
650 |
coeffs = s.split() |
---|
651 |
if len(coeffs) < n_cols: |
---|
652 |
log.debug("Incoherent number of columns in mute") |
---|
653 |
return false |
---|
654 |
j = 0 |
---|
655 |
for c in coeffs[0:n_cols]: |
---|
656 |
if transpose_coeff: |
---|
657 |
self.mutes_interface.setValue(j, i, int(c)) |
---|
658 |
else: |
---|
659 |
self.mutes_interface.setValue(i, j, int(c)) |
---|
660 |
j += 1 |
---|
661 |
i += 1 |
---|
662 |
del coeffs |
---|
663 |
|
---|
664 |
try: |
---|
665 |
idxb = readMatrixString.index('<inverts>') |
---|
666 |
idxe = readMatrixString.index('</inverts>') |
---|
667 |
except Exception: |
---|
668 |
log.debug("No mixer inverts coefficients specified") |
---|
669 |
idxb = -1 |
---|
670 |
idxe = -1 |
---|
671 |
if idxb >= 0: |
---|
672 |
if idxe < idxb + n_rows + 1: |
---|
673 |
log.debug("Incoherent number of rows in inverts") |
---|
674 |
return false |
---|
675 |
i = 0 |
---|
676 |
for s in readMatrixString[idxb+1:idxb + n_rows + 1]: |
---|
677 |
coeffs = s.split() |
---|
678 |
if len(coeffs) < n_cols: |
---|
679 |
log.debug("Incoherent number of columns in inverts") |
---|
680 |
return false |
---|
681 |
j = 0 |
---|
682 |
for c in coeffs[0:n_cols]: |
---|
683 |
if transpose_coeff: |
---|
684 |
self.inverts_interface.setValue(j, i, int(c)) |
---|
685 |
else: |
---|
686 |
self.inverts_interface.setValue(i, j, int(c)) |
---|
687 |
j += 1 |
---|
688 |
i += 1 |
---|
689 |
del coeffs |
---|
690 |
|
---|
691 |
self.refreshValues() |
---|
692 |
return True |
---|
693 |
|
---|
694 |
class VolumeSlider(QSlider): |
---|
695 |
sliderChanged = pyqtSignal(tuple) |
---|
696 |
def __init__(self, In, Out, value, parent): |
---|
697 |
QSlider.__init__(self, QtCore.Qt.Vertical, parent) |
---|
698 |
|
---|
699 |
self.setTickPosition(QSlider.TicksBothSides) |
---|
700 |
v_min = 10.0*toDBvalue(0) |
---|
701 |
v_max = 10.0*toDBvalue(65536) |
---|
702 |
self.setTickInterval(int((v_max-v_min)/10)) |
---|
703 |
self.setMinimum(int(v_min)) |
---|
704 |
self.setMaximum(int(v_max)) |
---|
705 |
self.setSingleStep(1) |
---|
706 |
self.sliderSetValue(value) |
---|
707 |
self.In = In |
---|
708 |
self.Out = Out |
---|
709 |
self.valueChanged.connect(self.sliderValueChanged) |
---|
710 |
|
---|
711 |
def sliderSetValue(self, value): |
---|
712 |
#log.debug("Volume slider value changed( %i )" % value) |
---|
713 |
v = 10.0*toDBvalue(value) |
---|
714 |
#log.debug("Volume slider value changed(dB: %g )" % (0.1*v)) |
---|
715 |
self.setValue(int(v)) |
---|
716 |
|
---|
717 |
def sliderReadValue(self, value): |
---|
718 |
return fromDBvalue(0.1*value) |
---|
719 |
|
---|
720 |
# Restore absolute value from DB |
---|
721 |
# Emit signal for further use, especially for matrix view |
---|
722 |
def sliderValueChanged(self, value): |
---|
723 |
value = fromDBvalue(0.1*value) |
---|
724 |
self.sliderChanged.emit((self.In, self.Out, value)) |
---|
725 |
self.update() |
---|
726 |
|
---|
727 |
|
---|
728 |
class VolumeSliderValueInfo(QLineEdit): |
---|
729 |
def __init__(self, In, Out, value, parent): |
---|
730 |
QLineEdit.__init__(self, parent) |
---|
731 |
|
---|
732 |
self.setReadOnly(True) |
---|
733 |
self.setAlignment(Qt.AlignCenter) |
---|
734 |
self.setAutoFillBackground(True) |
---|
735 |
self.setFrame(False) |
---|
736 |
|
---|
737 |
self.sliderSetMinimalDim() |
---|
738 |
|
---|
739 |
self.bgcolors = BckgrdColorForNumber() |
---|
740 |
|
---|
741 |
self.sliderSetValue(value) |
---|
742 |
|
---|
743 |
def sliderSetMinimalDim(self): |
---|
744 |
fontmetrics = self.fontMetrics() |
---|
745 |
self.setMinimumSize(fontmetrics.boundingRect("-00.0 dB").size()*1.1) |
---|
746 |
|
---|
747 |
def sliderSetValue(self, value): |
---|
748 |
color = self.bgcolors.getColor(value) |
---|
749 |
palette = self.palette() |
---|
750 |
palette.setColor(QPalette.Active, QPalette.Base, color) |
---|
751 |
palette.setColor(QPalette.Active, QPalette.Text, self.bgcolors.getFrgdColor(color)) |
---|
752 |
self.setPalette(palette) |
---|
753 |
|
---|
754 |
v = round(toDBvalue(value),1) |
---|
755 |
if (v > -40): |
---|
756 |
text = "%.1f dB" % v |
---|
757 |
else: |
---|
758 |
symb_inf = u"\u221E" |
---|
759 |
text = "-" + symb_inf + " dB" |
---|
760 |
|
---|
761 |
self.setText(text) |
---|
762 |
|
---|
763 |
class BalanceSlider(QSlider): |
---|
764 |
sliderChanged = pyqtSignal(tuple) |
---|
765 |
def __init__(self, In, Out, value, parent): |
---|
766 |
QSlider.__init__(self, QtCore.Qt.Horizontal, parent) |
---|
767 |
|
---|
768 |
v_min = -50 |
---|
769 |
v_max = 50 |
---|
770 |
self.setTickPosition(QSlider.TicksBothSides) |
---|
771 |
self.setTickInterval(int((v_max-v_min)/2)) |
---|
772 |
self.setMinimum(v_min) |
---|
773 |
self.setMaximum(v_max) |
---|
774 |
self.setSingleStep(1) |
---|
775 |
self.In = In |
---|
776 |
self.Out = Out |
---|
777 |
self.sliderSetValue(value) |
---|
778 |
self.valueChanged.connect(self.sliderValueChanged) |
---|
779 |
|
---|
780 |
def sliderSetValue(self, value): |
---|
781 |
#log.debug("Balance fader value set( %d, %d, %f )" % (self.In, self.Out, value)) |
---|
782 |
v = int(round(50.0*value, 2)) |
---|
783 |
self.setValue(v) |
---|
784 |
|
---|
785 |
def sliderReadValue(self): |
---|
786 |
return float(round(self.value()/50.0, 2)) |
---|
787 |
|
---|
788 |
def sliderValueChanged(self, value): |
---|
789 |
value = float(round(self.value()/50.0, 2)) |
---|
790 |
#log.debug("Balance fader value changed( %d, %d, %f )" % (self.In, self.Out, value)) |
---|
791 |
self.sliderChanged.emit((self.In, self.Out, value)) |
---|
792 |
|
---|
793 |
# Slider view widget |
---|
794 |
class SliderControlView(QWidget): |
---|
795 |
valueChanged = pyqtSignal(tuple) |
---|
796 |
def __init__(self, parent, servername, basepath, rule="Columns_are_inputs", shortname=False, shortinname="Ch", shortoutname="Ch", stereochannels = []): |
---|
797 |
QWidget.__init__(self, parent) |
---|
798 |
|
---|
799 |
if not ffado.config.bypassdbus: |
---|
800 |
self.bus = dbus.SessionBus() |
---|
801 |
self.dev = self.bus.get_object(servername, basepath) |
---|
802 |
self.interface = dbus.Interface(self.dev, dbus_interface="org.ffado.Control.Element.MatrixMixer") |
---|
803 |
|
---|
804 |
self.rule = rule |
---|
805 |
self.shortname = shortname |
---|
806 |
self.shortinname = shortinname |
---|
807 |
self.shortoutname = shortoutname |
---|
808 |
|
---|
809 |
self.stereochannels = stereochannels |
---|
810 |
|
---|
811 |
self.out = [] |
---|
812 |
self.nbIn = self.getNbIn() |
---|
813 |
self.nbOut = self.getNbOut() |
---|
814 |
self.outmatrix = [] |
---|
815 |
|
---|
816 |
k = 0 |
---|
817 |
for i in range(self.nbOut): |
---|
818 |
widget = QWidget(parent) |
---|
819 |
v_layout = QVBoxLayout(widget) |
---|
820 |
v_layout.setAlignment(Qt.AlignCenter) |
---|
821 |
widget.setLayout(v_layout) |
---|
822 |
self.out.append(widget) |
---|
823 |
|
---|
824 |
self.out[i].is_stereo = False |
---|
825 |
self.out[i].out_1 = k |
---|
826 |
self.outmatrix.append(i) |
---|
827 |
self.out[i].outname = "Out %d" % (k+1) |
---|
828 |
if k in self.stereochannels: |
---|
829 |
self.out[i].is_stereo = True |
---|
830 |
self.out[i].outname += "+%d" % (k+2) |
---|
831 |
self.outmatrix.append(i) |
---|
832 |
|
---|
833 |
self.out[i].lbl = [] |
---|
834 |
|
---|
835 |
# Mixer/Out info label |
---|
836 |
if (self.nbOut > 1): |
---|
837 |
lbl = QLabel(widget) |
---|
838 |
lbl.setText(self.getOutName(i, self.shortname)) |
---|
839 |
lbl.setAlignment(Qt.AlignCenter) |
---|
840 |
v_layout.addWidget(lbl) |
---|
841 |
self.out[i].lbl.append(lbl) |
---|
842 |
|
---|
843 |
h_layout_wid = QWidget(widget) |
---|
844 |
h_layout = QHBoxLayout(h_layout_wid) |
---|
845 |
h_layout.setAlignment(Qt.AlignCenter) |
---|
846 |
h_layout_wid.setLayout(h_layout) |
---|
847 |
v_layout.addWidget(h_layout_wid) |
---|
848 |
self.out[i].volume = [] |
---|
849 |
self.out[i].svl = [] |
---|
850 |
self.out[i].balance = [] |
---|
851 |
|
---|
852 |
for j in range(self.nbIn): |
---|
853 |
h_v_layout_wid = QWidget(h_layout_wid) |
---|
854 |
h_v_layout = QVBoxLayout(h_v_layout_wid) |
---|
855 |
h_v_layout.setAlignment(Qt.AlignCenter) |
---|
856 |
h_v_layout_wid.setLayout(h_v_layout) |
---|
857 |
h_layout.addWidget(h_v_layout_wid) |
---|
858 |
|
---|
859 |
# Mixer/In info label |
---|
860 |
if (self.nbIn > 1): |
---|
861 |
lbl = QLabel(h_v_layout_wid) |
---|
862 |
lbl.setText(self.getInName(j, self.shortname)) |
---|
863 |
lbl.setAlignment(Qt.AlignCenter) |
---|
864 |
h_v_layout.addWidget(lbl) |
---|
865 |
self.out[i].lbl.append(lbl) |
---|
866 |
|
---|
867 |
h_v_h_layout_wid = QWidget(h_v_layout_wid) |
---|
868 |
h_v_h_layout = QHBoxLayout(h_v_h_layout_wid) |
---|
869 |
h_v_h_layout.setAlignment(Qt.AlignCenter) |
---|
870 |
h_v_h_layout_wid.setLayout(h_v_h_layout) |
---|
871 |
h_v_layout.addWidget(h_v_h_layout_wid) |
---|
872 |
|
---|
873 |
volume = VolumeSlider(j, i, self.getVolumeValue(j,i), h_v_h_layout_wid) |
---|
874 |
h_v_h_layout.addWidget(volume) |
---|
875 |
self.out[i].volume.append(volume) |
---|
876 |
self.volumeConnect(volume) |
---|
877 |
|
---|
878 |
# Volume slider info |
---|
879 |
svl = VolumeSliderValueInfo(j, i, self.getVolumeValue(j,i), h_v_layout_wid) |
---|
880 |
h_v_layout.addWidget(svl) |
---|
881 |
self.out[i].svl.append(svl) |
---|
882 |
|
---|
883 |
# Balance fader |
---|
884 |
if self.out[i].is_stereo: |
---|
885 |
balance = BalanceSlider(j, i, self.getBalanceValue(j,i), h_v_layout_wid) |
---|
886 |
h_v_layout.addWidget(balance) |
---|
887 |
self.out[i].balance.append(balance) |
---|
888 |
self.balanceConnect(balance) |
---|
889 |
k += 1 |
---|
890 |
if self.out[i].is_stereo: |
---|
891 |
k += 1 |
---|
892 |
|
---|
893 |
def volumeConnect(self, volume): |
---|
894 |
volume.sliderChanged.connect(self.valueChangedVolume) |
---|
895 |
|
---|
896 |
def volumeDisconnect(self, volume): |
---|
897 |
volume.sliderChanged.disconnect(self.valueChangedVolume) |
---|
898 |
|
---|
899 |
def balanceConnect(self, balance): |
---|
900 |
balance.sliderChanged.connect(self.valueChangedBalance) |
---|
901 |
|
---|
902 |
def balanceDisconnect(self, balance): |
---|
903 |
balance.sliderChanged.disconnect(self.valueChangedBalance) |
---|
904 |
|
---|
905 |
def getNbIn(self): |
---|
906 |
if ffado.config.bypassdbus: |
---|
907 |
return 2 |
---|
908 |
if (self.rule == "Columns_are_inputs"): |
---|
909 |
return self.interface.getColCount() |
---|
910 |
else: |
---|
911 |
return self.interface.getRowCount() |
---|
912 |
|
---|
913 |
def getNbOut(self): |
---|
914 |
if ffado.config.bypassdbus: |
---|
915 |
return 2 |
---|
916 |
if (self.rule == "Columns_are_inputs"): |
---|
917 |
nbout = self.interface.getRowCount() |
---|
918 |
else: |
---|
919 |
nbout = self.interface.getColCount() |
---|
920 |
return nbout-len(self.stereochannels) |
---|
921 |
|
---|
922 |
def getVolumeValue(self, In, i): |
---|
923 |
if ffado.config.bypassdbus: |
---|
924 |
return 1 |
---|
925 |
Out = self.out[i].out_1 |
---|
926 |
if (self.rule == "Columns_are_inputs"): |
---|
927 |
vl = self.interface.getValue(Out, In) |
---|
928 |
else: |
---|
929 |
vl = self.interface.getValue(In, Out) |
---|
930 |
if (self.out[i].is_stereo): |
---|
931 |
if (self.rule == "Columns_are_inputs"): |
---|
932 |
vr = self.interface.getValue(Out+1, In) |
---|
933 |
else: |
---|
934 |
vr = self.interface.getValue(In, Out+1) |
---|
935 |
return getStereoVolume(vl, vr) |
---|
936 |
else: |
---|
937 |
return vl; |
---|
938 |
|
---|
939 |
def getBalanceValue(self, In, i): |
---|
940 |
if ffado.config.bypassdbus: |
---|
941 |
return 0.5 |
---|
942 |
Out = self.out[i].out_1 |
---|
943 |
if (self.rule == "Columns_are_inputs"): |
---|
944 |
vl = self.interface.getValue(Out, In) |
---|
945 |
vr = self.interface.getValue(Out+1, In) |
---|
946 |
else: |
---|
947 |
vl = self.interface.getValue(In, Out) |
---|
948 |
vr = self.interface.getValue(In, Out+1) |
---|
949 |
return getStereoBalance(vl, vr) |
---|
950 |
|
---|
951 |
def setValue(self, In, Out, val): |
---|
952 |
if ffado.config.bypassdbus: |
---|
953 |
return |
---|
954 |
if (self.rule == "Columns_are_inputs"): |
---|
955 |
return self.interface.setValue(Out, In, val) |
---|
956 |
else: |
---|
957 |
return self.interface.setValue(In, Out, val) |
---|
958 |
|
---|
959 |
def updateValues(self, n): |
---|
960 |
nbitems = len(n) // 3 |
---|
961 |
for j in range(nbitems): |
---|
962 |
n_0 = n[3*j] |
---|
963 |
n_1 = n[3*j+1] |
---|
964 |
n_2 = n[3*j+2] |
---|
965 |
i = self.outmatrix[n_1] |
---|
966 |
if (self.out[i].is_stereo): |
---|
967 |
v = self.getVolumeValue(n_0, i) |
---|
968 |
self.volumeDisconnect(self.out[i].volume[n_0]) |
---|
969 |
self.balanceDisconnect(self.out[i].balance[n_0]) |
---|
970 |
self.out[i].volume[n_0].sliderSetValue(v) |
---|
971 |
self.out[i].svl[n_0].sliderSetValue(v) |
---|
972 |
b = self.getBalanceValue(n_0, i) |
---|
973 |
# log.debug("update Value (%d %d %d %f)" % (n_0, i, v, b)) |
---|
974 |
self.out[i].balance[n_0].sliderSetValue(b) |
---|
975 |
self.volumeConnect(self.out[i].volume[n_0]) |
---|
976 |
self.balanceConnect(self.out[i].balance[n_0]) |
---|
977 |
else: |
---|
978 |
v = n_2 |
---|
979 |
# log.debug("update Value (%d %d %d)" % (n_0, i, v)) |
---|
980 |
self.volumeDisconnect(self.out[i].volume[n_0]) |
---|
981 |
self.out[i].volume[n_0].sliderSetValue(v) |
---|
982 |
self.out[i].svl[n_0].sliderSetValue(v) |
---|
983 |
self.volumeConnect(self.out[i].volume[n_0]) |
---|
984 |
|
---|
985 |
def valueChangedVolume(self, n): |
---|
986 |
#log.debug("VolumeSlider.valueChanged( %s )" % str(n)) |
---|
987 |
v = n[2] |
---|
988 |
n1 = self.out[n[1]].out_1 |
---|
989 |
if (self.out[n[1]].is_stereo): |
---|
990 |
b = self.out[n[1]].balance[n[0]].value()/50.0 |
---|
991 |
vl = int(getVolumeLeft(v, b)) |
---|
992 |
self.setValue(n[0], n1, vl) |
---|
993 |
n2 = n1+1 |
---|
994 |
vr = int(getVolumeRight(v, b)) |
---|
995 |
self.setValue(n[0], n2, vr) |
---|
996 |
n_t = (n[0], n1, vl, n[0], n2, vr) |
---|
997 |
self.valueChanged.emit(n_t) |
---|
998 |
else: |
---|
999 |
self.setValue(n[0], n1, v) |
---|
1000 |
n_t = (n[0], n1, v) |
---|
1001 |
self.valueChanged.emit(n_t) |
---|
1002 |
self.out[n[1]].svl[n[0]].sliderSetValue(v) |
---|
1003 |
|
---|
1004 |
def valueChangedBalance(self, n): |
---|
1005 |
#log.debug("BalanceSlider.valueChanged( %s )" % str(n)) |
---|
1006 |
n1 = self.out[n[1]].out_1 |
---|
1007 |
v = fromDBvalue(0.1*self.out[n[1]].volume[n[0]].value()) |
---|
1008 |
b = n[2] |
---|
1009 |
vl = int(getVolumeLeft(v, b)) |
---|
1010 |
self.setValue(n[0], n1, vl) |
---|
1011 |
n2 = n1+1 |
---|
1012 |
vr = int(getVolumeRight(v, b)) |
---|
1013 |
self.setValue(n[0], n2, vr) |
---|
1014 |
n_t = (n[0], n1, vl, n[0], n2, vr) |
---|
1015 |
self.valueChanged.emit(n_t) |
---|
1016 |
|
---|
1017 |
def getOutName(self, i, shortname): |
---|
1018 |
self.shortname = shortname |
---|
1019 |
k = self.out[i].out_1 |
---|
1020 |
if (shortname): |
---|
1021 |
if (self.out[i].is_stereo): |
---|
1022 |
number = " %d+%d" % (k+1, k+2) |
---|
1023 |
else: |
---|
1024 |
number = " %d" % (k+1) |
---|
1025 |
name = self.shortoutname + number |
---|
1026 |
return name |
---|
1027 |
else: |
---|
1028 |
if ffado.config.bypassdbus: |
---|
1029 |
return 'OutName ' + str(i) |
---|
1030 |
if (self.rule == "Columns_are_inputs"): |
---|
1031 |
if (self.out[i].is_stereo): |
---|
1032 |
name = self.interface.getRowName(k).replace('\n','')+" + "+self.interface.getRowName(k+1).replace('\n','') |
---|
1033 |
else: |
---|
1034 |
name = self.interface.getRowName(k).replace('\n','') |
---|
1035 |
return name |
---|
1036 |
else: |
---|
1037 |
if (self.out[i].is_stereo): |
---|
1038 |
name = self.interface.getColName(k).replace('\n','')+" + "+self.interface.getColName(k+1).replace('\n','') |
---|
1039 |
else: |
---|
1040 |
name = self.interface.getColName(k).replace('\n','') |
---|
1041 |
return name |
---|
1042 |
|
---|
1043 |
def getInName(self, j, shortname): |
---|
1044 |
self.shortname = shortname |
---|
1045 |
if (shortname): |
---|
1046 |
number = " %d" % (j+1) |
---|
1047 |
name = self.shortinname + number |
---|
1048 |
return name |
---|
1049 |
else: |
---|
1050 |
if ffado.config.bypassdbus: |
---|
1051 |
return 'InName ' + str(j) |
---|
1052 |
if (self.rule == "Columns_are_inputs"): |
---|
1053 |
return self.interface.getColName(j) |
---|
1054 |
else: |
---|
1055 |
return self.interface.getRowName(j) |
---|
1056 |
|
---|
1057 |
# Update when routing is modified |
---|
1058 |
def updateRouting(self): |
---|
1059 |
for i in range(self.nbOut): |
---|
1060 |
if (self.nbOut > 1): |
---|
1061 |
last_name = self.out[i].lbl[0].text() |
---|
1062 |
out_name = self.getOutName(i, self.shortname) |
---|
1063 |
if last_name != out_name: |
---|
1064 |
#log.debug("SliderControlView.updateRouting( %s )" % str(out_name)) |
---|
1065 |
self.out[i].lbl[0].setText(out_name) |
---|
1066 |
if (self.nbIn > 1): |
---|
1067 |
for j in range(self.nbIn): |
---|
1068 |
last_name = self.out[i].lbl[j+1].text() |
---|
1069 |
in_name = self.getInName(j, self.shortname) |
---|
1070 |
if last_name != in_name: |
---|
1071 |
#log.debug("SliderControlView.updateRouting( %s )" % str(in_name)) |
---|
1072 |
self.out[i].lbl[j+1].setText(in_name) |
---|
1073 |
|
---|
1074 |
def refreshValues(self): |
---|
1075 |
for n_out in range(self.nbOut): |
---|
1076 |
for n_in in range(self.nbIn): |
---|
1077 |
i = self.outmatrix[n_out] |
---|
1078 |
v = self.getVolumeValue(n_in, i) |
---|
1079 |
if (self.out[i].is_stereo): |
---|
1080 |
self.volumeDisconnect(self.out[i].volume[n_in]) |
---|
1081 |
self.out[i].volume[n_in].sliderSetValue(v) |
---|
1082 |
self.out[i].svl[n_in].sliderSetValue(v) |
---|
1083 |
b = self.getBalanceValue(n_in, i) |
---|
1084 |
# log.debug("update Value (%d %d %d %f)" % (n_0, i, v, b)) |
---|
1085 |
self.out[i].balance[n_in].sliderSetValue(b) |
---|
1086 |
self.volumeConnect(self.out[i].volume[n_in]) |
---|
1087 |
else: |
---|
1088 |
# log.debug("update Value (%d %d %d)" % (n_0, i, v)) |
---|
1089 |
self.out[i].volume[n_in].sliderSetValue(v) |
---|
1090 |
self.out[i].svl[n_in].sliderSetValue(v) |
---|
1091 |
|
---|
1092 |
def saveSettings(self, indent): |
---|
1093 |
if ffado.config.bypassdbus: |
---|
1094 |
rows = 2 |
---|
1095 |
cols = 2 |
---|
1096 |
else: |
---|
1097 |
rows = self.interface.getRowCount() |
---|
1098 |
cols = self.interface.getColCount() |
---|
1099 |
matrixSaveString = [] |
---|
1100 |
matrixSaveString.append('%s <row_number>\n' % indent) |
---|
1101 |
matrixSaveString.append('%s %d\n' % (indent, rows)) |
---|
1102 |
matrixSaveString.append('%s </row_number>\n' % indent) |
---|
1103 |
matrixSaveString.append('%s <col_number>\n' % indent) |
---|
1104 |
matrixSaveString.append('%s %d\n' % (indent, cols)) |
---|
1105 |
matrixSaveString.append('%s </col_number>\n' % indent) |
---|
1106 |
matrixSaveString.append('%s <coefficients>\n' % indent) |
---|
1107 |
for i in range(rows): |
---|
1108 |
line = '%s ' % indent |
---|
1109 |
for j in range(cols): |
---|
1110 |
line += '%d ' % self.interface.getValue(i,j) |
---|
1111 |
line += '\n' |
---|
1112 |
matrixSaveString.append(line) |
---|
1113 |
matrixSaveString.append('%s </coefficients>\n' % indent) |
---|
1114 |
|
---|
1115 |
return matrixSaveString |
---|
1116 |
|
---|
1117 |
def readSettings(self, readMatrixString, transpose_coeff): |
---|
1118 |
if ffado.config.bypassdbus: |
---|
1119 |
rows = 2 |
---|
1120 |
cols = 2 |
---|
1121 |
else: |
---|
1122 |
rows = self.interface.getRowCount() |
---|
1123 |
cols = self.interface.getColCount() |
---|
1124 |
if readMatrixString[0].find("<row_number>") == -1: |
---|
1125 |
log.debug("Number of matrix rows must be specified") |
---|
1126 |
return False |
---|
1127 |
if readMatrixString[2].find("</row_number>") == -1: |
---|
1128 |
log.debug("Non-conformal xml file") |
---|
1129 |
return False |
---|
1130 |
n_rows = int(readMatrixString[1]) |
---|
1131 |
|
---|
1132 |
if readMatrixString[3].find("<col_number>") == -1: |
---|
1133 |
log.debug("Number of matrix columns must be specified") |
---|
1134 |
return False |
---|
1135 |
if readMatrixString[5].find("</col_number>") == -1: |
---|
1136 |
log.debug("Non-conformal xml file") |
---|
1137 |
return False |
---|
1138 |
n_cols = int(readMatrixString[4]) |
---|
1139 |
|
---|
1140 |
if transpose_coeff: |
---|
1141 |
if n_rows > cols: |
---|
1142 |
n_rows = cols |
---|
1143 |
if n_cols > rows: |
---|
1144 |
n_cols = rows |
---|
1145 |
else: |
---|
1146 |
if n_rows > rows: |
---|
1147 |
n_rows = rows |
---|
1148 |
if n_cols > cols: |
---|
1149 |
n_cols = cols |
---|
1150 |
log.debug("Setting %d rows and %d columns coefficients" % (n_rows, n_cols)) |
---|
1151 |
|
---|
1152 |
try: |
---|
1153 |
idxb = readMatrixString.index('<coefficients>') |
---|
1154 |
idxe = readMatrixString.index('</coefficients>') |
---|
1155 |
except Exception: |
---|
1156 |
log.debug("No mixer matrix coefficients specified") |
---|
1157 |
idxb = -1 |
---|
1158 |
idxe = -1 |
---|
1159 |
if idxb >= 0: |
---|
1160 |
if idxe < idxb + n_rows + 1: |
---|
1161 |
log.debug("Incoherent number of rows in coefficients") |
---|
1162 |
return False |
---|
1163 |
i = 0 |
---|
1164 |
for s in readMatrixString[idxb+1:idxb + n_rows + 1]: |
---|
1165 |
coeffs = s.split() |
---|
1166 |
if len(coeffs) < n_cols: |
---|
1167 |
log.debug("Incoherent number of columns in coefficients") |
---|
1168 |
return False |
---|
1169 |
j = 0 |
---|
1170 |
for c in coeffs[0:n_cols]: |
---|
1171 |
if transpose_coeff: |
---|
1172 |
self.interface.setValue(j, i, int(c)) |
---|
1173 |
else: |
---|
1174 |
self.interface.setValue(i, j, int(c)) |
---|
1175 |
j += 1 |
---|
1176 |
i += 1 |
---|
1177 |
del coeffs |
---|
1178 |
|
---|
1179 |
self.refreshValues() |
---|
1180 |
return True |
---|
1181 |
|
---|
1182 |
from functools import partial |
---|
1183 |
|
---|
1184 |
class MatrixMixer(QWidget): |
---|
1185 |
def __init__(self, servername, basepath, parent=None, rule="Columns_are_inputs", sliderMaxValue=-1, mutespath=None, invertspath=None, smallFont=False, taborientation=QTabWidget.West, tabshape=QTabWidget.Triangular): |
---|
1186 |
QWidget.__init__(self, parent) |
---|
1187 |
self.servername = servername |
---|
1188 |
self.basepath = basepath |
---|
1189 |
self.sliderMaxValue = sliderMaxValue |
---|
1190 |
self.mutespath = mutespath |
---|
1191 |
self.invertspath = invertspath |
---|
1192 |
self.smallFont = smallFont |
---|
1193 |
|
---|
1194 |
self.layout = QVBoxLayout(self) |
---|
1195 |
self.setLayout(self.layout) |
---|
1196 |
|
---|
1197 |
# Mixer view Tool bar |
---|
1198 |
mxv_set = QToolBar("View settings", self) |
---|
1199 |
|
---|
1200 |
# Here is a hack; the first action button appears to behaves strangely, |
---|
1201 |
# possibly a PyQt bug (or an unsufficient fair implementation of it) |
---|
1202 |
# Feel free to remove the next three lines at a time in the future |
---|
1203 |
hack = QAction(" ", mxv_set) |
---|
1204 |
hack.setDisabled(True) |
---|
1205 |
mxv_set.addAction(hack) |
---|
1206 |
|
---|
1207 |
transpose_matrix = QAction("Transpose", mxv_set) |
---|
1208 |
self.transpose = False |
---|
1209 |
transpose_matrix.setShortcut('Ctrl+T') |
---|
1210 |
transpose_matrix.setToolTip("Invert rows and columns in Matrix view") |
---|
1211 |
mxv_set.addAction(transpose_matrix) |
---|
1212 |
transpose_matrix.triggered.connect(self.transposeMatrixView) |
---|
1213 |
mxv_set.addSeparator() |
---|
1214 |
|
---|
1215 |
self.hide_matrix = QAction("Hide matrix", mxv_set) |
---|
1216 |
self.hide_matrix_bool = False |
---|
1217 |
mxv_set.addAction(self.hide_matrix) |
---|
1218 |
self.hide_matrix.triggered.connect(self.hideMatrixView) |
---|
1219 |
mxv_set.addSeparator() |
---|
1220 |
|
---|
1221 |
self.hide_per_output = QAction("Hide per Output", mxv_set) |
---|
1222 |
self.hide_per_output_bool = False |
---|
1223 |
mxv_set.addAction(self.hide_per_output) |
---|
1224 |
self.hide_per_output.triggered.connect(self.hidePerOutputView) |
---|
1225 |
mxv_set.addSeparator() |
---|
1226 |
|
---|
1227 |
self.use_short_names = QAction("Short names", mxv_set) |
---|
1228 |
self.short_names_bool = False |
---|
1229 |
mxv_set.addAction(self.use_short_names) |
---|
1230 |
self.use_short_names.setToolTip("Use short or full names for input and output channels") |
---|
1231 |
self.use_short_names.triggered.connect(self.shortChannelNames) |
---|
1232 |
mxv_set.addSeparator() |
---|
1233 |
|
---|
1234 |
font_switch_lbl = QLabel(mxv_set) |
---|
1235 |
font_switch_lbl.setText("Font size ") |
---|
1236 |
mxv_set.addWidget(font_switch_lbl) |
---|
1237 |
font_switch = QComboBox(mxv_set) |
---|
1238 |
font_switch.setToolTip("Labels font size") |
---|
1239 |
font = font_switch.font() |
---|
1240 |
for i in range(10): |
---|
1241 |
font_switch.addItem(" %d " % (font.pointSize()+4-i)) |
---|
1242 |
font_switch.setCurrentIndex(font_switch.findText(" %d " % font.pointSize())) |
---|
1243 |
mxv_set.addWidget(font_switch) |
---|
1244 |
mxv_set.addSeparator() |
---|
1245 |
font_switch.activated.connect(self.changeFontSize) |
---|
1246 |
|
---|
1247 |
self.layout.addWidget(mxv_set) |
---|
1248 |
self.mxv_set = mxv_set |
---|
1249 |
|
---|
1250 |
# First tab is for matrix view |
---|
1251 |
# Next are for "per Out" view |
---|
1252 |
self.tabs = QTabWidget(self) |
---|
1253 |
self.tabs.setTabPosition(taborientation) |
---|
1254 |
self.tabs.setTabShape(tabshape) |
---|
1255 |
self.layout.addWidget(self.tabs) |
---|
1256 |
|
---|
1257 |
# Inputs/Outputs versus rows/columns rule |
---|
1258 |
self.rule = rule |
---|
1259 |
|
---|
1260 |
# Matrix view tab |
---|
1261 |
if (rule == "Columns_are_inputs"): |
---|
1262 |
self.matrix = MatrixControlView(servername, basepath, self, sliderMaxValue, mutespath, invertspath, smallFont, self.short_names_bool, "In", "Out", self.transpose) |
---|
1263 |
else: |
---|
1264 |
self.matrix = MatrixControlView(servername, basepath, self, sliderMaxValue, mutespath, invertspath, smallFont, self.short_names_bool, "Out", "In", self.transpose) |
---|
1265 |
self.matrix.valueChanged.connect(self.matrixControlChanged) |
---|
1266 |
|
---|
1267 |
self.scrollarea_matrix = QScrollArea(self.tabs) |
---|
1268 |
self.scrollarea_matrix.setWidgetResizable(True) |
---|
1269 |
self.scrollarea_matrix.setWidget(self.matrix) |
---|
1270 |
self.tabs.addTab(self.scrollarea_matrix, " Matrix ") |
---|
1271 |
|
---|
1272 |
# Add stereo/mono output choice in tool bar |
---|
1273 |
if (rule == "Columns_are_inputs"): |
---|
1274 |
if (self.transpose): |
---|
1275 |
nb_out_mono = self.matrix.cols |
---|
1276 |
else: |
---|
1277 |
nb_out_mono = self.matrix.rows |
---|
1278 |
else: |
---|
1279 |
if (self.transpose): |
---|
1280 |
nb_out_mono = self.matrix.rows |
---|
1281 |
else: |
---|
1282 |
nb_out_mono = self.matrix.cols |
---|
1283 |
|
---|
1284 |
stereo_switch_lbl = QLabel(mxv_set) |
---|
1285 |
stereo_switch_lbl.setText("Stereo: ") |
---|
1286 |
mxv_set.addWidget(stereo_switch_lbl) |
---|
1287 |
|
---|
1288 |
self.stereo_channels = [] |
---|
1289 |
|
---|
1290 |
self.stereo_switch = [] |
---|
1291 |
for i in range(int(nb_out_mono/2)): |
---|
1292 |
stereo_switch = QPushButton("%d+%d" % (2*i+1, 2*i+2), mxv_set) |
---|
1293 |
stereo_switch.setToolTip("Set these output channels as stereo") |
---|
1294 |
stereo_switch.setCheckable(True) |
---|
1295 |
stereo_switch.clicked.connect(partial(self.switchStereoChannel, i)) |
---|
1296 |
stereo_switch.setMinimumSize(stereo_switch_lbl.fontMetrics().boundingRect("%d+%d" % (nb_out_mono, nb_out_mono)).size()*1.05) |
---|
1297 |
stereo_switch.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)) |
---|
1298 |
stereo_switch.is_stereo = False |
---|
1299 |
mxv_set.addWidget(stereo_switch) |
---|
1300 |
self.stereo_switch.append(stereo_switch) |
---|
1301 |
mxv_set.addSeparator() |
---|
1302 |
|
---|
1303 |
# Per out view tabs |
---|
1304 |
self.perOut = SliderControlView(self, servername, basepath, rule, self.short_names_bool, "In", "Out", self.stereo_channels) |
---|
1305 |
self.perOut.valueChanged.connect(self.sliderControlChanged) |
---|
1306 |
for i in range(self.perOut.nbOut): |
---|
1307 |
self.perOut.out[i].scrollarea = QScrollArea(self.tabs) |
---|
1308 |
self.perOut.out[i].scrollarea.setWidgetResizable(True) |
---|
1309 |
self.perOut.out[i].scrollarea.setWidget(self.perOut.out[i]) |
---|
1310 |
self.tabs.addTab(self.perOut.out[i].scrollarea, " %s " % self.perOut.out[i].outname) |
---|
1311 |
|
---|
1312 |
def transposeMatrixView(self): |
---|
1313 |
self.transpose = not(self.transpose) |
---|
1314 |
self.tabs.removeTab(0) |
---|
1315 |
self.scrollarea_matrix.destroy() |
---|
1316 |
if (self.rule == "Columns_are_inputs"): |
---|
1317 |
self.matrix = MatrixControlView(self.servername, self.basepath, self, self.sliderMaxValue, self.mutespath, self.invertspath, self.smallFont, self.short_names_bool, "In", "Out", self.transpose) |
---|
1318 |
else: |
---|
1319 |
self.matrix = MatrixControlView(self.servername, self.basepath, self, self.sliderMaxValue, self.mutespath, self.invertspath, self.smallFont, self.short_names_bool, "Out", "In", self.transpose) |
---|
1320 |
self.matrix.valueChanged.connect(self.matrixControlChanged) |
---|
1321 |
|
---|
1322 |
self.scrollarea_matrix = QScrollArea(self.tabs) |
---|
1323 |
self.scrollarea_matrix.setWidgetResizable(True) |
---|
1324 |
self.scrollarea_matrix.setWidget(self.matrix) |
---|
1325 |
self.tabs.insertTab(0, self.scrollarea_matrix, "Matrix") |
---|
1326 |
self.tabs.setCurrentIndex(0) |
---|
1327 |
|
---|
1328 |
def hideMatrixView(self): |
---|
1329 |
self.hide_matrix_bool = not(self.hide_matrix_bool) |
---|
1330 |
if (self.hide_matrix_bool): |
---|
1331 |
self.tabs.removeTab(0) |
---|
1332 |
self.hide_matrix.setText("Show Matrix") |
---|
1333 |
else: |
---|
1334 |
self.tabs.insertTab(0, self.scrollarea_matrix, "Matrix") |
---|
1335 |
self.tabs.setCurrentIndex(0) |
---|
1336 |
self.hide_matrix.setText("Hide Matrix") |
---|
1337 |
|
---|
1338 |
def hidePerOutputView(self): |
---|
1339 |
self.hide_per_output_bool = not(self.hide_per_output_bool) |
---|
1340 |
if (self.hide_per_output_bool): |
---|
1341 |
index_0 = 1 |
---|
1342 |
if (self.hide_matrix_bool): |
---|
1343 |
index_0 = 0 |
---|
1344 |
for i in range(self.perOut.nbOut): |
---|
1345 |
self.tabs.removeTab(index_0) |
---|
1346 |
self.hide_per_output.setText("Show per Output") |
---|
1347 |
else: |
---|
1348 |
for i in range(self.perOut.nbOut): |
---|
1349 |
self.tabs.insertTab(i+1, self.perOut.out[i].scrollarea, " %s " % self.perOut.out[i].outname) |
---|
1350 |
self.hide_per_output.setText("Hide per Output") |
---|
1351 |
|
---|
1352 |
# Font size for channel names |
---|
1353 |
def changeFontSize(self, size): |
---|
1354 |
font = self.mxv_set.font() |
---|
1355 |
font.setPointSize(int(size)) |
---|
1356 |
self.mxv_set.setFont(font) |
---|
1357 |
|
---|
1358 |
font = self.tabs.font() |
---|
1359 |
font.setPointSize(int(size)) |
---|
1360 |
self.tabs.setFont(font) |
---|
1361 |
|
---|
1362 |
font = self.matrix.font() |
---|
1363 |
font.setPointSize(int(size)) |
---|
1364 |
self.matrix.setFont(font) |
---|
1365 |
|
---|
1366 |
font = self.perOut.font() |
---|
1367 |
font.setPointSize(int(size)) |
---|
1368 |
self.perOut.setFont(font) |
---|
1369 |
|
---|
1370 |
for i in range(self.perOut.nbOut): |
---|
1371 |
for j in range(self.perOut.nbIn): |
---|
1372 |
self.perOut.out[i].svl[j].sliderSetMinimalDim() |
---|
1373 |
|
---|
1374 |
# Allows long name for Mixer/Out and /In to be hidden |
---|
1375 |
def shortChannelNames(self): |
---|
1376 |
checked = not(self.short_names_bool) |
---|
1377 |
if (self.matrix.cols > 1): |
---|
1378 |
for i in range(self.matrix.cols): |
---|
1379 |
self.matrix.columnHeaders[i].name = self.matrix.getColName(i, checked) |
---|
1380 |
self.matrix.columnHeaders[i].lbl.setText(self.matrix.columnHeaders[i].name) |
---|
1381 |
|
---|
1382 |
if (self.matrix.rows > 1): |
---|
1383 |
for j in range(self.matrix.rows): |
---|
1384 |
self.matrix.rowHeaders[j].name = self.matrix.getRowName(j, checked) |
---|
1385 |
self.matrix.rowHeaders[j].lbl.setText(self.matrix.rowHeaders[j].name) |
---|
1386 |
|
---|
1387 |
for i in range(self.perOut.nbOut): |
---|
1388 |
if (self.perOut.nbOut > 1): |
---|
1389 |
self.perOut.out[i].lbl[0].setText(self.perOut.getOutName(i, checked)) |
---|
1390 |
if (self.perOut.nbIn > 1): |
---|
1391 |
for j in range(self.perOut.nbIn): |
---|
1392 |
self.perOut.out[i].lbl[j+1].setText(self.perOut.getInName(j, checked)) |
---|
1393 |
|
---|
1394 |
# Care for hidden columns |
---|
1395 |
if (self.matrix.cols > 1): |
---|
1396 |
for i in self.matrix.hiddenCols: |
---|
1397 |
self.matrix.columnHeaders[i].lbl.setText("%d" % (i+1)) |
---|
1398 |
# Care for hidden rows |
---|
1399 |
if (self.matrix.rows > 1): |
---|
1400 |
for j in self.matrix.hiddenRows: |
---|
1401 |
self.matrix.rowHeaders[j].lbl.setText("%d" % (j+1)) |
---|
1402 |
|
---|
1403 |
self.short_names_bool = checked |
---|
1404 |
if (self.short_names_bool): |
---|
1405 |
self.use_short_names.setText("Long names") |
---|
1406 |
else: |
---|
1407 |
self.use_short_names.setText("Short names") |
---|
1408 |
|
---|
1409 |
# Sliders value change |
---|
1410 |
# Care that some recursive process is involved and only stop when exactly same values are involved |
---|
1411 |
# Matrix view |
---|
1412 |
def matrixControlChanged(self, n): |
---|
1413 |
# Update value needed for "per Out" view |
---|
1414 |
#log.debug("Update per Output( %s )" % str(n)) |
---|
1415 |
nbitems = len(n) // 3 |
---|
1416 |
if (self.rule == "Columns_are_inputs"): |
---|
1417 |
n_t = n |
---|
1418 |
else: |
---|
1419 |
n_t = () |
---|
1420 |
for i in range(nbitems): |
---|
1421 |
n_t += (n[3*i+1], n[3*i], n[3*i+2]) |
---|
1422 |
|
---|
1423 |
self.perOut.updateValues(n_t) |
---|
1424 |
|
---|
1425 |
# "per Out" view |
---|
1426 |
def sliderControlChanged(self, n): |
---|
1427 |
# Update value needed for matrix view |
---|
1428 |
#log.debug("Update Matrix( %s )" % str(n)) |
---|
1429 |
nbitems = len(n) // 3 |
---|
1430 |
if (((self.rule == "Columns_are_inputs") and not self.transpose) or ((self.rule != "Columns_are_inputs") and self.transpose)): |
---|
1431 |
n_t = () |
---|
1432 |
for i in range(nbitems): |
---|
1433 |
n_t += (n[3*i+1], n[3*i], n[3*i+2]) |
---|
1434 |
else: |
---|
1435 |
n_t = n |
---|
1436 |
|
---|
1437 |
self.matrix.updateValues(n_t) |
---|
1438 |
|
---|
1439 |
def refreshValues(self): |
---|
1440 |
# Refreshing matrix coefficient should be sufficient, |
---|
1441 |
# propagating the changes to perOut view |
---|
1442 |
self.matrix.refreshValues() |
---|
1443 |
|
---|
1444 |
def switchStereoChannel(self, channel, is_stereo): |
---|
1445 |
#log.debug(" switching channels %d+%d to stereo/mono" % (2*channel, 2*channel+1)) |
---|
1446 |
self.stereo_switch[channel].is_stereo = self.stereo_switch[channel].isChecked(); |
---|
1447 |
if (self.stereo_switch[channel].is_stereo): |
---|
1448 |
self.stereo_channels.append(2*channel) |
---|
1449 |
else: |
---|
1450 |
self.stereo_channels.remove(2*channel) |
---|
1451 |
|
---|
1452 |
# tab 0 is for matrix except if it is hidden |
---|
1453 |
index_0 = 1 |
---|
1454 |
if (self.hide_matrix_bool): |
---|
1455 |
index_0 = 0 |
---|
1456 |
for i in range(self.perOut.nbOut): |
---|
1457 |
self.tabs.removeTab(index_0) |
---|
1458 |
self.perOut.destroy() |
---|
1459 |
self.perOut = SliderControlView(self, self.servername, self.basepath, self.rule, self.short_names_bool, "In", "Out", self.stereo_channels) |
---|
1460 |
self.perOut.valueChanged.connect(self.sliderControlChanged) |
---|
1461 |
current = 0 |
---|
1462 |
for i in range(self.perOut.nbOut): |
---|
1463 |
self.perOut.out[i].scrollarea = QScrollArea(self.tabs) |
---|
1464 |
self.perOut.out[i].scrollarea.setWidgetResizable(True) |
---|
1465 |
self.perOut.out[i].scrollarea.setWidget(self.perOut.out[i]) |
---|
1466 |
self.tabs.addTab(self.perOut.out[i].scrollarea, " %s " % self.perOut.out[i].outname) |
---|
1467 |
if self.perOut.out[i].out_1 == 2*channel: |
---|
1468 |
current = i |
---|
1469 |
|
---|
1470 |
self.tabs.setCurrentWidget(self.perOut.out[current].scrollarea) |
---|
1471 |
|
---|
1472 |
# Update when routing is modified |
---|
1473 |
def updateRouting(self): |
---|
1474 |
self.matrix.updateRouting() |
---|
1475 |
self.perOut.updateRouting() |
---|
1476 |
|
---|
1477 |
def saveSettings(self, indent): |
---|
1478 |
mixerString = [] |
---|
1479 |
mixerString.append("%s<matrices>\n" % indent) |
---|
1480 |
mixerString.extend(self.matrix.saveSettings(indent)) |
---|
1481 |
mixerString.append("%s</matrices>\n" % indent) |
---|
1482 |
mixerString.append("%s<stereo_outputs>\n" % indent) |
---|
1483 |
mixerString.append("%s <number>\n" % indent) |
---|
1484 |
n = len(self.stereo_channels) |
---|
1485 |
mixerString.append("%s %d\n" % (indent, n)) |
---|
1486 |
mixerString.append("%s </number>\n" % indent) |
---|
1487 |
if n > 0: |
---|
1488 |
mixerString.append("%s <channels>\n" % indent) |
---|
1489 |
for i in self.stereo_channels: |
---|
1490 |
mixerString.append("%s %d %d\n" % (indent, i+1, i+2)) |
---|
1491 |
mixerString.append("%s </channels>\n" % indent) |
---|
1492 |
mixerString.append("%s</stereo_outputs>\n" % indent) |
---|
1493 |
return mixerString |
---|
1494 |
|
---|
1495 |
def readSettings(self, readMixerString, transpose_coeff): |
---|
1496 |
try: |
---|
1497 |
idxb = readMixerString.index('<matrices>') |
---|
1498 |
idxe = readMixerString.index('</matrices>') |
---|
1499 |
except Exception: |
---|
1500 |
log.debug("No matrices found") |
---|
1501 |
idxb = -1 |
---|
1502 |
idxe = -1 |
---|
1503 |
if idxb >= 0: |
---|
1504 |
if idxe > idxb+1: |
---|
1505 |
readString = [] |
---|
1506 |
for s in readMixerString[idxb+1:idxe]: |
---|
1507 |
readString.append(s) |
---|
1508 |
if self.matrix.readSettings(readString, transpose_coeff): |
---|
1509 |
log.debug("Mixer matrices settings modified") |
---|
1510 |
del readString |
---|
1511 |
try: |
---|
1512 |
idx = readMixerString.index('<stereo_outputs>') |
---|
1513 |
except Exception: |
---|
1514 |
log.debug("No stereo outputs channels information found") |
---|
1515 |
idx = -1 |
---|
1516 |
if idx >= 0: |
---|
1517 |
if readMixerString[idx+1].find('<number>') == -1: |
---|
1518 |
log.debug("Number of stereo output channels must be specified") |
---|
1519 |
return False |
---|
1520 |
n = int(readMixerString[idx+2]) |
---|
1521 |
if n > self.perOut.nbOut // 2: |
---|
1522 |
log.debug("Incoherent number of stereo channels") |
---|
1523 |
return False |
---|
1524 |
if n > 0: |
---|
1525 |
if readMixerString[idx+3].find('</number>') == -1: |
---|
1526 |
log.debug("No </number> tag found") |
---|
1527 |
return False |
---|
1528 |
if readMixerString[idx+4].find('<channels>') == -1: |
---|
1529 |
log.debug("No <channels> tag found") |
---|
1530 |
return False |
---|
1531 |
for s in readMixerString[idx+5:idx+5+n]: |
---|
1532 |
i = (int(s.split()[0]) - 1)/2 |
---|
1533 |
self.stereo_switch[i].setChecked(True); |
---|
1534 |
self.switchStereoChannel(i, True) |
---|
1535 |
return True |
---|
1536 |
|
---|
1537 |
# |
---|
1538 |
# vim: et ts=4 sw=4 fileencoding=utf8 |
---|