Пример #1
0
        public static FilterProfile Profile(ISoundObj impulse, SmoothingType type, double resolution)
        {
            uint nSR  = impulse.SampleRate;
            uint nSR2 = nSR / 2;

            ushort nChannels = impulse.NumChannels;

            for (ushort c = 0; c < nChannels; c++)
            {
                // Read channel into a buffer
                SingleChannel channel = impulse.Channel(c);
                SoundBuffer   buff    = new SoundBuffer(channel);
                buff.ReadAll();

                // And then double in length to prevent wraparound
                buff.PadTo(buff.Count * 2);
                // Pad to next higher power of two
                buff.PadToPowerOfTwo();
                // Read out into array of complex
                Complex[][] data  = buff.ToComplexArray();
                Complex[]   cdata = data[0];

                // Then we're done with the buffer for this channel
                buff = null;
                GC.Collect();

                // FFT in place
                Fourier.FFT(cdata.Length, cdata);

                int n = cdata.Length / 2;

                // Now we have an array of complex, from 0Hz to Nyquist and back again.
                // We really only care about the first half of the cdata buffer, but
                // treat it as circular anyway (i.e. wrap around for negative values).
                //
                // We're only working with magnitudes from here on,
                // so we can save some space by computing mags right away and storing them in the
                // real part of the complex array; then we can use the imaginary portion for the
                // smoothed data.
                for (int j = 0; j < cdata.Length; j++)
                {
                    cdata[j].Re = cdata[j].Magnitude;
                    cdata[j].Im = 0;
                }

                // Take a rectangular window of width (resolution)*(octave or ERB band)
                // Add up all magnitudes falling within this window
                //
                // Move the window forward by one thingummajig
                //double wMid = 0;    // center of the window
                //double wLen = 0;
            }
            return(new FilterProfile()); // temp
        }
Пример #2
0
        /// <summary>
        /// Calculate the weighted volume of a sound source.
        /// NB: this consumes lots of memory for long sources.
        /// </summary>
        /// <param name="src"></param>
        /// <param name="dbSPL"></param>
        /// <returns>Volume (units, not dB)</returns>
        public static double WeightedVolume(ISoundObj src, double dbSPL, double dbSPLBase)
        {
            double wv = 0;

            for (ushort c = 0; c < src.NumChannels; c++)
            {
                SingleChannel channel = src.Channel(c);
                wv += Loudness.WeightedVolume1(channel, dbSPL, dbSPLBase);
            }
            src.Reset();
            wv = wv / src.NumChannels;
            return(wv);
        }
