class SideToneWidget(QWidget): def __init__(self, parent): super().__init__(parent) # Save link to main window self.main_window = parent # Get the status bar self.status_bar = parent.statusBar() # Slot that will point to a QAudioInput some day self.input_device = None # Slot that will point to a QAudioOutput in time self.otput_device = None # set up layout, creating: # self.input_info_list, list of QAudioInfo for inputs # self.cb_inputs, combox of input names in same order # self.otput_info_list, list of QAudioInfo for outputs # self.cb_otputs, combox of output names in same order # self.volume, volume slider # self.mute, mute checkbox self._uic() # Connect up signals to slots. # Changes in the comboxes go to in_device and ot_device self.cb_inputs.currentIndexChanged.connect(self.in_dev_change) self.cb_otputs.currentIndexChanged.connect(self.ot_dev_change) # Mute button goes to mute_change self.mute.stateChanged.connect(self.mute_change) # Change in volume goes to volume_change self.volume.valueChanged.connect(self.volume_change) # Start with the mute switch on. This triggers the above two signals. self.mute.setChecked(True) # Slot for any change in volume (or mute). If we have an output device # then convert level to a real and pass to the output device. If the new # level is 0, tell the device to stop; if nonzero, tell it to start. # Note we don't do anything about the input device level, it is always 1.0 def volume_change(self, new_level): if self.otput_device: # we have an output device self.otput_device.setVolume(self.volume.value() / 100) if new_level == 0: # looks like a mute # tell the output device to stop just in case it # doesn't know about volume control. self.otput_device.stop() else: # non-zero level, if the output is stopped, start it if self.otput_device.state() == QAudio.StoppedState: self.otput_device.start() # Slot for mute switch. Note that any change to the volume slider # generates a signal to the volume_change slot. def mute_change(self, onoff): if onoff: # Mute has been clicked ON. Remember the current volume. # Turn the volume to zero. self.volume_level = self.volume.value() self.volume.setValue(0) else: # Mute has been clicked OFF. If we do not yet have input and # output devices, get them. Then reset the old volume level. if self.otput_device is None: # We are starting up and have no devices. Fake a call to # the checkbox-change entries thus creating devices. self.in_dev_change(self.cb_inputs.currentIndex()) self.ot_dev_change(self.cb_inputs.currentIndex()) self.volume.setValue(self.volume_level) # Slots for changes in the selection of the input- and output-device # combo boxes. On startup we have neither an input nor an output device. # We do not know which combox the user will fiddle with first. So either # has to assume that the other device may not yet exist. # # On a change of input choice: if we have an input device, get rid of it. # Create a new input device. Set its level to 1.0. If we # have an output, connect the two. def in_dev_change(self, new_index): if self.input_device: if self.otput_device: self.otput_device.stop() self.input_device.stop() self.input_device = None # goodby object # Get the QAudioDeviceInfo corresponding to this index of the combox. audio_info = self.input_info_list[new_index] # Create a new QAudioInput based on that. preferred_format = audio_info.preferredFormat() self.input_device = QAudioInput(audio_info, preferred_format) self.input_device.setVolume(1.0) self.input_device.setBufferSize(384) # If we have an output device, redirect it to this input. This is # done by asking the input device for its QIODevice, and passing that # to the output device's start() method. if self.otput_device: self.input_device.start(self.otput_device.start()) #self.otput_device.start( self.input_device.start() ) # On a change in the selection of output choice: If we have an output # device, get rid of it. Create a new output device. If we have an input # device, connect the two. Set the output level from the volume slider. def ot_dev_change(self, new_index): if self.otput_device: if self.input_device: self.input_device.stop() self.otput_device.stop() self.otput_device = None audio_info = self.otput_info_list[new_index] preferred_format = audio_info.preferredFormat() self.otput_device = QAudioOutput(audio_info, preferred_format) self.otput_device.setVolume(self.volume.value() / 100) #self.otput_device.setBufferSize( 384 ) if self.input_device: self.input_device.start(self.otput_device.start()) #self.otput_device.start( self.input_device.start() ) def _uic(self): ''' set up our layout which consists of: Big Honkin' Label [input combobox] [output combobox] [volume slider] [x] Mute hooking put the signals to useful slots is the job of __init__. Here just make the layout. ''' self.setMinimumWidth(400) # Create the big honkin' label and logo icon_pixmap = QPixmap(':/icon.png').scaledToWidth(64) icon_label = QLabel() icon_label.setPixmap(icon_pixmap) text_label = QLabel("Sidetone!") hb_label = QHBoxLayout() hb_label.addStretch(1) hb_label.addWidget(icon_label, 0) hb_label.addWidget(text_label, 0) hb_label.addStretch(1) # Create a list of QAudioInfo objects for inputs self.input_info_list = QAudioDeviceInfo.availableDevices( QAudio.AudioInput) if 0 == len(self.input_info_list): self.input_info_list = [QAudioDeviceInfo.defaultInputDevice()] # Create a combo box and populate it with names of inputs self.cb_inputs = QComboBox() self.cb_inputs.addItems( [audio_info.deviceName() for audio_info in self.input_info_list]) # Create a list of QAudioInfo objects for outputs self.otput_info_list = QAudioDeviceInfo.availableDevices( QAudio.AudioOutput) if 0 == len(self.otput_info_list): self.otput_info_list = [QAudioDeviceInfo.defaultOutputDevice()] self.status_bar.showMessage( '{} inputs {} otputs'.format(len(self.input_info_list), len(self.otput_info_list)), 2000) # Create a combo box and populate it with names of outputs self.cb_otputs = QComboBox() self.cb_otputs.addItems( [audio_info.deviceName() for audio_info in self.otput_info_list]) # Lay those two out aligned to the outside hb_combos = QHBoxLayout() hb_combos.addWidget(self.cb_inputs, 1) hb_combos.addStretch(0) hb_combos.addWidget(self.cb_otputs, 1) # Create a volume slider from 0 to 100. self.volume = QSlider(Qt.Horizontal, self) self.volume.setMinimum(0) self.volume.setMaximum(100) self.volume.setTickInterval(10) self.volume.setTickPosition(QSlider.TicksBothSides) # Create a checkbox "Mute" self.mute = QCheckBox('Mute') # Put those together in a row squeezed in the center hb_volume = QHBoxLayout() hb_volume.addStretch(1) hb_volume.addWidget(self.volume, 1) hb_volume.addWidget(self.mute, 0) hb_volume.addStretch(1) # Stack all those up as this widget's layout vlayout = QVBoxLayout() vlayout.addLayout(hb_label) vlayout.addLayout(hb_combos) vlayout.addLayout(hb_volume) self.setLayout(vlayout)
class SideToneWidget(QWidget): def __init__(self, parent): super().__init__(parent) # Save link to main window self.main_window = parent # Get the status bar self.status_bar = parent.statusBar() # Slot that will point to a QAudioInput some day self.input_device = None # Slot that will point to a QAudioOutput in time self.otput_device = None # set up layout, creating: # self.input_info_list, list of QAudioInfo for inputs # self.cb_inputs, combox of input names in same order # self.otput_info_list, list of QAudioInfo for outputs # self.cb_otputs, combox of output names in same order # self.volume, volume slider # self.mute, mute checkbox self._uic() # Connect up signals to slots. # Changes in the comboxes go to in_device and ot_device self.cb_inputs.currentIndexChanged.connect(self.in_dev_change) self.cb_otputs.currentIndexChanged.connect(self.ot_dev_change) # Mute button goes to mute_change self.mute.stateChanged.connect(self.mute_change) # Change in volume goes to volume_change self.volume.valueChanged.connect(self.volume_change) # Start with the mute switch on. This triggers the above two signals. self.mute.setChecked(True) # Slot for any change in volume (or mute). If we have an output device # then convert level to a real and pass to the output device. If the new # level is 0, tell the device to stop; if nonzero, tell it to start. # Note we don't do anything about the input device level, it is always 1.0 def volume_change(self, new_level): if self.otput_device: # we have an output device self.otput_device.setVolume(self.volume.value() / 100) if new_level == 0: # looks like a mute # tell the output device to stop just in case it # doesn't know about volume control. self.otput_device.stop() else: # non-zero level, if the output is stopped, start it if self.otput_device.state() == QAudio.StoppedState: self.otput_device.start() # Slot for mute switch. Note that any change to the volume slider # generates a signal to the volume_change slot. def mute_change(self, onoff): if onoff: # Mute has been clicked ON. Remember the current volume. # Turn the volume to zero. self.volume_level = self.volume.value() self.volume.setValue(0) else: # Mute has been clicked OFF. If we do not yet have input and # output devices, get them. Then reset the old volume level. if self.otput_device is None: # We are starting up and have no devices. Fake a call to # the checkbox-change entries thus creating devices. self.in_dev_change(self.cb_inputs.currentIndex()) self.ot_dev_change(self.cb_inputs.currentIndex()) self.volume.setValue(self.volume_level) # Slots for changes in the selection of the input- and output-device # combo boxes. On startup we have neither an input nor an output device. # We do not know which combox the user will fiddle with first. So either # has to assume that the other device may not yet exist. # # On a change of input choice: if we have an input device, get rid of it. # Create a new input device. Set its level to 1.0. If we # have an output, connect the two. def in_dev_change(self, new_index): if self.input_device: if self.otput_device: self.otput_device.stop() self.input_device.stop() self.input_device = None # goodby object # Get the QAudioDeviceInfo corresponding to this index of the combox. audio_info = self.input_info_list[new_index] # Create a new QAudioInput based on that. preferred_format = audio_info.preferredFormat() self.input_device = QAudioInput(audio_info, preferred_format) self.input_device.setVolume(1.0) self.input_device.setBufferSize(384) # If we have an output device, redirect it to this input. This is # done by asking the input device for its QIODevice, and passing that # to the output device's start() method. if self.otput_device: self.input_device.start(self.otput_device.start()) # self.otput_device.start( self.input_device.start() ) # On a change in the selection of output choice: If we have an output # device, get rid of it. Create a new output device. If we have an input # device, connect the two. Set the output level from the volume slider. def ot_dev_change(self, new_index): if self.otput_device: if self.input_device: self.input_device.stop() self.otput_device.stop() self.otput_device = None audio_info = self.otput_info_list[new_index] preferred_format = audio_info.preferredFormat() self.otput_device = QAudioOutput(audio_info, preferred_format) self.otput_device.setVolume(self.volume.value() / 100) # self.otput_device.setBufferSize( 384 ) if self.input_device: self.input_device.start(self.otput_device.start()) # self.otput_device.start( self.input_device.start() ) def _uic(self): """ set up our layout which consists of: Big Honkin' Label [input combobox] [output combobox] [volume slider] [x] Mute hooking put the signals to useful slots is the job of __init__. Here just make the layout. """ self.setMinimumWidth(400) # Create the big honkin' label and logo icon_pixmap = QPixmap(":/icon.png").scaledToWidth(64) icon_label = QLabel() icon_label.setPixmap(icon_pixmap) text_label = QLabel("Sidetone!") hb_label = QHBoxLayout() hb_label.addStretch(1) hb_label.addWidget(icon_label, 0) hb_label.addWidget(text_label, 0) hb_label.addStretch(1) # Create a list of QAudioInfo objects for inputs self.input_info_list = QAudioDeviceInfo.availableDevices(QAudio.AudioInput) if 0 == len(self.input_info_list): self.input_info_list = [QAudioDeviceInfo.defaultInputDevice()] # Create a combo box and populate it with names of inputs self.cb_inputs = QComboBox() self.cb_inputs.addItems([audio_info.deviceName() for audio_info in self.input_info_list]) # Create a list of QAudioInfo objects for outputs self.otput_info_list = QAudioDeviceInfo.availableDevices(QAudio.AudioOutput) if 0 == len(self.otput_info_list): self.otput_info_list = [QAudioDeviceInfo.defaultOutputDevice()] self.status_bar.showMessage( "{} inputs {} otputs".format(len(self.input_info_list), len(self.otput_info_list)), 2000 ) # Create a combo box and populate it with names of outputs self.cb_otputs = QComboBox() self.cb_otputs.addItems([audio_info.deviceName() for audio_info in self.otput_info_list]) # Lay those two out aligned to the outside hb_combos = QHBoxLayout() hb_combos.addWidget(self.cb_inputs, 1) hb_combos.addStretch(0) hb_combos.addWidget(self.cb_otputs, 1) # Create a volume slider from 0 to 100. self.volume = QSlider(Qt.Horizontal, self) self.volume.setMinimum(0) self.volume.setMaximum(100) self.volume.setTickInterval(10) self.volume.setTickPosition(QSlider.TicksBothSides) # Create a checkbox "Mute" self.mute = QCheckBox("Mute") # Put those together in a row squeezed in the center hb_volume = QHBoxLayout() hb_volume.addStretch(1) hb_volume.addWidget(self.volume, 1) hb_volume.addWidget(self.mute, 0) hb_volume.addStretch(1) # Stack all those up as this widget's layout vlayout = QVBoxLayout() vlayout.addLayout(hb_label) vlayout.addLayout(hb_combos) vlayout.addLayout(hb_volume) self.setLayout(vlayout)
class SideToneWidget(QWidget): def __init__(self, parent, the_settings): super().__init__(parent) # Save link to main window self.main_window = parent # Save link to settings object self.settings = the_settings # Get the status bar self.status_bar = parent.statusBar() # just the time self.time = QTime() self.time.start() # Slot that will point to a QAudioInput some day self.input_device = None # Slot that will point to a QAudioOutput in time self.otput_device = None # set up layout, creating: # self.input_info_list, list of QAudioInfo for inputs # self.cb_inputs, combox of input names in same order # self.otput_info_list, list of QAudioInfo for outputs # self.cb_otputs, combox of output names in same order # self.volume, volume slider # self.mute, mute checkbox self._uic() # Connect up signals to slots. Up to this point, the changes that # _uic() made in e.g. the volume or mute, or the combobox selections, # raised signals that were not connected. Now connect the signals # so that user-changes go to our slots for handling. # Mute button goes to mute_change self.mute.stateChanged.connect(self.mute_change) # Change in volume goes to volume_change self.volume.valueChanged.connect(self.volume_change) # Changes in the combox selections go to in_device and ot_device self.cb_inputs.currentIndexChanged.connect(self.in_dev_change) self.cb_otputs.currentIndexChanged.connect(self.ot_dev_change) # Now pretend the user has made a selection of the in and out devices. # That should result in activating everythings. self.in_dev_change(self.cb_inputs.currentIndex()) self.ot_dev_change(self.cb_otputs.currentIndex()) # Method to disconnect the input and output devices, if they exist. # This is called prior to any change in device selection. # Note that the QWidget class has an existing method disconnect(), # and we do not want to override that, so that name is not used. def disconnect_devices(self): # If an output device exists, make it stop. That prevents it # trying to pull any data from the input device if any. if self.otput_device is not None: self.otput_device.stop() # If an input device exists, make it stop also. That means it # loses track of the output device it was formerly connected to. if self.input_device is not None: self.input_device.stop() # Method to connect the input and output devices, if both exist. This is # called after making any change in device selection. def reconnect_devices(self): if (self.input_device is not None) \ and (self.otput_device is not None ) : # Connect the devices by asking the OUTput device for its # QIODevice, and passing that to the INput device's start() # method. This could equally well be done the other way, # by passing the input dev's IODevice to the output device. self.input_device.start(self.otput_device.start()) #self.otput_device.start( self.input_device.start() ) # In case the output device was just created, set its volume. self.set_volume() # Method to set the volume on the output device. (The input device volume # is always 1.0.) This is called on any change of the volume slider or # of the Mute button or of the output device choice. def set_volume(self): if self.mute.isChecked(): # Mute is ON, set volume to 0 regardless of volume slider volume = 0.0 else: # Mute is OFF, set volume to float version of volume slider volume = self.volume.value() / 100 if self.otput_device: # an output device exists (almost always true), set it self.otput_device.setVolume(volume) # Slot entered upon any change in the volume slider widget. def volume_change(self, new_level): if self.mute.isChecked(): # The Mute button is ON; assume the user wants it OFF, else why # move the slider? Note this causes a call to set_volume(). self.mute.setChecked(False) else: # The Mute button is OFF, just change the volume. self.set_volume() # Slot entered upon toggling of the mute switch, by the user or by the # code calling mute.setChecked(). Make sure the volume is set appropriately. def mute_change(self, onoff): self.set_volume() # Slots for selection of the input and output devices. On startup we have # neither an input nor an output device. We do not know which combox the # user will fiddle with first. # Slot entered upon any change in the selection of the input device # combo box. The argument is the new index of the list of values. def in_dev_change(self, new_index): # Disconnect and stop the devices if they are connected. self.disconnect_devices() self.input_device = None # device object goes out of scope # Get the QAudioDeviceInfo corresponding to this index of the combox. audio_info = self.input_info_list[new_index] # Create a new QAudioInput based on that. preferred_format = audio_info.preferredFormat() self.input_device = QAudioInput(audio_info, preferred_format) # the input device volume is always 1.0, wide open. self.input_device.setVolume(1.0) # The choice of buffer size has a major impact on the lag. It needs # to be small or there is severe echo; but if it is too small, there # is a sputtering or "motor-boating" effect. self.input_device.setBufferSize(384) # hook up possible debug status display self.input_device.stateChanged.connect(self.in_dev_state_change) # reconnect the devices if possible. self.reconnect_devices() # Slot entered upon any change in the selection of output. The argument # is the index to the list of output devices in the combobox. def ot_dev_change(self, new_index): print('index', new_index) # Disconnect and stop the devices if they are connected. self.disconnect_devices() self.otput_device = None # device object goes out of scope # Get the QAudioDeviceInfo corresponding to this index of the combox. audio_info = self.otput_info_list[new_index] # Create a new QAudioOutput based on that. preferred_format = audio_info.preferredFormat() self.otput_device = QAudioOutput(audio_info, preferred_format) self.otput_device.setVolume(0) # reconnect will set correct volume # hook up possible debug status display self.otput_device.stateChanged.connect(self.ot_dev_state_change) # reconnect the devices if possible. Which also sets the volume. self.reconnect_devices() # Show some text in the main-window status bar for 1 second, more or less. def show_status(self, text, duration=1000): self.status_bar.showMessage(text, duration) # Slots called on any "state" change of an audio device. Optionally # show the state in the main window status bar. def in_dev_state_change(self, new_state): #self.show_status( #'{} in dev state {}'.format(self.time.elapsed(),int(new_state)) #) pass def ot_dev_state_change(self, new_state): #self.show_status( #'{} ot dev state {}'.format(self.time.elapsed(),int(new_state)) #) pass # Close events are only received by a top-level widget. When our top-level # widget gets one, indicating the app is done, it calls this method. def closeEvent(self, event): # if we have devices, make them stop. self.disconnect_devices() # if the devices exist, reset them and then trash them. if self.otput_device is not None: self.otput_device.reset() self.otput_device = None if self.input_device is not None: self.input_device.reset() self.input_device = None # Save the current selection of the input and output combo boxes, # in the settings file. in_dev_name = self.cb_inputs.currentText() self.settings.setValue('in_dev_name', in_dev_name) ot_dev_name = self.cb_otputs.currentText() self.settings.setValue('ot_dev_name', ot_dev_name) # Save the volume setting and mute status in the settings. self.settings.setValue('volume', self.volume.value()) self.settings.setValue('mute_status', int(self.mute.isChecked())) def _uic(self): ''' set up our layout which consists of: Big Honkin' Label [input combobox] [output combobox] [volume slider] [x] Mute Hooking the signals to useful slots is the job of __init__. Here just make the layout. ''' self.setMinimumWidth(400) # Create the big honkin' label and logo icon_pixmap = QPixmap(':/icon.png').scaledToWidth(64) icon_label = QLabel() icon_label.setPixmap(icon_pixmap) text_label = QLabel("Sidetone!") hb_label = QHBoxLayout() hb_label.addStretch(1) hb_label.addWidget(icon_label, 0) hb_label.addWidget(text_label, 0) hb_label.addStretch(1) # Create a list of QAudioInfo objects for inputs self.input_info_list = QAudioDeviceInfo.availableDevices( QAudio.AudioInput) if 0 == len(self.input_info_list): self.input_info_list = [QAudioDeviceInfo.defaultInputDevice()] # Make a list of the name-strings for those items. in_dev_names = [ audio_info.deviceName() for audio_info in self.input_info_list ] # Create a combo box and populate it with those names self.cb_inputs = QComboBox() self.cb_inputs.addItems(in_dev_names) # If the in_dev_name from the previous run is in the current list, # make it current, otherwise pick the first item. in_dev_name = self.settings.value('in_dev_name', 'unknown') if in_dev_name in in_dev_names: self.cb_inputs.setCurrentIndex(in_dev_names.index(in_dev_name)) else: self.cb_inputs.setCurrentIndex(0) # Create a list of QAudioInfo objects for outputs self.otput_info_list = QAudioDeviceInfo.availableDevices( QAudio.AudioOutput) if 0 == len(self.otput_info_list): self.otput_info_list = [QAudioDeviceInfo.defaultOutputDevice()] # Make a list of the name-strings of those things ot_dev_names = [ audio_info.deviceName() for audio_info in self.otput_info_list ] # Create a combo box and populate it with those names self.cb_otputs = QComboBox() self.cb_otputs.addItems(ot_dev_names) # If the ot_dev_name from the previous run is in the current list, # make it the current choice in the box. ot_dev_name = self.settings.value('ot_dev_name', 'unknown') if ot_dev_name in ot_dev_names: self.cb_otputs.setCurrentIndex(ot_dev_names.index(ot_dev_name)) else: self.cb_otputs.setCurrentIndex(0) #self.show_status( #'{} inputs {} otputs'.format(len(self.input_info_list),len(self.otput_info_list)) #) # Create a combo box and populate it with names of outputs # Lay those two out aligned to the outside hb_combos = QHBoxLayout() hb_combos.addWidget(self.cb_inputs, 1) hb_combos.addStretch(0) hb_combos.addWidget(self.cb_otputs, 1) # Create a volume slider from 0 to 100. self.volume = QSlider(Qt.Horizontal, self) self.volume.setMinimum(0) self.volume.setMaximum(100) self.volume.setTickInterval(10) self.volume.setTickPosition(QSlider.TicksBothSides) # set the volume slider to the value from the previous run, or zero. self.volume.setValue(self.settings.value('volume', 0)) # Create a checkbox "Mute" self.mute = QCheckBox('Mute') # Set it to the value at the end of the last run, or to True self.mute.setChecked(bool(self.settings.value('mute_status', 1))) # Put those together in a row squeezed in the center hb_volume = QHBoxLayout() hb_volume.addStretch(1) hb_volume.addWidget(self.volume, 1) hb_volume.addWidget(self.mute, 0) hb_volume.addStretch(1) # Stack all those up as this widget's layout vlayout = QVBoxLayout() vlayout.addLayout(hb_label) vlayout.addLayout(hb_combos) vlayout.addLayout(hb_volume) self.setLayout(vlayout)