-
-
Save boylea/1a0b5442171f9afbf372 to your computer and use it in GitHub Desktop.
""" | |
Tested on Linux with python 3.7 | |
Must have portaudio installed (e.g. dnf install portaudio-devel) | |
pip install pyqtgraph pyaudio PyQt5 | |
""" | |
import numpy as np | |
import pyqtgraph as pg | |
import pyaudio | |
from PyQt5 import QtCore, QtGui | |
FS = 44100 #Hz | |
CHUNKSZ = 1024 #samples | |
class MicrophoneRecorder(): | |
def __init__(self, signal): | |
self.signal = signal | |
self.p = pyaudio.PyAudio() | |
self.stream = self.p.open(format=pyaudio.paInt16, | |
channels=1, | |
rate=FS, | |
input=True, | |
frames_per_buffer=CHUNKSZ) | |
def read(self): | |
data = self.stream.read(CHUNKSZ, exception_on_overflow=False) | |
y = np.fromstring(data, 'int16') | |
self.signal.emit(y) | |
def close(self): | |
self.stream.stop_stream() | |
self.stream.close() | |
self.p.terminate() | |
class SpectrogramWidget(pg.PlotWidget): | |
read_collected = QtCore.pyqtSignal(np.ndarray) | |
def __init__(self): | |
super(SpectrogramWidget, self).__init__() | |
self.img = pg.ImageItem() | |
self.addItem(self.img) | |
self.img_array = np.zeros((1000, int(CHUNKSZ/2+1))) | |
# bipolar colormap | |
pos = np.array([0., 1., 0.5, 0.25, 0.75]) | |
color = np.array([[0,255,255,255], [255,255,0,255], [0,0,0,255], (0, 0, 255, 255), (255, 0, 0, 255)], dtype=np.ubyte) | |
cmap = pg.ColorMap(pos, color) | |
lut = cmap.getLookupTable(0.0, 1.0, 256) | |
# set colormap | |
self.img.setLookupTable(lut) | |
self.img.setLevels([-50,40]) | |
# setup the correct scaling for y-axis | |
freq = np.arange((CHUNKSZ/2)+1)/(float(CHUNKSZ)/FS) | |
yscale = 1.0/(self.img_array.shape[1]/freq[-1]) | |
self.img.scale((1./FS)*CHUNKSZ, yscale) | |
self.setLabel('left', 'Frequency', units='Hz') | |
# prepare window for later use | |
self.win = np.hanning(CHUNKSZ) | |
self.show() | |
def update(self, chunk): | |
# normalized, windowed frequencies in data chunk | |
spec = np.fft.rfft(chunk*self.win) / CHUNKSZ | |
# get magnitude | |
psd = abs(spec) | |
# convert to dB scale | |
psd = 20 * np.log10(psd) | |
# roll down one and replace leading edge with new data | |
self.img_array = np.roll(self.img_array, -1, 0) | |
self.img_array[-1:] = psd | |
self.img.setImage(self.img_array, autoLevels=False) | |
if __name__ == '__main__': | |
app = QtGui.QApplication([]) | |
w = SpectrogramWidget() | |
w.read_collected.connect(w.update) | |
mic = MicrophoneRecorder(w.read_collected) | |
# time (seconds) between reads | |
interval = FS/CHUNKSZ | |
t = QtCore.QTimer() | |
t.timeout.connect(mic.read) | |
t.start(1000/interval) #QTimer takes ms | |
app.exec_() | |
mic.close() |
Does this still work? I installed pyqt5 but I get this error when I try to run it:
AttributeError: module 'PyQt5.QtGui' has no attribute 'QApplication'. Did you mean: 'QGuiApplication'?
Yes, using 'QGuiApplication' fixed that error. I had a separate issue, getting a message:
Unable to locate theme engine in module_path: "adwaita"
...which cleared after installing gnome-themes-standard
If you find Qt5 is out of date, pip installing pyside2 will fix that, but it is a large download.
I tried QGuiApplication, but it didn't work. What got it working for me was switching to:
app = QtWidgets.QApplication([])
I'm having trouble with the scaling. Is this a Pyqt version problem?
File "livespec.py", line 142, in __init__
self.img.setScale((1./FS)*CHUNKSZ, yscale)
TypeError: setScale(self, scale: float): too many arguments
I also had this problem. I had to switch to using:
self.img.setTransform(QtGui.QTransform.fromScale((1./samplerate)*samplesize, yscale))
I'm not sure if it does the exact same thing but it seems to work.
Awesome tool. I am wondering if there is a way to an interactive color bar. Any help would be appreciated. Thanks
Does this still work? I installed pyqt5 but I get this error when I try to run it:
AttributeError: module 'PyQt5.QtGui' has no attribute 'QApplication'. Did you mean: 'QGuiApplication'?