Klippel Near Field Scanner on a Shoestring

completed the port to matplotlib and got a contour plot working

pics

automatically detects where to put the gates (tbd how reliable this work, works for this data)

1.png

applying the window

2.png

resulting FR

3.png

and in contour form, interval between contours and colour scheme is selectable atm

4.png

still need to sort out the labelling of the x axis
 
Getting good data out of CalSpatial will be a major milestone. The DataOut should be plotted to see if it looks reasonable.
I can call CalSpatial and get results back as expected & I added UI fields for the various parameters and set them to the stated defaults.

I assumed dataIn is the output from linToLog btw

spatial.png

I just need to know how to plot this and what good looks like.

I'll try getting the magnitude and plotting as a contour to see what that looks like.
 
any preferences on file formats to support for importing measurement data? I was thinking

- holm (single text file)
- REW (impulse as txt)
- ARTA (pir)
- wav
- dbl

anything else?

I think all of those are trivial except ARTA which has some custom binary format so I might not get to that until I feel the need (NB: trivial means practically no thought or code required as opposed to easy)
 
right ok, I had not considered that (left window position) effect on phase. Thanks for the explanation. I interpret this as meaning the following defaults should be applied

1) use a rectangular window by default
2) set the left window to the first "non zero" sample by default (where non zero is to be defined but could something like >1e-5 or perhaps ~15 samples behind the peak could be a more reliable default), point being to guide the user to the right choice

It would be nice to try to set the right gate by default as well but I'm less confident that is going to be worthwhile/reliable (i.e. easy thing for the eye/human brain to spot, harder to write code for).

agree?

Since setting the window automatically is fraught with danger, I would just leave this to the user. FORCE them to make a choice and you will have less problems than letting them use the defaults. That's what I do.

It sounds like you sorted out the variables.

The DataOut array is not in angle, it is in modes. The first mode versus frequency, the second, etc. Hence a contour plot makes little sense. Plot it as separate frequency responses for each mode.

To get to a reconstructed polar plot you need to go one step further and use

Code:
Declare Function CalPolar Lib "MeasCalcs.dll" (ByRef DataIn As Complex, _
                                                   ByVal NumCoef As Integer, _
                                                   ByVal angl As Double, _
                                                   ByVal freq As Double, _
                                                   ByVal farnum As Double, _
                                                   ByVal Velnum As Double) As Complex

Complex(8) function CalPolar( DataIn, NumCoefs, Theta, freq, FarRadius, SourceRadius)
use deffs

IMPLICIT NONE
!DEC$ ATTRIBUTES REFERENCE :: DataIn 
!DEC$ ATTRIBUTES Value :: NumCoefs 
!DEC$ ATTRIBUTES Value :: Theta 
!DEC$ ATTRIBUTES Value :: freq 
!DEC$ ATTRIBUTES Value :: FarRadius 
!DEC$ ATTRIBUTES Value :: SourceRadius 

integer, intent(in):: NumCoefs

complex(8), intent(in):: DataIn(0:NumCoefs-1)

Integer icoeff, j, ia, ij
real(8)  Theta, plgndr, freq, kR, ka, SourceRadius, FarRadius, cosTheta
complex(8) Hankel, filter

A call for each frequency and angle point as passed in, i.e. Pressure(Freq, theta, FarRadius) - Calpolar(DataIn, Freq, Theta, FarRadius)

FarRadius is always 10.0 meters in my work.

You should then smooth the data using "smooth"

Code:
 Declare Sub Smooth Lib "MeasCalcs.dll" _
                                (ByRef Data_in As Double, ByVal Freqs() As Double, ByVal numpts_ As Integer, _
                                 ByVal atype As Integer)

   subroutine Smooth(  DataPass,   &   ! Array of real values at FFT points in and smoothed data out
                        Freqs,      &   ! array of frequencies
                        NumLogPts,  &   ! number of log points
                        type_       &   ! smoothing type
                        )
    !-------------------------------------------------------------------------------
                !
                !  This routine smoothes the data
                !       "type_" sets the smoothing 
                !
                !   Copyright  August 2014  Earl R. Geddes
                !
                !
                !
                !_____________________________________________________________________

use deffs    
implicit none 

! Specify that LinToLog is exported from the DLL
!DEC$ ATTRIBUTES DLLEXPORT :: Smooth
! and that the external name is 'Smooth'
!DEC$ ATTRIBUTES ALIAS:'Smooth' :: Smooth
!DEC$ ATTRIBUTES REFERENCE :: DataPass 
!DEC$ ATTRIBUTES REFERENCE :: Freqs 
!DEC$ ATTRIBUTES Value :: Type_ 
!DEC$ ATTRIBUTES Value :: NumLogPts 

