Friday, May 26, 2017

Calibrating My Earphones with the Tympan

At its core, a hearing aid is a microphone, an amplifier, and an earphone.  Each element is critical to the quality of the sound produced by the device.  For the Tympan, it (currently) is simply the amplifier (ie, the electronics) -- you have to bring your own microphones and speakers.  In my last post, I looked at a few different microphones.  Today, I'm going to look at some earphones.  I'm going to calibrate a set of earphones with the Tympan to measure the frequency response and overall loudness.
Earphones:  The earphones that I'm going to calibrate are my Klipsch S4 earbuds.  They are absolutely nothing special.  They are simply low-cost, consumer-level earbuds like anyone might have.  I think that inexpensive earbuds are where a lot of people will start when using the Tympan, so it's where I'm going to start with my earphone calibration.

Setup:  My overall setup is shown in the figure above.  I'm using the the Tympan electronics to drive the earbuds.  The Tympan has been programmed to generate a tone that sweeps from low frequency to high frequency .  The earbuds are fixed in a coupler, which mates to a laboratory-grade microphone (B&K 4192) and data acquisition system.  So, by generating tones at known digital level in the Tympan, I can measure the corresponding sound pressure level produced by the earbuds in the 2cc coupler.
Using the B&K 4949 2cc Coupler
Coupler:  The coupler is a very important part of this setup.  Earbuds are supposed to be worn in an ear canal.  There is very little volume in your ear canal that the earbud needs to drive.  So, if you want to measure the response of an earbud, you need to use a coupler that creates a similar small-volume environment.

Which Coupler to Use?  There are two typical choices for couplers for this kind of testing: (1) an ANSI-standard 2cc coupler, or (2) a so-called "artificial ear".  While the artificial ear is designed to give a more realistic response, a basic 2cc coupler is what the ANSI standard uses for hearing aid assessment, so that's what I'm starting with.  My 2cc coupler is a B&K Type 4946, shown above.
Fitting my earbud to the coupler's adapter.  The output of the earbud should be flush with the hole in the adapter plate.

Fitting the Earbud:  The 4949 includes different adapter pieces to help fit a variety of hearing aid styles.  For my earbud, I chose to use the broad dish-shaped adapter piece intended for in-the-ear hearing aids.  As you can see above, I used putty to hold the earbud in place and positioned the earbud such that the output of the earbud is flush with the opening of the adapter.  It isn't pretty, but I think that it gets the job done.

Completing the Setup:  Once the earbud and adapter plate were fit to one end of the coupler, I inserted the measurement microphone into the other end of the coupler.  I connected the earbuds to the Tympan electronics and programmed the electronics to produce frequency sweeps at different digital levels (my code is on my GitHub here).
My setup used to calibrate the Tympan output with my Klipsch consumer-level earbuds.
Raw Data:  Some raw data from this measurement is shown below (raw data is in my GitHub here).  It is the measured sound output from the earpbud as measured by the microphone in the 2cc coupler.  This microphone is calibrated to units of Pascals from which sound pressure level (SPL) is easily computed.  As you can see, I had the Tympan generate five frequency sweeps, each one at a louder amplitude.

Raw data recorded from the Klipsch earbuds during this calibration testing.
Actual Amplitude:  In my Arduino code, I thought that I was commanding the amplitude to step from 0.1 up to 1.0.  In later analysis, however, I discovered that the ToneSweep object from the Teensy Audio library only produces half the amplitude that you think.  So, when you command "1.0", you actually get an amplitude of 0.5.  I confirmed through by sending the audio out the USB Audio connection to the PC.  It was half.  So, instead of spanning 0.1 to 1.0, my test spanned only 0.05 to 0.5.

Low-Level Noise:  When I looked at this raw data in the frequency domain (see spectrogram below), I was surprised to see this strange, low-level background noise appear at the higher drive levels.  It first appears at a digital drive level of 0.25.  What is this?  At lower drive levels it is not there -- it just magically appears at 0.25.  This is very strange.
Spectrogram of audio produced by the Tympan via the Klipsch earbuds during the calibration tests.  When the drive amplitude is 0.25 and above, a low-level background noise appears.
[Follow-Up: Additional testing suggests that this noise might be related to the Tympan's audio codec (TI 3206) struggling to deliver the required current to these low impedance (18 ohm) earbuds.  That's my current working theory, but I cannot yet definitely confirm that that's the issue.  I'll have to do more examination of this in the future.]

Data Analysis:  From this raw data, I divided the recording to isolate each frequency sweep.  Then I measured the SPL at each frequency (analysis code here, more Matlab functions here).  For each drive amplitude, the measured response is shown in the figure below.  As expected, the frequency response is basically independent of amplitude, which is good.
Measured frequency response for different drive levels.
The Bottom Line:  The figure below is how I chose to summarize these results.  The red line is the measured frequency response and loudness when driving at an amplitude of 0.25, which is when that low-level noise/distortion appears.  The blue line is the frequency response that I measured at my strongest drive level (0.5 relative to full scale) that I then scaled up as if I had driven it at the hardware's maximum drive level (1.0 relative to digital full scale).  As can be seen, at 1 kHz, the system should be able to produce about 117 dB SPL at full drive.
Bass Boost:  A secondary result that is obvious in this graph is that these earphones do not have a flat frequency response.  Not at all.  These consumer-level earphones clearly provide a strong boost to the frequencies below, say, 500 Hz.  I'm guessing that this bass boost this was a conscious decision by the designers in order to appeal to a certain segment of the earbud market.  This is perhaps not the ideal response for a hearing instrument.  I'm curious to test other earphones to see which earbuds might have a more flat response.