Пример #3
0
        static SoundObj Deconvolve(string infile, out Complex[] impulseFFT, out int peakpos)
        {
            WaveReader reader = new WaveReader(infile);
            ushort nChannels = reader.NumChannels;
            uint sampleRate = reader.SampleRate;

            CallbackSource cs;
            SingleChannel[] channels = new SingleChannel[2];
            Complex[][][] data = new Complex[2][][];
            double[] stdDev = new double[2];
            double[] maxLogs = new double[2];
            double[] maxs = new double[2];
            double[] avgLog = new double[2];

            Complex[] swp_data = null;
            Complex[] mea_data = null;

            if (_fmax == int.MaxValue)
            {
                _fmax = (int)sampleRate / 2;
            }
            bool isEstimatedSweepRange = false;

            if (nChannels != 2)
            {
                throw new Exception("Input must have two channels.");
            }

            peakpos = 0;
            int cSwp = 0;
            int cMea = 1;
            int L = 0;
            int Nh = 0;
            double mx;

            double max = 0;
            double maxLog = 0;
            double stdev = 0;
            double avg = 0;

            for (int iteration = 1; iteration <= 2; iteration++)
            {
                // Read and FFT all the data
                // one channel at a time
                for (ushort c = 0; c < 2; c++)
                {
                    SingleChannel channel = reader.Channel(c);
                    Complex[] cdata;

                    SoundBuffer buff = new SoundBuffer(channel);
                    buff.ReadAll();

                    if (iteration==2 && _split)
                    {
                        // Split up the input file
                        string infile2 = Path.ChangeExtension(Path.GetFileName(infile), ".PCM");
                        if (c == cSwp)
                        {
                            WaveWriter wri = new WaveWriter("refchannel_" + infile2, 1, channel.SampleRate, 32, DitherType.NONE, WaveFormat.IEEE_FLOAT);
                            wri.Input = buff;
                            wri.Run(); wri.Close();
                        }
                        if (c == cMea)
                        {
                            WaveWriter wri = new WaveWriter("sweep_" + infile2, 1, channel.SampleRate, 32, DitherType.NONE, WaveFormat.IEEE_FLOAT);
                            wri.Input = buff;
                            wri.Run(); wri.Close();
                        }
                    }

                    // And then double in length to prevent wraparound
                    buff.PadTo(buff.Count * 2);

                    // Pad to next higher power of two
                    buff.PadToPowerOfTwo();

                    // Read out into array of complex
                    data[c] = buff.ToComplexArray();

                    // Then we're done with the buffer for this channel
                    buff = null;
                    GC.Collect();

                    cdata = data[c][0];

                    if (iteration==2 && c==cSwp && _power > 0)
                    {
                        // Deconvolve against a power of the sweep,
                        // for distortion measurement of harmonic _power
                        Complex p = new Complex((double)_power,0);
                        for (int j = 0; j < cdata.Length; j++)
                        {
                            cdata[j].Pow(p);
                        }
                    }

                    // FFT in place
                    Fourier.FFT(cdata.Length, cdata);

                    if (false && iteration==1)
                    {
                        // write the fft magnitudes to disk
                        cs = new CallbackSource(1, sampleRate, delegate(long j)
                        {
                            if (j >= cdata.Length)
                            {
                                return null;
                            }
                            Complex si = cdata[j];
                            Sample s = new Sample(1);
                            double f = (double)j * sampleRate / cdata.Length;
                            s[0] = mag(sampleRate, f, si.Magnitude);
                            return s;
                        });
                        // cs.SampleRate = sampleRate;
                        // cs.NumChannels = 1;
                        WaveWriter writer = new WaveWriter("fft_" + c + "_" + infile);
                        writer.Format = WaveFormat.IEEE_FLOAT;
                        writer.BitsPerSample = 32;
                        writer.SampleRate = _sampleRate;
                        writer.Input = cs;
                        writer.Normalization = -3;
                        writer.Run();
                        writer.Close();
                    }

                    // Take a slice of the FFT, std dev of log(|fft|),
                    // the lower value should be the sweep
                    int n3 = cdata.Length / 4;
                    int n1 = n3 / 2;
                    int n2 = n1 + n3;
                    get_stddev(sampleRate, n1, n2, cdata, out max, out maxLog, out stdev, out avg);

                    maxs[c] = max;
                    maxLogs[c] = maxLog;
                    stdDev[c] = stdev;
                    avgLog[c] = avg;

                    // Trace.WriteLine("Channel {0} standard deviation {1}", c, stdDev[c]);
                }
                GC.Collect();

                if (iteration == 1)
                {
                    if (stdDev[1] < stdDev[0])
                    {
                        if (_refchannel == -1)
                        {
                            cSwp = 1;
                            cMea = 0;
                            stderr.WriteLine("  Right channel seems to be the sweep");
                        }
                        else
                        {
                            stderr.WriteLine("  Right channel seems to be the sweep");
                            stderr.WriteLine("  But you said use refchannel {0}, so using that.", _refchannel);
                            cSwp = _refchannel;
                            cMea = (_nInFiles - 1) - _refchannel;
                        }
                    }
                    else
                    {
                        if (_refchannel == -1)
                        {
                            stderr.WriteLine("  Left channel seems to be the sweep");
                        }
                        else
                        {
                            stderr.WriteLine("  Left channel seems to be the sweep");
                            stderr.WriteLine("  But you said use refchannel {0}, so using that.", _refchannel);
                            cSwp = _refchannel;
                            cMea = (_nInFiles - 1) - _refchannel;
                        }
                    }
                }

                swp_data = data[cSwp][0];
                mea_data = data[cMea][0];

                L = swp_data.Length;
                Nh = L / 2;

                // stderr.WriteLine("avgLog=" + avgLog[cSwp] + " maxLog=" + maxLogs[cSwp]);

                double hz1 = L / sampleRate;
                if (false && _fmin == 0)
                {
                    isEstimatedSweepRange = true;
                    // Working back from 100Hz,
                    // Look for the first range where average of a 1Hz window
                    // is less than 0.7* average for the sweep itself
                    int kmin = (int)(hz1 * 100);
                    _fmin = 100;
                    while (kmin > 0)
                    {
                        get_stddev(sampleRate, kmin, (int)(kmin + hz1), swp_data, out max, out maxLog, out stdev, out avg);
                        if (avg < avgLog[cSwp] * 0.5)
                        {
                            break;
                        }
                        kmin -= (int)hz1;
                        _fmin--;
                    }
                    // stderr.WriteLine("avg/2: kmin=" + kmin + ", _fmin=" + _fmin);
                }

                if (false && _fmax == sampleRate / 2)
                {
                    isEstimatedSweepRange = true;
                    // Working forward from (say) 15kHz,
                    // Look for the first range where average of a 100Hz window
                    // is less than 0.7* average for the sweep itself
                    int kmax = (int)(hz1 * 10000);
                    _fmax = 10000;
                    get_stddev(sampleRate, kmax, (int)(kmax + 100 * hz1), swp_data, out max, out maxLog, out stdev, out avg);
                    double stdTest = stdev;
                    while (kmax < L / 2)
                    {
                        get_stddev(sampleRate, kmax, (int)(kmax + 100 * hz1), swp_data, out max, out maxLog, out stdev, out avg);
                        if (avg < avgLog[cSwp] * 0.5)
                        {
                            break;
                        }
                        kmax += (int)(100 * hz1);
                        _fmax += 100;
                    }
                    // stderr.WriteLine("StdDev Peak: kmax=" + kmax + ", _fmax=" + _fmax + ", sdev=" + stdev + " ref " + stdTest + " (1Hz=" + hz1 + "), avgLog=" + avg);
                }

                if (!_noSubsonicFilter)
                {
                    // Filter LF from the measurement data
                    // to avoid spurious stuff below 15Hz
                    int s1 = (int)(7 * hz1);
                    int s2 = (int)(15 * hz1);
                    for (int j = 0; j < s1; j++)
                    {
                        mea_data[j].Set(0, 0);
                        mea_data[swp_data.Length - j - 1].Set(0, 0);
                    }
                    for (int j = s1; j < s2; j++)
                    {
                        double n = (double)(j - s1) / (s2 - s1);
                        double m = 0.5 * (1 + Math.Cos(Math.PI * (1 + n)));
                        mea_data[j].mul(m);
                        mea_data[swp_data.Length - j - 1].mul(m);
                    }
                }

                // Divide in complex domain
                for (int j = 0; j < swp_data.Length; j++)
                {
                    swp_data[j].idiv(mea_data[j]);
                    // Make a copy in mea_data, we'll need it later
                    mea_data[j] = swp_data[j];
                }

                // IFFT to get the impulse response
                Fourier.IFFT(swp_data.Length, swp_data);

                // Scan the imp to find maximum
                mx = 0;
                int mp = 0;
                for (int j = 0; j < sampleRate; j++)
                {
                    Complex si = swp_data[j];
                    double mg = Math.Abs(si.Magnitude);
                    if (mg > mx) { mx = mg; mp = j; }
                }
                // Look one sample backwards from max position
                // to find the likely "pulse peak" if within 30% of max
                peakpos = mp;
                if (mp>0 && swp_data[mp - 1].Magnitude / mx > 0.7)
                {
                    peakpos = mp - 1;
                }
            }

            // stderr.WriteLine("  {0} range {1}Hz to {2}Hz", isEstimatedSweepRange ? "Estimated sweep" : "Sweep", _fmin, _fmax);
            if (_fmaxSpecified && _fminSpecified)
            {
                HackSweep(swp_data, mea_data, peakpos, L, sampleRate);
            }
            else
            {
                Fourier.FFT(swp_data.Length, swp_data);
            }

            // Window the extremities of the whole response, finally?

            if (true)
            {
                // Make a copy in mea_data, we'll need it later
                for (int j = 0; j < swp_data.Length; j++)
                {
                    mea_data[j] = swp_data[j];
                }

                // Return FFT of impulse
                impulseFFT = mea_data;
            }

            // IFFT to get the impulse response
            Fourier.IFFT(swp_data.Length, swp_data);

            // Scan the imp to find maximum
            mx = 0;
            for (int j = 0; j < sampleRate; j++)
            {
                Complex si = swp_data[j];
                double mg = Math.Abs(si.Magnitude);
                if (mg > mx) mx = mg;
            }

            if (_noNorm)
            {
                mx = 1.0;
            }

            // Yield the first half (normalized) as result
            cs = new CallbackSource(1, sampleRate, delegate(long j)
            {
                if (j > (_returnAll ? L-1 : L / 2))
                {
                    return null;
                }
                Complex si = swp_data[j];
                Sample s = new Sample(si.Re / mx);
                return s;
            });
            cs.SampleRate = sampleRate;
            cs.NumChannels = 1;

            return cs;
        }
