[MOD][AUDIO] biQuads - Utilizing Qualcomm's Audio Codec for Headphone Compensation

Search This thread

chdloc

Senior Member
Jul 19, 2010
1,140
1,724
UPDATE
Things have gotten a little easier: A native Android app that replaces the below script mess along with the Tasker-generated app is now available here. Unfortunately, you will still need to manually update your mixer_paths.xml file.


Newer devices and/or kernels now utilize a codec feature that is incompatible with biQuads. If after appropriate modification of your phone's mixer file you only hear audio coming out of your right headphone channel, then I suggest you read this post for a fix.

This mod is not just another collection of black-box sound "enhancement" libraries. There are plenty of those in this forum. The uniqueness of this mod is its ability to offer highly flexible and truly personalized headphone compensation filters. The potential of this proposition becomes clear when you realize that audio "quality" is experienced on a very subjective/individual level. The final compensation filters as obtained by the attached app simply require a modification of a single text file (mixer_paths.xml) that control the IIR filter as offered by the audio codec (Qualcomm only!). The audio mod itself is independent of kernel, ROM, and audio playback software used.


Introduction

The aim of this mod is to offer the user of a rooted Qualcomm-based Android device running Lollipop or later the ability to take full advantage of the IIR filter implementation offered by Qualcomm audio codecs (WCD93xx). The IIR filters are implemented in hardware as a cascade of five biquads that can be designed independently. Before diving into your own filter designs, you may want to visit this website to play with an interactive visualization of biquad filters. The mod and associated app aims to provide a similar design experience right on your device.

The advantages of using Qualcomm's hardware filter as opposed to other equalization filters offered by software implementations, such as Viper and friends are:
  • the filters are implemented in hardware and independent of the ROM, kernel, and media playback software used
  • the additional impact on battery usage is virtually non-existent
  • no black box: the user has full control over the design; the filters can be designed to avoid any distortions to the audio signal

Installation instructions

  • install the attached app
    [*]make sure you have busybox installed
    [*]flash the attached zip using a custom recovery (tested with TWRP) Update: For devices using the Qualcomm WCD9335 and newer you will need to install an updated version, which is available here
  • prepare your mixer (/system/etc/mixer_paths.xml) to accept the hardware filter. This is the most complex part of this mod. Unfortunately, this needs to be done manually as each device is (potentially) different. This post hosts the relevant sections of various devices, which will hopefully be updated regularly with new devices.
  • Newer devices and/or kernels now utilize a codec feature that is incompatible with biQuads. If after appropriate modification of your phone's mixer file you only hear audio coming out of your right headphone channel, then I suggest you read this post for a fix.

Operation of the software

This software is very useful to (within reason) design compensation filters for your headphone's frequency response. For example, many in-ear-monitors (IEMs) suffer from some level of high-frequency boost due to ear canal resonances. These resonances can be effectively "eliminated" by attenuating the offending frequencies. Each offending frequency (or frequency range) is then taken care of by designing its own biquad. You can find measured frequency responses (that you can use as a reference) of a large number of headphones either on GoldenEars, Inner Fidelity, or in forums like Head-Fi.

Open the biQuads app and you will see the main screen that allows you to enter the parameters of each of the five available biquads, see the example section below. Enter the parameters and tap "Design Biquad". You can repeat this process for up to five biquads, but you can also leave any number of biquads at their default, which is a simple bypass (does not change the frequency response at all). A biquad that is kept at its default values is marked with a white IIR X (X=1,2,...,5) text label. Once a biquad has been designed the text turns green. A designed biquad filter can be reset to its default value by tapping the corresponding green text label. Note that your most recent filter design will remain in memory and will even survive a reboot.

Once you are happy with your biquad design you can tap the "Review Filter" button. This action will combine all previously designed biquads (green text labels) to the final filter which can then be applied to the audio codec ("Listen" button) while you are listening to music. Once you are completely satisfied with your filter you can hit the "Save" button which will allow you to save the filter coefficients along with a screenshot of the final frequency response along with all design parameters. You will then need to manually copy the contents of the saved filter coefficients file to your (modified) mixer_paths.xml file. The format of the coefficients file is such you can simply copy and paste into your mixer_path.xml file.

Please keep an eye on the maximum gain applied by the overall filter. In order to avoid distortions introduced by that gain, I highly recommend to appropriately lower the playback volume; each volume "tick" in the upper playback range corresponds to 3 dB. For example if your overall filter adds a maximum of 3 dB of gain, the playback level should be kept below the highest Volume setting to avoid non-linear distortions.