Caveat:  An important caveat to this work is that the measurements were taken using a 2cc coupler.  Your ear is NOT the same as the 2cc coupler.  In a real ear, the frequency response could be quite different.  Perhaps the overal SPL produced by the earphones will be higher (or lower).  These are important questions.  So, in the future, I'd like to repeat these tests using an artificial ear, which should better simulate a real ear.  Will the results be different than on the 2cc coupler?  I look forward to finding out!

Sunday, May 21, 2017

Calibrating Microphones with Tympan

One goal of our open source hearing aid platform ("Tympan") is to get people to experiment with new sound processing algorithms but another goal of the Tympan is to enable people to experiment with different microphones and speakers.  So, with regards to microphones, we designed the Tympan electronics to have several different kinds of inputs to allow it to support a range of microphones.  Today, I'll start by calibrating three different microphones with the Tympan.  Then, in a later post, I'll look at how the different microphones influence the self-noise of the system.

Microphones:  The three microphones that I'm using today are shown above.  There's the "Sony Mic", which  is a lapel electret mic intended for picking up voice.  Then, there is the "PCB Mic", which is an inexpensive surface-mount silicon MEMS mic that we've included on the Tympan PCB.  And, finally, there's the "Knowles Mic", which is a high-sensitivity, low-noise mic intended for use in hearing instruments like hearing aids.

Why Calibrate?  If you calibrate your microphones, your audio processing will be able to look at the in-coming digital data and know what sound level is happening in the real world.  Knowing the true sound level in the different frequency bands allows you to tailor your algorithms (amplification, compression, noise reduction) to better respond to a person's specific hearing loss.
Calibration Approach:  To calibrate these microphones, I'm using the approach shown above.  Here, I put the Tympan in my sound chamber and play known sounds at it.  I record the digital values obtained by the Tympan (recorded via its SD card) and compare them to the "truth" that was simultaneously recorded from my laboratory-grade microphone (Bruel & Kjaer 4191).

Truth Microphone:  It is important that the truth microphone be placed very close to the Tympan microphone during this calibration.  Ideally, they'll see exactly the same sound levels.  Pictures showing my arrangement is shown below.
Connecting the Tympan Microphone:  Each of the three test microphones connects to the Tympan in a different way:
  • Sony Mic:  Like many lapel microphones, this mic comes nicely packaged with a 1/8" (3.5 mm) phono plug.  We have a mic jack on the Tympan PCB just for this purpose!  I programmed the Typman to supply a 2.5V bias voltage for this microphone.  
  • PCB Mic: This microphone is just a raw element.  We designed the Tympan to have two of these mics right on the circuit board so that they'd be easy to use.  Therefore, this mic is already wired and simply needs to be enabled in the Tympan software.  
  • Knowles Mic: This is a raw hearing aid microphone.  I soldered some wires to it and connected the wires to the Tympan's "line in" holes that are on the edge of the Tympan PCB.  I provided the bias voltage from a pair of AA alkaline batteries.
Software:  For this test, I wrote an Arduino sketch that allows me to switch the Tympan between the three different microphones.  The sketch saves the digitized audio to the Teensy's SD card.  Additionally, the sketch steps through different levels of gain on the Tympan input (0 dB to +40 dB) so that I can see its effect.  My Arduino code is on my GitHub here.  You'll also need the Tympan library, which you can get here.

Example Data:  Below is a spectrogram of the audio data that I recorded during one of these calibration tests.  The top plot shows the audio from the truth microphone.  The bottom plot is the signal recorded by the Tympan, in this case for the Sony mic.  You can see that I had a loop of audio that I was playing over-and-over into the room.  The audio loop alternates between white noise and a 1 kHz test tone.  In the bottom plot, you can clearly the see the effect of increasing the gain of the Tympan input.
Analyzing the Truth Data:  I started by analyzing the truth data.  Looking at just the white noise periods, I filtered the audio into 3rd-octave bands from 125 Hz to 16 kHz and assessed the average signal level in each band.  Because this laboratory microphone is itself regularly calibrated, I know its data in units of Pascals, which leads directly to units of sound pressure level (SPL).  The SPL that I measured in each band is shown in the figure below.  This is the truth to which I will compare the Tympan data.
Analyzing the Tympan Data:  I performed the same 3rd-octave band analysis of the Tympan data.  Because this microphone is not calibrated (that's what we're doing here) I can only measure the signal levels relative to digital full scale (ie, relative to the level at which the system starts to digitally clip the data).  The plot below shows the 3rd-octave band levels that I measured for the Tympan for the five different gain settings.  The blue line at the bottom shows the levels when the input gain was set to 0 dB while the green line at the top is for an input gain of +40 dB.
Combining with the Truth:  The last step to computing the scale factor is to combine the Tympan data with the truth data.  By bringing the two sets of data together, I can compute how the Tympan digital values (dBFS) can be scaled to reveal the true, in-the-air sound pressure level (SPL).  The figure below shows the resulting scale factor for the Sony mic for the different Tympan gain settings.  It is good to see that the gain setting only appears to affect the overall sensitivity of the system -- that the shape of the frequency response is basically the same for all gain settings.