Пример #4
0
        static void WriteImpulsePCM(ISoundObj stereoImpulse, string fileNameL, string fileNameR)
        {
            ISoundObj sb;
            WaveWriter writer;
            ushort bits = 32;
            int nTot = 196607;
            int nBef = 98300;

            // need padding before the sample.
            // window the sample first, then pad it.
            // Total length 65536, includes nPad and _peakPosL before impulse
            int nPad = nBef - _peakPosL;
            int usableLength = nTot - nPad;
            int windowCenter = usableLength / 2;
            int windowSide = _peakPosL * 2 / 3;
            int windowFlat = windowCenter - windowSide;
            ISoundObj ch = new SingleChannel(stereoImpulse, 0);
            ISoundObj wn = new BlackmanHarris(windowCenter, windowSide, windowFlat);
            wn.Input = ch;
            sb = new SampleBuffer(wn).PaddedSubset(-nPad, nTot);

            writer = new WaveWriter(fileNameL);
            writer.Input = sb;
            writer.Format = WaveFormat.IEEE_FLOAT;
            writer.BitsPerSample = bits;
            writer.SampleRate = _sampleRate;
            writer.Raw = true;

            writer.Normalization = -6.0;
            writer.NormalizationType = NormalizationType.PEAK_DBFS;

            writer.Run();
            writer.Close();

            double g = writer.Gain;

            writer = null;

            nPad = nBef - _peakPosR;
            usableLength = nTot - nPad;
            windowCenter = usableLength / 2;
            windowSide = _peakPosR * 2 / 3;
            windowFlat = windowCenter - windowSide;
            ch = new SingleChannel(stereoImpulse, 1);
            wn = new BlackmanHarris(windowCenter, windowSide, windowFlat);
            wn.Input = ch;
            sb = new SampleBuffer(wn).PaddedSubset(-nPad, nTot);

            writer = new WaveWriter(fileNameR);
            writer.Input = sb;
            writer.Format = WaveFormat.IEEE_FLOAT;
            writer.BitsPerSample = bits;
            writer.SampleRate = _sampleRate;
            writer.Raw = true;
            writer.Gain = g;
            writer.Run();
            writer.Close();
            writer = null;
        }
