0

I'm new here, and hope my question is right here. I'm trying to programm a GUI where I can record some sound from the microphone and plot it in real-time, to see the left and right channel of the sound.

I'm using PyQt 5 for my GUI und Matplotlib-FigureCanvas for Plotting. The streaming works fine, however the Plot only shows after the recording stops and does not update during recording, like it should. In Debug-Mode I can see the plots everytime they should update, but when I run the code, the GUI freezes until the recording is done. Is that, because the Plotting is too slow? I tried different approaches with animate or threading, but nothing worked so far.

I would like to have a real-time-updating plot in the end, how can I achieve this? Also with longer recording?

I hope someone can help me, thanks in advance!

Here is the part of my code, where I'm recording and plotting:

import sys
from PyQt5 import QtGui, QtWidgets, QtCore
import numpy as np
import time
import pyaudio
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
import matplotlib.gridspec as gridspec
import matplotlib.animation as animation


class Window(QtWidgets.QMainWindow):

  def __init__(self):  # sort of template for rest of GUI, is always there, menubar/ mainmenu eg.
      super(Window, self).__init__()
      self.setGeometry(50, 50, 1500, 900)
      self.setWindowTitle("PyQt Tutorial!")

      self.centralwidget = QtWidgets.QWidget(self)
      self.centralwidget.setObjectName("centralwidget")

      self.channels = 2
      self.fs = 44100  # samplerate
      self.Chunks = 1024

      self.tapeLength = 2  # seconds
      self.tape = np.empty(self.fs * self.tapeLength) * np.nan  # tapes where recorded audio is stored
      self.taper = np.empty(self.fs * self.tapeLength) * np.nan
      self.tapel = np.empty(self.fs * self.tapeLength) * np.nan

      self.home()

  def home(self):
      btn = QtWidgets.QPushButton("Stream and Plot", self)  # Button to start streaming
      btn.clicked.connect(self.plot)
      btn.resize(btn.sizeHint())
      btn.move(100, 100)

      self.scrollArea = QtWidgets.QScrollArea(self)
      self.scrollArea.move(75, 400)
      self.scrollArea.resize(600, 300)
      self.scrollArea.setWidgetResizable(False)

      self.scrollArea2 = QtWidgets.QScrollArea(self)
      self.scrollArea2.move(775, 400)
      self.scrollArea2.resize(600, 300)
      self.scrollArea2.setWidgetResizable(False)
      self.scrollArea.horizontalScrollBar().valueChanged.connect(self.scrollArea2.horizontalScrollBar().setValue)
      self.scrollArea2.horizontalScrollBar().valueChanged.connect(self.scrollArea.horizontalScrollBar().setValue)

      self.figure = Figure((15, 2.8), dpi=100)  # figure instance (to plot on) F(width, height, ...)
      self.canvas = FigureCanvas(self.figure)
      self.scrollArea.setWidget(self.canvas)
      self.toolbar = NavigationToolbar(self.canvas, self.scrollArea)

      self.canvas2 = FigureCanvas(self.figure)
      self.scrollArea2.setWidget(self.canvas2)
      self.toolbar2 = NavigationToolbar(self.canvas2, self.scrollArea2)

      self.gs = gridspec.GridSpec(1, 1)
      self.ax = self.figure.add_subplot(self.gs[0])
      self.ax2 = self.figure.add_subplot(self.gs[0])
      self.figure.subplots_adjust(left=0.05)

      self.ax.clear()

  def start_streamsignal(self, start=True):
      # open and start the stream
      if start is True:
          print("start Signals")

          self.p = pyaudio.PyAudio()
          self.stream = self.p.open(format=pyaudio.paFloat32, channels=self.channels, rate=self.fs, input_device_index=1,
                             output_device_index=5, input=True, frames_per_buffer=self.Chunks)
          print("recording...")

  def start_streamread(self):
      """return values for Chunks of stream"""
      data = self.stream.read(self.Chunks)
      npframes2 = np.array(data).flatten()
      npframes2 = np.fromstring(npframes2, dtype=np.float32)

      norm_audio2 = (npframes2 / np.max(np.abs(npframes2)))  # normalize
      left2 = norm_audio2[::2]
      right2 = norm_audio2[1::2]
      print(norm_audio2)
      return left2, right2

  def tape_add(self):
      """add chunks to tape"""
      self.tape[:-self.Chunks] = self.tape[self.Chunks:]
      self.taper = self.tape
      self.tapel = self.tape
      self.taper[-self.Chunks:], self.tapel[-self.Chunks:] = self.start_streamread()

  def plot(self, use_blit=True):
      # Plot the Tape and update chunks
      print('Plotting')
      self.start_streamsignal(start=True)
      start = True

      for duration in range(0, 15, 1):
          plotsec = 1
          time.sleep(2)
          self.timeArray = np.arange(self.taper.size)
          self.timeArray = (self.timeArray / self.fs) * 1000  # scale to milliseconds
          self.tape_add()

          # self.canvas.draw()
          while start is True and plotsec < 3:

              # self.ani = animation.FuncAnimation(self.figure, self.animate, interval=25, blit=True)
              self.ax2.plot(self.taper, '-b')
              self.canvas.draw()

              self.ax2.clear()
              self.ax2.plot(self.tapel, 'g-')
              self.canvas2.draw()

              plotsec += 1

  def animate(self):
      self.line.set_xdata(self.taper)
      return self.line,


