]>
code.delx.au - pulseaudio/blob - src/utils/qpaeq
2 # qpaeq is a equalizer interface for pulseaudio's equalizer sinks
3 # Copyright (C) 2009 Jason Newton <nevion@gmail.com
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License as
7 # published by the Free Software Foundation, either version 3 of the
8 # License, or (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU Affero General Public License for more details.
15 # You should have received a copy of the GNU Affero General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
22 from PyQt4
import QtGui
,QtCore
23 import dbus
.mainloop
.qt
25 except ImportError as e
:
26 sys
.stderr
.write('There was an error importing needed libraries\n'
27 'Make sure you have qt4 and dbus-python installed\n'
28 'The error that occured was:\n'
32 from functools
import partial
35 signal
.signal(signal
.SIGINT
, signal
.SIG_DFL
)
38 CORE_PATH
= "/org/pulseaudio/core1"
39 CORE_IFACE
= "org.PulseAudio.Core1"
42 if 'PULSE_DBUS_SERVER' in os
.environ
:
43 address
= os
.environ
['PULSE_DBUS_SERVER']
45 bus
= dbus
.SessionBus() # Should be UserBus, but D-Bus doesn't implement that yet.
46 server_lookup
= bus
.get_object('org.PulseAudio1', '/org/pulseaudio/server_lookup1')
47 address
= server_lookup
.Get('org.PulseAudio.ServerLookup1', 'Address', dbus_interface
='org.freedesktop.DBus.Properties')
48 return dbus
.connection
.Connection(address
)
49 except Exception as e
:
50 sys
.stderr
.write('There was an error connecting to pulseaudio, '
51 'please make sure you have the pulseaudio dbus '
52 'module loaded, exiting...\n')
56 #TODO: signals: sink Filter changed, sink reconfigured (window size) (sink iface)
57 #TODO: manager signals: new sink, removed sink, new profile, removed profile
58 #TODO: add support for changing of window_size 1000-fft_size (adv option)
59 #TODO: reconnect support loop 1 second trying to reconnect
60 #TODO: just resample the filters for profiles when loading to different sizes
62 prop_iface
='org.freedesktop.DBus.Properties'
63 eq_iface
='org.PulseAudio.Ext.Equalizing1.Equalizer'
64 device_iface
='org.PulseAudio.Core1.Device'
65 class QPaeq(QtGui
.QWidget
):
66 manager_path
='/org/pulseaudio/equalizing1'
67 manager_iface
='org.PulseAudio.Ext.Equalizing1.Manager'
68 core_iface
='org.PulseAudio.Core1'
69 core_path
='/org/pulseaudio/core1'
70 module_name
='module-equalizer-sink'
73 QtGui
.QWidget
.__init
__(self
)
74 self
.setWindowTitle('qpaeq')
75 self
.slider_widget
=None
77 self
.filter_state
=None
82 self
.connect_to_sink(self
.sinks
[0])
84 self
.setMinimumSize(self
.sizeHint())
86 def create_layout(self
):
87 self
.main_layout
=QtGui
.QVBoxLayout()
88 self
.setLayout(self
.main_layout
)
89 toprow_layout
=QtGui
.QHBoxLayout()
90 sizePolicy
= QtGui
.QSizePolicy(QtGui
.QSizePolicy
.Preferred
, QtGui
.QSizePolicy
.Fixed
)
91 sizePolicy
.setHorizontalStretch(0)
92 sizePolicy
.setVerticalStretch(0)
93 #sizePolicy.setHeightForWidth(self.profile_box.sizePolicy().hasHeightForWidth())
95 toprow_layout
.addWidget(QtGui
.QLabel('Sink'))
96 self
.sink_box
= QtGui
.QComboBox()
97 self
.sink_box
.setSizePolicy(sizePolicy
)
98 self
.sink_box
.setDuplicatesEnabled(False)
99 self
.sink_box
.setInsertPolicy(QtGui
.QComboBox
.InsertAlphabetically
)
100 #self.sink_box.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents)
101 toprow_layout
.addWidget(self
.sink_box
)
103 toprow_layout
.addWidget(QtGui
.QLabel('Channel'))
104 self
.channel_box
= QtGui
.QComboBox()
105 self
.channel_box
.setSizePolicy(sizePolicy
)
106 toprow_layout
.addWidget(self
.channel_box
)
108 toprow_layout
.addWidget(QtGui
.QLabel('Preset'))
109 self
.profile_box
= QtGui
.QComboBox()
110 self
.profile_box
.setSizePolicy(sizePolicy
)
111 self
.profile_box
.setInsertPolicy(QtGui
.QComboBox
.InsertAlphabetically
)
112 #self.profile_box.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents)
113 toprow_layout
.addWidget(self
.profile_box
)
115 large_icon_size
=self
.style().pixelMetric(QtGui
.QStyle
.PM_LargeIconSize
)
116 large_icon_size
=QtCore
.QSize(large_icon_size
,large_icon_size
)
117 save_profile
=QtGui
.QToolButton()
118 save_profile
.setIcon(self
.style().standardIcon(QtGui
.QStyle
.SP_DriveFDIcon
))
119 save_profile
.setIconSize(large_icon_size
)
120 save_profile
.setToolButtonStyle(QtCore
.Qt
.ToolButtonIconOnly
)
121 save_profile
.clicked
.connect(self
.save_profile
)
122 remove_profile
=QtGui
.QToolButton()
123 remove_profile
.setIcon(self
.style().standardIcon(QtGui
.QStyle
.SP_TrashIcon
))
124 remove_profile
.setIconSize(large_icon_size
)
125 remove_profile
.setToolButtonStyle(QtCore
.Qt
.ToolButtonIconOnly
)
126 remove_profile
.clicked
.connect(self
.remove_profile
)
127 toprow_layout
.addWidget(save_profile
)
128 toprow_layout
.addWidget(remove_profile
)
130 reset_button
= QtGui
.QPushButton('Reset')
131 reset_button
.clicked
.connect(self
.reset
)
132 toprow_layout
.addStretch()
133 toprow_layout
.addWidget(reset_button
)
134 self
.layout().addLayout(toprow_layout
)
136 self
.profile_box
.activated
.connect(self
.load_profile
)
137 self
.channel_box
.activated
.connect(self
.select_channel
)
138 def connect_to_sink(self
,name
):
139 #TODO: clear slots for profile buttons
141 #flush any pending saves for other sinks
142 if self
.filter_state
is not None:
143 self
.filter_state
.flush_state()
144 sink
=self
.connection
.get_object(object_path
=name
)
145 self
.sink_props
=dbus
.Interface(sink
,dbus_interface
=prop_iface
)
146 self
.sink
=dbus
.Interface(sink
,dbus_interface
=eq_iface
)
147 self
.filter_state
=FilterState(sink
)
148 #sample_rate,filter_rate,channels,channel)
150 self
.channel_box
.clear()
151 self
.channel_box
.addItem('All',self
.filter_state
.channels
)
152 for i
in range(self
.filter_state
.channels
):
153 self
.channel_box
.addItem('%d' %(i
+1,),i
)
154 self
.setMinimumSize(self
.sizeHint())
156 self
.set_slider_widget(SliderArray(self
.filter_state
))
159 #set the signal listener for this sink
160 core
=self
._get
_core
()
161 #temporary hack until signal filtering works properly
162 core
.ListenForSignal('',[dbus
.ObjectPath(self
.sink_name
),dbus
.ObjectPath(self
.manager_path
)])
163 #for x in ['FilterChanged']:
164 # core.ListenForSignal("%s.%s" %(self.eq_iface,x),[dbus.ObjectPath(self.sink_name)])
165 #core.ListenForSignal(self.eq_iface,[dbus.ObjectPath(self.sink_name)])
166 self
.sink
.connect_to_signal('FilterChanged',self
.read_filter
)
168 def set_slider_widget(self
,widget
):
170 if self
.slider_widget
is not None:
171 i
=layout
.indexOf(self
.slider_widget
)
172 layout
.removeWidget(self
.slider_widget
)
173 self
.slider_widget
.deleteLater()
174 layout
.insertWidget(i
,self
.slider_widget
)
176 layout
.addWidget(widget
)
177 self
.slider_widget
=widget
180 core_obj
=self
.connection
.get_object(object_path
=self
.core_path
)
181 core
=dbus
.Interface(core_obj
,dbus_interface
=self
.core_iface
)
183 def sink_added(self
,sink
):
184 #TODO: preserve selected sink
186 def sink_removed(self
,sink
):
187 #TODO: preserve selected sink, try connecting to backup otherwise
188 if sink
==self
.sink_name
:
189 #connect to new sink?
192 def save_profile(self
):
193 #popup dialog box for name
194 current
=self
.profile_box
.currentIndex()
195 profile
,ok
=QtGui
.QInputDialog
.getItem(self
,'Preset Name','Preset',self
.profiles
,current
)
196 if not ok
or profile
=='':
198 if profile
in self
.profiles
:
199 mbox
=QtGui
.QMessageBox(self
)
200 mbox
.setText('%s preset already exists'%(profile
,))
201 mbox
.setInformativeText('Do you want to save over it?')
202 mbox
.setStandardButtons(mbox
.Save|mbox
.Discard|mbox
.Cancel
)
203 mbox
.setDefaultButton(mbox
.Save
)
207 self
.sink
.SaveProfile(self
.filter_state
.channel
,dbus
.String(profile
))
208 if self
.filter_state
.channel
==self
.filter_state
.channels
:
209 for x
in range(1,self
.filter_state
.channels
):
210 self
.sink
.LoadProfile(x
,dbus
.String(profile
))
211 def remove_profile(self
):
212 #find active profile name, remove it
213 profile
=self
.profile_box
.currentText()
214 manager
=dbus
.Interface(self
.manager_obj
,dbus_interface
=self
.manager_iface
)
215 manager
.RemoveProfile(dbus
.String(profile
))
216 def load_profile(self
,x
):
217 profile
=self
.profile_box
.itemText(x
)
218 self
.filter_state
.load_profile(profile
)
219 def select_channel(self
,x
):
220 self
.filter_state
.channel
= self
.channel_box
.itemData(x
).toPyObject()
221 self
._set
_profile
_name
()
222 self
.filter_state
.readback()
224 #TODO: add back in preamp!
226 #main_layout.addLayout(self.create_slider(partial(self.update_coefficient,0),
229 def set_connection(self
):
230 self
.connection
=connect()
232 self
.manager_obj
=self
.connection
.get_object(object_path
=self
.manager_path
)
233 manager_props
=dbus
.Interface(self
.manager_obj
,dbus_interface
=prop_iface
)
235 self
.sinks
=manager_props
.Get(self
.manager_iface
,'EqualizedSinks')
236 except dbus
.exceptions
.DBusException
:
237 # probably module not yet loaded, try to load it:
239 core
=self
.connection
.get_object(object_path
=self
.core_path
)
240 core
.LoadModule(self
.module_name
,{},dbus_interface
=self
.core_iface
)
241 # yup, we don't need to re-create manager_obj and manager_props,
242 # these are late-bound
243 self
.sinks
=manager_props
.Get(self
.manager_iface
,'EqualizedSinks')
244 except dbus
.exceptions
.DBusException
:
245 sys
.stderr
.write('It seems that running pulseaudio does not support '
246 'equalizer features and loading %s module failed.\n'
247 'Exiting...\n' % self
.module_name
)
250 def set_callbacks(self
):
251 manager
=dbus
.Interface(self
.manager_obj
,dbus_interface
=self
.manager_iface
)
252 manager
.connect_to_signal('ProfilesChanged',self
.update_profiles
)
253 manager
.connect_to_signal('SinkAdded',self
.sink_added
)
254 manager
.connect_to_signal('SinkRemoved',self
.sink_removed
)
255 #self._get_core().ListenForSignal(self.manager_iface,[])
256 #self._get_core().ListenForSignal(self.manager_iface,[dbus.ObjectPath(self.manager_path)])
257 #core=self._get_core()
258 #for x in ['ProfilesChanged','SinkAdded','SinkRemoved']:
259 # core.ListenForSignal("%s.%s" %(self.manager_iface,x),[dbus.ObjectPath(self.manager_path)])
260 self
.update_profiles()
262 def update_profiles(self
):
263 #print('update profiles called!')
264 manager_props
=dbus
.Interface(self
.manager_obj
,dbus_interface
=prop_iface
)
265 self
.profiles
=manager_props
.Get(self
.manager_iface
,'Profiles')
266 self
.profile_box
.blockSignals(True)
267 self
.profile_box
.clear()
268 self
.profile_box
.addItems(self
.profiles
)
269 self
.profile_box
.blockSignals(False)
270 self
._set
_profile
_name
()
271 def update_sinks(self
):
272 self
.sink_box
.blockSignals(True)
273 self
.sink_box
.clear()
275 sink
=self
.connection
.get_object(object_path
=x
)
276 sink_props
=dbus
.Interface(sink
,dbus_interface
=prop_iface
)
277 simple_name
=sink_props
.Get(device_iface
,'Name')
278 self
.sink_box
.addItem(simple_name
,x
)
279 self
.sink_box
.blockSignals(False)
280 self
.sink_box
.setMinimumSize(self
.sink_box
.sizeHint())
281 def read_filter(self
):
282 #print(self.filter_frequencies)
283 self
.filter_state
.readback()
285 coefs
=dbus
.Array([1/math
.sqrt(2.0)]*(self
.filter_state
.filter_rate
//2+1))
287 self
.filter_state
.set_filter(preamp
,coefs
)
288 def _set_profile_name(self
):
289 self
.profile_box
.blockSignals(True)
290 profile_name
=self
.sink
.BaseProfile(self
.filter_state
.channel
)
291 if profile_name
is not None:
292 i
=self
.profile_box
.findText(profile_name
)
294 self
.profile_box
.setCurrentIndex(i
)
295 self
.profile_box
.blockSignals(False)
298 class SliderArray(QtGui
.QWidget
):
299 def __init__(self
,filter_state
,parent
=None):
300 super(SliderArray
,self
).__init
__(parent
)
301 #self.setStyleSheet('padding: 0px; border-width: 0px; margin: 0px;')
302 #self.setStyleSheet('font-size: 7pt; font-family: monospace;'+outline%('blue'))
303 self
.filter_state
=filter_state
304 self
.setLayout(QtGui
.QHBoxLayout())
306 self
.set_sub_array(SliderArraySub(self
.filter_state
))
307 self
.inhibit_resize
=0
308 def set_sub_array(self
,widget
):
309 if self
.sub_array
is not None:
310 self
.layout().removeWidget(self
.sub_array
)
311 self
.sub_array
.disconnect_signals()
312 self
.sub_array
.deleteLater()
313 self
.sub_array
=widget
314 self
.layout().addWidget(self
.sub_array
)
315 self
.sub_array
.connect_signals()
316 self
.filter_state
.readback()
317 def resizeEvent(self
,event
):
318 super(SliderArray
,self
).resizeEvent(event
)
319 if self
.inhibit_resize
==0:
320 self
.inhibit_resize
+=1
321 #self.add_sliders_to_fit()
322 t
=QtCore
.QTimer(self
)
323 t
.setSingleShot(True)
325 t
.timeout
.connect(partial(self
.add_sliders_to_fit
,event
))
327 def add_sliders_to_fit(self
,event
):
328 if event
.oldSize().width()>0 and event
.size().width()>0:
329 i
=len(self
.filter_state
.frequencies
)*int(round(float(event
.size().width())/event
.oldSize().width()))
331 i
=len(self
.filter_state
.frequencies
)
333 t_w
=self
.size().width()
334 def evaluate(filter_state
, target
, variable
):
335 base_freqs
=self
.filter_state
.freq_proper(self
.filter_state
.DEFAULT_FREQUENCIES
)
336 filter_state
._set
_frequency
_values
(subdivide(base_freqs
,variable
))
337 new_widget
=SliderArraySub(filter_state
)
338 w
=new_widget
.sizeHint().width()
340 def searcher(initial
,evaluator
):
342 def d(e
): return 1 if e
>=0 else -1
344 old_direction
=d(error
)
349 if direction
!=old_direction
:
351 #while direction<0 and error!=0:
355 return k
, evaluator(k
)
357 old_direction
=direction
358 searcher(i
,partial(evaluate
,self
.filter_state
,t_w
))
359 self
.set_sub_array(SliderArraySub(self
.filter_state
))
360 self
.inhibit_resize
-=1
362 class SliderArraySub(QtGui
.QWidget
):
363 def __init__(self
,filter_state
,parent
=None):
364 super(SliderArraySub
,self
).__init
__(parent
)
365 self
.filter_state
=filter_state
366 self
.setLayout(QtGui
.QGridLayout())
367 self
.slider
=[None]*len(self
.filter_state
.frequencies
)
368 self
.label
=[None]*len(self
.slider
)
369 #self.setStyleSheet('padding: 0px; border-width: 0px; margin: 0px;')
370 #self.setStyleSheet('font-size: 7pt; font-family: monospace;'+outline%('blue'))
372 #self.layout().setHorizontalSpacing(1)
373 def add_slider(slider
,label
, c
):
374 self
.layout().addWidget(slider
,0,c
,qt
.AlignHCenter
)
375 self
.layout().addWidget(label
,1,c
,qt
.AlignHCenter
)
376 self
.layout().setColumnMinimumWidth(c
,max(label
.sizeHint().width(),slider
.sizeHint().width()))
377 def create_slider(slider_label
):
378 slider
=QtGui
.QSlider(QtCore
.Qt
.Vertical
,self
)
379 label
=SliderLabel(slider_label
,filter_state
,self
)
380 slider
.setRange(-1000,2000)
381 slider
.setSingleStep(1)
382 return (slider
,label
)
383 self
.preamp_slider
,self
.preamp_label
=create_slider('Preamp')
384 add_slider(self
.preamp_slider
,self
.preamp_label
,0)
385 for i
,hz
in enumerate(self
.filter_state
.frequencies
):
386 slider
,label
=create_slider(self
.hz2label(hz
))
387 self
.slider
[i
]=slider
388 #slider.setStyleSheet('font-size: 7pt; font-family: monospace;'+outline%('red',))
391 add_slider(slider
,label
,i
+1)
392 def hz2label(self
, hz
):
395 elif hz
==self
.filter_state
.sample_rate
//2:
398 label_text
=hz2str(hz
)
401 def connect_signals(self
):
402 def connect(writer
,reader
,slider
,label
):
403 slider
.valueChanged
.connect(writer
)
404 self
.filter_state
.readFilter
.connect(reader
)
405 label_cb
=partial(slider
.setValue
,0)
406 label
.clicked
.connect(label_cb
)
409 self
.preamp_writer_cb
=self
.write_preamp
410 self
.preamp_reader_cb
=self
.sync_preamp
411 self
.preamp_label_cb
=connect(self
.preamp_writer_cb
,
412 self
.preamp_reader_cb
,
415 self
.writer_callbacks
=[None]*len(self
.slider
)
416 self
.reader_callbacks
=[None]*len(self
.slider
)
417 self
.label_callbacks
=[None]*len(self
.label
)
418 for i
in range(len(self
.slider
)):
419 self
.writer_callbacks
[i
]=partial(self
.write_coefficient
,i
)
420 self
.reader_callbacks
[i
]=partial(self
.sync_coefficient
,i
)
421 self
.label_callbacks
[i
]=connect(self
.writer_callbacks
[i
],
422 self
.reader_callbacks
[i
],
425 def disconnect_signals(self
):
426 def disconnect(writer
,reader
,label_cb
,slider
,label
):
427 slider
.valueChanged
.disconnect(writer
)
428 self
.filter_state
.readFilter
.disconnect(reader
)
429 label
.clicked
.disconnect(label_cb
)
430 disconnect(self
.preamp_writer_cb
, self
.preamp_reader_cb
,
431 self
.preamp_label_cb
, self
.preamp_slider
, self
.preamp_label
)
432 for i
in range(len(self
.slider
)):
433 disconnect(self
.writer_callbacks
[i
],
434 self
.reader_callbacks
[i
],
435 self
.label_callbacks
[i
],
439 def write_preamp(self
, v
):
440 self
.filter_state
.preamp
=self
.slider2coef(v
)
441 self
.filter_state
.seed()
442 def sync_preamp(self
):
443 self
.preamp_slider
.blockSignals(True)
444 self
.preamp_slider
.setValue(self
.coef2slider(self
.filter_state
.preamp
))
445 self
.preamp_slider
.blockSignals(False)
448 def write_coefficient(self
,i
,v
):
449 self
.filter_state
.coefficients
[i
]=self
.slider2coef(v
)/math
.sqrt(2.0)
450 self
.filter_state
.seed()
451 def sync_coefficient(self
,i
):
452 slider
=self
.slider
[i
]
453 slider
.blockSignals(True)
454 slider
.setValue(self
.coef2slider(math
.sqrt(2.0)*self
.filter_state
.coefficients
[i
]))
455 slider
.blockSignals(False)
458 return (1.0+(x
/1000.0))
461 return int((x
-1.0)*1000)
462 outline
='border-width: 1px; border-style: solid; border-color: %s;'
464 class SliderLabel(QtGui
.QLabel
):
465 clicked
=QtCore
.pyqtSignal()
466 def __init__(self
,label_text
,filter_state
,parent
=None):
467 super(SliderLabel
,self
).__init
__(parent
)
468 self
.setStyleSheet('font-size: 7pt; font-family: monospace;')
469 self
.setText(label_text
)
470 self
.setMinimumSize(self
.sizeHint())
471 def mouseDoubleClickEvent(self
, event
):
473 super(SliderLabel
,self
).mouseDoubleClickEvent(event
)
475 #until there are server side state savings, do it in the client but try and avoid
476 #simulaneous broadcasting situations
477 class FilterState(QtCore
.QObject
):
478 #DEFAULT_FREQUENCIES=map(float,[25,50,75,100,150,200,300,400,500,800,1e3,1.5e3,3e3,5e3,7e3,10e3,15e3,20e3])
479 DEFAULT_FREQUENCIES
=[31.75,63.5,125,250,500,1e3
,2e3
,4e3
,8e3
,16e3
]
480 readFilter
=QtCore
.pyqtSignal()
481 def __init__(self
,sink
):
482 super(FilterState
,self
).__init
__()
483 self
.sink_props
=dbus
.Interface(sink
,dbus_interface
=prop_iface
)
484 self
.sink
=dbus
.Interface(sink
,dbus_interface
=eq_iface
)
485 self
.sample_rate
=self
.get_eq_attr('SampleRate')
486 self
.filter_rate
=self
.get_eq_attr('FilterSampleRate')
487 self
.channels
=self
.get_eq_attr('NChannels')
488 self
.channel
=self
.channels
489 self
.set_frequency_values(self
.DEFAULT_FREQUENCIES
)
490 self
.sync_timer
=QtCore
.QTimer()
491 self
.sync_timer
.setSingleShot(True)
492 self
.sync_timer
.timeout
.connect(self
.save_state
)
494 def get_eq_attr(self
,attr
):
495 return self
.sink_props
.Get(eq_iface
,attr
)
496 def freq_proper(self
,xs
):
497 return [0]+xs
+[self
.sample_rate
//2]
498 def _set_frequency_values(self
,freqs
):
499 self
.frequencies
=freqs
500 #print('base',self.frequencies)
501 self
.filter_frequencies
=[int(round(x
)) for x
in self
.translate_rates(self
.filter_rate
,self
.sample_rate
,
503 self
.coefficients
=[0.0]*len(self
.frequencies
)
505 def set_frequency_values(self
,freqs
):
506 self
._set
_frequency
_values
(self
.freq_proper(freqs
))
508 def translate_rates(dst
,src
,rates
):
509 return list([x
*dst
/src
for x
in rates
])
511 self
.sink
.SeedFilter(self
.channel
,self
.filter_frequencies
,self
.coefficients
,self
.preamp
)
512 self
.sync_timer
.start(SYNC_TIMEOUT
)
514 coefs
,preamp
=self
.sink
.FilterAtPoints(self
.channel
,self
.filter_frequencies
)
515 self
.coefficients
=coefs
517 self
.readFilter
.emit()
518 def set_filter(self
,preamp
,coefs
):
519 self
.sink
.SetFilter(self
.channel
,dbus
.Array(coefs
),self
.preamp
)
520 self
.sync_timer
.start(SYNC_TIMEOUT
)
521 def save_state(self
):
522 print('saving state')
523 self
.sink
.SaveState()
524 def load_profile(self
,profile
):
525 self
.sink
.LoadProfile(self
.channel
,dbus
.String(profile
))
526 self
.sync_timer
.start(SYNC_TIMEOUT
)
527 def flush_state(self
):
528 if self
.sync_timer
.isActive():
529 self
.sync_timer
.stop()
544 return '%dKHz' %(hz
/(10.0**3),)
546 return '%.1fKHz' %(hz
/(10.0**3),)
548 def subdivide(xs
, t_points
):
549 while len(xs
)<t_points
:
552 for i
in range(1,len(m
),2):
553 m
[i
]=(m
[i
-1]+m
[i
+1])//2
555 p_drop
=len(xs
)-t_points
556 p_drop_left
=p_drop
//2
557 p_drop_right
=p_drop
-p_drop_left
559 #print('dropping %d, %d left, %d right' %(p_drop,p_drop_left,p_drop_right))
561 left
=xs
[0:p_drop_left
*2:2]+xs
[p_drop_left
*2:c
]
562 right
=list(reversed(xs
[c
:]))
563 right
=right
[0:p_drop_right
*2:2]+right
[p_drop_right
*2:]
564 right
=list(reversed(right
))
568 dbus
.mainloop
.qt
.DBusQtMainLoop(set_as_default
=True)
569 app
=QtGui
.QApplication(sys
.argv
)
572 sys
.exit(app
.exec_())
574 if __name__
=='__main__':