def input_device_changed(self, index): self.parent().ui.actionStart.setChecked(False) success, index = AudioBackend().select_input_device(index) self.comboBox_inputDevice.setCurrentIndex(index) if not success: # Note: the error message is a child of the settings dialog, so that # that dialog remains on top when the error message is closed error_message = QtWidgets.QErrorMessage(self) error_message.setWindowTitle("Input device error") error_message.showMessage( "Impossible to use the selected input device, reverting to the previous one" ) # reset the channels channels = AudioBackend().get_readable_current_channels() self.comboBox_firstChannel.clear() self.comboBox_secondChannel.clear() for channel in channels: self.comboBox_firstChannel.addItem(channel) self.comboBox_secondChannel.addItem(channel) first_channel = AudioBackend().get_current_first_channel() self.comboBox_firstChannel.setCurrentIndex(first_channel) second_channel = AudioBackend().get_current_second_channel() self.comboBox_secondChannel.setCurrentIndex(second_channel) self.parent().ui.actionStart.setChecked(True)
def __init__(self, logger): QMainWindow.__init__(self) # exception hook that logs to console, file, and display a message box self.errorDialogOpened = False sys.excepthook = self.excepthook # logger self.logger = logger # Setup the user interface self.ui = Ui_MainWindow() self.ui.setupUi(self) # Initialize the audio data ring buffer self.audiobuffer = AudioBuffer(self.logger) # Initialize the audio backend self.audiobackend = AudioBackend(self.logger) # signal containing new data from the audio callback thread, processed as numpy array self.audiobackend.new_data_available.connect(self.audiobuffer.handle_new_data) # this timer is used to update widgets that just need to display as fast as they can self.display_timer = QtCore.QTimer() self.display_timer.setInterval(SMOOTH_DISPLAY_TIMER_PERIOD_MS) # constant timing # slow timer self.slow_timer = QtCore.QTimer() self.slow_timer.setInterval(SLOW_TIMER_PERIOD_MS) # constant timing self.about_dialog = About_Dialog(self, self.logger, self.audiobackend, self.slow_timer) self.settings_dialog = Settings_Dialog(self, self.logger, self.audiobackend) self.centralwidget = CentralWidget(self.ui.centralwidget, self.logger, "central_widget", 0) self.centralLayout = QVBoxLayout(self.ui.centralwidget) self.centralLayout.setContentsMargins(0, 0, 0, 0) self.centralLayout.addWidget(self.centralwidget) self.dockmanager = DockManager(self, self.logger) # timer ticks self.display_timer.timeout.connect(self.centralwidget.canvasUpdate) self.display_timer.timeout.connect(self.dockmanager.canvasUpdate) # toolbar clicks self.ui.actionStart.triggered.connect(self.timer_toggle) self.ui.actionSettings.triggered.connect(self.settings_called) self.ui.actionAbout.triggered.connect(self.about_called) self.ui.actionNew_dock.triggered.connect(self.dockmanager.new_dock) # restore the settings and widgets geometries self.restoreAppState() # start timers self.timer_toggle() self.slow_timer.start() self.logger.push("Init finished, entering the main loop")
def stats_update(self): if not self.LabelStats.isVisible(): return label = "Chunk #%d\n"\ "Number of overflowed inputs (XRUNs): %d"\ % (AudioBackend().chunk_number, AudioBackend().xruns) self.LabelStats.setText(label)
def timer_toggle(self): if self.display_timer.isActive(): self.logger.info("Timer stop") self.display_timer.stop() self.ui.actionStart.setText("Start") AudioBackend().pause() self.dockmanager.pause() else: self.logger.info("Timer start") self.display_timer.start() self.ui.actionStart.setText("Stop") AudioBackend().restart() self.dockmanager.restart()
def device_changed(self, index): device = AudioBackend().output_devices[index] # save current stream in case we need to restore it previous_stream = self.stream previous_device = self.device error_message = "" Logger().push("Trying to write to output device " + device['name']) # first see if the format is supported by PortAudio try: success = AudioBackend().is_output_format_supported(device, np.int16) except Exception as exception: Logger().push("Format is not supported: " + str(exception)) success = False if success: try: self.stream = AudioBackend().open_output_stream(device, self.audio_callback) self.device = device self.stream.start() if self.state not in [STARTING, PLAYING]: self.stream.stop() success = True except OSError as error: Logger().push("Fail: " + str(error)) success = False if success: Logger().push("Success") previous_stream.stop() else: if self.stream is not None: self.stream.stop() # restore previous stream self.stream = previous_stream self.device = previous_device # Note: the error message is a child of the settings dialog, so that # that dialog remains on top when the error message is closed error_message = QtWidgets.QErrorMessage(self.settings_dialog) error_message.setWindowTitle("Output device error") error_message.showMessage("Impossible to use the selected output device, reverting to the previous one. Reason is: " + error_message) self.settings_dialog.combobox_output_device.setCurrentIndex(AudioBackend().output_devices.index(self.device))
def __init__(self, logger): QMainWindow.__init__(self) # logger self.logger = logger # Setup the user interface self.ui = Ui_MainWindow() self.ui.setupUi(self) # sharedGLWidget is a hidden GL widget that will be used as a parent # to all QGLWidgets so that they have the same GL context, and can # share display lists, etc. self.sharedGLWidget = QtOpenGL.QGLWidget(self) self.sharedGLWidget.hide() # Initialize the audio data ring buffer self.audiobuffer = AudioBuffer(self.logger) # Initialize the audio backend self.audiobackend = AudioBackend(self.logger) # this timer is used to update widgets that just need to display as fast as they can self.display_timer = QtCore.QTimer() self.display_timer.setInterval(SMOOTH_DISPLAY_TIMER_PERIOD_MS) # constant timing # slow timer self.slow_timer = QtCore.QTimer() self.slow_timer.setInterval(SLOW_TIMER_PERIOD_MS) # constant timing self.about_dialog = About_Dialog(self, self.logger, self.audiobackend, self.slow_timer) self.settings_dialog = Settings_Dialog(self, self.logger, self.audiobackend) self.centralwidget = CentralWidget(self.ui.centralwidget, self.sharedGLWidget, self.logger, "central_widget", 0) self.centralLayout = QVBoxLayout(self.ui.centralwidget) self.centralLayout.setContentsMargins(0, 0, 0, 0) self.centralLayout.addWidget(self.centralwidget) self.dockmanager = DockManager(self, self.sharedGLWidget, self.logger) # timer ticks self.connect(self.display_timer, QtCore.SIGNAL('timeout()'), self.update_buffer) self.connect(self.display_timer, QtCore.SIGNAL('timeout()'), self.centralwidget.update) self.connect(self.display_timer, QtCore.SIGNAL('timeout()'), self.dockmanager.update) # toolbar clicks self.connect(self.ui.actionStart, QtCore.SIGNAL('triggered()'), self.timer_toggle) self.connect(self.ui.actionSettings, QtCore.SIGNAL('triggered()'), self.settings_called) self.connect(self.ui.actionAbout, QtCore.SIGNAL('triggered()'), self.about_called) self.connect(self.ui.actionNew_dock, QtCore.SIGNAL('triggered()'), self.dockmanager.new_dock) # restore the settings and widgets geometries self.restoreAppState() # start timers self.timer_toggle() self.slow_timer.start() self.logger.push("Init finished, entering the main loop")
def __init__(self, parent): QtWidgets.QDialog.__init__(self, parent) Ui_Settings_Dialog.__init__(self) self.logger = logging.getLogger(__name__) # Setup the user interface self.setupUi(self) devices = AudioBackend().get_readable_devices_list() if devices == []: # no audio input device: display a message and exit QtWidgets.QMessageBox.critical(self, no_input_device_title, no_input_device_message) QtCore.QTimer.singleShot(0, self.exitOnInit) sys.exit(1) return for device in devices: self.comboBox_inputDevice.addItem(device) channels = AudioBackend().get_readable_current_channels() for channel in channels: self.comboBox_firstChannel.addItem(channel) self.comboBox_secondChannel.addItem(channel) current_device = AudioBackend().get_readable_current_device() self.comboBox_inputDevice.setCurrentIndex(current_device) first_channel = AudioBackend().get_current_first_channel() self.comboBox_firstChannel.setCurrentIndex(first_channel) second_channel = AudioBackend().get_current_second_channel() self.comboBox_secondChannel.setCurrentIndex(second_channel) # signals self.comboBox_inputDevice.currentIndexChanged.connect( self.input_device_changed) self.comboBox_firstChannel.activated.connect( self.first_channel_changed) self.comboBox_secondChannel.activated.connect( self.second_channel_changed) self.radioButton_single.toggled.connect( self.single_input_type_selected) self.radioButton_duo.toggled.connect(self.duo_input_type_selected)
def draw(self, painter, xMap, yMap, rect): # update the spectrogram according to possibly new canvas dimensions self.frequency_resampler.setnsamples(rect.height()) self.resampler.set_height(rect.height()) self.canvasscaledspectrogram.setcanvas_height(rect.height()) # print self.jitter_s, self.T, rect.width(), rect.width()*(1 + self.jitter_s/self.T) jitter_pix = rect.width() * self.jitter_s / self.T self.canvasscaledspectrogram.setcanvas_width(rect.width() + jitter_pix) screen_rate_frac = Fraction(rect.width(), int(self.T * 1000)) self.resampler.set_ratio(self.sfft_rate_frac, screen_rate_frac) # time advance # This function is meant to be called at paintevent time, for better time sync. pixmap = self.canvasscaledspectrogram.getpixmap() offset = self.canvasscaledspectrogram.getpixmapoffset( delay=jitter_pix / 2) if self.isPlaying: delta_t = self.timer.nsecsElapsed() * 1e-9 self.timer.restart() pixel_advance = delta_t / (self.T + self.jitter_s) * rect.width() self.canvasscaledspectrogram.addPixelAdvance(pixel_advance) time = AudioBackend().get_stream_time() time_delay = time - self.last_data_time pixel_delay = rect.width() * time_delay / self.T draw_delay = time - self.last_time self.last_time = time offset += pixel_delay rolling = True if rolling: # draw the whole canvas with a selected portion of the pixmap hints = painter.renderHints() # enable bilinear pixmap transformation painter.setRenderHints(hints | QtGui.QPainter.SmoothPixmapTransform) # Note: nstead of a generic bilinear transformation, a specialized one could be more efficient, # since no transformation is needed in y, and the sampling rate is already known to be ok in x. sw = rect.width() sh = rect.height() source_rect = QtCore.QRectF(offset, 0, sw, sh) # QRectF since the offset and width may be non-integer painter.drawPixmap(QtCore.QRectF(rect), pixmap, source_rect) else: sw = rect.width() sh = rect.height() source_rect = QtCore.QRectF(0, 0, sw, sh) painter.drawPixmap(QtCore.QRectF(rect), pixmap, source_rect)
def __init__(self, parent): super().__init__(parent) self.setObjectName("Spectrogram_Widget") self.gridLayout = QtWidgets.QGridLayout(self) self.gridLayout.setObjectName("gridLayout") self.PlotZoneImage = ImagePlot(self) self.PlotZoneImage.setObjectName("PlotZoneImage") self.gridLayout.addWidget(self.PlotZoneImage, 0, 1, 1, 1) self.audiobuffer = None # initialize the class instance that will do the fft self.proc = audioproc() self.maxfreq = DEFAULT_MAXFREQ self.proc.set_maxfreq(self.maxfreq) self.minfreq = DEFAULT_MINFREQ self.fft_size = 2**DEFAULT_FFT_SIZE * 32 self.proc.set_fftsize(self.fft_size) self.spec_min = DEFAULT_SPEC_MIN self.spec_max = DEFAULT_SPEC_MAX self.weighting = DEFAULT_WEIGHTING self.update_weighting() self.freq = self.proc.get_freq_scale() self.timerange_s = DEFAULT_TIMERANGE self.canvas_width = 100. self.old_index = 0 self.overlap = 3. / 4. self.overlap_frac = Fraction(3, 4) self.dT_s = self.fft_size * (1. - self.overlap) / float(SAMPLING_RATE) self.PlotZoneImage.setlog10freqscale() # DEFAULT_FREQ_SCALE = 1 #log10 self.PlotZoneImage.setfreqrange(self.minfreq, self.maxfreq) self.PlotZoneImage.setspecrange(self.spec_min, self.spec_max) self.PlotZoneImage.setweighting(self.weighting) self.PlotZoneImage.settimerange(self.timerange_s, self.dT_s) self.update_jitter() sfft_rate_frac = Fraction(SAMPLING_RATE, self.fft_size) / ( Fraction(1) - self.overlap_frac) / 1000 self.PlotZoneImage.set_sfft_rate(sfft_rate_frac) # initialize the settings dialog self.settings_dialog = Spectrogram_Settings_Dialog(self) AudioBackend().underflow.connect( self.PlotZoneImage.plotImage.canvasscaledspectrogram.syncOffsets) self.last_data_time = 0. self.mustRestart = False
def device_changed(self, index): device = AudioBackend().output_devices[index] # save current stream in case we need to restore it previous_stream = self.stream previous_device = self.device self.logger.info("Trying to write to output device '%s'", device['name']) # first see if the format is supported by PortAudio try: AudioBackend().is_output_format_supported(device, np.int16) except sounddevice.PortAudioError as err: self.on_device_change_error( previous_stream, previous_device, "Format is not supported: {0}".format(err)) return try: self.stream = AudioBackend().open_output_stream( device, self.audio_callback) self.device = device self.stream.start() if self.state not in [STARTING, PLAYING]: self.stream.stop() except (sounddevice.PortAudioError, OSError) as err: self.on_device_change_error( previous_stream, previous_device, "Failed to open output device: {0}".format(err)) return self.logger.info("Success") previous_stream.stop() self.settings_dialog.combobox_output_device.setCurrentIndex( AudioBackend().output_devices.index(self.device))
def second_channel_changed(self, index): self.parent().ui.actionStart.setChecked(False) success, index = AudioBackend().select_second_channel(index) self.comboBox_secondChannel.setCurrentIndex(index) if not success: # Note: the error message is a child of the settings dialog, so that # that dialog remains on top when the error message is closed error_message = QtWidgets.QErrorMessage(self) error_message.setWindowTitle("Input device error") error_message.showMessage( "Impossible to use the selected channel as the second channel, reverting to the previous one" ) self.parent().ui.actionStart.setChecked(True)
def on_device_change_error(self, previous_stream, previous_device, message): self.logger.exception(message) if self.stream is not None: self.stream.stop() # restore previous stream self.stream = previous_stream self.device = previous_device # Note: the error message is a child of the settings dialog, so that # that dialog remains on top when the error message is closed error_message = QtWidgets.QErrorMessage(self.settings_dialog) error_message.setWindowTitle("Output device error") error_message.showMessage( "Impossible to use the selected output device, reverting to the previous one. Reason is: " + message) self.settings_dialog.combobox_output_device.setCurrentIndex( AudioBackend().output_devices.index(self.device))
class Friture( QMainWindow, ): def __init__(self, logger): QMainWindow.__init__(self) # exception hook that logs to console, file, and display a message box self.errorDialogOpened = False sys.excepthook = self.excepthook # logger self.logger = logger # Setup the user interface self.ui = Ui_MainWindow() self.ui.setupUi(self) # Initialize the audio data ring buffer self.audiobuffer = AudioBuffer(self.logger) # Initialize the audio backend self.audiobackend = AudioBackend(self.logger) # signal containing new data from the audio callback thread, processed as numpy array self.audiobackend.new_data_available.connect( self.audiobuffer.handle_new_data) # this timer is used to update widgets that just need to display as fast as they can self.display_timer = QtCore.QTimer() self.display_timer.setInterval( SMOOTH_DISPLAY_TIMER_PERIOD_MS) # constant timing # slow timer self.slow_timer = QtCore.QTimer() self.slow_timer.setInterval(SLOW_TIMER_PERIOD_MS) # constant timing self.about_dialog = About_Dialog(self, self.logger, self.audiobackend, self.slow_timer) self.settings_dialog = Settings_Dialog(self, self.logger, self.audiobackend) self.centralwidget = CentralWidget(self.ui.centralwidget, self.logger, "central_widget", 0) self.centralLayout = QVBoxLayout(self.ui.centralwidget) self.centralLayout.setContentsMargins(0, 0, 0, 0) self.centralLayout.addWidget(self.centralwidget) self.dockmanager = DockManager(self, self.logger) # timer ticks self.display_timer.timeout.connect(self.centralwidget.canvasUpdate) self.display_timer.timeout.connect(self.dockmanager.canvasUpdate) # toolbar clicks self.ui.actionStart.triggered.connect(self.timer_toggle) self.ui.actionSettings.triggered.connect(self.settings_called) self.ui.actionAbout.triggered.connect(self.about_called) self.ui.actionNew_dock.triggered.connect(self.dockmanager.new_dock) # restore the settings and widgets geometries self.restoreAppState() # start timers self.timer_toggle() self.slow_timer.start() self.logger.push("Init finished, entering the main loop") # exception hook that logs to console, file, and display a message box def excepthook(self, exception_type, exception_value, traceback_object): gui_message = fileexcepthook(exception_type, exception_value, traceback_object) # we do not want to flood the user with message boxes when the error happens repeatedly on each timer event if not self.errorDialogOpened: self.errorDialogOpened = True errorBox(gui_message) self.errorDialogOpened = False # slot def settings_called(self): self.settings_dialog.show() # slot def about_called(self): self.about_dialog.show() # event handler def closeEvent(self, event): self.audiobackend.close() self.saveAppState() event.accept() # method def saveAppState(self): settings = QtCore.QSettings("Friture", "Friture") settings.beginGroup("Docks") self.dockmanager.saveState(settings) settings.endGroup() settings.beginGroup("CentralWidget") self.centralwidget.saveState(settings) settings.endGroup() settings.beginGroup("MainWindow") windowGeometry = self.saveGeometry() settings.setValue("windowGeometry", windowGeometry) windowState = self.saveState() settings.setValue("windowState", windowState) settings.endGroup() settings.beginGroup("AudioBackend") self.settings_dialog.saveState(settings) settings.endGroup() # method def restoreAppState(self): settings = QtCore.QSettings("Friture", "Friture") settings.beginGroup("Docks") self.dockmanager.restoreState(settings) settings.endGroup() settings.beginGroup("CentralWidget") self.centralwidget.restoreState(settings) settings.endGroup() settings.beginGroup("MainWindow") self.restoreGeometry( settings.value("windowGeometry", type=QtCore.QByteArray)) self.restoreState(settings.value("windowState", type=QtCore.QByteArray)) settings.endGroup() settings.beginGroup("AudioBackend") self.settings_dialog.restoreState(settings) settings.endGroup() # slot def timer_toggle(self): if self.display_timer.isActive(): self.logger.push("Timer stop") self.display_timer.stop() self.ui.actionStart.setText("Start") self.audiobackend.pause() self.centralwidget.pause() self.dockmanager.pause() else: self.logger.push("Timer start") self.display_timer.start() self.ui.actionStart.setText("Stop") self.audiobackend.restart() self.centralwidget.restart() self.dockmanager.restart()
def __init__(self, logger): QMainWindow.__init__(self) # logger self.logger = logger # Setup the user interface self.ui = Ui_MainWindow() self.ui.setupUi(self) self.settings_dialog = Settings_Dialog(self) self.about_dialog = About_Dialog(self) self.chunk_number = 0 self.buffer_timer_time = 0. self.cpu_percent = 0. # Initialize the audio data ring buffer self.audiobuffer = AudioBuffer() # Initialize the audio backend self.audiobackend = AudioBackend(self.logger) devices = self.audiobackend.get_readable_devices_list() for device in devices: self.settings_dialog.comboBox_inputDevice.addItem(device) channels = self.audiobackend.get_readable_current_channels() for channel in channels: self.settings_dialog.comboBox_firstChannel.addItem(channel) self.settings_dialog.comboBox_secondChannel.addItem(channel) current_device = self.audiobackend.get_readable_current_device() self.settings_dialog.comboBox_inputDevice.setCurrentIndex(current_device) first_channel = self.audiobackend.get_current_first_channel() self.settings_dialog.comboBox_firstChannel.setCurrentIndex(first_channel) second_channel = self.audiobackend.get_current_second_channel() self.settings_dialog.comboBox_secondChannel.setCurrentIndex(second_channel) # this timer is used to update widgets that just need to display as fast as they can self.display_timer = QtCore.QTimer() self.display_timer.setInterval(SMOOTH_DISPLAY_TIMER_PERIOD_MS) # constant timing # slow timer self.slow_timer = QtCore.QTimer() self.slow_timer.setInterval(1000) # constant timing self.centralwidget = CentralWidget(self.ui.centralwidget, self.logger, "central_widget", 0) self.centralLayout = QVBoxLayout(self.ui.centralwidget) self.centralLayout.setContentsMargins(0, 0, 0, 0) self.centralLayout.addWidget(self.centralwidget) # timer ticks self.connect(self.display_timer, QtCore.SIGNAL('timeout()'), self.update_buffer) self.connect(self.display_timer, QtCore.SIGNAL('timeout()'), self.statistics) # timer ticks self.connect(self.slow_timer, QtCore.SIGNAL('timeout()'), self.get_cpu_percent) # toolbar clicks self.connect(self.ui.actionStart, QtCore.SIGNAL('triggered()'), self.timer_toggle) self.connect(self.ui.actionSettings, QtCore.SIGNAL('triggered()'), self.settings_called) self.connect(self.ui.actionAbout, QtCore.SIGNAL('triggered()'), self.about_called) self.connect(self.ui.actionNew_dock, QtCore.SIGNAL('triggered()'), self.new_dock_called) # settings signals self.connect(self.settings_dialog.comboBox_inputDevice, QtCore.SIGNAL('currentIndexChanged(int)'), self.input_device_changed) self.connect(self.settings_dialog.comboBox_firstChannel, QtCore.SIGNAL('currentIndexChanged(int)'), self.first_channel_changed) self.connect(self.settings_dialog.comboBox_secondChannel, QtCore.SIGNAL('currentIndexChanged(int)'), self.second_channel_changed) self.connect(self.settings_dialog.radioButton_single, QtCore.SIGNAL('toggled(bool)'), self.single_input_type_selected) self.connect(self.settings_dialog.radioButton_duo, QtCore.SIGNAL('toggled(bool)'), self.duo_input_type_selected) self.connect(self.settings_dialog.doubleSpinBox_delay, QtCore.SIGNAL('valueChanged(double)'), self.delay_changed) # log change self.connect(self.logger, QtCore.SIGNAL('logChanged'), self.log_changed) self.connect(self.about_dialog.log_scrollarea.verticalScrollBar(), QtCore.SIGNAL('rangeChanged(int,int)'), self.log_scroll_range_changed) # restore the settings and widgets geometries self.restoreAppState() # start timers self.timer_toggle() self.slow_timer.start() self.logger.push("Init finished, entering the main loop")
def restart(self): self.isPlaying = True self.last_time = AudioBackend().get_stream_time() self.timer.restart()
def __init__(self, logger): QMainWindow.__init__(self) # logger self.logger = logger # Setup the user interface self.ui = Ui_MainWindow() self.ui.setupUi(self) self.settings_dialog = Settings_Dialog(self) self.about_dialog = About_Dialog(self) self.chunk_number = 0 self.buffer_timer_time = 0. self.cpu_percent = 0. # Initialize the audio data ring buffer self.audiobuffer = AudioBuffer() # Initialize the audio backend self.audiobackend = AudioBackend(self.logger) devices = self.audiobackend.get_readable_devices_list() for device in devices: self.settings_dialog.comboBox_inputDevice.addItem(device) channels = self.audiobackend.get_readable_current_channels() for channel in channels: self.settings_dialog.comboBox_firstChannel.addItem(channel) self.settings_dialog.comboBox_secondChannel.addItem(channel) current_device = self.audiobackend.get_readable_current_device() self.settings_dialog.comboBox_inputDevice.setCurrentIndex( current_device) first_channel = self.audiobackend.get_current_first_channel() self.settings_dialog.comboBox_firstChannel.setCurrentIndex( first_channel) second_channel = self.audiobackend.get_current_second_channel() self.settings_dialog.comboBox_secondChannel.setCurrentIndex( second_channel) # this timer is used to update widgets that just need to display as fast as they can self.display_timer = QtCore.QTimer() self.display_timer.setInterval( SMOOTH_DISPLAY_TIMER_PERIOD_MS) # constant timing # slow timer self.slow_timer = QtCore.QTimer() self.slow_timer.setInterval(1000) # constant timing self.centralwidget = CentralWidget(self.ui.centralwidget, self.logger, "central_widget", 0) self.centralLayout = QVBoxLayout(self.ui.centralwidget) self.centralLayout.setContentsMargins(0, 0, 0, 0) self.centralLayout.addWidget(self.centralwidget) # timer ticks self.connect(self.display_timer, QtCore.SIGNAL('timeout()'), self.update_buffer) self.connect(self.display_timer, QtCore.SIGNAL('timeout()'), self.statistics) # timer ticks self.connect(self.slow_timer, QtCore.SIGNAL('timeout()'), self.get_cpu_percent) # toolbar clicks self.connect(self.ui.actionStart, QtCore.SIGNAL('triggered()'), self.timer_toggle) self.connect(self.ui.actionSettings, QtCore.SIGNAL('triggered()'), self.settings_called) self.connect(self.ui.actionAbout, QtCore.SIGNAL('triggered()'), self.about_called) self.connect(self.ui.actionNew_dock, QtCore.SIGNAL('triggered()'), self.new_dock_called) # settings signals self.connect(self.settings_dialog.comboBox_inputDevice, QtCore.SIGNAL('currentIndexChanged(int)'), self.input_device_changed) self.connect(self.settings_dialog.comboBox_firstChannel, QtCore.SIGNAL('currentIndexChanged(int)'), self.first_channel_changed) self.connect(self.settings_dialog.comboBox_secondChannel, QtCore.SIGNAL('currentIndexChanged(int)'), self.second_channel_changed) self.connect(self.settings_dialog.radioButton_single, QtCore.SIGNAL('toggled(bool)'), self.single_input_type_selected) self.connect(self.settings_dialog.radioButton_duo, QtCore.SIGNAL('toggled(bool)'), self.duo_input_type_selected) self.connect(self.settings_dialog.doubleSpinBox_delay, QtCore.SIGNAL('valueChanged(double)'), self.delay_changed) # log change self.connect(self.logger, QtCore.SIGNAL('logChanged'), self.log_changed) self.connect(self.about_dialog.log_scrollarea.verticalScrollBar(), QtCore.SIGNAL('rangeChanged(int,int)'), self.log_scroll_range_changed) # restore the settings and widgets geometries self.restoreAppState() # start timers self.timer_toggle() self.slow_timer.start() self.logger.push("Init finished, entering the main loop")
class Friture(QMainWindow, ): def __init__(self, logger): QMainWindow.__init__(self) # logger self.logger = logger # Setup the user interface self.ui = Ui_MainWindow() self.ui.setupUi(self) self.settings_dialog = Settings_Dialog(self) self.about_dialog = About_Dialog(self) self.chunk_number = 0 self.buffer_timer_time = 0. self.cpu_percent = 0. # Initialize the audio data ring buffer self.audiobuffer = AudioBuffer() # Initialize the audio backend self.audiobackend = AudioBackend(self.logger) devices = self.audiobackend.get_readable_devices_list() for device in devices: self.settings_dialog.comboBox_inputDevice.addItem(device) channels = self.audiobackend.get_readable_current_channels() for channel in channels: self.settings_dialog.comboBox_firstChannel.addItem(channel) self.settings_dialog.comboBox_secondChannel.addItem(channel) current_device = self.audiobackend.get_readable_current_device() self.settings_dialog.comboBox_inputDevice.setCurrentIndex(current_device) first_channel = self.audiobackend.get_current_first_channel() self.settings_dialog.comboBox_firstChannel.setCurrentIndex(first_channel) second_channel = self.audiobackend.get_current_second_channel() self.settings_dialog.comboBox_secondChannel.setCurrentIndex(second_channel) # this timer is used to update widgets that just need to display as fast as they can self.display_timer = QtCore.QTimer() self.display_timer.setInterval(SMOOTH_DISPLAY_TIMER_PERIOD_MS) # constant timing # slow timer self.slow_timer = QtCore.QTimer() self.slow_timer.setInterval(1000) # constant timing self.centralwidget = CentralWidget(self.ui.centralwidget, self.logger, "central_widget", 0) self.centralLayout = QVBoxLayout(self.ui.centralwidget) self.centralLayout.setContentsMargins(0, 0, 0, 0) self.centralLayout.addWidget(self.centralwidget) # timer ticks self.connect(self.display_timer, QtCore.SIGNAL('timeout()'), self.update_buffer) self.connect(self.display_timer, QtCore.SIGNAL('timeout()'), self.statistics) # timer ticks self.connect(self.slow_timer, QtCore.SIGNAL('timeout()'), self.get_cpu_percent) # toolbar clicks self.connect(self.ui.actionStart, QtCore.SIGNAL('triggered()'), self.timer_toggle) self.connect(self.ui.actionSettings, QtCore.SIGNAL('triggered()'), self.settings_called) self.connect(self.ui.actionAbout, QtCore.SIGNAL('triggered()'), self.about_called) self.connect(self.ui.actionNew_dock, QtCore.SIGNAL('triggered()'), self.new_dock_called) # settings signals self.connect(self.settings_dialog.comboBox_inputDevice, QtCore.SIGNAL('currentIndexChanged(int)'), self.input_device_changed) self.connect(self.settings_dialog.comboBox_firstChannel, QtCore.SIGNAL('currentIndexChanged(int)'), self.first_channel_changed) self.connect(self.settings_dialog.comboBox_secondChannel, QtCore.SIGNAL('currentIndexChanged(int)'), self.second_channel_changed) self.connect(self.settings_dialog.radioButton_single, QtCore.SIGNAL('toggled(bool)'), self.single_input_type_selected) self.connect(self.settings_dialog.radioButton_duo, QtCore.SIGNAL('toggled(bool)'), self.duo_input_type_selected) self.connect(self.settings_dialog.doubleSpinBox_delay, QtCore.SIGNAL('valueChanged(double)'), self.delay_changed) # log change self.connect(self.logger, QtCore.SIGNAL('logChanged'), self.log_changed) self.connect(self.about_dialog.log_scrollarea.verticalScrollBar(), QtCore.SIGNAL('rangeChanged(int,int)'), self.log_scroll_range_changed) # restore the settings and widgets geometries self.restoreAppState() # start timers self.timer_toggle() self.slow_timer.start() self.logger.push("Init finished, entering the main loop") # slot # update the log widget with the new log content def log_changed(self): self.about_dialog.LabelLog.setText(self.logger.text()) # slot # scroll the log widget so that the last line is visible def log_scroll_range_changed(self, min, max): scrollbar = self.about_dialog.log_scrollarea.verticalScrollBar() scrollbar.setValue(max) # slot def settings_called(self): self.settings_dialog.show() # slot def about_called(self): self.about_dialog.show() # slot def new_dock_called(self): # the dock objectName is unique docknames = [dock.objectName() for dock in self.docks] dockindexes = [int(str(name).partition(' ')[-1]) for name in docknames] if len(dockindexes) == 0: index = 1 else: index = max(dockindexes)+1 name = "Dock %d" %index new_dock = Dock(self, self.logger, name) self.addDockWidget(QtCore.Qt.TopDockWidgetArea, new_dock) self.docks += [new_dock] #slot def dock_closed(self, dock): self.docks.remove(dock) # event handler def closeEvent(self, event): self.saveAppState() event.accept() # method def saveAppState(self): settings = QtCore.QSettings("Friture", "Friture") settings.beginGroup("Docks") docknames = [dock.objectName() for dock in self.docks] settings.setValue("dockNames", docknames) for dock in self.docks: settings.beginGroup(dock.objectName()) dock.saveState(settings) settings.endGroup() settings.endGroup() settings.beginGroup("CentralWidget") self.centralwidget.saveState(settings) settings.endGroup() settings.beginGroup("MainWindow") windowGeometry = self.saveGeometry() settings.setValue("windowGeometry", windowGeometry) windowState = self.saveState() settings.setValue("windowState", windowState) settings.endGroup() settings.beginGroup("AudioBackend") self.settings_dialog.saveState(settings) settings.endGroup() # method def restoreAppState(self): settings = QtCore.QSettings("Friture", "Friture") settings.beginGroup("Docks") if settings.contains("dockNames"): docknames = settings.value("dockNames", []).toList() docknames = [dockname.toString() for dockname in docknames] # list of docks self.docks = [Dock(self, self.logger, name) for name in docknames] for dock in self.docks: settings.beginGroup(dock.objectName()) dock.restoreState(settings) settings.endGroup() else: self.logger.push("First launch, display a default set of docks") self.docks = [] self.docks += [Dock(self, self.logger, "Dock 0", type = 3)] #spectrogram self.docks += [Dock(self, self.logger, "Dock 1", type = 4)] #octave spectrum #self.docks += [Dock(self, self.logger, "Dock 2", type = 1)] #scope #self.docks += [Dock(self, self.logger, "Dock 3", type = 0)] #level for dock in self.docks: self.addDockWidget(QtCore.Qt.TopDockWidgetArea, dock) settings.endGroup() settings.beginGroup("CentralWidget") self.centralwidget.restoreState(settings) settings.endGroup() settings.beginGroup("MainWindow") self.restoreGeometry(settings.value("windowGeometry").toByteArray()) self.restoreState(settings.value("windowState").toByteArray()) settings.endGroup() settings.beginGroup("AudioBackend") self.settings_dialog.restoreState(settings) settings.endGroup() # slot def timer_toggle(self): if self.display_timer.isActive(): self.logger.push("Timer stop") self.display_timer.stop() self.ui.actionStart.setText("Start") for dock in self.docks: dock.custom_timer_stop() self.centralwidget.custom_timer_stop() else: self.logger.push("Timer start") self.display_timer.start() self.ui.actionStart.setText("Stop") for dock in self.docks: dock.custom_timer_start() self.centralwidget.custom_timer_start() # slot def update_buffer(self): (chunks, t, newpoints) = self.audiobackend.update(self.audiobuffer.ringbuffer) self.audiobuffer.set_newdata(newpoints) self.chunk_number += chunks self.buffer_timer_time = (95.*self.buffer_timer_time + 5.*t)/100. def get_cpu_percent(self): self.cpu_percent = psutil.cpu_percent() # method def statistics(self): if not self.about_dialog.LabelStats.isVisible(): return label = "Chunk #%d\n"\ "Audio buffer retrieval: %.02f ms\n"\ "Global CPU usage: %d %%"\ % (self.chunk_number, self.buffer_timer_time, self.cpu_percent) self.about_dialog.LabelStats.setText(label) # slot def input_device_changed(self, index): self.ui.actionStart.setChecked(False) success, index = self.audiobackend.select_input_device(index) self.settings_dialog.comboBox_inputDevice.setCurrentIndex(index) if not success: # Note: the error message is a child of the settings dialog, so that # that dialog remains on top when the error message is closed error_message = QErrorMessage(self.settings_dialog) error_message.setWindowTitle("Input device error") error_message.showMessage("Impossible to use the selected input device, reverting to the previous one") # reset the channels first_channel = self.audiobackend.get_current_first_channel() self.settings_dialog.comboBox_firstChannel.setCurrentIndex(first_channel) second_channel = self.audiobackend.get_current_second_channel() self.settings_dialog.comboBox_secondChannel.setCurrentIndex(second_channel) self.ui.actionStart.setChecked(True) # slot def first_channel_changed(self, index): self.ui.actionStart.setChecked(False) success, index = self.audiobackend.select_first_channel(index) self.settings_dialog.comboBox_firstChannel.setCurrentIndex(index) if not success: # Note: the error message is a child of the settings dialog, so that # that dialog remains on top when the error message is closed error_message = QErrorMessage(self.settings_dialog) error_message.setWindowTitle("Input device error") error_message.showMessage("Impossible to use the selected channel as the first channel, reverting to the previous one") self.ui.actionStart.setChecked(True) # slot def second_channel_changed(self, index): self.ui.actionStart.setChecked(False) success, index = self.audiobackend.select_second_channel(index) self.settings_dialog.comboBox_secondChannel.setCurrentIndex(index) if not success: # Note: the error message is a child of the settings dialog, so that # that dialog remains on top when the error message is closed error_message = QErrorMessage(self.settings_dialog) error_message.setWindowTitle("Input device error") error_message.showMessage("Impossible to use the selected channel as the second channel, reverting to the previous one") self.ui.actionStart.setChecked(True) # slot def single_input_type_selected(self, checked): if checked: self.settings_dialog.groupBox_second.setEnabled(False) self.settings_dialog.label_delay.setEnabled(False) self.settings_dialog.doubleSpinBox_delay.setEnabled(False) self.audiobackend.set_single_input() self.logger.push("Switching to single input") # slot def duo_input_type_selected(self, checked): if checked: self.settings_dialog.groupBox_second.setEnabled(True) self.settings_dialog.label_delay.setEnabled(True) self.settings_dialog.doubleSpinBox_delay.setEnabled(True) self.audiobackend.set_duo_input() self.logger.push("Switching to difference between two inputs") # slot def delay_changed(self, delay_ms): self.delay_ms = delay_ms self.logger.push("Delay changed to %f" %delay_ms) self.audiobuffer.set_delay_ms(delay_ms)
def duo_input_type_selected(self, checked): if checked: self.groupBox_second.setEnabled(True) AudioBackend().set_duo_input() Logger().push("Switching to difference between two inputs")
class Friture(QMainWindow, ): def __init__(self, logger): QMainWindow.__init__(self) # logger self.logger = logger # Setup the user interface self.ui = Ui_MainWindow() self.ui.setupUi(self) # Initialize the audio data ring buffer self.audiobuffer = AudioBuffer(self.logger) # Initialize the audio backend self.audiobackend = AudioBackend(self.logger) # signal containing new data from the audio callback thread, processed as numpy array self.audiobackend.new_data_available.connect(self.audiobuffer.handle_new_data) # this timer is used to update widgets that just need to display as fast as they can self.display_timer = QtCore.QTimer() self.display_timer.setInterval(SMOOTH_DISPLAY_TIMER_PERIOD_MS) # constant timing # slow timer self.slow_timer = QtCore.QTimer() self.slow_timer.setInterval(SLOW_TIMER_PERIOD_MS) # constant timing self.about_dialog = About_Dialog(self, self.logger, self.audiobackend, self.slow_timer) self.settings_dialog = Settings_Dialog(self, self.logger, self.audiobackend) self.centralwidget = CentralWidget(self.ui.centralwidget, self.logger, "central_widget", 0) self.centralLayout = QVBoxLayout(self.ui.centralwidget) self.centralLayout.setContentsMargins(0, 0, 0, 0) self.centralLayout.addWidget(self.centralwidget) self.dockmanager = DockManager(self, self.logger) # timer ticks self.display_timer.timeout.connect(self.centralwidget.canvasUpdate) self.display_timer.timeout.connect(self.dockmanager.canvasUpdate) # toolbar clicks self.ui.actionStart.triggered.connect(self.timer_toggle) self.ui.actionSettings.triggered.connect(self.settings_called) self.ui.actionAbout.triggered.connect(self.about_called) self.ui.actionNew_dock.triggered.connect(self.dockmanager.new_dock) # restore the settings and widgets geometries self.restoreAppState() # start timers self.timer_toggle() self.slow_timer.start() self.logger.push("Init finished, entering the main loop") # slot def settings_called(self): self.settings_dialog.show() # slot def about_called(self): self.about_dialog.show() # event handler def closeEvent(self, event): self.audiobackend.close() self.saveAppState() event.accept() # method def saveAppState(self): settings = QtCore.QSettings("Friture", "Friture") settings.beginGroup("Docks") self.dockmanager.saveState(settings) settings.endGroup() settings.beginGroup("CentralWidget") self.centralwidget.saveState(settings) settings.endGroup() settings.beginGroup("MainWindow") windowGeometry = self.saveGeometry() settings.setValue("windowGeometry", windowGeometry) windowState = self.saveState() settings.setValue("windowState", windowState) settings.endGroup() settings.beginGroup("AudioBackend") self.settings_dialog.saveState(settings) settings.endGroup() # method def restoreAppState(self): settings = QtCore.QSettings("Friture", "Friture") settings.beginGroup("Docks") self.dockmanager.restoreState(settings) settings.endGroup() settings.beginGroup("CentralWidget") self.centralwidget.restoreState(settings) settings.endGroup() settings.beginGroup("MainWindow") self.restoreGeometry(settings.value("windowGeometry", type=QtCore.QByteArray)) self.restoreState(settings.value("windowState", type=QtCore.QByteArray)) settings.endGroup() settings.beginGroup("AudioBackend") self.settings_dialog.restoreState(settings) settings.endGroup() # slot def timer_toggle(self): if self.display_timer.isActive(): self.logger.push("Timer stop") self.display_timer.stop() self.ui.actionStart.setText("Start") self.audiobackend.pause() self.centralwidget.pause() self.dockmanager.pause() else: self.logger.push("Timer start") self.display_timer.start() self.ui.actionStart.setText("Stop") self.audiobackend.restart() self.centralwidget.restart() self.dockmanager.restart()
class Friture( QMainWindow, ): def __init__(self, logger): QMainWindow.__init__(self) # logger self.logger = logger # Setup the user interface self.ui = Ui_MainWindow() self.ui.setupUi(self) # Initialize the audio data ring buffer self.audiobuffer = AudioBuffer(self.logger) # Initialize the audio backend self.audiobackend = AudioBackend(self.logger) # this timer is used to update widgets that just need to display as fast as they can self.display_timer = QtCore.QTimer() self.display_timer.setInterval( SMOOTH_DISPLAY_TIMER_PERIOD_MS) # constant timing # slow timer self.slow_timer = QtCore.QTimer() self.slow_timer.setInterval(SLOW_TIMER_PERIOD_MS) # constant timing self.about_dialog = About_Dialog(self, self.logger, self.audiobackend, self.slow_timer) self.settings_dialog = Settings_Dialog(self, self.logger, self.audiobackend) self.centralwidget = CentralWidget(self.ui.centralwidget, self.logger, "central_widget", 0) self.centralLayout = QVBoxLayout(self.ui.centralwidget) self.centralLayout.setContentsMargins(0, 0, 0, 0) self.centralLayout.addWidget(self.centralwidget) self.dockmanager = DockManager(self, self.logger) # timer ticks self.connect(self.display_timer, QtCore.SIGNAL('timeout()'), self.update_buffer) self.connect(self.display_timer, QtCore.SIGNAL('timeout()'), self.centralwidget.update) self.connect(self.display_timer, QtCore.SIGNAL('timeout()'), self.dockmanager.update) # toolbar clicks self.connect(self.ui.actionStart, QtCore.SIGNAL('triggered()'), self.timer_toggle) self.connect(self.ui.actionSettings, QtCore.SIGNAL('triggered()'), self.settings_called) self.connect(self.ui.actionAbout, QtCore.SIGNAL('triggered()'), self.about_called) self.connect(self.ui.actionNew_dock, QtCore.SIGNAL('triggered()'), self.dockmanager.new_dock) # restore the settings and widgets geometries self.restoreAppState() # start timers self.timer_toggle() self.slow_timer.start() self.logger.push("Init finished, entering the main loop") # slot def settings_called(self): self.settings_dialog.show() # slot def about_called(self): self.about_dialog.show() # event handler def closeEvent(self, event): self.saveAppState() event.accept() # method def saveAppState(self): settings = QtCore.QSettings("Friture", "Friture") settings.beginGroup("Docks") self.dockmanager.saveState(settings) settings.endGroup() settings.beginGroup("CentralWidget") self.centralwidget.saveState(settings) settings.endGroup() settings.beginGroup("MainWindow") windowGeometry = self.saveGeometry() settings.setValue("windowGeometry", windowGeometry) windowState = self.saveState() settings.setValue("windowState", windowState) settings.endGroup() settings.beginGroup("AudioBackend") self.settings_dialog.saveState(settings) settings.endGroup() # method def restoreAppState(self): settings = QtCore.QSettings("Friture", "Friture") settings.beginGroup("Docks") self.dockmanager.restoreState(settings) settings.endGroup() settings.beginGroup("CentralWidget") self.centralwidget.restoreState(settings) settings.endGroup() settings.beginGroup("MainWindow") self.restoreGeometry(settings.value("windowGeometry").toByteArray()) self.restoreState(settings.value("windowState").toByteArray()) settings.endGroup() settings.beginGroup("AudioBackend") self.settings_dialog.restoreState(settings) settings.endGroup() # slot def timer_toggle(self): if self.display_timer.isActive(): self.logger.push("Timer stop") self.display_timer.stop() self.ui.actionStart.setText("Start") else: self.logger.push("Timer start") self.display_timer.start() self.ui.actionStart.setText("Stop") # slot def update_buffer(self): newpoints = self.audiobackend.update(self.audiobuffer.ringbuffer) self.audiobuffer.set_newdata(newpoints)
class Friture(QMainWindow, ): def __init__(self, logger): QMainWindow.__init__(self) # logger self.logger = logger # Setup the user interface self.ui = Ui_MainWindow() self.ui.setupUi(self) # sharedGLWidget is a hidden GL widget that will be used as a parent # to all QGLWidgets so that they have the same GL context, and can # share display lists, etc. self.sharedGLWidget = QtOpenGL.QGLWidget(self) self.sharedGLWidget.hide() # Initialize the audio data ring buffer self.audiobuffer = AudioBuffer(self.logger) # Initialize the audio backend self.audiobackend = AudioBackend(self.logger) # this timer is used to update widgets that just need to display as fast as they can self.display_timer = QtCore.QTimer() self.display_timer.setInterval(SMOOTH_DISPLAY_TIMER_PERIOD_MS) # constant timing # slow timer self.slow_timer = QtCore.QTimer() self.slow_timer.setInterval(SLOW_TIMER_PERIOD_MS) # constant timing self.about_dialog = About_Dialog(self, self.logger, self.audiobackend, self.slow_timer) self.settings_dialog = Settings_Dialog(self, self.logger, self.audiobackend) self.centralwidget = CentralWidget(self.ui.centralwidget, self.sharedGLWidget, self.logger, "central_widget", 0) self.centralLayout = QVBoxLayout(self.ui.centralwidget) self.centralLayout.setContentsMargins(0, 0, 0, 0) self.centralLayout.addWidget(self.centralwidget) self.dockmanager = DockManager(self, self.sharedGLWidget, self.logger) # timer ticks self.connect(self.display_timer, QtCore.SIGNAL('timeout()'), self.update_buffer) self.connect(self.display_timer, QtCore.SIGNAL('timeout()'), self.centralwidget.update) self.connect(self.display_timer, QtCore.SIGNAL('timeout()'), self.dockmanager.update) # toolbar clicks self.connect(self.ui.actionStart, QtCore.SIGNAL('triggered()'), self.timer_toggle) self.connect(self.ui.actionSettings, QtCore.SIGNAL('triggered()'), self.settings_called) self.connect(self.ui.actionAbout, QtCore.SIGNAL('triggered()'), self.about_called) self.connect(self.ui.actionNew_dock, QtCore.SIGNAL('triggered()'), self.dockmanager.new_dock) # restore the settings and widgets geometries self.restoreAppState() # start timers self.timer_toggle() self.slow_timer.start() self.logger.push("Init finished, entering the main loop") # slot def settings_called(self): self.settings_dialog.show() # slot def about_called(self): self.about_dialog.show() # event handler def closeEvent(self, event): self.saveAppState() event.accept() # method def saveAppState(self): settings = QtCore.QSettings("Friture", "Friture") settings.beginGroup("Docks") self.dockmanager.saveState(settings) settings.endGroup() settings.beginGroup("CentralWidget") self.centralwidget.saveState(settings) settings.endGroup() settings.beginGroup("MainWindow") windowGeometry = self.saveGeometry() settings.setValue("windowGeometry", windowGeometry) windowState = self.saveState() settings.setValue("windowState", windowState) settings.endGroup() settings.beginGroup("AudioBackend") self.settings_dialog.saveState(settings) settings.endGroup() # method def restoreAppState(self): settings = QtCore.QSettings("Friture", "Friture") settings.beginGroup("Docks") self.dockmanager.restoreState(settings) settings.endGroup() settings.beginGroup("CentralWidget") self.centralwidget.restoreState(settings) settings.endGroup() settings.beginGroup("MainWindow") self.restoreGeometry(settings.value("windowGeometry").toByteArray()) self.restoreState(settings.value("windowState").toByteArray()) settings.endGroup() settings.beginGroup("AudioBackend") self.settings_dialog.restoreState(settings) settings.endGroup() # slot def timer_toggle(self): if self.display_timer.isActive(): self.logger.push("Timer stop") self.display_timer.stop() self.ui.actionStart.setText("Start") self.centralwidget.pause() self.dockmanager.pause() else: self.logger.push("Timer start") self.display_timer.start() self.ui.actionStart.setText("Stop") self.centralwidget.restart() self.dockmanager.restart() # slot def update_buffer(self): newpoints = self.audiobackend.update(self.audiobuffer.ringbuffer) self.audiobuffer.set_newdata(newpoints)
def closeEvent(self, event): AudioBackend().close() self.saveAppState() event.accept()
def audio_callback(self, out_data, frame_count, time_info, status): if status: print(status, flush=True) N = frame_count if self.state == STOPPED: out_data.fill(0) return # if we cannot write any sample, return now if N == 0: return t = self.t + np.arange(0, N / float(SAMPLING_RATE), 1. / float(SAMPLING_RATE)) name = self.combobox_generator_kind.currentText() generators = [generator for generator in self.generators if generator.name == name] if len(generators) == 0: print("generator error : index of signal type not found") out_data.fill(0) return if len(generators) > 1: print("generator error : 2 (or more) generators have the same name") out_data.fill(0) return generator = generators[0] floatdata = generator.signal(t) # add smooth ramps at start/stop to avoid undesirable bursts if self.state == STARTING: # add a ramp at the start t_ramp = self.t_start + np.arange(0, N / float(SAMPLING_RATE), 1. / float(SAMPLING_RATE)) t_ramp = np.clip(t_ramp, 0., RAMP_LENGTH) floatdata *= t_ramp / RAMP_LENGTH self.t_start += N / float(SAMPLING_RATE) if self.t_start > RAMP_LENGTH: self.state = PLAYING if self.state == STOPPING: print("stopping", self.t_stop, N) # add a ramp at the end t_ramp = self.t_stop - np.arange(0, N / float(SAMPLING_RATE), 1. / float(SAMPLING_RATE)) t_ramp = np.clip(t_ramp, 0., RAMP_LENGTH) floatdata *= t_ramp / RAMP_LENGTH self.t_stop -= N / float(SAMPLING_RATE) if self.t_stop < 0.: self.state = STOPPED self.stream_stop_ramp_finished.emit() # output channels are interleaved # we output to all channels simultaneously with the same data maxOutputChannels = AudioBackend().get_device_outputchannels_count(self.device) floatdata = np.tile(floatdata, (maxOutputChannels, 1)).transpose() int16info = np.iinfo(np.int16) norm_coeff = min(abs(int16info.min), int16info.max) intdata = (np.clip(floatdata, int16info.min, int16info.max) * norm_coeff).astype(np.int16) # update the time counter self.t += N / float(SAMPLING_RATE) # data copy out_data[:] = intdata
def __init__(self): QMainWindow.__init__(self) self.logger = logging.getLogger(__name__) # exception hook that logs to console, file, and display a message box self.errorDialogOpened = False sys.excepthook = self.excepthook # Setup the user interface self.ui = Ui_MainWindow() self.ui.setupUi(self) # Initialize the audio data ring buffer self.audiobuffer = AudioBuffer() # Initialize the audio backend # signal containing new data from the audio callback thread, processed as numpy array AudioBackend().new_data_available.connect( self.audiobuffer.handle_new_data) # this timer is used to update widgets that just need to display as fast as they can self.display_timer = QtCore.QTimer() self.display_timer.setInterval( SMOOTH_DISPLAY_TIMER_PERIOD_MS) # constant timing # slow timer self.slow_timer = QtCore.QTimer() self.slow_timer.setInterval(SLOW_TIMER_PERIOD_MS) # constant timing self.about_dialog = About_Dialog(self, self.slow_timer) self.settings_dialog = Settings_Dialog(self) self.level_widget = Levels_Widget(self) self.level_widget.set_buffer(self.audiobuffer) self.audiobuffer.new_data_available.connect( self.level_widget.handle_new_data) self.hboxLayout = QHBoxLayout(self.ui.centralwidget) self.hboxLayout.setContentsMargins(0, 0, 0, 0) self.hboxLayout.addWidget(self.level_widget) self.centralLayout = TileLayout() self.centralLayout.setContentsMargins(0, 0, 0, 0) self.hboxLayout.addLayout(self.centralLayout) self.dockmanager = DockManager(self) # timer ticks self.display_timer.timeout.connect(self.dockmanager.canvasUpdate) self.display_timer.timeout.connect(self.level_widget.canvasUpdate) self.display_timer.timeout.connect(AudioBackend().fetchAudioData) # toolbar clicks self.ui.actionStart.triggered.connect(self.timer_toggle) self.ui.actionSettings.triggered.connect(self.settings_called) self.ui.actionAbout.triggered.connect(self.about_called) self.ui.actionNew_dock.triggered.connect(self.dockmanager.new_dock) # restore the settings and widgets geometries self.restoreAppState() # make sure the toolbar is shown # in case it was closed by mistake (before it was made impossible) self.ui.toolBar.setVisible(True) # prevent from hiding or moving the toolbar self.ui.toolBar.toggleViewAction().setVisible(True) self.ui.toolBar.setMovable(False) self.ui.toolBar.setFloatable(False) # start timers self.timer_toggle() self.slow_timer.start() self.logger.info("Init finished, entering the main loop")
def __init__(self, parent): super().__init__(parent) self.audiobuffer = None self.setObjectName("Generator_Widget") self.grid_layout = QtWidgets.QGridLayout(self) self.grid_layout.setObjectName("grid_layout") self.generators = [] self.generators.append(SineGenerator(self)) self.generators.append(WhiteGenerator(self)) self.generators.append(PinkGenerator(self)) self.generators.append(SweepGenerator(self)) self.generators.append(BurstGenerator(self)) self.combobox_generator_kind = QtWidgets.QComboBox(self) self.combobox_generator_kind.setObjectName("combobox_generator_kind") self.stacked_settings_layout = QtWidgets.QStackedLayout() for generator in self.generators: self.combobox_generator_kind.addItem(generator.name) self.stacked_settings_layout.addWidget(generator.settingsWidget()) self.combobox_generator_kind.setCurrentIndex(DEFAULT_GENERATOR_KIND_INDEX) self.t = 0. self.t_start = 0. self.t_stop = RAMP_LENGTH self.state = STOPPED self.stream_stop_ramp_finished.connect(self.stop_stream_after_ramp) self.device = None self.stream = None # we will try to open all the output devices until one # works, starting by the default input device for device in AudioBackend().output_devices: Logger().push("Opening the stream for device: "+ device['name']) try: self.stream = AudioBackend().open_output_stream(device, self.audio_callback) self.stream.start() self.stream.stop() self.device = device Logger().push("Stream opened successfully") break except Exception as exception: Logger().push("Failed to open stream: " + str(exception)) self.start_stop_button = QtWidgets.QPushButton(self) startStopIcon = QtGui.QIcon() startStopIcon.addPixmap(QtGui.QPixmap(":/images-src/start.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off) startStopIcon.addPixmap(QtGui.QPixmap(":/images-src/stop.svg"), QtGui.QIcon.Normal, QtGui.QIcon.On) startStopIcon.addPixmap(QtGui.QPixmap(":/images-src/stop.svg"), QtGui.QIcon.Active, QtGui.QIcon.On) startStopIcon.addPixmap(QtGui.QPixmap(":/images-src/stop.svg"), QtGui.QIcon.Selected, QtGui.QIcon.On) startStopIcon.addPixmap(QtGui.QPixmap(":/images-src/stop.svg"), QtGui.QIcon.Disabled, QtGui.QIcon.On) self.start_stop_button.setIcon(startStopIcon) self.start_stop_button.setObjectName("generatorStartStop") self.start_stop_button.setText("Start") self.start_stop_button.setToolTip("Start/Stop generator") self.start_stop_button.setCheckable(True) self.start_stop_button.setChecked(False) self.grid_layout.addWidget(self.start_stop_button, 0, 0, 1, 1) self.grid_layout.addWidget(self.combobox_generator_kind, 1, 0, 1, 1) self.grid_layout.addLayout(self.stacked_settings_layout, 2, 0, 1, 1) self.combobox_generator_kind.activated.connect(self.stacked_settings_layout.setCurrentIndex) self.start_stop_button.toggled.connect(self.start_stop_button_toggle) # initialize the settings dialog devices = AudioBackend().get_readable_output_devices_list() if self.device is not None: device_index = AudioBackend().output_devices.index(self.device) else: device_index = None self.settings_dialog = Generator_Settings_Dialog(self, devices, device_index) self.settings_dialog.combobox_output_device.currentIndexChanged.connect(self.device_changed)
def single_input_type_selected(self, checked): if checked: self.groupBox_second.setEnabled(False) AudioBackend().set_single_input() Logger().push("Switching to single input")
class Friture( QMainWindow, ): def __init__(self, logger): QMainWindow.__init__(self) # logger self.logger = logger # Setup the user interface self.ui = Ui_MainWindow() self.ui.setupUi(self) self.settings_dialog = Settings_Dialog(self) self.about_dialog = About_Dialog(self) self.chunk_number = 0 self.buffer_timer_time = 0. self.cpu_percent = 0. # Initialize the audio data ring buffer self.audiobuffer = AudioBuffer() # Initialize the audio backend self.audiobackend = AudioBackend(self.logger) devices = self.audiobackend.get_readable_devices_list() for device in devices: self.settings_dialog.comboBox_inputDevice.addItem(device) channels = self.audiobackend.get_readable_current_channels() for channel in channels: self.settings_dialog.comboBox_firstChannel.addItem(channel) self.settings_dialog.comboBox_secondChannel.addItem(channel) current_device = self.audiobackend.get_readable_current_device() self.settings_dialog.comboBox_inputDevice.setCurrentIndex( current_device) first_channel = self.audiobackend.get_current_first_channel() self.settings_dialog.comboBox_firstChannel.setCurrentIndex( first_channel) second_channel = self.audiobackend.get_current_second_channel() self.settings_dialog.comboBox_secondChannel.setCurrentIndex( second_channel) # this timer is used to update widgets that just need to display as fast as they can self.display_timer = QtCore.QTimer() self.display_timer.setInterval( SMOOTH_DISPLAY_TIMER_PERIOD_MS) # constant timing # slow timer self.slow_timer = QtCore.QTimer() self.slow_timer.setInterval(1000) # constant timing self.centralwidget = CentralWidget(self.ui.centralwidget, self.logger, "central_widget", 0) self.centralLayout = QVBoxLayout(self.ui.centralwidget) self.centralLayout.setContentsMargins(0, 0, 0, 0) self.centralLayout.addWidget(self.centralwidget) # timer ticks self.connect(self.display_timer, QtCore.SIGNAL('timeout()'), self.update_buffer) self.connect(self.display_timer, QtCore.SIGNAL('timeout()'), self.statistics) # timer ticks self.connect(self.slow_timer, QtCore.SIGNAL('timeout()'), self.get_cpu_percent) # toolbar clicks self.connect(self.ui.actionStart, QtCore.SIGNAL('triggered()'), self.timer_toggle) self.connect(self.ui.actionSettings, QtCore.SIGNAL('triggered()'), self.settings_called) self.connect(self.ui.actionAbout, QtCore.SIGNAL('triggered()'), self.about_called) self.connect(self.ui.actionNew_dock, QtCore.SIGNAL('triggered()'), self.new_dock_called) # settings signals self.connect(self.settings_dialog.comboBox_inputDevice, QtCore.SIGNAL('currentIndexChanged(int)'), self.input_device_changed) self.connect(self.settings_dialog.comboBox_firstChannel, QtCore.SIGNAL('currentIndexChanged(int)'), self.first_channel_changed) self.connect(self.settings_dialog.comboBox_secondChannel, QtCore.SIGNAL('currentIndexChanged(int)'), self.second_channel_changed) self.connect(self.settings_dialog.radioButton_single, QtCore.SIGNAL('toggled(bool)'), self.single_input_type_selected) self.connect(self.settings_dialog.radioButton_duo, QtCore.SIGNAL('toggled(bool)'), self.duo_input_type_selected) self.connect(self.settings_dialog.doubleSpinBox_delay, QtCore.SIGNAL('valueChanged(double)'), self.delay_changed) # log change self.connect(self.logger, QtCore.SIGNAL('logChanged'), self.log_changed) self.connect(self.about_dialog.log_scrollarea.verticalScrollBar(), QtCore.SIGNAL('rangeChanged(int,int)'), self.log_scroll_range_changed) # restore the settings and widgets geometries self.restoreAppState() # start timers self.timer_toggle() self.slow_timer.start() self.logger.push("Init finished, entering the main loop") # slot # update the log widget with the new log content def log_changed(self): self.about_dialog.LabelLog.setText(self.logger.text()) # slot # scroll the log widget so that the last line is visible def log_scroll_range_changed(self, min, max): scrollbar = self.about_dialog.log_scrollarea.verticalScrollBar() scrollbar.setValue(max) # slot def settings_called(self): self.settings_dialog.show() # slot def about_called(self): self.about_dialog.show() # slot def new_dock_called(self): # the dock objectName is unique docknames = [dock.objectName() for dock in self.docks] dockindexes = [int(str(name).partition(' ')[-1]) for name in docknames] if len(dockindexes) == 0: index = 1 else: index = max(dockindexes) + 1 name = "Dock %d" % index new_dock = Dock(self, self.logger, name) self.addDockWidget(QtCore.Qt.TopDockWidgetArea, new_dock) self.docks += [new_dock] #slot def dock_closed(self, dock): self.docks.remove(dock) # event handler def closeEvent(self, event): self.saveAppState() event.accept() # method def saveAppState(self): settings = QtCore.QSettings("Friture", "Friture") settings.beginGroup("Docks") docknames = [dock.objectName() for dock in self.docks] settings.setValue("dockNames", docknames) for dock in self.docks: settings.beginGroup(dock.objectName()) dock.saveState(settings) settings.endGroup() settings.endGroup() settings.beginGroup("CentralWidget") self.centralwidget.saveState(settings) settings.endGroup() settings.beginGroup("MainWindow") windowGeometry = self.saveGeometry() settings.setValue("windowGeometry", windowGeometry) windowState = self.saveState() settings.setValue("windowState", windowState) settings.endGroup() settings.beginGroup("AudioBackend") self.settings_dialog.saveState(settings) settings.endGroup() # method def restoreAppState(self): settings = QtCore.QSettings("Friture", "Friture") settings.beginGroup("Docks") if settings.contains("dockNames"): docknames = settings.value("dockNames", []).toList() docknames = [dockname.toString() for dockname in docknames] # list of docks self.docks = [Dock(self, self.logger, name) for name in docknames] for dock in self.docks: settings.beginGroup(dock.objectName()) dock.restoreState(settings) settings.endGroup() else: self.logger.push("First launch, display a default set of docks") self.docks = [] self.docks += [Dock(self, self.logger, "Dock 0", type=3)] #spectrogram self.docks += [Dock(self, self.logger, "Dock 1", type=4)] #octave spectrum #self.docks += [Dock(self, self.logger, "Dock 2", type = 1)] #scope #self.docks += [Dock(self, self.logger, "Dock 3", type = 0)] #level for dock in self.docks: self.addDockWidget(QtCore.Qt.TopDockWidgetArea, dock) settings.endGroup() settings.beginGroup("CentralWidget") self.centralwidget.restoreState(settings) settings.endGroup() settings.beginGroup("MainWindow") self.restoreGeometry(settings.value("windowGeometry").toByteArray()) self.restoreState(settings.value("windowState").toByteArray()) settings.endGroup() settings.beginGroup("AudioBackend") self.settings_dialog.restoreState(settings) settings.endGroup() # slot def timer_toggle(self): if self.display_timer.isActive(): self.logger.push("Timer stop") self.display_timer.stop() self.ui.actionStart.setText("Start") for dock in self.docks: dock.custom_timer_stop() self.centralwidget.custom_timer_stop() else: self.logger.push("Timer start") self.display_timer.start() self.ui.actionStart.setText("Stop") for dock in self.docks: dock.custom_timer_start() self.centralwidget.custom_timer_start() # slot def update_buffer(self): (chunks, t, newpoints) = self.audiobackend.update(self.audiobuffer.ringbuffer) self.audiobuffer.set_newdata(newpoints) self.chunk_number += chunks self.buffer_timer_time = (95. * self.buffer_timer_time + 5. * t) / 100. def get_cpu_percent(self): self.cpu_percent = psutil.cpu_percent() # method def statistics(self): if not self.about_dialog.LabelStats.isVisible(): return label = "Chunk #%d\n"\ "Audio buffer retrieval: %.02f ms\n"\ "Global CPU usage: %d %%"\ % (self.chunk_number, self.buffer_timer_time, self.cpu_percent) self.about_dialog.LabelStats.setText(label) # slot def input_device_changed(self, index): self.ui.actionStart.setChecked(False) success, index = self.audiobackend.select_input_device(index) self.settings_dialog.comboBox_inputDevice.setCurrentIndex(index) if not success: # Note: the error message is a child of the settings dialog, so that # that dialog remains on top when the error message is closed error_message = QErrorMessage(self.settings_dialog) error_message.setWindowTitle("Input device error") error_message.showMessage( "Impossible to use the selected input device, reverting to the previous one" ) # reset the channels first_channel = self.audiobackend.get_current_first_channel() self.settings_dialog.comboBox_firstChannel.setCurrentIndex( first_channel) second_channel = self.audiobackend.get_current_second_channel() self.settings_dialog.comboBox_secondChannel.setCurrentIndex( second_channel) self.ui.actionStart.setChecked(True) # slot def first_channel_changed(self, index): self.ui.actionStart.setChecked(False) success, index = self.audiobackend.select_first_channel(index) self.settings_dialog.comboBox_firstChannel.setCurrentIndex(index) if not success: # Note: the error message is a child of the settings dialog, so that # that dialog remains on top when the error message is closed error_message = QErrorMessage(self.settings_dialog) error_message.setWindowTitle("Input device error") error_message.showMessage( "Impossible to use the selected channel as the first channel, reverting to the previous one" ) self.ui.actionStart.setChecked(True) # slot def second_channel_changed(self, index): self.ui.actionStart.setChecked(False) success, index = self.audiobackend.select_second_channel(index) self.settings_dialog.comboBox_secondChannel.setCurrentIndex(index) if not success: # Note: the error message is a child of the settings dialog, so that # that dialog remains on top when the error message is closed error_message = QErrorMessage(self.settings_dialog) error_message.setWindowTitle("Input device error") error_message.showMessage( "Impossible to use the selected channel as the second channel, reverting to the previous one" ) self.ui.actionStart.setChecked(True) # slot def single_input_type_selected(self, checked): if checked: self.settings_dialog.groupBox_second.setEnabled(False) self.settings_dialog.label_delay.setEnabled(False) self.settings_dialog.doubleSpinBox_delay.setEnabled(False) self.audiobackend.set_single_input() self.logger.push("Switching to single input") # slot def duo_input_type_selected(self, checked): if checked: self.settings_dialog.groupBox_second.setEnabled(True) self.settings_dialog.label_delay.setEnabled(True) self.settings_dialog.doubleSpinBox_delay.setEnabled(True) self.audiobackend.set_duo_input() self.logger.push("Switching to difference between two inputs") # slot def delay_changed(self, delay_ms): self.delay_ms = delay_ms self.logger.push("Delay changed to %f" % delay_ms) self.audiobuffer.set_delay_ms(delay_ms)
class Generator_Widget(QtWidgets.QWidget): stream_stop_ramp_finished = QtCore.pyqtSignal() def __init__(self, parent): super().__init__(parent) self.audiobuffer = None self.setObjectName("Generator_Widget") self.grid_layout = QtWidgets.QGridLayout(self) self.grid_layout.setObjectName("grid_layout") self.generators = [] self.generators.append(SineGenerator(self)) self.generators.append(WhiteGenerator(self)) self.generators.append(PinkGenerator(self)) self.generators.append(SweepGenerator(self)) self.generators.append(BurstGenerator(self)) self.combobox_generator_kind = QtWidgets.QComboBox(self) self.combobox_generator_kind.setObjectName("combobox_generator_kind") self.stacked_settings_layout = QtWidgets.QStackedLayout() for generator in self.generators: self.combobox_generator_kind.addItem(generator.name) self.stacked_settings_layout.addWidget(generator.settingsWidget()) self.combobox_generator_kind.setCurrentIndex(DEFAULT_GENERATOR_KIND_INDEX) self.t = 0. self.t_start = 0. self.t_stop = RAMP_LENGTH self.state = STOPPED self.stream_stop_ramp_finished.connect(self.stop_stream_after_ramp) self.device = None self.stream = None # we will try to open all the output devices until one # works, starting by the default input device for device in AudioBackend().output_devices: Logger().push("Opening the stream for device: "+ device['name']) try: self.stream = AudioBackend().open_output_stream(device, self.audio_callback) self.stream.start() self.stream.stop() self.device = device Logger().push("Stream opened successfully") break except Exception as exception: Logger().push("Failed to open stream: " + str(exception)) self.start_stop_button = QtWidgets.QPushButton(self) startStopIcon = QtGui.QIcon() startStopIcon.addPixmap(QtGui.QPixmap(":/images-src/start.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off) startStopIcon.addPixmap(QtGui.QPixmap(":/images-src/stop.svg"), QtGui.QIcon.Normal, QtGui.QIcon.On) startStopIcon.addPixmap(QtGui.QPixmap(":/images-src/stop.svg"), QtGui.QIcon.Active, QtGui.QIcon.On) startStopIcon.addPixmap(QtGui.QPixmap(":/images-src/stop.svg"), QtGui.QIcon.Selected, QtGui.QIcon.On) startStopIcon.addPixmap(QtGui.QPixmap(":/images-src/stop.svg"), QtGui.QIcon.Disabled, QtGui.QIcon.On) self.start_stop_button.setIcon(startStopIcon) self.start_stop_button.setObjectName("generatorStartStop") self.start_stop_button.setText("Start") self.start_stop_button.setToolTip("Start/Stop generator") self.start_stop_button.setCheckable(True) self.start_stop_button.setChecked(False) self.grid_layout.addWidget(self.start_stop_button, 0, 0, 1, 1) self.grid_layout.addWidget(self.combobox_generator_kind, 1, 0, 1, 1) self.grid_layout.addLayout(self.stacked_settings_layout, 2, 0, 1, 1) self.combobox_generator_kind.activated.connect(self.stacked_settings_layout.setCurrentIndex) self.start_stop_button.toggled.connect(self.start_stop_button_toggle) # initialize the settings dialog devices = AudioBackend().get_readable_output_devices_list() if self.device is not None: device_index = AudioBackend().output_devices.index(self.device) else: device_index = None self.settings_dialog = Generator_Settings_Dialog(self, devices, device_index) self.settings_dialog.combobox_output_device.currentIndexChanged.connect(self.device_changed) # channels = AudioBackend().get_readable_current_output_channels() # for channel in channels: # self.settings_dialog.comboBox_firstChannel.addItem(channel) # self.settings_dialog.comboBox_secondChannel.addItem(channel) # current_device = AudioBackend().get_readable_current_output_device() # self.settings_dialog.combobox_output_device.setCurrentIndex(current_device) # first_channel = AudioBackend().get_current_first_channel() # self.settings_dialog.comboBox_firstChannel.setCurrentIndex(first_channel) # second_channel = AudioBackend().get_current_second_channel() # self.settings_dialog.comboBox_secondChannel.setCurrentIndex(second_channel) def device_changed(self, index): device = AudioBackend().output_devices[index] # save current stream in case we need to restore it previous_stream = self.stream previous_device = self.device error_message = "" Logger().push("Trying to write to output device " + device['name']) # first see if the format is supported by PortAudio try: success = AudioBackend().is_output_format_supported(device, np.int16) except Exception as exception: Logger().push("Format is not supported: " + str(exception)) success = False if success: try: self.stream = AudioBackend().open_output_stream(device, self.audio_callback) self.device = device self.stream.start() if self.state not in [STARTING, PLAYING]: self.stream.stop() success = True except OSError as error: Logger().push("Fail: " + str(error)) success = False if success: Logger().push("Success") previous_stream.stop() else: if self.stream is not None: self.stream.stop() # restore previous stream self.stream = previous_stream self.device = previous_device # Note: the error message is a child of the settings dialog, so that # that dialog remains on top when the error message is closed error_message = QtWidgets.QErrorMessage(self.settings_dialog) error_message.setWindowTitle("Output device error") error_message.showMessage("Impossible to use the selected output device, reverting to the previous one. Reason is: " + error_message) self.settings_dialog.combobox_output_device.setCurrentIndex(AudioBackend().output_devices.index(self.device)) def settings_called(self, checked): self.settings_dialog.show() # method def set_buffer(self, buffer): self.audiobuffer = buffer # slot def start_stop_button_toggle(self, checked): if checked: self.start_stop_button.setText("Stop") if self.state == STOPPED or self.state == STOPPING: self.state = STARTING self.t_start = 0. self.stream.start() else: self.start_stop_button.setText("Start") if self.state == PLAYING or self.state == STARTING: self.state = STOPPING self.t_stop = RAMP_LENGTH # will stop at the end of the ramp def stop_stream_after_ramp(self): self.stream.stop() def handle_new_data(self, floatdata): # we do not make anything of the input data in the generator... return def audio_callback(self, out_data, frame_count, time_info, status): if status: print(status, flush=True) N = frame_count if self.state == STOPPED: out_data.fill(0) return # if we cannot write any sample, return now if N == 0: return t = self.t + np.arange(0, N / float(SAMPLING_RATE), 1. / float(SAMPLING_RATE)) name = self.combobox_generator_kind.currentText() generators = [generator for generator in self.generators if generator.name == name] if len(generators) == 0: print("generator error : index of signal type not found") out_data.fill(0) return if len(generators) > 1: print("generator error : 2 (or more) generators have the same name") out_data.fill(0) return generator = generators[0] floatdata = generator.signal(t) # add smooth ramps at start/stop to avoid undesirable bursts if self.state == STARTING: # add a ramp at the start t_ramp = self.t_start + np.arange(0, N / float(SAMPLING_RATE), 1. / float(SAMPLING_RATE)) t_ramp = np.clip(t_ramp, 0., RAMP_LENGTH) floatdata *= t_ramp / RAMP_LENGTH self.t_start += N / float(SAMPLING_RATE) if self.t_start > RAMP_LENGTH: self.state = PLAYING if self.state == STOPPING: print("stopping", self.t_stop, N) # add a ramp at the end t_ramp = self.t_stop - np.arange(0, N / float(SAMPLING_RATE), 1. / float(SAMPLING_RATE)) t_ramp = np.clip(t_ramp, 0., RAMP_LENGTH) floatdata *= t_ramp / RAMP_LENGTH self.t_stop -= N / float(SAMPLING_RATE) if self.t_stop < 0.: self.state = STOPPED self.stream_stop_ramp_finished.emit() # output channels are interleaved # we output to all channels simultaneously with the same data maxOutputChannels = AudioBackend().get_device_outputchannels_count(self.device) floatdata = np.tile(floatdata, (maxOutputChannels, 1)).transpose() int16info = np.iinfo(np.int16) norm_coeff = min(abs(int16info.min), int16info.max) intdata = (np.clip(floatdata, int16info.min, int16info.max) * norm_coeff).astype(np.int16) # update the time counter self.t += N / float(SAMPLING_RATE) # data copy out_data[:] = intdata def canvasUpdate(self): return def saveState(self, settings): settings.setValue("generator kind", self.combobox_generator_kind.currentIndex()) for generator in self.generators: generator.settingsWidget().saveState(settings) self.settings_dialog.saveState(settings) def restoreState(self, settings): generator_kind = settings.value("generator kind", DEFAULT_GENERATOR_KIND_INDEX, type=int) self.combobox_generator_kind.setCurrentIndex(generator_kind) self.stacked_settings_layout.setCurrentIndex(generator_kind) for generator in self.generators: generator.settingsWidget().restoreState(settings) self.settings_dialog.restoreState(settings)
def __init__(self, logger): QMainWindow.__init__(self) # logger self.logger = logger # Setup the user interface self.ui = Ui_MainWindow() self.ui.setupUi(self) # Initialize the audio data ring buffer self.audiobuffer = AudioBuffer(self.logger) # Initialize the audio backend self.audiobackend = AudioBackend(self.logger) # this timer is used to update widgets that just need to display as fast as they can self.display_timer = QtCore.QTimer() self.display_timer.setInterval( SMOOTH_DISPLAY_TIMER_PERIOD_MS) # constant timing # slow timer self.slow_timer = QtCore.QTimer() self.slow_timer.setInterval(SLOW_TIMER_PERIOD_MS) # constant timing self.about_dialog = About_Dialog(self, self.logger, self.audiobackend, self.slow_timer) self.settings_dialog = Settings_Dialog(self, self.logger, self.audiobackend) self.centralwidget = CentralWidget(self.ui.centralwidget, self.logger, "central_widget", 0) self.centralLayout = QVBoxLayout(self.ui.centralwidget) self.centralLayout.setContentsMargins(0, 0, 0, 0) self.centralLayout.addWidget(self.centralwidget) self.dockmanager = DockManager(self, self.logger) # timer ticks self.connect(self.display_timer, QtCore.SIGNAL('timeout()'), self.update_buffer) self.connect(self.display_timer, QtCore.SIGNAL('timeout()'), self.centralwidget.update) self.connect(self.display_timer, QtCore.SIGNAL('timeout()'), self.dockmanager.update) # toolbar clicks self.connect(self.ui.actionStart, QtCore.SIGNAL('triggered()'), self.timer_toggle) self.connect(self.ui.actionSettings, QtCore.SIGNAL('triggered()'), self.settings_called) self.connect(self.ui.actionAbout, QtCore.SIGNAL('triggered()'), self.about_called) self.connect(self.ui.actionNew_dock, QtCore.SIGNAL('triggered()'), self.dockmanager.new_dock) # restore the settings and widgets geometries self.restoreAppState() # start timers self.timer_toggle() self.slow_timer.start() self.logger.push("Init finished, entering the main loop")