0
$\begingroup$

I'm developing an audio file spectrum analyzer for a University Project. My main goal is to have an application that plots the Db Spectrum of a 16 Bit WAV PCM audio file (at this time only mono files) in real time and my developing environment is Fedora Workstation 29.

The computational work is done in C++ using the following libraries:

  • libsndfile for opening and reading the wav file
  • libportaudio for audio playback
  • FFTW3 for performing the FFT of each chunk of audio stream
  • libboost for IPC

The result of the FFT is then exposed to a Python script through an array of the following struct using a shared memory object (libboost' shared memory object and mapped region).

typedef struct FFTW_Point {
    int x_val;
    float y_val;
} FFTW_Point;

Plotting is done with Python and PyQtGraph, grabbing the data to be plotted from shared memory.

Everything is fine when plotting just "pure" signals like sine or triangle waves sweeping over time, simple guitar tones or percussion samples. frame of a sine sweep plot Triangle wave at about 220 Hz Plotting of a Guitar riff

The problem is that when I try to plot a "real" song i get a strange behavior in the 15 kHz - 20 kHz range, something like an energy cut and it looks really weird

Song plot with defects

The following code snippets show how I do the critical parts of the task:

Portaudio Callback:

static int pa_callback(
    const void                     *input,
    void                           *output,
    unsigned long                   frameCount,
    const PaStreamCallbackTimeInfo *timeInfo,
    PaStreamCallbackFlags           statusFlags,
    void                           *userData
) {
    float           *out;
    snd_data *p_data = (snd_data*)userData;
    sf_count_t       num_read;    

    out = (float*)output;
    p_data = (snd_data*)userData;

    memset(out, 0, sizeof(float) * frameCount * p_data->info.channels);    
    num_read = sf_read_float(p_data->file, out, frameCount * p_data->info.channels);        

    if ((long unsigned int) num_read < frameCount)
    {   
        fftw->setNumSamples(num_read);
        fftw->loadInputData(out);
        //fftw->writeWav();
        return paComplete;
    }
    fftw->loadInputData(out);
    return paContinue;
}

FFTW of audio chunks

inline static double hannWindow(float value, int i, unsigned int size) {
    return value * (0.5 * (1 - cos(2*M_PI*i/(size))));
}

void FFTW_Tool::loadInputData(float* buffer) { 
    for (size_t i = 0; i < numSamples; ++i) {
        in[i][0] = hannWindow(buffer[i], i, numSamples);
        in[i][1] = 0;        
    }
    StartFFTW();    
}

void FFTW_Tool::executePlan(const unsigned int numSamples,
    const int direction,
    const unsigned int calculationType) {    

    fftw_plan plan = fftw_plan_dft_1d(
        numSamples,
        in,
        out,
        direction,
        calculationType
    );

    fftw_execute(plan);
    //shm_region.flush();
    //outputSize = numSamples/2        
    for (size_t i = 0; i < outputSize; ++i) { 
        sharedStruct[i].x_val = i * samplingFreq / numSamples;
        auto real = out[i][0];
        auto img = out[i][1];
        auto mag = complexModule(real, img);
        auto amp = (2*mag)/numSamples;                     
        sharedStruct[i].y_val = amp;
    }

    fftw_destroy_plan(plan);    
    std::memcpy(shm_region.get_address(), sharedStruct, (sizeof(FFTW_Point) * outputSize));

}

PyQtGraph update function:

def update(self):
        shmem = mmap.mmap(fd, 0, access=mmap.ACCESS_READ)        
        xs = []
        ys = []                
        for i1, i2, i3, i4, f1, f2, f3, f4 in zip(*[iter(shmem)]*8):
            x_val = struct.unpack('i', i1+i2+i3+i4)[0]
            y_val = struct.unpack('f', f1+f2+f3+f4)[0]              
            xs.append(x_val)
            ys.append(20*np.log10(y_val))                       
            #ys.append(y_val)
        self.set_plotdata(name='spectrum', data_x=xs, data_y=ys)

I really don't know why I get this strange behavior, maybe it is about wrong windowing or maybe it is about the shared memory mechanism and floating point values interpretation.

What bugs me the most is that it should always display wrong data in that frequency range but this happens only with complex audio signals.

$\endgroup$
0
$\begingroup$

Just guessing here: It's quite possible that this is in the content. That typically happens if the file was at one time encoded as an MP3/AAC/Ogg/Opus etc. Did you get it by actually ripping it from a CD or is this a file you recorded yourself or bought from the internet ?

What's more suspicious is the continuing rise from 16 kHz to 20 kHz even after the drop. That shouldn't be there unless there is some noise in the system. Finally, you probably want to smooth or group the frequency in a logarithmic scale.The raw FFT spectrum get's really dense at higher frequencies and tends to be visually dominated by the biggest line in the neighborhood which isn't exactly what's happening.

$\endgroup$
  • $\begingroup$ You could be right and i feel a bit stupid not having thoght about it by myself. The image above is from a wav taken from internet, trying with a track produced by my self the drop is gone but it has a really steep curve probably caused by the Hann windowing and it is acceptable but not optimal i guess. This is a frame of that track: imgur.com/a/H6EIUzQ $\endgroup$ – ResonantFilter May 15 at 11:22
  • $\begingroup$ so, @ResonantFilter can you modify your images to all be based on "known to be good" signals? $\endgroup$ – Marcus Müller May 15 at 22:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.