0

I have some function for sound processing/ sound processing. And before it was all a single channel. But know i make it less or more multi channel. At this point i have the feeling i do part of the scrips over and over again.

In this example it are two functions(my original function is longer) but the same happens also in single scripts.

my Two functions
import numpy as np


# def FFT(x, fs, *args, **kwargs):
def FFT(x, fs, output='complex'):
    from scipy.fftpack import fft, fftfreq
    N = len(x)
    X = fft(x) / N
    if output is 'complex':
        F = np.linspace(0, N) / (N / fs)
        return(F, X, [])
    elif output is 'ReIm':
        F = np.linspace(0, N) / (N / fs)
        RE = np.real(X)
        IM = np.imag(X)
        return(F, RE, IM)
    elif output is 'AmPh0':
        F = np.linspace(0, (N-1)/2, N/2)
        F = F/(N/fs)
        # N should be int becouse of nfft
        half_spec = np.int(N / 2)
        AMP = abs(X[0:half_spec])
        PHI = np.arctan(np.real(X[0:half_spec]) / np.imag(X[0:half_spec]))
        return(F, AMP, PHI)
    elif output is 'AmPh':
        half_spec = np.int(N / 2)
        F = np.linspace(1, (N-1)/2, N/2 - 1)
        F = F/(N/fs)
        AMP = abs(X[1:half_spec])
        PHI = np.arctan(np.real(X[1:half_spec])/np.imag(X[1:half_spec]))
        return(F, AMP, PHI)


def mFFT(x, fs, spectrum='complex'):
    fft_shape = np.shape(x)
    if len(fft_shape) == 1:
        mF, mX1, mX2 = FFT(x, fs, spectrum)
    elif len(fft_shape) == 2:
        if fft_shape[0] < fft_shape[1]:
            pass
        elif fft_shape[0] > fft_shape[1]:
            x = x.T
            fft_shape = np.shape(x)
        mF = mX1 = mX2 = []
        for channel in range(fft_shape[0]):
            si_mF, si_mX1, si_mX2 = FFT(x[channel], fs, spectrum)
            if channel == 0:
                mF = np.append(mF, si_mF)
                mX1 = np.append(mX1, si_mX1)
                mX2 = np.append(mX2, si_mX2)
            else:
                mF = np.vstack((mF, si_mF))
                mX1 = np.vstack((mX1, si_mX1))
                if si_mX2 == []:
                    pass
                else:
                    mX2 = np.vstack((mX2, si_mX2))
    elif len(fft_shape) > 2:
        raise ValueError("Shape of input can't be greather than 2")
    return(mF, mX1, mX2)

The second funcion in this case have the problem.
The reason for this checks is best to understand with an example:

I have recorded a sample of 1 second of audio data with 4 microphones. so i have an ndim array of 4 x 44100 samples. The FFT works on every even length array. This means that i get an result in both situations (4 x 44100 and 44100 x 4).
For all function after this function i have also 2 data types. or a complex signal or an tuple of two signals (amplitude and phase)... what's create an extra switch/ check in the script.

  • check type (tuple or complex data)
  • check direction (ad change it)
  • Check size / shape
  • run function and append/ stack this

Are there some methods to make this less repeative i have this situation in at least 10 functions...

Jan-Bert
  • 921
  • 4
  • 13
  • 22

1 Answers1

0

Bert,

The problematic I'm understanding is the repeat of the calls you're making to do checks of all sorts. I'm not understanding all but I'm guessing they are made to format your data in a way you'll be able to execute fft on it.

One of the philosophy about computer programming in Python is "It's easier to ask forgiveness than it is to get permission."[1] . This means, you should probably try first and then ask forgiveness (try, except). It's much faster to do it this way then to do a lots of checks on the value. Also, those who are going to use your program should understand how it works pretty easily; make it easy to read without those check by separating the logic business from the technical logic. Don't worry, it's not evident and the fact you're asking is an indicator you're catching something isn't right :).

Here is what I would propose for your case (and it's not the perfect solution!):

def mFFT(x, fs, spectrum='complex'):
    #Assume we're correcty align when receiving the data
    #:param: x assume that we're multi-channel in the format [channel X soundtrack ]
    #also, don't do this:
    #mF = mX1 = si_mX2 = []
    # see why : https://stackoverflow.com/questions/2402646/python-initializing-multiple-lists-line
    mF = []
    mX1 = []
    mX2 = []
    try:
        for channel in range(len(x)):
            si_mF, si_mX1, si_mX2 = FFT(x[channel], fs, spectrum)
            mF.append(si_mF)
            mX1.append(si_mX1)
            mX2.append(si_mX2)
        return (mF, mX1, mX2)
    except:
        #this is where you would try to point out why it could have failed.  One good you had was the check for the orientation of the data and try again;
        if np.shape(x)[0] > np.shape(x)[1]:
             result = mFFT(x.T,fs,spectrum)
             return result
        else :
             if np.shape(x)[0] > 2:
                 raise(ValueError("Shape of input isn't supported for greather than 2"))

I gave an example because I believe you expected one, but I'm not giving the perfect answer away ;). The problematic you have is a design problematic and no, there are no easy solution. What I propose to you is to start by assuming that the order is always in this format [ n-th channel X sample size ] (i.e. [ 4 channel X 44100 sample]). That way, you try it out first like this(as in try/except), then maybe as the inverse order.

Another suggestion (and it really depends on your use case), would be to make a data structure class that would manipulate the FFT data to return the complex or the ReIm or the AmPh0 or the AmPh as getters. (so you treat the input data as to be always time and you just give what the users want).

    class FFT(object):
       def __init__(self,x, fs):
         from scipy.fftpack import fft, fftfreq
         self.N = len(x)
         self.fs = fs
         self.X = fft(x) / N

       def get_complex(self):
         F = np.linspace(0, self.N) / (self.N / self.fs)
         return(F, self.X, [])

       def get_ReIm(self):
         F = np.linspace(0, self.N) / (self.N / self.fs)
         RE,IM = np.real(self.X), np.imag(self.X)
         return(F, RE, IM)

       def get_AmPh0(self):
         F = np.linspace(0, (self.N-1)/2, self.N/2)/(self.N/self.fs)
         # N should be int because of nfft
         half_spec = np.int(self.N / 2)
         AMP = abs(self.X[:half_spec])
         PHI = np.arctan(np.real(self.X[:half_spec]) / np.imag(self.X[:half_spec]))
         return(F, AMP, PHI)

This can then be used to be called depending on the desired output from another class with an eval to get the desire output (but you require to use the same convention across your code ;) ). 2

Community
  • 1
  • 1
Eric L.
  • 89
  • 7
  • I add an example in my question why i think you're example don't work. It was to much to explain it in a comment – Jan-Bert Oct 18 '16 at 06:27
  • Hi, I've modified my answer to include another suggestion that may be more adapted to what you want – Eric L. Oct 18 '16 at 13:56