3
$\begingroup$

I have a sound file with a computed spectrogram from 0 to 8000 Hz (spanning about 5 octaves). The spectrogram has 128 logarithmically spaced frequency bins. How can I reduce the number of bins so that I keep an equal bandwidth in octaves? Can I average adjacent bins?

As an example, let's say I have 128 bandpass filters which are spaced equally along a log frequency axis, with center frequencies 180-7040 Hz spanning 5.3 octaves. How can I reduce this to 42 bins with equal bandwidth in octaves? Matlab or python examples are welcome.

$\endgroup$
  • $\begingroup$ Don't you want the maximum of several bins, not the average? $\endgroup$ – endolith Oct 13 '17 at 16:15
1
$\begingroup$

One possibility is use something similar to an MFCC Mel triangular filterbank with your FFT input vector, but using log spacing instead of Mel spacing for the triangular filter centers. See: http://cmusphinx.sourceforge.net/doc/sphinx4/edu/cmu/sphinx/frontend/frequencywarp/MelFrequencyFilterBank.html and https://www.mathworks.com/matlabcentral/answers/195651-creating-mel-triangular-filters-function, and modify the triangle frequencies as needed.

$\endgroup$
  • $\begingroup$ So you're saying instead of averaging I should just filter the sound with the triangular filterbank? I suppose this is just a convolution of the two vectors, right? $\endgroup$ – wiggalicious Feb 14 '17 at 9:29
  • $\begingroup$ Does filtering really reduce the number of bins? $\endgroup$ – wiggalicious Feb 14 '17 at 10:34
  • $\begingroup$ Don't filter in the time domain. Just multiply by each triangle in your bank in the frequency domain. Use as few triangles as you need for result elements. $\endgroup$ – hotpaw2 Feb 15 '17 at 4:33
  • $\begingroup$ I suppose this is the easiest approach, thanks. I'll try it out and let you know how it went. $\endgroup$ – wiggalicious Feb 15 '17 at 13:24
  • $\begingroup$ Note that Sinc interpolation in the frequency domain will work for those portions of the log spectrum needed that are closer together than the FFT bins from which you will be interpolating. $\endgroup$ – hotpaw2 Feb 15 '17 at 13:46
1
$\begingroup$

I suggest you perform an interpolation of the spectrum in the linear domain, according to the code below:

from scipy import interpolate

f_lin = np.arange(180, 7040) # the range of test frequencies
# a function that we use to test the interpolation
H_lin_func = lambda f: np.sin(2*np.pi*0.001*f) * np.cos(2*np.pi*0.00011*f)


# Frequencies of the measured samples
input_samples = np.logspace(np.log10(180), np.log10(7040), 128)

# Frequencies where we want to interpolate to
output_samples = np.logspace(np.log10(180), np.log10(7040), 42)

# "measure" the function at the 128 known points
H_at_input_samples = H_lin_func(input_samples)

# Interpolate to the 42 points
interpolationFunc = interpolate.interp1d(input_samples, H_at_input_samples)
H_at_output_samples = interpolationFunc(output_samples)

# plot the results
plt.figure(figsize=(10,6))
plt.plot(f_lin, H_lin_func(f_lin), label='Original function')
plt.plot(input_samples, H_at_input_samples, 'rx', label='Input samples')
plt.plot(output_samples, H_at_output_samples, 'go', label='Output samples')
plt.grid(True)
plt.legend()

In the code, I present an (arbitrary) function H_lin_func, which represents the original spectrum we have sampled. Then, we create an interpolation function (e.g. using scipy.interpolate) with the measured samples. Then, finally we call the interpolation function with the samples where we want to know the interpolated values.

enter image description here As you see, there are more samples at the lower frequencies, due to the log-spacing. However, also for the wider spacing at higher frequencies the interpolation works fine.

EDIT for as a comment from hotpaw2: This interpolation to fewer samples is essentially a downsampling operation, which is vulnerable to aliasing. In "normal" systems, aliasing is not a problem, if the sampling frequency after downsampling is still 2 times the bandwith of the original signal. However, you have a special case that your samples are not equally spaced, but log-spaced. So, I would say your spectrum needs to be band-limited in the log domain to prevent aliasing. If this is not the case you'd need to apply an anti-aliasing filter in the log domain and then interpolate/downsample. More info about aliasing in general can be found in this question and this article.

$\endgroup$
  • $\begingroup$ An interpolation function won't meet the requirements unless the bandwidth of the interpolation continuously changes with frequency. $\endgroup$ – hotpaw2 Feb 15 '17 at 4:39
  • $\begingroup$ @hotpaw2 What do you mean with "bandwidth of interpolation"? Here, I do not refer to an interpolation filter, but perform linear/cubic/whatever interpolation between the samples of the spectrum. Right, these samples are not evenly spaced, but the interpolation still works, as the green points are quite closely at on the blue curve. $\endgroup$ – Maximilian Matthé Feb 15 '17 at 6:14
  • $\begingroup$ If you downsample, an interpolation that does not low-pass filter will cause aliasing. Even a cubic regression may have to span a lot more than the nearest 3 points. $\endgroup$ – hotpaw2 Feb 15 '17 at 6:18
  • $\begingroup$ OK, I see what you mean. I wanted to express this problem with my last sentence ("as long as the original function does not change too quickly"). I will make it more clear that the original function needs to be bandlimited to the bin distance to not have aliasing. $\endgroup$ – Maximilian Matthé Feb 15 '17 at 6:22
  • $\begingroup$ another downvote? Would be interested what the critics is specifically. $\endgroup$ – Maximilian Matthé Feb 15 '17 at 9:10

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.