Limitations of this software
  • this app has been created with the incredible Tasker software. While Tasker is extremely powerful, exported apps are not exactly pretty and they do not show up in the "Recent Apps" view. (At least not this app) may not scale well on certain screen resolutions.
  • unfortunately, mixer_path edits are close to impossible to automate and some manual edits are necessary
  • when listening to the effect of the designed filters, keep in mind that the filters will reset to whatever (if any) filter has been hardcoded in your mixer_path.xml file with any audio stream change (fast forward, skip, etc.). You will have to tap the "Listen" button to, again, apply the designed filters.
  • not necessarily a limitation of this app, but a limitation of using the Qualcomm-provided IIR filter for headphone compensation: any other reference to IIR filtering in your mixer_paths.xml file should be removed. The original use-case for the IIR filter is sidetone compensation for telephony.
  • due to the nature of this implementation (Tasker executing shell scripts and writing files that don't sit in /system), root privileges are frequently requested. I have seen instances where SuperSU runs out of memory to spawn new "root shells". At this point, no app will work that relies on root privileges and yo have to reboot your device. I'm not sure whether this is a Tasker or SuperSU problem.


A design example with screenshots

Below is a screenshot of the parameter entry screen for a peak filter (IIR/biquad 1) at 7 kHz with a gain of -6.0 dB (attenuation) and Q=1.0:
thumbnail



Below is a screenshot of the frequency response screen (after tapping the "Design Biquad" button) for a peak filter (IIR/biquad 1) at 7 kHz with a gain of -6.0 dB (attenuation) and Q=1.0:
thumbnail


Below is a screenshot of the parameter entry screen for a lowpass (LP) shelf filter (IIR/biquad 2) at 200 Hz with a gain of 3.0 dB and Q=1.0:
thumbnail


Below is a screenshot of the frequency response screen (after tapping the "Design Biquad" button) for a lowpass (LP) shelf filter (IIR/biquad 2) at 200 Hz with a gain of 3.0 dB and Q=1.0:
thumbnail


Below is a screenshot of the overall designed filter (after tapping the "Review Filter" button) of the two biquads designed above:
thumbnail



Useful posts in this thread

Tested devices
  • Nexus 5 (my own and only device)
  • Samsung Note 4 (Qualcomm)
  • HTC M9

Acknowledgements
Many thanks to my good friends @bjrmd and @avs333 whose comments and time spent on debugging have been incredibly valuable to the project.

Changelog
  • Version 1.0 (December 12, 2015): Initial revision
  • Version 1.0.1 (December 14, 2015): added tinymix binary required to apply filters to the codec from within the app
  • Version 1.1 (December 20, 2015): added feature: the gain introduced by the IIR filter can now be compensated for. This is particularly important when playing back audio at high levels and designing biquads that introduce overall gain, particularly at low frequencies. Now you can long-tap the "Listen" button to force gain compensation. This update also includes a few bugfixes. Please reflash the attached zip.
  • Version 1.1.1 (December 21, 2015): for as of yet unknown reasons, the tinymix binary that I've packaged with the last two releases does not seem to work with Android version prior to Marshmallow. This release packages the tinymix attached to the second post, which has been built from Lollipop sources and still works on Marshmallow. Thanks @bjrmd for reporting the issue!
  • Version 1.2 (December 29, 2015): app now allows for applying any of the designed biquads to either the left channel or right channel only, which paths the way for compensation of asymmetric hearing and/or physically different transducers (loudspeakers on the phone). This mode can be enabled by tapping the filter parameter text box on the "Review Filter" screen. The filters are still applied to both channels simultaneously by default. Note that, at this stage, the filters are still saved for both channels, regardless of what playback mode has been selected. Some manual labor is required to transfer the final filters to your mixer_paths.xml file. Note that you will need additional edits to your mixer_paths file to support built-in speaker compensation. See this post for an example pertaining to the Nexus 5.
  • Version 1.2.1 (March 9, 2016): small update to address a potential problem writing data (filter coefficients) to external storage. Thanks @bjrmd for reporting the issue!
  • Version 1.2.2 (April 4, 2016): update that fixes filter coefficient overflow inside the codec that may occur with a limited number of design parameter combinations. Yet again, I'm indebted to @bjrmd for pointing out the issue.
  • (New) Version 1.0 (November 17, 2017): First release of a native Android app doing the same as the previous version 1.2.2. This app now self-contained and root-method-agnostic, so there is no need to flash anything anymore.
 

Attachments

  • biQuads_v1.0.apk
    2.3 MB · Views: 3,070
Last edited:

chdloc

Senior Member
Jul 19, 2010
1,140
1,724
Some Theory

The following hidden text shows the original two posts of this thread for those of you who are interested in some gory details

Let me start off by pointing out that all of this is very technical and that there may be a steep learning curve. So please fire away with all your questions, so I can improve on the clarity of this and the following few posts.

Modern Qualcomm audio codecs, such as the WCD9310 (used, e.g., in the Nexus 4), the WCD9320 (used, e.g., in the Nexus 5), or the WCD9330 (used, e.g., in the Nexus 6, Nexus 5x, and Nexus 6p) offer the option to apply hardware filtering of the audio signals passing through the chip. Qualcomm offers two software-customizable 10-th order infinite-impulse response (IIR) filter, implemented as a cascade of five direct-form I biquad IIR filters, also known as second-order sections (SOS).

What are the advantages of using hardware filters instead of filters implemented in software at the Android level?
  • as the filters are implemented in hardware, there is no (maybe minute?) additional impact on battery life
  • as the filters are implemented at the lowest possible level, there is, in principle, no dependence on the media player, ROM, or kernel. Hence, a useful application of this IIR filter may be to compensate (within reasonable levels) one's favorite headphones for any media player. Please see this post for a suggestion on how you could measure your headphones (and hearing) to derive your own compensation filter.
  • the filter coefficients are supplied in the human-readable mixer_paths.xml file which can be modified by any knowledgeable user. More on that below.
  • if designed properly, the filters will not introduce any non-linear distortions; the user has full control over the design (see post #3)
  • a very simple channel cross-feed can be implemented. Cross-feed can help some folks that suffer from listening fatigue when listening to music via headphones.


As there is no documentation available that details the audio filtering options offered by the Qualcomm codecs, I spent some time reverse engineering.
After some quality time, I have succeeded in enabling the hardware audio filters inside the Qualcomm for equalization purposes which may be useful for any device that sports a Qualcomm codec. My examples below are tailored toward the Nexus 5, but porting this to other devices should be relatively straightforward.

(If you are reading this for the first time it may be instructional to jump to post #3 now, which details a practical example, before continuing to read this post)

The following details the current status of my investigations.


Qualcomm's biquad implementation is given by the following difference equation:
Code:
a0*y[n] = b0*x[n] + b1*x[n-1] + b2*x[x-2] - a1*y[n-1] - a2*y[n-2]
a0 is implicitly set to 1 internally, so it does not need to be defined explicitly.
Hence, each SOS is fully represented by five parameters, i.e. b0, b1, b2, a1, and a2.

The fixed-point numbers defining the filter coefficients seem to be stored in Q28 format, i.e. 1.0 corresponds to 2^{28}=268435456


Example: Highpass filter w/ cutoff @ 100Hz
  • Matlab design: elliptic IIR highpass; f_stop=80Hz, f_pass=100Hz, A_stop=40dB, A_pass=1dB; order = 5, sections: 3
    SOS1: b0=1, b1=-1.99989100262078, b2=1, a0=1, a1=-1.99852109834172, a2=0.998692521205726
    SOS2: b0=1, b1=-1.99994495162412, b2=1, a0=1, a1=-1.99015476348031, a2=0.990440326636932
    SOS3: b0=1, b1=-1 b2=0, a0=1, a1=-0.966597321365327, a2=0
  • SOS fixed point <=> floor(SOS*268435456)
    SOS1: b0=268435456, b1=-536841654, b2=268435456, a0=268435456, a1=-536473923, a2=268084482
    SOS2: b0=268435456, b1=-536856136, b2=268435456, a0=268435456, a1=-534228102, a2=265869300
    SOS3: b0=268435456, b1=-268435456, b2=0, a0=268435456, a1=-259468993, a2=0
  • id="0" <=> b0, id="1" <=> b1, id="2" <=> b2, id="3" <=> a1, id="4"<=> a2
  • keep all positive numbers as is and omit a0
  • negative numbers: two's complement, i.e. convert absolute number to binary, flip bits and add 1;
    the two MSB are reserved (cf. wcd9320.c @ function set_iir_band_coeff() )!
    example:
    -536841654 (b1 of first SOS)
    0001 1111 1111 1111 1000 1101 1011 0110 (converted absolute number, i.e. 536841654, to 32-bit binary)
    1110 0000 0000 0000 0111 0010 0100 1001 (flipped bits of 32-bit binary)
    0010 0000 0000 0000 0111 0010 0100 1010 (flip bits + 1; top two bits reserved, i.e. zero'ed)
    536900170 (converted back to decimal)


What follows is a new "headphone" section for a Nexus 5 in mixer_paths.xml file with modified routing and the IIR filters set to be in "passthrough" mode, i.e. the data flows through the filters, but remain unmodified. In other words, b0=1.0, a1=a2=b1=b2=0.0, i.e. y[n]=x[n]. This new headphone section can now be used to accept any newly designed IIR filter on-the-fly for testing (Warning: make sure that the filters are stable before trying to route audio through them!). When a final filter is found the filter coefficients need to be hard-coded into the mixer_paths.xml file.

Code:
    <path name="headphones">
        <ctl name="SLIM RX1 MUX" value="AIF1_PB" />
        <ctl name="SLIM RX2 MUX" value="AIF1_PB" />
        <ctl name="SLIM_0_RX Channels" value="Two" />
<!-- begin stock routing (commented out) -->
        <!-- <ctl name="RX1 MIX1 INP1" value="RX1" /> -->
        <!-- <ctl name="RX2 MIX1 INP1" value="RX2" /> -->
<!-- end stock routing (commented out) -->
        <ctl name="CLASS_H_DSM MUX" value="DSM_HPHL_RX1" />
        <ctl name="HPHL DAC Switch" value="1" />

<!-- enabling IIR filter; coefficients can be modified on-the-fly with tinymix, see below -->
   <!-- RX1 plugs into the IIR1 mux and RX2 plugs into the IIR2 mux -->
        <ctl name="IIR1 INP1 MUX" value="RX1" />
        <ctl name="IIR2 INP1 MUX" value="RX2" />	
   <!-- IIR1 filter cofficients (pass-through) -->
        <ctl name="IIR1 Band1" id ="0" value="268435456" />
        <ctl name="IIR1 Band1" id ="1" value="0" />   
        <ctl name="IIR1 Band1" id ="2" value="0" />   
        <ctl name="IIR1 Band1" id ="3" value="0" />   
        <ctl name="IIR1 Band1" id ="4" value="0" />
        <ctl name="IIR1 Band2" id ="0" value="268435456" /> 
        <ctl name="IIR1 Band2" id ="1" value="0" />  
        <ctl name="IIR1 Band2" id ="2" value="0" />   
        <ctl name="IIR1 Band2" id ="3" value="0" />   
        <ctl name="IIR1 Band2" id ="4" value="0" />
        <ctl name="IIR1 Band3" id ="0" value="268435456" /> 
        <ctl name="IIR1 Band3" id ="1" value="0" />  
        <ctl name="IIR1 Band3" id ="2" value="0" />   
        <ctl name="IIR1 Band3" id ="3" value="0" />   
        <ctl name="IIR1 Band3" id ="4" value="0" />
        <ctl name="IIR1 Band4" id ="0" value="268435456" /> 
        <ctl name="IIR1 Band4" id ="1" value="0" />  
        <ctl name="IIR1 Band4" id ="2" value="0" />   
        <ctl name="IIR1 Band4" id ="3" value="0" />   
        <ctl name="IIR1 Band4" id ="4" value="0" />
        <ctl name="IIR1 Band5" id ="0" value="268435456" /> 
        <ctl name="IIR1 Band5" id ="1" value="0" />  
        <ctl name="IIR1 Band5" id ="2" value="0" />   
        <ctl name="IIR1 Band5" id ="3" value="0" />   
        <ctl name="IIR1 Band5" id ="4" value="0" />
        <ctl name="IIR1 Enable Band1" value="1" />
        <ctl name="IIR1 Enable Band2" value="1" />
        <ctl name="IIR1 Enable Band3" value="1" />
        <ctl name="IIR1 Enable Band4" value="1" />
        <ctl name="IIR1 Enable Band5" value="1" />
   <!-- IIR2 filter coefficients; same as IIR1 -->
        <ctl name="IIR2 Band1" id ="0" value="268435456" />
        <ctl name="IIR2 Band1" id ="1" value="0" />   
        <ctl name="IIR2 Band1" id ="2" value="0" />   
        <ctl name="IIR2 Band1" id ="3" value="0" />   
        <ctl name="IIR2 Band1" id ="4" value="0" />
        <ctl name="IIR2 Band2" id ="0" value="268435456" /> 
        <ctl name="IIR2 Band2" id ="1" value="0" />  
        <ctl name="IIR2 Band2" id ="2" value="0" />   
        <ctl name="IIR2 Band2" id ="3" value="0" />   
        <ctl name="IIR2 Band2" id ="4" value="0" />
        <ctl name="IIR2 Band3" id ="0" value="268435456" /> 
        <ctl name="IIR2 Band3" id ="1" value="0" />  
        <ctl name="IIR2 Band3" id ="2" value="0" />   
        <ctl name="IIR2 Band3" id ="3" value="0" />   
        <ctl name="IIR2 Band3" id ="4" value="0" />
        <ctl name="IIR2 Band4" id ="0" value="268435456" /> 
        <ctl name="IIR2 Band4" id ="1" value="0" />  
        <ctl name="IIR2 Band4" id ="2" value="0" />   
        <ctl name="IIR2 Band4" id ="3" value="0" />   
        <ctl name="IIR2 Band4" id ="4" value="0" />
        <ctl name="IIR2 Band5" id ="0" value="268435456" /> 
        <ctl name="IIR2 Band5" id ="1" value="0" />  
        <ctl name="IIR2 Band5" id ="2" value="0" />   
        <ctl name="IIR2 Band5" id ="3" value="0" />   
        <ctl name="IIR2 Band5" id ="4" value="0" />
        <ctl name="IIR2 Enable Band1" value="1" />
        <ctl name="IIR2 Enable Band2" value="1" />
        <ctl name="IIR2 Enable Band3" value="1" />
        <ctl name="IIR2 Enable Band4" value="1" />
        <ctl name="IIR2 Enable Band5" value="1" />
   <!-- IIR1 plugs back into RX1 mux and IIR2 plugs back into RX2 mux -->
        <ctl name="RX1 MIX1 INP1" value="IIR1" />
        <ctl name="RX2 MIX1 INP1" value="IIR2" />
   <!-- set IIR1 and IIR2 gain (84<=>0dB; lower this parameter appropriately if any of the biquads introduce gain) -->
        <ctl name="IIR1 INP1 Volume" value="84" />
        <ctl name="IIR2 INP1 Volume" value="84" />

 <!-- cross-feed into second input of the IIR filter (needs kernel mod on hammerhead!) -->
        <ctl name="IIR1 INP2 MUX" value="RX2" />                                                                                                        
        <ctl name="IIR2 INP2 MUX" value="RX1" />  
 <!-- set the gain of the cross-feed (here: cross terms down by 24 dB, i.e 84-60) -->
        <ctl name="IIR1 INP2 Volume" value="60" />                                                                                                      
        <ctl name="IIR2 INP2 Volume" value="60" />

<!-- end test filter --> 

<!-- back to stock below -->
        <ctl name="RX1 Digital Volume" value="84" />
        <ctl name="RX2 Digital Volume" value="84" />
        <!-- lowered the HPHL[R] Volume from 15 to 10 to significantly reduce THD -->
        <ctl name="HPHL Volume" value="10" />
        <ctl name="HPHR Volume" value="10" />
    </path>
Note that the entries pertaining to cross-feed are optional and do not work on the Nexus 5 stock kernel as the AOSP codec driver is missing the plumbing for multiple inputs.
I noticed that the "headphones" section on Shamu and Mako do look different. Other devices will likely differ from the Hammerhead one, too. I should be able to help folks getting their "headphones" section on non-Hammerhead devices in shape.

Once booted with the modified headphone section, the filters can be modified on-the-fly by using tinymix:
Code:
# first, disable the filter to make sure that the filter does not become unstable during the update process!
tinymix "IIR1 Enable Band1" 0
tinymix "IIR1 Enable Band2" 0
tinymix "IIR1 Enable Band3" 0
tinymix "IIR1 Enable Band4" 0
tinymix "IIR1 Enable Band5" 0
tinymix "IIR2 Enable Band1" 0
tinymix "IIR2 Enable Band2" 0
tinymix "IIR2 Enable Band3" 0
tinymix "IIR2 Enable Band4" 0
tinymix "IIR2 Enable Band5" 0
# second, write new IIR1 filter (for the left audio channel)
tinymix "IIR1 Band1" 268435456 537978230 268435456 778747325 114847078
tinymix "IIR1 Band2" 268435456 542902433 268435456 601441125 243151927
tinymix "IIR1 Band3" 268435456 0 0 0 0
tinymix "IIR1 Band4" 268435456 0 0 0 0
tinymix "IIR1 Band5" 268435456 0 0 0 0
# third, write new IIR2 filter (for the right audio channel); you typically want IIR2 to be the same filter as IIR1
tinymix "IIR2 Band1" 268435456 537978230 268435456 778747325 114847078
tinymix "IIR2 Band2" 268435456 542902433 268435456 601441125 243151927
tinymix "IIR2 Band3" 268435456 0 0 0 0
tinymix "IIR2 Band4" 268435456 0 0 0 0
tinymix "IIR2 Band5" 268435456 0 0 0 0
# fourth, enable the filter
tinymix "IIR1 Enable Band1" 1
tinymix "IIR1 Enable Band2" 1
tinymix "IIR1 Enable Band3" 1
tinymix "IIR1 Enable Band4" 1
tinymix "IIR1 Enable Band5" 1
tinymix "IIR2 Enable Band1" 1
tinymix "IIR2 Enable Band2" 1
tinymix "IIR2 Enable Band3" 1
tinymix "IIR2 Enable Band4" 1
tinymix "IIR2 Enable Band5" 1

NOTE: the filter coefficients shown above just provide a proof-of-concept. The designed filter in this particular example eliminates all signals below 1000 Hz to make the effect apparent. I will make Octave scripts (a free Matlab alternative) available with which you can design you own filters in a later post. To go back to passthrough mode use
Code:
tinymix "IIR1 Enable Band1" 0
tinymix "IIR1 Enable Band2" 0
tinymix "IIR1 Enable Band3" 0
tinymix "IIR1 Enable Band4" 0
tinymix "IIR1 Enable Band5" 0
tinymix "IIR2 Enable Band1" 0
tinymix "IIR2 Enable Band2" 0
tinymix "IIR2 Enable Band3" 0
tinymix "IIR2 Enable Band4" 0
tinymix "IIR2 Enable Band5" 0
tinymix "IIR1 Band1" 268435456 0 0 0 0 
tinymix "IIR1 Band2" 268435456 0 0 0 0
tinymix "IIR1 Band3" 268435456 0 0 0 0
tinymix "IIR1 Band4" 268435456 0 0 0 0
tinymix "IIR1 Band5" 268435456 0 0 0 0
tinymix "IIR2 Band1" 268435456 0 0 0 0
tinymix "IIR2 Band2" 268435456 0 0 0 0 
tinymix "IIR2 Band3" 268435456 0 0 0 0
tinymix "IIR2 Band4" 268435456 0 0 0 0
tinymix "IIR2 Band5" 268435456 0 0 0 0
tinymix "IIR1 Enable Band1" 1
tinymix "IIR1 Enable Band2" 1
tinymix "IIR1 Enable Band3" 1
tinymix "IIR1 Enable Band4" 1
tinymix "IIR1 Enable Band5" 1
tinymix "IIR2 Enable Band1" 1
tinymix "IIR2 Enable Band2" 1
tinymix "IIR2 Enable Band3" 1
tinymix "IIR2 Enable Band4" 1
tinymix "IIR2 Enable Band5" 1


Note that restarting the audio stream will default to the filters currently present in the mixer_paths.xml file.

Known issues
  • the above implementation seems to be incompatible with any type of hotword detection: if audio is playing through headphones and the user goes back to the home screen with hotword detection enabled causes the codec to freak out. The exact reason is as of yet unknown, but it is believed to be related to the new routing scheme. Bottom line is that the music will stop playing through the headphones (and it does not come back automatically) if hotword detection is being activated, no matter how. Audio will resume only after a swap of the audio route, e.g. by unplugging the headphones, has been forced. Everything will work as expected if audio is first stopped, Google Now invoked manually, followed by resuming audio playback after the Google Now interaction has ended. Hotword detection now works happily alongside IIR filtering. First, however, the reference to "IIR1 INP1 MUX" needs to be removed from the entire device path that is used for the hotword detection device in mixer_paths.xml. On hammerhead, it is the "voice-rec-mic" device. You may need to trace all the way back to one of the "adcX" devices, where X={1,2,3,...}
  • the above implementation may not work with any "advanced" audio player that is capable of direct manipulation of the soundcard and configuring it to sampling rates higher than 48 kHz. I did get it to work, however, while writing audio (up to 24bit/192kHz) to the audio codec directly via ALSA. One thing to keep in mind, however, is that the sampling frequency is a filter design parameter. Hence, if desired/necessary, separate filters need to be designed for 48, 96, and 192 kHz sampling rate.
  • going from 16 bit to 24 bit sample resolution causes a drop of 48 dB in level when playing back a stereo signal via ALSA directly (libasound and alsa_aplay; not sure about libtinyalsa and tinyplay as I have not gotten it to work yet).
    I believe this is due to the fact that the ALSA library presents the 24 bit in the lower 3 Bytes while the codec looks at the top 3 Bytes for its digital-to-analog conversion. The codec's digital volume control ("RX Volume" and "IIR INP Volume") can be used to apply the missing 48 dB of gain, i.e. shifting the lower 3 Bytes to the top 3 Bytes.
  • audio needs to be played once with IIR filtering disabled immediately after the sampling rate has been changed (48 <-> 96 <-> 192).
  • The IIR filter gain parameter, "IIR1/2 INP1 Volume", seems to be ignored. Please see this post for further details.
  • The IIR filter seems to add a 180 degree phase shift to the signals, which is perceptually irrelevant as long as both channels undergo the same phase shift.




The IIR filter is implemented inside the Qualcomm audio codec as a cascade of five biquad filters. A straightforward design of the IIR filter can be achieved by designing each individual biquad separately, where each section "takes care" of a certain region of the frequency response to be modified. For example, imagine you have a headphone that exhibits a resonance (peak) in its frequency response. One of the five biquads can be designed to reduce the gain around that frequency to the desired level. I will be giving examples in the third post below.

This post discusses, how the biquads -- and the resulting IIR filter to be written to the codec -- can be designed.

Prerequisites
  • familiarize yourself with the types of biquads available and how each of them change the frequency response with a set of parameters. There is an excellent website available that offers a very nice interface for playing around with any of the biquad designs
  • once you have a rough understanding of biquads, download and install Octave. Once installed, you will also need the "signal package", see here and here
  • download the attached archive and extract the two Octave/Matlab scripts, wcd93xx_sos.m and wcd93xx_filter.m
  • depending on whether you are running a lollipop- or kitkat-based ROM, download the appropriate archive that contains the tinymix binary (part of AOSP, but not installed by default). Copy it to, e.g. /system/bin, and make executable (744).

Now, launch Octave and type (after navigating to the directory where the two scripts reside)
Code:
help wcd93xx_sos
which will show some details on how to use this function to design individual biquads. For example, let's assume we would like to reduce a resonance around 6.5 kHz by about 6 dB for 48 kHz sampling rate. One possible option would be
Code:
[biquad1, SOS1]=wcd93xx_sos(6500,3,-6,48000,7);
which will show you the magnitude and phase response of the biquad.
You can now design up to five biquads to "deal" with deficiencies in the frequency response you are trying to equalize (lowpass, highpass, bandpass, notch filter, or shelf filter). Make sure you call each biquad with different output parameters [biquadX, SOSX] where X={1,2,3,4,5}. Once you have a set of biquads you deem appropriate, call the script wcd93xx_filter which will simply ask you how many biquads you have designed to generate the cascade and to plot the overall magnitude and phase response. This script also prints a series tinymix commands with which the filters can be applied to the Qualcomm audio codec while music is playing through your headphones, but only if the "headphones" section from the previous post had been added to your mixer_paths.xml file. The series of commands as well as the above "headphones" section target the Nexus 5, but they may work with other phones either unaltered, or with only minor modifications. Please post your "headphones" section of /system/etc/mixer_paths.xml if you run into trouble.

I understand that all of this may be overwhelming and confusing. To address this, I will walk you through a complete design procedure in the next post in great detail.

Some basic design guidelines
  • be very careful when applying any gains (AdB>0) as you may end up with bad distortions. If you do need amplification of certain frequencies or ranges of frequencies, you may want to appropriately reduce the IIR filter gain first (mixer variable: "IIR1/2 INP1 Volume", see next post)
  • <to be completed>

Make sure you make a backup of your /system/etc/mixer_paths.xml file (and know how to restore it if things go south) first before attempting to change anything.


Update (07/31/2015): small update to the Matlab/Octave scripts. A bug has been fixed that would show the overall compensation filter using 48 kHz sampling rate only.
 

Attachments

  • tinymix_lollipop.tar.gz
    2.9 KB · Views: 1,140
  • tinymix_kitkat.tar.gz
    2.9 KB · Views: 555
  • matlab_octave_scripts_v2.tar.gz
    3.4 KB · Views: 680
Last edited:

chdloc

Senior Member
Jul 19, 2010
1,140
1,724
Real-world Filter Examples - Part 1: Apple Earpods

In this post I will be detailing the design process for the current Apple earpods. Note that the resulting design just serves as an example and is only meant as a starting point for your own designs and is by no means "optimal". A possible compensation filter design for the Sennheiser IE80 IEMs is detailed here.

Before we start, let me get this out of the way:
I highly suggest you not "go crazy" with the compensation filters as the danger of introducing distortion is high if you don't know what you are doing. I rather see the option to apply hardware filtering as a fine tuning tool. If your headphones are lacking quality I'd highly recommend finding headphones whose "sound signature" you like out-of-the-box. Filtering will not magically make make $5 headphones sound like $100 ones. Design flaws cannot be compensated for by filters. Also, please read posts #1 and #2 for some general information, prerequisites, and the current incompatibility of this approach with hotword detection.

Step 1: Find/measure the frequency response of the headphones you'd like to equalize

If you cannot find frequency responses of your headphones online and have no means to measure it (see here for a suggestion on how to make relative measurements), trust your ears and experiment.
There are plenty of measurements available for the Apple earpods, and I measured them myself with a lab-grade sound recording device.
Below is the resulting measurement.
3284932.jpg

Note that there is virtually no signal being visible below 1 kHz. The reason for this is that I simply held the microphone of my recording device very close to one of the earbuds. Earbuds and in-ear-monitors (IEMs) rely on the fact that they sit either in the ear or in the ear canal which will provide some level of seal that helps with amplifying the low frequencies that cannot be measured in "free-field". Hence, low frequency response is critically dependent on the eartips used. When listening to these earpods I noticed some harshness which I attribute to the two peaks (resonances) at around 2 kHz and 6.5 kHz. I will be addressing these two "shortcomings" with the below filter design.

Step 2: Design the biquads

You will either need to have Matlab (somewhat unlikely as it is very expensive for non-students) or Octave (see previous post) installed.
I will be showing the process with Matlab, but the commands for Octave are identical. The plots are formatted slightly different, though.

Step 2.1: First biquad - notch filter to reduce the gain around 6.5 kHz by 6 dB

Type
Code:
help wcd93xx_sos
to learn about the options that the biquad design tool offers.
A possible way to design the notch filter is
Code:
[biquad1, SOS1]=wcd93xx_sos(6500,3,-6,48000,7);
The design tool check for filter stability (should always be) and minimum phase and returns the result on the command line. A filter that is not minimum phase may introduce artifacts to the audio, see e.g. here.
The resulting filter that is returned by the design tool is shown below
3284933.jpg

You can see that, indeed, the biquad attenuates signals around 6.5 kHz. The width of the "cone" can be controlled by the Q-factor, which is the second input parameter to the wcd93xx_sos function (chosen to be 3 in this example). This plot also shows how the phase of a signal traveling through this filter will be modified. Phase modifications are less audible than amplitude modifications, and I do not expect this particular filter having much audible effect due to it modifying the phase.

Step 2.2: Second biquad - notch filter to reduce the gain around 2 kHz by 3 dB

Similarly to the above steps, this biquad can be designed with
Code:
[biquad2, SOS2]=wcd93xx_sos(2000,3,-3,48000,7);
and the resulting filter is shown below
3284934.jpg


Step 2.3-2.5: third-to-fifth biquad - any additional designs
This particular example does not have any additional filters, but up to three more biquads can be incorporated. Make sure that you call them with the following command as the output parameters have to have a specified name (biquad and SOS)
Code:
[biquadN, SOSN]=wcd93xx_sos(F0,Q,AdB,fs,type);
where N={3,4,5}.
Important NOTE: be very careful if you decide to choose AdB>0, i.e. if you want to apply gain to the signal. It is important to understand that music material is mastered to take advantage of the entire (well, at least the top-end of the) dynamic range. This means that loud sections of music will be very close to digital full-scale which will "max out" the digital representation of the signal. Any further digital amplification of these signals will invariably lead to clipping, which in turn will introduce non-linear distortions. This problem is precisely the reason why I typically avoid any type of sound "enhancement" apps/libraries. If you do feel that amplification of certain frequencies or ranges of frequencies is warranted, then you should appropriately lower the input gain of the IIR filter, i.e. the mixer setting "IIRx INP1 Volume" where x={1,2} in your mixer_paths.xml. The neutral gain setting, i.e. 0 dB, is 84. Each "tick" corresponds to 1 dB. For instance, setting this parameter to 78 will lower the gain by 6 dB while setting this parameter to 90 will increase the gain by 6 dB.

UPDATE: please read this post on limitations of the IIR filter gain, i.e. "IIR1/2 INP1 Volume" as specified in mixer_paths.xml

Step 3: Get overall filter and coefficients suitable for the Qualcomm codec

Within the same Matlab/Octave session, type
Code:
wcd93xx_filter
You will be asked how many biquads you have designed. In this example the answer would be two and the script plots the overall filter (cascade of two biquads):
3284935.jpg

The script also prints a series of mixer settings which should be copied to an executable shell script, e.g. /data/local/tmp/apply_filter.sh
Code:
tinymix "IIR1 Enable Band1" 0
tinymix "IIR1 Enable Band2" 0
tinymix "IIR1 Enable Band3" 0
tinymix "IIR1 Enable Band4" 0
tinymix "IIR1 Enable Band5" 0
tinymix "IIR2 Enable Band1" 0
tinymix "IIR2 Enable Band2" 0
tinymix "IIR2 Enable Band3" 0
tinymix "IIR2 Enable Band4" 0
tinymix "IIR2 Enable Band5" 0
tinymix "IIR1 Band1" 248299376 772991194 207835511 772991194 187699432
tinymix "IIR1 Band2" 264612186 580454136 246076806 580454136 242253536
tinymix "IIR1 Band3" 268435456 0 0 0 0
tinymix "IIR1 Band4" 268435456 0 0 0 0
tinymix "IIR1 Band5" 268435456 0 0 0 0
tinymix "IIR2 Band1" 248299376 772991194 207835511 772991194 187699432
tinymix "IIR2 Band2" 264612186 580454136 246076806 580454136 242253536
tinymix "IIR2 Band3" 268435456 0 0 0 0
tinymix "IIR2 Band4" 268435456 0 0 0 0
tinymix "IIR2 Band5" 268435456 0 0 0 0
tinymix "IIR1 Enable Band1" 1
tinymix "IIR1 Enable Band2" 1
tinymix "IIR1 Enable Band3" 1
tinymix "IIR1 Enable Band4" 1
tinymix "IIR1 Enable Band5" 1
tinymix "IIR2 Enable Band1" 1
tinymix "IIR2 Enable Band2" 1
tinymix "IIR2 Enable Band3" 1
tinymix "IIR2 Enable Band4" 1
tinymix "IIR2 Enable Band5" 1
Now you can run the script (or multiple ones if you want to do A/B comparisons, a script for passthrough mode can be obtained from the OP) while audio is playing through your headphones. Note, that you will need to have IIR filtering enabled by having copied the modified "headphones" section to your mixer_paths.xml, see OP. Note also that the posted "headphones" section is taken from the Nexus 5. I do expect other phones have very similar if not identical "headphones" sections.
I applied this example filter to the Apple earpods and measured the resulting frequency response, which is shown as the blue curve in the first plot of this post. Personally, I believe this filter removes some of the harshness in the sound signature of these headphones.

Step 4: Hardcode your final filter design

Once you are happy with the filters you have designed you need to copy the coefficients to your mixer_paths.xml file and reboot, so they are applied whenever audio is playing.
 

Attachments

  • apple_earpods.jpg
    apple_earpods.jpg
    55.3 KB · Views: 2,860
  • apple_sos1_6p5kHz_notch.jpg
    apple_sos1_6p5kHz_notch.jpg
    36.1 KB · Views: 2,851
  • apple_sos2_2kHz_notch.jpg
    apple_sos2_2kHz_notch.jpg
    34.8 KB · Views: 2,834
  • apple_compensation_filter.jpg
    apple_compensation_filter.jpg
    34.3 KB · Views: 2,759
Last edited:

chdloc

Senior Member
Jul 19, 2010
1,140
1,724
Unlocking the hardware filter in the Qualcomm Snapdragon?

Ignoring Bluetooth audio for the time being, there are two ways of playing audio on Android
  1. Let Android do all the work (decoding, fx, volume control, and mixing) and send audio to the codec (ADM)
  2. Let the aDSP do the heavy lifting (decoding, fx, volume control, and mixing) and have it send the final audio stream to the codec (ASM)
The ASM kernel-aDSP interface offers the ability to apply fx to the audio stream. One of the fx options are 12 biquads (they can be queried with tinymix). Unfortunately, the stock kernel on Nexus and Pixel devices cannot take advantage of the biquads as only the MultiMedia1, MultiMedia2, and MultiMedia3 DAI front-ends are defined/enabled. To use the biquads one needs to, at a minimum, enable the plumbing for MultiMedia4 EQ in the kernel, which is the one dedicated to offload processing.

To make a long story short, the biquads in the aDSP would even theoretically only be accessible when audio is being offloaded to the aDSP. That does not work, however, as the required plumbing is not present. So even if the plumbing were present, only very few apps would be making use of it as most apps do not offload.


This is very much work in progress. Some random thoughts and discoveries are listed below.
(The details refer to the Nexus 5, but should be generally applicable to any Snapdragon-based device)

  • There is another set of biquad (?) filters available inside the Snapdragon chip; they show up in tinymix (and in the kernel sources @ msm-pcm-routing.c) as MultiMediaX EQ BandY, where X={1,2,3} and Y=[1(1)12]. This means that the filters are only applicable to streams that make use of one of the first three MultiMedia streams. However, by default, music is routed through MultiMedia5, which is not eligible for filtering. This filter may be a 24-th order IIR filter which would allow more degrees of freedom than the IIR filters in the codec would.
    Simply changing the stream from MulitMedia5 to MultiMedia1 in mixer_paths.xml (section: low-latency-playback) does not work, kernel log error: "MSM8974 LowLatency: asoc: MSM8974 LowLatency no valid playback route from source to sink". Where in the kernel sources is the backend-to-frontend forced to use MultiMedia5 for low-latency-playback?
    logcat error:
    D/audio_hw_primary( 193): enable_snd_device: snd_device(5: headphones)
    D/audio_hw_primary( 193): enable_audio_route: apply and update mixer path: low-latency-playback
    E/audio_hw_primary( 193): start_output_stream: cannot open device '/dev/snd/pcmC0D15p': Invalid argument

    The device #15 is hard-coded in the audio HAL (audio_hw.c: [USECASE_AUDIO_PLAYBACK_LOW_LATENCY] = {15, 15} and msm8974/platform.h: #define LOWLATENCY_PCM_DEVICE 15)

    # cat /proc/asound/card0/pcm15p/info
    card: 0
    device: 15
    subdevice: 0
    stream: PLAYBACK
    id: MultiMedia5 (*)
    [...]

    MultiMedia1 is mapped to pcm0p, MultiMedia2 is mapped to pcm1p, MultiMedia3/4 are not mapped anywhere
    If the music is offloaded, then it seems to always bypass the MultiMedia mixer and directly connect to MM_DL4

    The only way to enable the filters seems to change the hardcoded device number in the audio HAL from 15 to either 0 (MultiMedia1) or 1 (MultiMedia2) and make the corresponding changes in mixer_paths.xml (point to MultiMedia1/2 for the low-latency-playback use-case).
    Filters will only be available in the non-offloaded case.


    UPDATE: applied the above modifications using MultiMedia2; music plays fine, but kernel log reads
    <3>[ 101.532335] q6asm_get_audio_client: invalid session: -1065112576
    <3>[ 101.532453] msm_send_eq_values: Could not get audio client for session: -1065112576
    and the filters never get applied; also, the maximum allowed value for the first filter parameter seems to be 65535; hence the filter implementation differs from the IIR in the WCD93xx codec.
  • Lots of debug information in /sys/kernel/debug/asoc/msm8974-taiko-mtp-snd-card and /sys/devices/sound.29/

Some new findings:
Indeed, the filters in the ADSP are a set of biquads, each of which expects the following five parameters (msm-pcm-routing-v2.c):
  • first parameter: band_indx: ranges from 0 to 11
  • second parameter: filter_type: the following types are defined in apr_audio-v2.h
    /* No equalizer effect.*/
    #define ASM_PARAM_EQYPE_NONE 0
    /* Bass boost equalizer effect.*/
    #define ASM_PARAM_EQ_BASS_BOOST 1
    /*Bass cut equalizer effect.*/
    #define ASM_PARAM_EQ_BASS_CUT 2
    /* Treble boost equalizer effect */
    #define ASM_PARAM_EQREBLE_BOOST 3
    /* Treble cut equalizer effect.*/
    #define ASM_PARAM_EQREBLE_CUT 4
    /* Band boost equalizer effect.*/
    #define ASM_PARAM_EQ_BAND_BOOST 5
    /* Band cut equalizer effect.*/
    #define ASM_PARAM_EQ_BAND_CUT 6
  • third parameter: center_freq_hz
  • fourth parameter: filter_gain: -12 to +12 dB in 1 dB increments; format unclear
  • fifth parameter: q_factor: expects the numerator of the Q8 representation; the numerator is always 2^8=256; for example 1.5=384/256, so 384 is the expected parameter

The basic problem persists: the filters are applied only to MultiMediaX, X={1,2,3}, streams. At least on the devices I've seen, music playback is routed through MultiMedia4 (compress-offload-playback or low-latency-playback use cases, see mixer_paths.xml).
There are two options, both of which require changes to the kernel sources
  1. add support for EQ on MultiMedia4 path
  2. modify front-end to back-end routes from MultiMedia4 to MultiMedia1
 
Last edited:

chdloc

Senior Member
Jul 19, 2010
1,140
1,724
modified mixer_paths.xml for various Qualcomm-based devices

The intention of this post is to host the relevant device sections of mixer_path.xml files that enable IIR hardware filtering. Simply replace the relevant section in your mixer with the device-appropriate section below.

Alternatively, try the attached entire IIR-prepared mixer_paths.xml file for your device. However, there may be carrier/country-specific variances between mixer_paths files. Only Nexus devices seem to be immune from this carrier-specific madness. Please make sure to backup your current mixer_paths.xml file before proceeding.

Relevant device in mixer_paths.xml ("headphones") modified for IIR passthrough (bypass) on Nexus 5 and LG G2. Thanks @lordc !
Code:
    <path name="headphones">
        <ctl name="SLIM RX1 MUX" value="AIF1_PB" />
        <ctl name="SLIM RX2 MUX" value="AIF1_PB" />
        <ctl name="SLIM_0_RX Channels" value="Two" />
<!-- begin stock routing (commented out) -->
        <!-- <ctl name="RX1 MIX1 INP1" value="RX1" /> -->
        <!-- <ctl name="RX2 MIX1 INP1" value="RX2" /> -->
<!-- end stock routing (commented out) -->
        <ctl name="CLASS_H_DSM MUX" value="DSM_HPHL_RX1" />
        <ctl name="HPHL DAC Switch" value="1" />

<!-- enabling IIR filter; coefficients can be modified on-the-fly with tinymix, see below -->
   <!-- RX1 plugs into the IIR1 mux and RX2 plugs into the IIR2 mux -->
        <ctl name="IIR1 INP1 MUX" value="RX1" />
        <ctl name="IIR2 INP1 MUX" value="RX2" />	
   <!-- IIR1 filter cofficients (pass-through) -->
        <ctl name="IIR1 Band1" id ="0" value="268435456" />
        <ctl name="IIR1 Band1" id ="1" value="0" />   
        <ctl name="IIR1 Band1" id ="2" value="0" />   
        <ctl name="IIR1 Band1" id ="3" value="0" />   
        <ctl name="IIR1 Band1" id ="4" value="0" />
        <ctl name="IIR1 Band2" id ="0" value="268435456" /> 
        <ctl name="IIR1 Band2" id ="1" value="0" />  
        <ctl name="IIR1 Band2" id ="2" value="0" />   
        <ctl name="IIR1 Band2" id ="3" value="0" />   
        <ctl name="IIR1 Band2" id ="4" value="0" />
        <ctl name="IIR1 Band3" id ="0" value="268435456" /> 
        <ctl name="IIR1 Band3" id ="1" value="0" />  
        <ctl name="IIR1 Band3" id ="2" value="0" />   
        <ctl name="IIR1 Band3" id ="3" value="0" />   
        <ctl name="IIR1 Band3" id ="4" value="0" />
        <ctl name="IIR1 Band4" id ="0" value="268435456" /> 
        <ctl name="IIR1 Band4" id ="1" value="0" />  
        <ctl name="IIR1 Band4" id ="2" value="0" />   
        <ctl name="IIR1 Band4" id ="3" value="0" />   
        <ctl name="IIR1 Band4" id ="4" value="0" />
        <ctl name="IIR1 Band5" id ="0" value="268435456" /> 
        <ctl name="IIR1 Band5" id ="1" value="0" />  
        <ctl name="IIR1 Band5" id ="2" value="0" />   
        <ctl name="IIR1 Band5" id ="3" value="0" />   
        <ctl name="IIR1 Band5" id ="4" value="0" />
        <ctl name="IIR1 Enable Band1" value="1" />
        <ctl name="IIR1 Enable Band2" value="1" />
        <ctl name="IIR1 Enable Band3" value="1" />
        <ctl name="IIR1 Enable Band4" value="1" />
        <ctl name="IIR1 Enable Band5" value="1" />
   <!-- IIR2 filter coefficients; same as IIR1 -->
        <ctl name="IIR2 Band1" id ="0" value="268435456" />
        <ctl name="IIR2 Band1" id ="1" value="0" />   
        <ctl name="IIR2 Band1" id ="2" value="0" />   
        <ctl name="IIR2 Band1" id ="3" value="0" />   
        <ctl name="IIR2 Band1" id ="4" value="0" />
        <ctl name="IIR2 Band2" id ="0" value="268435456" /> 
        <ctl name="IIR2 Band2" id ="1" value="0" />  
        <ctl name="IIR2 Band2" id ="2" value="0" />   
        <ctl name="IIR2 Band2" id ="3" value="0" />   
        <ctl name="IIR2 Band2" id ="4" value="0" />
        <ctl name="IIR2 Band3" id ="0" value="268435456" /> 
        <ctl name="IIR2 Band3" id ="1" value="0" />  
        <ctl name="IIR2 Band3" id ="2" value="0" />   
        <ctl name="IIR2 Band3" id ="3" value="0" />   
        <ctl name="IIR2 Band3" id ="4" value="0" />
        <ctl name="IIR2 Band4" id ="0" value="268435456" /> 
        <ctl name="IIR2 Band4" id ="1" value="0" />  
        <ctl name="IIR2 Band4" id ="2" value="0" />   
        <ctl name="IIR2 Band4" id ="3" value="0" />   
        <ctl name="IIR2 Band4" id ="4" value="0" />
        <ctl name="IIR2 Band5" id ="0" value="268435456" /> 
        <ctl name="IIR2 Band5" id ="1" value="0" />  
        <ctl name="IIR2 Band5" id ="2" value="0" />   
        <ctl name="IIR2 Band5" id ="3" value="0" />   
        <ctl name="IIR2 Band5" id ="4" value="0" />
        <ctl name="IIR2 Enable Band1" value="1" />
        <ctl name="IIR2 Enable Band2" value="1" />
        <ctl name="IIR2 Enable Band3" value="1" />
        <ctl name="IIR2 Enable Band4" value="1" />
        <ctl name="IIR2 Enable Band5" value="1" />
   <!-- IIR1 plugs back into RX1 mux and IIR2 plugs back into RX2 mux -->
        <ctl name="RX1 MIX1 INP1" value="IIR1" />
        <ctl name="RX2 MIX1 INP1" value="IIR2" />
   <!-- set IIR1 and IIR2 gain (84<=>0dB; lower this parameter appropriately if any of the biquads introduce gain) -->
        <ctl name="IIR1 INP1 Volume" value="84" />
        <ctl name="IIR2 INP1 Volume" value="84" />

 <!-- uncomment below if desired for cross-feed into second input of the IIR filter (needs kernel mod on hammerhead!) -->
  <!--      <ctl name="IIR1 INP2 MUX" value="RX2" />         -->                                                                                               
  <!--         <ctl name="IIR2 INP2 MUX" value="RX1" />    -->
 <!-- set the gain of the cross-feed (here: cross terms down by 24 dB, i.e 84-60) -->
   <!--        <ctl name="IIR1 INP2 Volume" value="60" />        -->                                                                                               
  <!--         <ctl name="IIR2 INP2 Volume" value="60" /> -->

<!-- end test filter --> 

<!-- back to stock below -->
        <ctl name="RX1 Digital Volume" value="84" />
        <ctl name="RX2 Digital Volume" value="84" />
        <!-- lowered the HPHL[R] Volume from 15 to 10 to significantly reduce THD -->
        <ctl name="HPHL Volume" value="10" />
        <ctl name="HPHR Volume" value="10" />
    </path>
Relevant device in mixer_paths.xml ("headset") modified for IIR passthrough on Note 4 (Snapdragon). Thanks @bjrmd !
Code:
<path name="headset">
      <ctl name="SLIM RX1 MUX" value="AIF1_PB"/>
      <ctl name="SLIM RX2 MUX" value="AIF1_PB"/>
      <ctl name="SLIM_0_RX Channels" value="Two"/>
      <ctl name="CLASS_H_DSM MUX" value="DSM_HPHL_RX1"/>
      <ctl name="HPHL DAC Switch" value="1"/>
      
      <!-- enabling IIR filter in pass-through mode; coefficients can be modified on-the-fly with tinymix -->
      <!-- RX1 plugs into the IIR1 mux and RX2 plugs into the IIR2 mux -->
      <ctl name="IIR1 INP1 MUX" value="RX1"/>
      <ctl name="IIR2 INP1 MUX" value="RX2"/>
      <!-- coefficients below define a no-op filter (0 dB of gain for all frequencies) -->
      <ctl name="IIR1 Band1" id="0" value="268435456"/>
      <ctl name="IIR1 Band1" id="1" value="0"/>
      <ctl name="IIR1 Band1" id="2" value="0"/>
      <ctl name="IIR1 Band1" id="3" value="0"/>
      <ctl name="IIR1 Band1" id="4" value="0"/>
      <ctl name="IIR1 Band2" id="0" value="268435456"/>
      <ctl name="IIR1 Band2" id="1" value="0"/>
      <ctl name="IIR1 Band2" id="2" value="0"/>
      <ctl name="IIR1 Band2" id="3" value="0"/>
      <ctl name="IIR1 Band2" id="4" value="0"/>
      <ctl name="IIR1 Band3" id="0" value="268435456"/>
      <ctl name="IIR1 Band3" id="1" value="0"/>
      <ctl name="IIR1 Band3" id="2" value="0"/>
      <ctl name="IIR1 Band3" id="3" value="0"/>
      <ctl name="IIR1 Band3" id="4" value="0"/>
      <ctl name="IIR1 Band4" id="0" value="268435456"/>
      <ctl name="IIR1 Band4" id="1" value="0"/>
      <ctl name="IIR1 Band4" id="2" value="0"/>
      <ctl name="IIR1 Band4" id="3" value="0"/>
      <ctl name="IIR1 Band4" id="4" value="0"/>
      <ctl name="IIR1 Band5" id="0" value="268435456"/>
      <ctl name="IIR1 Band5" id="1" value="0"/>
      <ctl name="IIR1 Band5" id="2" value="0"/>
      <ctl name="IIR1 Band5" id="3" value="0"/>
      <ctl name="IIR1 Band5" id="4" value="0"/>
      <ctl name="IIR1 Enable Band1" value="1"/>
      <ctl name="IIR1 Enable Band2" value="1"/>
      <ctl name="IIR1 Enable Band3" value="1"/>
      <ctl name="IIR1 Enable Band4" value="1"/>
      <ctl name="IIR1 Enable Band5" value="1"/>
      <!--  IIR2 filter coefficients; same as IIR1  -->
      <ctl name="IIR2 Band1" id="0" value="268435456"/>
      <ctl name="IIR2 Band1" id="1" value="0"/>
      <ctl name="IIR2 Band1" id="2" value="0"/>
      <ctl name="IIR2 Band1" id="3" value="0"/>
      <ctl name="IIR2 Band1" id="4" value="0"/>
      <ctl name="IIR2 Band2" id="0" value="268435456"/>
      <ctl name="IIR2 Band2" id="1" value="0"/>
      <ctl name="IIR2 Band2" id="2" value="0"/>
      <ctl name="IIR2 Band2" id="3" value="0"/>
      <ctl name="IIR2 Band2" id="4" value="0"/>
      <ctl name="IIR2 Band3" id="0" value="268435456"/>
      <ctl name="IIR2 Band3" id="1" value="0"/>
      <ctl name="IIR2 Band3" id="2" value="0"/>
      <ctl name="IIR2 Band3" id="3" value="0"/>
      <ctl name="IIR2 Band3" id="4" value="0"/>
      <ctl name="IIR2 Band4" id="0" value="268435456"/>
      <ctl name="IIR2 Band4" id="1" value="0"/>
      <ctl name="IIR2 Band4" id="2" value="0"/>
      <ctl name="IIR2 Band4" id="3" value="0"/>
      <ctl name="IIR2 Band4" id="4" value="0"/>
      <ctl name="IIR2 Band5" id="0" value="268435456"/>
      <ctl name="IIR2 Band5" id="1" value="0"/>
      <ctl name="IIR2 Band5" id="2" value="0"/>
      <ctl name="IIR2 Band5" id="3" value="0"/>
      <ctl name="IIR2 Band5" id="4" value="0"/>
      <ctl name="IIR2 Enable Band1" value="1"/>
      <ctl name="IIR2 Enable Band2" value="1"/>
      <ctl name="IIR2 Enable Band3" value="1"/>
      <ctl name="IIR2 Enable Band4" value="1"/>
      <ctl name="IIR2 Enable Band5" value="1"/>
      <!--IIR1 plugs back into RX1 mux and IIR2 plugs back into RX2 mux -->
      <ctl name="RX1 MIX1 INP1" value="IIR1"/>
      <ctl name="RX2 MIX1 INP1" value="IIR2"/>
      <!--  set IIR1 and IIR2 gain (84 equals 0 dB)  -->
      <ctl name="IIR1 INP1 Volume" value="84"/>
      <ctl name="IIR2 INP1 Volume" value="84"/>
      
        <!-- cross-feed into second input of the IIR filter (needs kernel mod on hammerhead!) -->
   <!--         <ctl name="IIR1 INP2 MUX" value="RX2" />    -->                                                                                                    
    <!--         <ctl name="IIR2 INP2 MUX" value="RX1" />  -->
          <!-- set the gain of the cross-feed (here: cross terms down by 24 dB, i.e 84-60) -->
   <!--          <ctl name="IIR1 INP2 Volume" value="60" />          -->                                                                                            
   <!--          <ctl name="IIR2 INP2 Volume" value="60" />        -->

      <ctl name="HPHL Volume" value="20"/>
      <ctl name="HPHR Volume" value="20"/>
      <ctl name="RX1 Digital Volume" value="80"/>
      <ctl name="RX2 Digital Volume" value="80"/>
      <ctl name="COMP1 Switch" value="1"/>
</path>
 

Attachments

  • mixer_paths_iir_bypass_nexus5.xml.gz
    2.8 KB · Views: 752
  • mixer_paths_iir_bypass_Note4.xml.gz
    6.4 KB · Views: 534
  • mixer_paths_iir_bypass_lgg2.xml.gz
    5.2 KB · Views: 311
  • mixer_paths_iir_bypass_m9.xml.gz
    7.5 KB · Views: 367
  • mixer_paths_iir_bypass_nexus6p.xml.gz
    6.9 KB · Views: 394
  • mixer_paths_iir_bypass_nexus5_headphone_speaker.xml.gz
    3.5 KB · Views: 550
Last edited:

avs333

Senior Member
Apr 12, 2005
239
312
Fabulous -- really works on my Nexus 5!

Tried your example with higher bitrates as well, and it seems to cut too much starting with 88.2 so you don't hear anything at 192, exactly as you warned.
No go with htc m9 (obviously) as the "headphones" mixer paths are very different there. Will try messing with it later.
 

chdloc

Senior Member
Jul 19, 2010
1,140
1,724
Fabulous -- really works on my Nexus 5!

Tried your example with higher bitrates as well, and it seems to cut too much starting with 88.2 so you don't hear anything at 192, exactly as you warned.
No go with htc m9 (obviously) as the "headphones" mixer paths are very different there. Will try messing with it later.

I'm not sure what's happening with higher sampling and/or bit rates. I had tested 24bit/192kHz as well and I measured the gain to be down by 54 dB (!) compared to 16bit/48kHz. The loss in gain is there regardless of the IIR filter being in place or not. The good thing about having the IIR filters in place is that there is yet another gain setting at our disposal, i.e. "IIRX INP1 Volume" where X={1,2}. With this gain setting we can make up for the missing 54 dB and we are back in business.

Edit: Having thought about this some more, I think it is possible that there is a codec driver bug that causes an erroneous register shift when going from 16 bit to 24 bit, which I suppose could cause a drop of 48 dB in gain (6 dB per bit).
Edit2: Some more insight around this added to OP (known issues section)

Please post the "headphones" path of your M9. Maybe I can be of assistance, though I know you are more than capable of doing this yourself...;)
 
Last edited:

nitephlight

Senior Member
Mar 16, 2012
275
100
LG V60 ThinQ
Fantastic work mate, this is what xda is all about. I already had a pretty fantastic correction filter for my ATH-M50 but its only sampled at 48khz . Should be an interesting experiment ?
 

avs333

Senior Member
Apr 12, 2005
239
312
Please post the "headphones" path of your M9. Maybe I can be of assistance, though I know you are more than capable of doing this yourself...;)

Oh. Never mind that, of course it was easy.
I dare say your investigation opens up something that we've been lacking before. I mean there always must be a script, say, accepting some frequency response on input, and another flat one, on output. As you suggest, it amounts to solving some kind of difference schemes for each particular case, doesn't it? (To some degree or other! E.g., if your headphones are initially broken, it won't help anyway. But in the most common case where you have some controls over that, you're likely be in clover after finding the only proper way around...).

Well, how to write that script (I believe it should be accessible on-line!) where you upload some -- say, RMAA results or whatever -- and to obtain /a patch to mixers paths/ or just alsa IIR settings for particular headphones. The latter would require very expensive equipment nobody has. But as to correcting the wcd's output, there _is_ a hardware loopback through and back the analog path, and I'm sure there's a clear chance to improve this stuff. In particular, for QC, just routing back AFE output to AFE input (_both_ analog). Then decide, whatever happened... And then, yes, this IIR stuff will be really helpful for _everyone_ 'cause the analog circuits are bound to differ within their respective tolerances...
 
  • Like
Reactions: skvalex and chdloc

chdloc

Senior Member
Jul 19, 2010
1,140
1,724
Oh. Never mind that, of course it was easy.
I dare say your investigation opens up something that we've been lacking before. I mean there always must be a script, say, accepting some frequency response on input, and another flat one, on output. As you suggest, it amounts to solving some kind of difference schemes for each particular case, doesn't it? (To some degree or other! E.g., if your headphones are initially broken, it won't help anyway. But in the most common case where you have some controls over that, you're likely be in clover after finding the only proper way around...).

Well, how to write that script (I believe it should be accessible on-line!) where you upload some -- say, RMAA results or whatever -- and to obtain /a patch to mixers paths/ or just alsa IIR settings for particular headphones. The latter would require very expensive equipment nobody has. But as to correcting the wcd's output, there _is_ a hardware loopback through and back the analog path, and I'm sure there's a clear chance to improve this stuff. In particular, for QC, just routing back AFE output to AFE input (_both_ analog). Then decide, whatever happened... And then, yes, this IIR stuff will be really helpful for _everyone_ 'cause the analog circuits are bound to differ within their respective tolerances...

I'm pretty sure that the analog and digital front-end of the WCD series of codecs is pretty flat in frequency response as long as the data is not clipped and the analog amplifier is not set close to its maximum gain. Any amplifier will introduce some level of non-linear distortions as a function of its analog gain setting. However, non-linear distortions cannot be compensated for. Hence, I think that any type of practical hardware filtering only makes sense to target the headphones themselves. Hardware loopback is interesting, but likely not of much help in pushing the idea of IIR hardware filtering forward.

It is certainly possible to come up with some fully automated black box that takes in a DUT (device-under-test) frequency response along with a desired frequency response, and have the black box spit out IIR filter coefficients. A classical least-squares optimization problem, which I could tackle (if I had the luxury of time on my side).

Having said all of this, I'm not convinced that people really care about this. From the feedback I have received so far I'd say, there is only a *very* small number of folks passionate enough about audio quality to care. I have not received a single inquiry about the design procedure itself, which may very well due to the fact that this stuff appears complicated.

I think that, at least initially, I see this as a crowdsourcing project for us few geeks to get the ball rolling and
  • share the "headphones" section of a wide range of WCD codec based Android phones, so we can prep them for IIR filtering
  • compare notes on IIR filter coefficients of specific headphones and determine how "universally useful" they are (remember: sound quality is first and foremost subjective).
You could help with item #1 and post the modified "headphones" section of your M9 and I'll add to item #2 by posting a (possible) compensation filter for my Sennheiser IE80 soon.
 
Last edited:
  • Like
Reactions: avs333 and skvalex

avs333

Senior Member
Apr 12, 2005
239
312
No filter is capable of compensating any distortions, right as rain. Meaning, compensating anything that hasn't been introduced at random.
I should have missed your point, the problem was the frequency response

And of course, I'm in! Let's do something along this way. As the matter of fact, I don't believe it's such clear _even_ with the digital FE. Well, some measurements have been already scheduled, it'll begin after May, 10. Keeping in touch!
 
Last edited:
  • Like
Reactions: skvalex

chdloc

Senior Member
Jul 19, 2010
1,140
1,724
No filter is capable of compensating any distortions, right as rain. I should have missed your point, the problem was the frequency response

And of course, I'm in! Let's do something along this way. As the matter of fact, I don't believe it's such clear _even_ with the digital FE. Well, some measurements have been already scheduled, it'll begin after May, 10. Keeping in touch!

This is exciting times for "high-end" audio on Android. Some folks that are stopping by here may not be familiar with your current efforts. Your "hardware" player along with (hopefully user-friendly) hardware filtering should get us closer in finding the "holy audio grail" on Android...;)
 
  • Like
Reactions: skvalex and avs333

avs333

Senior Member
Apr 12, 2005
239
312
This is exciting times for "high-end" audio on Android. Some folks that are stopping by here may not be familiar with your current efforts. Your "hardware" player along with (hopefully user-friendly) hardware filtering should get us closer in finding the "holy audio grail" on Android...;)
Thanks, chdloc. Well I don't really believe that :) If a guy would somehow prefer listening music on Android, I should say he's gone nuts :)
 
