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 Visualizer(QMainWindow): def __init__(self): super().__init__() self.initUI() self._buflen = 1440 # 4096 def initUI(self): # main window/layout window = QWidget() layout = QVBoxLayout() # layout for audio device and sample rate selection deviceLayout = QHBoxLayout() # make audio device selection box and list of available devices self.deviceBox = QComboBox() defaultDeviceInfo = QAudioDeviceInfo.defaultInputDevice() self.availableDevices = [defaultDeviceInfo] self.availableDevices += QAudioDeviceInfo.availableDevices( QAudio.AudioInput) for device in self.availableDevices: self.deviceBox.addItem(device.deviceName()) # make sample rate label and combobox sRateLabel = QLabel("Sample rate:") sRateLabel.setAlignment(Qt.AlignRight) # user can choose between 44.1 and 48kHz (valid DetectorBank rates) self.sRateBox = QComboBox() self.sRateBox.addItem("44100") self.sRateBox.addItem("48000") self.sRateBox.setCurrentIndex(1) # add device and sr widgets to device layout deviceLayout.addWidget(self.deviceBox) deviceLayout.addWidget(sRateLabel) deviceLayout.addWidget(self.sRateBox) # add device layout to main layout layout.addLayout(deviceLayout) # DetectorBank parameters layout # two rows of three parameters # each param needs label and edit, # and a 'Start' button will be added at the bottom # so grid should be 3x6 detBankParamLayout = QGridLayout() # label and lineedit for each bandwidthLabel = QLabel("Bandwidth (cents):") dampingLabel = QLabel("Damping:") gainLabel = QLabel("Gain:") edoLabel = QLabel("EDO:") lwrLabel = QLabel("Lower note:") uprLabel = QLabel("Upper note:") self.bandwidthEdit = QLineEdit("0") self.dampingEdit = QLineEdit("0.0001") self.gainEdit = QLineEdit("25") self.edoEdit = QLineEdit("12") self.lwrEdit = QLineEdit("A1") self.uprEdit = QLineEdit("A7") # store all in lists detBankParamLabels = [bandwidthLabel, dampingLabel, gainLabel, edoLabel, lwrLabel, uprLabel] detBankParamEdits = [self.bandwidthEdit, self.dampingEdit, self.gainEdit, self.edoEdit, self.lwrEdit, self.uprEdit] # fill first two rows of grid with labels and edits row = 0 for row in range(2): widgetNum = 0 for i in range((row*3), (row*3)+3): detBankParamLayout.addWidget(detBankParamLabels[i], row, widgetNum) widgetNum += 1 detBankParamLayout.addWidget(detBankParamEdits[i], row, widgetNum) widgetNum += 1 # align labels to the right (next to the edit) for i in range(len(detBankParamLabels)): detBankParamLabels[i].setAlignment(Qt.AlignRight) # button to make DetectorBank and start visualisation row += 1 startButton = QPushButton("&Start!") detBankParamLayout.addWidget(startButton, row, 5) startButton.clicked.connect(self.start) # add grid of detbank params (and start button) to main layout layout.addLayout(detBankParamLayout) window.setLayout(layout) self.setCentralWidget(window) self.show() def initializeAudio(self, deviceInfo): """ Make a QAudioInput from the given device """ # make buffers of 40ms of samples self.refRate = 0.04 # mono, 32-bit float audio fmt = QAudioFormat() fmt.setSampleRate(self.getSampleRate()) fmt.setChannelCount(1) fmt.setSampleSize(32) fmt.setSampleType(QAudioFormat.Float) fmt.setByteOrder(QAudioFormat.LittleEndian) fmt.setCodec("audio/pcm") if not deviceInfo.isFormatSupported(fmt): fmt = deviceInfo.nearestFormat(fmt) self.audioInput = QAudioInput(deviceInfo, fmt) self.audioInput.setBufferSize(4*self.buflen) # set size in bytes def startAudio(self): self.audioDevice = self.audioInput.start() self.audioDevice.readyRead.connect(self.updatePlot) def start(self): """ Initialise audio, make DetectorBank, open PlotData window and start audio """ print('Initializing audio...') deviceIdx = self.deviceBox.currentIndex() device = self.availableDevices[deviceIdx] self.initializeAudio(device) print('Making DetectorBank...') pitchOffset = self.makeDetectorBank() print('Making PlotData object...') self.pd = PlotData(self.db.getChans(), pitchOffset) # self.pd.show() print('Starting audio...') self.startAudio() def updatePlot(self): # get data as float32 # 4*buflen is number of bytes data = self.audioDevice.read(4*self.buflen) data = np.frombuffer(data, dtype=np.int16) data = np.array(data/2**15, dtype=np.dtype('float32')) # set DetectorBank input self.db.setInputBuffer(data) # fill z with detector output self.db.getZ(self.z) # self.db.absZ(self.r, self.z) self.pd.update(self.z) # self.close() def makeDetectorBank(self): """ Make DetectorBank from given parameters """ sr = self.getSampleRate() bandwidth_cents = float(self.bandwidthEdit.text()) dmp = float(self.dampingEdit.text()) gain = float(self.gainEdit.text()) edo = float(self.edoEdit.text()) lwr = self.lwrEdit.text() upr = self.uprEdit.text() lwr, pitchOffset = getNoteNum(lwr, edo) upr, _ = getNoteNum(upr, edo) upr += 1 # include upr note in DetectorBank # make and fill frequency and bandwidth arrays freq = np.zeros(int(upr-lwr)) bw = np.zeros(len(freq)) for i in range(len(freq)): k = lwr+i freq[i] = 440*2**(k/edo) # if non-minimum bandwidth detectors requested, find B in Hz if bandwidth_cents != 0: bw[i] = centsToHz(freq[i], bandwidth_cents, edo) # combine into stacked array det_char = np.stack((freq,bw), axis=1) # (almost) empty input buffer buffer = np.zeros(1, dtype=np.float32) # DetectorBank features method = DetectorBank.runge_kutta f_norm = DetectorBank.freq_unnormalized a_norm = DetectorBank.amp_unnormalized self.db = DetectorBank(sr, buffer, 4, det_char, method|f_norm|a_norm, dmp, gain) # create empty output array self.z = np.zeros((int(self.db.getChans()),self.buflen), dtype=np.complex128) self.r = np.zeros(self.z.shape) print("Made DetectorBank with {} channels, with a sample rate of {}Hz" .format(self.db.getChans(), self.db.getSR())) return pitchOffset ## get and/or set various values def getSampleRate(self, returnType=int): return returnType(self.sRateBox.currentText()) @property def refreshRate(self): return self._refRate @refreshRate.setter def refreshRate(self, value): self._refRate = value self.buflen = self._refRate * self.getSampleRate() @property def buflen(self): return self._buflen @buflen.setter def buflen(self, value): self._buflen = int(value)
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)
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 QmyMainWindow(QMainWindow): def __init__(self, parent=None): super().__init__(parent) #调用父类构造函数,创建窗体 self.ui=Ui_MainWindow() #创建UI对象 self.ui.setupUi(self) #构造UI界面 self.ui.progBar_Max.setMaximum(256) self.ui.progBar_Min.setMaximum(256) self.ui.progBar_Diff.setMaximum(256) self.ui.sliderVolumn.setMaximum(100) self.ui.sliderVolumn.setValue(100) self.ui.comboDevices.clear() self.__deviceList=QAudioDeviceInfo.availableDevices(QAudio.AudioInput) #音频输入设备列表 for i in range(len(self.__deviceList)): device=self.__deviceList[i] #QAudioDeviceInfo类 self.ui.comboDevices.addItem(device.deviceName()) ## self.__deviceInfo =None #当前设备信息,QAudioDeviceInfo self.audioDevice=None #音频输入设备,QAudioInput self.BUFFER_SIZE=4000 self.ioDevice=None #第1种读取方法,内建的IODevice ## self.externalReader=None #第2种读取方法,外建的IODevice self.recordFile=QFile() #第3种读取方法,使用QFile直接写入文件 if len(self.__deviceList)>0: self.ui.comboDevices.setCurrentIndex(0) #触发comboDevices的信号currentIndexChanged() ## self.__deviceInfo =deviceList[0] else: self.ui.actStart.setEnabled(False) self.ui.actDeviceTest.setEnabled(False) self.ui.groupBoxDevice.setTitle("支持的音频输入设置(无设备)") ## ==============自定义功能函数======================== def __getSampleTypeStr(self,sampleType): result="Unknown" if sampleType==QAudioFormat.SignedInt: result = "SignedInt" elif sampleType==QAudioFormat.UnSignedInt: result = "UnSignedInt" elif sampleType==QAudioFormat.Float: result = "Float" elif sampleType==QAudioFormat.Unknown: result = "Unknown" return result def __getByteOrderStr(self,endian): if (endian==QAudioFormat.LittleEndian): return "LittleEndian" else: return "BigEndian" ## ==============event处理函数========================== ## ==========由connectSlotsByName()自动连接的槽函数============ @pyqtSlot(int) ##选择音频输入设备 def on_comboDevices_currentIndexChanged(self,index): deviceInfo =self.__deviceList[index] #当前音频设备,QAudioDeviceInfo类型 self.ui.comboCodec.clear() codecs = deviceInfo.supportedCodecs() #支持的音频编码,字符串列表 for strLine in codecs: self.ui.comboCodec.addItem(strLine) self.ui.comboSampleRate.clear() #支持的采样率 sampleRates = deviceInfo.supportedSampleRates() #QList<int> for i in sampleRates: self.ui.comboSampleRate.addItem("%d"% i) self.ui.comboChannels.clear() #支持的通道数 Channels = deviceInfo.supportedChannelCounts() #QList<int> for i in Channels: self.ui.comboChannels.addItem("%d"%i ) self.ui.comboSampleTypes.clear() #支持的采样点类型 sampleTypes = deviceInfo.supportedSampleTypes() #QList<QAudioFormat::SampleType> for i in sampleTypes: sampTypeStr=self.__getSampleTypeStr(i) self.ui.comboSampleTypes.addItem(sampTypeStr,i) self.ui.comboSampleSizes.clear() #采样点大小 sampleSizes = deviceInfo.supportedSampleSizes() #QList<int> for i in sampleSizes: self.ui.comboSampleSizes.addItem("%d"%i) self.ui.comboByteOrder.clear() #字节序 endians = deviceInfo.supportedByteOrders() #QList<QAudioFormat::Endian> for i in endians: self.ui.comboByteOrder.addItem(self.__getByteOrderStr(i)) @pyqtSlot() ##使用内建IODevice def on_radioSaveMode_Inner_clicked(self): self.ui.groupBox_disp.setVisible(True) @pyqtSlot() ##使用QFile对象(test.raw) def on_radioSaveMode_QFile_clicked(self): self.ui.groupBox_disp.setVisible(False) @pyqtSlot(int) ##调节录音音量 def on_sliderVolumn_valueChanged(self,value): self.ui.LabVol.setText("录音音量(%d%%)"%value) @pyqtSlot() ##测试音频输入设备是否支持选择的设置 def on_actDeviceTest_triggered(self): settings=QAudioFormat() settings.setCodec(self.ui.comboCodec.currentText()) settings.setSampleRate(int(self.ui.comboSampleRate.currentText())) settings.setChannelCount(int(self.ui.comboChannels.currentText())) k=self.ui.comboSampleTypes.currentData() settings.setSampleType(k) #QAudioFormat.SampleType settings.setSampleSize(int(self.ui.comboSampleSizes.currentText())) if (self.ui.comboByteOrder.currentText()=="LittleEndian"): settings.setByteOrder(QAudioFormat.LittleEndian) else: settings.setByteOrder(QAudioFormat.BigEndian) index=self.ui.comboDevices.currentIndex() deviceInfo =self.__deviceList[index] #当前音频设备 if deviceInfo.isFormatSupported(settings): QMessageBox.information(self,"消息","测试成功,设备支持此设置") else: QMessageBox.critical(self,"错误","测试失败,设备不支持此设置") @pyqtSlot() ##开始音频输入 def on_actStart_triggered(self): audioFormat=QAudioFormat() #使用固定格式 audioFormat.setSampleRate(8000) audioFormat.setChannelCount(1) audioFormat.setSampleSize(8) audioFormat.setCodec("audio/pcm") audioFormat.setByteOrder(QAudioFormat.LittleEndian) audioFormat.setSampleType(QAudioFormat.UnSignedInt) index=self.ui.comboDevices.currentIndex() deviceInfo =self.__deviceList[index] #当前音频设备 if (False== deviceInfo.isFormatSupported(audioFormat)): QMessageBox.critical(self,"错误","测试失败,输入设备不支持此设置") return self.audioDevice = QAudioInput(deviceInfo,audioFormat) #音频输入设备 self.audioDevice.setBufferSize(self.BUFFER_SIZE) #设置的缓冲区大小,字节数,并不一定等于实际数据块大小 self.audioDevice.stateChanged.connect(self.do_stateChanged) #状态变化 ##1. 使用 start()->QIODevice 启动,返回的内置的IODevice, pull mode,利用readyRead()信号读出数据 if self.ui.radioSaveMode_Inner.isChecked(): self.ioDevice=self.audioDevice.start() #返回内建的IODevice self.ioDevice.readyRead.connect(self.do_IO_readyRead) ## 2. 自定义流设备QWAudioBlockReader,push mode, start(QIODevice),不行 ## if self.ui.radioSaveMode_External.isChecked(): ## self.externalReader = QmyAudioReader() ## self.externalReader.open(QIODevice.WriteOnly) ## self.externalReader.updateBlockInfo.connect(self.do_updateBlockInfo) ## self.audioDevice.start(self.externalReader) #使用外建的IODevice ##3. 写入文件,用 start(QIODevice)启动 if self.ui.radioSaveMode_QFile.isChecked(): self.recordFile.setFileName("test.raw") self.recordFile.open(QIODevice.WriteOnly) self.audioDevice.start(self.recordFile) @pyqtSlot() ##停止音频输入 def on_actStop_triggered(self): self.audioDevice.stop() self.audioDevice.deleteLater() ##1. 使用 QIODevice =start()返回的内置的IODevice, pull mode,利用readyRead()信号读出数据 #无需处理,停止后self.ioDevice自动变为无效 ##2. 外部IODevice接收音频输入数据的流设备 ## if self.ui.radioSaveMode_External.isChecked(): ## self.externalReader.close() ## self.externalReader.updateBlockSize.disconnect(self.do_updateBlockInfo) ## del self.externalReader #删除外建设备 ##3. 写入文件,start(QIODevice) 启动 if self.ui.radioSaveMode_QFile.isChecked(): self.recordFile.close() #关闭文件 ## =============自定义槽函数=============================== ##1. 使用QIODevice* start()返回的内置的IODevice, pull mode,利用readyRead()信号读出数据 def do_IO_readyRead(self): ##内建IODevice,读取缓冲区数据 self.ui.LabBufferSize.setText("bufferSize()=%d" %self.audioDevice.bufferSize()) byteCount = self.audioDevice.bytesReady() #可以读取的字节数 self.ui.LabBytesReady.setText("bytesReady()=%d"%byteCount) if byteCount>self.BUFFER_SIZE: byteCount=self.BUFFER_SIZE buffer=self.ioDevice.read(byteCount) #返回的是bytes类型,便于处理 ## buffer=self.ioDevice.readAll() #不能用readAll()读取,返回的是QByteArray,需要再转换为bytes ## print(type(buffer)) ## print(buffer) maxSize=len(buffer) ## print(maxSize) self.ui.LabBlockSize.setText("IODevice数据字节数=%d"%maxSize) maxV=0 minV=255 for k in range(maxSize): V=buffer[k] #取一个字节,整数 if V>maxV: maxV=V #求最大值 if V<minV: minV=V #求最小值 self.ui.progBar_Max.setValue(maxV) self.ui.progBar_Min.setValue(minV) self.ui.progBar_Diff.setValue(maxV-minV) def do_stateChanged(self,state): ##设备状态变化 isStoped=(state== QAudio.StoppedState) #停止状态 self.ui.groupBox_saveMode.setEnabled(isStoped) self.ui.sliderVolumn.setEnabled(isStoped) self.ui.actStart.setEnabled(isStoped) self.ui.actStop.setEnabled(not isStoped) self.ui.actDeviceTest.setEnabled(isStoped) self.ui.sliderVolumn.setEnabled(isStoped) if state== QAudio.ActiveState: self.ui.statusBar.showMessage("state: ActiveState") elif state== QAudio.SuspendedState: self.ui.statusBar.showMessage("state: SuspendedState") elif state== QAudio.StoppedState: self.ui.statusBar.showMessage("state: StoppedState") elif state== QAudio.IdleState: self.ui.statusBar.showMessage("state: IdleState") elif state== QAudio.InterruptedState: self.ui.statusBar.showMessage("state: InterruptedState")