Пример #5
0
        static ISoundObj DecodeBFormatUHJ(ISoundObj source)
        {
            ISoundObj input = source;
            uint sr = input.SampleRate;
            /*
            if (_ambiUseShelf)
            {
                // Shelf-filters
                // boost W at high frequencies, and boost X, Y at low frequencies
                FilterProfile lfgXY = new FilterProfile();
                lfgXY.Add(new FreqGain(_ambiShelfFreq / 2, 0));
                lfgXY.Add(new FreqGain(_ambiShelfFreq * 2, -1.25));
                FilterImpulse fiXY = new FilterImpulse(0, lfgXY, FilterInterpolation.COSINE, sr);

                FilterProfile lfgW = new FilterProfile();
                lfgW.Add(new FreqGain(_ambiShelfFreq / 2, 0));
                lfgW.Add(new FreqGain(_ambiShelfFreq * 2, 1.76));
                FilterImpulse fiW = new FilterImpulse(0, lfgW, FilterInterpolation.COSINE, sr);
            }
            if (_ambiUseDistance)
            {
                // Distance compensation filters
                // apply phase shift to X, Y at (very) low frequencies
                double fc = MathUtil.FcFromMetres(_ambiDistance);
                IIR1 discomp = new IIR1LP(sr, fc, 8192);    // tbd: chain this
            }
            */

            // Transformation filters
            //
            // Primary reference:
            // Gerzon 1985 "Ambisonics in Multichannel Broadcasting and Video"
            //
            // Coefficients from: http://en.wikipedia.org/wiki/Ambisonic_UHJ_format:
            // S = 0.9396926*W + 0.1855740*X
            // D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y
            // Left = (S + D)/2.0
            // Right = (S - D)/2.0
            // which makes
            // Left = (0.092787 + 0.2549302j)X + (0.4698463 - 0.17101005j)W + (0.3277258)Y
            // Right= (0.092787 - 0.2549302j)X + (0.4698463 + 0.17101005j)W - (0.3277258)Y
            //
            // Coefficients from: http://www.york.ac.uk/inst/mustech/3d_audio/ambis2.htm
            // Left = (0.0928 + 0.255j)X + (0.4699 - 0.171j)W + (0.3277)Y
            // Right= (0.0928 - 0.255j)X + (0.4699 + 0.171j)W - (0.3277)Y

            // The Mid-Side versions are simpler
            // L+R = (0.0928 + 0.255j)X + (0.4699 - 0.171j)W + (0.3277)Y + ((0.0928 - 0.255j)X + (0.4699 + 0.171j)W - (0.3277)Y)
            //     = (0.1856)X          + (0.9398)W
            // L-R = (0.0928 + 0.255j)X + (0.4699 - 0.171j)W + (0.3277)Y - ((0.0928 - 0.255j)X + (0.4699 + 0.171j)W - (0.3277)Y)
            //     =          (0.510j)X +          (0.342j)W + (0.6554)Y
            // but since we're delaying signal via convolution anyway, not *too* much extra processing to do in LR mode...

            // Separate the WXY channels
            ISoundObj channelW = new SingleChannel(input, 0);
            ISoundObj channelX = new SingleChannel(input, 1, true);
            ISoundObj channelY = new SingleChannel(input, 2, true);

            // Z not used; height is discarded in UHJ conversion.
            // Don't assume it's there; horizontal-only .AMB files won't have a fourth channel
            //          ISoundObj channelZ = new SingleChannel(input, 3);

            // Phase shift j is implemented with Hilbert transforms
            // so let's load up some filters, multiply by the appropriate coefficients.
            int len = 8191;
            PhaseMultiplier xl = new PhaseMultiplier(new Complex(0.0927870, 0.25493020), len, sr);
            PhaseMultiplier wl = new PhaseMultiplier(new Complex(0.4698463, -0.17101005), len, sr);
            PhaseMultiplier yl = new PhaseMultiplier(new Complex(0.3277258, 0.00000000), len, sr);
            PhaseMultiplier xr = new PhaseMultiplier(new Complex(0.0927870, -0.25493020), len, sr);
            PhaseMultiplier wr = new PhaseMultiplier(new Complex(0.4698463, 0.17101005), len, sr);
            PhaseMultiplier yr = new PhaseMultiplier(new Complex(-0.3277258, 0.00000000), len, sr);

            // The convolvers to filter
            FastConvolver cwl = new FastConvolver(channelW, wl);
            FastConvolver cxl = new FastConvolver(channelX, xl);
            FastConvolver cyl = new FastConvolver(channelY, yl);
            FastConvolver cwr = new FastConvolver(channelW, wr);
            FastConvolver cxr = new FastConvolver(channelX, xr);
            FastConvolver cyr = new FastConvolver(channelY, yr);

            // Sum to get the final output of these things:
            Mixer mixerL = new Mixer();
            mixerL.Add(cwl, 1.0);
            mixerL.Add(cxl, 1.0);
            mixerL.Add(cyl, 1.0);

            Mixer mixerR = new Mixer();
            mixerR.Add(cwr, 1.0);
            mixerR.Add(cxr, 1.0);
            mixerR.Add(cyr, 1.0);

            // output in stereo
            ChannelSplicer uhj = new ChannelSplicer();
            uhj.Add(mixerL);
            uhj.Add(mixerR);

            return uhj;
        }