Last edited:

chdloc

Senior Member
Jul 19, 2010
1,140
1,724
Thanks, chdloc. Well I don't really believe that :) If a guy would somehow prefer listening music on Android, I should say he's gone nuts :)
Well, I have yet to see objective data that suggests that the codec/amp in my Nexus 5 is inferior to the one in my iPad Mini 2. I made measurements that suggest that the non-linear distortion created by the two codecs is virtually identical at the same output volume. Of course, there is more to sound quality than simple TDH+N measurements suggest, but I would have to fire up my MacBook Pro, or of course utilize dedicated hardware, to see measurably "cleaner" data.

My point is that we may soon get audio quality that is close to what the hardware is capable of delivering as we will be bypassing Android's signal processing chain (clipping, dithering, resampling, etc) altogether.
 

chdloc

Senior Member
Jul 19, 2010
1,140
1,724
Real-world Filter Examples - Part 2: Sennheiser IE80

In this post I'm sharing a possible compensation filter for the Sennheiser IE80 in-the-ear monitors (IEMs). These are somewhat harder to deal with than the Apple earpods as they allow for an additional degree of freedom, i.e. sub-bass adjustment via a dial on the device. In my opinion these IEM's low frequencies are a little overpowering, even when the sub-bass dial is set to minimum. This can be seen in frequency response measurements posted online, for instance here. In those measurements a relatively strong resonance around 5 kHz can also be observed. In my own measurement I found this resonance to be peaking just below 5 kHz:
thumbnail

