rme: clarify a comment in the mixer code. With r2294, the vertical sizing issues...
[ffado.git] / libffado / support / mixer-qt4 / ffado / mixer / rme.py
blob3fae3cd4615289d55dab0253d241c77dd9cac3e6
2 # Copyright (C) 2009, 2011 by Jonathan Woithe
4 # This file is part of FFADO
5 # FFADO = Free Firewire (pro-)audio drivers for linux
7 # FFADO is based upon FreeBoB.
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.
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.
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/>.
23 from PyQt4 import QtGui
25 from PyQt4.QtCore import SIGNAL, SLOT, QObject, Qt, QTimer
26 from PyQt4.QtGui import QWidget, QApplication
27 from ffado.config import *
29 from ffado.widgets.matrixmixer import MatrixMixer
31 import logging
32 log = logging.getLogger('rme')
34 # Model defines. These must agree with what is used in rme_avdevice.h.
35 RME_MODEL_NONE = 0x0000
36 RME_MODEL_FF800 = 0x0001
37 RME_MODEL_FF400 = 0x0002
39 class Rme(QWidget):
40 def __init__(self,parent = None):
41 QWidget.__init__(self,parent)
42 uicLoad("ffado/mixer/rme", self)
44 self.init()
46 def init(self):
48 self.PhantomSwitches={
49 self.phantom_0: ['/Control/Phantom', 0],
50 self.phantom_1: ['/Control/Phantom', 1],
51 self.phantom_2: ['/Control/Phantom', 2],
52 self.phantom_3: ['/Control/Phantom', 3],
55 self.Switches={
56 self.ff400_chan3_opt_instr: ['/Control/Chan3_opt_instr'],
57 self.ff400_chan3_opt_pad: ['/Control/Chan3_opt_pad'],
58 self.ff400_chan4_opt_instr: ['/Control/Chan4_opt_instr'],
59 self.ff400_chan4_opt_pad: ['/Control/Chan4_opt_pad'],
61 self.spdif_output_optical: ['/Control/SPDIF_output_optical', 0],
62 self.spdif_output_emphasis: ['/Control/SPDIF_output_emphasis', 0],
63 self.spdif_output_pro: ['/Control/SPDIF_output_pro', 0],
64 self.spdif_output_nonaudio: ['/Control/SPDIF_output_nonaudio', 0],
67 self.Radiobuttons={
68 self.level_in_lo_gain: ['/Control/Input_level', 0],
69 self.level_in_p4dBu: ['/Control/Input_level', 2],
70 self.level_in_m10dBV: ['/Control/Input_level', 1],
72 self.level_out_hi_gain: ['/Control/Output_level', 2],
73 self.level_out_p4dBu: ['/Control/Output_level', 1],
74 self.level_out_m10dBV: ['/Control/Output_level', 0],
76 self.spdif_input_coax: ['/Control/SPDIF_input_mode', 0],
77 self.spdif_input_optical: ['/Control/SPDIF_input_mode', 1],
79 self.phones_hi_gain: ['/Control/Phones_level', 0],
80 self.phones_p4dBu: ['/Control/Phones_level', 1],
81 self.phones_m10dBV: ['/Control/Phones_level', 2],
83 self.clock_mode_autosync: ['/Control/Clock_mode', 1],
84 self.clock_mode_master: ['/Control/Clock_mode', 0],
86 self.sync_ref_wordclk: ['/Control/Sync_ref', 0],
87 self.sync_ref_adat1: ['/Control/Sync_ref', 1],
88 self.sync_ref_adat2: ['/Control/Sync_ref', 2],
89 self.sync_ref_spdif: ['/Control/Sync_ref', 3],
90 self.sync_ref_tco: ['/Control/Sync_ref', 4],
93 self.Checkboxes={
94 self.ch1_instr_fuzz: ['/Control/Chan1_instr_opts', 0x04],
95 self.ch1_instr_limiter: ['/Control/Chan1_instr_opts', 0x08],
96 self.ch1_instr_filter: ['/Control/Chan1_instr_opts', 0x02],
99 self.Gains={
100 self.gain_mic1: ['/Control/Gains', 0],
101 self.gain_mic2: ['/Control/Gains', 1],
102 self.gain_input3: ['/Control/Gains', 2],
103 self.gain_input4: ['/Control/Gains', 3],
106 self.Combos={
107 self.ff800_ch1_src: ['/Control/Chan1_source'],
108 self.ff800_ch7_src: ['/Control/Chan7_source'],
109 self.ff800_ch8_src: ['/Control/Chan8_source'],
112 # Other mixer variables
113 self.is_streaming = 0
114 self.sample_rate = 0
115 self.model = 0
116 self.tco_present = 0
118 # Public slot: update phantom power hardware switches
119 def updatePhantomSwitch(self, a0):
120 sender = self.sender()
121 # Value is the phantom switch value, with a corresponding enable
122 # bit in the high 16 bit word
123 val = (a0 << self.PhantomSwitches[sender][1]) | (0x00010000 << self.PhantomSwitches[sender][1])
124 log.debug("phantom switch %d set to %d" % (self.PhantomSwitches[sender][1], a0))
125 self.hw.setDiscrete(self.PhantomSwitches[sender][0], val)
127 # Public slot: update generic switches
128 def updateSwitch(self, a0):
129 sender = self.sender()
130 log.debug("switch %s set to %d" % (self.Switches[sender][0], a0))
131 self.hw.setDiscrete(self.Switches[sender][0], a0)
133 # Public slot: update generic radiobuttons
134 def updateRadiobutton(self, a0):
135 sender = self.sender()
136 if (a0 != 0):
137 # Only change the control state on a button being "checked"
138 log.debug("radiobutton group %s set to %d" % (self.Radiobuttons[sender][0], self.Radiobuttons[sender][1]))
139 self.hw.setDiscrete(self.Radiobuttons[sender][0], self.Radiobuttons[sender][1])
141 def updateCheckboxes(self, a0):
142 sender = self.sender()
143 val = self.hw.getDiscrete(self.Checkboxes[sender][0]);
144 if (a0 != 0):
145 val = val | self.Checkboxes[sender][1]
146 else:
147 val = val & ~self.Checkboxes[sender][1]
148 log.debug("checkbox group %s set to %d" % (self.Checkboxes[sender][0], val));
149 self.hw.setDiscrete(self.Checkboxes[sender][0], val)
151 # Public slot: update gains
152 def updateGain(self, a0):
153 sender = self.sender()
154 log.debug("gain %s[%d] set to %d" % (self.Gains[sender][0], self.Gains[sender][1], a0))
155 self.hw.setMatrixMixerValue(self.Gains[sender][0], 0, self.Gains[sender][1], a0)
157 def updateBandwidthLimit(self, a0):
158 # Account for the "No ADAT-2" item which will not be present on
159 # a FF400.
160 if (self.model==RME_MODEL_FF400 and a0>0):
161 a0 = a0 + 1
162 # log.debug("limit update: %d" % (a0));
163 self.hw.setDiscrete('/Control/Bandwidth_limit', a0);
165 def updateCombo(self, a0):
166 sender = self.sender()
167 log.debug("combo %s set to %d" % (self.Combos[sender][0], a0))
168 self.hw.setDiscrete(self.Combos[sender][0], a0)
170 def updateStreamingState(self):
171 ss = self.streamingstatus.selected()
172 ss_txt = self.streamingstatus.getEnumLabel(ss)
173 if ss_txt != 'Idle':
174 self.is_streaming = True
175 else:
176 self.is_streaming = False
177 if (self.last_streaming_state != self.is_streaming):
178 self.bandwidth_limit.setEnabled(not(self.is_streaming));
179 self.last_streaming_state = self.is_streaming
181 def status_update(self):
182 # log.debug("timer event")
183 self.updateStreamingState()
184 clk_mode = ['Master', 'Slave']
185 src_str = ['None', 'ADAT 1', 'ADAT 2', 'SPDIF', 'Wordclock', 'TCO']
186 sync_stat = ['No lock', 'Locked', 'Synced']
187 sysclock_mode = self.hw.getDiscrete('/Control/sysclock_mode')
188 sysclock_freq = self.hw.getDiscrete('/Control/sysclock_freq')
189 autosync_freq = self.hw.getDiscrete('/Control/autosync_freq')
190 autosync_src = self.hw.getDiscrete('/Control/autosync_src')
191 sync_status = self.hw.getDiscrete('/Control/sync_status')
192 spdif_freq = self.hw.getDiscrete('/Control/spdif_freq')
193 self.sysclock_freq.setText("%d Hz" % (sysclock_freq))
194 self.sysclock_mode.setText(clk_mode[sysclock_mode])
195 self.autosync_freq.setText("%d Hz" % (autosync_freq))
196 self.autosync_src.setText(src_str[autosync_src])
197 self.sync_check_adat1_status.setText(sync_stat[sync_status & 0x03])
198 self.sync_check_adat2_status.setText(sync_stat[(sync_status >> 2) & 0x03])
199 self.sync_check_spdif_status.setText(sync_stat[(sync_status >> 4) & 0x03])
200 self.sync_check_wclk_status.setText(sync_stat[(sync_status >> 6) & 0x03])
201 self.sync_check_tco_status.setText(sync_stat[(sync_status >> 8) & 0x03])
202 self.spdif_freq.setText("%d Hz" % (spdif_freq))
204 # Hide and disable a control
205 def disable_hide(self,widget):
206 for w in widget.children():
207 if isinstance(w, QWidget):
208 w.hide()
209 w.setEnabled(False)
210 widget.hide()
211 widget.setEnabled(False)
213 def initValues(self):
215 # print self.hw.servername
216 # print self.hw.basepath
217 self.inputmatrix = MatrixMixer(self.hw.servername, self.hw.basepath+"/Mixer/InputFaders", self, 0x8000, self.hw.basepath+"/Mixer/InputMutes", self.hw.basepath+"/Mixer/InputInverts", True)
218 layout = QtGui.QVBoxLayout()
219 scrollarea = QtGui.QScrollArea()
220 scrollarea.setWidgetResizable(True)
221 scrollarea.setWidget(self.inputmatrix)
222 layout.addWidget(scrollarea)
223 self.mixer.setLayout(layout)
225 self.playbackmatrix = MatrixMixer(self.hw.servername, self.hw.basepath+"/Mixer/PlaybackFaders", self, 0x8000, self.hw.basepath+"/Mixer/PlaybackMutes", self.hw.basepath+"/Mixer/PlaybackInverts", True)
226 layout = QtGui.QVBoxLayout()
227 scrollarea = QtGui.QScrollArea()
228 scrollarea.setWidgetResizable(True)
229 scrollarea.setWidget(self.playbackmatrix)
230 layout.addWidget(scrollarea)
231 self.playbackmixer.setLayout(layout)
233 self.outputmatrix = MatrixMixer(self.hw.servername, self.hw.basepath+"/Mixer/OutputFaders", self, 0x8000, self.hw.basepath+"/Mixer/OutputMutes", None, True)
234 layout = QtGui.QVBoxLayout()
235 scrollarea = QtGui.QScrollArea()
236 scrollarea.setWidget(self.outputmatrix)
237 scrollarea.setWidgetResizable(True)
239 # This is a bit of a hack, but it works to ensure this single-row
240 # matrix mixer doesn't fill the entire screen but also doesn't end
241 # up with a pointless scrollbar. The matrix mixer's minimum height
242 # is 0 according to minimumHeight(), which is probably the
243 # fundamental issue here; however, I've already wasted too much time
244 # trying to get this to work so if the hack is effective we'll run
245 # with it.
246 scrollarea.setMinimumHeight(150)
247 layout.addWidget(scrollarea, 0, Qt.AlignTop)
248 self.outputmixer.setLayout(layout)
250 self.is_streaming = False
251 self.last_streaming_state = False
253 # For now, disable the device operation buttons since they are
254 # not yet implemented.
255 self.disable_hide(self.device_operations)
256 #self.control_load.setEnabled(False)
257 #self.control_save.setEnabled(False)
258 #self.mixer_load.setEnabled(False)
259 #self.mixer_save.setEnabled(False)
260 #self.mixer_preset_ffado_default.setEnabled(False)
262 # Retrieve other device settings as needed and customise the UI
263 # based on these options.
264 self.model = self.hw.getDiscrete('/Control/Model')
265 log.debug("device model identifier: %d" % (self.model))
266 self.tco_present = self.hw.getDiscrete('/Control/TCO_present')
267 log.debug("device has TCO: %d" % (self.tco_present))
268 #self.sample_rate = self.hw.getDiscrete('/Mixer/Info/SampleRate')
269 #log.debug("device sample rate: %d" % (self.sample_rate))
271 # The Fireface-400 only has 2 phantom-capable channels
272 if (self.model == RME_MODEL_FF400):
273 self.disable_hide(self.phantom_2)
274 self.disable_hide(self.phantom_3)
275 else:
276 self.phantom_0.setText("Mic 7")
277 self.phantom_1.setText("Mic 8")
278 self.phantom_2.setText("Mic 9")
279 self.phantom_3.setText("Mic 10")
281 # Instrument options, input jack selection controls and an ADAT2
282 # input are applicable only to the FF800
283 if (self.model != RME_MODEL_FF800):
284 self.instrument_options_group.setEnabled(False)
285 self.input_plug_select_group.setEnabled(False)
286 self.sync_ref_adat2.setEnabled(False)
287 self.sync_check_adat2_label.setEnabled(False)
288 self.sync_check_adat2_status.setEnabled(False)
290 for ctrl, info in self.Combos.iteritems():
291 if (not(ctrl.isEnabled())):
292 continue;
293 val = self.hw.getDiscrete(info[0])
294 log.debug("combo %s is %d" % (info[0], val));
295 ctrl.setCurrentIndex(val);
296 QObject.connect(ctrl, SIGNAL('currentIndexChanged(int)'), self.updateCombo)
298 if (not(self.tco_present)):
299 self.sync_check_tco_label.setEnabled(False)
300 self.sync_check_tco_status.setEnabled(False)
301 self.sync_ref_tco.setEnabled(False)
303 # Only the FF400 has specific channel 3/4 options, input gain
304 # controls and switchable phones level
305 if (self.model != RME_MODEL_FF400):
306 # Hide the upper-level frame (and everything in it) to ensure it
307 # requests no vertical space when its contents aren't needed.
308 self.disable_hide(self.igains_chan34_opts_frame)
309 self.phones_level_group.setEnabled(False)
311 # Add the "No ADAT-2" item to the bandwidth limit control if the
312 # device is not a FF400. Set the control to reflect the current
313 # device setting and connect an update signal.
314 if (self.model != RME_MODEL_FF400):
315 self.bandwidth_limit.insertItem(1, "No ADAT-2")
316 val = self.hw.getDiscrete('/Control/Bandwidth_limit')
317 if (self.model==RME_MODEL_FF400 and val>1):
318 val = val - 1
319 self.bandwidth_limit.setCurrentIndex(val);
320 QObject.connect(self.bandwidth_limit, SIGNAL('currentIndexChanged(int)'), self.updateBandwidthLimit)
322 # Get current hardware values and connect GUI element signals to
323 # their respective slots
324 for ctrl, info in self.PhantomSwitches.iteritems():
325 if (not(ctrl.isEnabled())):
326 continue
327 val = (self.hw.getDiscrete(info[0]) >> info[1]) & 0x01
328 log.debug("phantom switch %d is %d" % (info[1], val))
329 if val:
330 ctrl.setChecked(True)
331 else:
332 ctrl.setChecked(False)
333 QObject.connect(ctrl, SIGNAL('toggled(bool)'), self.updatePhantomSwitch)
335 for ctrl, info in self.Switches.iteritems():
336 if (not(ctrl.isEnabled())):
337 continue
338 val = self.hw.getDiscrete(info[0])
339 log.debug("switch %s is %d" % (info[0], val))
340 if val:
341 ctrl.setChecked(True)
342 else:
343 ctrl.setChecked(False)
344 QObject.connect(ctrl, SIGNAL('toggled(bool)'), self.updateSwitch)
346 for ctrl, info in self.Radiobuttons.iteritems():
347 if (not(ctrl.isEnabled())):
348 continue;
349 # This is a touch wasteful since it means we retrieve the control
350 # value once per radio button rather than once per radio button
351 # group. In time we might introduce radiobutton groupings in the
352 # self.* datastructures to avoid this, but for the moment this is
353 # easy and it works.
354 val = self.hw.getDiscrete(info[0])
355 if (val == info[1]):
356 val = 1
357 else:
358 val = 0
359 ctrl.setChecked(val)
360 log.debug("Radiobutton %s[%d] is %d" % (info[0], info[1], val))
361 QObject.connect(ctrl, SIGNAL('toggled(bool)'), self.updateRadiobutton)
363 for ctrl, info in self.Checkboxes.iteritems():
364 if (not(ctrl.isEnabled())):
365 continue;
366 # This is a touch wasteful since it means we retrieve the control
367 # value once per checkbox button rather than once per checkbox
368 # group. In time we might introduce checkbox groupings in the
369 # self.* datastructures to avoid this, but for the moment this is
370 # easy and it works.
371 val = self.hw.getDiscrete(info[0])
372 if (val & info[1]):
373 val = 1
374 else:
375 val = 0
376 ctrl.setChecked(val)
377 log.debug("Checkbox %s[%d] is %d" % (info[0], info[1], val))
378 QObject.connect(ctrl, SIGNAL('toggled(bool)'), self.updateCheckboxes)
380 for ctrl, info in self.Gains.iteritems():
381 if (not(ctrl.isEnabled())):
382 continue
383 val = self.hw.getMatrixMixerValue(info[0], 0, info[1])
384 log.debug("gain %s[%d] is %d" % (info[0], info[1], val))
385 ctrl.setValue(val);
386 QObject.connect(ctrl, SIGNAL('valueChanged(int)'), self.updateGain)
388 self.updateStreamingState()
389 #log.debug("device streaming flag: %d" % (self.is_streaming))
391 self.update_timer = QTimer(self)
392 QObject.connect(self.update_timer, SIGNAL('timeout()'), self.status_update)
393 self.update_timer.start(1000)
395 # vim: et