real(8), intent(in) :: Freqs(0:NumLogPts-1)
Real(8) DataPass(0:NumLogPts-1)
Real(8) Datatemp(0:NumLogPts-1), Datasum  
integer iff, NumLogPts, intBandLow, intBandhigh
Integer Bandwidth, is, intBandwidth, iwidthTO, iwidthCBZ, iwidthCBM, width, Type_
real(8) it
real(8)  filter, FreqAvgFilter

Where Type is:

Public SmoothType_() As String = {"1/3 Octave", "1/6 Octave", "1/12 Octave", "CB Zwicker", "CB Moore", "Narrow"}

Passed in as an integer to these values, i.e. 3 = "CBZwicker" Critical bands as defined by Zwicker.

I was not too impressed by the contour plots. I could never find a packaged contour plot that did what I wanted. You need contour lines at every 2 dB near 0 dB but then every 6 dB after - 6 dB. My colors were chosen based on how the eye interprets color with white and yellow being high intensity and moving to greens and then blues for lower intensity.
 
Last edited:
OK thanks, will continue working through those next steps.

In the mean time I have added support for reading arta pir files, holm single files, wavs, REW impulses as txt and dbl files.

re colour schemes, there are loads of discussions on this subject online (regarding colour maps that correctly convey the perception of intensity) and matplotlib includes a number of them. You can see those in Colormaps in Matplotlib — Matplotlib 2.2.2 documentation

There is a yellow-green-blue scheme in there though Colorcet — colorcet 1.0.0 documentation has a white-yellow-green-blue scheme which looks like what you're describing. I can include all of these without a problem.

Completely custom maps can be done too so if someone wants to define such a thing then I can add it.

re contour lines, I can do any sequence we like. If you want every 2dB from 0 to -6 and every 6dB thereafter then I can do that.
 
this is working on the impulse response rather than FRD

what format does the smith & larson software export to? no obvious manual online so hard to tell what it can do

The Smith & Larson software exports to almost every style you have mentioned. So after looking today and doing a bunch of measurements I can say you have it covered.
 
I don't follow this bit

To get to a reconstructed polar plot you need to go one step further and use

Code:
Declare Function CalPolar Lib "MeasCalcs.dll" (ByRef DataIn As Complex, _
                                                     ByVal NumCoef As Integer, _
                                                     ByVal angl As Double, _
                                                     ByVal freq As Double, _
                                                     ByVal farnum As Double, _
                                                     ByVal Velnum As Double) As Complex
at this point we have

1) a set of measurements at various angles (e.g. 0, 5, 10...180)
2) a set of log spaced frequencies
3) a chosen number of modal coefficients
4) an (angle, freq) dataset (of complex numbers) via linToLog(fft(x)) where angle are those in 1) and freq from 2)
5) a (mode, freq) dataset (of complex numbers) via calSpatial where mode is a range of 1 to teh value in 3) and freq is from 2)

now we have the CalPolar function which returns a complex number given

- dataIn : is this the data from 4) or 5)? The arguments and the reference to Pressure make me think it must be 4) but in that case I don't understand what numCoefs refers to.
- numCoefs: is the same as 3) above? i.e. the no of coeffs passed into calSpatial
- angl : the angles from 1) in radians again?
- freq : the log spaced frequencies from 2)
- farnum : hardcoded to 10.0
- velnum : the !DEC$ section suggests this is SourceRadius which I guess is referring to the driver radius? i.e. the value passed to CalSpatial as "Radius"

the combination of references to coefs and angles is what confuses me as I don't understand how the two are related.

Anyway if it is the linToLog output then I call this function for every single value returned from linToLog. This output is complex so I calculate the magnitude and pass it to smooth and then I use that to feed the contour map?

If I'm right so far, I don't understand the relevance of the data from CalSpatial

I imagine this would make more sense if I understood the maths ....
 
Last edited:
attempting to call CalPower I notice that as soon as angl is non zero then the program pauses (permanently) and prints

Code:
bad arguments in sphbes

to the console

google finds some code to do with a spherical bessel function that has this output, e.g.

Numerical Recipes in C: The Art of Scientific Computing - William H. Press - Google Books

if this code is in the dll then it would be good to remove it (blowing up hard is better than hanging IMV)

in the meantime I'll try to recreate in c#
 
You have it mostly wrong.

1) are measurements, inputs.
2) Are the frequencies where we want data
LintoLog gives us the same set of measurements but now log spaced.
3)Is just a number - the number of modes used in the reconstruction, it can be anything, but 15 seems about right for us. Lower values, but finite, would be the easiest to debug.
4) you have correct, its the output of the 2) and the next line
5) Is correct as well.