As in the previous example, I was only able to measure the free-field response of the headphones as I do not have access to something like a Kemar Head and Torso Simulator, so the low frequencies are virtually absent from the measurement. I used the plots provided by GoldenEars to work on the low frequencies.
To compensate for the peak, I designed a notch filter with
Code:
[biquad1, SOS_fixed1, SOS1]=wcd93xx_sos(4875,2,-9,48000,7);
and a lowpass shelf filter with
Code:
[biquad2, SOS_fixed2, SOS2]=wcd93xx_sos(500,0.707,-9,48000,9);
The low pass shelf filter was designed to get rid of the "hump" in the frequency response for frequencies below 800 Hz. The sub-bass adjustment dial can then be used to accentuate the very low frequencies again to individual tastes.
The resulting tinymix commands to insert the filter on a Nexus 5 are
Code:
tinymix "IIR1 Enable Band1" 0
tinymix "IIR1 Enable Band2" 0
tinymix "IIR1 Enable Band3" 0
tinymix "IIR1 Enable Band4" 0
tinymix "IIR1 Enable Band5" 0
tinymix "IIR2 Enable Band1" 0
tinymix "IIR2 Enable Band2" 0
tinymix "IIR2 Enable Band3" 0
tinymix "IIR2 Enable Band4" 0
tinymix "IIR2 Enable Band5" 0
tinymix "IIR1 Band1" 233795534 728771120 195695835 728771120 161055914
tinymix "IIR1 Band2" 262009200 568438374 243939794 569025299 238100463
tinymix "IIR1 Band3" 268435456 0 0 0 0
tinymix "IIR1 Band4" 268435456 0 0 0 0
tinymix "IIR1 Band5" 268435456 0 0 0 0
tinymix "IIR2 Band1" 233795534 728771120 195695835 728771120 161055914
tinymix "IIR2 Band2" 262009200 568438374 243939794 569025299 238100463
tinymix "IIR2 Band3" 268435456 0 0 0 0
tinymix "IIR2 Band4" 268435456 0 0 0 0
tinymix "IIR2 Band5" 268435456 0 0 0 0
tinymix "IIR1 Enable Band1" 1
tinymix "IIR1 Enable Band2" 1
tinymix "IIR1 Enable Band3" 1
tinymix "IIR1 Enable Band4" 1
tinymix "IIR1 Enable Band5" 1
tinymix "IIR2 Enable Band1" 1
tinymix "IIR2 Enable Band2" 1
tinymix "IIR2 Enable Band3" 1
tinymix "IIR2 Enable Band4" 1
tinymix "IIR2 Enable Band5" 1
The resulting compensation filter is shown below
thumbnail

