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:
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.

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.