Showing posts with label python. Show all posts
Showing posts with label python. Show all posts

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!