Repeat for Other Mics:  When I perform this calibration process for all of the microphones, I get the figure below.  It shows that the Sony mic (blue) has a up-sloping response, which is good for a closely-placed lapel microphone.  The PCB mic (orange) is more flat, but has nearly the same sensitivity as the Sony mic.  The most interesting response is the Knowles mic (yellow) because it is nearly 20 dB more sensitive than the other two.  That is quite a difference!
What Gain Setting to Use?  In my tests, I tried a bunch of different gain settings.  Which is the right one to use?  Well, one approach to picking the right gain is to decide what is loudest sound that is likely to be seen.  Let's assume that 120 dB SPL is the loudest sound that we want the Tympan to handle  Then, we look at our sensitivity numbers and choose a gain setting that permits this maximum SPL without clipping.  Using the data for the graph above, the calculation goes like this:

  • Sony Mic at 120 dB SPL yields a digital level of: (120-94) + -48.6 = -22.6 dBFS.  This means that it has 22.6 dB of excess headroom.  I can safely set the gain to +20 dB.
  • PCB Mic at 120 dB SPL yields a digital level of: (120-94) + -47.4 = -21.4 dBFS.  This means that it has 21.4 dB of excess headroom.  I can safely set the gain to +20 dB.
  • Knowles Mic at 120 dB SPL yields a digital level of; (120-94) + -28.9 = -2.9 dBFS.  This means that it only has 2.9 dB of headroom.  For this mic, I would leave the gain at 0 dB
We're Calibrated!  With this testing, we've now calibrated these three microphones with the Tympan.  We know the frequency response and we know how to relate our recorded digital values to real-world SPL numbers.  We also have some guidance as to what gain value we should use for each microphone.  We got a lot done!

Follow-Up:  I've added the raw data and my Matlab analysis files to my GitHub repo.  You can get them here.

Saturday, May 20, 2017

Enclosing the Electronics

After designing and building our custom electronics for the Tympan, it was clear that we needed an enclosure to hold it all together.  Using zip ties (my usual go-to solution) just wasn't good enough.  I needed something more proper to wrap around and protect the electronics.  I was kinda nervous about taking this step because, if you've never designed an enclosure before, it is HARD!  Here's a quick picture tour of how I got to a workable enclosure.  Yay pictures!
Making Something Attractive?  Normally, one wants an enclosure to be both functional and attractive.  As I started into this task, though, I very quickly learned that "attractive" is not really part of my skill set.  It's especially challenging  when the electronics (ie, the bulky guts that I'm trying to cover up with my enclosure) have already been designed and built and can't be altered.  This imposes some challenging constraints on the enclosure.  So, I set aside the goal of making it attractive and I focused on simply making it functional.

First Attempt:  Working with a fellow engineer who knows more about CAD than I do, we decided to use a two piece design that would sandwich the electronics and battery.  We'd 3D print the two pieces of the enclosure and hold the whole thing together with four small screws.  As you can see below, our first design ended up being pretty boxy.

Our first design.  Boxy!
Begin the Revisions!  Seeing it on the screen, I could begin to visualize in my brain how I might want to hold and use the device.  I decided that, if I was wear this device, I'd most likely keep it in my pants' pocket.  I decided that the boxiness of its nose would make it hard to slide into and out of my pocket.  So, we revised the design to make the bottom cover slimmer and we put big slope on the top cover.