Пример #6
0
        static ISoundObj DecodeBFormatCrossed(ISoundObj source)
        {
            // Stereo decode:
            // A shuffle of the X (front-back) and Y (left-right) channels
            // with some W mixed, so the effective mics become (hyper)cardioid instead of figure-8.
            // If _ambiCardioid=0, this is the same as Blumlein.
            // If _ambiCardioid=1, this is cardioid
            // Of any value between, for hypercardioid patterns.

            // Separate the WXY channels
            ISoundObj channelW = new SingleChannel(source, 0);
            ISoundObj channelX = new SingleChannel(source, 1, true);
            ISoundObj channelY = new SingleChannel(source, 2, true);

            // The _ambiMicAngle says angle between the virtual microphones (degrees)
            // e.g. if this is 100
            //   Left and right are each 50 degrees from forward.
            // These are implemented by mixing appropriate amounts of X and Y;
            //   X * cos(angle/2)
            //   Y * sin(angle/2)
            //
            // For default mic angle of 90 degrees, mulX=mulY=sqrt(2)/2
            //
            double mulX = Math.Cos(MathUtil.Radians(_ambiMicAngle / 2));
            double mulY = Math.Sin(MathUtil.Radians(_ambiMicAngle / 2));

            // Mix back together, adding appropriate amounts of W.
            // The W channel gain is conventionally sqrt(2)/2 relative to X and Y,
            Mixer mixerL = new Mixer();
            mixerL.Add(channelW, _ambiCardioid * MathUtil.INVSQRT2);
            mixerL.Add(channelX, mulX);
            mixerL.Add(channelY, mulY);

            Mixer mixerR = new Mixer();
            mixerR.Add(channelW, _ambiCardioid * MathUtil.INVSQRT2);
            mixerR.Add(channelX, mulX);
            mixerR.Add(channelY, -mulY);

            // output in stereo
            ChannelSplicer stereo = new ChannelSplicer();
            stereo.Add(mixerL);
            stereo.Add(mixerR);
            return stereo;
        }
