2017-05-01 3 views
0

Ich versuche, ein Python-Skript zu schreiben, um als Violin-Tuner/Echtzeit-Spektralanzeige zu fungieren. Bisher habe ich pyaudio aufgenommen, um Datenblöcke vom Mikrofon aufzunehmen und das Frequenzspektrum für kurze Zeitreihen von Audio zu berechnen. Ich möchte diese in Echtzeit mit Matplotlib plotten, aber mein Figurenfenster ist leer, während die Daten aufgezeichnet wurden und nur das letzte Plot auf dem Bildschirm aktualisiert wird, nachdem das Skript beendet wurde. Was mache ich falsch?Violin-Tuner mit Python und Matplotlib

# -*- coding: utf-8 -*- 
""" 
Created on Mon May 1 00:03:55 2017 

@author: Hugo. 
""" 

import pyaudio 
import struct 
import numpy as np 
import matplotlib.pyplot as plt 
from time import sleep 


CHUNK = 2**14 #2**15 #4096 
WIDTH = 2 
FORMAT = pyaudio.paInt16 
CHANNELS = 2 
RATE = 44100 
dt = 1.0/RATE 


### frequencies of the strings for the violin (tunned in A), in Hz 
f4 = 195.998 ## G3 
f3 = 293.665 ## D4 
f2 = 440.000 ## A4 
f1 = 659.255 ## E5 

n = CHUNK 
freqs = np.fft.rfftfreq(n, d = dt) 

def Frequency_of_position(position): 
    """ Returns the frequency (Hz) of the note in from its position (halftones) 
    relative to A4 in an equal tempered scale. Ex: 0 -> 440 Hz (A4), 
    12 -> 880 Hz (A5).""" 
    return 440.0*(2**(1.0/12.0))**position 


def Position_to_note(position): 
    "A A# B C C# D D# E F F# G G#" 
    SCALE = ["A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#"] 
    LETTER = SCALE[position % 12] 
    NUMBER = str(int((position+48)/12)) 
    return LETTER+NUMBER 

pos = np.array(range(-36,48)) 
vnote_freqs = np.vectorize(Frequency_of_position) 
note_freqs = vnote_freqs(pos) 


def get_frequency(spectrum): 
    return freqs[np.argmax(spectrum)] 