Calpolar returns the dcomplex pressure at any requested "angle" and "Freq", no reference to what was actually measured. Array 5) is a "model" if you will of the speaker for all angles (in the horizontal plane) and all frequencies. It is simply now in "modal" space. Modal space completely defines the radiation pattern.

Want a very close analogy Think of electrons patterns around a nucleus. Knowing the shell numbers completely specifies the electron distribution. It's basically the exact same problem.

I already defined the other variables.

The routine is exactly the one in your reference, and it would be impossible to remove as it is key to the software. And yes, it is an antique, probably 40 years old now or more! I didn't write it and it would be very dangerous to modify it. This has never been a problem for me when the data is sent in correctly.

Your plots look OK, except for the 600 dB scale, I can't live with more than 100. But you can see the modes "cut-in". That's exactly what they do. Does your plotting have interpolation for smoother plots?
 
OK thanks for the explanation.

Let me play this back to make sure I understand

(dataIn, angl, freq) is the output from linToLog

i.e. I can call CalPolar something like this

Code:
        logData, logFreqs = linToLog(measurement.fftData, measurement.fs / measurement.fftPoints)
        for ld in logData:
            for lf in logFreqs:
                print(calPolar(ld, 15, math.radians(measurement.angle), lf, 0.10, 0.15))

i.e. call linToLog for a single measurement and then call calPolar for each log spaced frequency and piece of data returned

is it correct?

If so, this does produce a pause as soon as I move onto the 5 degree measurement

in C# terms, it pauses on the following data

Code:
using System;
using System.Numerics;
using System.Runtime.InteropServices;
using System.IO;

namespace CalPower
{
    class Program
    {
        static void Main(string[] args)
        {
            Complex a1 = new Complex(0.00743564367029359, -0.0020608701139408466);
            int a2 = 14;
            double a3 = 0.08726646259971647;
            double a4 = 20.0;
            double a5 = 0.15;
            Complex output = MeasCalcs.CalPower(a1, a2, a3, a4, 0.10, a5);
            Console.WriteLine(output);
            System.Threading.Thread.Sleep(20000);
        }
    }

class MeasCalcs
    {
        [DllImport("MeasCalcs.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern Complex CalPower(Complex dataIn, int numCoef, double angl, double freq, double farnum, double velnum);
    }
}

So I am going wrong somewhere but I don't know where so I'd appreciate your help is debugging this.
 
Your plots look OK, except for the 600 dB scale, I can't live with more than 100. But you can see the modes "cut-in". That's exactly what they do. Does your plotting have interpolation for smoother plots?
atm I've left it to automatically determine the range from the boundaries in the data, I can reduce this to more sensible levels though for sure. There are lots of options on how to interpolate both lines and grids for the contour plots. If there is a particular preferred method then I can look for an equivalent otherwise I can just add one later on.
 
Not correct yet.

Let's look at the big picture:

I want a modal model that represents the device at ANY angle and frequency.

1) Read in (M for measurement) M.linear.pres(M.angles, freq.lin)
2) convert using LintoLog to M.log.pres(M.angles, Freq.log)
3) convert M.log.pres to Modal.model(numModes,freq.log) using Spatial
4) Modal.model is now a model of the sound radiation, basically the velocity distribution on some fictitious sphere of radius Source.radius, which can be used to find the Pressure response at any angle and frequency (although it is best to use the frequencies freq.log for the reconstruction.)
5) the pressure response at some desired field location (R is reconstructed) R.pres(freq.log, R.angle, FieldRadius) is obtained by calling the Function CalPolar once for each field point and frequency.
CalPolar needs a vector (a row) of the complex array Modal.model at the desired frequency Freq.log(Current Frequency) and the desired R.angle, Freq.log(Current Frequency) and FieldRadius (your farnum) as double real numbers. It also needs the assumed source.radius (your velnum) which is not the driver radius, but the radius of a sphere which is the same volume as the enclosure.

Hope this helps.

One thing that we will have to keep close track of is that FORTRAN stores data in the reverse of any other language. Thus an Array(A,B) must become Array(B,A) when passed into any FORTRAN code.

In thinking about this CalPolar should be rewritten so that it takes in the entire Modal.Model array and Freq.log and passes out an array of frequency response at the desired angle, but that is not the way it is right now.
 
Last edited:
Looking at the project long term I realized that in order to do the two layer scan suppressing the reflections one has to scan the field NOT rotate the source. So we would never be able to do this with our simplified method of data acquisition. We have to move the mic, not the source. I now see why Klippel scans as he does, since there is no other way to do this. But, as I said, I don't see this as an issue since we gate and we can use other techniques below the gate frequency.