Python Forum

Full Version: Assign Label Text as number in Hz
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Pages: 1 2
I'm totally new to this sound-involving programming, so I have this entrusted project to me, and they want my sound visualizer program can also showing frequency detail of the sound (in realtime) in number (Hz).

I have to put this number (Hz) info alone like in their own window, not in the same as my sound visualizer graph (using graph in PyQt4 promoted to pyqtgraph). Confused

This code below is the sound graph
import pyaudio
import time
import numpy as np
import threading

def getFFT(data,rate):
    """Given some data and rate, returns FFTfreq and FFT (half)."""
    data=data*np.hamming(len(data))
    fft=np.fft.fft(data)
    fft=np.abs(fft)
    #fft=10*np.log10(fft)
    freq=np.fft.fftfreq(len(fft),1.0/rate)
    return freq[:int(len(freq)/2)],fft[:int(len(fft)/2)]

class SWHear():
    def __init__(self,device=None,rate=None,updatesPerSecond=10):
        self.p=pyaudio.PyAudio()
        self.chunk=4096 # gets replaced automatically
        self.updatesPerSecond=updatesPerSecond
        self.chunksRead=0
        self.device=device
        self.rate=rate

    ### SYSTEM TESTS

    def valid_low_rate(self,device):
        """set the rate to the lowest supported audio rate."""
        for testrate in [44100]:
            if self.valid_test(device,testrate):
                return testrate
        print("SOMETHING'S WRONG! I can't figure out how to use DEV",device)
        return None

    def valid_test(self,device,rate=44100):
        """given a device ID and a rate, return TRUE/False if it's valid."""
        try:
            self.info=self.p.get_device_info_by_index(device)
            if not self.info["maxInputChannels"]>0:
                return False
            stream=self.p.open(format=pyaudio.paInt16,channels=1,
               input_device_index=device,frames_per_buffer=self.chunk,
               rate=int(self.info["defaultSampleRate"]),input=True)
            stream.close()
            return True
        except:
            return False

    def valid_input_devices(self):
        """
        See which devices can be opened for microphone input.
        call this when no PyAudio object is loaded.
        """
        mics=[]
        for device in range(self.p.get_device_count()):
            if self.valid_test(device):
                mics.append(device)
        if len(mics)==0:
            print("no microphone devices found!")
        else:
            print("found %d microphone devices: %s"%(len(mics),mics))
        return mics

    ### SETUP AND SHUTDOWN

    def initiate(self):
        """run this after changing settings (like rate) before recording"""
        if self.device is None:
            self.device=self.valid_input_devices()[0] #pick the first one
        if self.rate is None:
            self.rate=self.valid_low_rate(self.device)
        self.chunk = int(self.rate/self.updatesPerSecond) # hold one tenth of a second in memory
        if not self.valid_test(self.device,self.rate):
            print("guessing a valid microphone device/rate...")
            self.device=self.valid_input_devices()[0] #pick the first one
            self.rate=self.valid_low_rate(self.device)
        self.datax=np.arange(self.chunk)/float(self.rate)
        msg='recording from "%s" '%self.info["name"]
        msg+='(device %d) '%self.device
        msg+='at %d Hz'%self.rate
        print(msg)

    def close(self):
        """gently detach from things."""
        print(" -- sending stream termination command...")
        self.keepRecording=False #the threads should self-close
        while(self.t.isAlive()): #wait for all threads to close
            time.sleep(.1)
        self.stream.stop_stream()
        self.p.terminate()

    ### STREAM HANDLING

    def stream_readchunk(self):
        """reads some audio and re-launches itself"""
        try:
            self.data = np.fromstring(self.stream.read(self.chunk),dtype=np.int16)
            self.fftx, self.fft = getFFT(self.data,self.rate)

        except Exception as E:
            print(" -- exception! terminating...")
            print(E,"\n"*5)
            self.keepRecording=False
        if self.keepRecording:
            self.stream_thread_new()
        else:
            self.stream.close()
            self.p.terminate()
            print(" -- stream STOPPED")
        self.chunksRead+=1

    def stream_thread_new(self):
        self.t=threading.Thread(target=self.stream_readchunk)
        self.t.start()

    def stream_start(self):
        """adds data to self.data until termination signal"""
        self.initiate()
        print(" -- starting stream")
        self.keepRecording=True # set this to False later to terminate stream
        self.data=None # will fill up with threaded recording data
        self.fft=None
        self.dataFiltered=None #same
        self.stream=self.p.open(format=pyaudio.paInt16,channels=1,
                      rate=self.rate,input=True,frames_per_buffer=self.chunk)
        self.stream_thread_new()

