If this something does not produce nonlinear distortion (like clipping, compression or similar) then we can use this simple method.
JAPA is a nice little tool by Fons Adriaensen. From his web page:
Fons Adriaensen wrote:Japa (JACK and ALSA Perceptual Analyser), is a 'perceptual' or 'psychoacoustic' audio spectrum analyser.
In contrast to JAAA, this is more an acoustical or musical tool than a purely technical one. Possible uses include spectrum monitoring while mixing or mastering, evaluation of ambient noise, and (using pink noise), equalisation of PA systems.
This HOWTO assumes that JAPA is already installed on the system and that it works with JACK correctly.
Don't be scared by the quantity of text: it is actually all simple. The fact is that I like to explain things in details (or at least I try to...).
1: The basics
Every linear system has an impulse response. Let's call the unknown impulse response of the system under test h(t), where t is time, in seconds. So, if we give an input b(t) to the system, we get an output a(t) = b(t) * h(t), where * denotes convolution.
We can describe a system also in the frequency domain. Through Fourier transform we can get these quantities:
h(t) -> H(f) system frequency response
b(t) -> B(f) input spectrum
a(t) -> A(f) output spectrum
where f is frequency, in Hertz. H(f) will be the frequency response. For example, for filters it is the law that, for each value of frequency, tells us the level and phase of the output. In frequency domain things are easier, with the output spectrum being easily calculated through ordinary multiplication:
A(f) = B(f) . H(f) (. denotes multiplication)
Hence, we can find H(f) easily (let's not dig into the maths about why this is not always appropriate):
H(f) = A(f) / B(f)
This is a very crude way to do it, but it most often works, and we can use JAPA for that.
2: A test ride
Let's test things out first. Let's fire up JACK and JAPA. Then, let's open up Calf filter (our test bed) and let's set this up:
We can see that JAPA has two built-in signal generators, one white noise generator and one pink noise generator. Go back to this:
H(f) = A(f) / B(f)
we need a broadband input signal so that we can solve the equation above for as many values of f as possible. So we use noise, which is a signal made of many different frequencies. But it doesn't need to be flat (white) noise. In fact, above we connected the pink noise generator. Then, the input signal goes directly into JAPA in_2 to act as the reference, while the test bed output goes into JAPA in_1. The fact that we use a reference input removes the need to use white noise and makes the measurement smoother. On the side panel we assign the input to A and B (see why I called the signals above that way?) and then, in the traces section, we set up JAPA to show us A/B. We can adjust the scale using the controls below the input assignment section, and we are done! What we see in JAPA has a very good agreement with the expected shape as shown by Calf filter. If we play with Calf filter parameters we will see the plot in JAPA changing accordingly.
Why this works? (intuitive explanation with less maths)
In the frequency domain division acts like a "comparison operator". All of we are doing is to compare the spectrum at the input of the system-under-test (Calf filter in this case) to the spectrum at its output, to find out what changed. Given that what changed is due to the system response, that's what we find.
3: Soundcard Response
With the same method we can measure our soundcard response too!
But first, what we do to measure the soundcard? We just loop it back as shown here.
To avoid excessive levels due to feedback, please start with all controls with volume down to 0! Then, slowly raise output level and input gain until you have a safe level. In my case I connected system playback_1 back to system capture_1 with an audio cable.
To be clear, what we measure in this way are actually 2 things. In fact the signal flows as follow:
JAPA noise output -> Soundcard DA converter -> Soundcard Output Stage -> -> Soundcard Input Stage -> Soundcard AD Converter -> JAPA in_1
This method allows us to measure whatever we put between JAPA noise output and JAPA in_1 (provided that we stream also a reference to in_2). So we are actually assessing the combined response of Soundcard input and output.
We might be then tempted to think that we can just do as above, but instead of Calf filter we put our soundcard between JAPA noise output and JAPA in_1. However, if we do just that by sending the noise to system playback_1 and by routing system capture_1 to JAPA input 1, then we will only have a wiggly line:
Why is that happening? Well, the let's look at the equation once again:
H(f) = A(f) / B(f)
A(f) is the spectrum of the system-under-test output, while b(t) is the spectrum of the signal we input to the system-under-test, i.e. our reference. While we supply b(t) to JAPA in_2 digitally, in_2 goes into the DA/AD converters of the soundcard, so it goes through additional latency!
Now, when we try to solve the equation above, A(f) must be the output which comes as a consequence of supplying B(f) as an input. This means that there is causality between them: B(f) => A(f), so we are comparing the input against the output it produces.
If latency is introduced this does not happen: by the time our reference signal gets into JAPA in_2 its output still has to arrive at JAPA in_1, as latency is delaying it! The signals at JAPA in_1 and in_2 are hence not correlated at all, so we are measuring nothing! How we can solve this?
Compensation Delay Line
Easy, by introducing an equal delay for the reference signal, so that we restore the lost causality!
First, let's measure our soundcard latency. Another software tool based on work by Fons can be used for that. It is called jack_iodelay. Just launch it from the command line and connect it like this:
As you can see I have a fairly large latency, as I am running with these settings:
Code: Select all
/usr/bin/jackd -P70 -t5000 -dalsa -r96000 -p4096 -n3 -D -Chw:USB -Phw:USB
It does not matter how big is the latency, and for measurements it is best to set up for highest stability (higher latency) so that xruns do not pollute our tests.
So, now we see our soundcard latency, but if we loop back jack_iodelay to itself what we observe?
That is, there is a latency associated with just streaming digital data, big as our JACK Frames/Period value (in samples). It is due to buffering and you can read about it here.
So, what value of latency our reference signal should have to preserve input/output causality? Well, given that a latency of Frames/Period samples will be introduced just by JACK alone, the correct value is the following:
soundcard latency - Frames/Period value
in our case: 17780.760 - 4096 = 13684.760 samples
How can we do it? For me, the easiest approach was to write a simple delay line with Faust. If you installed Faust from git, just drop this in a text file called fdelayLine.dsp (not really sure how much sense makes to put a license on 6 lines of code...):
Code: Select all
// Simple Delay Line
declare name "fdelayLine";
declare author "CrocoDuck o'Ducks";
declare copyright "CrocoDuck o'Ducks";
declare version "1.00";
declare license "LGPL";
de = library("delays.lib");
delayLine = de.fdelay(192000.0, samples)
samples = nentry("Samples", 0, 0, 192000.0, 0.001);
process = delayLine, delayLine;
Then, in a terminal, compile it to a standalone JACK GTK application by issuing this command:
Code: Select all
This will create the executable fdelayLine you can launch from the command line:
Code: Select all
Alternatively, you can download the precompiled program here (not sure whether it will work) or use another delay program. Then, we just use it as follows:
We see a way better result already! However, the raise in high frequency is not very realistic. I found that rounding the latency value to the nearest integer (13685 in this case) helps (maybe those intersample values are not too accurate):
Now, that's a nice realistic soundcard frequency response! It might be worth to set the speed control to slow, as the low frequency will be probably pretty wiggly.
So, in short:
- Find your soundcard latency, in samples.
- Subtract from it your JACK buffer size.
- Round the result to the nearest integer.
- Connect JAPA noise output to playback_1, loop your soundcard playback_1 output to capture_1 input and route capture_1 to JAPA in_1.
- Route JAPA noise output to the delay line with the value of latency we calculated above, and then the output of the delay line to JAPA in_2.
4: Measuring Microphones Relative Responses
Let's imagine we have 2 microphones and we want to compare their frequency responses. How can we do it? Look at the picture below.
The picture represent a loudspeaker on the left, to which we have connected one of the JAPA noise outputs, and two microphones under test (the black dots), going to our JAPA inputs. JAPA is configured as always. Now, what happens to the noise output signal?
As it gets radiated from the speaker, it spectrum gets multiplied by the speaker frequency response Hs. By calling the spectrum of the noise N:
N . Hs
Then, the radiated noise propagates through air to the respective microphones. The propagation through air is described by the frequency responses Hsm1 and Hsm2. So, at each of the microphones, the spectrum of the noise looks like this:
N . Hs . Hsm1 (mic 1)
N . Hs . Hsm2 (mic 2)
Finally, each of the microphones has its own response Hm1 and Hm2, so the spectra at JAPA inputs are:
N . Hs . Hsm1 . Hm1 (JAPA in_1)
N . Hs . Hsm2 . Hm2 (JAPA in_2)
Now, JAPA will divide the spectrum at in_1 by the spectrum at in_2, giving as a result:
(Hsm1 . Hm1) / (Hsm2 . Hm2)
Since N and Hs cancel each other. Now, imagine that the two microphones are very very close (but not touching!). Then, Hsm1 and Hsm2 will be very similar, almost equal:
Hsm1 ~ Hsm2
Meaning that we are left with:
Hm1 / Hm2
Again, division means comparison, so the curve we will observe on JAPA shows the difference between mic 1 with respect mic 2 (the reference). For example, if the curve at 500 Hz is +10 dB we know that mic 1 response at 500 Hz is +10 dB with respect whatever value mic 2 response has at 500 Hz.
Now, as a final step, imagine that mic 2, the reference, has a very very flat response. For example, this microphone has a very good flat response. This will mean that Hm2 is a straight line, so Hm1 / Hm2 shows, a part for an overall gain factor, the exact response of Hm1.
Be careful though! Remember about this approximation: Hsm1 ~ Hsm2. This is true only if the mics are very close. The closer the mics the higher the frequency up until we can consider Hsm1 and Hsm2 identical. This is because the space in between the mics is short. Every sound field is made by many components at different wavelengths, which is related to frequency. The higher the frequency, the shorter the wavelength. If the wavelength of the sound is much longer than the space between the mics, than the wave at that frequency is not varying a lot between the two mics. On the contrast, high frequency sound which has wavelength shorter than the gap between the mics will vary a lot between the mics, producing a difference.
In other words: Hsm1 and Hsm2 would be identical if we could put mic 1 and mic 2 in the very same point in space. But we can't, we can only put them very close. Since low frequency sound varies very little in space, as it has a long wavelength, then mic1 and mic2 appear as they were in the same location for low frequency sound.
As a result, this method will show you a real comparison of the microphones up to a certain upper frequency, after which the result is not accurate. How will we understand where the approximation breaks? Well, the volume of air between the mics will, excited by the noise, bounce between them. This will produce a high frequency resonance. If you see a resonant peak at the end of the spectrum (usually after 10 kHz) don't worry: that's not real and it is due to the approximation breaking down.
This is a very easy and good method to test microphones frequency responses, and one of the very few that can be done easily in any kind of room.
5: Room Responses and Compensation EQs
We can then try to design compensation EQs for a system. For example, we might want to compensate for the response of our room. Then, we could place a microphone where our head is gonna be while listening and, for each channel, we could realize the following chain (here represented for only one channel):
Which is very similar to that of the sound card measurement. Simply speaking, our output noise will go the one output channel, then to the speaker. Then, we put our mic in position and we route it into a soundcard input. Instead to connect this soundcard input directly to JAPA in_1, we put an EQ in between. In this example, Calf EQ.
As for the reference, we route our test signal to JAPA in_2, but through a compensation delay line. How we calculate the compensation delay in this case? We can use jack_iodelay to assess the latency of this chain:
Latency 1: soundcard -> speaker -> mic -> soundcard -> Calf EQ
and this chain:
Latency 2: Calf EQ latency.
Things might get a bit loud when measuring Latency 1, but it should work. Now, the compensation delay will be:
round(Latency 1 - Latency 2), where round() means that we round to the nearest integer.
What are we actually doing? If the EQ is flat, then we are essentially measuring the room response. However, as we tweak the EQ, we will change the curve on JAPA plot. When the EQ is flat (or flattish, rather) it means that we found the EQ that compensates for the room response, as the combined response of room and EQ is flat.
Here a pic of how my room response looks when Calf EQ is bypassed:
And here when I activate the best EQ I could find:
We can see that the measured response is much flatter! It wasn't too hard to find a decent EQ, and when I activate it the sound does sound more neutral to me. My speaker system is really lacking on the bass, so the EQ really helps in getting those bass out properly. However, as my speakers are small and cheap, they cannot really output that much low frequency sound without distorting a bit. So, to compare EQ ON and OFF fairly I had to lower down the volume. If you do a comparison yourself, make sure the loudness of EQ ON and EQ OFF is the same, or you will be unable to judge fairly if the compensation is good.
Oh, and remember to do this process for each channel. The EQ solution for every channel might actually vary. In my case, as the listening position is central in the room and the stereo speakers are symmetric, the difference was minimal.
How good of a compensation will it be? Well, we are limited by these:
- The EQ we use: it will determine how easily we can match the room response.
- The fact that we measure with a microphone. The mic response is different from that of our head so, even if we get a straight line in JAPA, the compensation will not be optimal when we put our head where the mic is. This will affect mostly the high frequency.