The measured frequency response is shown above (truncated for clarity as it is flat above 10 kHz) in the measured free-field response as the blue curve.

As in the previous example, this compensation filter is by no means "optimum", but I believe it removes some of the boomy-ness of these otherwise excellent IEMs.
 

Attachments

  • ie80_compensation_filter.jpg
    ie80_compensation_filter.jpg
    39.2 KB · Views: 536
  • sennheiser_ie80.jpg
    sennheiser_ie80.jpg
    29.2 KB · Views: 544
Last edited:

avs333

Senior Member
Apr 12, 2005
239
312
chdloc,
Just tried this, being a Sennheiser IE80 owner!
VERY impressive, I should say! It needs thorough testing, as I've only listened several tracks, but, indeed, say...
I'm not a HATS, I'm a human being. I just tested it, and it _does_ sound _a great deal better_ than the original setup with their screws in each ear. To be contiuned!
 
  • Like
Reactions: chdloc

avs333

Senior Member
Apr 12, 2005
239
312
Strange things will happen... Don't know how to explain that. 192/24 plays just as nice here, i.e. just tested the files I attach (d=0 passthru, d=1 chdloc):

./alsaplayer -d 0 -x zz.xml /sdcard/test16_44100.flac
./alsaplayer -d 1 -x zz.xml /sdcard/test16_44100.flac
./alsaplayer -d 0 -x zz.xml /sdcard/test24_88200.flac
./alsaplayer -d 1 -x zz.xml /sdcard/test24_88200.flac

"d 0" quite expectedly, kinda of louder than 1.

But, I wonder why it should play 88/24 at all. And somehow, it becomes more timid after the 4-th or 5-th second, but it does sound kinda better == just like on my SH-650. I tried the same thing with 192/24 (yeah, it plays with the same settings!), and yes the same result.
 

Attachments

  • alsaplayer.bz2
    95.2 KB · Views: 101
  • test24_82200.flac.bz2
    2.6 MB · Views: 70
  • test16_44100.flac.bz2
    750 KB · Views: 62
  • zz.xml.bz2
    1.1 KB · Views: 76

chdloc

Senior Member
Jul 19, 2010
1,140
1,724
But, I wonder why it should play 88/24 at all. And somehow, it becomes more timid after the 4-th or 5-th second, but it does sound kinda better == just like on my SH-650. I tried the same thing with 192/24 (yeah, it plays with the same settings!), and yes the same result.

