Headphone FIR equalization using Foobar

Discussion in 'Headphones' started by ultrabike, Sep 10, 2016.

  1. Serious

    Serious Inquisitive Frequency Response Plot

    Pyrate BWC MZR
    Joined:
    Sep 28, 2015
    Likes Received:
    2,594
    Trophy Points:
    113
    Location:
    near Munich, Germany
    Oh, I didn't really EQ, but if I would, I'd just use the JRiver parametric EQ. I mean, I tried some stuff at some point but always went back to no EQ.

    Yep, that's also what I think. The curve Tyll measured seems U-shaped with a ton of bass to me. The B&K target seems very good to me, but some listening situations might need slightly different targets, for example a BBC dip might make it sound more realistic depending on a number of factors.
     
  2. Dreyka

    Dreyka Acquaintance

    Banned
    Joined:
    May 5, 2016
    Likes Received:
    38
    Trophy Points:
    18
    From the innerfidelity article:
    • The standard Reference Room (RR) curve (red) used in the 2013 Harman headphone study was developed using double-blind preference tests by trained listeners. A very similar curve for the M2 (green) was loaded in the speaker DSP for Tyll's head simulator tests.
    • An alternative curve RR1 (violet) was also measured in the 2013 Harman study, described as "...generally preferred based on a previous study where different room correction systems with different target response curves were evaluated by trained listeners." Curve RR1 is similar to a 1 dB/octave slope (orange).
    The trends suggest that speakers like the Revel F208 and M2 will tend to naturally follow curve RR1 and 1 dB/octave slope within the limits of their bass extension when placed in a good room, discounting the bass irregularities Toole attributes to standing waves.
     
  3. spoony

    spoony Spooky

    Pyrate
    Joined:
    Sep 28, 2015
    Likes Received:
    651
    Trophy Points:
    93
    Can you share the latest revision? I want to try this!
     
  4. ultrabike

    ultrabike Measurbator - Admin

    Staff Member Pyrate MZR
    Joined:
    Sep 25, 2015
    Likes Received:
    8,960
    Trophy Points:
    113
    Location:
    Irvine CA
    Sure.

    This is the B&K function:

    Code:
    function [b] = b_k()
    
    % paramters
    fs = 44.1e3;
    fn = fs/2;
    n = 450;
    
    % target response
    s = -6/(log10(20000)-log10(50));
    r = log10(50):0.001:log10(fn);
    p = s.*(r - log10(50));
    f = 10.^r;
    bk_t = [[0 f]; -[0 p]].';
    
    % approximation
    b = firls(n, bk_t(:,1).'/fn, 10.^(-bk_t(:,2).'/20.0));
    
    % check
    figure;
    [h, w] = freqz(b,1,2^15,fs);
    semilogx(w(2:end), 20*log10(abs(h(2:end))));
    grid on;
    axis([5 20000 -23 17]);
    
    And this is the latest script using the B&K function:

    Code:
    % Load REW impulse responses and validate
    [h, fs, nbits] = wavread('hd600_IR_4.wav');
    H_left = freqz(h(:,1),1,length(h(:,1)));
    H_right = freqz(h(:,2),1,length(h(:,2)));
    step = (22050-5)/length(h);
    f = 5:step:22050;f=f(1:end-1);
    figure;
    subplot(2,1,1);
    semilogx(f,20*log10(abs(H_left))+90);
    grid on;
    axis([20 20000 65 110]);
    subplot(2,1,2);
    semilogx(f,20*log10(abs(H_right))+90);
    grid on;
    axis([20 20000 65 110]);
    
    % hd600 impulse response sample to learn (and invert)
    MatchSize = 3000;
    PreCursor = 100;
    MaxIndex = find(h(:,1) == max(h(:,1)));
    hn = h(MaxIndex-PreCursor:MatchSize+MaxIndex-PreCursor,:);
    figure;
    subplot(2,1,1);
    plot(hn(:,1));
    grid on;
    axis tight;
    subplot(2,1,2);
    plot(hn(:,2));
    grid on;
    axis tight;
    
    % Perform LMS (least-mean-squares) on each channel and derive
    % optimized filters (c)
    N = 200000;                       % iterations
    taps = 2000;                   % number of equalizer taps
    mu = [0.0001 0.00001];  % adaptation (learning) gains
    gu = 1-0.0000001;             % lossy gain
    inDly = 1000;                   % delay amount for input samples
    Main = 900;
    i = randn(2,N);                 % input samples
    o = zeros(2,N);                 % output samples
    e = zeros(2,N);                 % error
    id = zeros(2,N);             % delayed input samples
    d = zeros(2,taps);            % adaptive filter delay line
    c = zeros(2,taps);            % filter coefficients
    c(:,Main) = 1;                    % filter center tap initialization
    y = zeros(size(i));
    for k = 1:2
      y(k,:) = filter(hn(:,k),1,i(k,:)); % convolve with hd600 response (channel to equalize)
      id(k,:) = filter([zeros(1,inDly) 1], 1, i(k,:)); % delayed input
    end
    for n = 1:N
        if n < N/2                   % change adaptation gear
            mup = mu(1);
        else
            mup = mu(2);
        end
      for k = 1:2
         d(k,:) = [y(k,n) d(k,1:end-1)];         % adaptive filter delay line update
         o(k,n) = d(k,:)*c(k,:).';                     % apply adaptive filter (filtering)
         e(k,n) = o(k,n) - id(k,n);               % error
         c(k,:) = gu*c(k,:) - mup*e(k,n)*d(k,:);    % lms coefficient adaptation (learning the headphone)
      end
    end
    
    % remove high frequency corrections
    %[b, a] = besself(11, 0.95, 'z');
    [b, a] = butter(7,0.99);
    for k = 1:2
      c(k,:) = filter(b, a, c(k,:));
    end
    
    % apply b&k curve
    [b] = b_k();
    for k = 1:2
      c(k,:) = filter(b, 1, c(k,:));
    end
    
    % Plot errors (check convergence)
    figure;
    for k = 1:2
      subplot(2,1,k);
      plot(e(k,:));
      grid on;
      axis tight;
    end
    
    % Plot correction filters
    figure;
    for k = 1:2
      subplot(2,1,k);
      plot(c(k,:));
      grid on;
      axis tight;
    end
    
    % Plot correction filters frequency response
    figure(1);
    for k = 1:2
      subplot(2,1,k);
      hold on;
      C = freqz(c(k,:),1,length(c(k,:)));
      step = (22050-5)/length(c(k,:));
      f = 5:step:22050;f=f(1:end-1);
      semilogx(f,20*log10(abs(C))+90,'r');
      grid on;
      axis([20 20000 65 110]);
    end
    
    % Plot predicted impulse responses
    figure;
    PlotSize = 200;
    corr = zeros(size(h));
    for k = 1:2
      subplot(2,1,k);
      corr(:,k) = filter(c(k,:), 1, h(:,k));
      plot(h(MaxIndex-PreCursor:PlotSize+MaxIndex-PreCursor,1));
      hold on;
      plot(corr(MaxIndex-PreCursor+Main:PlotSize+MaxIndex-PreCursor+Main,k),'r');
      legend('original','corrected');
      grid on;
      axis tight;
    end
    
    % Plot predicted frequency responses
    figure;
    for k = 1:2
      CORR = freqz(corr(:,k),1,length(corr(:,k)));
      step = (22050-5)/length(CORR);
      f = 5:step:22050;f=f(1:end-1);
      subplot(2,1,k);
      semilogx(f,20*log10(abs(CORR))+90,'r');
      grid on;
      axis([20 20000 65 110]);
    end
    
    % Save filter
    filt = c.';
    wavwrite(filt, fs, nbits, 'hd600_eq.wav');
    
    You might have to play around with the script depending on where the headphone impulse responses main peak happens. Though I might have corrected that.
     
    Last edited: Sep 11, 2016
  5. ultrabike

    ultrabike Measurbator - Admin

    Staff Member Pyrate MZR
    Joined:
    Sep 25, 2015
    Likes Received:
    8,960
    Trophy Points:
    113
    Location:
    Irvine CA
    Deleted a rogue 9 in the b_k calling. Not sure why Octave didn't complain about that. But other versions and Matlab might.

    EDIT: One will need to install the signal pkg for Octave.
     
    Last edited: Sep 11, 2016
  6. spoony

    spoony Spooky

    Pyrate
    Joined:
    Sep 28, 2015
    Likes Received:
    651
    Trophy Points:
    93
    Thank you very much! Does this look correct to you?

    fr.png
     
  7. ultrabike

    ultrabike Measurbator - Admin

    Staff Member Pyrate MZR
    Joined:
    Sep 25, 2015
    Likes Received:
    8,960
    Trophy Points:
    113
    Location:
    Irvine CA
    Yep! :sail:
     
  8. ultrabike

    ultrabike Measurbator - Admin

    Staff Member Pyrate MZR
    Joined:
    Sep 25, 2015
    Likes Received:
    8,960
    Trophy Points:
    113
    Location:
    Irvine CA
    A few folks were wondering if I could share the HD600 correction filter I came up with. Here is a link to it:

    https://drive.google.com/file/d/0BzGhKu7Sq7bCSlczalFSZE5GVDg/view?usp=sharing

    I don't think it will sound good if you just play it. Proly a blip or so. But I think one can download it and apply it as is to fb2k's convolution plugin, in which case it may sound good with an HD600 set of cans. Or one can check it out in Audacity. Or this or that.

    Note this filter will give one a B&K response from an HD600 assuming my measurement rig. It will work well if one's HD600s match the response of mine closely (and if my measurement rig is not too far off).
     
    Last edited: Sep 14, 2016
  9. ultrabike

    ultrabike Measurbator - Admin

    Staff Member Pyrate MZR
    Joined:
    Sep 25, 2015
    Likes Received:
    8,960
    Trophy Points:
    113
    Location:
    Irvine CA
    I would also like to point out that there are companies that provide one with professional calibration/equalization services that can actually be tailored to your particular headphone such as these guys:

    http://sonarworks.com/headphones/overview/
     
  10. spoony

    spoony Spooky

    Pyrate
    Joined:
    Sep 28, 2015
    Likes Received:
    651
    Trophy Points:
    93
    Forgot to post my impressions. It surely makes a competent pair of headphones sound like nothing, and that's a good thing. However, either my measurement rig rolls of severely above 10 KHz or is unreliable in that area, or I can't stand flat treble up to 20 KHz because some stuff became just too sharp. I will be playing with the parameters.

    Thanks, Ultra.
     
  11. ultrabike

    ultrabike Measurbator - Admin

    Staff Member Pyrate MZR
    Joined:
    Sep 25, 2015
    Likes Received:
    8,960
    Trophy Points:
    113
    Location:
    Irvine CA
    More than likely is the measurement rig. Which means this may actually help fine tuning one's measurement rig.

    With my setup things open up with the HD600s. However, it does become a little brighter. Even with the B&K curve. I don't think my measurement rig is too far off, but it's not perfect.

    One thing that can be done is compare results with a set of flat speakers. Perhaps experiment with different coupling materials and/or compensation.
     
  12. ultrabike

    ultrabike Measurbator - Admin

    Staff Member Pyrate MZR
    Joined:
    Sep 25, 2015
    Likes Received:
    8,960
    Trophy Points:
    113
    Location:
    Irvine CA
    BTW, how do your un-equalized HD600s measurements look?

    EDIT: In some folks measurement rigs, certain headphones do look a bit way off particularly in the treble and upper mids area. Either too depressed or too peaky. If this is a short coming of the measurement rig, automatic equalization will obliviously try to correct for a problem that is not there. First step should be to get the measurement rig right.

    Also, compensation should be applied to the IMPULSE RESPONSE, not the frequency response magnitude. This means that one needs to get the equivalent impulse response of the frequency response magnitude compensation. I don't know of a single measurement package that does this. Even the mega bucks ones. One would have to do this by hand.
     
    Last edited: Sep 15, 2016
  13. spoony

    spoony Spooky

    Pyrate
    Joined:
    Sep 28, 2015
    Likes Received:
    651
    Trophy Points:
    93
    I don't own the HD600/650, I tried it with my modded Fostex which appear to have severe roll-off past 10 KHz. Maybe it's a coupling thing. I'm using a C.U.N.T.-like coupler with the UMIK-1 which is supposed to be calibrated flat from 20-20KHz. Measurements differ from those taken with the Dayton mics in similar rigs, especially in the treble.
     
  14. ultrabike

    ultrabike Measurbator - Admin

    Staff Member Pyrate MZR
    Joined:
    Sep 25, 2015
    Likes Received:
    8,960
    Trophy Points:
    113
    Location:
    Irvine CA
    Did some open air measurements on HD600s and KPPs. Interesting stuff.

    Problem areas are around 7 to 10 kHz with some few dBs of discrepancy vs my measurement shack. It is not super consistent across cans. I will have to do more measurements and listen more closely.

    I can also say that flat equalization or B&K equalization using my rig as reference is not perfect. It's not too far off though. As far as results after listening, I like some things and I don't like some others. But the fact that some things do improve is encouraging. I'm cool with my cans w/o eq though.

    In particular, with equalization the HD600s open up. And they do so just right. But they do become a little more edgy. The KPPs on the other hand become less strident, but become more flat in dynamics. Looking at the open air measurements there are some clues as to why. But again, would have to look into it. No hurry though. Again, I'm cool with my cans the way they are.

    I'm also not searching for the ultimate stuff. Just curious. And like to see for myself that things can indeed be tuned to some extent with DSP if so desired, with some positive (and not so positive) results. Other approaches obviously work as well.
     
    Last edited: Sep 16, 2016
  15. ultrabike

    ultrabike Measurbator - Admin

    Staff Member Pyrate MZR
    Joined:
    Sep 25, 2015
    Likes Received:
    8,960
    Trophy Points:
    113
    Location:
    Irvine CA
    So here is what I mean when I say there are differences between my HD600 measurement shack results and an free air measurement:

    comparo.png

    To demonstrate consistency with the measurements, I included dates. The shack rig measurements are actually years apart. The open ones are one day apart. I was not super nm careful in positioning the cans. Hell, for the open air measurements I merely held the cans with my hand.

    If there is this huge change due to burn in, or whatevers, I fail to see it. At least with these cans.

    Now, what the open air measurements tell me is that there is indeed a bit of mid range shoutiness due to the double hump between 3 kHz and 5 kHz. The measurement shack is perhaps injecting an artificial boost betwen 7 kHz and 10 kHz. But I fail to see the horrid mess with peaks and nulls all over the place.

    The open air measurements actually correlate best with what I hear from 2 kHz and up. The two measurements correlate well between 2 kHz and 6 kHz and then they deviate a bit. But again, we are not way off the mark. Down from 2 kHz, the measurement shack pulls things together.

    Can I combine the two measurements all the way to impulse responses. Proly.

    Can I come up with a headphone tailred impulse response compensation from open air to shack that only applies to 2 kHz and up. Proly.

    Would these be useful at all to improve things. Perhaps. It would allow me to experiment with some approaches to see if I can come up with a better equalization file. At it may help in maybe improving frequency response plots that better correlate with what we hear.

    BTW, doing a similar thing with other headphones may not result in the same set of compensation curves. So it is possible that this has to be done on a headphone to headphone basis, or on a headphone type to headphone type basis.

    I do not currently know if this is necessary. Hopefully experience and time will tell.

    In the mean time, the equalization files that I produce should improve some things, and IMO they don't result in horrid sound. I like the corrections for the HD600s better than the KPPs. But the KPPs corrections don't sound horrid to me either. They also improve some things at the expense of others.
     
    Last edited: Sep 17, 2016
  16. Gatucho

    Gatucho New

    Joined:
    Aug 25, 2016
    Likes Received:
    4
    Trophy Points:
    8
    Location:
    México
    Hello
    I was really interested in this, the code by @ultrabike was particularly nice!:bow:

    I was looking at the code and it occurred to me that maybe a direct de-convolution could be used instead of a recursive least squares. I made a very rough code to verify if this is possible. This code is only a curiosity for those interested, it is still not usable for equ-ing.

    I still am building my measuring rig so I dont have any real measurements, I got the impulse response of the FIR filter that Ultraibke posted, therefore Im actually estimating the impulse response that Ultrabike measured :p (or something like that). It seems that some of the low frequency ripple can be diminished by using a larger FIR filter and using a low value for the pseudo-inverse.
    Once I get my rig up and going Im gonna test all of this.

    Code:
    clear
    clc
    close all
    
    %%superbike code (thanks!)
    % paramters
    fs = 44.1e3;
    fn = fs/2;
    Ts=1/fs;
    n = 500;
    
    % target response
    s = -6/(log10(20000)-log10(50));
    r = log10(50):0.001:log10(fn);
    p = s.*(r - log10(50));
    f = 10.^r;
    bk_t = [[0 f]; -[0 p]].';
    
    % approximation
    b = (firls(n, bk_t(:,1).'/fn, 10.^(-bk_t(:,2).'/20.0)))';
    
    
    %% my code
    %input and lengths
    hd600=wavread('hd600_eq.wav');
    lc=hd600(:,1);
    figure(10),plot(lc)
    largo=4000; %filter size
    in_le=length(lc); %measured impulse size
    
    
    %original impulse
    yio=lc; %measurement
    %desired impulse
    yid=[zeros(in_le,1); b; zeros(largo-length(b),1)]; %use for bk target
    %yid=[zeros(1,in_le) 1 zeros(1,largo-1)]'; %use for flat
    
    %deconvolution
    H_le=largo+in_le;
    H=convmtx(yio,H_le);H=H(1:H_le,:); %convolution matrix
    lam = 0.001;%pseudo inverse constant (small better more sensible to noise, larger less sensible to noise less ideal)
    yf = (H'*H + lam * eye(H_le))\ (H' * yid(1:H_le)); %least squares
    yf=yf(1:largo); %resulting filter impulse
    figure (1), plot(yf);
    
    %verification of impulses
    yim=conv(yio,yf);
    figure(2), hold off; plot(yim), hold on; plot(yid,'r')
    
    
    %% frequency domain plots (small bits also from ultrabike code)
    
    figure(8)
    %filter
    [h, w] = freqz(yf,1,2^15,fs);
    semilogx(w(2:end), 20*log10(abs(h(2:end))));
    hold on;
    %original
    [h, w] = freqz(yio,1,2^15,fs);
    semilogx(w(2:end), 20*log10(abs(h(2:end))),'r');
    grid on;
    axis([5 20000 -23 17]);
    
    figure(9)
    %appliying filter
    yim=filter(yio,1,[yf; zeros(largo*2,1)]);
    [h, w] = freqz(yim,1,2^15,fs);
    semilogx(w(2:end), 20*log10(abs(h(2:end))));
    hold on;
    %desired response
    [h, w] = freqz(yid,1,2^15,fs);
    semilogx(w(2:end), 20*log10(abs(h(2:end))),'r');
    axis([5 20000 -23 17]);
    grid on;
    
     
  17. ultrabike

    ultrabike Measurbator - Admin

    Staff Member Pyrate MZR
    Joined:
    Sep 25, 2015
    Likes Received:
    8,960
    Trophy Points:
    113
    Location:
    Irvine CA
    I'll give your approach a go tonite if I can because I don't have the HD600 measurements with me at the moment.

    However, I think de-convolution is the equivalent of a zero-forcing filter which may be problematic on deep narrow nulls and peaks.

    I didn't added a noise floor to the MMSE approach I did. But if I do, I can control the null depth in many cases.

    I also wouldn't worry too much about the ringing. One might add a low pass filter or something. Though a larger filter will indeed take care of the low frequencies better. Just note many headphones are not that linear in such regions.
     
  18. ultrabike

    ultrabike Measurbator - Admin

    Staff Member Pyrate MZR
    Joined:
    Sep 25, 2015
    Likes Received:
    8,960
    Trophy Points:
    113
    Location:
    Irvine CA
    err.. I meant adding a high pass filter to the result to remove the ringing. I'm dyslexic.
     
  19. Gatucho

    Gatucho New

    Joined:
    Aug 25, 2016
    Likes Received:
    4
    Trophy Points:
    8
    Location:
    México
    Yes, you are right, the method may be sensitive to noise and peaks. This can be lessened up to some point by using a larger pseudo inverse constant.

    One thing that occurs to me to improve this is to obtain the impulse response from the smoothed frequency response instead of the raw fr.

    Another thing that intrigues me is that the filters are being designed as non-minimum phase (I believe that yours is also NMP?). However the impulse response appears only slightly NMP (pre-ringing in the impulse response). I don't know if this would really have an audible effect.

    I may try to obtain a minimum phase filter for the causal part of the impulse response and then compensate with a non-minimum phase for the remaining part. However it may take me a while to look in to this.
     
    Last edited: Oct 28, 2016
  20. ultrabike

    ultrabike Measurbator - Admin

    Staff Member Pyrate MZR
    Joined:
    Sep 25, 2015
    Likes Received:
    8,960
    Trophy Points:
    113
    Location:
    Irvine CA
    While the response of a headphone or general driver may be mostly MP, I don't think the compensation should be.

    You can obtain a MP equivalent of the NMP correction filter (google cepstrum). I do not recommend it however. The force tells me so.

    Using smoothing sounds like a plan also, if you use deconvobulatation-isation.
     

Share This Page