dB単位の音量フェーダー pic.twitter.com/mJK2E6RmnJ
— Kurene (@_kurene) 2020年12月22日
PyQtで作るdB単位の音量フェーダーを作ってみました。
信号に乗算するゲイン係数は、音量フェーダーのdB値 x から、 として算出しています。
ソースコード
GUI
""" date: 2020/12/23 author: @_kurene """ import os import sys import threading from PyQt5.QtWidgets import QWidget, QPushButton, QGridLayout, QFileDialog from PyQt5.QtWidgets import QLabel, QSlider, QHBoxLayout from PyQt5.QtGui import QKeySequence from PyQt5.QtWidgets import QApplication from PyQt5.QtCore import Qt from audioplayer import AudioPlayer class PyAudioPylerGUI(QWidget): def __init__(self): super().__init__() self.ap = AudioPlayer() self.Init_UI() self.show() def Init_UI(self): self.setGeometry(100, 100, 400, 400) grid = QGridLayout() self.setWindowTitle('PyAudioPlayer') button_play = QPushButton("Play") button_pause = QPushButton("Pause") button_stop = QPushButton("Stop") button_exit = QPushButton("Exit") button_dialog = QPushButton("Open file") self.label = QLabel(self) grid.addWidget(button_dialog, 0, 0, 1, 3) grid.addWidget(button_play, 1, 0) grid.addWidget(button_pause, 1, 1) grid.addWidget(button_stop, 1, 2) grid.addWidget(button_exit, 2, 0, 1, 3) grid.addWidget(self.label, 3, 0, 1, 3) button_dialog.clicked.connect(self.button_openfile) button_play.clicked.connect(self.ap.play) button_pause.clicked.connect(self.ap.pause) button_stop.clicked.connect(self.ap.stop) button_exit.clicked.connect(self.button_exit) hbox = QHBoxLayout() self.slider_label = QLabel(self) self.slider_label.setAlignment(Qt.AlignCenter | Qt.AlignVCenter) self.slider_label.setMinimumWidth(160) slider = QSlider(Qt.Vertical) slider.setFocusPolicy(Qt.NoFocus) slider.setMinimum(-200) slider.setMaximum(60) slider.setTickInterval(1) slider.setValue(0) slider.valueChanged.connect(self.change_value) self.change_value(0.0) hbox.addStretch() hbox.addWidget(slider) hbox.addSpacing(15) hbox.addWidget(self.slider_label) hbox.addStretch() grid.addLayout(hbox, 4, 0, 1, 3) self.setLayout(grid) def change_value(self, value): value_mod = value / 10 self.slider_label.setText(f"{value_mod:.1f} dB") # dB => gain coef. self.ap.gain = 10 ** (value_mod/20) def button_exit(self): self.ap.terminate() QApplication.quit() def button_openfile(self): filepath, _ = QFileDialog.getOpenFileName(self, 'Open file','c:\\',"Audio files (*.wav *.mp3 *.flac)") filename = os.path.basename(filepath) if os.path.exists(filepath): self.label.setText(filename) self.label.adjustSize() self.ap.set_audiofile(filepath) if __name__ == '__main__': app = QApplication(sys.argv) w = PyAudioPylerGUI() app.exit(app.exec_())
楽曲再生
""" date: 2020/12/23 author: @_kurene """ import sys import pyaudio import threading import numpy as np import librosa import warnings from numba import jit warnings.simplefilter('ignore') @jit def process_block(input, output, n_ch, n_chunk, gain): for c in range(0, n_ch): for n in range(0, n_chunk): output[c, n] = gain * input[c, n] class AudioPlayer(): def __init__(self, sr=44100, n_chunk=1024, format="float32", loop_on=True): self.sr = sr self.n_ch = 2 self.n_chunk = n_chunk self.loop_on = loop_on self.length = 0 self.offset = 0 self.format = pyaudio.paInt16 if format == "int16" else pyaudio.paFloat32 self.dtype = np.float32 self.gain = 1.0 self.signal = None self.p = pyaudio.PyAudio() self.stream = self.p.open(format=self.format, channels=self.n_ch, rate=self.sr, frames_per_buffer=self.n_chunk, input=False, output=True) self.stream.stop_stream() def set_audiofile(self, filepath): self.stop() signal, self.sr = librosa.load(filepath, sr=self.sr, mono=False) length = signal.shape[0] if signal.ndim == 1 else signal.shape[1] self.length = (length // self.n_chunk)*self.n_chunk + (self.n_chunk if length % self.n_chunk > 0 else 0) self.signal = np.zeros((self.n_ch, self.length)) for k in range(0, self.n_ch): self.signal[k, 0:length] = signal / self.n_ch if signal.ndim == 1 else signal[k] def __run(self): output = np.zeros((self.n_ch, self.n_chunk)) while self.stream.is_active(): input = self.signal[:, self.offset : self.offset + self.n_chunk] process_block(input, output, self.n_ch, self.n_chunk, self.gain) # Convert nd-array into stream chunk chunk_data = np.reshape(output.T, (self.n_chunk * self.n_ch)) chunk = chunk_data.astype(self.dtype).tostring() self.stream.write(chunk) # Update offset self.offset += self.n_chunk if self.offset >= self.length: if self.loop_on: self.offset = 0 else: self.stop() return True #========================================================== # Control funcs #========================================================== def pause(self): self.stream.stop_stream() def stop(self): self.stream.stop_stream() self.offset = 0 def play(self): self.stream.start_stream() t = threading.Thread(target=self.__run) t.start() def terminate(self): self.stream.stop_stream() self.stream.close() self.p.terminate()