2017-03-19

Creating the gainstaging utility — Part 3

Initial coding is done (with only some pain — see Part 2), and is available up at its GitHub page. Huzzah!

Download it, and Python 2.7 if you don't have it, and give it a go!

Real-life Usage Example

Consider a signal path in my original post about classical recording, from acoustic space, through a microphone to a voltage, through an amp to another voltage, and finally through an ADC into a digital signal. Lets call them zones 0, 1, 2, and 3 for the acoustic, two voltage and final digital zones.

Using a mic and ADC from that post to provide example figures (Schoeps MK21/CMC6, and Merging HAPI), we now need to work out which zone each clipping or noise component belongs to.

Note: through out this post I state and calculate noise levels without mentioning their time and/or frequency weightings. Stay consistent with frequency weighting and your results should still be a useful approximation.

Quasi-peak or voltage-average time weightings will cause errors with noise summing performed in later which relies on power-summing. For reference, I've used RMS A-weighted specs for all noises, as that is the only one in common between the specsheets of the products in the signal path.

micGain = Gain("13mV/Pa")
micNoise = Level("15 dB SPL", zone=0)
micClip = Level("132 dB SPL", zone=0)
adcGain = Gain("+13dBu", "0dBFS")
adcEIN = Level("-128dBu", zone=1)
adcDNR = Level("-119.5dBFS", zone=3)
adcClip = Level("0dBFS", zone=3)

From this we can construct the gain list using one of the mic gains, a Gain() function for the amp gain (in V/V or dB V/V), and the ADC's gain. Initially setting the amp's gain to 1 (or 0dB), the gainList becomes:

[ micGain, Gain("0 dB V/V"), adcGain ]

Clipping-level-aligned gain

Now we have this, we can workout the gain needed to align the clipping levels of the mics and ADC. With the amp gain set to 0dB, the mic clipping level equivalent at zone 3 (the digital output) will be the opposite of the gain needed to align the clipping levels.

levelAtZone([micGain, Gain("0dB V/V"), adcGain], micClip, 3).dB("FS")

This returns acoustic clipping points of -10.48…dBFS.

As the Hapi has gain steps of 0.5dB, we can set the amp gain to +10.5dB to match the clipping levels. The new gainList is:

[ micGain, Gain("+10.5 dB V/V"), adcGain ]

System noise

The dynamic range of this system can now be calculated as follows:

powersum([micGain, Gain("+10.5 dB V/V"), adcGain], [micNoise, adcEIN, adcDNR], 3).dB("FS")

giving the system noise of -114.91…dBFS. We can report this as an equivalent SPL by changing the zone and dB reference:

powersum([micGain, Gain("+10.5 dB V/V"), adcGain], [micNoise, adcEIN, adcDNR], 0).dB("SPL")

giving 17.055…dB SPL. Referring back to the datasheets, this is a Noise Factor of 2dB. A noise factor of 3dB would show the total of the noise sources excluding the mic are equal to the mic noise itself. A figure under 3dB shows the mic noise is higher than the rest of the system's noise.

Corrections for DNR – output noise assumption, and some helper functions

At very low gains, the inaccuracies from the assumption that the ADC's specified DNR is purely output noise might start to play. Really we should subtrack the EIN's power from the 0-gain noise power to find the true output-portion of the noise. For amplifiers with wide gain ranges, it is not necessary to do this the other way round, as at high gains the EIN will completely dominate the output noise.

At 0dB gain, the EIN's portion of the DNR is:

levelAtZone([micGain, Gain("1 V/V"), adcGain], adcEIN, 3)

which is -141dBFS.

There are extra 'helper' functions in the gainstaging module that are helpful here. dbta() and atdb() convert dB to and from amplitudes, dbtp() and ptdb() convert dB to and from amplitude-squared (i.e. proportional to power), and atp() and pta() perform squaring and square-rooting.

So to find the true output-portion noise:

ptdb( dbtp(-119.5) - dbtp(-141) )

which for this amp/ADC is -119.53dBFS — insignificantly different. For another amp it could be a different story.

Other issues that could be explored are the assumptions that all noise sources have the same spectrum and are not correlated.

Conclusion

It may feel a little clunky if the extent of your mathematical comfort is a four-function calculator, but with this utility, finding sensible gain settings and the extent of their associated compromises need no longer be purely subjective.

