1 from PyQt4
.QtCore
import SIGNAL
, SLOT
, QObject
, Qt
2 from PyQt4
.QtGui
import QWidget
, QMessageBox
, QHBoxLayout
, QVBoxLayout
, QTabWidget
, QGroupBox
, QLabel
, QDial
, QSlider
, QToolButton
, QSizePolicy
4 from ffado
.config
import *
7 log
= logging
.getLogger('MAudioBeBoB')
9 class MAudio_BeBoB_Input_Widget(QWidget
):
10 def __init__(self
,parent
= None):
11 QWidget
.__init
__(self
,parent
)
12 uicLoad("ffado/mixer/maudio_bebob_input", self
)
14 class MAudio_BeBoB_Output_Widget(QWidget
):
15 def __init__(self
,parent
= None):
16 QWidget
.__init
__(self
,parent
)
17 uicLoad("ffado/mixer/maudio_bebob_output", self
)
20 class MAudio_BeBoB(QWidget
):
21 def __init__(self
,parent
= None):
22 QWidget
.__init
__(self
,parent
)
24 info
= {0x0000000a: [0, "Ozonic"],
25 0x00010062: [1, "Firewire Solo"],
26 0x00010060: [2, "Firewire Audiophile"],
27 0x00010046: [3, "Firewire 410"],
31 {"inputs": ["Analog 1/2", "Analog 3/4", "Stream 1/2", "Stream 3/4"],
32 "mixers": ["Mixer 1/2", "Mixer 3/4"],
33 "outputs": ["Analog 1/2", "Analog 3/4"]},
34 {"inputs": ["Analog 1/2", "Digital 1/2", "Stream 1/2", "Stream 3/4"],
35 "mixers": ["Mixer 1/2", "Mixer 3/4"],
36 "outputs": ["Analog 1/2", "Digital 1/2"]},
37 {"inputs": ["Analog 1/2", "Digital 1/2", "Stream 1/2", "Stream 3/4", "Stream 5/6"],
38 "mixers": ["Mixer 1/2", "Mixer 3/4", "Mixer 5/6", "Aux 1/2"],
39 "outputs": ["Analog 1/2", "Analog 3/4", "Digital 1/2", "Headphone 1/2"]},
40 {"inputs": ["Analog 1/2", "Digital 1/2", "Stream 1/2", "Stream 3/4", "Stream 5/6", "Stream 7/8", "Stream 9/10"],
41 "mixers": ["Mixer 1/2", "Mixer 3/4", "Mixer 5/6", "Mixer 7/8", "Mixer 9/10", "Aux 1/2"],
42 "outputs": ["Analog 1/2", "Analog 3/4", "Analog 5/6", "Analog 7/8", "Digital 1/2", "Headphone 1/2"]}
45 # hardware inputs and stream playbacks
46 # format: function_id/channel_idx/panning-able
47 # NOTE: function_id = channel_idx = panning-able = labels["inputs"]
49 [[0x03, 0x04, 0x01, 0x02],
50 [[0x01, 0x02], [0x01, 0x02], [0x01, 0x02], [0x01, 0x02]],
51 [True, True, False, False]],
52 [[0x01, 0x02, 0x04, 0x03],
53 [[0x01, 0x02], [0x01, 0x02], [0x01, 0x02], [0x01, 0x02]],
54 [True, True, False, False]],
55 [[0x04, 0x05, 0x01, 0x02, 0x03],
56 [[0x01, 0x02], [0x01, 0x02], [0x01, 0x02], [0x01, 0x02], [0x01, 0x02]],
57 [True, True, False, False, False]],
58 [[0x03, 0x04, 0x02, 0x01, 0x01, 0x01, 0x01],
59 [[0x01, 0x02], [0x01, 0x02], [0x01, 0x02], [0x01, 0x02], [0x03, 0x04], [0x05, 0x06], [0x07, 0x08]],
60 [True, True, False, False, False, False, False]]
63 # jack sources except for headphone
64 # format: function_id/source id
65 # NOTE: "function_id" = labels["output"] - "Headphone 1/2/3/4"
66 # NOTE: "source_id" = labels["mixer"]
71 [0x00, 0x00, 0x00, 0x01]],
72 [[0x02, 0x03, 0x04, 0x05, 0x06],
73 [0x00, 0x00, 0x00, 0x00, 0x00, 0x01]]
77 # format: sink id/source id
78 # NOTE: "source_id" = labels["mixer"]
83 [0x00, 0x01, 0x02, 0x03]],
85 [0x02, 0x03, 0x04, 0x05, 0x06, 0x07]]
90 # NOTE: "function_id" = labels["output"]
94 [0x0c, 0x0d, 0x0e, 0x0f],
95 [0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f]
98 # Mixer inputs/outputs
99 # format: function_id/output_stereo_channel_id/input_id/input_stereo_channel_id
100 # NOTE: function_id = output_stereo_channel_id = labels["mixers"]
101 # NOTE: input_id = input_stereo_channel_id = labels["inputs"]
104 [[0x01, 0x02], [0x01, 0x02]],
105 [0x02, 0x03, 0x00, 0x01],
106 [[0x01, 0x02], [0x01, 0x02], [0x01, 0x02], [0x01, 0x02]]],
108 [[0x01, 0x02], [0x03, 0x04]],
109 [0x00, 0x01, 0x03, 0x02],
110 [[0x01, 0x02], [0x01, 0x02], [0x01, 0x02], [0x01, 0x02]]],
111 [[0x01, 0x02, 0x03, 0x04],
112 [[0x01, 0x02], [0x01, 0x02], [0x01, 0x02], [0x01, 0x02]],
113 [0x03, 0x04, 0x00, 0x01, 0x02],
114 [[0x01, 0x02], [0x01, 0x02], [0x01, 0x02], [0x01, 0x02], [0x01, 0x02]]],
115 [[0x01, 0x01, 0x01, 0x01, 0x01, 0x07],
116 [[0x01, 0x02], [0x03, 0x04], [0x05, 0x06], [0x07, 0x08], [0x09, 0x0a], [0x01, 0x02]],
117 [0x02, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00],
118 [[0x01, 0x02], [0x01, 0x02], [0x01, 0x02], [0x01, 0x02], [0x03, 0x04], [0x05, 0x06], [0x07, 0x08]]]
121 # Aux mixer inputs/outputs
122 # format: function_id/input_id/input_stereo_channel_id
123 # NOTE: input_id = labels["inputs"]
128 [0x09, 0x0a, 0x06, 0x07, 0x08],
129 [[0x01, 0x02], [0x01, 0x02], [0x01, 0x02], [0x01, 0x02], [0x01, 0x02]]],
131 [0x07, 0x08, 0x06, 0x05, 0x05, 0x05, 0x05],
132 [[0x01, 0x02], [0x01, 0x02], [0x01, 0x02], [0x01, 0x02], [0x03, 0x04], [0x05, 0x06], [0x07, 0x08]]]
135 def getDisplayTitle(self
):
136 model
= self
.configrom
.getModelId()
137 return self
.info
[model
][1]
139 def buildMixer(self
):
147 model
= self
.configrom
.getModelId()
148 if model
not in self
.info
:
151 self
.id = self
.info
[model
][0]
152 name
= self
.info
[model
][1]
154 tabs_layout
= QHBoxLayout(self
)
155 tabs
= QTabWidget(self
)
157 self
.addInputTab(tabs
)
159 if self
.aux
[self
.id] is not None:
161 self
.addOutputTab(tabs
)
163 tabs_layout
.addWidget(tabs
)
166 def addInputTab(self
, tabs
):
167 tab_input
= QWidget(self
)
168 tabs
.addTab(tab_input
, "In")
170 tab_input_layout
= QHBoxLayout()
171 tab_input
.setLayout(tab_input_layout
)
173 in_labels
= self
.labels
[self
.id]["inputs"]
174 in_ids
= self
.inputs
[self
.id][0]
175 in_ch_ids
= self
.inputs
[self
.id][1]
176 in_pan
= self
.inputs
[self
.id][2]
178 for i
in range(len(in_ids
)):
179 l_idx
= self
.inputs
[self
.id][1][i
][0]
180 r_idx
= self
.inputs
[self
.id][1][i
][1]
182 widget
= MAudio_BeBoB_Input_Widget(tab_input
)
183 tab_input_layout
.addWidget(widget
)
185 widget
.name
.setText(in_labels
[i
])
187 self
.Volumes
[widget
.l_sld
] = ["/Mixer/Feature_Volume_%d" % in_ids
[i
], l_idx
, widget
.r_sld
, r_idx
, widget
.link
]
188 self
.Volumes
[widget
.r_sld
] = ["/Mixer/Feature_Volume_%d" % in_ids
[i
], r_idx
, widget
.l_sld
, l_idx
, widget
.link
]
189 self
.Mutes
[widget
.mute
] = [widget
.l_sld
, widget
.r_sld
]
192 widget
.l_pan
.setDisabled(True)
193 widget
.r_pan
.setDisabled(True)
195 self
.Pannings
[widget
.l_pan
] = ["/Mixer/Feature_LRBalance_%d" % in_ids
[i
], l_idx
]
196 self
.Pannings
[widget
.r_pan
] = ["/Mixer/Feature_LRBalance_%d" % in_ids
[i
], r_idx
]
198 tab_input_layout
.addStretch()
201 def addMixTab(self
, tabs
):
202 tab_mix
= QWidget(self
)
203 tabs
.addTab(tab_mix
, "Mix")
205 tab_layout
= QHBoxLayout()
206 tab_mix
.setLayout(tab_layout
)
208 in_labels
= self
.labels
[self
.id]["inputs"]
209 in_idxs
= self
.inputs
[self
.id][0]
211 mix_labels
= self
.labels
[self
.id]["mixers"]
212 mix_idxs
= self
.mixers
[self
.id][0]
214 for i
in range(len(mix_idxs
)):
215 if mix_labels
[i
] == 'Aux 1/2':
218 grp
= QGroupBox(tab_mix
)
219 grp_layout
= QVBoxLayout()
220 grp
.setLayout(grp_layout
)
221 tab_layout
.addWidget(grp
)
224 grp_layout
.addWidget(label
)
226 label
.setText(mix_labels
[i
])
227 label
.setAlignment(Qt
.AlignCenter
)
228 label
.setSizePolicy(QSizePolicy
.Expanding
, QSizePolicy
.Minimum
)
230 for j
in range(len(in_idxs
)):
231 mix_in_id
= self
.mixers
[self
.id][2][j
]
233 button
= QToolButton(grp
)
234 grp_layout
.addWidget(button
)
236 button
.setSizePolicy(QSizePolicy
.Expanding
, QSizePolicy
.Minimum
)
237 button
.setText('%s In' % in_labels
[j
])
238 button
.setCheckable(True)
240 self
.Mixers
[button
] = ["/Mixer/EnhancedMixer_%d" % mix_idxs
[i
], mix_in_id
, j
, i
]
242 grp_layout
.addStretch()
243 tab_layout
.addStretch()
245 def addAuxTab(self
, tabs
):
247 def addLinkButton(parent
, layout
):
248 button
= QToolButton(grp
)
249 grp_layout
.addWidget(button
)
250 button
.setSizePolicy(QSizePolicy
.Expanding
, QSizePolicy
.Minimum
)
251 button
.setText('Link')
252 button
.setCheckable(True)
254 def addMuteButton(parent
, layout
):
255 button
= QToolButton(parent
)
256 layout
.addWidget(button
)
257 button
.setSizePolicy(QSizePolicy
.Expanding
, QSizePolicy
.Minimum
)
258 button
.setText('Mute')
259 button
.setCheckable(True)
263 tab_aux
= QWidget(self
)
264 tabs
.addTab(tab_aux
, "Aux")
266 layout
= QHBoxLayout()
267 tab_aux
.setLayout(layout
)
269 aux_label
= self
.labels
[self
.id]["mixers"][-1]
270 aux_id
= self
.aux
[self
.id][0]
272 aux_in_labels
= self
.labels
[self
.id]["inputs"]
273 aux_in_ids
= self
.aux
[self
.id][1]
275 for i
in range(len(aux_in_ids
)):
276 in_ch_l
= self
.aux
[self
.id][2][i
][0]
277 in_ch_r
= self
.aux
[self
.id][2][i
][1]
279 grp
= QGroupBox(tab_aux
)
280 grp_layout
= QVBoxLayout()
281 grp
.setLayout(grp_layout
)
282 layout
.addWidget(grp
)
285 grp_layout
.addWidget(label
)
286 label
.setText("%s\nIn" % aux_in_labels
[i
])
287 label
.setAlignment(Qt
.AlignCenter
)
289 grp_sld
= QGroupBox(grp
)
290 grp_sld_layout
= QHBoxLayout()
291 grp_sld
.setLayout(grp_sld_layout
)
292 grp_layout
.addWidget(grp_sld
)
294 l_sld
= QSlider(grp_sld
)
295 grp_sld_layout
.addWidget(l_sld
)
296 r_sld
= QSlider(grp_sld
)
297 grp_sld_layout
.addWidget(r_sld
)
299 button
= addLinkButton(grp
, grp_layout
)
300 self
.Volumes
[l_sld
] = ["/Mixer/Feature_Volume_%d" % aux_in_ids
[i
], in_ch_l
, r_sld
, in_ch_r
, button
]
301 self
.Volumes
[r_sld
] = ["/Mixer/Feature_Volume_%d" % aux_in_ids
[i
], in_ch_r
, l_sld
, in_ch_l
, button
]
303 button
= addMuteButton(grp
, grp_layout
)
304 self
.Mutes
[button
] = [l_sld
, r_sld
]
306 grp
= QGroupBox(tab_aux
)
307 grp_layout
= QVBoxLayout()
308 grp
.setLayout(grp_layout
)
309 layout
.addWidget(grp
)
312 grp_layout
.addWidget(label
)
313 label
.setText("%s\nOut" % aux_label
)
314 label
.setAlignment(Qt
.AlignCenter
)
316 grp_sld
= QGroupBox(grp
)
317 grp_sld_layout
= QHBoxLayout()
318 grp_sld
.setLayout(grp_sld_layout
)
319 grp_layout
.addWidget(grp_sld
)
321 l_sld
= QSlider(grp_sld
)
322 grp_sld_layout
.addWidget(l_sld
)
323 r_sld
= QSlider(grp_sld
)
324 grp_sld_layout
.addWidget(r_sld
)
326 button
= addLinkButton(grp
, grp_layout
)
327 self
.Volumes
[l_sld
] = ["/Mixer/Feature_Volume_%d" % aux_id
, 1, r_sld
, 2, button
]
328 self
.Volumes
[r_sld
] = ["/Mixer/Feature_Volume_%d" % aux_id
, 2, l_sld
, 1, button
]
330 button
= addMuteButton(grp
, grp_layout
)
331 self
.Mutes
[button
] = [l_sld
, r_sld
]
335 def addOutputTab(self
, tabs
):
336 tab_out
= QWidget(self
)
337 tabs
.addTab(tab_out
, "Out")
339 layout
= QHBoxLayout()
340 tab_out
.setLayout(layout
)
342 out_labels
= self
.labels
[self
.id]["outputs"]
343 out_ids
= self
.outputs
[self
.id]
345 mixer_labels
= self
.labels
[self
.id]["mixers"]
347 if self
.jack_src
[self
.id] is None:
348 for i
in range(len(out_ids
)):
349 label
= QLabel(tab_out
)
350 layout
.addWidget(label
)
351 label
.setText("%s Out is fixed to %s Out" % (mixer_labels
[i
], out_labels
[i
]))
354 mixer_ids
= self
.jack_src
[self
.id][1]
357 for i
in range(len(out_ids
)):
358 out_label
= out_labels
[i
]
359 if out_label
.find('Headphone') >= 0:
362 out_id
= self
.jack_src
[self
.id][0][i
]
364 widget
= MAudio_BeBoB_Output_Widget(tab_out
)
365 layout
.addWidget(widget
)
367 widget
.name
.setText(out_label
)
369 self
.Volumes
[widget
.l_sld
] = ["/Mixer/Feature_Volume_%d" % out_ids
[i
], 1, widget
.r_sld
, 2, widget
.link
]
370 self
.Volumes
[widget
.r_sld
] = ["/Mixer/Feature_Volume_%d" % out_ids
[i
], 2, widget
.l_sld
, 1, widget
.link
]
371 self
.Mutes
[widget
.mute
] = [widget
.l_sld
, widget
.r_sld
]
373 self
.Selectors
[widget
.cmb_src
] = ["/Mixer/Selector_%d" % out_id
]
375 for j
in range(len(mixer_ids
)):
376 if (i
!= j
and j
!= len(mixer_ids
) - 1):
378 widget
.cmb_src
.addItem("%s Out" % mixer_labels
[j
], mixer_ids
[j
])
381 for i
in range(len(out_ids
)):
382 out_label
= out_labels
[i
]
383 if out_label
.find('Headphone') < 0:
386 hp_label
= self
.labels
[self
.id]["outputs"][i
]
387 hp_id
= self
.hp_src
[self
.id][0][0]
389 mixer_labels
= self
.labels
[self
.id]["mixers"]
391 widget
= MAudio_BeBoB_Output_Widget(tab_out
)
392 layout
.addWidget(widget
)
394 widget
.name
.setText(hp_label
)
396 mixer_labels
= self
.labels
[self
.id]["mixers"]
397 mixer_ids
= self
.mixers
[self
.id][0]
399 self
.Volumes
[widget
.l_sld
] = ["/Mixer/Feature_Volume_%d" % out_ids
[i
], 1, widget
.r_sld
, 2, widget
.link
]
400 self
.Volumes
[widget
.r_sld
] = ["/Mixer/Feature_Volume_%d" % out_ids
[i
], 2, widget
.l_sld
, 1, widget
.link
]
401 self
.Mutes
[widget
.mute
] = [widget
.l_sld
, widget
.r_sld
]
403 for i
in range(len(mixer_ids
)):
404 widget
.cmb_src
.addItem("%s Out" % mixer_labels
[i
], mixer_ids
[i
])
407 self
.Selectors
[widget
.cmb_src
] = ["/Mixer/Selector_%d" % hp_id
]
409 QObject
.connect(widget
.cmb_src
, SIGNAL('activated(int)'), self
.update410HP
)
410 self
.FW410HP
= widget
.cmb_src
414 def initValues(self
):
415 for ctl
, params
in self
.Selectors
.items():
417 state
= self
.hw
.getDiscrete(path
)
418 ctl
.setCurrentIndex(state
)
419 QObject
.connect(ctl
, SIGNAL('activated(int)'), self
.updateSelector
)
421 # Right - Center - Left
422 # 0x8000 - 0x0000 - 0x0001 - 0x7FFE
423 # ..., -1, 0, +1, ...
424 for ctl
, params
in self
.Pannings
.items():
427 curr
= self
.hw
.getContignuous(path
, idx
)
428 state
= -(curr
/ 0x7FFE) * 50 + 50
430 QObject
.connect(ctl
, SIGNAL('valueChanged(int)'), self
.updatePanning
)
432 for ctl
, params
in self
.Volumes
.items():
439 db
= self
.hw
.getContignuous(path
, idx
)
440 vol
= self
.db2vol(db
)
442 QObject
.connect(ctl
, SIGNAL('valueChanged(int)'), self
.updateVolume
)
444 # to activate link button, a pair is checked twice, sign...
445 pair_db
= self
.hw
.getContignuous(path
, p_idx
)
447 link
.setChecked(True)
449 for ctl
, params
in self
.Mutes
.items():
450 QObject
.connect(ctl
, SIGNAL('clicked(bool)'), self
.updateMute
)
452 for ctl
, params
in self
.Mixers
.items():
455 mix_in_idx
= params
[2]
456 mix_out_idx
= params
[3]
457 in_ch_l
= self
.mixers
[self
.id][3][mix_in_idx
][0]
458 out_ch_l
= self
.mixers
[self
.id][1][mix_out_idx
][0]
459 # see /libffado/src/bebob/bebob_mixer.cpp
460 mux_id
= self
.getMultiplexedId(in_id
, in_ch_l
, out_ch_l
)
461 curr
= self
.hw
.getContignuous(path
, mux_id
);
466 ctl
.setChecked(state
)
467 QObject
.connect(ctl
, SIGNAL('clicked(bool)'), self
.updateMixer
)
473 def vol2db(self
, vol
):
474 return (log10(vol
+ 1) - 2) * 16384
476 def db2vol(self
, db
):
477 return pow(10, db
/ 16384 + 2) - 1
479 def getMultiplexedId(self
, in_id
, in_ch_l
, out_ch_l
):
480 # see /libffado/src/bebob/bebob_mixer.cpp
481 return (in_id
<< 8) |
(in_ch_l
<< 4) |
(out_ch_l
<< 0)
483 def updateSelector(self
, state
):
484 sender
= self
.sender()
485 path
= self
.Selectors
[sender
][0]
486 log
.debug("set %s to %d" % (path
, state
))
487 self
.hw
.setDiscrete(path
, state
)
489 def updatePanning(self
, state
):
490 sender
= self
.sender()
491 path
= self
.Pannings
[sender
][0]
492 idx
= self
.Pannings
[sender
][1]
493 value
= (state
- 50) * 0x7FFE / -50
494 log
.debug("set %s for %d to %d(%d)" % (path
, idx
, value
, state
))
495 self
.hw
.setContignuous(path
, value
, idx
)
497 def updateVolume(self
, vol
):
498 sender
= self
.sender()
499 path
= self
.Volumes
[sender
][0]
500 idx
= self
.Volumes
[sender
][1]
501 pair
= self
.Volumes
[sender
][2]
502 p_idx
= self
.Volumes
[sender
][3]
503 link
= self
.Volumes
[sender
][4]
505 db
= self
.vol2db(vol
)
506 log
.debug("set %s for %d to %d(%d)" % (path
, idx
, db
, vol
))
507 self
.hw
.setContignuous(path
, db
, idx
)
512 # device remeber gain even if muted
513 def updateMute(self
, state
):
514 sender
= self
.sender()
515 l_sld
= self
.Mutes
[sender
][0]
516 r_sld
= self
.Mutes
[sender
][1]
522 for w
in [l_sld
, r_sld
]:
523 path
= self
.Volumes
[w
][0]
524 self
.hw
.setContignuous(self
.Volumes
[w
][0], db
)
527 def updateMixer(self
, checked
):
533 sender
= self
.sender()
534 path
= self
.Mixers
[sender
][0]
535 in_id
= self
.Mixers
[sender
][1]
536 mix_in_idx
= self
.Mixers
[sender
][2]
537 mix_out_idx
= self
.Mixers
[sender
][3]
538 in_ch_l
= self
.mixers
[self
.id][3][mix_in_idx
][0]
539 out_ch_l
= self
.mixers
[self
.id][1][mix_out_idx
][0]
541 mux_id
= self
.getMultiplexedId(in_id
, in_ch_l
, out_ch_l
)
543 log
.debug("set %s for 0x%04X(%d/%d/%d) to %d" % (path
, mux_id
, in_id
, in_ch_l
, out_ch_l
, state
))
544 self
.hw
.setContignuous(path
, state
, mux_id
)
547 path
= "/Mixer/Selector_7"
548 sel
= self
.hw
.getDiscrete(path
)
554 path
= "/Mixer/EnhancedMixer_7"
559 mux_id
= self
.getMultiplexedId(in_id
, in_ch_l
, out_ch_l
)
560 state
= self
.hw
.getContignuous(path
, mux_id
)
561 if enbl
< 0 and state
== 0:
564 self
.hw
.setContignuous(path
, 0x8000, mux_id
)
565 # if inconsistency between Selector and Mixer, set AUX as default
567 self
.hw
.setDiscrete('/Mixer/Selector_7', 1);
570 self
.FW410HP
.setCurrentIndex(enbl
)
572 def update410HP(self
, state
):
573 hp_id
= self
.hp_src
[self
.id][0][0]
575 # each output from mixer can be multiplexed in headphone
576 # but here they are exclusive because of GUI simpleness, sigh...
577 path
= "/Mixer/EnhancedMixer_7"
582 mux_id
= self
.getMultiplexedId(in_id
, in_ch_l
, out_ch_l
)
587 self
.hw
.setContignuous(path
, value
, mux_id
)
589 # Mixer/Aux is selectable exclusively
590 path
= "/Mixer/Selector_7"
592 self
.hw
.setDiscrete(path
, sel
)