class Freq_analysis(object): 
    def __init__(self): 
     self.pa = pyaudio.PyAudio() 
     self.stream = self.open_mic_stream() 
     self.plots = self.prepare_figure() 
     #self.fig_and_axes = self.prepare_figure() 
     #self.first_plot = self.plot_first_figure() 


    def stop(self): 
     self.stream.close() 

    def open_mic_stream(self): 
     device_index = self.find_input_device() 

     stream = self.pa.open( format = FORMAT, 
           channels = CHANNELS, 
           rate = RATE, 
           input = True, 
           input_device_index = device_index, 
           frames_per_buffer = CHUNK) 

     return stream 

    def find_input_device(self): 
     device_index = None    
     for i in range(self.pa.get_device_count()):  
      devinfo = self.pa.get_device_info_by_index(i) 
      print("Device %d: %s"%(i,devinfo["name"])) 

      for keyword in ["mic","input"]: 
       if keyword in devinfo["name"].lower(): 
        print("Found an input: device %d - %s"% (i,devinfo["name"])) 
        device_index = i 
        return device_index 

     if device_index == None: 
      print("No preferred input found; using default input device.") 

     return device_index 

    def prepare_figure(self): 
     fig1 = plt.figure(1, figsize = (16,6)) 
     wide_plot = plt.subplot(2,1,1) 
     plt.vlines([f1,f2,f3,f4],1,1e17, linestyles = 'dashed') 
     plt.xlabel("freq (Hz)") 
     plt.ylabel("S^2 (u. arb.)") 
     plt.xscale('log') 
     plt.yscale('log') 
     plt.xlim([80,4000]) 
     #plt.xlim([600,700]) 
     #plt.xlim([400,500]) 
     plt.ylim([1e0,1e17]) 
     spec_w, = plt.plot([1,1],[1,1], '-',c = 'blue') 

     f4_plot = plt.subplot(2,4,5) 
     plt.vlines(f4,1,1e17, linestyles = 'dashed') 
     plt.xlabel("freq (Hz)") 
     plt.ylabel("S^2 (u. arb.)") 
     plt.yscale('log') 
     plt.xlim([140,260]) 
     plt.ylim([1e0,1e17]) 
     spec_f4, = plt.plot([1,1],[1,1], '-',c = 'blue') 

     f3_plot = plt.subplot(2,4,6) 
     plt.vlines(f3,1,1e17, linestyles = 'dashed') 
     plt.xlabel("freq (Hz)") 
     plt.yscale('log') 
     plt.xlim([220,380]) 
     plt.ylim([1e0,1e17]) 
     spec_f3, = plt.plot([1,1],[1,1], '-',c = 'blue') 

     f2_plot = plt.subplot(2,4,7) 
     plt.vlines(f2,1,1e17, linestyles = 'dashed') 
     plt.xlabel("freq (Hz)") 
     plt.yscale('log') 
     plt.xlim([400,500]) 
     plt.ylim([1e0,1e17]) 
     spec_f2, = plt.plot([1,1],[1,1], '-',c = 'blue') 

     f1_plot = plt.subplot(2,4,8) 
     plt.vlines(f1,1,1e17, linestyles = 'dashed') 
     plt.xlabel("freq (Hz)") 
     plt.yscale('log') 
     plt.xlim([600,700]) 
     plt.ylim([1e0,1e17]) 
     spec_f1, = plt.plot([1,1],[1,1], '-',c = 'blue') 

     plt.show() 

    #return fig1, wide_plot, f1_plot, f2_plot, f3_plot, f4_plot 
     return spec_w, spec_f1, spec_f2, spec_f3, spec_f4 


    def PrintFreq(self, S2): 
     dominant = get_frequency(S2) 
     dist = np.abs(note_freqs-dominant) 
     closest_pos = pos[np.argmin(dist)] 
     closest_note = Position_to_note(closest_pos) 
     print(dominant, "(",closest_note, "=",Frequency_of_position(closest_pos),")") 

    def listen(self): 
     try: 
      block = self.stream.read(CHUNK) 
     except IOError: 
      # An error occurred. 
      print("Error recording.") 
      return 
     indata = np.array(struct.unpack("%dh"%(len(block)/2),block)) 
     n = indata.size 
     freqs = np.fft.rfftfreq(n, d = dt) 
     data_rfft = np.fft.rfft(indata) 
     S2 = np.abs(data_rfft)**2 
     #self.PrintFreq(block) 
     #self.update_fig(block) 
     self.PrintFreq(S2) 
     self.update_fig(freqs, S2) 

    def update_fig(self, freqs, S2): 
     self.plots[0].set_xdata(freqs) 
     self.plots[1].set_xdata(freqs) 
     self.plots[2].set_xdata(freqs) 
     self.plots[3].set_xdata(freqs) 
     self.plots[4].set_xdata(freqs) 

     self.plots[0].set_ydata(S2) 
     self.plots[1].set_ydata(S2) 
     self.plots[2].set_ydata(S2) 
     self.plots[3].set_ydata(S2) 
     self.plots[4].set_ydata(S2) 

    #plt.draw() 
    #plt.show() 

if __name__ == "__main__": 
    Tuner = Freq_analysis() 

    for i in range(1000): 
     Tuner.listen() 
     plt.show() 
+0