if __name__=="__main__":
    ear=SWHear(updatesPerSecond=10) # optinoally set sample rate here
    ear.stream_start() #goes forever
    lastRead=ear.chunksRead
    while True:
        while lastRead==ear.chunksRead:
            time.sleep(.01)
        print(ear.chunksRead,len(ear.data))
        lastRead=ear.chunksRead
    print("DONE")
So my question is: how to assigned a label text so they can turn into number and keep updating while listening to the sound?
oh and code samples are most appreciated Big Grin

FYI I have done this "keep updating label text" before but in Delphy which is totally different.

Thanks in advance for any help that I receive! Blush
You can use a QTimer for that;

def __init__(self):
	self.timer = QtCore.QTimer(interval=1000)
	self.timer.timeout.connect(self.update)
	self.timer.start()

def update(self):
	print("Hello world")
(Jul-01-2018, 05:28 PM)Alfalfa Wrote: [ -> ]You can use a QTimer for that;

def __init__(self):
	self.timer = QtCore.QTimer(interval=1000)
	self.timer.timeout.connect(self.update)
	self.timer.start()

def update(self):
	print("Hello world")

Thank you for your reply, can you tell me more specific where I should put this code? I'm so sorry the codes I've showed is the last update of my friend before I get entrusted with this project

I'll show you the rest of the code, maybe you can take a look of it

this codes belong to the main window
from PyQt4 import QtCore, QtGui

try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    def _fromUtf8(s):
        return s

try:
    _encoding = QtGui.QApplication.UnicodeUTF8
    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig)

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName(_fromUtf8("MainWindow"))
        MainWindow.resize(800, 620)
        self.centralwidget = QtGui.QWidget(MainWindow)
        self.centralwidget.setObjectName(_fromUtf8("centralwidget"))
        self.frame = QtGui.QFrame(self.centralwidget)
        self.frame.setGeometry(QtCore.QRect(0, 0, 801, 611))
        self.frame.setFrameShape(QtGui.QFrame.NoFrame)
        self.frame.setFrameShadow(QtGui.QFrame.Plain)
        self.frame.setObjectName(_fromUtf8("frame"))
        self.grFFT = PlotWidget(self.frame)
        self.grFFT.setGeometry(QtCore.QRect(10, 30, 781, 241))
        self.grFFT.setObjectName(_fromUtf8("grFFT"))
        self.grPCM = PlotWidget(self.frame)
        self.grPCM.setGeometry(QtCore.QRect(10, 300, 781, 241))
        self.grPCM.setObjectName(_fromUtf8("grPCM"))
        self.pbLevel = QtGui.QProgressBar(self.frame)
        self.pbLevel.setGeometry(QtCore.QRect(10, 550, 791, 23))
        self.pbLevel.setMaximum(1000)
        self.pbLevel.setProperty("value", 123)
        self.pbLevel.setObjectName(_fromUtf8("pbLevel"))
        self.label_2 = QtGui.QLabel(self.frame)
        self.label_2.setGeometry(QtCore.QRect(10, 10, 948, 13))
        self.label_2.setObjectName(_fromUtf8("label_2"))
        self.label_3 = QtGui.QLabel(self.frame)
        self.label_3.setGeometry(QtCore.QRect(10, 280, 721, 20))
        self.label_3.setObjectName(_fromUtf8("label_3"))
        self.pushButton = QtGui.QPushButton(self.frame)
        self.pushButton.setGeometry(QtCore.QRect(10, 580, 191, 23))
        self.pushButton.setObjectName(_fromUtf8("pushButton"))
        MainWindow.setCentralWidget(self.centralwidget)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
        self.label_2.setText(_translate("MainWindow", "frequency data (FFT):", None))
        self.label_3.setText(_translate("MainWindow", "raw data (PCM):", None))
        self.pushButton.setText(_translate("MainWindow", "PushButton", None))

from pyqtgraph import PlotWidget
and this codes is to run the entire above codes (along with the one I posted before)
from PyQt4 import QtGui,QtCore

import sys
import ui_main
import numpy as np
import pyqtgraph
import SWHear

