How to determine frequency dependant amplitude with FFT

51 Views Asked by At

I'm trying to measure and calculate the frequency response of an audio device by simply generating input signals and measuring the output with a frequency sweep.

Here's what I'm trying to achieve in pseudo code:

for each frequency f in (10-20k):
    generate reference signal s with frequency f
    async play s and record result r
    determine amplitude a of r using FFT
    add tuple (f,a) to result

And in python:

import numpy as np
from matplotlib import pyplot as plt
import sounddevice as sd
import wave
from math import log10, ceil
from scipy.fft import fft, rfft, rfftfreq, fftfreq

SAMPLE_RATE = 44100     # Hertz
DURATION = 3            # Seconds

def generate_sine_wave(freq, sample_rate, duration):
    x = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)
    frequencies = x * freq
    y = np.sin((2 * np.pi) * frequencies)
    return x, y

def main():
    # info about our device
    print(sd.query_devices(device="Studio 24c"))

    # default device settings
    sd.default.device = 'Studio 24c'
    sd.default.samplerate = SAMPLE_RATE

    f_start = 10
    f_end = 20000
    samples_per_decade = 10
    ndecades = ceil(log10(f_end) - log10(f_start))
    npoints = ndecades * samples_per_decade
    freqs = np.logspace(log10(f_start), log10(f_end), num=npoints, endpoint=True, base=10)
    
    measure_duration = 0.25 # seconds
    peaks = []
    for f in freqs:
        _, y = generate_sine_wave(f, SAMPLE_RATE, measure_duration)
        rec = sd.playrec(y, SAMPLE_RATE, input_mapping=[2], output_mapping=[2])
        sd.wait()
        yf = np.fft.rfft(rec, axis=0)
        yf_abs = 1 / rec.size * np.abs(yf)
        xf = np.fft.rfftfreq(rec.size, d=1./SAMPLE_RATE)
        peaks.append(np.max(yf_abs))

    plt.xscale("log")
    plt.scatter(freqs,peaks)
    plt.grid()
    plt.show()

if __name__ == "__main__":
    main()

Before measuring the actual device, I simply looped the output signal to the input of my audio interface to basically "calibrate" what I'm doing. I was expecting the amplitude to be equal across all frequencies. However, this is what I got:

enter image description here

Why are all the amplitudes all over the place even though the generated sine wave is the same? I didn't change anything on my audio interface during the sweep measuring phase.

1

There are 1 best solutions below

1
thomas.cloud On BEST ANSWER

Those differences seem relatively small to me (~1.0E-5 min, 1.6E-5 max). You are getting really close to the Nyquist frequency of 22.05 kHz at the upper end of your frequency range too, so I imagine only having about 2 samples per sine wave cycle would inherently result in an inaccurate Fourier transform.

I know you are looking at an audio system where 44.1 kHz is a common sampling frequency (e.g. CDs) but maybe try bumping your sampling frequency to 500 kHz as a test to see if this gives you more consistent rates. It would be more of a pain, but you could also try sampling over n complete cycles for each frequency. I.e. for 10 Hz, sample for 1 second to get 10 cycles, for 1000 Hz, sample for 10 mSecs to get 10 cycles. I think this would help your fundamental frequency line up so you would only ever get the imaginary portion of the Fourier transform so you can do more of an apples-to-apples comparison. Otherwise you will be clipping the sine wave mid-cycle in many cases & introducing real Fourier transform components. Also, since you are not using any windowing you will get increased frequency components outside of your expected frequency. I know you are taking the absolute value, so this should not make the imperfect sine wave cycle count much of an issue, but maybe just try it while testing to see why you are not getting a flatter frequency response like you are expecting.