def initAUD(self): # info = QAudioDeviceInfo.defaultInputDevice() if (~info.isFormatSupported(self.audio.format)): # print("警告,设置的默认音频格式并不支持,将尝试采用最相近的支持格式") # 不知道这里面有什么神改动? self.audio.format = info.nearestFormat(self.audio.format) # update_interval = 160 self.audioRecorder = QAudioInput(self.audio.format) self.audioRecorder.setNotifyInterval(update_interval) #按毫秒ms 类似于QTimer的作用 self.audioRecorder.notify.connect(self.processAudioData) self.audioRecorder_TD = QThread() self.audioRecorder.moveToThread(self.audioRecorder_TD) self.audioRecorder_TD.started.connect(self.startRecord) self.audioRecorder.stateChanged.connect(self.recordStopped) # 总结来说线程只是一个容器,里面执行的循环要是没法结束,强制退出也不好操作 # 所以还是好好写好任务流然后发送信号比较合理 self.audioPlayer = QAudioOutput(self.audio.format) self.audioPlayer.setNotifyInterval(update_interval) self.audioPlayer.notify.connect(self.processAudioData) self.audioPlayer_TD = QThread() self.audioPlayer.moveToThread(self.audioPlayer_TD) self.audioPlayer_TD.started.connect(self.startPlay) self.audioPlayer.stateChanged.connect(self.playStopped)
def createAudioOutput(self): self.m_audioOutput = QAudioOutput(self.m_device, self.m_format) self.m_audioOutput.notify.connect(self.notified) self.m_audioOutput.stateChanged.connect(self.handleStateChanged) self.m_generator.start() self.m_audioOutput.start(self.m_generator) self.m_volumeSlider.setValue(self.m_audioOutput.volume() * 100)
def initAudio(self): self.audioformat = QAudioFormat() self.audioformat.setSampleRate(self.samplerate) self.audioformat.setChannelCount(2) self.audioformat.setSampleSize(32) self.audioformat.setCodec('audio/pcm') self.audioformat.setByteOrder(QAudioFormat.LittleEndian) self.audioformat.setSampleType(QAudioFormat.Float) self.audiooutput = QAudioOutput(self.audioformat) self.audiooutput.setVolume(1.0)
def __init__(self, parent=None): print("Tone Widget inst") QWidget.__init__(self, parent) self.activeGen = ActiveGen(sampleRate=SAMPLE_RATE, samplePerRead=Meep.SAMPLES_PER_READ) self.createUI(self) # Meep playback format initialization format = QAudioFormat() format.setChannelCount(AUDIO_CHANS) format.setSampleRate(SAMPLE_RATE) format.setSampleSize(SAMPLE_SIZE) format.setCodec("audio/pcm") format.setByteOrder(QAudioFormat.LittleEndian) format.setSampleType(QAudioFormat.SignedInt) # check compatibility of format with device info = QAudioDeviceInfo(QAudioDeviceInfo.defaultOutputDevice()) if info.isFormatSupported(format) is False: print( "Raw audio format not supported by backend, cannot play audio." ) return None # Audio Output init self.output = QAudioOutput(format, self) output_buffer_size = \ int(2*SAMPLE_RATE \ *CTRL_INTERVAL/1000) self.output.setBufferSize(output_buffer_size) # initialize and start the audio playback self.generator = Meep(format, self.activeGen, self) self.generator.start() self.output.start(self.generator) # Create the port reader object self.midiListener = MidiPortReader() # Create a thread which will read it self.listenerThread = QThread() # move QObjet to a new thread self.midiListener.moveToThread(self.listenerThread) # Connect the signals to slot functions self.midiListener.newNoteFrequency.connect(self.activeGen.setFreq) self.midiListener.newNotePress.connect(self.activeGen.setNote) # Tell Qt the function to call # when it starts the thread self.listenerThread.started.connect(self.midiListener.listener) # start the thread self.listenerThread.start()
def initAudio(self): self.audioformat = QAudioFormat() self.audioformat.setSampleRate(self.samplerate) self.audioformat.setChannelCount(2) self.audioformat.setSampleSize(32) self.audioformat.setCodec('audio/pcm') self.audioformat.setByteOrder(QAudioFormat.LittleEndian) self.audioformat.setSampleType(QAudioFormat.Float) # self.audiodeviceinfo = QAudioDeviceInfo(QAudioDeviceInfo.defaultOutputDevice()) self.audiooutput = QAudioOutput(self.audioformat) self.audiooutput.setVolume(self.initVolume)
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())
def __init__(self, parent=None): QWidget.__init__(self, parent) format = QAudioFormat() format.setChannelCount(1) format.setSampleRate(22050) format.setSampleSize(16) format.setCodec("audio/pcm") format.setByteOrder(QAudioFormat.LittleEndian) format.setSampleType(QAudioFormat.SignedInt) self.output = QAudioOutput(format, self) self.frequency = 440 self.volume = 0 self.buffer = QBuffer() self.data = QByteArray() self.deviceLineEdit = QLineEdit() self.deviceLineEdit.setReadOnly(True) self.deviceLineEdit.setText( QAudioDeviceInfo.defaultOutputDevice().deviceName()) self.pitchSlider = QSlider(Qt.Horizontal) self.pitchSlider.setMaximum(100) self.volumeSlider = QSlider(Qt.Horizontal) self.volumeSlider.setMaximum(32767) self.volumeSlider.setPageStep(1024) self.playButton = QPushButton(self.tr("&Play")) self.pitchSlider.valueChanged.connect(self.changeFrequency) self.volumeSlider.valueChanged.connect(self.changeVolume) self.playButton.clicked.connect(self.play) formLayout = QFormLayout() formLayout.addRow(self.tr("Device:"), self.deviceLineEdit) formLayout.addRow(self.tr("P&itch:"), self.pitchSlider) formLayout.addRow(self.tr("&Volume:"), self.volumeSlider) buttonLayout = QVBoxLayout() buttonLayout.addWidget(self.playButton) buttonLayout.addStretch() horizontalLayout = QHBoxLayout(self) horizontalLayout.addLayout(formLayout) horizontalLayout.addLayout(buttonLayout) self.play() self.createData()
def __init__(self): super(JackStreamListen, self).__init__() self.ip = '127.0.0.1' self.port = '23' self.state = 'disconnected' self.channel_select = -1 self.channel_count = -1 # self.tsfmt = '%y%m%d-%H%M%S:' # self.textFgColor = 'rgb(0,0,0)' # self.textBgColor = 'rgb(255,255,255)' # self.fontstr = 'Helvetica' logging.debug('DBG: state = ' + self.state) # self.insFile = "";self.insDir="" # self.insSleepAmount = 0; self.qsock = QtNetwork.QTcpSocket(self) self.qsock.readyRead.connect(self.onReadyRead) # create None/empty variables to be created/updated later self.prevpkt = bytearray() # note a QByteArray b/c reusing msgify_pkt self.audiodata = bytearray() self.clips = [] self.rms = [] self.audiofmt = QAudioFormat() self.audioout = QAudioOutput() self.iodevice = self.createIoDevice() #FIXME self.tcpSocket.error.connect(self.displayError) # self.font = QtGui.QFont() self.loadSettings() self.initUI()
def create_AUDIO(self, format): format.setChannelCount(AUDIO_CHANS) format.setSampleRate(SAMPLE_RATE) format.setSampleSize(SAMPLE_SIZE) format.setCodec("audio/pcm") format.setByteOrder( QAudioFormat.LittleEndian ) format.setSampleType( QAudioFormat.SignedInt ) self.output = QAudioOutput(format, self) output_buffer_size = \ int(2*SAMPLE_RATE \ *CTRL_INTERVAL/1000) self.output.setBufferSize( output_buffer_size )
def __init__(self,parent=None): #UI QWidget.__init__(self,parent) self.create_UI(parent) #audio formatting format=QAudioFormat() format.setChannelCount(AUDIO_CHANS) format.setSampleRate(SAMPLE_RATE) format.setSampleSize(SAMPLE_SIZE) format.setCodec("audio/pcm") format.setByteOrder( QAudioFormat.LittleEndian ) format.setSampleType( QAudioFormat.SignedInt ) self.output=QAudioOutput(format,self) output_buffer_size=\ int(2*SAMPLE_RATE \ *CTRL_INTERVAL/1000) self.output.setBufferSize( output_buffer_size ) self.generator=Generator(format,self) #THREADS self.midiListener=MidiPortReader() self.listenerThread=QThread() self.midiListener.moveToThread( self.listenerThread ) self.listenerThread.started.connect( self.midiListener.listener ) self.listenerThread.start() self.midiListener.noteOff.connect(self.generator.noteOff) self.midiListener.noteVelocity.connect(self.generator.noteVelocity) self.midiListener.noteOn.connect(self.generator.noteOn) self.volumeSlider.valueChanged.connect(self.generator.volSlide) self.qfacSlider.valueChanged.connect(self.generator.qFactor) self.generator.start() self.output.start(self.generator)
class ToneGenerator(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) format = QAudioFormat() format.setChannelCount(AUDIO_CHANS) format.setSampleRate(SAMPLE_RATE) format.setSampleSize(SAMPLE_SIZE) format.setCodec("audio/pcm") format.setByteOrder(QAudioFormat.LittleEndian) format.setSampleType(QAudioFormat.SignedInt) self.output = QAudioOutput(format, self) output_buffer_size = \ int(2*SAMPLE_RATE \ *CTRL_INTERVAL/1000) self.output.setBufferSize(output_buffer_size) self.generator = Meep(format, self) self.generator.start() self.output.start(self.generator) # Create the port reader object self.midiListener = MidiPortReader() # Create a thread which will read it self.listenerThread = QThread() # Take the object and move it # to the new thread (it isn't running yet) self.midiListener.moveToThread(self.listenerThread) self.midiListener.newNoteFrequency.connect(self.generator.changeFreq) # Tell Qt the function to call # when it starts the thread self.listenerThread.started.connect(self.midiListener.listener) # Fingers in ears, eyes tight shut... self.listenerThread.start()
def set_data(self, mono_sig, sr): # if not self.format: self.format = QAudioFormat() self.format.setChannelCount(1) self.format.setSampleRate(sr) #numpy is in bites, qt in bits self.format.setSampleSize(mono_sig.dtype.itemsize * 8) self.format.setCodec("audio/pcm") self.format.setByteOrder(QAudioFormat.LittleEndian) self.format.setSampleType(QAudioFormat.Float) self.output = QAudioOutput(self.format, self) self.output.stateChanged.connect(self.audio_state_changed) #change the content without stopping playback p = self.buffer.pos() if self.buffer.isOpen(): self.buffer.close() self.data = mono_sig.tobytes() self.buffer.setData(self.data) self.buffer.open(QIODevice.ReadWrite) self.buffer.seek(p)
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()
def __init__(self, parent=None): QWidget.__init__(self, parent) format = QAudioFormat() format.setChannelCount(AUDIO_CHANS) format.setSampleRate(SAMPLE_RATE) format.setSampleSize(SAMPLE_SIZE) format.setCodec("audio/pcm") format.setByteOrder(QAudioFormat.LittleEndian) format.setSampleType(QAudioFormat.SignedInt) self.output = QAudioOutput(format, self) output_buffer_size = \ int(2*SAMPLE_RATE \ *CTRL_INTERVAL/1000) self.output.setBufferSize(output_buffer_size) self.generator = Meep(format, self) self.generator.start() self.output.start(self.generator)
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())
def get_audio_output(params): """ Create and return a QAudioOutput from wav params. """ enc = 'audio/pcm' fmt = QAudioFormat() fmt.setChannelCount(params.nchannels) fmt.setSampleRate(params.framerate) fmt.setSampleSize(params.sampwidth * 8) fmt.setCodec(enc) fmt.setByteOrder(QAudioFormat.LittleEndian) fmt.setSampleType(QAudioFormat.SignedInt) return QAudioOutput(fmt)
def __init__(self, parent=None): QWidget.__init__(self, parent) format = QAudioFormat() format.setChannelCount(AUDIO_CHANS) format.setSampleRate(SAMPLE_RATE) format.setSampleSize(SAMPLE_SIZE) format.setCodec("audio/pcm") format.setByteOrder(QAudioFormat.LittleEndian) format.setSampleType(QAudioFormat.SignedInt) self.output = QAudioOutput(format, self) output_buffer_size = \ int(2*CTRL_INTERVAL/1000) self.output.setBufferSize(output_buffer_size) self.generator = Generator(format, self) self.midiListener = MidiPortReader() self.listenerThread = QThread() self.midiListener.moveToThread(self.listenerThread) self.listenerThread.started.connect(self.midiListener.listener) self.midiListener.addVoice.connect(self.generator.addVoice) self.midiListener.removeVoice.connect(self.generator.removeVoice) self.createUI() self.pslider.valueChanged.connect(self.generator.changeP) self.fslider.valueChanged.connect(self.generator.qCalc) self.semiDown.clicked.connect(self.smDown) self.semiUp.clicked.connect(self.smUp) self.octaveDown.clicked.connect(self.ovDown) self.octaveUp.clicked.connect(self.ovUp) self.listenerThread.start() self.generator.start() self.output.start(self.generator)
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 AudioAnalysis(QDialog): def __init__(self, mainwin, dir): super().__init__() self.main_Win = mainwin self.snd_record_ctr = 0 self.snd_play_ctr = 0 self.snd_reset_ctr = 0 self.is_snd_recording = None # self.audio = Audio(save_dir=dir) self.initAUD() self.initIF() self.initWaveList() def initIF(self): self.layout = QGridLayout(self) self.setLayout(self.layout) icon = QIcon() icon.addPixmap(QPixmap("./style/logo3.png")) self.setWindowIcon(icon) self.setWindowTitle("语音录制与分析") self.list_LW = QListWidget(self) self.list_LW.setMaximumWidth(160) class WaveSpectrum(QWidget): def __init__(self, parent=None, maindlg=None): super(WaveSpectrum, self).__init__(parent) self.main_Dlg = maindlg #self.pg_PL = pg.PlotWidget(enableMenu=False) self.audio = self.main_Dlg.audio self.layout = QGridLayout(self) self.setLayout(self.layout) self.pg_PL = pg.PlotWidget() #pg.plot(title="Three plot curves") self.pg_PL.hideButtons() self.layout.addWidget(self.pg_PL) self.item = self.pg_PL.getPlotItem() self.item.hideButtons() self.item.setMouseEnabled(y=False) self.item.setYRange(0,20000) range = self.audio.rate/2 self.item.setXRange(-range,range, padding=0) self.axis = self.item.getAxis("bottom") self.axis.setLabel("频率(赫兹)") def updatePlot(self): try: data = np.fromstring(self.audio.block, 'int16') #print(data) T = 1.0/self.audio.rate N = data.shape[0] Fx = (1./N) * np.fft.fft(data) # 万一N==0 except Exception as e: print("??",e) else: f = np.fft.fftfreq(N, T) Fx = np.fft.fftshift(Fx) f = np.fft.fftshift(f) self.item.plot(x=f.tolist(), y=(np.absolute(Fx)).tolist(), clear=True) self.wave_spectrum_PG = WaveSpectrum(maindlg=self) self.result_LB = QLabel(self) self.result_LB.setText("欢迎使用") self.running_SL = QSlider(Qt.Horizontal) self.running_SL.setMinimum(0) self.running_SL.setMaximum(100) self.running_SL.setStyleSheet("QSlider::handle:horizontal {background-color: #d91900;}") self.save_BT = QPushButton(self) self.save_BT.setText("保存与分析") self.save_BT.setMinimumSize(128,32) self.record_BT = QPushButton(self) self.record_BT.setText("开始录音") self.record_BT.setMinimumSize(144,32) self.play_BT = QPushButton(self) self.play_BT.setText("开始播放") self.play_BT.setMinimumSize(144,32) self.reset_BT = QPushButton(self) self.reset_BT.setText("停止") self.reset_BT.setMinimumSize(128,32) self.layout.addWidget(self.list_LW, 0,0,1,1) self.layout.addWidget(self.wave_spectrum_PG, 0,1, 1,3) self.layout.addWidget(self.result_LB, 1,0, 1,4) self.layout.addWidget(self.running_SL, 2,0, 1,4) self.layout.addWidget(self.save_BT, 3,0, 2,1) self.layout.addWidget(self.record_BT, 3,1, 2,1) self.layout.addWidget(self.play_BT, 3,2, 2,1) self.layout.addWidget(self.reset_BT, 3,3, 2,1) self.list_LW.itemClicked.connect(self.sel2Play) self.record_BT.clicked.connect(self.click2Record) self.running_SL.sliderReleased.connect(self.dragPosPlay) # 注意这里得是用户主动的动作哟 另外如果需要点击位置定位的话还必须要重写mousePressEvent,这里就不弄了 self.play_BT.clicked.connect(self.click2Play) self.reset_BT.clicked.connect(self.click2Reset) self.save_BT.clicked.connect(self.click2Save) def initWaveList(self): self.wave_dict = {"小黄":["catH1.wav",0],"小黄骚":["catH2.wav",0], "小黄又骚":["catH3.wav",0], "小黄又又骚":["catH4.wav",0] ,"煤球":["catM1.wav",0],"煤球骚":["catM2.wav",0], "煤球又骚":["catM3.wav",0] ,"老公":["laog.wav",0], "老婆":["laop.wav",0]} for k in self.wave_dict: item = QListWidgetItem() item.setText(k) item.setData(Qt.UserRole, self.wave_dict[k]) self.list_LW.addItem(item) def initAUD(self): # info = QAudioDeviceInfo.defaultInputDevice() if (~info.isFormatSupported(self.audio.format)): # print("警告,设置的默认音频格式并不支持,将尝试采用最相近的支持格式") # 不知道这里面有什么神改动? self.audio.format = info.nearestFormat(self.audio.format) # update_interval = 160 self.audioRecorder = QAudioInput(self.audio.format) self.audioRecorder.setNotifyInterval(update_interval) #按毫秒ms 类似于QTimer的作用 self.audioRecorder.notify.connect(self.processAudioData) self.audioRecorder_TD = QThread() self.audioRecorder.moveToThread(self.audioRecorder_TD) self.audioRecorder_TD.started.connect(self.startRecord) self.audioRecorder.stateChanged.connect(self.recordStopped) # 总结来说线程只是一个容器,里面执行的循环要是没法结束,强制退出也不好操作 # 所以还是好好写好任务流然后发送信号比较合理 self.audioPlayer = QAudioOutput(self.audio.format) self.audioPlayer.setNotifyInterval(update_interval) self.audioPlayer.notify.connect(self.processAudioData) self.audioPlayer_TD = QThread() self.audioPlayer.moveToThread(self.audioPlayer_TD) self.audioPlayer_TD.started.connect(self.startPlay) self.audioPlayer.stateChanged.connect(self.playStopped) # def startRecord(self): self.audioRecorder.start(self.audio.record_buffer) # 独立出来主要就是为了传个参数进去 def click2Record(self): if self.snd_play_ctr != 0: self.audioPlayer.suspend() self.audioPlayer.stop() self.audio.play_buffer.close() self.running_SL.setValue(0) self.audioPlayer_TD.quit() self.snd_play_ctr = 0 self.play_BT.setText("开始播放") # self.is_snd_recording = True self.running_SL.setStyleSheet("QSlider::handle:horizontal {background-color: #d91900;}") self.running_SL.setValue(0) if self.snd_record_ctr == 0: self.audio.record_buffer.open(QIODevice.WriteOnly) self.audioRecorder_TD.start() #注意这里是分线程进行 self.record_BT.setText("暂停录音") self.reset_BT.setText("停止录音") elif self.snd_record_ctr % 2 == 1: self.audioRecorder.suspend() self.result_LB.setText("录音暂停") self.record_BT.setText("继续录音") else: # self.snd_record_ctr % 2 == 0: self.audioRecorder.resume() self.record_BT.setText("暂停录音") self.snd_record_ctr += 1 def recordStopped(self): if self.audioRecorder.state() == QAudio.StoppedState: #==2 #QAudio.IdleState: #==3; self.audioRecorder_TD.quit() def startPlay(self): self.audioPlayer.start(self.audio.play_buffer) def click2Play(self): if self.is_snd_recording == None: self.result_LB.setText("还没录音呢!!!") else: if self.snd_record_ctr % 2 == 1: self.audioRecorder.suspend() self.record_BT.setText("继续录音") self.snd_record_ctr += 1 # self.is_snd_recording = False self.running_SL.setStyleSheet("QSlider::handle:horizontal {background-color: #007ad9;}") if self.snd_play_ctr == 0: data = self.audio.record_buffer.data() self.audio.play_buffer.setData(data) self.audio.play_buffer.open(QIODevice.ReadOnly) # 要在关闭的情况下设置数据然后在以某种模式打开 self.audioPlayer_TD.start() self.running_SL.setValue(0) self.start_time = 0 # self.start_time = self.running_SL.value() / 100 * self.audio.duration # self.audioPlayer.setVolume(0.8) self.play_BT.setText("暂停播放") self.reset_BT.setText("停止播放") elif self.snd_play_ctr % 2 == 1: self.audioPlayer.suspend() self.result_LB.setText("播放暂停") self.play_BT.setText("继续播放") else: self.audioPlayer.resume() self.play_BT.setText("暂停播放") self.snd_play_ctr += 1 def playStopped(self): if self.audioPlayer.state() == QAudio.IdleState: #==3; #QAudio.StoppedState: #==2 self.audioPlayer.stop() self.running_SL.setValue(0) self.audio.play_buffer.close() self.audioPlayer_TD.quit() self.snd_play_ctr = 0 self.play_BT.setText("开始播放") def dragPosPlay(self): if self.is_snd_recording == None: self.running_SL.setValue(0) else: if self.is_snd_recording & (self.snd_record_ctr % 2 == 1): self.audioRecorder.suspend() self.record_BT.setText("继续录音") self.snd_record_ctr += 1 if (not self.is_snd_recording) & (self.snd_play_ctr % 2 == 1): self.audioPlayer.suspend() self.play_BT.setText("继续播放") self.snd_play_ctr += 1 self.is_snd_recording = False self.running_SL.setStyleSheet("QSlider::handle:horizontal {background-color: #007ad9;}") self.audioPlayer.stop() self.audio.play_buffer.close() self.audioPlayer_TD.quit() # data = self.audio.record_buffer.data() self.audio.play_buffer.setData(data) self.audio.play_buffer.open(QIODevice.ReadOnly) # 要在关闭的情况下设置数据然后在以某种模式打开 self.audioPlayer_TD.start() data_size = self.audio.record_buffer.data().size() sel_pcent = self.running_SL.value() / 100 sel_size = int(sel_pcent * data_size) self.audio.pos = int(sel_pcent * (data_size / self.audio.chunksize)) # 重设第几个chunk开始播放 self.start_time = int(sel_pcent * self.audio.duration) # 重设开始播放时间 self.audio.play_buffer.seek(sel_size) self.snd_play_ctr = 1 self.play_BT.setText("暂停播放") def sel2Play(self, item): c0 = (self.is_snd_recording == None) c1 = ((self.is_snd_recording == False) & (self.snd_play_ctr % 2 == 0)) c2 = ((self.is_snd_recording == True) & (self.snd_record_ctr % 2 == 0)) if (c0 | c1 | c2): self.cur_item = item sound_dir = "./sound/" #self.cur_wave = os.path.abspath(item.data(Qt.UserRole)[0]) self.cur_wave = item.data(Qt.UserRole)[0] sound_path = os.path.join(sound_dir, self.cur_wave) with wave.open(sound_path, 'rb') as wf: data = wf.readframes(wf.getnframes()) self.audio.play_buffer.setData(data) self.audio.play_buffer.open(QIODevice.ReadOnly) self.start_time = 0 self.audio.duration = 10 # 随便给了个值,避免产生除0的问题其他没啥用 self.audioPlayer_TD.start() def click2Reset(self): if self.is_snd_recording == None: self.result_LB.setText("还没录音呢!!!") elif self.is_snd_recording: self.audioRecorder.stop() self.audio.record_buffer = QBuffer() self.snd_record_ctr = 0 self.result_LB.setText("录音停止") self.record_BT.setText("开始录音") else: #not self.is_snd_recording: self.audioPlayer.stop() self.audio.pos = 0 self.snd_play_ctr = 0 self.result_LB.setText("播放停止") self.play_BT.setText("开始播放") self.running_SL.setValue(0) def click2Save(self): if self.is_snd_recording == None: self.result_LB.setText("还没录音呢!!!") elif self.is_snd_recording: self.audioRecorder.suspend() else: self.audioPlayer.suspend() #self.audio.save_path = QFileDialog().getSaveFileName(self.main_Dlg, "选个保存的地方吧", new_path)[0] # 注意末尾那个[0]别丢了,不然返回的是tuple类型 self.audio.saveWave() self.snd_record_ctr = 0 self.result_LB.setText("录音存于:{};刚刚应该是{}叫了:)".format(os.path.abspath(self.audio.save_path), self.getMinDist())) self.record_BT.setText("开始录音") def processAudioData(self): if self.is_snd_recording: #self.audioRecorder.state() == QAudio.ActiveState: self.audio.block = self.audio.record_buffer.data().right(self.audio.chunksize) self.audio.duration = self.audioRecorder.processedUSecs() # 注意这里是微秒!!! interval = 10 self.running_SL.setValue((self.audio.duration / 1000000) % interval * (100 / interval)) show_info = "已录制{:.1f}秒".format(self.audio.duration/1000000.0) self.result_LB.setText(show_info) else: # self.audioPlayer.state() == QAudio.ActiveState: # 试过chop 不过好像没有必要 self.audio.block = self.audio.play_buffer.data().mid(self.audio.pos*self.audio.chunksize, self.audio.chunksize) self.audio.pos += 1 self.running_SL.setValue((self.start_time + self.audioPlayer.processedUSecs())/self.audio.duration*100) show_info = "正在播放{:.1f}/{:.1f}秒".format((self.start_time + self.audioPlayer.processedUSecs())/1000000.0 ,self.audio.duration/1000000.0) self.result_LB.setText(show_info) self.wave_spectrum_PG.updatePlot() def getMFCC(self,path): (rate, sig) = scwav.read(path) mfcc_feature = mfcc(sig, rate) nmfcc = np.array(mfcc_feature) y, sr = librosa.load(path) return librosa.feature.mfcc(y, sr) def compareMFCC(self, demo_path): mfcc1 = self.getMFCC(self.audio.save_path) print(demo_path) mfcc2 = self.getMFCC(demo_path) norm = lambda x, y: nlnorm(x-y, ord=1) d, cost_matrix, acc_cost_matrix, path = dtw(mfcc1.T, mfcc2.T, dist=norm) return d def getMinDist(self): i = 1000000 sound_dir = "./sound/" for k in self.wave_dict: self.wave_dict[k][1] = self.compareMFCC(os.path.join(sound_dir, self.wave_dict[k][0])) print("{}:{:.1f}".format(k, self.wave_dict[k][1])) i = min(i, self.wave_dict[k][1]) if i == self.wave_dict[k][1]: min_k = k return min_k
class AudioTest(QMainWindow): PUSH_MODE_LABEL = "Enable push mode" PULL_MODE_LABEL = "Enable pull mode" SUSPEND_LABEL = "Suspend playback" RESUME_LABEL = "Resume playback" DurationSeconds = 1 ToneSampleRateHz = 600 DataSampleRateHz = 44100 def __init__(self): super(AudioTest, self).__init__() self.m_device = QAudioDeviceInfo.defaultOutputDevice() self.m_output = None self.initializeWindow() self.initializeAudio() def initializeWindow(self): layout = QVBoxLayout() self.m_deviceBox = QComboBox(activated=self.deviceChanged) for deviceInfo in QAudioDeviceInfo.availableDevices( QAudio.AudioOutput): self.m_deviceBox.addItem(deviceInfo.deviceName(), deviceInfo) layout.addWidget(self.m_deviceBox) self.m_modeButton = QPushButton(clicked=self.toggleMode) self.m_modeButton.setText(self.PUSH_MODE_LABEL) layout.addWidget(self.m_modeButton) self.m_suspendResumeButton = QPushButton( clicked=self.toggleSuspendResume) self.m_suspendResumeButton.setText(self.SUSPEND_LABEL) layout.addWidget(self.m_suspendResumeButton) volumeBox = QHBoxLayout() volumeLabel = QLabel("Volume:") self.m_volumeSlider = QSlider(Qt.Horizontal, minimum=0, maximum=100, singleStep=10, valueChanged=self.volumeChanged) volumeBox.addWidget(volumeLabel) volumeBox.addWidget(self.m_volumeSlider) layout.addLayout(volumeBox) window = QWidget() window.setLayout(layout) self.setCentralWidget(window) def initializeAudio(self): self.m_pullTimer = QTimer(self, timeout=self.pullTimerExpired) self.m_pullMode = True self.m_format = QAudioFormat() self.m_format.setSampleRate(self.DataSampleRateHz) self.m_format.setChannelCount(1) self.m_format.setSampleSize(16) self.m_format.setCodec('audio/pcm') self.m_format.setByteOrder(QAudioFormat.LittleEndian) self.m_format.setSampleType(QAudioFormat.SignedInt) info = QAudioDeviceInfo(QAudioDeviceInfo.defaultOutputDevice()) if not info.isFormatSupported(self.m_format): qWarning("Default format not supported - trying to use nearest") self.m_format = info.nearestFormat(self.m_format) self.m_generator = Generator(self.m_format, self.DurationSeconds * 1000000, self.ToneSampleRateHz, self) self.createAudioOutput() def createAudioOutput(self): self.m_audioOutput = QAudioOutput(self.m_device, self.m_format) self.m_audioOutput.notify.connect(self.notified) self.m_audioOutput.stateChanged.connect(self.handleStateChanged) self.m_generator.start() self.m_audioOutput.start(self.m_generator) self.m_volumeSlider.setValue(self.m_audioOutput.volume() * 100) def deviceChanged(self, index): self.m_pullTimer.stop() self.m_generator.stop() self.m_audioOutput.stop() self.m_device = self.m_deviceBox.itemData(index) self.createAudioOutput() def volumeChanged(self, value): if self.m_audioOutput is not None: self.m_audioOutput.setVolume(value / 100.0) def notified(self): qWarning( "bytesFree = %d, elapsedUSecs = %d, processedUSecs = %d" % (self.m_audioOutput.bytesFree(), self.m_audioOutput.elapsedUSecs(), self.m_audioOutput.processedUSecs())) def pullTimerExpired(self): if self.m_audioOutput is not None and self.m_audioOutput.state( ) != QAudio.StoppedState: chunks = self.m_audioOutput.bytesFree( ) // self.m_audioOutput.periodSize() for _ in range(chunks): data = self.m_generator.read(self.m_audioOutput.periodSize()) if data is None or len( data) != self.m_audioOutput.periodSize(): break self.m_output.write(data) def toggleMode(self): self.m_pullTimer.stop() self.m_audioOutput.stop() if self.m_pullMode: self.m_modeButton.setText(self.PULL_MODE_LABEL) self.m_output = self.m_audioOutput.start() self.m_pullMode = False self.m_pullTimer.start(20) else: self.m_modeButton.setText(self.PUSH_MODE_LABEL) self.m_pullMode = True self.m_audioOutput.start(self.m_generator) self.m_suspendResumeButton.setText(self.SUSPEND_LABEL) def toggleSuspendResume(self): if self.m_audioOutput.state() == QAudio.SuspendedState: qWarning("status: Suspended, resume()") self.m_audioOutput.resume() self.m_suspendResumeButton.setText(self.SUSPEND_LABEL) elif self.m_audioOutput.state() == QAudio.ActiveState: qWarning("status: Active, suspend()") self.m_audioOutput.suspend() self.m_suspendResumeButton.setText(self.RESUME_LABEL) elif self.m_audioOutput.state() == QAudio.StoppedState: qWarning("status: Stopped, resume()") self.m_audioOutput.resume() self.m_suspendResumeButton.setText(self.SUSPEND_LABEL) elif self.m_audioOutput.state() == QAudio.IdleState: qWarning("status: IdleState") stateMap = { QAudio.ActiveState: "ActiveState", QAudio.SuspendedState: "SuspendedState", QAudio.StoppedState: "StoppedState", QAudio.IdleState: "IdleState" } def handleStateChanged(self, state): qWarning("state = " + self.stateMap.get(state, "Unknown"))
class ToneWindow(QWidget): """ Central Widget of the application. Defines the format of the audio channel and initializes the midi listener, generators and modifiers. The midi listener is operated on separate thread handled by a Qthread. The view components are initialized and put together in a general layout using the CreateUI method. """ def __init__(self, parent=None): print("Tone Widget inst") QWidget.__init__(self, parent) self.activeGen = ActiveGen(sampleRate=SAMPLE_RATE, samplePerRead=Meep.SAMPLES_PER_READ) self.createUI(self) # Meep playback format initialization format = QAudioFormat() format.setChannelCount(AUDIO_CHANS) format.setSampleRate(SAMPLE_RATE) format.setSampleSize(SAMPLE_SIZE) format.setCodec("audio/pcm") format.setByteOrder(QAudioFormat.LittleEndian) format.setSampleType(QAudioFormat.SignedInt) # check compatibility of format with device info = QAudioDeviceInfo(QAudioDeviceInfo.defaultOutputDevice()) if info.isFormatSupported(format) is False: print( "Raw audio format not supported by backend, cannot play audio." ) return None # Audio Output init self.output = QAudioOutput(format, self) output_buffer_size = \ int(2*SAMPLE_RATE \ *CTRL_INTERVAL/1000) self.output.setBufferSize(output_buffer_size) # initialize and start the audio playback self.generator = Meep(format, self.activeGen, self) self.generator.start() self.output.start(self.generator) # Create the port reader object self.midiListener = MidiPortReader() # Create a thread which will read it self.listenerThread = QThread() # move QObjet to a new thread self.midiListener.moveToThread(self.listenerThread) # Connect the signals to slot functions self.midiListener.newNoteFrequency.connect(self.activeGen.setFreq) self.midiListener.newNotePress.connect(self.activeGen.setNote) # Tell Qt the function to call # when it starts the thread self.listenerThread.started.connect(self.midiListener.listener) # start the thread self.listenerThread.start() def createUI(self, parent): """Receives a parent QWidget and produces the view in it""" print("Create UI") slidLayout = QHBoxLayout() vLayout = QVBoxLayout(self) vLayout.setSpacing(10) # initialize title label and UI constraints self.title = QLabel(self) self.title.setText("Tone Generator") self.title.setMargin(10) self.title.setFixedHeight(50) self.title.setFixedWidth(400) self.title.setAlignment(Qt.AlignCenter) # set title style self.title.setStyleSheet(""" font-family : "Lucida Console"; font-size: 20px; color : #F2F2F2; border: 2px solid green; border-radius: 4px; border-color: #082126; padding: 2px; background-color : #0E3740 ; """) # set background color self.setStyleSheet(" background-color : #082126 ; ") # initialize the view components filtSlider = sliders.FiltSlider(self.activeGen, parent).createUI(parent) adsrSlider = sliders.ADSRSlider(self.activeGen, parent).createUI(parent) masterSlider = sliders.MasterSlider(self.activeGen, parent).createUI(parent) genSlider = sliders.GenSlider(self.activeGen, parent).createUI(parent) # add the two sliders and ADSR layout in a horizontal layout slidLayout.addStretch(1) slidLayout.addWidget(filtSlider) slidLayout.addStretch(1) slidLayout.addWidget(adsrSlider, Qt.AlignHCenter) slidLayout.addStretch(1) slidLayout.addWidget(masterSlider) slidLayout.addStretch(1) # put all components in the vertical layout vLayout.addWidget(self.title) vLayout.setAlignment(self.title, Qt.AlignCenter) vLayout.addSpacing(20) vLayout.addLayout(slidLayout) vLayout.addSpacing(20) vLayout.addWidget(genSlider) vLayout.setAlignment(genSlider, Qt.AlignCenter)
class MainWindow(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) format = QAudioFormat() format.setChannelCount(AUDIO_CHANS) format.setSampleRate(SAMPLE_RATE) format.setSampleSize(SAMPLE_SIZE) format.setCodec("audio/pcm") format.setByteOrder(QAudioFormat.LittleEndian) format.setSampleType(QAudioFormat.SignedInt) self.output = QAudioOutput(format, self) output_buffer_size = \ int(2*CTRL_INTERVAL/1000) self.output.setBufferSize(output_buffer_size) self.generator = Generator(format, self) self.midiListener = MidiPortReader() self.listenerThread = QThread() self.midiListener.moveToThread(self.listenerThread) self.listenerThread.started.connect(self.midiListener.listener) self.midiListener.addVoice.connect(self.generator.addVoice) self.midiListener.removeVoice.connect(self.generator.removeVoice) self.createUI() self.pslider.valueChanged.connect(self.generator.changeP) self.fslider.valueChanged.connect(self.generator.qCalc) self.semiDown.clicked.connect(self.smDown) self.semiUp.clicked.connect(self.smUp) self.octaveDown.clicked.connect(self.ovDown) self.octaveUp.clicked.connect(self.ovUp) self.listenerThread.start() self.generator.start() self.output.start(self.generator) def createUI(self): label = QLabel() label.setText("Pulse ammount") self.pslider = QSlider(Qt.Horizontal) self.pslider.setMinimum(0) self.pslider.setMaximum(100) self.pslider.setValue(60) flabel = QLabel() flabel.setText("Q") self.fslider = QSlider(Qt.Horizontal) self.fslider.setMinimum(10) self.fslider.setMaximum(10000) self.fslider.setValue(1000) self.octaveUp = QPushButton("+12") self.octaveDown = QPushButton("-12") self.semiUp = QPushButton("+1") self.semiDown = QPushButton("-1") self.transLabel = QLabel(str(self.generator.transpose)) vl = QVBoxLayout(self) hl = QHBoxLayout() hl.addWidget(label) hl.addStretch(1) hl.addWidget(self.pslider) vl.addLayout(hl) fhl = QHBoxLayout() fhl.addWidget(flabel) fhl.addStretch(1) fhl.addWidget(self.fslider) vl.addLayout(fhl) bhl = QHBoxLayout() bhl.addWidget(self.octaveDown) bhl.addWidget(self.semiDown) bhl.addWidget(self.transLabel) bhl.addWidget(self.semiUp) bhl.addWidget(self.octaveUp) vl.addLayout(bhl) def transposeChange(self, amt): self.generator.transpose = self.generator.transpose + amt self.transLabel.setText(str(self.generator.transpose)) @pyqtSlot() def ovUp(self): self.transposeChange(12) @pyqtSlot() def ovDown(self): self.transposeChange(-12) @pyqtSlot() def smUp(self): self.transposeChange(1) @pyqtSlot() def smDown(self): self.transposeChange(-1)
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 MayRenderer(QWidget): shouldSave = pyqtSignal() texsize = 512 samplerate = 44100 shaderHeader = '#version 130\nuniform float iTexSize;\nuniform float iBlockOffset;\nuniform float iSampleRate;\n\n' def __init__(self, parent): super().__init__() self.parent = parent self.blocksize = (self.texsize * self.texsize) / self.samplerate self.initState() self.initUI() self.initAudio() def initState(self): self.playing = False self.initVolume = 1 self.useWatchFile = False self.watchFileName = '' self.storeCodeIfNotWatching = '' self.useSynDump = False self.synDrumName = '' self.synFileName = '' def initUI(self): self.mainLayout = QVBoxLayout() self.codeLayout = QVBoxLayout() self.codeButtonBar = QHBoxLayout() self.codeWatchFileBar = QHBoxLayout() self.renderBar = QHBoxLayout() self.playbackBar = QHBoxLayout() self.renderGroup = QGroupBox() self.renderGroupLayout = QVBoxLayout() self.renderGroupLayout.addLayout(self.renderBar) self.renderGroupLayout.addLayout(self.playbackBar) self.renderGroup.setLayout(self.renderGroupLayout) self.renderGroup.setObjectName("renderGroup") self.synGroupLayout = QHBoxLayout() self.synGroup = QGroupBox() self.synGroup.setLayout(self.synGroupLayout) self.synDumpCheckBox = QCheckBox('Dump as') self.synDrumNameBox = QLineEdit(self) self.synFileNameBox = QLineEdit(self) self.synFileButton = QPushButton('...') self.synGroupLayout.addWidget(self.synDumpCheckBox, 1) self.synGroupLayout.addWidget(self.synDrumNameBox, 2) self.synGroupLayout.addWidget(QLabel('in'), .1) self.synGroupLayout.addWidget(self.synFileNameBox, 5) self.synGroupLayout.addWidget(self.synFileButton, 0.1) self.synDumpCheckBox.stateChanged.connect(self.toggleSynDump) self.synDrumNameBox.setPlaceholderText('drumname') self.synDrumNameBox.textChanged.connect(self.setSynDrumName) self.synFileNameBox.setPlaceholderText('some aMaySyn .syn file') self.synFileNameBox.textChanged.connect(self.setSynFileName) self.synFileButton.setMaximumWidth(40) self.synFileButton.clicked.connect(self.chooseSynFile) self.codeGroup = QGroupBox() self.buttonCopy = QPushButton('↬ Clipboard', self) self.buttonCopy.clicked.connect(self.copyToClipboard) self.buttonPaste = QPushButton('Paste ↴', self) self.buttonPaste.clicked.connect(self.pasteClipboard) self.buttonClear = QPushButton('×', self) self.buttonClear.clicked.connect(self.clearEditor) self.codeEditor = QPlainTextEdit(self) self.codeEditor.setLineWrapMode(QPlainTextEdit.WidgetWidth) #self.codeEditor.setCenterOnScroll(True) #self.codeEditor.textChanged.formatEditor()) # this gives a recursion problem, but how to filter e.g. tabs? self.codeEditor.cursorPositionChanged.connect(self.updatePosLabel) self.codeEditor.setTabStopWidth(14) self.watchFileCheckBox = QCheckBox('watch file:', self) self.watchFileCheckBox.stateChanged.connect(self.toggleWatchFile) self.watchFileNameBox = QLineEdit(self) self.watchFileNameBox.setPlaceholderText( 'use GLSL code file instead of the above editor...') self.watchFileNameBox.textChanged.connect(self.setWatchFileName) self.buttonWatchFile = QPushButton('...', self) self.buttonWatchFile.setMaximumWidth(40) self.buttonWatchFile.clicked.connect(self.chooseWatchFile) self.renderButton = QPushButton(self) self.renderButton.clicked.connect(self.pressRenderShader) self.renderLengthBox = QDoubleSpinBox(self) self.renderLengthBox.setMinimum(0) self.renderLengthBox.setValue(4 * self.blocksize - .01) self.renderLengthBox.setSingleStep(self.blocksize) self.renderLengthBox.setSuffix(' sec') self.renderLengthBox.setToolTip('render length') self.renderBpmBox = QSpinBox(self) self.renderBpmBox.setRange(1, 999) self.renderBpmBox.setValue(160) self.renderBpmBox.setPrefix('BPM ') self.renderBpmBox.setToolTip('--> determines SPB') self.playbackVolumeSlider = QSlider(Qt.Horizontal) self.playbackVolumeSlider.setMaximum(100) self.playbackVolumeSlider.setValue(self.initVolume * 100) self.playbackVolumeSlider.setToolTip('volume') self.playbackVolumeSlider.sliderMoved.connect(self.setVolume) self.renderBar.addWidget(self.renderButton, 60) self.renderBar.addWidget(self.renderBpmBox, 20) self.renderBar.addWidget(self.renderLengthBox, 20) self.progressBar = QProgressBar(self) self.progressBar.setEnabled(False) self.pauseButton = QPushButton(self) self.pauseButton.setEnabled(False) self.pauseButton.clicked.connect(self.pressPauseButton) self.playbackBar.addWidget(self.progressBar, 60) self.playbackBar.addWidget(self.playbackVolumeSlider, 20) self.playbackBar.addWidget(self.pauseButton, 20) self.codeButtonBar.addWidget(self.buttonCopy) self.codeButtonBar.addWidget(self.buttonPaste) self.codeButtonBar.addWidget(self.buttonClear) self.codeWatchFileBar.addWidget(self.watchFileCheckBox) self.codeWatchFileBar.addWidget(self.watchFileNameBox) self.codeWatchFileBar.addWidget(self.buttonWatchFile) self.codeHeader = QHBoxLayout() self.codePosLabel = QLabel('(0,0)') self.codeHeader.addWidget(QLabel('GLSL code')) self.codeHeader.addStretch() self.codeHeader.addWidget(self.codePosLabel) self.codeLayout.addLayout(self.codeHeader) self.codeLayout.addLayout(self.codeButtonBar) self.codeLayout.addWidget(self.codeEditor) self.codeLayout.addLayout(self.codeWatchFileBar) self.codeGroup.setLayout(self.codeLayout) self.mainLayout.addWidget(self.synGroup) self.mainLayout.addWidget(self.codeGroup) self.mainLayout.addWidget(self.renderGroup) self.updatePlayingUI() self.setLayout(self.mainLayout) def updatePlayingUI(self, keepActive=False): self.renderButton.setText( 'shut the f**k up' if self.playing else 'send to hell') if not self.playing and not keepActive: self.progressBar.setValue(0) self.progressBar.setEnabled(self.playing if not keepActive else True) self.pauseButton.setEnabled(self.playing if not keepActive else True) self.pauseButton.setText('||' if ( self.playing and self.audiooutput.state() != QAudio.SuspendedState ) else '▶') def initAudio(self): self.audioformat = QAudioFormat() self.audioformat.setSampleRate(self.samplerate) self.audioformat.setChannelCount(2) self.audioformat.setSampleSize(32) self.audioformat.setCodec('audio/pcm') self.audioformat.setByteOrder(QAudioFormat.LittleEndian) self.audioformat.setSampleType(QAudioFormat.Float) # self.audiodeviceinfo = QAudioDeviceInfo(QAudioDeviceInfo.defaultOutputDevice()) self.audiooutput = QAudioOutput(self.audioformat) self.audiooutput.setVolume(self.initVolume) def paste(self, source): self.codeEditor.clear() source = source.replace(4 * ' ', '\t').replace(3 * ' ', '\t') self.codeEditor.insertPlainText(source) #self.codeEditor.setFocus() #TODO: think about whether we want this self.codeEditor.ensureCursorVisible() def pasteClipboard(self): self.paste(self.shaderHeader + QApplication.clipboard().text()) def copyToClipboard(self): text = self.codeEditor.toPlainText().replace('\t', 4 * ' ') QApplication.clipboard().setText(text) def clearEditor(self): self.codeEditor.setPlainText('') self.codeEditor.setFocus() def updatePosLabel(self): cursor = self.codeEditor.textCursor() self.codePosLabel.setText( f'({cursor.blockNumber()},{cursor.positionInBlock()})') # def formatEditor(self): # plainText = self.codeEditor.toPlainText().replace('\t', 4*' ') # self.codeEditor.setPlainText(plainText) def toggleSynDump(self, state): self.useSynDump = (state == Qt.Checked) self.shouldSave.emit() def chooseSynFile(self): dialogResult, _ = QFileDialog.getSaveFileName( self, 'Choose SYN definition file', '', 'aMaySyn definition files (*.syn);;All files (*)') print(dialogResult) self.synFileNameBox.setText(dialogResult) self.synDumpCheckBox.setCheckState(Qt.Checked) if self.synFileName == '': self.synFileNameBox.setFocus() self.shouldSave.emit() def setSynFileName(self): self.synFileName = self.synFileNameBox.text() self.shouldSave.emit() def setSynDrumName(self): self.synDrumName = self.synDrumNameBox.text() self.shouldSave.emit() def setSynDumpParameters(self, useSynDump, synFileName, synDrumName): self.synDumpCheckBox.setChecked(useSynDump) self.synFileNameBox.setText(synFileName) self.synDrumNameBox.setText(synDrumName) def toggleWatchFile(self, state): if not self.useWatchFile and state == Qt.Checked: self.storeCodeIfNotWatching = self.codeEditor.toPlainText() self.useWatchFile = (state == Qt.Checked) self.codeEditor.setEnabled(not self.useWatchFile) if self.useWatchFile: self.showWatchFileInfo() else: self.codeEditor.setPlainText(self.storeCodeIfNotWatching) def chooseWatchFile(self): dialogResult = QFileDialog.getOpenFileName( self, 'Choose file with GLSL code', '', 'GLSL files (*.glsl);;All files (*)') print(dialogResult) self.watchFileNameBox.setText(dialogResult[0]) self.watchFileCheckBox.setCheckState(Qt.Checked) self.shouldSave.emit() def setWatchFileName(self): self.watchFileName = self.watchFileNameBox.text() self.showWatchFileInfo() def showWatchFileInfo(self): if self.useWatchFile: fileInfo = QFileInfo(self.watchFileName) infoText = 'use code from file:\n' + self.watchFileName + '\n' + ( '(exists)' if fileInfo.exists() else '(doesn\'t exist)') self.codeEditor.setPlainText(infoText) def pressRenderShader(self): self.playing = not self.playing if self.playing: self.renderShaderAndPlay() else: self.stopShader() def pressPauseButton(self): state = self.audiooutput.state() if state == QAudio.ActiveState: self.audiooutput.suspend() elif state == QAudio.SuspendedState: self.audiooutput.resume() self.updatePlayingUI(keepActive=True) def stopShader(self): self.audiooutput.stop() self.updatePlayingUI() def renderShaderAndPlay(self, file=None): self.playing = True self.updatePlayingUI() shaderSource = self.shaderHeader + """ uniform float SPB; void main() { float t = (iBlockOffset + gl_FragCoord.x + gl_FragCoord.y*iTexSize) / iSampleRate; t = floor(t*BITS.) / BITS.; vec2 s = .2 * vec2(sin(2.*3.14159*49.*t*(1.+t)*SPB*2.667)); // let's make it fun and squeaky vec2 v = floor((0.5+0.5*s)*65535.0); vec2 vl = mod(v,256.0)/255.0; vec2 vh = floor(v/256.0)/255.0; gl_FragColor = vec4(vl.x,vh.x,vl.y,vh.y); } """ # this is the SUPER FUN BITCRUSHER for the test shader nr_bits = randint(128, 8192) shaderSource = shaderSource.replace('BITS', str(nr_bits)) print(nr_bits, 'bits for the SUPER FUN BITCRUSHER in the test shader.') starttime = datetime.now() try: if self.useWatchFile: watchFile = QFile(self.watchFileName) if not watchFile.open(QFile.ReadOnly | QFile.Text): QMessageBox.warning( self, "Öhm... blöd.", "File öffnen ging nicht. Is genügend Pfeffer drauf?") self.playing = False self.updatePlayingUI() return textStream = QTextStream(watchFile) textStream.setCodec('utf-8') shaderSource = self.shaderHeader + textStream.readAll() else: code = self.codeEditor.toPlainText() if code: shaderSource = code except: raise uniforms = {} SPB = 60 / float(self.renderBpmBox.value()) uniforms.update({'SPB': SPB}) print(self.renderLengthBox.value()) try: duration = self.renderLengthBox.value() except: print('couldn\'t read duration field. take 10secs.') duration = 10 glwidget = SFXGLWidget(self, self.audioformat.sampleRate(), duration, self.texsize, moreUniforms=uniforms) glwidget.show() log = glwidget.newShader(shaderSource) print(log) self.music = glwidget.music floatmusic = glwidget.floatmusic glwidget.hide() glwidget.destroy() if self.music == None: return self.renderLengthBox.setValue(round(glwidget.duration_real, 2) - .01) self.bytearray = QByteArray(self.music) self.audiobuffer = QBuffer(self.bytearray) self.audiobuffer.open(QIODevice.ReadOnly) endtime = datetime.now() el = endtime - starttime print("Compile time: {:.3f}s".format(el.total_seconds())) self.audiooutput.stop() self.audiooutput.start(self.audiobuffer) self.audiooutput.setNotifyInterval(100) self.audiooutput.stateChanged.connect(self.updatePlayingUI) self.progressBar.setMaximum(self.audiobuffer.size()) self.audiooutput.notify.connect(self.proceedAudio) if file is not None: floatmusic_L = [] floatmusic_R = [] for n, sample in enumerate(floatmusic): if n % 2 == 0: floatmusic_L.append(sample) else: floatmusic_R.append(sample) floatmusic_stereo = np.transpose( np.array([floatmusic_L, floatmusic_R], dtype=np.float32)) wavfile.write(file, self.samplerate, floatmusic_stereo) def proceedAudio(self): # print(self.audiobuffer.pos() / self.audioformat.sampleRate()) self.progressBar.setValue(self.audiobuffer.pos()) if self.audiobuffer.atEnd(): self.audiooutput.stop() self.playing = False self.updatePlayingUI() def setVolume(self): self.audiooutput.setVolume(self.playbackVolumeSlider.value() * .01) def dumpInSynFile(self, drumatizeL, drumatizeR, envCode, releaseTime): if not self.synDumpCheckBox.isChecked(): return if self.synDrumName == '': print("specify a valid drum name!!") return if self.synFileName == '': print("specify a valid .syn filename!!") return if not path.exists(self.synFileName): open(self.synFileName, 'a').close() uniqueEnv = f'_{self.synDrumName}ENV' drumatizeL = drumatizeL.replace('__ENV', uniqueEnv) drumatizeR = drumatizeR.replace('__ENV', uniqueEnv) envCode = envCode.replace('__ENV', uniqueEnv).replace('\n', ' ') parLine = f'param include src="{envCode}"\n' synLine = f'maindrum {self.synDrumName} src="{drumatizeL}" srcr="{drumatizeR}" release={releaseTime}\n' print(parLine, '\n', synLine) tmpSynFile = 'tmp.syn' copyfile(self.synFileName, tmpSynFile) parWritten, synWritten = False, False with open(tmpSynFile, 'r') as synFileHandle: synFileLines = synFileHandle.readlines() with open(self.synFileName, 'w') as synFileHandle: for line in synFileLines: parseLine = line.strip('\n').split() if parseLine[0:2] == ['maindrum', self.synDrumName]: synFileHandle.write(synLine) synWritten = True elif parseLine[0:2] == ['param', 'include' ] and line.find(uniqueEnv) != -1: synFileHandle.write(parLine) parWritten = True else: synFileHandle.write(line) if not parWritten: synFileHandle.write('\n' + parLine) if not synWritten: synFileHandle.write('\n' + synLine) synFileHandle.close()
class AudioTest(QMainWindow): PUSH_MODE_LABEL = "Enable push mode" PULL_MODE_LABEL = "Enable pull mode" SUSPEND_LABEL = "Suspend playback" RESUME_LABEL = "Resume playback" DurationSeconds = 1 ToneSampleRateHz = 600 DataSampleRateHz = 44100 def __init__(self): super(AudioTest, self).__init__() self.m_device = QAudioDeviceInfo.defaultOutputDevice() self.m_output = None self.initializeWindow() self.initializeAudio() def initializeWindow(self): layout = QVBoxLayout() self.m_deviceBox = QComboBox(activated=self.deviceChanged) for deviceInfo in QAudioDeviceInfo.availableDevices(QAudio.AudioOutput): self.m_deviceBox.addItem(deviceInfo.deviceName(), deviceInfo) layout.addWidget(self.m_deviceBox) self.m_modeButton = QPushButton(clicked=self.toggleMode) self.m_modeButton.setText(self.PUSH_MODE_LABEL) layout.addWidget(self.m_modeButton) self.m_suspendResumeButton = QPushButton( clicked=self.toggleSuspendResume) self.m_suspendResumeButton.setText(self.SUSPEND_LABEL) layout.addWidget(self.m_suspendResumeButton) volumeBox = QHBoxLayout() volumeLabel = QLabel("Volume:") self.m_volumeSlider = QSlider(Qt.Horizontal, minimum=0, maximum=100, singleStep=10, valueChanged=self.volumeChanged) volumeBox.addWidget(volumeLabel) volumeBox.addWidget(self.m_volumeSlider) layout.addLayout(volumeBox) window = QWidget() window.setLayout(layout) self.setCentralWidget(window) def initializeAudio(self): self.m_pullTimer = QTimer(self, timeout=self.pullTimerExpired) self.m_pullMode = True self.m_format = QAudioFormat() self.m_format.setSampleRate(self.DataSampleRateHz) self.m_format.setChannelCount(1) self.m_format.setSampleSize(16) self.m_format.setCodec('audio/pcm') self.m_format.setByteOrder(QAudioFormat.LittleEndian) self.m_format.setSampleType(QAudioFormat.SignedInt) info = QAudioDeviceInfo(QAudioDeviceInfo.defaultOutputDevice()) if not info.isFormatSupported(self.m_format): qWarning("Default format not supported - trying to use nearest") self.m_format = info.nearestFormat(self.m_format) self.m_generator = Generator(self.m_format, self.DurationSeconds * 1000000, self.ToneSampleRateHz, self) self.createAudioOutput() def createAudioOutput(self): self.m_audioOutput = QAudioOutput(self.m_device, self.m_format) self.m_audioOutput.notify.connect(self.notified) self.m_audioOutput.stateChanged.connect(self.handleStateChanged) self.m_generator.start() self.m_audioOutput.start(self.m_generator) self.m_volumeSlider.setValue(self.m_audioOutput.volume() * 100) def deviceChanged(self, index): self.m_pullTimer.stop() self.m_generator.stop() self.m_audioOutput.stop() self.m_device = self.m_deviceBox.itemData(index) self.createAudioOutput() def volumeChanged(self, value): if self.m_audioOutput is not None: self.m_audioOutput.setVolume(value / 100.0) def notified(self): qWarning("bytesFree = %d, elapsedUSecs = %d, processedUSecs = %d" % ( self.m_audioOutput.bytesFree(), self.m_audioOutput.elapsedUSecs(), self.m_audioOutput.processedUSecs())) def pullTimerExpired(self): if self.m_audioOutput is not None and self.m_audioOutput.state() != QAudio.StoppedState: chunks = self.m_audioOutput.bytesFree() // self.m_audioOutput.periodSize() for _ in range(chunks): data = self.m_generator.read(self.m_audioOutput.periodSize()) if data is None or len(data) != self.m_audioOutput.periodSize(): break self.m_output.write(data) def toggleMode(self): self.m_pullTimer.stop() self.m_audioOutput.stop() if self.m_pullMode: self.m_modeButton.setText(self.PULL_MODE_LABEL) self.m_output = self.m_audioOutput.start() self.m_pullMode = False self.m_pullTimer.start(20) else: self.m_modeButton.setText(self.PUSH_MODE_LABEL) self.m_pullMode = True self.m_audioOutput.start(self.m_generator) self.m_suspendResumeButton.setText(self.SUSPEND_LABEL) def toggleSuspendResume(self): if self.m_audioOutput.state() == QAudio.SuspendedState: qWarning("status: Suspended, resume()") self.m_audioOutput.resume() self.m_suspendResumeButton.setText(self.SUSPEND_LABEL) elif self.m_audioOutput.state() == QAudio.ActiveState: qWarning("status: Active, suspend()") self.m_audioOutput.suspend() self.m_suspendResumeButton.setText(self.RESUME_LABEL) elif self.m_audioOutput.state() == QAudio.StoppedState: qWarning("status: Stopped, resume()") self.m_audioOutput.resume() self.m_suspendResumeButton.setText(self.SUSPEND_LABEL) elif self.m_audioOutput.state() == QAudio.IdleState: qWarning("status: IdleState") stateMap = { QAudio.ActiveState: "ActiveState", QAudio.SuspendedState: "SuspendedState", QAudio.StoppedState: "StoppedState", QAudio.IdleState: "IdleState"} def handleStateChanged(self, state): qWarning("state = " + self.stateMap.get(state, "Unknown"))
class ToneGenerator(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) super().__init__(parent) # start new thread that constantly polls MIDI input self.create_MIDI() # create GUI (windows, slider, etc...) self.create_UI(parent) format = QAudioFormat() self.create_AUDIO(format) self.generator = Flute(format, self) self.generator.start() self.output.start(self.generator) def create_AUDIO(self, format): format.setChannelCount(AUDIO_CHANS) format.setSampleRate(SAMPLE_RATE) format.setSampleSize(SAMPLE_SIZE) format.setCodec("audio/pcm") format.setByteOrder( QAudioFormat.LittleEndian ) format.setSampleType( QAudioFormat.SignedInt ) self.output = QAudioOutput(format, self) output_buffer_size = \ int(2*SAMPLE_RATE \ *CTRL_INTERVAL/1000) self.output.setBufferSize( output_buffer_size ) def create_MIDI(self): # Create the port reader object self.midiListener = MidiPortReader() # Create a thread which will read it self.listenerThread = QThread() # Take the object and move it # to the new thread (it isn't running yet) self.midiListener.moveToThread(self.listenerThread) # Tell Qt the function to call # when it starts the thread self.listenerThread.started.connect(self.midiListener.listener) # connect pyqtSignals to slots in ToneGenerator self.midiListener.newNoteFrequency.connect(self.on_newNoteFrequency) self.midiListener.newNoteVelocity.connect(self.on_newNoteVelocity) self.midiListener.portClosed.connect(self.on_portClosed) # Fingers in ears, eyes tight shut... self.listenerThread.start() # Good grief, IT WORKS! def create_UI(self, parent): # Create a slider to fine tune freq and two buttons #self.octaveBox = QSlider(Qt.Horizontal) #self.octaveBox.setMinimum(-100) #self.octaveBox.setMaximum(100) self.octaveBox = QSpinBox() self.octaveBox.setRange(-5,5) self.octaveLabel = QLabel("Octave: ") self.quitButton = QPushButton(self.tr('&Quit')) # create dropdown menu so user can choose midi device in use (populate list from data from midi listener object) self.MIDIMenu = QComboBox() self.MIDIMenu.addItems(self.midiListener.getMIDIDevices()) # create ADSR sliders self.aSlider = QSlider(Qt.Vertical) self.aSlider.setMinimum(1) self.aSlider.setMaximum(50) self.aSlider.setSliderPosition(1) self.dSlider = QSlider(Qt.Vertical) self.dSlider.setMinimum(1) self.dSlider.setMaximum(50) self.dSlider.setSliderPosition(8) self.sSlider = QSlider(Qt.Vertical) self.sSlider.setMinimum(1) self.sSlider.setMaximum(100) self.sSlider.setSliderPosition(80) self.rSlider = QSlider(Qt.Vertical) self.rSlider.setMinimum(1) self.rSlider.setMaximum(100) self.rSlider.setSliderPosition(50) # No parent: we're going to add this # to vLayout. hLayout1 = QHBoxLayout() hLayout1.addWidget(self.octaveLabel) hLayout1.addWidget(self.octaveBox) hLayout2 = QHBoxLayout() hLayout2.addWidget(self.aSlider) hLayout2.addWidget(self.dSlider) hLayout2.addWidget(self.sSlider) hLayout2.addWidget(self.rSlider) hLayout3 = QHBoxLayout() hLayout3.addWidget(self.MIDIMenu) hLayout3.addWidget(self.quitButton) # parent = self: this is the # "top level" layout vLayout = QVBoxLayout(self) vLayout.addLayout(hLayout1) vLayout.addLayout(hLayout2) vLayout.addLayout(hLayout3) # connect qt object signals to slots self.quitButton.clicked.connect(self.quitClicked) self.octaveBox.valueChanged.connect(self.changeOctave) self.aSlider.valueChanged.connect(self.changeADSRParam) self.dSlider.valueChanged.connect(self.changeADSRParam) self.sSlider.valueChanged.connect(self.changeADSRParam) self.rSlider.valueChanged.connect(self.changeADSRParam) self.MIDIMenu.currentIndexChanged[str].connect(self.changeMIDIDevice) # this isn't a @pyqtSlot(), the text is passed directly to the function def changeMIDIDevice(self, dev): self.midiListener.setMIDIDevice(dev) @pyqtSlot() def quitClicked(self): self.close() @pyqtSlot() def changeOctave(self): #scale slider value to freq and pass to subclass to update filter self.generator.setOctave(self.octaveBox.value()) @pyqtSlot(float) def on_newNoteFrequency(self, freq): # change value of filter cutoff so it sounds like we're playing a different note self.generator.updateFilter(freq) @pyqtSlot(int) def on_newNoteVelocity(self, value): # note_off message doesn't work with shit USB keyboards, so we simulate a note_off message with the velocity if value != 0: self.generator.playNote() else: self.generator.stopNote() @pyqtSlot() def on_portClosed(self): # when port has closed, clear drop down menu and rescan for MIDI devices self.MIDIMenu.clear() self.MIDIMenu.addItems(self.midiListener.getMIDIDevices()) @pyqtSlot() def changeADSRParam(self): self.generator.updateADSR(self.aSlider.value()/10, self.dSlider.value()/10, self.sSlider.value()/100, self.rSlider.value()/10)
class Window(QWidget): volSlide=pyqtSignal(int) def __init__(self,parent=None): #UI QWidget.__init__(self,parent) self.create_UI(parent) #audio formatting format=QAudioFormat() format.setChannelCount(AUDIO_CHANS) format.setSampleRate(SAMPLE_RATE) format.setSampleSize(SAMPLE_SIZE) format.setCodec("audio/pcm") format.setByteOrder( QAudioFormat.LittleEndian ) format.setSampleType( QAudioFormat.SignedInt ) self.output=QAudioOutput(format,self) output_buffer_size=\ int(2*SAMPLE_RATE \ *CTRL_INTERVAL/1000) self.output.setBufferSize( output_buffer_size ) self.generator=Generator(format,self) #THREADS self.midiListener=MidiPortReader() self.listenerThread=QThread() self.midiListener.moveToThread( self.listenerThread ) self.listenerThread.started.connect( self.midiListener.listener ) self.listenerThread.start() self.midiListener.noteOff.connect(self.generator.noteOff) self.midiListener.noteVelocity.connect(self.generator.noteVelocity) self.midiListener.noteOn.connect(self.generator.noteOn) self.volumeSlider.valueChanged.connect(self.generator.volSlide) self.qfacSlider.valueChanged.connect(self.generator.qFactor) self.generator.start() self.output.start(self.generator) def create_UI(self,parent): rockLabel=QLabel() rockLabel.setText("Let's ROCK!!!") volLabel=QLabel() volLabel.setText("Volume") self.volumeSlider=QSlider(Qt.Horizontal) self.volumeSlider.setMinimum(0) self.volumeSlider.setMaximum(100) qfacLabel=QLabel() qfacLabel.setText("Q-Factor") self.qfacSlider=QSlider(Qt.Horizontal) self.qfacSlider.setMinimum(0) self.qfacSlider.setMaximum(1000) self.qfacSlider.setValue(5000) self.quitButton=\ QPushButton(self.tr('&Quit')) self.volSlide.emit(7) self.lynch=QLabel() self.halfDown=QPushButton("+1") self.halfUp=QPushButton("-1") pixmap=QPixmap() pixmap.load('lynch2.jpeg') pixmap=pixmap.scaledToWidth(200) self.lynch.setPixmap(pixmap) vLayout=QVBoxLayout(self) h0Layout=QHBoxLayout() h0Layout.addWidget(rockLabel) h0Layout.addStretch(1) h0Layout.addWidget(self.lynch) vLayout.addLayout(h0Layout) hLayout=QHBoxLayout() hLayout.addWidget(volLabel) hLayout.addStretch(1) hLayout.addWidget(self.volumeSlider) vLayout.addLayout(hLayout) h2Layout=QHBoxLayout() h2Layout.addWidget(qfacLabel) h2Layout.addStretch(1) h2Layout.addWidget(self.qfacSlider) vLayout.addLayout(h2Layout) self.quitButton.clicked.connect( self.quitClicked ) @pyqtSlot() def quitClicked(self): self.close()
class AudioWidget(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) self.format = None self.output = None self.buffer = QBuffer() self.volumeSlider = QSlider(Qt.Horizontal) self.volumeSlider.setMaximum(10) self.volumeSlider.setPageStep(1) self.volumeSlider.setValue(5) self.playButton = QPushButton() self.playButton.setIcon(QIcon("icons/play.png")) self.stopButton = QPushButton() self.stopButton.setIcon(QIcon("icons/stop.png")) self.volumeSlider.valueChanged.connect(self.change_volume) self.playButton.clicked.connect(self.play_pause) self.stopButton.clicked.connect(self.stop) layout = QHBoxLayout(self) layout.addWidget(self.playButton) layout.addWidget(self.stopButton) layout.addWidget(self.volumeSlider) layout.addStretch() def stop(self): if self.output: if self.output.state() != QAudio.StoppedState: self.output.stop() def set_data(self, mono_sig, sr): # if not self.format: self.format = QAudioFormat() self.format.setChannelCount(1) self.format.setSampleRate(sr) #numpy is in bites, qt in bits self.format.setSampleSize(mono_sig.dtype.itemsize * 8) self.format.setCodec("audio/pcm") self.format.setByteOrder(QAudioFormat.LittleEndian) self.format.setSampleType(QAudioFormat.Float) self.output = QAudioOutput(self.format, self) self.output.stateChanged.connect(self.audio_state_changed) #change the content without stopping playback p = self.buffer.pos() if self.buffer.isOpen(): self.buffer.close() self.data = mono_sig.tobytes() self.buffer.setData(self.data) self.buffer.open(QIODevice.ReadWrite) self.buffer.seek(p) def audio_state_changed(self, new_state): #adjust the button icon if new_state != QAudio.ActiveState: self.playButton.setIcon(QIcon("icons/play.png")) else: self.playButton.setIcon(QIcon("icons/pause.png")) def cursor(self, t): #seek towards the time t #todo: handle EOF case try: if self.format: t = max(0, t) b = self.format.bytesForDuration(t * 1000000) self.buffer.seek(b) except: print("cursor error") def play_pause(self): if self.output: #(un)pause the audio output, keeps the buffer intact if self.output.state() == QAudio.ActiveState: self.output.suspend() elif self.output.state() == QAudio.SuspendedState: self.output.resume() else: self.buffer.seek(0) self.output.start(self.buffer) def change_volume(self, value): if self.output: #need to wrap this because slider gives not float output self.output.setVolume(value / 10)
class SleaZynth(QMainWindow): autoSaveFile = 'auto.save' texsize = 512 samplerate = 44100 def __init__(self): QMainWindow.__init__(self) self.ui = Ui_MainWindow() self.ui.setupUi(self) self.show() self.initModelView() self.initSignals() self.initState() self.autoLoad() self.initAMaySyn() self.initAudio() def keyPressEvent(self, event): if event.key() == Qt.Key_Escape: self.close() elif event.key() == Qt.Key_F1: self.debugOutput() if event.modifiers() & Qt.ControlModifier: if event.key() == Qt.Key_S: self.autoSave() elif event.key() == Qt.Key_L: self.autoLoad() elif event.key() == Qt.Key_T: self.renderWhateverWasLast() def closeEvent(self, event): QApplication.quit() def initSignals(self): self.ui.btnChooseFilename.clicked.connect(self.loadAndImportMayson) self.ui.btnImport.clicked.connect(self.importMayson) self.ui.btnExport.clicked.connect(self.exportChangedMayson) self.ui.checkAutoReimport.clicked.connect(self.toggleAutoReimport) self.ui.checkAutoRender.clicked.connect(self.toggleAutoRender) self.ui.editFilename.editingFinished.connect( partial(self.updateStateFromUI, only='maysonFile')) self.ui.editBPM.editingFinished.connect( partial(self.updateStateFromUI, only='BPM')) self.ui.spinBOffset.valueChanged.connect( partial(self.updateStateFromUI, only='B_offset')) self.ui.spinBStop.valueChanged.connect( partial(self.updateStateFromUI, only='B_stop')) self.ui.spinLevelSyn.valueChanged.connect( partial(self.updateStateFromUI, only='level_syn')) self.ui.spinLevelDrum.valueChanged.connect( partial(self.updateStateFromUI, only='level_drum')) self.ui.checkWriteWAV.clicked.connect( partial(self.updateStateFromUI, only='writeWAV')) self.ui.spinTimeShift.valueChanged.connect( partial(self.updateStateFromUI, only='extraTimeShift')) self.ui.editTrackName.textChanged.connect(self.trackSetName) self.ui.spinTrackVolume.valueChanged.connect(self.trackSetVolume) self.ui.checkTrackMute.stateChanged.connect(self.trackSetMute) self.ui.spinModOn.valueChanged.connect(self.moduleSetModOn) self.ui.spinModTranspose.valueChanged.connect(self.moduleSetTranspose) self.ui.btnApplyPattern.clicked.connect(self.moduleSetPattern) self.ui.btnApplyNote.clicked.connect(self.noteApplyChanges) self.ui.btnTrackClone.clicked.connect(self.trackClone) self.ui.btnTrackDelete.clicked.connect(self.trackDelete) self.ui.btnRandomSynth.clicked.connect(self.trackSetRandomSynth) self.ui.btnRandomizeSynth.clicked.connect(self.synthRandomize) self.ui.btnSaveSynth.clicked.connect(self.synthHardClone) self.ui.btnApplySynthName.clicked.connect(self.synthChangeName) self.ui.btnReloadSyn.clicked.connect(self.loadSynthsFromSynFile) self.ui.btnRenderModule.clicked.connect(self.renderModule) self.ui.btnRenderTrack.clicked.connect(self.renderTrack) self.ui.btnRenderSong.clicked.connect(self.renderSong) self.ui.btnStopPlayback.clicked.connect(self.stopPlayback) # model/view signals self.ui.trackList.selectionModel().currentChanged.connect( self.trackLoad) self.ui.patternCombox.currentIndexChanged.connect(self.patternLoad) self.ui.moduleList.selectionModel().currentChanged.connect( self.moduleLoad) self.ui.synthList.selectionModel().currentChanged.connect( self.trackSetSynth) self.ui.noteList.selectionModel().currentChanged.connect(self.noteLoad) self.ui.drumList.selectionModel().currentChanged.connect( self.noteSetDrum) def initModelView(self): self.trackModel = TrackModel() self.ui.trackList.setModel(self.trackModel) self.moduleModel = ModuleModel() self.ui.moduleList.setModel(self.moduleModel) self.patternModel = PatternModel() self.ui.patternCombox.setModel(self.patternModel) self.noteModel = NoteModel() self.ui.noteList.setModel(self.noteModel) self.synthModel = QStringListModel() self.ui.synthList.setModel(self.synthModel) self.drumModel = QStringListModel() self.ui.drumList.setModel(self.drumModel) self.noteModel.dataChanged.connect( self.updateModulesWithChangedPattern) self.noteModel.reloadNoteParameters.connect(self.noteLoad) def initState(self): self.state = { 'maysonFile': '', 'autoReimport': False, 'autoRender': False, 'lastRendered': '', 'writeWAV': False, 'selectedTrack': 0, 'selectedModule': 0, 'extraTimeShift': 0, } self.info = {} self.patterns = [] self.synths = [] self.drumkit = [] self.amaysyn = None self.fileObserver = None def loadAndImportMayson(self): name, _ = QFileDialog.getOpenFileName( self, 'Load MAYSON file', '', 'aMaySyn export *.mayson(*.mayson)') if name == '': return self.state['maysonFile'] = name self.state['title'], self.state[ 'synFile'] = self.getTitleAndSynFromMayson(name) self.autoSave() self.importMayson() def importMayson(self): maysonData = {} try: file = open(self.state['maysonFile'], 'r') maysonData = json.load(file) except FileNotFoundError: print( f"{self.state['maysonFile']} could not be imported. make sure that it exists, or choose another one." ) self.loadAndImportMayson() except json.decoder.JSONDecodeError: print( f"{self.state['maysonFile']} is changing right now, pause for 1 sec..." ) sleep(1) self.importMayson() finally: file.close() if maysonData == {}: return self.info = maysonData['info'] self.info.update({'title': self.state['title']}) if self.amaysyn is not None: self.amaysyn.updateState(info=self.info) self.trackModel.setTracks(maysonData['tracks']) self.patternModel.setPatterns(maysonData['patterns']) self.synthModel.setStringList(maysonData['synths']) self.drumModel.setStringList(maysonData['drumkit']) self.trackModel.layoutChanged.emit() if self.state['selectedTrack'] >= self.trackModel.rowCount(): self.state['selectedTrack'] = 0 self.selectIndex(self.ui.trackList, self.trackModel, self.state['selectedTrack']) if self.state['selectedModule'] >= self.moduleModel.rowCount(): self.state['selectedModule'] = 0 self.selectIndex(self.ui.moduleList, self.moduleModel, self.state['selectedModule']) self.noteModel.layoutChanged.emit() if self.noteModel.rowCount() > 0: self.selectIndex(self.ui.noteList, self.noteModel, 0) self.synthModel.layoutChanged.emit() self.drumModel.layoutChanged.emit() if self.drumIndex().isValid(): self.selectIndex(self.ui.drumList, self.drumModel, self.drumIndex().row()) self.applyStateToUI() def exportChangedMayson(self): name, _ = QFileDialog.getSaveFileName( self, 'Export with Changes', self.state['maysonFile'], 'aMaySyn export *.mayson(*.mayson)') if name == '': return data = { 'info': self.info, 'tracks': self.trackModel.tracks, 'patterns': self.patternModel.patterns, 'synths': self.synthModel.stringList(), 'drumkit': self.drumModel.stringList(), } file = open(name, 'w') json.dump(data, file) file.close() def updateStateFromUI(self, only=None): if only is None or only == 'maysonFile': self.state.update({'maysonFile': self.ui.editFilename.text()}) title, synFile = self.getTitleAndSynFromMayson( self.state['maysonFile']) self.state.update({'synFile': synFile}) self.state.update({'title': title}) self.info['title'] = title if only is None or only == 'BPM': self.info['BPM'] = self.ui.editBPM.text() if only is None or only == 'B_offset': self.info['B_offset'] = self.ui.spinBOffset.value() if only is None or only == 'B_stop': self.info['B_stop'] = self.ui.spinBStop.value() if only is None or only == 'level_syn': self.info['level_syn'] == self.ui.spinLevelSyn.value() if only is None or only == 'level_drum': self.info['level_drum'] == self.ui.spinLevelDrum.value() if only is None or only == 'writeWAV': self.state['writeWAV'] = self.ui.checkWriteWAV.isChecked() if only is None or only == 'extraTimeShift': self.state['extraTimeShift'] = self.ui.spinTimeShift.value() if self.amaysyn is not None: self.amaysyn.updateState(info=self.info, synFile=synFile) def applyStateToUI(self): self.ui.editFilename.setText(self.state['maysonFile']) # TODO: think about - do I want self.state['override']['BPM'] etc.?? self.ui.editBPM.setText(self.info['BPM']) self.ui.spinBOffset.setValue(self.info['B_offset']) self.ui.spinBStop.setValue(self.info['B_stop']) self.ui.spinLevelSyn.setValue(self.info['level_syn']) self.ui.spinLevelDrum.setValue(self.info['level_drum']) self.ui.checkAutoReimport.setChecked(self.state['autoReimport']) self.ui.checkAutoRender.setChecked(self.state['autoRender']) self.ui.checkWriteWAV.setChecked(self.state['writeWAV']) self.ui.spinTimeShift.setValue(self.state['extraTimeShift']) def autoSave(self): file = open(self.autoSaveFile, 'w') json.dump(self.state, file) file.close() def autoLoad(self): loadState = {} try: file = open(self.autoSaveFile, 'r') loadState = json.load(file) file.close() except FileNotFoundError: pass for key in loadState: self.state[key] = loadState[key] if 'autoReimport' in self.state: self.toggleAutoReimport(self.state['autoReimport']) if 'maysonFile' not in self.state or self.state['maysonFile'] == '': self.loadAndImportMayson() else: self.importMayson() def toggleAutoRender(self, checked): self.state['autoRender'] = checked self.autoSave() def toggleAutoReimport(self, checked): self.state['autoReimport'] = checked self.autoSave() if self.fileObserver is not None: self.fileObserver.stop() self.fileObserver.join() self.fileObserver = None if checked: file = self.state['maysonFile'] eventHandler = FileModifiedHandler(file) eventHandler.fileChanged.connect( self.importAndRender if self.state['autoRender'] else self. importMayson) self.fileObserver = Observer() self.fileObserver.schedule(eventHandler, path=path.dirname(file), recursive=False) self.fileObserver.start() def importAndRender(self): self.importMayson() if self.amaysyn is None: print( "You want to Reimport&Render, but why is aMaySyn not initialized? do some rendering first!" ) return self.renderWhateverWasLast() #################################### GENERAL HELPERS ########################################### def selectIndex(self, list, model, index): list.selectionModel().setCurrentIndex( model.createIndex(index, 0), QItemSelectionModel.SelectCurrent) def patternIndexOfName(self, name): patternNames = [p['name'] for p in self.patternModel.patterns] if name in patternNames: return patternNames.index(name) else: return None def getTitleAndSynFromMayson(self, maysonFile): synFile = '.'.join(maysonFile.split('.')[:-1]) + '.syn' title = '.'.join(path.basename(maysonFile).split('.')[:-1]) return title, synFile def placeholder(self): print("FUNCTION NOT IMPLEMENTED. Sorrriiiiiiieee! (not sorry.)") #################################### TRACK FUNCTIONALITY ####################################### def track(self): return self.trackModel.tracks[self.trackIndex().row( )] if self.trackModel.rowCount() > 0 else None def trackIndex(self): return self.ui.trackList.currentIndex() def trackModelChanged(self): self.trackModel.dataChanged.emit(self.trackIndex(), self.trackIndex()) def trackLoad(self, currentIndex): cTrack = self.trackModel.tracks[currentIndex.row()] self.ui.editTrackName.setText(cTrack['name']) self.ui.spinTrackVolume.setValue(100 * cTrack['par_norm']) self.ui.checkTrackMute.setChecked(not cTrack['mute']) self.moduleModel.setModules(cTrack['modules']) if len(cTrack['modules']) > 0: self.selectIndex(self.ui.moduleList, self.moduleModel, cTrack['current_module']) self.moduleLoad() self.selectIndex(self.ui.synthList, self.synthModel, cTrack['current_synth']) self.state['selectedTrack'] = currentIndex.row() def trackClone(self): self.trackModel.cloneRow(self.trackIndex().row()) def trackDelete(self): self.trackModel.removeRow(self.trackIndex().row()) def trackSetName(self, name): self.track()['name'] = name self.trackModelChanged() def trackSetVolume(self, value): self.track()['par_norm'] = round(value * .01, 3) self.trackModelChanged() def trackSetMute(self, state): self.track()['mute'] = (state != Qt.Checked) self.trackModelChanged() def trackSetSynth(self, index): self.track()['current_synth'] = self.synthModel.stringList().index( self.synthModel.data(index, Qt.DisplayRole)) self.ui.editSynthName.setText(self.synthName()) self.trackModelChanged() if self.synth()[0] == 'D': self.noteModel.useDrumkit(self.drumModel.stringList()) else: self.noteModel.useDrumkit(None) def trackSetRandomSynth(self): randomIndex = self.synthModel.createIndex( randint(0, len(self.instrumentSynths()) - 1), 0) self.ui.synthList.setCurrentIndex(randomIndex) #################################### MODULE FUNCTIONALITY ###################################### def module(self): return self.moduleModel.modules[self.moduleIndex().row( )] if self.moduleModel.rowCount() > 0 else None def moduleIndex(self): return self.ui.moduleList.currentIndex() def moduleModelChanged(self): self.moduleModel.dataChanged.emit(self.moduleIndex(), self.moduleIndex()) def moduleLoad(self, currentIndex=None): if currentIndex is None: cModule = self.module() else: cModule = self.moduleModel.modules[currentIndex.row()] self.state['selectedModule'] = currentIndex.row() self.ui.patternCombox.setCurrentIndex( self.patternIndexOfName(cModule['pattern']['name'])) self.ui.spinModOn.setValue(cModule['mod_on']) self.ui.spinModTranspose.setValue(cModule['transpose']) def moduleAssignPattern(self, pattern): self.module()['pattern'] = pattern # deepcopy(pattern) def moduleSetPattern(self): self.moduleAssignPattern(self.pattern()) self.moduleModelChanged() def moduleSetModOn(self, value): self.module()['mod_on'] = self.ui.spinModOn.value() self.moduleModelChanged() def moduleSetTranspose(self, value): self.module()['transpose'] = self.ui.spinModTranspose.value() self.moduleModelChanged() #################################### PATTERN FUNCTIONALITY ##################################### def pattern(self): return self.patternModel.patterns[ self.patternIndex()] if self.patternModel.rowCount() > 0 else None def patternIndex(self): return self.ui.patternCombox.currentIndex() def patternLoad(self, currentIndex): cPattern = self.patternModel.patterns[currentIndex] self.noteModel.setNotes(cPattern['notes']) def updateModulesWithChangedPattern(self, rowBegin, rowEnd): self.moduleAssignPattern(self.pattern()) self.moduleModelChanged() self.trackModel.updateModulesWithChangedPattern(self.pattern()) self.trackModelChanged() #################################### NOTE FUNCTIONALITY ######################################## def note(self): return self.noteModel.notes[ self.noteIndex().row()] if self.noteModel.rowCount() > 0 else None def noteIndex(self): return self.ui.noteList.currentIndex() def noteModelChanged(self): self.noteModel.dataChanged.emit(self.noteIndex(), self.noteIndex()) def noteLoad(self, currentIndex): self.ui.editNote.setText( self.noteModel.data(currentIndex, Qt.DisplayRole)) self.ui.editNote.setCursorPosition(0) def noteApplyChanges(self): self.noteModel.changeByString(self.noteIndex(), self.ui.editNote.text()) def noteSetDrum(self, currentIndex): if self.drum() is not None: self.noteModel.changeDrumTo(self.noteIndex(), self.drum()) ################################ SYNTH / DRUM FUNCTIONALITY #################################### def synth(self): return self.synthModel.data(self.ui.synthList.currentIndex(), Qt.DisplayRole) def synthName(self): return self.synth()[2:] def instrumentSynths(self): return [ I_synth for I_synth in self.synthModel.stringList() if I_synth[0] == 'I' ] def drum(self): if not self.drumIndex(): print("LOLOLOL DRUM INDEX IS NONE (should never happen)") if not self.drumIndex().isValid(): print("LOLOLOL DRUM INDEX NOT VALID") return self.drumModel.data(self.drumIndex(), Qt.DisplayRole) def drumIndex(self): return self.ui.drumList.currentIndex() def synthRandomize(self): self.amaysyn.aMaySynatize(reshuffle_randoms=True) def synthHardClone(self): if self.synth()[0] == 'D': self.synthHardCloneDrum(self) return else: count = 0 oldID = self.synthName() synths = self.instrumentSynths() while True: formID = oldID + '.' + str(count) print("TRYING", formID, synths) if 'I_' + formID not in synths: break count += 1 try: formTemplate = next( form for form in self.amaysyn.last_synatized_forms if form['id'] == oldID) formType = formTemplate['type'] formMode = formTemplate['mode'] formBody = ' '.join(key + '=' + formTemplate[key] for key in formTemplate if key not in ['type', 'id', 'mode']) if formMode: formBody += ' mode=' + ','.join(formMode) except StopIteration: print( "Current synth is not compiled yet. Do so and try again.") return except: print("could not CLONE HARD:", formID, formTemplate) raise else: with open(self.state['synFile'], mode='a') as filehandle: filehandle.write('\n' + formType + 4 * ' ' + formID + 4 * ' ' + formBody) self.loadSynthsFromSynFile() def synthHardDrum(self): print("NOT IMPLEMENTED YET") return count = 0 oldID = self.synthName() synths = self.instrumentSynths() while True: formID = oldID + '.' + str(count) print("TRYING", formID, synths) if 'I_' + formID not in synths: break count += 1 try: formTemplate = next(form for form in self.amaysyn.last_synatized_forms if form['id'] == oldID) formType = formTemplate['type'] formMode = formTemplate['mode'] formBody = ' '.join(key + '=' + formTemplate[key] for key in formTemplate if key not in ['type', 'id', 'mode']) if formMode: formBody += ' mode=' + ','.join(formMode) except StopIteration: print("Current synth is not compiled yet. Do so and try again.") return except: print("could not CLONE HARD:", formID, formTemplate) raise else: with open(self.state['synFile'], mode='a') as filehandle: filehandle.write('\n' + formType + 4 * ' ' + formID + 4 * ' ' + formBody) self.loadSynthsFromSynFile() def synthChangeName(self): if self.synth()[0] != 'I': print("Nah. Select an instrument synth (I_blabloo)") return newID = self.ui.editSynthName.text() if newID == '': return formID = self.synthName() tmpFile = self.state['synFile'] + '.tmp' move(self.state['synFile'], tmpFile) with open(tmpFile, mode='r') as tmp_handle: with open(self.state['synFile'], mode='w') as new_handle: for line in tmp_handle.readlines(): lineparse = line.split() if len(lineparse) > 2 and lineparse[0] in [ 'main', 'maindrum' ] and lineparse[1] == formID: new_handle.write( line.replace(' ' + formID + ' ', ' ' + newID + ' ')) else: new_handle.write(line) self.loadSynthsFromSynFile() def loadSynthsFromSynFile(self): self.amaysyn.aMaySynatize() self.synthModel.setStringList(self.amaysyn.synths) self.synthModel.dataChanged.emit( self.synthModel.createIndex(0, 0), self.synthModel.createIndex(self.synthModel.rowCount(), 0)) self.drumModel.setStringList(self.amaysyn.drumkit) self.drumModel.dataChanged.emit( self.drumModel.createIndex(0, 0), self.drumModel.createIndex(self.drumModel.rowCount(), 0)) self.trackModel.setSynthList(self.amaysyn.synths) self.trackModelChanged() # TODO: function to change drumkit order / assignment? ######################################## SleaZYNTHesizer ####################################### def initAMaySyn(self): self.amaysyn = aMaySynBuilder(self, self.state['synFile'], self.info) def initAudio(self): self.audioformat = QAudioFormat() self.audioformat.setSampleRate(self.samplerate) self.audioformat.setChannelCount(2) self.audioformat.setSampleSize(32) self.audioformat.setCodec('audio/pcm') self.audioformat.setByteOrder(QAudioFormat.LittleEndian) self.audioformat.setSampleType(QAudioFormat.Float) self.audiooutput = QAudioOutput(self.audioformat) self.audiooutput.setVolume(1.0) def stopPlayback(self): self.audiooutput.stop() def renderWhateverWasLast(self): if self.state['lastRendered'] == 'module': self.renderModule() elif self.state['lastRendered'] == 'track': self.renderTrack() else: self.renderSong() def renderModule(self): print(self.track(), self.module()) self.state['lastRendered'] = 'module' restoreMute = self.track()['mute'] self.track()['mute'] = False modInfo = deepcopy(self.info) modInfo['B_offset'] = self.module()['mod_on'] modInfo['B_stop'] = self.module()['mod_on'] + self.module( )['pattern']['length'] self.amaysyn.info = modInfo self.amaysyn.extra_time_shift = self.state['extraTimeShift'] shader = self.amaysyn.build(tracks=[self.track()], patterns=[self.module()['pattern']]) self.amaysyn.info = self.info self.track()['mute'] = restoreMute self.executeShader(shader) def renderTrack(self): self.state['lastRendered'] = 'track' restoreMute = self.track()['mute'] self.track()['mute'] = False self.amaysyn.extra_time_shift = self.state['extraTimeShift'] shader = self.amaysyn.build(tracks=[self.track()], patterns=self.patternModel.patterns) self.track()['mute'] = restoreMute self.executeShader(shader) def renderSong(self): self.state['lastRendered'] = 'song' self.amaysyn.extra_time_shift = self.state['extraTimeShift'] shader = self.amaysyn.build(tracks=self.trackModel.tracks, patterns=self.patternModel.patterns) self.executeShader(shader) def executeShader(self, shader): self.ui.codeEditor.clear() self.ui.codeEditor.insertPlainText( shader.replace(4 * ' ', '\t').replace(3 * ' ', '\t')) self.ui.codeEditor.ensureCursorVisible() sequenceLength = len( self.amaysyn.sequence) if self.amaysyn.sequence is not None else 0 if not self.amaysyn.useSequenceTexture and sequenceLength > pow(2, 14): QMessageBox.critical( self, "I CAN'T", f"Either switch to using the Sequence Texture (ask QM), or reduce the sequence size by limiting the offset/stop positions or muting tracks.\nCurrent sequence length is:\n{sequenceLength} > {pow(2,14)}" ) return self.bytearray = self.amaysyn.executeShader( shader, self.samplerate, self.texsize, renderWAV=self.state['writeWAV']) self.audiobuffer = QBuffer(self.bytearray) self.audiobuffer.open(QIODevice.ReadOnly) self.audiooutput.stop() self.audiooutput.start(self.audiobuffer) ###################################### DEBUG STUFF ############################################# def debugOutput(self): print("TRACKS:", self.trackModel.rowCount()) print("===== TRACK ACCUMULATION =====") track_accumulate = 0 for t in self.trackModel.tracks: delta = len(t['modules']) print( f"{t['name']:>20} {track_accumulate:>10} {track_accumulate + delta:>10}" ) track_accumulate += delta print("END AT", track_accumulate) print() print("PATTERNS:", self.patternModel.rowCount()) print("===== PATTERN ACCUMULATION =====") pattern_accumulate = 0 for p in self.patternModel.patterns: delta = len(p['notes']) print( f"{p['name']:>20} {pattern_accumulate:>10} {pattern_accumulate + delta:>10}" ) pattern_accumulate += delta print("END AT", pattern_accumulate) print()
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 Window(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) format = QAudioFormat() format.setChannelCount(1) format.setSampleRate(22050) format.setSampleSize(16) format.setCodec("audio/pcm") format.setByteOrder(QAudioFormat.LittleEndian) format.setSampleType(QAudioFormat.SignedInt) self.output = QAudioOutput(format, self) self.frequency = 440 self.volume = 0 self.buffer = QBuffer() self.data = QByteArray() self.deviceLineEdit = QLineEdit() self.deviceLineEdit.setReadOnly(True) self.deviceLineEdit.setText( QAudioDeviceInfo.defaultOutputDevice().deviceName()) self.pitchSlider = QSlider(Qt.Horizontal) self.pitchSlider.setMaximum(100) self.volumeSlider = QSlider(Qt.Horizontal) self.volumeSlider.setMaximum(32767) self.volumeSlider.setPageStep(1024) self.playButton = QPushButton(self.tr("&Play")) self.pitchSlider.valueChanged.connect(self.changeFrequency) self.volumeSlider.valueChanged.connect(self.changeVolume) self.playButton.clicked.connect(self.play) formLayout = QFormLayout() formLayout.addRow(self.tr("Device:"), self.deviceLineEdit) formLayout.addRow(self.tr("P&itch:"), self.pitchSlider) formLayout.addRow(self.tr("&Volume:"), self.volumeSlider) buttonLayout = QVBoxLayout() buttonLayout.addWidget(self.playButton) buttonLayout.addStretch() horizontalLayout = QHBoxLayout(self) horizontalLayout.addLayout(formLayout) horizontalLayout.addLayout(buttonLayout) self.play() self.createData() def changeFrequency(self, value): self.frequency = 440 + (value * 2) self.createData() def play(self): if self.output.state() == QAudio.ActiveState: self.output.stop() if self.buffer.isOpen(): self.buffer.close() if self.output.error() == QAudio.UnderrunError: self.output.reset() self.buffer.setData(self.data) self.buffer.open(QIODevice.ReadOnly) self.buffer.seek(0) self.output.start(self.buffer) def changeVolume(self, value): self.volume = value self.createData() def createData(self): self.data.clear() for i in range(2 * 22050): t = i / 22050.0 value = int(self.volume * sin(2 * pi * self.frequency * t)) self.data.append(struct.pack("<h", value))