class SoundApp(QtGui.QMainWindow, ui_main.Ui_MainWindow):
    def __init__(self, parent=None):
        pyqtgraph.setConfigOption('background', 'w') #before loading widget
        super(SoundApp, self).__init__(parent)
        self.setupUi(self)
        self.grFFT.plotItem.showGrid(True, True, 0.7)
        self.grPCM.plotItem.showGrid(True, True, 0.7)
        self.maxFFT=0
        self.maxPCM=0
        self.ear = SWHear.SWHear(rate=44100,updatesPerSecond=20)
        self.ear.stream_start()

    def update(self):
        if not self.ear.data is None and not self.ear.fft is None:
            pcmMax=np.max(np.abs(self.ear.data))
            if pcmMax>self.maxPCM:
                self.maxPCM=pcmMax
                self.grPCM.plotItem.setRange(yRange=[-pcmMax,pcmMax])
            if np.max(self.ear.fft)>self.maxFFT:
                self.maxFFT=np.max(np.abs(self.ear.fft))
                #self.grFFT.plotItem.setRange(yRange=[0,self.maxFFT])
                self.grFFT.plotItem.setRange(yRange=[0,1])
            self.pbLevel.setValue(1000*pcmMax/self.maxPCM)
            pen=pyqtgraph.mkPen(color='b')
            self.grPCM.plot(self.ear.datax,self.ear.data,pen=pen,clear=True)
            pen=pyqtgraph.mkPen(color='r')
            self.grFFT.plot(self.ear.fftx,self.ear.fft/self.maxFFT,pen=pen,clear=True)
        QtCore.QTimer.singleShot(1, self.update) # QUICKLY repeat

if __name__=="__main__":
    app = QtGui.QApplication(sys.argv)
    form = SoundApp()
    form.show()
    form.update() #start with something
    app.exec_()
    print("DONE")
it'll be easier if you run it, the all .py codes I put it here in my drive
Sound App
Hi there,
I'm not sure about how you want to use it in your code, but QTimer are very easy. Like I shown you, you can simply init it in the __init__ of your gui loop (SoundApp), then use self.timer to manipulate it if needed. You don't need to init a new singleshot timer recursively like you've done (line 36). Single shots timers are.. for single shot. They fire only once. If you want to repeat an action every few seconds, simply init a QTimer with an interval, then start it and it will loop forever.
Hello, I can only help a bit with signal processing.
Instead of making a fft and throwing the negative frequencies away, you should do a rfft.

Here a corrected version and additional the same with a rfft.