Пример #7
0
        static ISoundObj RotateBFormat(ISoundObj source)
        {
            // Rotate a B-Format source
            ISoundObj channelW = new SingleChannel(source, 0);
            ISoundObj channelX = new SingleChannel(source, 1, true);
            ISoundObj channelY = new SingleChannel(source, 2, true);
            ISoundObj channelZ = new SingleChannel(source, 3, true);

            double rx = MathUtil.Radians(_ambiRotateX);
            double ry = MathUtil.Radians(_ambiRotateY);
            double rz = MathUtil.Radians(_ambiRotateZ);

            // Mixer W = new Mixer();
            Mixer X = new Mixer();
            Mixer Y = new Mixer();
            Mixer Z = new Mixer();

            // W is unchanged (omni)
            // W.Add(channelW, 1.0);

            // http://www.muse.demon.co.uk/fmhrotat.html

            // tilt
            // x1 = x * 1       + y * 0       + z * 0
            // y1 = x * 0       + y * Cos(rx) - z * Sin(rx)
            // z1 = x * 0       + y * Sin(rx) + z * Cos(rx)
            // tumble
            // x2 = x * Cos(ry) + y * 0       - z * Sin(ry)
            // y2 = x * 0       + y * 1       + z * 0
            // z2 = x * Sin(ry) + y * 0       + z * Cos(ry)
            // rotate
            // x3 = x * Cos(rz) - y * Sin(rz) + z * 0
            // y3 = x * Sin(rz) + y * Cos(rz) + z * 0
            // z3 = x * 0       + y * 0       + z * 1
            // (read that downwards to get:)

            X.Add(channelX, Math.Cos(ry) * Math.Cos(rz));
            X.Add(channelY, -Math.Sin(rz));
            X.Add(channelZ, -Math.Sin(ry));

            Y.Add(channelX, Math.Sin(rz));
            Y.Add(channelY, Math.Cos(rx) * Math.Cos(rz));
            Y.Add(channelZ, -Math.Sin(rx));

            Z.Add(channelX, Math.Sin(ry));
            Z.Add(channelY, Math.Sin(rz));
            Z.Add(channelZ, Math.Cos(rz) * Math.Cos(ry));

            ChannelSplicer ret = new ChannelSplicer();
            ret.Add(channelW);
            ret.Add(X);
            ret.Add(Y);
            ret.Add(Z);

            return ret;
        }