def main():
    app = QtWidgets.QApplication(sys.argv)
    GUI = Window()
    GUI.show()
    sys.exit(app.exec_())

main()
3
  • Put QApplication.processEvents() in your loop. Commented Aug 10, 2017 at 13:51
  • Or replace canvas2.draw() by canvas2.draw_idle(). Anyway, the code is too large to debug it. If any of the above comments do not solve your issue, try composing a minimal example with the problem. SO is not intended to be used to ask for help on an 100 lines of code. Commented Aug 10, 2017 at 14:03
  • @Chris, this little piece of Code (QtWidgets.QApplication.processEvents()) made the programm work like it should, perfect. Thank you very much. Commented Aug 11, 2017 at 6:33

2 Answers 2

2

I do have severe problems understanding the complete code as to what all the stuff in there should actually do. So all I can tell for now is that you may want to get rid of the loops and use FuncAnimation to display the animation.

  def plot(self, use_blit=True):
      # Plot the Tape and update chunks
      print('Plotting')
      self.start_streamsignal(start=True)
      start = True
      self.line, = self.ax2.plot([],[], '-b')
      self.ax.set_xlim(0, len(self.tape))
      self.ax2.set_xlim(0, len(self.tape))
      self.ax.set_ylim(-3, 3)
      self.ax2.set_ylim(-3, 3)

      self.ani = animation.FuncAnimation(self.figure, self.animate, frames=100, 
                                         interval=25, blit=use_blit)


  def animate(self,i):
      self.timeArray = np.arange(self.taper.size)
      self.timeArray = (self.timeArray / self.fs) * 1000  # scale to milliseconds
      self.tape_add()
      self.line.set_data(range(len(self.taper)),self.taper)
      return self.line,
Sign up to request clarification or add additional context in comments.

1 Comment

Sorry, I should have put more comments to the code, I know that know. Thank you, but with the programm working with the loops, I don't need the animate part at all anymore.
0

Thanks for all the Help, here is the now working code, if someone is interested:

import sys
from PyQt5 import QtGui, QtWidgets, QtCore
import numpy as np
import time
import pyaudio
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
import matplotlib.gridspec as gridspec
import matplotlib.animation as animation