def getFFT(data, rate):
    """Given some data and rate, returns FFTfreq and FFT (half)."""
    window_size = len(data)
    data *= np.hamming(window_size)
    fft = np.fft.fft(data).real
    freq = np.fft.fftfreq(window_size, 1.0 / rate)
    half_size = slice(None, window_size // 2)
    return freq[half_size], fft[half_size]


def getRFFT(data, rate):
    """Given some data and rate, returns RFFTfreq and RFFT."""
    window_size = len(data)
    data *= np.hamming(window_size)
    rfft = np.fft.rfft(data).real
    freq = np.fft.rfftfreq(window_size, 1.0 / rate)
    return freq, rfft
(Jul-03-2018, 04:17 PM)Alfalfa Wrote: [ -> ]Hi there,
I'm not sure about how you want to use it in your code, but QTimer are very easy. Like I shown you, you can simply init it in the __init__ of your gui loop (SoundApp), then use self.timer to manipulate it if needed. You don't need to init a new singleshot timer recursively like you've done (line 36). Single shots timers are.. for single shot. They fire only once. If you want to repeat an action every few seconds, simply init a QTimer with an interval, then start it and it will loop forever.

Thank you so much!! Big Grin
I'll try it right away when I get home, and see if there's any problem while I processing it

(Jul-03-2018, 10:02 PM)DeaD_EyE Wrote: [ -> ]Hello, I can only help a bit with signal processing.
Instead of making a fft and throwing the negative frequencies away, you should do a rfft.

Here a corrected version and additional the same with a rfft.

def getFFT(data, rate):
    """Given some data and rate, returns FFTfreq and FFT (half)."""
    window_size = len(data)
    data *= np.hamming(window_size)
    fft = np.fft.fft(data).real
    freq = np.fft.fftfreq(window_size, 1.0 / rate)
    half_size = slice(None, window_size // 2)
    return freq[half_size], fft[half_size]


def getRFFT(data, rate):
    """Given some data and rate, returns RFFTfreq and RFFT."""
    window_size = len(data)
    data *= np.hamming(window_size)
    rfft = np.fft.rfft(data).real
    freq = np.fft.rfftfreq(window_size, 1.0 / rate)
    return freq, rfft

Thank you so much! I'll try to changed the FFT then and see the results
but I have a question if I want to change the number that written in the axis XY inside the graph to alphabet like ABCD instead 0123, how can I do that?

I want to make a marker for some number into alphabet like 198 into G, 440 into A, etc
I have no clue about music.
Based on this Article and this Example with matplotlib, I wrote an example.

from itertools import product
import matplotlib.pyplot as plt


def calc_freq_from_midi(m):
    if m < 0 or m > 127:
        raise ValueError('m must be between 0 and 127.')
    notes = ('C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B')
    octaves = range(-1, 10)
    notes = product(octaves, notes)
    notes = [note + str(band) for band, note in notes]
    freq = 440 * 2 ** ((m - 69)/12)
    return freq, notes[m]

labels = [calc_freq_from_midi(m) for m in range(128)]
x_ticks, x_labels = zip(*labels) # it's a transpose


frequencies = [110.0, 220.0, 440.0] # A2, A3, A4
magnitude = [2, 3, 4]
plt.title('Spectrum')
plt.xlabel('Notes')
plt.ylabel('Magnitude')
plt.xticks(x_ticks, x_labels, rotation='vertical')
plt.plot(frequencies, magnitude, 'o')
plt.legend(['Signal'])
plt.show()
Just check if the notes are correct.
It should plot A2, A3 and A4 as blue dots.

I guess you can also print two different xticks.
Just explore the matplotlib examples.

If you don't use matplotlib inside of QT, you have to adapt it to the plotting framework you use.
(Jul-03-2018, 10:02 PM)DeaD_EyE Wrote: [ -> ]Hello, I can only help a bit with signal processing.
Instead of making a fft and throwing the negative frequencies away, you should do a rfft.

Here a corrected version and additional the same with a rfft.

def getFFT(data, rate):
    """Given some data and rate, returns FFTfreq and FFT (half)."""
    window_size = len(data)
    data *= np.hamming(window_size)
    fft = np.fft.fft(data).real
    freq = np.fft.fftfreq(window_size, 1.0 / rate)
    half_size = slice(None, window_size // 2)
    return freq[half_size], fft[half_size]


def getRFFT(data, rate):
    """Given some data and rate, returns RFFTfreq and RFFT."""
    window_size = len(data)
    data *= np.hamming(window_size)
    rfft = np.fft.rfft(data).real
    freq = np.fft.rfftfreq(window_size, 1.0 / rate)
    return freq, rfft

When I run the code (I do the changes on FFT inside the SWHear.py into your code)
The result is no error at all it runs perfectly but both of the graph is empty, showing empty data, its like couldn't recognize the data of the input somehow Wall
(Jul-03-2018, 04:17 PM)Alfalfa Wrote: [ -> ]Hi there,
I'm not sure about how you want to use it in your code, but QTimer are very easy. Like I shown you, you can simply init it in the __init__ of your gui loop (SoundApp), then use self.timer to manipulate it if needed. You don't need to init a new singleshot timer recursively like you've done (line 36). Single shots timers are.. for single shot. They fire only once. If you want to repeat an action every few seconds, simply init a QTimer with an interval, then start it and it will loop forever.

It does looping forever until I terminate the graphs window, then they stop.
But if I want to assign this Qtimer to show some frequency data as Label Text in my QtDesigner window, then how I can do that?

I do have a reference code on how to calculate the frequency data for marking some notes, and it's helpful to finalizing this project
import pyaudio
import os
import struct
import numpy as np
import matplotlib.pyplot as plt
import time
from time import sleep
 
%matplotlib tk
 
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()
I already have a satisfying result's but the one last thing left is only to show this data on the QtDesigner window wether it's on the same window with the graphs or standalone
(Jul-07-2018, 02:44 AM)mekha Wrote: [ -> ]It does looping forever until I terminate the graphs window, then they stop.
But if I want to assign this Qtimer to show some frequency data as Label Text in my QtDesigner window, then how I can do that?

I'm not sure to understand, are you asking about how to use the QTimer to refresh the text of a QLabel?

If so, simply add something like
self.ui.yourLabel.setText("frequency data")
into the function connected to your timer..

The purpose of QtDesigner is to organize your widget, but the dynamic content is rather set by using python.
Pages: 1 2