Пример #8
0
        private static double[] magbands(ISoundObj impulse, double bins)
        {
            uint nSR  = impulse.SampleRate;
            uint nSR2 = nSR / 2;

            int nn = (int)bins + 1;

            double[] muff = new double[nn];

            ushort nChannels = impulse.NumChannels;

            for (ushort c = 0; c < nChannels; c++)
            {
                // Read channel into a buffer
                SingleChannel channel = impulse.Channel(c);
                SoundBuffer   buff    = new SoundBuffer(channel);
                buff.ReadAll();

                // And then double in length to prevent wraparound
                buff.PadTo(buff.Count * 2);
                // Pad to next higher power of two
                buff.PadToPowerOfTwo();
                // Read out into array of complex
                Complex[][] data  = buff.ToComplexArray();
                Complex[]   cdata = data[0];

                // Then we're done with the buffer for this channel
                buff = null;
                GC.Collect();

                // FFT in place
                Fourier.FFT(cdata.Length, cdata);

                int n = cdata.Length / 2;

                // Drop the FFT magnitudes into the 'muff' array
                // according to an ERB-based scale (near-logarithmic).
                // Then smoothing is easy.
                double binw    = (nSR2 / (double)n);
                int    prevbin = 0;
                int    nbin    = 0;
                double v       = 0;
                for (int j = 0; j < n; j++)
                {
                    double f   = (double)j * binw;             // equiv freq, Hz
                    int    bin = (int)ERB.f2bin(f, nSR, bins); // the bin we drop this sample in
                    v += cdata[j].Magnitude;
                    nbin++;

                    if ((bin > prevbin) || (j == n - 1))
                    {
                        muff[prevbin] += (v / nbin);
                        v              = 0;
                        nbin           = 0;
                        prevbin        = bin;
                    }
                }
            }

            // Now muff is sum(all channels) of average-magnitude-per-bin.
            // Divide it all by the number of channels, so our gains are averaged...
            for (int j = 0; j < muff.Length; j++)
            {
                muff[j] = muff[j] / nChannels;
            }

            return(muff);
        }