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
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
40 def __init__(self
,parent
= None):
41 QWidget
.__init
__(self
,parent
)
42 uicLoad("ffado/mixer/rme", 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],
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],
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],
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],
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],
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
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()
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]);
145 val
= val | self
.Checkboxes
[sender
][1]
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
160 if (self
.model
==RME_MODEL_FF400
and a0
>0):
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
)
174 self
.is_streaming
= True
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
):
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
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
)
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())):
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):
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())):
327 val
= (self
.hw
.getDiscrete(info
[0]) >> info
[1]) & 0x01
328 log
.debug("phantom switch %d is %d" % (info
[1], val
))
330 ctrl
.setChecked(True)
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())):
338 val
= self
.hw
.getDiscrete(info
[0])
339 log
.debug("switch %s is %d" % (info
[0], val
))
341 ctrl
.setChecked(True)
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())):
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
354 val
= self
.hw
.getDiscrete(info
[0])
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())):
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
371 val
= self
.hw
.getDiscrete(info
[0])
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())):
383 val
= self
.hw
.getMatrixMixerValue(info
[0], 0, info
[1])
384 log
.debug("gain %s[%d] is %d" % (info
[0], info
[1], 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)