0

I'm trying to configure AutoEq for SuperEQ. The latter was originally a Winamp DSP, but now it's used in foobar2000 and DeaDBeeF.

AutoEq requires a list of central frequencies and Q-factors for equaliser bands. SuperEQ's cut-off frequencies can be found in the source:

#define NBANDS 17
static REAL bands[NBANDS] = {
    65.406392,92.498606,130.81278,184.99721,261.62557,369.99442,523.25113,
    739.9884 ,1046.5023,1479.9768,2093.0045,2959.9536,4186.0091,5919.9072,
    8372.0181,11839.814,16744.036
};

For the middle bands, central frequencies computed as geometric means sqrt(bands[i + 1] * bands[i]) do match the values displayed in GUI:

"55 Hz",
"77 Hz",
"110 Hz",
"156 Hz",
"220 Hz",
"311 Hz",
"440 Hz",
"622 Hz",
"880 Hz",
"1.2 kHz",
"1.8 kHz",
"2.5 kHz",
"3.5 kHz",
"5 kHz",
"7 kHz",
"10 kHz",
"14 kHz",
"20 kHz"

Q-factor for the middle bands sqrt(bands[i + 1] * bands[i]) / (bands[i + 1] - bands[i]) is 2.871.

The lowest and the highest bands, however, are problematic. Cut-off for the lowest band is 0, for the highest band it is the sampling rate (source):

(*pp)->lower = i == 0      ?  0 : bands[i-1];
(*pp)->upper = i == NBANDS ? fs : bands[i  ];

Geometric mean of 0 is zero, yet the central frequency of the lowest band is claimed to be 55 Hz in GUI. For the highest band, for a typical sampling rate of 44100, sqrt(16744.036 * 44100) = 27173.73709, which matches neither 20 kHz in DeaDBeeF's GUI, nor 19912 in Shibatch's original DSP.

Are there some different formulas for computing the central frequencies that would've reproduced 55 Hz and 20 kHz?

UPDATE: According to AviSynth docs, SuperEQ uses 16383th order FIR filters using FFT.

Also, it seems that only frequencies up to half of the sampling rate are actually used (makes sense considering the Nyquist–Shannon theorem):