Revising the design to make it slimmer and easier to slide down into a pocket.
Iterate, Iterate, Iterate!  With the overall configuration taking shape, we began to make lots of little changes to accommodate the battery wires, to give better access to the volume pot, and to expose the LEDs (box #3 below).  At this point, I decided that the sloping nose made it harder to get your fingers around the volume pot, so we we removed the slope (box #4).  This is the design that we used for our first 3D print.

Real-World Experience:  With the real, physical print of design #4, we were able to put it all together for the first time -- electronics plus battery plus top and bottom enclosure.  Almost immediately, I realized that I needed access to the Teensy's reset button, which was covered by our enclosure.  Oops!  So, we added a round hole to expose the reset button (see #5 above).  Then, we further expanded the hole to expose the SD card (#6 above).  Unsure if the hole was big enough, we took our 3D print of design #4 and used a dremel tool to cut the square hole (see picture below).  It wasn't pretty, but the hole worked just fine to access the SD card.
The opening to expose the SD card isn't attractive, but it does work.
Final Design.  After all of these iterations, we got to our final design, which is shown below.  The CAD files for this design are shared on the Tympan GitHub here.
Our final design.  Download it from our GitHub!
SLA vs FDM.  We printed a couple of copies of our final design using SLA.  The quality of the translucent SLA prints was fantastic, especially the surface finish.  But, the SLA prints were expensive -- too expensive to make lots of copies.  Luckily, I got hooked up with the good folks at Tangible Creative, who were able to FDM print 10 sets of enclosures for about $10 a set.  They came out pretty nicely (see picture below).  Sorry about the boring black color.  Next time I'll pick something more fun!

Fitting the Tympan electronics into the FDM-printed enclosure.
Make it Pretty!  So, while I ended up with an enclosure that does the job, it has plenty of room for improved aesthetics.  If you think that you can make it better, take our design files and make something better!  Please!

Tuesday, March 14, 2017

Tympan Electronics and Its Self-Noise

After measuring the audio performance of the Teensy Audio Board (see my previous post), I felt that I needed something better.  If I wanted my open-source hearing aid ("Tympan") to sound good, I needed a quieter audio interface with a bigger dynamic range.  So, with help from friends and colleagues, I decided that we should build our own.  Today's post gives a quick overview of its design and then I'll present some measurements of its performance.  Were we successful?  Were we able to get better dynamic range?  Let's find out...


The Audio Codec is the Heart:  The picture above shows the Tympan audio interface.  The heart of the board is the audio codec at the top-center.  An audio codec is a highly-integrated chip that, among other duties, does all of the amplification and digitization of the incoming analog audio signals.  Choosing the right codec and then properly designing the circuit board around it are both key elements to achieving a low-noise design with maximum dynamic range.

Choosing an Audio Codec:  The Teensy Audio Board uses the SGTL5000 audio codec.  Presumably it was chosen because it is small, low-cost, low-power, and has a built-in headphone driver.  For my Tympan audio interface, I want all these same features, but I also want it to be quieter.  After looking at a bunch of options, and after talking with colleagues who have experience with a variety of TI parts, we chose to go with the Texas Instruments TLV320AIC3206 (product page here).  It's got many of the same features as the SGTL5000 but promises better audio performance, though at the cost of a few extra bucks per chip.  If it gets me the wider dynamic range that I want, I'll be very happy with that trade-off.

Circuit Design:  Like with the SGTL5000 on the Teensy Audio Board, the TI 3206 needs both the I2C and I2S buses to communicate with the host processor (the host processor being a Teensy 3.5 or 3.6).  Also like the Teensy Audio Board, we will run the TI 3206 in "slave" mode, where all clocking is provided by the host processor.  In other words, our connections to the TI 3206 parallel the connections used by the SGTL5000.  Therefore, in designing our Tympan circuit, the schematic for the Teensy Audio Board was a great help.  Yay for open source!  And, to continue the sharing, our own schematic is available on the Tympan GitHub here.


Software Driver:  After laying out the PCB and getting it fabricated, we had to write software to allow the Teensy and the TI 3206 to talk to each other.  Since I wanted to fit within the Teensy Audio ecosystem, we needed to write a "AudioControl" module that configures the TI 3206 to be in the proper I2S mode so that Teensy's existing I2S functions can successfully transfer audio data to and from the codec.  Luckily, I've got a buddy (Brendan, of FlexVolt fame!) who dived in and figured it all out.  His Arduino/Teensy compatible "AudioControl" module is now on the Tympan GitHub (h-file is here, cpp file is here).  Thanks, Brendan!

Measuring the Self Noise:  Once Brendan got the software side of things working [and, in the process, finding errors in the Tympan design -- we erroneously swapped "DIN" and "DOUT" by accident!  Oops!  The schematic above has been corrected.], I turned my attention to measuring the new system's audio performance.  My primary concern was the noise floor of the new hardware.  Was it better than the Teensy Audio Board?  To find out, I used a raw 3.5 mm stereo plug in the Tympan's input jack and shorted both the left and right inputs to ground.  Now, when I start recording, I should only see the Tympan's own self-noise.


Arduino Sketch:  Now I need some software to do the actual recording.  So, starting from the Arduino sketch used to record the noise for the Teensy Audio Board (here), I swapped it over to use the Tympan audio board instead of the Teensy audio board (new version here).  This sketch digitizes the input audio (which has been shorted to ground) and sends the digital samples over USB to be recorded on the PC.

Results, USB Audio:  With the inputs shorted on my Tympan board, and with me recording the audio via USB in Audacity (as discussed here), I recorded the self-noise of the Tympan.  The spectrum of the self-noise is shown below.  It's a pretty flat spectrum, which is always nice to see.  The only unexpected feature is the increase in the noise seen at the highest frequencies.   What is that?
It's a pretty nice spectrum, except for the bump up at the high frequencies.  What's that?!?
USB vs SD:  The recording above was taken via USB.  Therefore, the USB cable was attached to the Teensy/Tympan.  USB cables are notorious for injecting noise.  Frankly, I surprised that the spectrum shown above is as low and as flat as it is.  To see if the USB connection was the cause in the bump at the higher frequencies, I revised the sketch to record the audio to the Teensy's SD card instead of sending it over USB (new sketch is here),  Re-running my test, I see that the high-frequency hump is gone!  Now that's a beautifully flat spectrum...
By disconnecting the USB cable and recording the audio via SD card, the bump in the spectrum goes away.  Now the self-noise is nice and flat.  Excellent.
Noise Floor Comparison:  My primary goal for the Tympan board was to have a lower the noise floor (and thereby increase the dynamic range) than I saw with the Teensy Audio Board.  To see if I was successful, I used my recordings to compute the total self-noise across the frequency range of 125-8000 Hz. This is the frequency range most relevant for my hearing aid work.  I assessed this broadband self-noise value for the Teensy and Tympan boards across a range of analog gain settings.  The result of this noise analysis is shown in the figure below.
Apples-to-Apples:  The Teensy and Tympan boards have different ways of specifying the input gain.  Ideally, I'd be able to set the same amount of gain for each board, but that wasn't possible.  So, to  align the data in the most fair way, you can see that plot the values as function of the "maximum allowed input signal".  As expected, increasing the gain decreases the max allowed input.  So, at any given value for max allowed input, it is a fair to compare between the two systems.  As can be seen, the Tympan audio board does indeed have lower noise than the Teensy audio board.

Dynamic Range Comparison:  Another way to express this same data is to show the dynamic range of the system.  The dynamic range is the difference between the max allowed input signal and the system's noise floor.  I want as wide a dynamic range as possible, so as to better mimic the human ear.  As can be seen below, the Tympan audio board does indeed provide greater dynamic range than the Teensy audio board.  Specifically, the Tympan is getting 92.4-94.6 dB of dynamic range (in the 125-8000 Hz band) versus 80-81 dB for the Teensy Audio Board.  This is quite an improvement!
Keeping Perspective:  While I am very pleased with the performance of the TI 3206 on the Tympan audio board, it's important to remember that the Teensy audio board has some significant advantages in other areas.  First, you can go buy your own Teensy Audio Board right now, whereas you can't (yet) get a Tympan.  Second, the Teensy Audio Board is remarkably inexpensive.  It's hard to see the Tympan board ever being that inexpensive.  Yes, PJRC did a fantastic job making a good piece of hardware at a fantastic price.  That's for sure.

Next Steps:  I'm going to use the Tympan as a platform for open source hearing aid experiments.  But, a hearing aid is more than just electronics.  So, my next steps are to start adding in other elements like microphones and earphones.  And, I have to get back to making audio processing algorithms!  That's where the fun really happens!

Sunday, March 12, 2017

Teensy Audio Board Self-Noise

When working with audio, one typically wants as quiet a system as possible -- no one likes listening to hiss.  So, when I started working with the Teensy and the Teensy Audio Board, I applied a critical ear to the board's performance.  While the Teensy Audio Board was very fun, quite easy to use, and relatively inexpensive, I found that it was noisier than I like.  In this post, I'm going to illustrate the noise levels that I recorded and I'll show a workaround that helped remove a lot of that noise.


Setup:  For these tests, I used a Teensy 3.6 attached to Teensy Audio Board.  I then wrote an Arduino sketch (shared on my GitHub here) that would record audio from the line-in and send it to both the headphone output as well as to the USB Audio output.  I recorded the digital audio coming through USB using Audacity (as discussed here).  As shown in the picture above, To try to isolate just the self-noise of the system, I shorted the inputs using a jumper across the Teensy Audio Boards line-in terminals.  This should be as quiet as it can get.

First Results:  The audio that I recorded (shared here) sounded funny to my ear.  If I turned it up, I could hear a very annoying unsteady whining noise.  Looking at the spectrum (see plot below), I could clearly see some narrowband tones starting around 2 kHz.  That's right where one's hearing is most sensitive, which makes this kind of self-noise particularly problematic.  It's definitely not what you want if you're trying to make a hearing aid type of device.  What could be the cause of this?

Disable the ADC's HP Filter:  After a bunch of messing around, I finally stumbled across this post ontthe Teensy forum.   Here, "Raj" stated that the SGTL5000 audio codec (which is at the heart of the Teensy Audio Board) has a high-pass filter in its ADC.  He said that this HP filter was introducing a lot of noise and he shared the command for disabling that filter.  When I put the command ""adcHighPassFilterDisable();" into my sketch and re-ran my tests, those annoying narrowband tones in the Teensy Board's self-noise went away (audio file here).  Ahhh.  That's so much better.

Varying the Gain:  The data shown above were taken with the input gain of the Teensy board set to "10" (ie, full-scale input spans 0.56 Vpp).  There are other gain settings that one could use.  So, I repeated my measurements at other gain levels to see if the input-referred self-noise was better or worse.  The results are below.

 As can be seen, the noise with the HP filter (the blue line) is noisier in all cases than when the HP filter is disabled (orange line).  So, as long as you can handle a little DC offset in your audio data, you should definitely disable that filter.

A second finding is that the apparent (input-referred) self-noise goes down as the gain increases.  So, if you have really quiet signals that you're trying to record, turning up the gain might help reveal those signals.

Dynamic Range:  The trade-off with turning up the gain is that the maximum signal level that can be recorded without clipping also goes down as the gain is increased.  For many applications, therefore, it is just as important to look at the dynamic range for the system.  The dynamic range is the difference between the maximum input level and the noise floor.  When I run the numbers for the Teensy Audio Board, I get the results below.

Here, I see that, when the HP filter is disabled (the orange line), the Teensy Audio Board has about 81 dB of dynamic range.  And, I also see that the dynamic range is basically independent of the gain setting.

Is it Good Enough?  So now we're down to the hard question:  is 81 dB of dynamic range good enough for my needs?  Well, it is a pretty decent number, especially for the price of the Teensy Audio Board.  But, for my use as part of an open-source hearing aid, where I need to amplify quiet sounds while not distorting on already-loud sounds, I think that I need a bigger dynamic range.  Normal human hearing spans on the order of 120 dB of dynamic range.  I think that I need hardware that'll get me at least a little closer to that number.

Motivation for a New Board:  The results shown here were my motivation for building the audio interface (the "Tympan") that I introduced in my last post.  In my next post, I'll share some details of that audio interface and I'll perform these same self-noise measurements to see if I was able to get a wider dynamic range.  Making wide dynamic range systems isn't easy, so it'll be exciting to find out how I did!

Follow-Up:  I measured the self-noise and dynamic range of the new Tympan board.  It's pretty good!  If you're interested, you can see the results here.

Friday, March 3, 2017

Unifying the Electronics to Make "Tympan"

After having lots of fun with the breadboard version of my Teensy Hearing Aid, I decided that it was much too bulky and fragile to be of much use.  So, with some help, we designed and built or own custom audio interface for the Teensy 3.6.  This new audio interface would take all of the elements of my breadboard system and bring them together into one unified board.  Stick a Teensy 3.6 on top and it's good to go!
As you can see, it's a lot smaller than my breadboard system.  Most importantly, there are no loose wires flying around to get caught on things or to pick up noise.  It's much more robust, thereby enabling you to be mobile.  And being mobile is the best way to trying out your new audio processing algorithms.   Being mobile is where things get fun.

On of my favorite features is that the board has two tiny little microphones built into the board itself.  So, for quick trials, all you need to do is plug in some headphones and you're ready-to-go.  I even put a hole in the PCB so that you can clip in a lanyard thing and wear it around the neck.  Sure, it's pretty nerdy to have raw, unenclosed electronics dangling around one's neck but, hey, I'm not afraid to show my geek pride.
When our new board is combined with the built-in capabilities of the Teensy, you've got a nice and compact little system for developing new audio processing algorithms.  It's got a lot of really useful features!
Now that I've got this cool little piece of hardware, I've got some colleagues who are going to start working with it, too.  As more folks work with it, we figured that it needed a name.  After trying out a bunch of names on friends, colleagues, and in the open-source community (and seeing what was available for handles at sites like GitHub), we chose the name Tympan.  As an open-source hearing aid device, that name seems pretty decent to me!

Follow-Up: If you're interested in the schematic and BOM, we've put them on Tympan's GitHub.  The original "Rev A" design is here.  The first hardware that is for sale ("Rev C") is here.

Follow-Up:  I measured the self-noise and dynamic range of the Tympan board.  It's pretty good!  You can check it out here.

Follow-Up:  We designed an enclosure for the Tympan.  You can check it out here.

Wednesday, February 15, 2017

Faster Log10 and Pow

After my previous post, which showed a tremendous increase in speed by choosing float-specific math functions (ie, "log10f") versus their generic counterpart (ie, "log10"), I still wasn't satisfied with the speed of my audio processing on the Teensy 3.6. I had some calculations that went into and out of dB space, which means I needed to do lots of log10f(x) and pow(10,x) function calls. I needed them to be faster.  Here's how I did it...

They Should be Faster:  The figure above shows the number of CPU cycles that I measured for each math function.  The red bars are using the standard math function.  What caught my eye was that the log10f() function is slower than the logf() function.  They should only be different by one multiplication (ie, 1-2 cycles).  Similarly, I was surprised that powf(10.0,x) was so much slower than expf(x).  It seemed like I ought to be able to accelerate these functions.

Faster powf():  I started with powf() it was in the most need of acceleration.  Because I did not need a general powf() command that could do any base number, I could optimize specifically for powf(10.0,x).  Through the rules of exponential and logs, I wrote my own macro that executed pow in terms of exp:

//powf(10.f,x) is exactly exp(log(10.0f)*x)
#define pow10f(x) expf(2.302585092994046f*x)  

Note that the reformulation above is not a numerical approximation.  If you had all of the digits for that 2.302 constant, this would be an exact substitution for pow(10,x).  Yet, as you'll see in a moment, it is 3 times faster.  Way faster with no loss in accuracy.  Wow!

Faster log10f():  Similarly, for log10f(), I started with a reformulating log10 in terms of log.  While that was effective, it was only a modest increase in speed.  I wanted it even faster.  So, in a post in the ARM community forum (here), I found that someone (thanks Dr. Beckmann!) had reformulated log10f using log base 2 and then further accelerated it using an approximation for log base 2 that exploits way that single-precision numbers are represented in memory.  It's a pretty neat solution.

So, using this, my complete substitution for log10f() is shown below.  The log2 approximation function is at the end of this post.

//log10f is exactly log2(x)/log2(10.0f)
#define log10f_fast(x)  (log2f_approx(x)*0.3010299956639812f)

Because this reformulation uses an approximation for log2(), it is not an exact substitution.  But, over the range of input values that I explored, the resulting error seemed to be less than 0.05%, which is good enough for my needs.


Faster by 3x!  In looking at the speed of these to reformulated functions, I saw that my log10f approximation was 2.8x faster than the standard log10f().  Similarly, I found that my pow10f function was 3.0x faster than the standard powf(10,x) function call.  That's a pretty nice acceleration!  I'm pleased.

As promised, here's the fast log2 approximation (source):

// This is a fast approximation to log2()
// Y = C[0]*F*F*F + C[1]*F*F + C[2]*F + C[3] + E;
float log2f_approx(float X) {
  float Y, F;
  int E;

  F = frexpf(fabsf(X), &E);
  Y = 1.23149591368684f;
  Y *= F;
  Y += -4.11852516267426f;
  Y *= F;
  Y += 6.02197014179219f;
  Y *= F;
  Y += -3.13396450166353f;
  Y += E;
  return(Y);
}

Wednesday, February 8, 2017

For Speedy Float Math, Specify the Float Version

When programming up my audio processing algorithms on the Teensy 3.6, I sometimes find that the math operations are much slower than I was expecting.  Yes, some things are super-fast: arithmetic, FIR filters, FFT.  But did my code with the logarithm run so slowly?  Well, it turns out that I was using the wrong function call.  If you use the float-specific version of your function, you get tremendously faster floating-point speeds.  Don't rely on the compiler; be explicit and call it yourself!
As you can see in the graph above, I measured how long it took to complete several different math functions when using floating-point data.  The interesting part is that each math function can be called in two different ways: (1) using its generic form such as sqrt(x) or (2) using its explicitly floating-point form such as sqrtf(x).  In all cases, the explicit floating-point form was much faster.

Being a Matlab programmer, my fingers generally type out the generic form of the function.  Being naive, I had assumed that the compiler would detect my data type and automatically substitute the exact same floating-point function that I would have called myself.  Apparently, I was wrong.  Way wrong.

The table above shows that the square root function benefits the most when using the explicitly floating-point form.  The explicitly floating-point version is over 30 times faster!  The Teensy (well, every ARM M4F) has hardware acceleration for doing square roots.  I'm guessing the sqrt(x) form does not use the acceleration whereas the sqrtf(x) form does.  30x is a huge large increase in speed.

Interestingly, the logarithm and exponential/power functions do not have hardware acceleration in the Teensy.  Yet, when using the explicitly floating-point version, they see a 10x increase in speed.  Stunning.

Why are the explicitly floating-point versions so much faster?  I don't know.  But I sure as heck am going to make sure that all my future code uses them.

Tech Info: This data was measured using a Teensy 3.6 running at 180 MHz.  It was programmed from the Arduino IDE (1.6.13) via the Teensyduino (1.35) add-on.  My code is on my GitHub here.

Follow-Up:  Why is the float-specific version ("logf()") so much faster than generic version ("log()")?  I posted this to the Teensy Forum.  Their replies (here) were pretty definitive.  Under the hood, the generic version (ie, "log()") will do the calculations as double-precision floating point math, which is all in software.  By contrasts, the floating-point version ("logf()") is for single-precision floating point math, which on the Teensy is done with hardware acceleration.  That explains why the float-specific version is so much faster.

Tuesday, January 17, 2017

Basic Dynamic Range Compressor

My last post described the need for dynamic range compression in hearing aids: if you amplify enough to hear quiet sounds, loud sounds will become too loud.  One solution is to include a Dynamic Range Compressor (DRC), which changes the gain depending upon the loudness of the signal.  Because of the DRC, loud sounds will be amplified less than quiet sounds.  Perfect!  In this post, I describe how I implemented a DRC and I show its effect on some simple signals.

Algorithm Overview:  In the figure above, I illustrate the basic signal flow through my DRC algorithm.  It's a feed-forward design with a side-chain that computes the time-varying amount of gain that should be applied.  The function of each block is described below:
  • Pre-Process Signal:  In this block, I apply the "pre-gain", which is the amount of gain that will be applied when the compressor is in its linear regime.  Also in this block, I use a high-pass filter to remove any DC offset in the audio signal.
  • Level Estimator:  For the compressor to vary the gain based on the loudness of the signal, it needs to first estimate the loudness of the signal.  That's what this block does.
  • Gain Calculator: Using the estimated loudness, this block calculates how much to reduce the gain of the system.  It calculates the desired gain reduction knowing the "compression ratio" and "compression threshold" that have been supplied by the user.  It also smooths the compressed gain value through time via "attack" and "release" time constants that have been supplied by the user.
  • Apply Gain: Once the desired compressed gain value has been calculated by the side-chain, this block applies the gain to the audio signal.
Arduino/Teensy Implementation:  I implemented this algorithm as an C++ class that can be called from any Arduino or Teensy program (see AudioEffectCompressor_F32 in my GitHub library here). It is built upon my F32-extension of the Teensy Audio Library.  Because it is so reliant on floating-point operations, it is probably only appropriate for use on the Teensy 3.5 or 3.6 (which have floating-point hardware support) and not the older 3.0, 3.1, or 3.2.


Algorithm Parameters:  In the signal flow diagram below, I show the algorithm parameters that are available for the user to set.  The main tricky part is how to set the time constant for the Level Estimator.  By default, I have this value always scale itself to be 20% of the time constants that the user has set for the Gain Calculator block.  If you don't like this default behavior, you can specify your own value using the method setLevelTimeConst_sec().

Example Sketch:  As part of including this DRC algorithm in my OpenAudio_ArduinoLibrary (GitHub page here), I made sure to include an example sketch called BasicCompressor_Float.  This example code assumes that you have a Teensy Audio Board along with a Teensy 3.5/3.6.  Within the code, there are a couple of options:

  • USB Audio:  For my testing, I chose to send and receive audio over the USB link, instead of via analog audio cables.  See this post, if you want more info on how to use the Teensy's USB Audio link.  To enable USB audio in this sketch, set DO_USB to a value of one.
  • Fast or Slow:  Another choice is how to set the values for the compressor's parameters.  In my example, I have two sets of parameters.  One set gives a "fast" compressor response that is appropriate for quick limiting of very loud sounds.  The other set of values gives a "slow" response that can be used as a automatic volume control.  For my testing, I chose to use the "fast" response, as shown below.


Test Setup:  For my testing, I'm using my breadboard prototype of my Teensy Hearing Aid, which is just a Teensy 3.6 with the Teensy Audio Board (plus microphones, battery, and Bluetooth module, which are not relevant to this test).   I've got it plugged into my laptop via USB, which is also used to send and receive audio to the Teensy via the Teensy's ability to do USB Audio.  On my laptop, I'm using the Arduino IDE to set the compressor's parameters (again, I'm using the "fast" configuration) and I'm using Audacity to send the audio to the Teensy and to record the audio returned by the Teensy.