24/88 plays fine because ALSA internally resamples the (unsupported by the codec) sample rate of 88.2 kHz down to 48 kHz.
Since the filter coefficients for the IE80 (post #17) have been designed for 48 kHz I would expect things to play nice. If you play a file that has been sampled at either 96 kHz or 192 kHz you would have to redesign the filter to have it work on the frequencies you expect it to.

The gains are a different story. At least when I write uncompressed audio to the codec directly (utilizing alsa_aplay kindly provided by @skvalex as part of his AlsaMixer app) using the sampling rates supported natively, i.e. {48,96,192} kHz, I see the 48 dB volume difference between 16 and 24 bit as theorized in the OP.
 

Top Liked Posts

  • There are no posts matching your filters.
  • 54
    UPDATE
    Things have gotten a little easier: A native Android app that replaces the below script mess along with the Tasker-generated app is now available here. Unfortunately, you will still need to manually update your mixer_paths.xml file.


    Newer devices and/or kernels now utilize a codec feature that is incompatible with biQuads. If after appropriate modification of your phone's mixer file you only hear audio coming out of your right headphone channel, then I suggest you read this post for a fix.

    This mod is not just another collection of black-box sound "enhancement" libraries. There are plenty of those in this forum. The uniqueness of this mod is its ability to offer highly flexible and truly personalized headphone compensation filters. The potential of this proposition becomes clear when you realize that audio "quality" is experienced on a very subjective/individual level. The final compensation filters as obtained by the attached app simply require a modification of a single text file (mixer_paths.xml) that control the IIR filter as offered by the audio codec (Qualcomm only!). The audio mod itself is independent of kernel, ROM, and audio playback software used.


    Introduction

    The aim of this mod is to offer the user of a rooted Qualcomm-based Android device running Lollipop or later the ability to take full advantage of the IIR filter implementation offered by Qualcomm audio codecs (WCD93xx). The IIR filters are implemented in hardware as a cascade of five biquads that can be designed independently. Before diving into your own filter designs, you may want to visit this website to play with an interactive visualization of biquad filters. The mod and associated app aims to provide a similar design experience right on your device.

    The advantages of using Qualcomm's hardware filter as opposed to other equalization filters offered by software implementations, such as Viper and friends are:
    • the filters are implemented in hardware and independent of the ROM, kernel, and media playback software used
    • the additional impact on battery usage is virtually non-existent
    • no black box: the user has full control over the design; the filters can be designed to avoid any distortions to the audio signal

    Installation instructions

    • install the attached app
      [*]make sure you have busybox installed
      [*]flash the attached zip using a custom recovery (tested with TWRP) Update: For devices using the Qualcomm WCD9335 and newer you will need to install an updated version, which is available here
    • prepare your mixer (/system/etc/mixer_paths.xml) to accept the hardware filter. This is the most complex part of this mod. Unfortunately, this needs to be done manually as each device is (potentially) different. This post hosts the relevant sections of various devices, which will hopefully be updated regularly with new devices.
    • Newer devices and/or kernels now utilize a codec feature that is incompatible with biQuads. If after appropriate modification of your phone's mixer file you only hear audio coming out of your right headphone channel, then I suggest you read this post for a fix.

    Operation of the software

    This software is very useful to (within reason) design compensation filters for your headphone's frequency response. For example, many in-ear-monitors (IEMs) suffer from some level of high-frequency boost due to ear canal resonances. These resonances can be effectively "eliminated" by attenuating the offending frequencies. Each offending frequency (or frequency range) is then taken care of by designing its own biquad. You can find measured frequency responses (that you can use as a reference) of a large number of headphones either on GoldenEars, Inner Fidelity, or in forums like Head-Fi.

    Open the biQuads app and you will see the main screen that allows you to enter the parameters of each of the five available biquads, see the example section below. Enter the parameters and tap "Design Biquad". You can repeat this process for up to five biquads, but you can also leave any number of biquads at their default, which is a simple bypass (does not change the frequency response at all). A biquad that is kept at its default values is marked with a white IIR X (X=1,2,...,5) text label. Once a biquad has been designed the text turns green. A designed biquad filter can be reset to its default value by tapping the corresponding green text label. Note that your most recent filter design will remain in memory and will even survive a reboot.

    Once you are happy with your biquad design you can tap the "Review Filter" button. This action will combine all previously designed biquads (green text labels) to the final filter which can then be applied to the audio codec ("Listen" button) while you are listening to music. Once you are completely satisfied with your filter you can hit the "Save" button which will allow you to save the filter coefficients along with a screenshot of the final frequency response along with all design parameters. You will then need to manually copy the contents of the saved filter coefficients file to your (modified) mixer_paths.xml file. The format of the coefficients file is such you can simply copy and paste into your mixer_path.xml file.

    Please keep an eye on the maximum gain applied by the overall filter. In order to avoid distortions introduced by that gain, I highly recommend to appropriately lower the playback volume; each volume "tick" in the upper playback range corresponds to 3 dB. For example if your overall filter adds a maximum of 3 dB of gain, the playback level should be kept below the highest Volume setting to avoid non-linear distortions.

    Limitations of this software
    • this app has been created with the incredible Tasker software. While Tasker is extremely powerful, exported apps are not exactly pretty and they do not show up in the "Recent Apps" view. (At least not this app) may not scale well on certain screen resolutions.
    • unfortunately, mixer_path edits are close to impossible to automate and some manual edits are necessary
    • when listening to the effect of the designed filters, keep in mind that the filters will reset to whatever (if any) filter has been hardcoded in your mixer_path.xml file with any audio stream change (fast forward, skip, etc.). You will have to tap the "Listen" button to, again, apply the designed filters.
    • not necessarily a limitation of this app, but a limitation of using the Qualcomm-provided IIR filter for headphone compensation: any other reference to IIR filtering in your mixer_paths.xml file should be removed. The original use-case for the IIR filter is sidetone compensation for telephony.
    • due to the nature of this implementation (Tasker executing shell scripts and writing files that don't sit in /system), root privileges are frequently requested. I have seen instances where SuperSU runs out of memory to spawn new "root shells". At this point, no app will work that relies on root privileges and yo have to reboot your device. I'm not sure whether this is a Tasker or SuperSU problem.


    A design example with screenshots

    Below is a screenshot of the parameter entry screen for a peak filter (IIR/biquad 1) at 7 kHz with a gain of -6.0 dB (attenuation) and Q=1.0:
    thumbnail



    Below is a screenshot of the frequency response screen (after tapping the "Design Biquad" button) for a peak filter (IIR/biquad 1) at 7 kHz with a gain of -6.0 dB (attenuation) and Q=1.0:
    thumbnail


    Below is a screenshot of the parameter entry screen for a lowpass (LP) shelf filter (IIR/biquad 2) at 200 Hz with a gain of 3.0 dB and Q=1.0:
    thumbnail


    Below is a screenshot of the frequency response screen (after tapping the "Design Biquad" button) for a lowpass (LP) shelf filter (IIR/biquad 2) at 200 Hz with a gain of 3.0 dB and Q=1.0:
    thumbnail


    Below is a screenshot of the overall designed filter (after tapping the "Review Filter" button) of the two biquads designed above:
    thumbnail



    Useful posts in this thread

    Tested devices
    • Nexus 5 (my own and only device)
    • Samsung Note 4 (Qualcomm)
    • HTC M9

    Acknowledgements
    Many thanks to my good friends @bjrmd and @avs333 whose comments and time spent on debugging have been incredibly valuable to the project.

    Changelog
    • Version 1.0 (December 12, 2015): Initial revision
    • Version 1.0.1 (December 14, 2015): added tinymix binary required to apply filters to the codec from within the app
    • Version 1.1 (December 20, 2015): added feature: the gain introduced by the IIR filter can now be compensated for. This is particularly important when playing back audio at high levels and designing biquads that introduce overall gain, particularly at low frequencies. Now you can long-tap the "Listen" button to force gain compensation. This update also includes a few bugfixes. Please reflash the attached zip.
    • Version 1.1.1 (December 21, 2015): for as of yet unknown reasons, the tinymix binary that I've packaged with the last two releases does not seem to work with Android version prior to Marshmallow. This release packages the tinymix attached to the second post, which has been built from Lollipop sources and still works on Marshmallow. Thanks @bjrmd for reporting the issue!
    • Version 1.2 (December 29, 2015): app now allows for applying any of the designed biquads to either the left channel or right channel only, which paths the way for compensation of asymmetric hearing and/or physically different transducers (loudspeakers on the phone). This mode can be enabled by tapping the filter parameter text box on the "Review Filter" screen. The filters are still applied to both channels simultaneously by default. Note that, at this stage, the filters are still saved for both channels, regardless of what playback mode has been selected. Some manual labor is required to transfer the final filters to your mixer_paths.xml file. Note that you will need additional edits to your mixer_paths file to support built-in speaker compensation. See this post for an example pertaining to the Nexus 5.
    • Version 1.2.1 (March 9, 2016): small update to address a potential problem writing data (filter coefficients) to external storage. Thanks @bjrmd for reporting the issue!
    • Version 1.2.2 (April 4, 2016): update that fixes filter coefficient overflow inside the codec that may occur with a limited number of design parameter combinations. Yet again, I'm indebted to @bjrmd for pointing out the issue.
    • (New) Version 1.0 (November 17, 2017): First release of a native Android app doing the same as the previous version 1.2.2. This app now self-contained and root-method-agnostic, so there is no need to flash anything anymore.
    19
    Some Theory

    The following hidden text shows the original two posts of this thread for those of you who are interested in some gory details

    Let me start off by pointing out that all of this is very technical and that there may be a steep learning curve. So please fire away with all your questions, so I can improve on the clarity of this and the following few posts.

    Modern Qualcomm audio codecs, such as the WCD9310 (used, e.g., in the Nexus 4), the WCD9320 (used, e.g., in the Nexus 5), or the WCD9330 (used, e.g., in the Nexus 6, Nexus 5x, and Nexus 6p) offer the option to apply hardware filtering of the audio signals passing through the chip. Qualcomm offers two software-customizable 10-th order infinite-impulse response (IIR) filter, implemented as a cascade of five direct-form I biquad IIR filters, also known as second-order sections (SOS).

    What are the advantages of using hardware filters instead of filters implemented in software at the Android level?
    • as the filters are implemented in hardware, there is no (maybe minute?) additional impact on battery life
    • as the filters are implemented at the lowest possible level, there is, in principle, no dependence on the media player, ROM, or kernel. Hence, a useful application of this IIR filter may be to compensate (within reasonable levels) one's favorite headphones for any media player. Please see this post for a suggestion on how you could measure your headphones (and hearing) to derive your own compensation filter.
    • the filter coefficients are supplied in the human-readable mixer_paths.xml file which can be modified by any knowledgeable user. More on that below.
    • if designed properly, the filters will not introduce any non-linear distortions; the user has full control over the design (see post #3)
    • a very simple channel cross-feed can be implemented. Cross-feed can help some folks that suffer from listening fatigue when listening to music via headphones.


    As there is no documentation available that details the audio filtering options offered by the Qualcomm codecs, I spent some time reverse engineering.
    After some quality time, I have succeeded in enabling the hardware audio filters inside the Qualcomm for equalization purposes which may be useful for any device that sports a Qualcomm codec. My examples below are tailored toward the Nexus 5, but porting this to other devices should be relatively straightforward.

    (If you are reading this for the first time it may be instructional to jump to post #3 now, which details a practical example, before continuing to read this post)

    The following details the current status of my investigations.


    Qualcomm's biquad implementation is given by the following difference equation:
    Code:
    a0*y[n] = b0*x[n] + b1*x[n-1] + b2*x[x-2] - a1*y[n-1] - a2*y[n-2]
    a0 is implicitly set to 1 internally, so it does not need to be defined explicitly.
    Hence, each SOS is fully represented by five parameters, i.e. b0, b1, b2, a1, and a2.

    The fixed-point numbers defining the filter coefficients seem to be stored in Q28 format, i.e. 1.0 corresponds to 2^{28}=268435456


    Example: Highpass filter w/ cutoff @ 100Hz
    • Matlab design: elliptic IIR highpass; f_stop=80Hz, f_pass=100Hz, A_stop=40dB, A_pass=1dB; order = 5, sections: 3
      SOS1: b0=1, b1=-1.99989100262078, b2=1, a0=1, a1=-1.99852109834172, a2=0.998692521205726
      SOS2: b0=1, b1=-1.99994495162412, b2=1, a0=1, a1=-1.99015476348031, a2=0.990440326636932
      SOS3: b0=1, b1=-1 b2=0, a0=1, a1=-0.966597321365327, a2=0
    • SOS fixed point <=> floor(SOS*268435456)
      SOS1: b0=268435456, b1=-536841654, b2=268435456, a0=268435456, a1=-536473923, a2=268084482
      SOS2: b0=268435456, b1=-536856136, b2=268435456, a0=268435456, a1=-534228102, a2=265869300
      SOS3: b0=268435456, b1=-268435456, b2=0, a0=268435456, a1=-259468993, a2=0
    • id="0" <=> b0, id="1" <=> b1, id="2" <=> b2, id="3" <=> a1, id="4"<=> a2
    • keep all positive numbers as is and omit a0
    • negative numbers: two's complement, i.e. convert absolute number to binary, flip bits and add 1;
      the two MSB are reserved (cf. wcd9320.c @ function set_iir_band_coeff() )!
      example:
      -536841654 (b1 of first SOS)
      0001 1111 1111 1111 1000 1101 1011 0110 (converted absolute number, i.e. 536841654, to 32-bit binary)
      1110 0000 0000 0000 0111 0010 0100 1001 (flipped bits of 32-bit binary)
      0010 0000 0000 0000 0111 0010 0100 1010 (flip bits + 1; top two bits reserved, i.e. zero'ed)
      536900170 (converted back to decimal)


    What follows is a new "headphone" section for a Nexus 5 in mixer_paths.xml file with modified routing and the IIR filters set to be in "passthrough" mode, i.e. the data flows through the filters, but remain unmodified. In other words, b0=1.0, a1=a2=b1=b2=0.0, i.e. y[n]=x[n]. This new headphone section can now be used to accept any newly designed IIR filter on-the-fly for testing (Warning: make sure that the filters are stable before trying to route audio through them!). When a final filter is found the filter coefficients need to be hard-coded into the mixer_paths.xml file.

    Code:
        <path name="headphones">
            <ctl name="SLIM RX1 MUX" value="AIF1_PB" />
            <ctl name="SLIM RX2 MUX" value="AIF1_PB" />
            <ctl name="SLIM_0_RX Channels" value="Two" />
    <!-- begin stock routing (commented out) -->
            <!-- <ctl name="RX1 MIX1 INP1" value="RX1" /> -->
            <!-- <ctl name="RX2 MIX1 INP1" value="RX2" /> -->
    <!-- end stock routing (commented out) -->
            <ctl name="CLASS_H_DSM MUX" value="DSM_HPHL_RX1" />
            <ctl name="HPHL DAC Switch" value="1" />
    
    <!-- enabling IIR filter; coefficients can be modified on-the-fly with tinymix, see below -->
       <!-- RX1 plugs into the IIR1 mux and RX2 plugs into the IIR2 mux -->
            <ctl name="IIR1 INP1 MUX" value="RX1" />
            <ctl name="IIR2 INP1 MUX" value="RX2" />	
       <!-- IIR1 filter cofficients (pass-through) -->
            <ctl name="IIR1 Band1" id ="0" value="268435456" />
            <ctl name="IIR1 Band1" id ="1" value="0" />   
            <ctl name="IIR1 Band1" id ="2" value="0" />   
            <ctl name="IIR1 Band1" id ="3" value="0" />   
            <ctl name="IIR1 Band1" id ="4" value="0" />
            <ctl name="IIR1 Band2" id ="0" value="268435456" /> 
            <ctl name="IIR1 Band2" id ="1" value="0" />  
            <ctl name="IIR1 Band2" id ="2" value="0" />   
            <ctl name="IIR1 Band2" id ="3" value="0" />   
            <ctl name="IIR1 Band2" id ="4" value="0" />
            <ctl name="IIR1 Band3" id ="0" value="268435456" /> 
            <ctl name="IIR1 Band3" id ="1" value="0" />  
            <ctl name="IIR1 Band3" id ="2" value="0" />   
            <ctl name="IIR1 Band3" id ="3" value="0" />   
            <ctl name="IIR1 Band3" id ="4" value="0" />
            <ctl name="IIR1 Band4" id ="0" value="268435456" /> 
            <ctl name="IIR1 Band4" id ="1" value="0" />  
            <ctl name="IIR1 Band4" id ="2" value="0" />   
            <ctl name="IIR1 Band4" id ="3" value="0" />   
            <ctl name="IIR1 Band4" id ="4" value="0" />
            <ctl name="IIR1 Band5" id ="0" value="268435456" /> 
            <ctl name="IIR1 Band5" id ="1" value="0" />  
            <ctl name="IIR1 Band5" id ="2" value="0" />   
            <ctl name="IIR1 Band5" id ="3" value="0" />   
            <ctl name="IIR1 Band5" id ="4" value="0" />
            <ctl name="IIR1 Enable Band1" value="1" />
            <ctl name="IIR1 Enable Band2" value="1" />
            <ctl name="IIR1 Enable Band3" value="1" />
            <ctl name="IIR1 Enable Band4" value="1" />
            <ctl name="IIR1 Enable Band5" value="1" />
       <!-- IIR2 filter coefficients; same as IIR1 -->
            <ctl name="IIR2 Band1" id ="0" value="268435456" />
            <ctl name="IIR2 Band1" id ="1" value="0" />   
            <ctl name="IIR2 Band1" id ="2" value="0" />   
            <ctl name="IIR2 Band1" id ="3" value="0" />   
            <ctl name="IIR2 Band1" id ="4" value="0" />
            <ctl name="IIR2 Band2" id ="0" value="268435456" /> 
            <ctl name="IIR2 Band2" id ="1" value="0" />  
            <ctl name="IIR2 Band2" id ="2" value="0" />   
            <ctl name="IIR2 Band2" id ="3" value="0" />   
            <ctl name="IIR2 Band2" id ="4" value="0" />
            <ctl name="IIR2 Band3" id ="0" value="268435456" /> 
            <ctl name="IIR2 Band3" id ="1" value="0" />  
            <ctl name="IIR2 Band3" id ="2" value="0" />   
            <ctl name="IIR2 Band3" id ="3" value="0" />   
            <ctl name="IIR2 Band3" id ="4" value="0" />
            <ctl name="IIR2 Band4" id ="0" value="268435456" /> 
            <ctl name="IIR2 Band4" id ="1" value="0" />  
            <ctl name="IIR2 Band4" id ="2" value="0" />   
            <ctl name="IIR2 Band4" id ="3" value="0" />   
            <ctl name="IIR2 Band4" id ="4" value="0" />
            <ctl name="IIR2 Band5" id ="0" value="268435456" /> 
            <ctl name="IIR2 Band5" id ="1" value="0" />  
            <ctl name="IIR2 Band5" id ="2" value="0" />   
            <ctl name="IIR2 Band5" id ="3" value="0" />   
            <ctl name="IIR2 Band5" id ="4" value="0" />
            <ctl name="IIR2 Enable Band1" value="1" />
            <ctl name="IIR2 Enable Band2" value="1" />
            <ctl name="IIR2 Enable Band3" value="1" />
            <ctl name="IIR2 Enable Band4" value="1" />
            <ctl name="IIR2 Enable Band5" value="1" />
       <!-- IIR1 plugs back into RX1 mux and IIR2 plugs back into RX2 mux -->
            <ctl name="RX1 MIX1 INP1" value="IIR1" />
            <ctl name="RX2 MIX1 INP1" value="IIR2" />
       <!-- set IIR1 and IIR2 gain (84<=>0dB; lower this parameter appropriately if any of the biquads introduce gain) -->
            <ctl name="IIR1 INP1 Volume" value="84" />
            <ctl name="IIR2 INP1 Volume" value="84" />
    
     <!-- cross-feed into second input of the IIR filter (needs kernel mod on hammerhead!) -->
            <ctl name="IIR1 INP2 MUX" value="RX2" />                                                                                                        
            <ctl name="IIR2 INP2 MUX" value="RX1" />  
     <!-- set the gain of the cross-feed (here: cross terms down by 24 dB, i.e 84-60) -->
            <ctl name="IIR1 INP2 Volume" value="60" />                                                                                                      
            <ctl name="IIR2 INP2 Volume" value="60" />
    
    <!-- end test filter --> 
    
    <!-- back to stock below -->
            <ctl name="RX1 Digital Volume" value="84" />
            <ctl name="RX2 Digital Volume" value="84" />
            <!-- lowered the HPHL[R] Volume from 15 to 10 to significantly reduce THD -->
            <ctl name="HPHL Volume" value="10" />
            <ctl name="HPHR Volume" value="10" />
        </path>
    Note that the entries pertaining to cross-feed are optional and do not work on the Nexus 5 stock kernel as the AOSP codec driver is missing the plumbing for multiple inputs.
    I noticed that the "headphones" section on Shamu and Mako do look different. Other devices will likely differ from the Hammerhead one, too. I should be able to help folks getting their "headphones" section on non-Hammerhead devices in shape.

    Once booted with the modified headphone section, the filters can be modified on-the-fly by using tinymix:
    Code:
    # first, disable the filter to make sure that the filter does not become unstable during the update process!
    tinymix "IIR1 Enable Band1" 0
    tinymix "IIR1 Enable Band2" 0
    tinymix "IIR1 Enable Band3" 0
    tinymix "IIR1 Enable Band4" 0
    tinymix "IIR1 Enable Band5" 0
    tinymix "IIR2 Enable Band1" 0
    tinymix "IIR2 Enable Band2" 0
    tinymix "IIR2 Enable Band3" 0
    tinymix "IIR2 Enable Band4" 0
    tinymix "IIR2 Enable Band5" 0
    # second, write new IIR1 filter (for the left audio channel)
    tinymix "IIR1 Band1" 268435456 537978230 268435456 778747325 114847078
    tinymix "IIR1 Band2" 268435456 542902433 268435456 601441125 243151927
    tinymix "IIR1 Band3" 268435456 0 0 0 0
    tinymix "IIR1 Band4" 268435456 0 0 0 0
    tinymix "IIR1 Band5" 268435456 0 0 0 0
    # third, write new IIR2 filter (for the right audio channel); you typically want IIR2 to be the same filter as IIR1
    tinymix "IIR2 Band1" 268435456 537978230 268435456 778747325 114847078
    tinymix "IIR2 Band2" 268435456 542902433 268435456 601441125 243151927
    tinymix "IIR2 Band3" 268435456 0 0 0 0
    tinymix "IIR2 Band4" 268435456 0 0 0 0
    tinymix "IIR2 Band5" 268435456 0 0 0 0
    # fourth, enable the filter
    tinymix "IIR1 Enable Band1" 1
    tinymix "IIR1 Enable Band2" 1
    tinymix "IIR1 Enable Band3" 1
    tinymix "IIR1 Enable Band4" 1
    tinymix "IIR1 Enable Band5" 1
    tinymix "IIR2 Enable Band1" 1
    tinymix "IIR2 Enable Band2" 1
    tinymix "IIR2 Enable Band3" 1
    tinymix "IIR2 Enable Band4" 1
    tinymix "IIR2 Enable Band5" 1

    NOTE: the filter coefficients shown above just provide a proof-of-concept. The designed filter in this particular example eliminates all signals below 1000 Hz to make the effect apparent. I will make Octave scripts (a free Matlab alternative) available with which you can design you own filters in a later post. To go back to passthrough mode use
    Code:
    tinymix "IIR1 Enable Band1" 0
    tinymix "IIR1 Enable Band2" 0
    tinymix "IIR1 Enable Band3" 0
    tinymix "IIR1 Enable Band4" 0
    tinymix "IIR1 Enable Band5" 0
    tinymix "IIR2 Enable Band1" 0
    tinymix "IIR2 Enable Band2" 0
    tinymix "IIR2 Enable Band3" 0
    tinymix "IIR2 Enable Band4" 0
    tinymix "IIR2 Enable Band5" 0
    tinymix "IIR1 Band1" 268435456 0 0 0 0 
    tinymix "IIR1 Band2" 268435456 0 0 0 0
    tinymix "IIR1 Band3" 268435456 0 0 0 0
    tinymix "IIR1 Band4" 268435456 0 0 0 0
    tinymix "IIR1 Band5" 268435456 0 0 0 0
    tinymix "IIR2 Band1" 268435456 0 0 0 0
    tinymix "IIR2 Band2" 268435456 0 0 0 0 
    tinymix "IIR2 Band3" 268435456 0 0 0 0
    tinymix "IIR2 Band4" 268435456 0 0 0 0
    tinymix "IIR2 Band5" 268435456 0 0 0 0
    tinymix "IIR1 Enable Band1" 1
    tinymix "IIR1 Enable Band2" 1
    tinymix "IIR1 Enable Band3" 1
    tinymix "IIR1 Enable Band4" 1
    tinymix "IIR1 Enable Band5" 1
    tinymix "IIR2 Enable Band1" 1
    tinymix "IIR2 Enable Band2" 1
    tinymix "IIR2 Enable Band3" 1
    tinymix "IIR2 Enable Band4" 1
    tinymix "IIR2 Enable Band5" 1


    Note that restarting the audio stream will default to the filters currently present in the mixer_paths.xml file.

    Known issues
    • the above implementation seems to be incompatible with any type of hotword detection: if audio is playing through headphones and the user goes back to the home screen with hotword detection enabled causes the codec to freak out. The exact reason is as of yet unknown, but it is believed to be related to the new routing scheme. Bottom line is that the music will stop playing through the headphones (and it does not come back automatically) if hotword detection is being activated, no matter how. Audio will resume only after a swap of the audio route, e.g. by unplugging the headphones, has been forced. Everything will work as expected if audio is first stopped, Google Now invoked manually, followed by resuming audio playback after the Google Now interaction has ended. Hotword detection now works happily alongside IIR filtering. First, however, the reference to "IIR1 INP1 MUX" needs to be removed from the entire device path that is used for the hotword detection device in mixer_paths.xml. On hammerhead, it is the "voice-rec-mic" device. You may need to trace all the way back to one of the "adcX" devices, where X={1,2,3,...}
    • the above implementation may not work with any "advanced" audio player that is capable of direct manipulation of the soundcard and configuring it to sampling rates higher than 48 kHz. I did get it to work, however, while writing audio (up to 24bit/192kHz) to the audio codec directly via ALSA. One thing to keep in mind, however, is that the sampling frequency is a filter design parameter. Hence, if desired/necessary, separate filters need to be designed for 48, 96, and 192 kHz sampling rate.
    • going from 16 bit to 24 bit sample resolution causes a drop of 48 dB in level when playing back a stereo signal via ALSA directly (libasound and alsa_aplay; not sure about libtinyalsa and tinyplay as I have not gotten it to work yet).
      I believe this is due to the fact that the ALSA library presents the 24 bit in the lower 3 Bytes while the codec looks at the top 3 Bytes for its digital-to-analog conversion. The codec's digital volume control ("RX Volume" and "IIR INP Volume") can be used to apply the missing 48 dB of gain, i.e. shifting the lower 3 Bytes to the top 3 Bytes.
    • audio needs to be played once with IIR filtering disabled immediately after the sampling rate has been changed (48 <-> 96 <-> 192).
    • The IIR filter gain parameter, "IIR1/2 INP1 Volume", seems to be ignored. Please see this post for further details.
    • The IIR filter seems to add a 180 degree phase shift to the signals, which is perceptually irrelevant as long as both channels undergo the same phase shift.




    The IIR filter is implemented inside the Qualcomm audio codec as a cascade of five biquad filters. A straightforward design of the IIR filter can be achieved by designing each individual biquad separately, where each section "takes care" of a certain region of the frequency response to be modified. For example, imagine you have a headphone that exhibits a resonance (peak) in its frequency response. One of the five biquads can be designed to reduce the gain around that frequency to the desired level. I will be giving examples in the third post below.

    This post discusses, how the biquads -- and the resulting IIR filter to be written to the codec -- can be designed.

    Prerequisites
    • familiarize yourself with the types of biquads available and how each of them change the frequency response with a set of parameters. There is an excellent website available that offers a very nice interface for playing around with any of the biquad designs
    • once you have a rough understanding of biquads, download and install Octave. Once installed, you will also need the "signal package", see here and here
    • download the attached archive and extract the two Octave/Matlab scripts, wcd93xx_sos.m and wcd93xx_filter.m
    • depending on whether you are running a lollipop- or kitkat-based ROM, download the appropriate archive that contains the tinymix binary (part of AOSP, but not installed by default). Copy it to, e.g. /system/bin, and make executable (744).

    Now, launch Octave and type (after navigating to the directory where the two scripts reside)
    Code:
    help wcd93xx_sos
    which will show some details on how to use this function to design individual biquads. For example, let's assume we would like to reduce a resonance around 6.5 kHz by about 6 dB for 48 kHz sampling rate. One possible option would be
    Code:
    [biquad1, SOS1]=wcd93xx_sos(6500,3,-6,48000,7);
    which will show you the magnitude and phase response of the biquad.
    You can now design up to five biquads to "deal" with deficiencies in the frequency response you are trying to equalize (lowpass, highpass, bandpass, notch filter, or shelf filter). Make sure you call each biquad with different output parameters [biquadX, SOSX] where X={1,2,3,4,5}. Once you have a set of biquads you deem appropriate, call the script wcd93xx_filter which will simply ask you how many biquads you have designed to generate the cascade and to plot the overall magnitude and phase response. This script also prints a series tinymix commands with which the filters can be applied to the Qualcomm audio codec while music is playing through your headphones, but only if the "headphones" section from the previous post had been added to your mixer_paths.xml file. The series of commands as well as the above "headphones" section target the Nexus 5, but they may work with other phones either unaltered, or with only minor modifications. Please post your "headphones" section of /system/etc/mixer_paths.xml if you run into trouble.

    I understand that all of this may be overwhelming and confusing. To address this, I will walk you through a complete design procedure in the next post in great detail.

    Some basic design guidelines
    • be very careful when applying any gains (AdB>0) as you may end up with bad distortions. If you do need amplification of certain frequencies or ranges of frequencies, you may want to appropriately reduce the IIR filter gain first (mixer variable: "IIR1/2 INP1 Volume", see next post)
    • <to be completed>

    Make sure you make a backup of your /system/etc/mixer_paths.xml file (and know how to restore it if things go south) first before attempting to change anything.


    Update (07/31/2015): small update to the Matlab/Octave scripts. A bug has been fixed that would show the overall compensation filter using 48 kHz sampling rate only.
    16
    Real-world Filter Examples - Part 1: Apple Earpods

    In this post I will be detailing the design process for the current Apple earpods. Note that the resulting design just serves as an example and is only meant as a starting point for your own designs and is by no means "optimal". A possible compensation filter design for the Sennheiser IE80 IEMs is detailed here.

    Before we start, let me get this out of the way:
    I highly suggest you not "go crazy" with the compensation filters as the danger of introducing distortion is high if you don't know what you are doing. I rather see the option to apply hardware filtering as a fine tuning tool. If your headphones are lacking quality I'd highly recommend finding headphones whose "sound signature" you like out-of-the-box. Filtering will not magically make make $5 headphones sound like $100 ones. Design flaws cannot be compensated for by filters. Also, please read posts #1 and #2 for some general information, prerequisites, and the current incompatibility of this approach with hotword detection.

    Step 1: Find/measure the frequency response of the headphones you'd like to equalize

    If you cannot find frequency responses of your headphones online and have no means to measure it (see here for a suggestion on how to make relative measurements), trust your ears and experiment.
    There are plenty of measurements available for the Apple earpods, and I measured them myself with a lab-grade sound recording device.
    Below is the resulting measurement.
    3284932.jpg

    Note that there is virtually no signal being visible below 1 kHz. The reason for this is that I simply held the microphone of my recording device very close to one of the earbuds. Earbuds and in-ear-monitors (IEMs) rely on the fact that they sit either in the ear or in the ear canal which will provide some level of seal that helps with amplifying the low frequencies that cannot be measured in "free-field". Hence, low frequency response is critically dependent on the eartips used. When listening to these earpods I noticed some harshness which I attribute to the two peaks (resonances) at around 2 kHz and 6.5 kHz. I will be addressing these two "shortcomings" with the below filter design.

    Step 2: Design the biquads

    You will either need to have Matlab (somewhat unlikely as it is very expensive for non-students) or Octave (see previous post) installed.
    I will be showing the process with Matlab, but the commands for Octave are identical. The plots are formatted slightly different, though.

    Step 2.1: First biquad - notch filter to reduce the gain around 6.5 kHz by 6 dB

    Type
    Code:
    help wcd93xx_sos
    to learn about the options that the biquad design tool offers.
    A possible way to design the notch filter is
    Code:
    [biquad1, SOS1]=wcd93xx_sos(6500,3,-6,48000,7);
    The design tool check for filter stability (should always be) and minimum phase and returns the result on the command line. A filter that is not minimum phase may introduce artifacts to the audio, see e.g. here.
    The resulting filter that is returned by the design tool is shown below
    3284933.jpg

    You can see that, indeed, the biquad attenuates signals around 6.5 kHz. The width of the "cone" can be controlled by the Q-factor, which is the second input parameter to the wcd93xx_sos function (chosen to be 3 in this example). This plot also shows how the phase of a signal traveling through this filter will be modified. Phase modifications are less audible than amplitude modifications, and I do not expect this particular filter having much audible effect due to it modifying the phase.

    Step 2.2: Second biquad - notch filter to reduce the gain around 2 kHz by 3 dB

    Similarly to the above steps, this biquad can be designed with
    Code:
    [biquad2, SOS2]=wcd93xx_sos(2000,3,-3,48000,7);
    and the resulting filter is shown below
    3284934.jpg


    Step 2.3-2.5: third-to-fifth biquad - any additional designs
    This particular example does not have any additional filters, but up to three more biquads can be incorporated. Make sure that you call them with the following command as the output parameters have to have a specified name (biquad and SOS)
    Code:
    [biquadN, SOSN]=wcd93xx_sos(F0,Q,AdB,fs,type);
    where N={3,4,5}.
    Important NOTE: be very careful if you decide to choose AdB>0, i.e. if you want to apply gain to the signal. It is important to understand that music material is mastered to take advantage of the entire (well, at least the top-end of the) dynamic range. This means that loud sections of music will be very close to digital full-scale which will "max out" the digital representation of the signal. Any further digital amplification of these signals will invariably lead to clipping, which in turn will introduce non-linear distortions. This problem is precisely the reason why I typically avoid any type of sound "enhancement" apps/libraries. If you do feel that amplification of certain frequencies or ranges of frequencies is warranted, then you should appropriately lower the input gain of the IIR filter, i.e. the mixer setting "IIRx INP1 Volume" where x={1,2} in your mixer_paths.xml. The neutral gain setting, i.e. 0 dB, is 84. Each "tick" corresponds to 1 dB. For instance, setting this parameter to 78 will lower the gain by 6 dB while setting this parameter to 90 will increase the gain by 6 dB.

    UPDATE: please read this post on limitations of the IIR filter gain, i.e. "IIR1/2 INP1 Volume" as specified in mixer_paths.xml

    Step 3: Get overall filter and coefficients suitable for the Qualcomm codec

    Within the same Matlab/Octave session, type
    Code:
    wcd93xx_filter
    You will be asked how many biquads you have designed. In this example the answer would be two and the script plots the overall filter (cascade of two biquads):
    3284935.jpg

    The script also prints a series of mixer settings which should be copied to an executable shell script, e.g. /data/local/tmp/apply_filter.sh
    Code:
    tinymix "IIR1 Enable Band1" 0
    tinymix "IIR1 Enable Band2" 0
    tinymix "IIR1 Enable Band3" 0
    tinymix "IIR1 Enable Band4" 0
    tinymix "IIR1 Enable Band5" 0
    tinymix "IIR2 Enable Band1" 0
    tinymix "IIR2 Enable Band2" 0
    tinymix "IIR2 Enable Band3" 0
    tinymix "IIR2 Enable Band4" 0
    tinymix "IIR2 Enable Band5" 0
    tinymix "IIR1 Band1" 248299376 772991194 207835511 772991194 187699432
    tinymix "IIR1 Band2" 264612186 580454136 246076806 580454136 242253536
    tinymix "IIR1 Band3" 268435456 0 0 0 0
    tinymix "IIR1 Band4" 268435456 0 0 0 0
    tinymix "IIR1 Band5" 268435456 0 0 0 0
    tinymix "IIR2 Band1" 248299376 772991194 207835511 772991194 187699432
    tinymix "IIR2 Band2" 264612186 580454136 246076806 580454136 242253536
    tinymix "IIR2 Band3" 268435456 0 0 0 0
    tinymix "IIR2 Band4" 268435456 0 0 0 0
    tinymix "IIR2 Band5" 268435456 0 0 0 0
    tinymix "IIR1 Enable Band1" 1
    tinymix "IIR1 Enable Band2" 1
    tinymix "IIR1 Enable Band3" 1
    tinymix "IIR1 Enable Band4" 1
    tinymix "IIR1 Enable Band5" 1
    tinymix "IIR2 Enable Band1" 1
    tinymix "IIR2 Enable Band2" 1
    tinymix "IIR2 Enable Band3" 1
    tinymix "IIR2 Enable Band4" 1
    tinymix "IIR2 Enable Band5" 1
    Now you can run the script (or multiple ones if you want to do A/B comparisons, a script for passthrough mode can be obtained from the OP) while audio is playing through your headphones. Note, that you will need to have IIR filtering enabled by having copied the modified "headphones" section to your mixer_paths.xml, see OP. Note also that the posted "headphones" section is taken from the Nexus 5. I do expect other phones have very similar if not identical "headphones" sections.
    I applied this example filter to the Apple earpods and measured the resulting frequency response, which is shown as the blue curve in the first plot of this post. Personally, I believe this filter removes some of the harshness in the sound signature of these headphones.

    Step 4: Hardcode your final filter design

    Once you are happy with the filters you have designed you need to copy the coefficients to your mixer_paths.xml file and reboot, so they are applied whenever audio is playing.
    15
    Unlocking the hardware filter in the Qualcomm Snapdragon?

    Ignoring Bluetooth audio for the time being, there are two ways of playing audio on Android
    1. Let Android do all the work (decoding, fx, volume control, and mixing) and send audio to the codec (ADM)
    2. Let the aDSP do the heavy lifting (decoding, fx, volume control, and mixing) and have it send the final audio stream to the codec (ASM)
    The ASM kernel-aDSP interface offers the ability to apply fx to the audio stream. One of the fx options are 12 biquads (they can be queried with tinymix). Unfortunately, the stock kernel on Nexus and Pixel devices cannot take advantage of the biquads as only the MultiMedia1, MultiMedia2, and MultiMedia3 DAI front-ends are defined/enabled. To use the biquads one needs to, at a minimum, enable the plumbing for MultiMedia4 EQ in the kernel, which is the one dedicated to offload processing.

    To make a long story short, the biquads in the aDSP would even theoretically only be accessible when audio is being offloaded to the aDSP. That does not work, however, as the required plumbing is not present. So even if the plumbing were present, only very few apps would be making use of it as most apps do not offload.


    This is very much work in progress. Some random thoughts and discoveries are listed below.
    (The details refer to the Nexus 5, but should be generally applicable to any Snapdragon-based device)

    • There is another set of biquad (?) filters available inside the Snapdragon chip; they show up in tinymix (and in the kernel sources @ msm-pcm-routing.c) as MultiMediaX EQ BandY, where X={1,2,3} and Y=[1(1)12]. This means that the filters are only applicable to streams that make use of one of the first three MultiMedia streams. However, by default, music is routed through MultiMedia5, which is not eligible for filtering. This filter may be a 24-th order IIR filter which would allow more degrees of freedom than the IIR filters in the codec would.
      Simply changing the stream from MulitMedia5 to MultiMedia1 in mixer_paths.xml (section: low-latency-playback) does not work, kernel log error: "MSM8974 LowLatency: asoc: MSM8974 LowLatency no valid playback route from source to sink". Where in the kernel sources is the backend-to-frontend forced to use MultiMedia5 for low-latency-playback?
      logcat error:
      D/audio_hw_primary( 193): enable_snd_device: snd_device(5: headphones)
      D/audio_hw_primary( 193): enable_audio_route: apply and update mixer path: low-latency-playback
      E/audio_hw_primary( 193): start_output_stream: cannot open device '/dev/snd/pcmC0D15p': Invalid argument

      The device #15 is hard-coded in the audio HAL (audio_hw.c: [USECASE_AUDIO_PLAYBACK_LOW_LATENCY] = {15, 15} and msm8974/platform.h: #define LOWLATENCY_PCM_DEVICE 15)

      # cat /proc/asound/card0/pcm15p/info
      card: 0
      device: 15
      subdevice: 0
      stream: PLAYBACK
      id: MultiMedia5 (*)
      [...]

      MultiMedia1 is mapped to pcm0p, MultiMedia2 is mapped to pcm1p, MultiMedia3/4 are not mapped anywhere
      If the music is offloaded, then it seems to always bypass the MultiMedia mixer and directly connect to MM_DL4

      The only way to enable the filters seems to change the hardcoded device number in the audio HAL from 15 to either 0 (MultiMedia1) or 1 (MultiMedia2) and make the corresponding changes in mixer_paths.xml (point to MultiMedia1/2 for the low-latency-playback use-case).
      Filters will only be available in the non-offloaded case.


      UPDATE: applied the above modifications using MultiMedia2; music plays fine, but kernel log reads
      <3>[ 101.532335] q6asm_get_audio_client: invalid session: -1065112576
      <3>[ 101.532453] msm_send_eq_values: Could not get audio client for session: -1065112576
      and the filters never get applied; also, the maximum allowed value for the first filter parameter seems to be 65535; hence the filter implementation differs from the IIR in the WCD93xx codec.
    • Lots of debug information in /sys/kernel/debug/asoc/msm8974-taiko-mtp-snd-card and /sys/devices/sound.29/

    Some new findings:
    Indeed, the filters in the ADSP are a set of biquads, each of which expects the following five parameters (msm-pcm-routing-v2.c):
    • first parameter: band_indx: ranges from 0 to 11
    • second parameter: filter_type: the following types are defined in apr_audio-v2.h
      /* No equalizer effect.*/
      #define ASM_PARAM_EQYPE_NONE 0
      /* Bass boost equalizer effect.*/
      #define ASM_PARAM_EQ_BASS_BOOST 1
      /*Bass cut equalizer effect.*/
      #define ASM_PARAM_EQ_BASS_CUT 2
      /* Treble boost equalizer effect */
      #define ASM_PARAM_EQREBLE_BOOST 3
      /* Treble cut equalizer effect.*/
      #define ASM_PARAM_EQREBLE_CUT 4
      /* Band boost equalizer effect.*/
      #define ASM_PARAM_EQ_BAND_BOOST 5
      /* Band cut equalizer effect.*/
      #define ASM_PARAM_EQ_BAND_CUT 6
    • third parameter: center_freq_hz
    • fourth parameter: filter_gain: -12 to +12 dB in 1 dB increments; format unclear
    • fifth parameter: q_factor: expects the numerator of the Q8 representation; the numerator is always 2^8=256; for example 1.5=384/256, so 384 is the expected parameter

    The basic problem persists: the filters are applied only to MultiMediaX, X={1,2,3}, streams. At least on the devices I've seen, music playback is routed through MultiMedia4 (compress-offload-playback or low-latency-playback use cases, see mixer_paths.xml).
    There are two options, both of which require changes to the kernel sources
    1. add support for EQ on MultiMedia4 path
    2. modify front-end to back-end routes from MultiMedia4 to MultiMedia1
    15
    modified mixer_paths.xml for various Qualcomm-based devices

    The intention of this post is to host the relevant device sections of mixer_path.xml files that enable IIR hardware filtering. Simply replace the relevant section in your mixer with the device-appropriate section below.

    Alternatively, try the attached entire IIR-prepared mixer_paths.xml file for your device. However, there may be carrier/country-specific variances between mixer_paths files. Only Nexus devices seem to be immune from this carrier-specific madness. Please make sure to backup your current mixer_paths.xml file before proceeding.

    Relevant device in mixer_paths.xml ("headphones") modified for IIR passthrough (bypass) on Nexus 5 and LG G2. Thanks @lordc !
    Code:
        <path name="headphones">
            <ctl name="SLIM RX1 MUX" value="AIF1_PB" />
            <ctl name="SLIM RX2 MUX" value="AIF1_PB" />
            <ctl name="SLIM_0_RX Channels" value="Two" />
    <!-- begin stock routing (commented out) -->
            <!-- <ctl name="RX1 MIX1 INP1" value="RX1" /> -->
            <!-- <ctl name="RX2 MIX1 INP1" value="RX2" /> -->
    <!-- end stock routing (commented out) -->
            <ctl name="CLASS_H_DSM MUX" value="DSM_HPHL_RX1" />
            <ctl name="HPHL DAC Switch" value="1" />
    
    <!-- enabling IIR filter; coefficients can be modified on-the-fly with tinymix, see below -->
       <!-- RX1 plugs into the IIR1 mux and RX2 plugs into the IIR2 mux -->
            <ctl name="IIR1 INP1 MUX" value="RX1" />
            <ctl name="IIR2 INP1 MUX" value="RX2" />	
       <!-- IIR1 filter cofficients (pass-through) -->
            <ctl name="IIR1 Band1" id ="0" value="268435456" />
            <ctl name="IIR1 Band1" id ="1" value="0" />   
            <ctl name="IIR1 Band1" id ="2" value="0" />   
            <ctl name="IIR1 Band1" id ="3" value="0" />   
            <ctl name="IIR1 Band1" id ="4" value="0" />
            <ctl name="IIR1 Band2" id ="0" value="268435456" /> 
            <ctl name="IIR1 Band2" id ="1" value="0" />  
            <ctl name="IIR1 Band2" id ="2" value="0" />   
            <ctl name="IIR1 Band2" id ="3" value="0" />   
            <ctl name="IIR1 Band2" id ="4" value="0" />
            <ctl name="IIR1 Band3" id ="0" value="268435456" /> 
            <ctl name="IIR1 Band3" id ="1" value="0" />  
            <ctl name="IIR1 Band3" id ="2" value="0" />   
            <ctl name="IIR1 Band3" id ="3" value="0" />   
            <ctl name="IIR1 Band3" id ="4" value="0" />
            <ctl name="IIR1 Band4" id ="0" value="268435456" /> 
            <ctl name="IIR1 Band4" id ="1" value="0" />  
            <ctl name="IIR1 Band4" id ="2" value="0" />   
            <ctl name="IIR1 Band4" id ="3" value="0" />   
            <ctl name="IIR1 Band4" id ="4" value="0" />
            <ctl name="IIR1 Band5" id ="0" value="268435456" /> 
            <ctl name="IIR1 Band5" id ="1" value="0" />  
            <ctl name="IIR1 Band5" id ="2" value="0" />   
            <ctl name="IIR1 Band5" id ="3" value="0" />   
            <ctl name="IIR1 Band5" id ="4" value="0" />
            <ctl name="IIR1 Enable Band1" value="1" />
            <ctl name="IIR1 Enable Band2" value="1" />
            <ctl name="IIR1 Enable Band3" value="1" />
            <ctl name="IIR1 Enable Band4" value="1" />
            <ctl name="IIR1 Enable Band5" value="1" />
       <!-- IIR2 filter coefficients; same as IIR1 -->
            <ctl name="IIR2 Band1" id ="0" value="268435456" />
            <ctl name="IIR2 Band1" id ="1" value="0" />   
            <ctl name="IIR2 Band1" id ="2" value="0" />   
            <ctl name="IIR2 Band1" id ="3" value="0" />   
            <ctl name="IIR2 Band1" id ="4" value="0" />
            <ctl name="IIR2 Band2" id ="0" value="268435456" /> 
            <ctl name="IIR2 Band2" id ="1" value="0" />  
            <ctl name="IIR2 Band2" id ="2" value="0" />   
            <ctl name="IIR2 Band2" id ="3" value="0" />   
            <ctl name="IIR2 Band2" id ="4" value="0" />
            <ctl name="IIR2 Band3" id ="0" value="268435456" /> 
            <ctl name="IIR2 Band3" id ="1" value="0" />  
            <ctl name="IIR2 Band3" id ="2" value="0" />   
            <ctl name="IIR2 Band3" id ="3" value="0" />   
            <ctl name="IIR2 Band3" id ="4" value="0" />
            <ctl name="IIR2 Band4" id ="0" value="268435456" /> 
            <ctl name="IIR2 Band4" id ="1" value="0" />  
            <ctl name="IIR2 Band4" id ="2" value="0" />   
            <ctl name="IIR2 Band4" id ="3" value="0" />   
            <ctl name="IIR2 Band4" id ="4" value="0" />
            <ctl name="IIR2 Band5" id ="0" value="268435456" /> 
            <ctl name="IIR2 Band5" id ="1" value="0" />  
            <ctl name="IIR2 Band5" id ="2" value="0" />   
            <ctl name="IIR2 Band5" id ="3" value="0" />   
            <ctl name="IIR2 Band5" id ="4" value="0" />
            <ctl name="IIR2 Enable Band1" value="1" />
            <ctl name="IIR2 Enable Band2" value="1" />
            <ctl name="IIR2 Enable Band3" value="1" />
            <ctl name="IIR2 Enable Band4" value="1" />
            <ctl name="IIR2 Enable Band5" value="1" />
       <!-- IIR1 plugs back into RX1 mux and IIR2 plugs back into RX2 mux -->
            <ctl name="RX1 MIX1 INP1" value="IIR1" />
            <ctl name="RX2 MIX1 INP1" value="IIR2" />
       <!-- set IIR1 and IIR2 gain (84<=>0dB; lower this parameter appropriately if any of the biquads introduce gain) -->
            <ctl name="IIR1 INP1 Volume" value="84" />
            <ctl name="IIR2 INP1 Volume" value="84" />
    
     <!-- uncomment below if desired for cross-feed into second input of the IIR filter (needs kernel mod on hammerhead!) -->
      <!--      <ctl name="IIR1 INP2 MUX" value="RX2" />         -->                                                                                               
      <!--         <ctl name="IIR2 INP2 MUX" value="RX1" />    -->
     <!-- set the gain of the cross-feed (here: cross terms down by 24 dB, i.e 84-60) -->
       <!--        <ctl name="IIR1 INP2 Volume" value="60" />        -->                                                                                               
      <!--         <ctl name="IIR2 INP2 Volume" value="60" /> -->
    
    <!-- end test filter --> 
    
    <!-- back to stock below -->
            <ctl name="RX1 Digital Volume" value="84" />
            <ctl name="RX2 Digital Volume" value="84" />
            <!-- lowered the HPHL[R] Volume from 15 to 10 to significantly reduce THD -->
            <ctl name="HPHL Volume" value="10" />
            <ctl name="HPHR Volume" value="10" />
        </path>
    Relevant device in mixer_paths.xml ("headset") modified for IIR passthrough on Note 4 (Snapdragon). Thanks @bjrmd !
    Code:
    <path name="headset">
          <ctl name="SLIM RX1 MUX" value="AIF1_PB"/>
          <ctl name="SLIM RX2 MUX" value="AIF1_PB"/>
          <ctl name="SLIM_0_RX Channels" value="Two"/>
          <ctl name="CLASS_H_DSM MUX" value="DSM_HPHL_RX1"/>
          <ctl name="HPHL DAC Switch" value="1"/>
          
          <!-- enabling IIR filter in pass-through mode; coefficients can be modified on-the-fly with tinymix -->
          <!-- RX1 plugs into the IIR1 mux and RX2 plugs into the IIR2 mux -->
          <ctl name="IIR1 INP1 MUX" value="RX1"/>
          <ctl name="IIR2 INP1 MUX" value="RX2"/>
          <!-- coefficients below define a no-op filter (0 dB of gain for all frequencies) -->
          <ctl name="IIR1 Band1" id="0" value="268435456"/>
          <ctl name="IIR1 Band1" id="1" value="0"/>
          <ctl name="IIR1 Band1" id="2" value="0"/>
          <ctl name="IIR1 Band1" id="3" value="0"/>
          <ctl name="IIR1 Band1" id="4" value="0"/>
          <ctl name="IIR1 Band2" id="0" value="268435456"/>
          <ctl name="IIR1 Band2" id="1" value="0"/>
          <ctl name="IIR1 Band2" id="2" value="0"/>
          <ctl name="IIR1 Band2" id="3" value="0"/>
          <ctl name="IIR1 Band2" id="4" value="0"/>
          <ctl name="IIR1 Band3" id="0" value="268435456"/>
          <ctl name="IIR1 Band3" id="1" value="0"/>
          <ctl name="IIR1 Band3" id="2" value="0"/>
          <ctl name="IIR1 Band3" id="3" value="0"/>
          <ctl name="IIR1 Band3" id="4" value="0"/>
          <ctl name="IIR1 Band4" id="0" value="268435456"/>
          <ctl name="IIR1 Band4" id="1" value="0"/>
          <ctl name="IIR1 Band4" id="2" value="0"/>
          <ctl name="IIR1 Band4" id="3" value="0"/>
          <ctl name="IIR1 Band4" id="4" value="0"/>
          <ctl name="IIR1 Band5" id="0" value="268435456"/>
          <ctl name="IIR1 Band5" id="1" value="0"/>
          <ctl name="IIR1 Band5" id="2" value="0"/>
          <ctl name="IIR1 Band5" id="3" value="0"/>
          <ctl name="IIR1 Band5" id="4" value="0"/>
          <ctl name="IIR1 Enable Band1" value="1"/>
          <ctl name="IIR1 Enable Band2" value="1"/>
          <ctl name="IIR1 Enable Band3" value="1"/>
          <ctl name="IIR1 Enable Band4" value="1"/>
          <ctl name="IIR1 Enable Band5" value="1"/>
          <!--  IIR2 filter coefficients; same as IIR1  -->
          <ctl name="IIR2 Band1" id="0" value="268435456"/>
          <ctl name="IIR2 Band1" id="1" value="0"/>
          <ctl name="IIR2 Band1" id="2" value="0"/>
          <ctl name="IIR2 Band1" id="3" value="0"/>
          <ctl name="IIR2 Band1" id="4" value="0"/>
          <ctl name="IIR2 Band2" id="0" value="268435456"/>
          <ctl name="IIR2 Band2" id="1" value="0"/>
          <ctl name="IIR2 Band2" id="2" value="0"/>
          <ctl name="IIR2 Band2" id="3" value="0"/>
          <ctl name="IIR2 Band2" id="4" value="0"/>
          <ctl name="IIR2 Band3" id="0" value="268435456"/>
          <ctl name="IIR2 Band3" id="1" value="0"/>
          <ctl name="IIR2 Band3" id="2" value="0"/>
          <ctl name="IIR2 Band3" id="3" value="0"/>
          <ctl name="IIR2 Band3" id="4" value="0"/>
          <ctl name="IIR2 Band4" id="0" value="268435456"/>
          <ctl name="IIR2 Band4" id="1" value="0"/>
          <ctl name="IIR2 Band4" id="2" value="0"/>
          <ctl name="IIR2 Band4" id="3" value="0"/>
          <ctl name="IIR2 Band4" id="4" value="0"/>
          <ctl name="IIR2 Band5" id="0" value="268435456"/>
          <ctl name="IIR2 Band5" id="1" value="0"/>
          <ctl name="IIR2 Band5" id="2" value="0"/>
          <ctl name="IIR2 Band5" id="3" value="0"/>
          <ctl name="IIR2 Band5" id="4" value="0"/>
          <ctl name="IIR2 Enable Band1" value="1"/>
          <ctl name="IIR2 Enable Band2" value="1"/>
          <ctl name="IIR2 Enable Band3" value="1"/>
          <ctl name="IIR2 Enable Band4" value="1"/>
          <ctl name="IIR2 Enable Band5" value="1"/>
          <!--IIR1 plugs back into RX1 mux and IIR2 plugs back into RX2 mux -->
          <ctl name="RX1 MIX1 INP1" value="IIR1"/>
          <ctl name="RX2 MIX1 INP1" value="IIR2"/>
          <!--  set IIR1 and IIR2 gain (84 equals 0 dB)  -->
          <ctl name="IIR1 INP1 Volume" value="84"/>
          <ctl name="IIR2 INP1 Volume" value="84"/>
          
            <!-- cross-feed into second input of the IIR filter (needs kernel mod on hammerhead!) -->
       <!--         <ctl name="IIR1 INP2 MUX" value="RX2" />    -->                                                                                                    
        <!--         <ctl name="IIR2 INP2 MUX" value="RX1" />  -->
              <!-- set the gain of the cross-feed (here: cross terms down by 24 dB, i.e 84-60) -->
       <!--          <ctl name="IIR1 INP2 Volume" value="60" />          -->                                                                                            
       <!--          <ctl name="IIR2 INP2 Volume" value="60" />        -->
    
          <ctl name="HPHL Volume" value="20"/>
          <ctl name="HPHR Volume" value="20"/>
          <ctl name="RX1 Digital Volume" value="80"/>
          <ctl name="RX2 Digital Volume" value="80"/>
          <ctl name="COMP1 Switch" value="1"/>
    </path>