If you can find a datasheet for it (and trust the manufacturer—more on that in another post!), then you can likely enter a world of greater headroom with confidence. No longer be a slave to the overzealous timpanist!


PS: Do still use your ears! If your mic amp relies on high gain settings for part of its Common-Mode rejection, digital gain afterwards will bring up both wanted and interfering signal. YMMV.

2017-03-18

Creating the gainstaging utility — Part 2

After messing around with complicated spreadsheet to calculate optimal mic gains, I though it really shouldn't be that hard. So I set about writing a python utility to take care of the heavy lifting.

For more background on what lead to here, see the original post about calculating gains for classical recording and the first post about this utility.

Development update

tl;dr: If you're here for calculating your mic gain settings, skip this section.

I chose Python as the programming language to go with, as I find it very quick way to through together a prototype, and I use it as a CLI calculator whenever I need something more powerful than dc. I'm using Python 2.7, but with all the unicode headache I caused myself supporting micro-Pascals as 'µPa', I really should have used Python 3.x!

For version control I've used SVN and Hg (Mercurial) before, but felt I should try out GitHub, and use that as an excuse to use Git. The repository is at https://github.com/heddmorf/gainstaging for anyone who wants to try out the utility, or laugh at me for the development history!

Once a Test Engineer, always a Test Engineer. I'm naturally drawn towards TDD (Test Driven Development), especially for prototyping. Not only can you clearly see the result your code must be written to produce, but having the test suite first means you know exactly which bits are broken if you go to demo your WIP to someone.

As an added bonus, the doctest tests I write can be used as a Travis CI test suite, with the simple config .travis.yml file:

language: python
script:
  - python -m doctest -v gainstaging.py

Now I can have the comforting "Build: Passing" badge to plaster around.

Regression testing is much easier too. But, your test coverage does need to be at a certain level of coverage to benefit. For instance, I handle going in signal-flow direction by multiplying, and going back up the signal path by dividing gains. When I made multiplying gains together unit-aware, my dividing function stopped working for more than two gains. I didn't have a test for that, so didn't notice this until writing a later function which depended on using the divide function thrice. The time I spent trying to fix the problem in the dependant function when the fault was elsewhere can hardly be called rapid prototyping!

I originally aimed to keep track of all gains and levels within a central object, but having written classes to provide unit-aware levels and gains, I had a little play with it -- I mean performed UX testing, cough -- and found swapping out mic capsules in the simulation etc. was easier if I left the values in the users hands. Provide the tools, rather than straight-jackets!

2017-03-16

Creating the gainstaging utility — Part 1

A utility to help calculate and keep track of levels and gains within the gain structure of an audio signal chain.
Currently, gain settings for microphone input channels are often set by 'feel' using meters while the acoustic source is near its expected loudest, leaving a chosen headroom. With the performance of modern studio equipment, and the availability of data sheets, I believe a range of 'optimum' gains could be calculated.
Handling the different units and positions in the gain structure is a major barrier to being able to do these calculations simply. So lets write a program to help!

Requirements

So, what do we want to do with it?
  • enter levels in a variety of units (inc dB relative to various references)
  • display levels in a variety of units (inc dB based)
  • keep track of levels in different "zones"
  • keep track of gains between "zones"
  • display levels from one zone as their respective level at another zone
  • distinguish between nominal levels, clipping levels, noise levels
  • make it easy to find which clipping point stage is responsible for the entire signal-path's clipping
  • display the power-sum of 'noise' sources
Stretch goal: graphic mode, showing scale for each gain zone, showing levels crossing the boundaries
As an example, I want software help with the following:
mic clips at X dB SPL, mic self-noise is at Y dB SPL, and has sensitivity of Z mV/Pa; ADC has 0-gain sensitivity of +13dBu for 0dBFS, gain of 0 -- 50dB, EIN of A dBu, DNR of B dBFS.
At what gain setting will the system noise be 1 dB higher than the microphone's self noise? Or, what is the system noise in dB SPL equivalent, when the gain is set to match the clipping levels of the mic and ADC?
Here's a hand sketch showing working for gain alignment (85 dB SPL at microphone aligned to 85 dB SPL at calibrated listening position (set at -18 dBFS): hand sketch showing levels translated by different gains ^ Sketch of various levels shown in pressure domain, the electrical domain at microphone output / mic amp input, electrical at mic amp output / ADC input, and Digital domain at ADC output.