Testing, Amplitude Sweep:  For my first test, I generated a steady 2kHz tone whose amplitude increases from quiet to loud at a rate of about 6 dB/sec (my test signals are shared here).  This is the signal that I'm sending to the Teensy (also shown in blue in the figure below).  When sending this signal, the compression algorithm on the Teensy returns the signal shown in orange.  If you look at the loudest portion of the audio (starting around time = 8 seconds) the amplitude of the orange output signal looks to be squashed compared to the blue input signal.  This is the effect of the compressor!  It successfully reduced the dynamic range of the audio, as intended.

Quantifying the Response:  One important question is whether the compressor kicked in at the correct amplitude.  To answer this question, I assessed the instantaneous amplitude of each signal, as shown in the bottom plot in the figure above.  It clearly shows that the compression of the output signal begins at an amplitude that is 15 dB below full-scale.  This is exactly the value that was specified in the example code by the call to setThresh_dBFS().  It is pleasing to see that the algorithm works as intended.

Testing, Step Changes in Amplitude:  Another important question is whether the compressor is responding at the correct speed to changes in signal amplitude.  To answer this question, I generated a test signal with instantaneous step changes in amplitude, as shown in blue in the figure below.  The compressor's response is shown in orange.
Quantifying the Attack and Release:  When I extract the amplitude of each signal versus time (bottom plot), you can see that the compressor lags in its response to the step changes.  When the input signal gets louder (say, at time = 2 sec), the compressor's 5 msec "attack" time constant allows the compressor to response very quickly.  But, when the input signal gets quieter (say, at time = 3 sec), the compressor's 200 msec "release" time constant makes it respond more slowly.  In my code, these time constants are used in the traditional sense of "time constant", meaning that 63% of the change (in dB) is achieved in one time constant.  Based on the figure above, it appears that my compressor is responding at the correct speed.  I'm pleased.