class Window(QtWidgets.QMainWindow):

  def __init__(self):  # sort of template for rest of GUI, is always there, menubar/ mainmenu eg.
      super(Window, self).__init__()
      self.setGeometry(50, 50, 1500, 900)
      self.setWindowTitle("PyQt Tutorial!")

      self.centralwidget = QtWidgets.QWidget(self)
      self.centralwidget.setObjectName("centralwidget")

      self.channels = 2
      self.fs = 44100  # samplerate
      self.Chunks = 1024

      self.tapeLength = 2  # seconds
      self.tape = np.empty(self.fs * self.tapeLength) * np.nan  # tapes where recorded audio is stored

      self.home()

  def home(self):
      btn = QtWidgets.QPushButton("Stream and Plot", self)  # Button to start streaming
      btn.clicked.connect(self.plot)
      btn.resize(btn.sizeHint())
      btn.move(100, 100)

      self.scrollArea = QtWidgets.QScrollArea(self)
      self.scrollArea.move(75, 400)
      self.scrollArea.resize(600, 300)
      self.scrollArea.setWidgetResizable(False)

      self.scrollArea2 = QtWidgets.QScrollArea(self)
      self.scrollArea2.move(775, 400)
      self.scrollArea2.resize(600, 300)
      self.scrollArea2.setWidgetResizable(False)
      self.scrollArea.horizontalScrollBar().valueChanged.connect(self.scrollArea2.horizontalScrollBar().setValue)
      self.scrollArea2.horizontalScrollBar().valueChanged.connect(self.scrollArea.horizontalScrollBar().setValue)

      self.figure = Figure((15, 2.8), dpi=100)  # figure instance (to plot on) F(width, height, ...)
      self.canvas = FigureCanvas(self.figure)
      self.scrollArea.setWidget(self.canvas)
      self.toolbar = NavigationToolbar(self.canvas, self.scrollArea)

      self.canvas2 = FigureCanvas(self.figure)
      self.scrollArea2.setWidget(self.canvas2)
      self.toolbar2 = NavigationToolbar(self.canvas2, self.scrollArea2)

      self.gs = gridspec.GridSpec(1, 1)
      self.ax = self.figure.add_subplot(self.gs[0])
      self.ax2 = self.figure.add_subplot(self.gs[0])
      self.figure.subplots_adjust(left=0.05)

      self.ax.clear()

  def start_streamsignal(self, start=True):
  # open and start the stream
      if start is True:
          print("start Signals")

          self.p = pyaudio.PyAudio()
          self.stream = self.p.open(format=pyaudio.paFloat32, channels=self.channels, rate=self.fs, input_device_index=1,
                         output_device_index=5, input=True, frames_per_buffer=self.Chunks)
          print("recording...")

  def start_streamread(self):
  """return values for Chunks of stream"""
      data = self.stream.read(self.Chunks)
      npframes2 = np.array(data).flatten()
      npframes2 = np.fromstring(npframes2, dtype=np.float32)

      norm_audio2 = (npframes2 / np.max(np.abs(npframes2)))  # normalize
      left2 = norm_audio2[::2]
      right2 = norm_audio2[1::2]
      print(norm_audio2)
      return left2, right2

  def tape_add(self):
  """add chunks to tape"""
      self.tape[:-self.Chunks] = self.tape[self.Chunks:]
      self.taper = self.tape
      self.tapel = self.tape
      self.taper[-self.Chunks:], self.tapel[-self.Chunks:] = self.start_streamread()

  def plot(self, use_blit=True):
  # Plot the Tape and update chunks
      print('Plotting')
      self.start_streamsignal(start=True)
      start = True

      for duration in range(0, 15, 1):
          QtWidgets.QApplication.processEvents()
          plotsec = 1
          time.sleep(2)
          self.timeArray = np.arange(self.taper.size)
          self.timeArray = (self.timeArray / self.fs) * 1000  # scale to milliseconds
          self.tape_add()

          while start is True and plotsec < 3:

              self.ax.plot(self.taper, '-b')
              self.canvas.draw()

              self.ax2.clear()
              self.ax2.plot(self.tapel, 'g-')
              self.canvas2.draw()

              plotsec += 1


def main():
    app = QtWidgets.QApplication(sys.argv)
    GUI = Window()
    GUI.show()
    sys.exit(app.exec_())

main()

1 Comment

Do you have a version that actually works? the supplied code isn't. For example self.taper, self.tapel, self.timeArray is used before it gets defined.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.