Für ein einfaches Beispiel mit 'matplotlib.animation' meine Beispielanwendung [plot_input.py] (https://github.com/spatialaudio/python- Sounddevice/Blob/Master/Beispiele/Plot_input.py). Es wandelt das Signal nicht in den Frequenzbereich um, aber ich denke, das könnte leicht hinzugefügt werden. – Matthias

Antwort

0

Da ich den Code nicht ausführen kann, kann ich nur raten. Aber es sieht so aus, als würdest du die Leinwand nie wirklich neu zeichnen.

Versuche Zugabe

self.plots[0].figure.canvas.draw_idle() 

am Ende der update_fig Funktion.

Dies könnte oder könnte nicht funktionieren. Vielleicht möchten Sie auch den interaktiven Modus ausprobieren. Drehen Sie plt.ion() und fügen

plt.draw() 
plt.pause(0.0001) 

am Ende der update_fig Funktion. Am Ende können Sie plt.ioff() drehen und plt.show() anrufen, um die Zahl offen zu halten.

Der folgende Code läuft gut für mich:

import pyaudio 
import struct 
import numpy as np 
import matplotlib.pyplot as plt 
from time import sleep 


CHUNK = 2**14 #2**15 #4096 
WIDTH = 2 
FORMAT = pyaudio.paInt16 
CHANNELS = 2 
RATE = 44100 
dt = 1.0/RATE 


### frequencies of the strings for the violin (tunned in A), in Hz 
f4 = 195.998 ## G3 
f3 = 293.665 ## D4 
f2 = 440.000 ## A4 
f1 = 659.255 ## E5 

n = CHUNK 
freqs = np.fft.rfftfreq(n, d = dt) 

def Frequency_of_position(position): 
    """ Returns the frequency (Hz) of the note in from its position (halftones) 
    relative to A4 in an equal tempered scale. Ex: 0 -> 440 Hz (A4), 
    12 -> 880 Hz (A5).""" 
    return 440.0*(2**(1.0/12.0))**position 


def Position_to_note(position): 
    "A A# B C C# D D# E F F# G G#" 
    SCALE = ["A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#"] 
    LETTER = SCALE[position % 12] 
    NUMBER = str(int((position+57)/12)) 
    return LETTER+NUMBER 

pos = np.array(range(-36,48)) 
vnote_freqs = np.vectorize(Frequency_of_position) 
note_freqs = vnote_freqs(pos) 


def get_frequency(spectrum): 
    return freqs[np.argmax(spectrum)] 



class Freq_analysis(object): 
    def __init__(self): 
     self.pa = pyaudio.PyAudio() 
     self.stream = self.open_mic_stream() 
     self.plots = self.prepare_figure() 
     #self.fig_and_axes = self.prepare_figure() 
     #self.first_plot = self.plot_first_figure() 


    def stop(self): 
     self.stream.close() 

    def open_mic_stream(self): 
     device_index = self.find_input_device() 

     stream = self.pa.open( format = FORMAT, 
           channels = CHANNELS, 
           rate = RATE, 
           input = True, 
           input_device_index = device_index, 
           frames_per_buffer = CHUNK) 

     return stream 

    def find_input_device(self): 
     device_index = None    
     for i in range(self.pa.get_device_count()):  
      devinfo = self.pa.get_device_info_by_index(i) 
      print("Device %d: %s"%(i,devinfo["name"])) 

      for keyword in ["mic","input"]: 
       if keyword in devinfo["name"].lower(): 
        print("Found an input: device %d - %s"% (i,devinfo["name"])) 
        device_index = i 
        return device_index 

     if device_index == None: 
      print("No preferred input found; using default input device.") 

     return device_index 

    def prepare_figure(self): 
     plt.ion() 
     fig1 = plt.figure(1, figsize = (16,6)) 
     wide_plot = plt.subplot(2,1,1) 
     plt.vlines([f1,f2,f3,f4],1,1e17, linestyles = 'dashed') 
     plt.xlabel("freq (Hz)") 
     plt.ylabel("S^2 (u. arb.)") 
     plt.xscale('log') 
     plt.yscale('log') 
     plt.xlim([80,4000]) 
     #plt.xlim([600,700]) 
     #plt.xlim([400,500]) 
     plt.ylim([1e0,1e17]) 
     spec_w, = plt.plot([1,1],[1,1], '-',c = 'blue') 

     f4_plot = plt.subplot(2,4,5) 
     plt.vlines(f4,1,1e17, linestyles = 'dashed') 
     plt.xlabel("freq (Hz)") 
     plt.ylabel("S^2 (u. arb.)") 
     plt.yscale('log') 
     plt.xlim([140,260]) 
     plt.ylim([1e0,1e17]) 
     spec_f4, = plt.plot([1,1],[1,1], '-',c = 'blue') 

     f3_plot = plt.subplot(2,4,6) 
     plt.vlines(f3,1,1e17, linestyles = 'dashed') 
     plt.xlabel("freq (Hz)") 
     plt.yscale('log') 
     plt.xlim([220,380]) 
     plt.ylim([1e0,1e17]) 
     spec_f3, = plt.plot([1,1],[1,1], '-',c = 'blue') 

     f2_plot = plt.subplot(2,4,7) 
     plt.vlines(f2,1,1e17, linestyles = 'dashed') 
     plt.xlabel("freq (Hz)") 
     plt.yscale('log') 
     plt.xlim([400,500]) 
     plt.ylim([1e0,1e17]) 
     spec_f2, = plt.plot([1,1],[1,1], '-',c = 'blue') 

     f1_plot = plt.subplot(2,4,8) 
     plt.vlines(f1,1,1e17, linestyles = 'dashed') 
     plt.xlabel("freq (Hz)") 
     plt.yscale('log') 
     plt.xlim([600,700]) 
     plt.ylim([1e0,1e17]) 
     spec_f1, = plt.plot([1,1],[1,1], '-',c = 'blue') 

     plt.draw() 

    #return fig1, wide_plot, f1_plot, f2_plot, f3_plot, f4_plot 
     return spec_w, spec_f1, spec_f2, spec_f3, spec_f4 


    def PrintFreq(self, S2): 
     dominant = get_frequency(S2) 
     dist = np.abs(note_freqs-dominant) 
     closest_pos = pos[np.argmin(dist)] 
     closest_note = Position_to_note(closest_pos) 
     print(dominant, "(",closest_note, "=",Frequency_of_position(closest_pos),")") 

    def listen(self): 
     try: 
      block = self.stream.read(CHUNK) 
     except IOError: 
      # An error occurred. 
      print("Error recording.") 
      return 
     indata = np.array(struct.unpack("%dh"%(len(block)/2),block)) 
     n = indata.size 
     freqs = np.fft.rfftfreq(n, d = dt) 
     data_rfft = np.fft.rfft(indata) 
     S2 = np.abs(data_rfft)**2 
     #self.PrintFreq(block) 
     #self.update_fig(block) 
     self.PrintFreq(S2) 
     self.update_fig(freqs, S2) 

    def update_fig(self, freqs, S2): 
     self.plots[0].set_xdata(freqs) 
     self.plots[1].set_xdata(freqs) 
     self.plots[2].set_xdata(freqs) 
     self.plots[3].set_xdata(freqs) 
     self.plots[4].set_xdata(freqs) 

     self.plots[0].set_ydata(S2) 
     self.plots[1].set_ydata(S2) 
     self.plots[2].set_ydata(S2) 
     self.plots[3].set_ydata(S2) 
     self.plots[4].set_ydata(S2) 
     plt.draw() 
     plt.pause(0.001) 


if __name__ == "__main__": 
    Tuner = Freq_analysis() 

    for i in range(100): 
     Tuner.listen() 
    plt.ioff() 
    plt.show() 
+0

Ich habe den Code bearbeitet, jetzt können Sie ihn ausführen. Das Hinzufügen von '... canvas.draw_idle()' hat ebenfalls nicht funktioniert. Ich habe auch 'plt.draw()' und 'plt.show()' mit 'plt.ion()' und 'plt.ioff()' versucht. Das verwirrt mich. –

+0

Für mich funktioniert es. Ich habe den Code hinzugefügt, den ich auf meinem Computer ausführen kann. – ImportanceOfBeingErnest

+0

Ja, es hat auch in meinem Computer funktioniert. Ich fand, dass ich 'sleep()' anstelle von 'plt.pause()' benutzte. Mit 'plt.pause()' gelang der Trick. Vielen Dank! Nun wirft das eine andere Frage auf: Was ist der Unterschied zwischen "plt.pause" und "sleep"? –

0

Ich denke, Sie müssen nur einige Ruhezeit zwischen Plot-Updates hinzufügen. Sie haben sich wahrscheinlich so geäußert, da Sie die sleep importiert haben.

from time import sleep 

... 

for i in range(1000): 
    sleep(10) 
    Tuner.listen() 
    plt.show() 

jedoch eine bessere Praxis der matplotlib.animation Modul wäre, die official example check it out!

+0

Ich versuchte es mit dem Schlaf. Es funktioniert nicht. Anscheinend hat die 'Pyaudio'-Operation Vorrang vor der Figurenaktualisierung. Die Figur bleibt bis zum Ende des Skripts leer, egal wie viel Zeit ich in den Schlaf hineinlege. Danke für den Tipp über 'matplotlib.animation'. Ich werde es als nächstes versuchen, aber ich befürchte, dass das gleiche Problem passieren wird. –