for(e=param2.elm->next;e->next != NULL && e->upper < fs/2;e = e->next)
  • Hmm... The lowest and the highest bands, however, are problematic. Cut-off for the lowest band is 0, for the highest band it is the sampling rate (source) . Probably you read wrong the conditional statements there ... in C/C++ array index starts from 0 ... so, 0 points there to index 0 and NBANDS to index 16 (i.e. bands[0] = 65.406392Hz and NBANDS points to bands[16] = 16744.036Hz ) ... can't break these limits without error in compilation. https://www.tutorialspoint.com/cplusplus/cpp_conditional_operator.htm – Juha P Jul 07 '23 at 10:39
  • Sorry, but I think I've got it correctly. (**pp)->lower is initialised from bands[i - 1], i.e. bands[-1] for i = 0. Such an out-of-bounds access is prevented by a ternary i == 0 ? 0 : bands[i - 1]. bands[0] is set as a lower bound for i = 1, which is the second element of an array, not the first one.

    #define NBANDS 17, not 16, and bands[NBANDS] is an out-of-bounds access, which is prevented by another ternary i == NBANDS ? fs : bands[i]

    – Nil Admirari Jul 07 '23 at 12:09
  • Peaking filter: BW = Fcenter / Q. Fcutoff (-3dB point) = Fcenter ± BW/2. – Juha P Jul 07 '23 at 19:46
  • I'm actually trying to compute the Q-factor using the formula that you've provided. The problem is that for the lowest and the highest bands, neither Fcenter nor the bandwidth are known. However, thanks for -3 dB point: may be it makes sense trying to find such a point experimentally by passing white noise through SuperEQ and plotting the spectrogram. – Nil Admirari Jul 09 '23 at 09:24
  • BW values in Hz can be calculated from table given at http://avisynth.nl/index.php/SuperEQ . Fcenter values are those showed in GUI. By those table entries if you use 24000Hz as the last f (high) value, your equation results 20046Hz as center frequency. 1st center value is probably calculated using ~45 for f (low). I calculate BW directly from Q value @band center frequency (BW=55/2.871 = 20 .. 55+20/2=65) but, if you calculate Q using BW from table data you'll get variable Q (variates in range (~2.75; ~2.93). – Juha P Jul 09 '23 at 10:01
  • Some explanation regarding Q and bandwidth (BW) relation. http://www.sengpielaudio.com/calculator-bandwidth.htm Also, AutoEq exports suitable format file for say GraphicEQ (example: EqualizerAPO component) which supports arbitrary curves ... probably you can make responses close to SuperEQ curves but I'm not sure if the resulting EQ is as good (clean) as what SuperEQ gives. – Juha P Jul 09 '23 at 12:27
  • “The Center frequency f0 is the geometric mean of f1 and f2.” The raison d'être of this entire question: what if f1 = 0? – Nil Admirari Jul 10 '23 at 09:17
  • GraphicEQ, EqualizerAPO etc.: I want to use Foobar Mobile on iOS, which has no way of getting a decent equaliser. When it comes to desktops, Foobar has both a convolver and a standard 31-band equaliser plugins, allowing one to avoid dealing with the default SuperEQ. – Nil Admirari Jul 10 '23 at 09:23
  • AutoEq exports EQ-data for EqualizerAPO's GraphicEQ (convolver) as a command line. File holds gain values for about 128 frequency points. You can use this data to set the EQ for SuperEQ manually (or make a script that collects those needed values and writes the preset file for SuperEQ). https://github.com/jaakkopasanen/AutoEq/tree/master/results/Headphone.com%20Legacy – Juha P Jul 10 '23 at 10:43
  • If you're making headphone EQ for mobile foobar (build-in EQ) then do you actually need to know other data than band center frequencies? AFAIK, foobar2k preset file holds only gain values for these 18 bands and values are in integer format (SuperEQ may use different format but is said to support foobar preset files). There is a csv file in each AutoEq results folder you'll find all data used for response plot. Use db value for each center frequency from fixed_band_eq column. If you're writing an extension for AutoEq then forget this post. – Juha P Jul 10 '23 at 21:24
  • AutoEq provides results for standard 10-band, 31-band etc. equalisers. For non-standard equalisers results must be recomputed. Central frequencies aren't sufficient to minimise the difference with the target: Q-factors are needed to determine how frequencies around a peak are affected. foobar2k presets map freq -> gain, AutoEq consumes freq -> Q and produces freq -> gain. – Nil Admirari Jul 11 '23 at 16:29
  • I made little change in code to export foobar2k preset. Band gains was calculated using normal peaking filter with Q=2.871 and I also calculated compensation curve between filter types (peaking vs bar). Here's plot (orange is foobar2k preset response): AKGK240Studio-FBKpreset response.png (7 4 0 -2 -3 -2 -2 -0 -3 1 5 -4 4 4 -2 -1 7 -13 – Juha P Jul 11 '23 at 21:08
  • Maybe easiest way to get SuperEQ preset done is to use data which is used to write .csv file. I did try few methods for the job and the latest addition implements this suggestion. https://dsp.stackexchange.com/questions/88850/matching-two-different-type-of-equalizer-responses – Juha P Aug 03 '23 at 19:43
  • I've opened a pull request in AutoEq repo quite a while ago: https://github.com/jaakkopasanen/AutoEq/pull/679/files. It assumes that the lowest and the highest filters are shelf filters, and the rest are peaking. I haven't really investigated SuperEQ implementation, and how it's compatible with the way AutoEQ does it's normalisation; if you did – maybe it's worthwhile to comment in that pull request? – Nil Admirari Aug 04 '23 at 10:18
  • That way you get gain values for 18 filters but, not the combined response data which actually is the important data which should follow the target response (see that response plot behind my link (separate filter responses drawn with green solid lines vs their combined response drawn with dashed red). Filters used in SuperEQ do not have as much effect on neighboring bands (each AutoEq's IIR filter effects in full frequency range when each SuperEQ filter effects in its bands bandwidth range (as seen in same plot)). – Juha P Aug 04 '23 at 21:07
  • Do you know how exactly AutoEq should be modified to account for SuperEQ? Apparently there's gotta be a new subclass of a ShelfFilter, in addition to already existing HighShelf and LowShelf. Do you know how biquad_coefficients for such a filter should look like? – Nil Admirari Aug 04 '23 at 21:33
  • Best result would be got by implementing SuperEQ in AutoEq. – Juha P Aug 05 '23 at 05:10

2 Answers2

0

Apparently 55 Hz and 19912 Hz labels in the GUI were obtained by dividing next/multiplying previous centre by $\sqrt 2$ (which is correct for the middle bands), without paying any attention to the fact that it doesn't reflect the actual implementation.

Equalisation in the lowest band has no definite peak (see below); thus computing bandwidth at -3 dB of the peak is not gonna work. Seems like the centre frequency have to be computed as 65.406392 / 2, and not as a geometric mean, resulting in a Q-factor of 0.5.

When it comes to experimentation, I've downloaded white noise from https://www.audiocheck.net/testtones_whitenoise.php, and passed it through Foobar's converter with +10 dB equalisation.

from dataclasses import dataclass

import numpy as np import matplotlib.pyplot as plt from scipy.io import wavfile from scipy.signal import savgol_filter, spectrogram

@dataclass class WavData: rate: int pcm: np.ndarray freqs: np.ndarray spectrum: np.ndarray

def read_wav(wav_path: str) -> WavData: rate, wav = wavfile.read(wav_path) freqs, _, spectrum = spectrogram(wav, rate, window=np.ones(wav.size), mode='magnitude') return WavData(rate, wav, freqs, spectrum.ravel())

orig = read_wav('./audiocheck.net_gaussian_whitenoise.wav') gauss_low = read_wav('./Gaussian 55 10.wav') gauss_high = read_wav('./Gaussian 20k 10.wav')

fig, axs = plt.subplots(3, 2) for i, (title, wav) in enumerate([ ('original', orig), ('55 Hz\n+10 dB', gauss_low), ('20 kHz\n+10 dB', gauss_high) ]): axs[i][0].set_ylabel(title) axs[i][0].plot(wav.freqs, wav.spectrum) axs[i][1].semilogx(wav.freqs, wav.spectrum) fig.tight_layout()

enter image description here

gauss_low_diff = gauss_low.spectrum - orig.spectrum
gauss_high_diff = gauss_high.spectrum - orig.spectrum

fig, axs = plt.subplots(4, 2) for i, (eq, wav, diff, slc, savgol_win) in enumerate([ ('55 Hz\n+10 dB', gauss_low, gauss_low_diff, slice(None, 1000), 100), ('20 kHz\n+10 dB', gauss_high, gauss_high_diff, slice(160000, None), 1000) ]): for row, title, data in [ (2 * i, eq, diff[slc]), (2 * i + 1, f'smooth\n{eq}', savgol_filter(diff[slc], savgol_win, 3)) ]: axs[row][0].set_ylabel(title) axs[row][0].plot(wav.freqs[slc], data) axs[row][1].semilogx(wav.freqs[slc], data) fig.tight_layout()

enter image description here

0

Ok, I looked around AutoEq code, and found out that the filter with zero cut-off is called low-shelf filter: main.py#L43.

Searching for “Q-factor for low-shelf filter” leads to Calculate Q factor of a Low Shelf and High Shelf filter on this very site. This question can be closed as a duplicate.

Formulas for shelf filters can be found at https://www.w3.org/TR/audio-eq-cookbook/:

$$ \frac 1 Q = \sqrt{\left(A + \frac 1 A\right)\left(\frac 1 S - 1\right) + 2} $$

Slope for SuperEQ is unknown, but it was suggested to always use 1, which results in $Q = \sqrt 2$. There is already a number of equalisers with such a value in AutoEq database:

'8_PEAKING_WITH_SHELVES': {
    ...
    'filters': [{
        'type': 'LOW_SHELF',
        'fc': 105.0,
        'q': 0.7
    },
    ...
}