Development

I'll write this in Python initially, as I often use a Python prompt as an (extended programmable) calculator anyway.
Watch this space for developments!

2017-03-03

Classical concert mic gain

144dB. That's HUGE. The oft-quoted dynamic range of a 24 bit digital signal is much greater than a normal orchestral concert. So why do mic inputs still need adjustable gain? In this 'digital' age can't you just do the gain digitally after the recorder, leaving as much headroom as possible for overzealous percussionists etc.?
That's what I found myself wondering while setting up for an orchestral recording recently.
I was using only two brands of microphone, with one model of mic amp / ADC, so thought it shouldn't be too difficult to work out what's going on.
Time to dig out the data-sheets.
The microphones I had were DPA 4006ER, Schoeps CMM 4 and Schoeps CMM 21. The ADCs (with integral mic amps) were Merging HAPIs with ADA8 analogue cards.
Some pertinent specifications are reproduced below.
Microphone4006ERCMM 4CMM 21
sensitivity, mV/Pa401313
self noise, dB SPL A-wgt151514
clipping level, dB SPL132132132
and for the HAPI ADA8:
0 dBFS at 0 dB gain*+13 dBu
Dynamic Range, ref +13 dBu119.5 dB A-wgt
Gain settings0 dB to +66 dB
Equivalent Input Noise< −128 dBu A-wgt
*ignoring pad
On initially reading these specs we can see the dynamic range of the microphones is 117 or 118dB, and the ADC reaches 119.5dB.
To do anything more involved with these numbers, we need to units; we currently have values in dBu, milliVolts, dB SPL, Pascals, and noises spec'd absolutely and relatively: a right mess! Time to crack open a spreadsheet and convert these all into consistent units.

Unit Rationalisation

To keep things simple, I'm going to convert everything to dB relative to SI units, with the additional dBFS (relative to Full Scale sine wave) for the digital domain. A purer approach would be to convert everything to SI units themselves, but I'm very much a dB person, and find the maths easier adding dB values rather than multiplying scalar values. For display I like my SPL relative to 20 µPa and my voltage relative to 0.775 V (i.e. dBu). But SI units will make the sums much easier, so here we go.
Field quantities are converted to deciBells thus: 20×log10(field quantity), and back again thus: 10(dB value)/20.
Aside: I say 'field quantity' to distinguish from the equations for power ratios, on which dBs are based — deci for a tenth of, and Bell, the log10 of the power ratios. Since power is proportional to the square of field quantity (Volts, Pascals, etc.), the log10 value is doubled for a field quantity change compared to the same ratio change in power. Hope that's cleared up the 3dB v 6dB for a doubling debate.
The conversions for different reference levels can be made either in the linear values by multiplying or in dB by adding the following conversion factors:
FromTolineardB
dB SPLdB(Pa)0.00002−94
mVdB(V)0.001−60
dBudB(V)0.775− 2.2
So now we can restate the specs in the more consistent units.
Microphone4006ERCMM 4CMM 21
sensitivity−28 dB(V/Pa)−37.7 dB(V/Pa)−37.7 dB(V/Pa)
self noise, A-wgt−79 dB(Pa)−79 dB(Pa)−80 dB(Pa)
clipping level, A-wgt+38 dB(Pa)+38 dB(Pa)+38 dB(Pa)
and for the HAPI ADA8:
sensitivity*, (at 0dB gain)−10.8 dB(FS/V)
Output noise, (0dB gain) A-wgt−119.5 dB(FS)
EIN, (high gain) A-wgt< −130.2 dB(V)
*ignoring pad

Clipping Level Alignment