Next Steps:  With the dynamic range compressor working correctly (and sounding pretty good when used with my Teensy Hearing Aid's microphones and my headphones), I'm well on my way to completing the signal processing hardware and software for a very basic hearing aid.  The main component that I'm still missing is some sort of frequency compensation to increase the gain on the frequencies that the listener needs to hear better, while reducing gain on those frequencies that the listener already hears well enough.  Stay tuned!

Follow-Up:  This algorithm converts to dB and back.  When doing the log10(x) and pow(10,x), be sure to call the correct versions and they'll run *much* faster.  Check it out here.

Saturday, January 14, 2017

The Need for Dynamic Range Compression

Sitting at an airport for any length of time, you quickly get a sense for how uncomfortable even a moderately loud environment can become.  At first, you can ignore the noise, but it quickly becomes grating and fatiguing and makes you irritable. A similar problem is faced by many hearing aid users. If you’ve got a hearing loss, how do you make quiet sounds loud enough to hear without also making already-loud sounds too uncomfortable?  For hearing aids, the answer is “dynamic range compression”.

If you have normal hearing, you can hear sounds over a wide range of loudness. To illustrate this point, the figure below shows the loudness for a few common sounds: from a whisper up to a table saw.  Quieter than a whisper, sounds start to become too quiet to hear. Around the loudness of a table saw, sounds are so loud that they become very uncomfortable (and also dangerous to your hearing). Between these two extremes, is one's usable dynamic range.  For a person with normal hearing, it might span over 100 dB.  It’s an impressive human capability.
If you have a hearing loss, you might not have this much range.  As shown in the figure below, if you have a hearing loss, your threshold for “too quiet” moves upward.  You can no longer hear hear really quiet sounds.  With a 40 dB hearing loss, you’ll likely lose the ability to hear whispering.  Worse, even normal conversation might feel quiet enough such that it requires a lot more effort to follow.  Losing the ability for easy conversation is when hearing loss really starts to affect one's quality of life.  Or, it's when you start blaming everyone for "mumbling too much".
Hearing loss is complicated, however.  It can affect your perception of quiet sounds differently than your perception of loud sounds. Your sensitivity to quiet sounds might have decreased by 40 dB, but your sensitivity to loud sounds might not have changed at all.  So, despite your hearing loss, that table saw might still feel uncomfortably loud.  The result is that your hearing loss has decreased your usable dynamic range.  You live in a narrower acoustic world.

Historically, this decreased dynamic range was a real challenge when fitting a hearing aid. If you configured a hearing aid to sufficiently amplify quiet sounds, loud sounds could end up feeling way too loud.  As an example, the figure below shows the effect of an old-fashioned linear hearing aid providing a uniform 30 dB of amplification to all sounds. As you can see, the whisper is now loud enough to be heard.  Great!  But, now look at the high end -- the uniform amplification has made the lawn mower and the table saw painfully loud.  Even a busy restaurant is perhaps loud enough to be fatiguing and difficult over time. This is very common struggle that people with hearing loss face.
One approach used by hearing aid developers to make smarter hearing aids that fully amplify quiet sounds, while providing less amplification for louder sounds.  In effect, this approach compresses the dynamic range of the sounds presented in the ear.  As a result, these are called "compression" hearing aids.  The figure below illustrates the behavior of a hearing aid configured for 30 dB of gain with a 2:1 compression ratio that is applied for any sound with an amplified volume of 70 dB.  You can clearly see the "kink" in the line where the dynamic range compression kicks in.
Because of the dynamic range compression, the whisper is loud enough to hear while the restaurant, lawn mower, and table saw stay at reasonable volumes.  This is exactly what we want!  Isn’t it great that we’ve just solved everyone’s hearing problems?

Of course, hearing loss isn’t this simple.  Dynamic range compression does not solve all of the problem of hearing loss.  But, it is a very important tool and is central to most hearing aid algorithms.  So, in a follow-up post, I’ll implement a simple dynamic range compressor for my Teensy Hearing Aid.  The compressor will be a key part of its functionality.

Oh, back to the airport.  When you’re killing time in the airport taking pictures for your blog post, it might occur to you that all of the other travelers can see you making your silly faces for “Too Quiet!” and “Too Loud!”.  Such a realization might give you the giggles.  You have been warned.

[Photo Credit: Blue Memo Productions]
Follow-Up:  I've implemented a basic dynamic range compressor.  Check it out here!