Saturday, May 2, 2020

Sennheiser Ambeo Headset Applications

As I have mentioned in the previous posts about optimizing audio in our Mercedes GLK, I used Sennheiser Ambeo Headset as a measurement device in the car. In this challenging acoustical environment it allowed to achieve better channel matching than a conventional measurement microphone. I decided to make a dedicated post about this headset because I've found some interesting applications for it.

Overview


I discovered this headset at the AES Headphones conference where it was used in conjunction with Magic Leap's One AR glasses. By the time when I decided to buy it for my experiments, Sennheiser had already abandoned its production. Nevertheless, it's still possible to buy leftovers from the stock and used gear.

This is how this device looks:


By comparing it with the image on the packing box it's easy to spot a marketing trick. On the box the controlling unit is pictured from the side, making an impression that it's thin and long. However, in reality this unit is pretty thick and looks a bit ugly:


The headphones themselves are designed to be worn around ears, sports-style. They don't however feel sturdy enough like a real sport-style headphone should—yet another perceptual mismatch. Overall, the look of these headphones isn't too exciting, certainly not as appealing as "iconic" Apple earbuds.

Speaking of the technical side, the only connection option offered is Apple Lightning connector. There is also a companion iOS app, however so far I was only using this headset with Android devices and laptops. This becomes possible using Anker's Lightning-to-USB-C adapter which is a must have device if you happen to own any good Lightning headsets and plan to connect them to other devices besides your iPhone. Anker's connector tech specs explicitly lists the Ambeo headset as a compatible device. As a side note, the adaptor also works great with Lightning cables by Audeze.

The controlling unit has a lot of buttons. Besides three usual media controls, there is also a rocking switch toggling between active noise cancelling, "normal" mode, and "transparent hearing"—when the device uses its built-in microphones to allow any external sounds in. This mode is useful because the headphones are designed for in-ear insertion and actually provide a good noise isolation even without active noise cancelling.

Another switch on the controlling unit activates "padding" for the stereo microphones. The designers intended it for use at concerts to avoid clipping during recording.

Speaking of the microphones, since this device was conceived for "3D" recording, besides the usual headset style mono microphone on the right earphone wire, it also has a microphone housed inside left and right earphone:


Before I bought this device I was thinking that the microphones are behind the grilles on the sides of the earphones, but actually the microphone is placed on the inner side of the earphone and faces the reflecting cavity of the pinna:


Overall, from a regular consumer's point of view, the appealing features of this headset are its noise cancelling function and the ability to create entertaining 3D "dummy head"-style recordings. However, the build of the earphones and bulkiness of the controlling unit (and probably relatively high price) most likely worked against its wide adoption.

Earphones


I didn't plan to actively use this headphone for listening to music, but it's still interesting to check what it is capable of. For comparison I'm using very well known and widespread Shure SE215 in-ear phones.

What you will immediately notice with the Ambeo headset is that it's very bright, up to the point when listening to vocal recordings with a bit of extra sibilance becomes unpleasant. My usual tracks for checking this are "Little Wing" performed by Valerie Joyce on "New York Blue" album, and Madonna's "Hang Up" from "Confessions on a Dance Floor".

On the other hand, this brightness also provides a very strong sense of spatiality that can be heard on Hol Baumann's "Endless Park" theme from "Human" album which sounds much duller and more two-dimensional on SE215.

I don't have a rig for measuring headphones, however I was able to capture reliably the high-frequency part of the transfer function of both Ambeo and SE215 by moving them in a free air close to a measurement microphone (a variant of MMA averaging)—the measurements are only valid starting from about 2 kHz. Then I simply divided these transfer functions and found this huge bump around 9 kHz on Ambeo:


To validate my finding, I used an equalizer first to add more high-end to SE215 and then to reduce the harshness of Ambeo, and it worked. The setting of the high-end equalization on Ambeo is extreme. The right setting seems to be somewhere in the middle between Ambeo and SE215—to add a wide peak of +6 dB Q 0.7 centered at 9 kHz to playback via SE215, and to apply a good dip when playing via Ambeo.

The difference in 1–5 kHz region can also be seen and it results in a more "distanced" perception of vocals. I tried adding a -2.5 dB Q 0.7 filter centered at 2 kHz and this helped adding some "depth" to the sounding of SE215 trading for some loss of clarity. Looks like these two settings result in a more "ambient" perception of an audio program. I suppose the reason for this equalization on the Ambeo headset is due to intention to use it primarily for immersive audio playback—playing back the "3D" sound captured with its microphones.

As a side note, I also liked that I found this equalization curve for Shure SE215, which by default sounds more "closer" and two-dimensional. It works even better if crossfeed is added. This experiment has rekindled my interest in SE215.

One problem that I've found at least with my particular Ambeo headset is the mismatch of the earphones transfer function at high frequencies. First I thought that this was due to my bad measurements—I used a DIY coupler to simulate an ear canal, so positioning of the earphone wasn't super precise. But then I also tried the averaging measurement method mentioned above. With both methods, I was always able to match left and right speakers on other in-ear headphones, except for Ambeo which always yielding rather different curves for the left and right earphones (below is the MMA measurement):


So I came to a conclusion that it must be the headset's fault. However, I can't say that I can hear this mismatch clearly, (especially the one in high frequencies). Still, for a headset of this price which has built-in DSP processing leaving this fairly obvious (via measurements) mismatch between left and right channels seems strange to me.

Microphones


Since my primary intended use of this headset was for "dummy head"-style measurements, I was curious to see how well the left and right microphones are matched and how they are tuned. Note that when this headset is connected to a PC (or Mac), it offers both "mono" and "stereo" recording modes. My expectation was that the "mono" mode uses the headset microphone (located on the right earphone wire) which is intended for communications. However, it turned out that the "mono" mode simply uses the left earphone microphone only. So I'm not sure how to activate the headset mic—perhaps when this headset is connected to an iOS device directly, it uses some special mode not available via the Anker adapter. Not a big loss though.

After seeing the mismatch between the outputs of the left and right earphones I was worried whether left and right "3D" mics are suffering from the same issue. I validated them by placing as close as possible to each other in a fixture (not on my head) and measuring the same sound source. Turned out that the mics are actually matched quite well, and we can see very close measurements when coherence is good. On the picture below the measurements are blanked out when coherence is less than 85%:


The tuning of the mics seems to be for "diffuse field"—with a prominent bump at high frequencies. This is important to know as would I try to tune a sound system to a "flat" curve using these mics, this will result in an excessively bright sound. Here is the comparison of measuring the same sound source in the same conditions using a Beyerdynamic MM-1 microphone with "free field" (0 degrees) calibration:



We can see that microphones of Ambeo start sloping up after 2 kHz at approximately 2 dB/octave rate. I wouldn't be paying much attention to other differences as they are likely due to differences in the microphones placement.

The next validation was to see how the transfer function of the microphones differs when they are inserted into ears. Due to the microphone placement, the incoming sound is now transformed by reflections from the pinna and torso. Below is the graph comparing freestanding vs. in ear microphone placements for the same sound source:


As we can see, the main difference is the prominent dip at approximately 4.8 kHz. I'm not a big specialist on anatomy of human hearing, so I can't say what it is caused by exactly. I tried putting a sound absorbing material on my shoulder and this changed nothing, so I suppose this dip is caused by some interference within the pinna. The wavelength corresponding to 4.8 kHz is approx. 7 cm, so half and quarter wavelength fit ear size.

There is a 23 dB boost in the speech range (300 Hz to 4 kHz)—I suppose this is thanks to the design of the pinna. And also noticeable a significant loss in high frequencies starting from approximately 14 kHz. This can actually explain why I'm not hearing well the mismatch between the left and the right earphones.

The differences in low frequencies are most likely due to variations of placement of the freestanding vs. in-ear and need to be ignored.

While writing this post I've looked up other reviews of Ambeo headset and found that on iOS it's possible to record at 24/96. Unfortunately the Anker adapter only supports 24/48. However, that's enough for my applications.

Applications


Now let's consider a couple of applications for this headset.

Sound System "offline" Evaluation

It can be useful to capture the produced sound field of a sound system for evaluating it later, perhaps in a more comfortable setting. This is similar to the original function of this headset—capturing 3D sound fields for realistic playback recreating the original environment.

There are great notes by S. Linkwitz of how much our perceptual system can ignore the room and focus on the direct sound of the speakers. However, if we reproduce a binaural recording of the system in a room back using the same system, we immediately start noticing all the room contributions (see the paper "Room Reflections Misunderstood?", Section 5). This is a really interesting experiment to try with this headset.

Note that since Ambeo is a binaural headset, not a spherical stereo microphone, the pinnaes of the person making the recording inevitably color the sound. As we have seen in the section above, the filtering by the pinna is non-negligible. I found that it's best to play back these recordings either on Ambeo itself (no surprise here), or on IEMs with close to direct field equalization. Playing on over-ear headphones or via speakers will "apply" the pinnae filter once again.

"Dummy Head" or "Spherical Microphone" Measurements

This is what I was doing when tuning audio in the car. Since the "room" is very small, and the presence of a human body introduces a significant change in the acoustic environment, using in-ear microphones for left and right speakers alignment produced better results than use of a measurement microphone.

To reiterate, I was using Ambeo only for matching the sound arriving into the left ear from the left speaker to the sound arriving into the right ear from the right speaker by equalizing the speakers. The final tonal adjustment was done using MMA averaging and double-checking with known music tracks. As we saw from the measurement of Ambeo's stereo microphones are well matched and are equalized for diffuse field. The dip around 4.8 kHz that occurs when they are inserted into ears (at least, my ears) must be ignored during sound sources matching.

A note of caution here. Ambeo headset is a digital device, not an analog microphone, and unlike pro audio interfaces it lacks external clock input. Since the audio output from Ambeo only goes into its earphones, one will need to use another digital audio interface for audio output. This is where the problem comes in—with two digital devices not synchronized via "world clock" feed there inevitably will be clock drift between them. To illustrate how bad the resulting measurements can be affected check the graph below:


The red trace is the original EQ filter (BTW, it's the SE215 "improvement" filter I was discussing in the Earphones section, with a bit of bass added), the magenta trace is the same filter as measured by Ambeo headset from a playback done via a separate audio interface. As we can see, there is a very serious spectral shift.

What to do about it? In REW the solution is to use the shortest test impulse (128k):


Using a shorter impulse has worse signal-to-noise ratio (there is more visible noise on the green trace) but at least there is almost no spectral shift. In fact, it's a well known problem with REW when it's used with USB microphones like miniDSP UMIK-1. I've seen several threads on forums where people were wondering why the results of their measurements using different log sweep lengths but otherwise the same setup didn't match. I'm really curious why REW allows for multi-device measurements by default.

The clock drift problem is the reason why Acourate only allows using a single device for input and output. With Acourate it's recommended to use even longer sweeps than REW uses, so attempts to "work around" single device limitation by using drivers like ASIO4All will inevitably lead to a severe spectral shift.

SMAART has a very useful feature for tracking the impulse response delay changes automatically. This is in fact the technique I used when aligning car speakers via Ambeo. I had to experiment with averaging settings to find the one that allowed for more reactive compensation of clock drift. Usually, the shorter the averaging is, the better.

Over-ear Headphones Equalization

Here is the full story of how I obtained the graphs above. I put on the Ambeo headset and then put Audeze EL-8 closed back over-ear headphones on top of it. Then I was playing test sweeps via EL-8 and measuring their output using Ambeo. Crazy, right? I don't think anyone at Sennheiser were considering this application of the Ambeo headset. However, as the last graph demonstrates, this setup can actually be used for measuring acoustically the effects of headphone equalization.

Does it mean this $200–$300 headset plus your own head can replace a head simulator for over-ear headphone measurements? Not quite. The trick with the measurement above was that I didn't move or replace the headphones while doing it, I simply was toggling equalization on and off. This allowed for quite reliable comparison of the measurements before and after equalization. What happens if I remove the headphones from the head, put them back again, and make another measurement? The measurement will be different. As people in the headphone industry know, in order to obtain a reliable measurement of headphones one needs to re-mount them several times and then average all the measurements taken.

This is how the derived EQ looks like when I actually re-installed EL-8 several times and used averaged measurements:

The results at high frequencies are not that reliable anymore, the tolerance is only within 2 dB, which is a lot for headphone measurements. This is what will happen if one will try to compare equalization of different over-ear headphones. So, Ambeo isn't a very precise tool for this task, at least for the whole operating frequency range.

However, Ambeo still provides a good reliable output for low frequencies. And in fact, it's the low frequencies where use of a head simulator is required because headphone drivers can only deliver their full bass output when there is a closed chamber between the driver and the ear drum. That brings an idea—we can use a combined measurement of MMA for high frequencies plus Ambeo for low frequencies.

As I've mentioned in the Earphones section, this is a variation of MMA where we slowly move the headphones near a microphone and wait for the RTA measurement with infinite averaging to stabilize. To demonstrate that the produced measurement can be reliable used, here is the EQ derived from this technique, I was holding EL-8 close to MM-1 and moving it slowly, waiting for 100 sampled measurements to accumulate:


As we can see, there is much better tolerance at high frequencies, but below 1 kHz the data is unreliable. And here is where Ambeo comes to the rescue. By merging together low frequency measurement done by Ambeo on a head with the rest of measurement obtained via MMA we can measure over-ear headphones output reliably.

A note of caution—this method is only good for comparing headphones. We measure one headset, then another, then derive the differences in their equalization. There is no way for  measuring absolute frequency response of headphones using this method.

For a practical demonstration I measured filtering applied by Audeze Cipher cable for EL-8. Everyone heard the debates whether or not headphone cables make the sound different. Well, in the case of the Cipher cable vs. analog cable the difference is real because Cipher cable is digital and contains a DSP in it. I noticed that even when the EQ in Audeze app is set to 0 dB at all bands, the sound via Cipher still differs from the sound via analog cable. And I was able to measure this using the technique described above:


For the analog cable measurements I used SPL Phonitor Mini, which has very low output impedance and thus provides adequate bass output. The section of the graph below 1 kHz was obtained from comparing measurements done by the Ambeo headset on my head. There is noise because I turned off FTW gating in SMAART to get a full bass extension. However, we can clearly see that the Cipher cable boosts the bass by almost 3 dB (remember, this is with a "flat" EQ setting in the controlling app!). The section of the graph after 1 kHz was obtained with MMA technique. A "scoop" at middle frequencies can be seen clearly.

I found electrical measurements of the Cipher cable done by user KaiSc on Head-Fi.org. It confirms the 3 dB boost, but not the middle section scoop. Although KaiSc also mentions compression effects from the Cipher DSP at high volume. Since I was testing EL-8 at high volume to obtain adequate free-field output, it's possible that the DSP has thrown in some compression at this point.

Sunday, April 5, 2020

Improving audio in Mercedes GLK350 (X204), Part 2

In Part 1 of this post I explained how I replaced the "basic" speakers with the ones from Harman-Kardon Logic 7 configuration and studied their behavior with AudioFrog GSC610C passive crossover. Now it's time to install the crossover into the car and tune the system.

Installing the Crossover


Initially I was planning to fit the crossover somewhere inside the door—affix it to the middle steel panel. However, the panel has turned out to be quite uneven, to improve its rigidity, I guess—so the crossover unit couldn't really fit anywhere on it. Then I went with the plan B—installed the crossover into the door pocket at the bottom of the door panel (also known as "map pocket"). It took about half of the space, still leaving room for a water bottle. Learning from the hardware designers of Mercedes, I used aluminium rivets to mount it.


Obviously, I had to tap into the wire harness that goes from the cockpit into the door to insert the crossover. And since I had installed the crossover into a removable part of the door, I had to enable the possibility to detach the connecting wires of the crossover. I ended up with the following arrangement:


There is a hatch in the door panel which is used to get access to the electrical wiring of the door without actually removing the former. There was still some room there so I put a screw terminals block that joins elongated wires from the door harness with the wires of the crossover unit. Would anyone need to remove the door panel, they will need to unscrew the crossover wires first.

Checking Tweeter Polarity


Now the most interesting part—squeezing the best possible sound from this configuration. First I decided to check the sound of the door speaker as a unit. The main problem with car door speakers is that the drivers are placed very far from each other. Ideally, one would need to listen to such a speaker from a good distance in order to achieve "blending" between the tweeter and the woofer. Unfortunately, this isn't an option in the car. The other problem is that the speaker components are not equidistant from the listeners' ears. In sophisticated car audio systems this problem is partially solved by installing a lot of speakers and tuning the delay of each speaker driver individually. In my case this wasn't an option though.

One thing I wanted to decide on is the polarity of the tweeter. Initially I just followed the color codes of the wires, attaching brown wires to "-" terminals of the crossover. However, this didn't exclude an option that the tweeter could be wired in reverse polarity at the factory to achieve better integration of the drivers. In order to check which polarity works the best, I opened the door and set up a microphone to be equidistant from the drivers (about 55 cm).


Then I captured the impulse response with the tweeter connected in the initial wiring and inverted. The frequency response didn't differ much:


The response shown is FDW-windowed at 7 cycles to get rid of surrounding reflections. Note that in both cases there is a huge notch between 500–600 Hz. Perhaps, it's caused by some reflection inside the door—the quarter-length wavelengths involved here are 17.2–14.3 cm, and the door isn't fully stuffed with absorbing material, so this seems to be a natural explanation.

However, the frequency response doesn't tell the full story. Here are the impulse responses:


As we can see, they look very much like mirror images of each other, and the inverted one (black) looks more correct to me as it's main peak goes in the positive direction. So I ended up inverting the tweeter polarity. I think it's actually the "natural" polarity for the tweeter, thus it seems that in the "basic" configuration the tweeter polarity was inverted, and I just restored it back.

Taming the Rear Door Speakers


As I mentioned in Part 1 of this post, I didn't replace the rear door speakers due to their peculiar mounting. However, I wanted to find a way to minimize their negative influence on the sound of the front speakers.

Fortunately, Audio 20 unit has an engineering menu (accessible by pressing "Hang Up", "1", and "#" buttons on the keypad simultaneously) that gives access to the equalizer and low- and high-pass filters for each door driver individually. I decided to leave to the rear door drivers the role of low frequency extensions, so I set up for them a low pass filter to 100 Hz with a 12 dB / octave roll-off:


Besides eliminating incoherent sound, this also leaves more current for the front speaker channels in the power amplifier.

Aligning Front Door Speakers


While verifying the polarity of the tweeter, I also checked how similar the resulting door speakers are. They were pretty close, which was good. However, in the car the speaker setup is highly asymmetric due to the presence of the steering wheel and the driver's body.

I was thinking for some time how to approach the alignment. I've found the paper by M. Ziemba "Test Signals for the Objective and Subjective Evaluation of Automotive Audio Systems" which suggested the use of a stereo spherical microphone which simulates human head better than a regular measurement mic or mic array. What I realized is that a real human head with in-ear microphones constitute an even better measurement device. Another advantage of using a real human is that we also perform exact measurement of the actual acoustic crossover formed by non-coincident door speaker drivers and the driver's body.

Recalling my visit to the AES Conference on Headphones I decided to buy Sennheiser Ambeo headset. I learned that Sennheiser has sunset this product, but I've managed to find it through 3rd party sellers. Yet another technical obstacle was that the Ambeo headset was only available for iPhones and thus has a Lightning connector. Thankfully, Anker has developed a Lightning-to-USB adapter which explicitly lists the Ambeo headset as one of the supported products. This allows using this headset with laptops. The Ambeo headset has turned out to be a very useful audio measurement instrument, although not free from issues. I plan to do a separate post about it.

Now my strategy was to align the frequency response of the left door speaker as measured by the left ear microphone with the frequency response of the right door speaker as measured by the right ear microphone. I used Rational Acoustics Smaart v8 in dual-channel FFT mode to be able to look at the correlation graphs for the measured transfer functions. Yet another useful feature of Smaart is the ability to apply gating in the dual-channel mode. Since the car cabin is full of reflections, it's important to ensure that we are equalizing the direct sound within the integration period (the first 10 ms of arrival). I was using an 8 ms window in my tuning process.

The tuning was done by manual real-time tweaking of the Audio 20 parametric equalizer. I didn't use REW because it isn't aware of the correlation between the test signal and the captured audio output and thus it might attempt to correct areas that are simply not correctable because the dips are caused by destructive acoustic interference or other acoustic interaction. Also, by tweaking PEQs manually in real time I was able to see the changes instantly on the analyzer.

This is what I've got for the left speaker as measured by the microphone in the left ear vs. the right speaker as measured by the microphone in the right ear. The levels are matched for the purposes of comparison. In reality the left channel is about 2 dB louder due to proximity.


This looks a bit scary, however this really demonstrates how challenging the car cabins are for accurate sound reproduction. I couldn't make the region of the left channel from 600 Hz to 2 kHz to match the right channel—it seems that the dip is caused by acoustic interference. Also note the 5 kHz dip which is an artifact of measurements with the Ambeo headset.

I've also made a moving microphone average measurement with both left and right channels playing, in the area where the driver's head is (w/o the driver being there, of course) using Beyerdynamic MM-1 microphone with diffuse field calibration profile. Here it is compared to the target curve recommended by AudioFrog:


I've got slightly more bass, but this is easily adjustable using the tone controls. Also, the bass in the car anyway needs a bit of boost while on the road due to engine and wheels noise. As we can see, there is no dip at 5 kHz we've seen on the measurement acquired via the Ambeo headset. However, there is a wide dip at 500–700 Hz likely the same one we've seen on the door measurement, and also a narrower dip at 2 kHz. It can also be seen on the door measurement, and it could be a result of an imperfect crossover overlap. If we recall the measurement showing the effect of the crossover on the HKL7 drivers:


I've set GSC610C to "-3 dB" setting to tame the high frequencies (yellow graph) and we can see that it has the fastest decay at the crossover point, however it's still a bit higher than w/o the crossover (green graph, the topmost in the right part).

Listening Impressions


I checked my usual "spatial" test tracks:
  • track 28 "Natural stereo imaging" from "Chesky Records Jazz Sampler & Audiophile Test Compact Disc, Vol. 3";
  • track 11 "LEDR" from "Chesky Records Jazz Sampler & Audiophile Test Compact Disc, Vol. 1"
For sure, the localization is much worse in the car cabin environment with lots of reflections and asymmetric seating. On the first track, there was no difference between "left" and "extreme left" (and right) drum positions, however, the drum running around the listener still could be imagined well. On the second track, the "going up" sound wasn't actually moving, but the one making an arc from the left to right speaker was perceived realistically—to my surprise.

I also checked some musical tracks and compared them to the sound in Shure SRH1540 headphones matched by volume. The car sound was more "relaxed" and even had spaciousness, it sounded more "enjoyable" than in headphones, which sounded more "forward" and a bit brighter.

Costs


Here is what this upgrade cost me (excluding tools):
  • HKL7 tweeters A2128201002, pair (from a disassembled car): $30
  • HKL7 woofers A2048202102, pair (new): $318
  • Door trim retainer clip A0009917940, pair: $5
  • AudioFrog GSC610C crossover, pair: $194
 Total: $547

Conclusions


This has turned out to be an interesting and challenging project, requiring a lot of research (at a hobbyist level). I'm glad that I started it, and like the result. It's a pity that basic car audio packages do not sound well. Next time I will be buying a car, I will definitely look into "premium audio" options.

Saturday, March 21, 2020

Improving audio in Mercedes GLK350 (X204), Part 1

We have owned our old trusty Merz GLK350 for a while. It was manufactured in 2012—the initial edition of X204, before the "facelift". It's a somewhat practical car in terms of its size and capacity, one obvious downside being relatively high gas consumption even in "economy" mode.


Anyways, this post is not about the car itself but rather about its audio system. Our car was stocked with the basic audio system called "Audio 20". It wasn't sounding very well. I could hear a lot of distortion and initially I was blaming the amplifier for this. However, when I've got a chance to look at the door speaker while overseeing a replacement of a broken passenger glass (a case of vandalism in Palo Alto, CA), I realized that this paper cone with a bit of magnetized steel can't sound good in principle.

Input


At that time we were living in an apartment in Palo Alto so there was no good place to work on the car. The only thing I could do was to modernize the input. My model of "Audio 20" (this name is used by Mercedes for the family of the head units, not for a particular generation) offers the following external inputs: Bluetooth SBC, iPod (the old multi-pin version), USB thumb drive, and Line In (Aux). The first 3 options are obviously outdated, but Line In could be utilized for connecting a modern audio source.

Conveniently, both the USB receptacle and the Line In socket are located in the gloves compartment and can be utilized for connecting a USB bus-powered Bluetooth receiver that supports AAC and aptX HD for streaming audio from a phone. There is no shortage of portable Bluetooth receivers today, the only culprit was to find one that doesn't require turning it on manually. I've found a BluDento BLT-HD "desktop" receiver that I liked for its minimalist design, absence of any buttons and a good analog output. This device turns itself on as soon as power is provided over USB. That means, it gets activated every time I turn on Audio 20, and then the phone automatically connects to BLT-HD, creating a "seamless" experience.

The only issue with BLT-HD is that its line output is a bit too powerful (2.1 Vrms) for the Line In input of Audio 20, so if you turn up Bluetooth volume on the phone too high, the input starts clipping. However, staying at lower BT volume levels works OK.

Replacing Front Door Speakers


Soon after moving to Southern California and now renting a house instead of apartment, I started researching possible speaker replacements. I quickly found that Mercedes was offering 3 kinds of audio system for X204 and its brother W204 (C-Class): the basic one that I have, a slightly better one, and a "premium" Harman-Kardon Logic 7 with 9 cabin speakers and a subwoofer mounted inside the spare wheel. Obviously, the premium system has to have good speakers as no amount of DSP correction could make a bad speaker sound good. Fortunately those different kinds of speakers all use the same housing and connectors so it's entirely possible to swap the bad ones for those from the HKL7 configuration.

After researching on MBWorld and on YouTube, I realized that only speakers in the front door can be replaced with a relative ease. Rear door speakers are attached from inside the door, so instead of removing them properly people usually just cut the paper cone out and stick some other speaker on top of the old housing. I didn't like this idea so I decided to focus on the front door speakers only, and later I've found a way to minimize the impact of the rear door speakers.

The front door speaker system consists of a woofer in the lower part of the door and a tweeter in the mirror triangle. The part number for the HKL7 woofer is A2048202102, and for the tweeter it's A2128201002. I've bought a pair of tweeters on eBay from some disassembled car—the speakers are quite sturdy so it's very hard to damage them. Woofers offered on eBay weren't looking promising—most of them had visible dents, so I decided to buy them as new replacement parts from an MB dealer instead.

BTW, just by comparing how these drivers were built it's easy to predict which of them sound good and which sound bad. The tweeter from the "basic" audio is a kind of a compression driver with a waveguide and a protective harness on top (on the left). From looking at this harness it's easy to see that it will produce reflections right away, resulting in a comb effect. In contrast, a HKL7 tweeter (on the right) is a hard dome with a protective grid.


Woofers for HKL7 also look more promising (see on the photo below on the right, on the left is the "basic" speaker)—aluminium cone on a suspension, but still quite lightweight compared to aluminium cone drivers used for regular speakers.


Replacing the front door tweeter in X204 is a no brainer—no tools are needed, just pop out the mirror triangle, disconnect the old tweeter and put the new one on its place—as I've mentioned, all the housing and connectors are the same. Replacing the woofer is more involved and requires taking off the door cover and dealing with rivets. But in the end it had turned out to be relatively easy, too. The only part that drove me mad was the retainer clip for the door panel (part A0009917940)—it's over-engineered for sure and probably requires some special tool for easy removal. Instead, I bought a spare pair of those clips from the dealer so I could break the old ones and proceed further.


While commencing the replacement I discovered another horrifying (from audio engineering perspective) thing—there is no crossover between the tweeter and the woofer in the "basic" audio configuration. The wire from the head unit is simply split at the door connector. I suppose, in the HKL7 configuration each speaker is driven separately in order to be able to time align the drivers. But just omitting a passive crossover in the "basic" configuration—that's so cheap. Shame on you, Mercedes!

Passive Crossover


I must say, the system with decent front door speakers already sounded noticeably better than the "basic" one. However, I was always returning to thoughts about the crossover and decided to do something about it. Since Mercedes doesn't bother to provide them, I had to look somewhere else.

The problem with passive crossovers is that the driver is part of the schematics, providing necessary load and inductive impedance, thus if we use a passive crossover designed for some other drivers it will not work as designed. It doesn't necessarily mean it won't work at all, just that the crossover point and attenuation will be different.

The other problem is that this crossover needs to be designed for use in an automobile, otherwise it could fall apart from vibration. I've found a company called AudioFrog which manufactures drives and passive crossovers for "hi-fi" auto installations. After looking at the specs of their drivers and comparing them to HKL7 drivers I decided to try AudioFrog's GSC610C passive crossover.


While researching this topic, I've made a couple of measurements of the HKL7 drivers. Here is the tweeter (measured with QuantAsylum QA401 + QA460):


Here is the woofer:


As we can see, the woofer has a nominal impedance closer to 2 Ohm, whereas the speakers GSC610C was designed for (GS10 and GS60) have nominal 4 Ohm impedance.

In order to investigate how GSC610C will work with HKL7 speakers I've built a model of a door speaker:


Yes, I used my experience from building LXmini and stocked all the parts at the plumbing department. Although the model looks funny, it allowed me to experiment with the speakers and the crossover in my room, without causing any harm to the car.

Here are the graphs of the electrical output from the GSC610C crossover when it's connected to HKL7 speakers:


GSC610C offers 3 settings for the high-pass filter, they can be seen on the right part of the graph. The slope of the low-pass filter is very gentle—about 1.2 dB per octave. As a result, there is only -3 dB of woofer output attenuation at the point where it crosses with the high-pass filter. However, don't forget that the woofer is located at the far end of the door, near the driver's feet so there is also acoustic attenuation.

Here is how acoustic output from the drivers is affected by the crossover. The measurements were taken using a microphone very close to each driver, using my model of the door speaker:


Note that the sharp roll-off at low frequencies and the hump at 230 Hz is an artifact of my model. When installed into the door, the woofer goes down to 40 Hz smoothly. The direct output from drivers are the top lines. As we can see, the output of the woofer goes up to 7 kHz (obviously, in break-up mode as this is a 6 inch driver) and has a resonance around 4 kHz (see the arrow). Thanks to the low-pass filter, the resonance gets attenuated. For the tweeter, application of the crossover allows to achieve a smoother roll-off. Overall, use of the crossover reduces interference between the drivers.

After making these preliminary measurements I decided to go ahead and install the crossover into the car. Installation and tuning will be covered in the second part of this post.

Friday, February 28, 2020

Understanding Git, Part 3

The final part of my talk about Git. Here are Part 1 and Part 2.


After familiarizing ourselves with the areas of Git repository and basic commands, let’s dive into practical application of this knowledge. In this last part of the talk we will consider how to manipulate changes to achieve your goals, how to track what other people have done, and how to get out of trouble.


Let’s start with understanding how to make amendments to changes. This is useful to correct any mistakes you’ve discovered after commits have already been submitted into local repository. Also, amending your previous changes is required if you are working with a distributed code review system like Gerrit.

As you can remember, the actual commit objects residing in the repository are immutable and can’t be changed once you have created them. However, what we can do is come up with another set of commits, trees, and blobs, connect the new commits to the commit graph, and update the branch reference to point to the new set of commits.

This is illustrated on this diagram. Imagine we have a master branch which points to commit D. We have realized that we need to amend the last two commits: C and D. We use them as starting points for creating new commits, beginning from the oldest one—C. When we create this new commit we can optionally use a different parent commit for it.

After we have created the last new commit—D' in this case, we reset the master branch reference to point to it. Now although the old commits C and D are still in the repository, Git will consider them garbage at some point as they are not referenced by any branch.

Note that even if we didn’t change the trees of C' and D' compared to the original commits, but just reparented C, it will still be a different commit because the commit hash is calculated from entire commit contents which includes the parent reference. And since C' becomes a different commit with a different hash, D' is also different from D even if it still references the same tree.


That was a generic case of history rewriting, let’s consider the simplest one—amending just the last commit. Git has a special option for the git commit command, which is called --amend. If you haven’t staged any changes, executing this command will only result in updating the commit message of the last commit. If you have staged any files, they will replace corresponding files in the last commit.

As always, you can skip the intermediate step of staging by passing the -a option together with --amend.


And this is an illustration of what happens when you amend the last change. The tree of the commit may change or it may stay the same. The commit itself changes if you change the commit description. The parent of the amended commits stays the same. Your current branch gets updated to point to the new commit object.


Let’s consider a more interesting example of amending commits. Imagine we were developing two features: X and Y in dedicated branches, and now want to integrate them into a single branch.

As we mentioned earlier, one way of doing that is to create a merge commit. If that is not an option, then we can take the changes specific to one of the branches and replant them onto another branch.


This is how we do that using git rebase command. Imagine we decided to take the changes from the featureX branch and rebase them onto the featureY branch. For that, we make the featureX branch current, then we provide git rebase with the identifier of the change that ends the commit chain, in this case this is commit B, and the destination branch: featureY.

What happens next is Git takes the commits from that chain and, one by one, plants them onto the destination branch. Then it updates the branch reference. Note that the current branch remain the same after rebasing, thus the HEAD meta-reference doesn’t change.


Here is another practical example of rebasing, this time using a tracking branch. This scenario happens when you are working on a branch which is being changed in a remote repository by someone else. Recall that by default when you do a git pull Git merges your local changes with remote changes. Instead we can do a rebase by specifying --rebase argument. In this case git will automagically rebase your local changes on top of remote changes. Here is how this happens.


To demonstrate what happens, let’s do the same that git pull --rebase does, but this time manually.
First we sync with a remote repository using git fetch. By comparing positions of the tracking and upstream branches, Git can find out that there are 100 new changes happened upstream, and we have a couple of changes, too. This is in fact the same situation as we have considered earlier with two features. The only difference is that this time one of the branches is a remote branch reference.

Now we do a rebase. We don’t have to specify the fork point and the destination branch to git rebase this time, because Git assumes we are rebasing onto the upstream branch, and it can figure out the fork point itself. After rebasing we have our changes on top of the changes from the upstream, as expected.


Since while we are rebasing we are creating new commits, it’s possible to apply more radical changes rather than just changing a parent or amending files. With Git interactive rebase we can also re-apply changes in different order, skip some changes, or add new ones, and combine multiple changes together. Basically, if you recall our “Patch algebra”, we can do all those operations on actual commits.

For that we use -i (for “interactive”) option to git rebase.


This power of being able to rearrange our changes in the hindsight allows us to apply “working in small steps” philosophy. What this means in practice is that we commit our changes as often as possible. Usually it’s after we have completed some logical step and the code at least compiles, although this is not a requirement. This allows us to rearrange our changes later, or to find a change that has broke everything.

Even if we use a code review system for the project, working in small steps is beneficial because by the time when we are ready to get our changes reviewed, we can group them into bigger chunks.


Imagine we were working on adding a resampler to our audio code. Let’s say, we have implemented the resampler partially, then hooked it up to existing code, then finished implementation, and then have fixed a bug in the integration code.

Now what we can do is fire up an interactive rebase to organize those changes into a more logical form.


After we have run git rebase -i we end up in our favorite text editor. Here we are presented with a list of changes. By default, Git will just pick them one by one, and this will result in no new commits, because no data will change. However, the interactive rebase offers a wide choice of operations listed below.

It is important to note that the commits are listed in direct chronological order (oldest go first), which is opposite to the order normally used by git log.


So what we have done here is we rearranged the changes in order to group together the two changes that implement the resampler. We use the squash (“s”) command to combine these changes and to edit the message of the resulting commit. On top of them we put the plumbing change and the fix for it. For the fix, we use fixup (“f”) command which simply uses the commit message from the first commit.


After we have done with editing the interactive rebase instructions, Git follow them, and this results in a new sequence of commits.


There is also a manual alternative to rebasing, known as “cherry-picking”. We can basically replant changes manually one by one in the order we want. This can be used, for example, if you want to try a new approach in a new branch, but want to reuse some of the work you have done on another branch.

What you do is you use git cherry-pick command providing a list of commit identifiers as parameters. There is no option to squash commits.

In this example, we decided to try to hook up our resampler in a different way, so we start another branch using master branch as a starting point, then we find out the hashes of the commits we are interested in, and apply them in the right order to our new branch. Obviously, this will result in new commits being created.


In the examples above we used a couple of times special syntax to reference commits that are ancestors of HEAD. The time has come to understand it. I called this section “Mini-Languages” to refer to the concept of Domain-Specific Languages. I also introduce Git-specific jargon here that is encountered in the documentation.

Let’s start with what is called “refname”, which I guess means “the name of a reference”. If you recall, HEAD is the name of the metarefence to the current branch.

Obviously, the name of the branch is a refname too, because as we know, any branch is a reference. If a local branch tracks a remote branch, we can retrieve it using “@{upstream}” syntax.

Another Git jargon word is “rev” which is a short name for “revision”, and that means specifying a commit. The canonical way is to use the full SHA-1 hash of the commit. However, it’s usually enough to specify just a few first characters of the hash. For small repositories, something like 5 first characters is enough, and for big ones we might need to provide up to 12.

Since the contents of a branch reference is a commit hash, a branch name can be trivially resolved into a commit. That’s why any Git command requiring commit hash will also accept a branch name.
If we have a rev, which as we remember is equivalent to having a commit, we can reference the Nth parent of this commit by using the “^N” syntax. If N isn’t specified, it’s assumed to be 1.

Since the parent of a rev is also a rev, we can apply the ^ “operator” multiple times moving one parent away with each application. There is a short syntax for this—”~N”. If N isn’t specified it’s assumed to be 1, thus a ~ alone is equivalent to ^.

And finally, this is the syntax we’ve seen when we were talking about reflogs. Recall that a reflog is the list of changes happened to a reference. In order to reference the Nth previous state, we use @{N} syntax.


As you remember, every commit has a tree object associated with it. You can reach that object by adding a colon (:) after the rev. And once you’ve got a tree, you can go down by it and reference files it contains. This way you can designate the state of any file at any commit—kind of a time machine.

Since you typically execute Git commands from some subdirectory of your working tree, Git can use the current path for resolving a relative path. The relative path is specified by prefixing it with “./” character sequence.

Here are a couple of examples. Both assume we are working with the frameworks/av repository of Android. The first example references the state of the file services/audioflinger/Threads.cpp 3 commits ago. The second example assumes that we are currently in the services subdirectory of that repository, and references the state of the same file at the specific commit.

Consult the git revisions help page for the full list of options.


So far, we were talking about how to reference just one commit. It is also useful to be able to specify a section of a path in the commit graph. This is called a “commit range”. A commit range may also denote a set of commits which are disjoint in the commit graph. Commit ranges are frequently used with git log command.

If you provide git log with just one rev, it will interpret it as “all the paths that go down from the commit <rev> to the initial commit”. Actually, if you don’t provide any revision specification to git log it will simply take HEAD. The problem with that is the fact that the path from HEAD to the initial commit is quite long. In order to restrict the range we can use “..” syntax, as shown here: <rev1>..<rev2>. This means “all commits not reachable from rev1 but reachable from rev2 (including rev2 itself)”.

In case of a linear history (when no merge commits present), this is simply the path from the commit rev2 down to but not including rev1. However, with non-linear history the result can be more interesting, as we can see on the diagram on the right.

In you don’t specify rev1 or rev2 in the range, Git will use HEAD.


Similar to printf function in C, Git has a template-based mini-language for specifying how to format the output of git log and other commands that can display commits, like git show. There is a set of predefined formats, called “pretty” in Git jargon. You can also construct a format that suits your needs by using placeholders prefixed with the percent symbol “%”. I’m not listing them here because usage of this mini-language is pretty straightforward and you can look up all the formatting instructions on the git pretty-formats help page.


Now we are armed with the knowledge of how to specify objects we want to view and how to represent the results. Let’s consider the commands that can be used for viewing different kinds of objects in Git.

A versatile command git show can display any repository object: a blob, a tree, or a commit. You can provide either the hash of the object, or a rev, or a path specification we have considered earlier.

The next command is well-known git log. It accepts a commit range, and its output can be limited to only consider certain paths so you can view changes specific only to a certain file or a directory. Don’t forget that you can specify output format and number of commit entries to display to make the output more manageable.

And finally, git status command which we have seen before. Its output can also be scoped to a certain path.


As we have discussed before, Git doesn’t store any diffs in the repository but generates them on request, for example when you invoke git diff command. Remember that diff is always generated from two objects, even if you specify only one or even no arguments to git diff. Here on the slide I listed possible useful options.

Sometimes we are not interested in seeing an entire diff. What we can do then—first, limit diff output to a certain path or paths. We can also ask diff to only display what is called in Git the “diff stats”—the amount of lines changed in every file. We can even only display the names of the changed files, with no extra information.


Of course you know about git blame command in Git. It’s perfect when you can immediately find the commit that made the change in question to the file. However, it’s not always that easy because in big and long-living repositories a lot of refactorings happen that just move lines between files or change formatting. But there is always a way to find the first functional change.

In this example, let’s consider this fragment of code from Android’s frameworks/av repository. Let’s say we want to find out the reason why “FLUSHED” state of a track is also considered as “stopped” state. From git blame output we can see that this line was last touched by someone named Eric in the commit with the hash starting from 81784.

We use git log to check the description of this commit (we could also use git show to view the entire commit), and unfortunately this last change was just code reorganization, so we need to look deeper.


It’s a bit of a problem that the line has been moved from one file to another. We need to find which file had this line before. One way to do that is to go manually through the change, and that’s doable if the change is small. In this case, the change was quite big so I used grep in order to find this line in the output of git show. I instruct grep to show a lot of lines (300) before the match to see the name of the original file and then grep the output once more just to show the line with the file name which diff prefixes with three dashes (“---”), as we can recall from our discussion of the patch format.

So I can see that the line was originally in AudioFlinger.h file. Now I instruct git blame to start looking from the ancestor of the refactoring commit. I use the hat (“^”) here to specify that revision, I could also use a tilde (“~”) if you recall the slide about revision specification. And I specify the name of the file I want to blame.

Now I see the last commit that actually changed this code, it was also made by Eric, and from looking at the description and probably diff, which didn’t fit on the slide so it’s not shown, I can see that I finally have found a functional change.


So git blame is really useful for figuring out commits that have changed a line of code. But changes can also remove lines of code. Obviously, we will not see the removed lines in the output of git blame. In order to see the history of removed lines, we can use the “pickaxe” tool of Git.

Recall that in diffs, each modification of a line of text is described as the removal of the old line of text, and the addition of the new version of that line. Any line can also be removed in one place of one file and added to some other place, maybe in some other file. Obviously, if the change adds a new line, there will be no old version of it. If the change removes a line, there will be no new version.

So what “pickaxe” does—it counts the amount of diff lines that remove a line containing a specified string, and the amount of diff lines that add it back. If these numbers do not match, that means the line has been added or removed in this diff. And this is what pickaxe reports.
In this example we are looking for changes that add or remove string “4.0” from the set of files. We provide -S argument to git log which invokes the pickaxe tool.


Some commits add or remove not just lines but entire files. Once our colleague Jean-Michel couldn’t anymore find a file that he knew existed before. He wanted to find the change that removed that file. That’s actually easy with git log which supports --compact-summary argument which only displays “diff stats”. We limit the scope of git log to that particular file and here is what we see—the last commit that had removed the file.


Managing commits is a complex business and sometimes things do not go as you expect. Remember that Git always allows you to pull out from a multi-step operation like rebasing or merging and get back to a clean slate. To pull out, just re-run the same command with --abort argument. Because all of the listed commands check before starting that there are no changes to the working tree, they guarantee that after aborting you end up in the state you were initially.


Here are some more Git commands that can help getting your working tree and index back to some good known state. First, there is git reset command which, if called with only a revision parameter resets the branch reference to the specified revision. You can always look up any previous state of any branch by examining its reflog, as we discussed earlier.

Second, recall about the git stash command which you can use to clear out of your way any uncommited changes without losing them. If you are sure you want to lose them, use git reset --hard command.

And finally, you can always restore any particular file to a previous state using git checkout command. Recall that Git repository is a time machine that stores the state of any file at any previously committed revision.


There is one thing in version control management that people hate as much as going to the dentist or paying taxes—it’s resolving merge conflicts. Let’s try to understand this problem better using the “patch algebra” we have discussed in the first part of this talk. In terms of patches, on one branch we have a file in state A to which a change M had been applied. Then on another branch the same file has received a change P. Now we are trying to merge these changes together. Recall that any change in the diff has a context. A merge conflict occurs if the change M modifies parts of the file that are included in the context of the change P.

A lot of times Git can work around these discrepancies and still apply the patch. However, sometimes the help of a human is required. What I highly recommend to do is to configure Git to use so called “diff3 conflict resolution style” using the command shown on the slide because it provides the most comprehensive information about the conflict.


Let’s consider an example. The change A which is common to both branches adds a file with 3 lines of text. Now let’s imagine that two persons have made two different changes to this file. The change M was done by a humble developer, while the change P was done by a more optimistic marketing person.


This is how Git presents this conflict to you using diff3 style. First goes the part of the file that hasn’t been changed. Then the change on our branch (change M). Then how was this part looking prior to the change on the remote branch, that is, the parent of the change P. In our case this is actually the same as the state A. And finally, how this part looks with the change P applied.

Now what we need to do is to either select one of the versions, or produce a new version which for example combines both changes. We also need to remove the lines with merge conflict markers. And we repeat this process for every conflicting hunk.


Another common scenario we encounter when working on Android is the need to transfer a change from one repository to another. Here Git offers several options.

The first one is to add the second repository as a remote, fetch its contents into our repository, and then cherry-pick the commit containing the change. The problem we encounter here is that with large repositories fetching objects from another repository will take a lot of time and end up consuming a lot of disk space.

So if we are not interested in the history of the change we are transferring, another option is to use so called “mail exchange” scenario. We use git format-patch command on the source repository to export the change in the form of a diff file that contains all the commit metadata, and then apply this diff to the target repository using git am command. git format-patch produces a series of files, one per commit, so we can end up with lots of files.

Finally, instead of using git format-patch, we can produce a diff using git diff that contains all the changes we are interested in, possibly from multiple commits, and then apply it using git apply command. But since in this case the diff will not contain commit metadata, we will need to provide it manually.


Note that while transferring changes from one repository to another it’s a common situation to encounter a merge conflict, because repositories are likely diverged significantly one from another, like Android internal master and AOSP. If there is a conflict you will be presented with the following instruction from Git.

What you need to do is to go over the files that have conflicts, resolve them, and then stage updated files using git add. Then you can let Git to continue.

As I’ve mentioned before, if anything goes wrong, cherry-picking can be aborted at any moment.


These are the final tips for your Git journeys. First, don’t panic! Remember that Git is a time machine and there is always a way out. Second, remember that with the way Git repository is organized it’s actually hard to lose anything. Most probably, your changes are somewhere in the repository, you just need to find them using one of the tools we have discussed. Third, the most important thing you must care about is files in your working directory—if you haven’t saved changes to them and you overwrite your working tree, that’s it—they are lost forever. So take care of the changes you are doing in your working tree and let Git takes care about the rest.

And finally, if you are losing track of what’s going on, recall the building blocks diagram. Commit objects are the keys to everything, so if you need to find something, always start with finding the corresponding commit.


And there is always more to learn about Git! Here are several recommended sources. First, it’s the free book about Git called “Pro Git”.

If you are a visual thinker like myself, then “A visual Git reference” provides a lot of illustrations on what Git commands are doing to Git objects.

Finally, I’m pretty confident that if you’ve got through this talk you are now able to understand the text of official Git documentation pages :) Invoking git help command with the name of another command as a parameter provides you with pages of text you might now even find helpful.