Now we have the specs in SI-dB units, we can work out the sensitivity of the system from pressure through to digital by adding the gains: −38.8 dB(FS/Pa) for the DPA at 0dB gain, and −48.5 dB(FS/Pa) for the Schoeps.
By adding the clipping levels and the sensitivities, we can also now work out some non-sensible gain settings: at 0dB gain, the microphones will clip before the ADC, i.e. below 0dBFS. For the DPA, matching the clipping levels requires only +0.8dB gain, but for the Schoeps any setting below +10.5dB will cause mic clipping below 0dBFS.
At these gain settings, the mic self-noise becomes:
micamp gainsystem gain dB(FS/Pa)noise dB(FS)
DPA+0.8 dB−38 dB−116.9
Schoeps+10.5 dB−38 dB−116.9
Both mics have the same dynamic range, so adjusting the amp's gain has aligned both their clipping points and therefore also their noise floors.
Time for a recap of where we are. We have seen that the dynamic range of the different microphones and the ADC are similar, and by applying the correct gain, we can eliminate wasted headroom in the ADC. We don't have enough dynamic range in the ADC to apply a single gain for all microphones and expect optimal performance.
Next we must look into the noise floors, and how the different noise sources contribute to the system noise floor, to decide if aligning gain for clipping levels gives adequate noise performance, or if we will need to compromise between head- and foot-room.

Noise Levels

The immediate naive view at the start is that as the dynamic range of the HAPI is greater than the microphones, it must "fit" in.
However, by the output of the ADC, there are more sources of noise than just the mic's self-noise that need to be considered. These noise sources add together proportional to their power (or Root-Mean-Square of the field quantity).
Aside: To find the RMS sum, we could either convert the dB values to field quantities, then square each field quantity, sum them, take it's square-root, and finally convert back to dB: $$dB_{Total} = 20 \times \log_{10} \sqrt {\sum (10^\frac{dB_{NoiseComponent}}{20} )^2 }$$ or simplify by converting the dB values to powers, which can simply be summed and converted back: $$dB_{Total} = 10 \times \log_{10} \sum 10^\frac{dB_{NoiseComponent}}{10}$$
A slightly less naive view would therefore be to see what the RMS sum of the mic noise and the HAPI is. For low gains, the dynamic range spec figure for a mic amp / ADC unit will represent the noise (w.r.t. clipping, i.e. 0dBFS). This gives a new noise floor figure of −115 dBFS — 2dB noisier than the mic's self-noise alone.
As gain increases, a mic amp's noise starts to increase too. We can model this as a noise source at the output and another noise source at the input, before the gain.
These can be taken (simplistically) or derived (more thoroughly) from the spec sheet figures of EIN (Equivalent Input Noise) and Dynamic Range. The EIN is calculated from the noise floor at high gain, where the amplified input noise dominates, and the Dynamic Range will be measured at low gain to get the best number for the marketeers.
Aside: the Dynamic Range spec represents the 'output' noise only as long as the EIN is vanishingly small by comparison at minimum gain. For the HAPI ADA8, the EIN at 0dB gain contributes −142 dB(FS). Compared to the −119.5 dB(FS) total noise of the ADA8, this is minuscule, and contributes only about 0.01dB. Within the tolerances we are working with, it's fair to say the output noise is −119.5 dB(FS).
If the numbers were closer, we should subtract the EIN power from system noise floor to find the output-associated noise.
Summing all the noise sources at the clipping-aligned gains, we get:
DPA… PowerSum of
mic self-noise: -79dB(Pa) + -28dB(V/Pa) + 0.8dB + -10.8dB(FS/V)
amp EIN: -130.2dB(V) + 0.8dB + -10.8dB(FS/V)
amp output noise: -119.5dB(FS)
= -115.0dB(FS)
Schoeps… PowerSum of
-79dB(Pa) + -37.7dB(V/Pa) + 10.5dB + -10.8dB(FS/V)
-130.2dB(V) + 10.5dB + -10.8dB(FS/V)
-119.5dB(FS)
= -114.9dB(FS)
So, at these low gains, the HAPI ADA8 dynamic range hasn't changed from the spec sheet value. For a 3dB increase, the amplified input noise would have to equal the output noise, so -130.2dB(V) + -10.8dB(FS/V) + GAIN = -119.5dB(FS)
∴ GAIN = 21.5dB


Conclusion

The Noise Factor (NF) is still about 2dB. Applying that back to the acoustic noise level, that's a system equivalent acoustic noise level of about 17dB SPL. Given that the ambient noise of a concert hall is probably in the order of NR 20 at best, I'm probably happy to take the hit of the slightly raised system noise floor, for the convenience of knowing that any clipping is down to the force mejeure of exuberant timpanists clipping the microphone itself.