Wraps a callback function to become a SoundObj Example: new CallbackSource( 1, 44100, delegate(int j) { ... return sample ... } );
Inheritance: SoundObj
Example #1
0
        /// <summary>
        /// Calculate the weighted volume of a *single channel* 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 WeightedVolume1(ISoundObj src, double dbSPL, double dbSPLBase)
        {
            if (src.NumChannels != 1)
            {
                throw new ArgumentException("Requires single-channel");
            }

            // Read channel into a buffer
            SoundBuffer buff = new SoundBuffer(src);

            buff.ReadAll();

            // And then double in length to prevent wraparound
            buff.PadTo(buff.Count * 2);
            // Pad to next higher power of two
            buff.PadToPowerOfTwo();
            int n = buff.Count;

            double wvImpulse = WeightedVolume2(buff, dbSPL, dbSPLBase);

            // compare with a Dirac pulse the same length
            CallbackSource dirac = new CallbackSource(1, src.SampleRate, delegate(long j)
            {
                if (j >= n)
                {
                    return(null);
                }
                double v = 0;
                if (j == n / 2)
                {
                    v = 1;
                }
                return(new Sample(v));
            });

            buff = new SoundBuffer(dirac);
            buff.ReadAll();
            double wvDirac = WeightedVolume2(buff, dbSPL, dbSPLBase);

            buff = null;
            GC.Collect();

            return(wvImpulse / wvDirac);
        }
Example #2
0
        /// <summary>
        /// Calculate the weighted volume of a *single channel* 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 WeightedVolume1(ISoundObj src, double dbSPL, double dbSPLBase)
        {
            if (src.NumChannels != 1)
            {
                throw new ArgumentException("Requires single-channel");
            }

            // Read channel into a buffer
            SoundBuffer buff = new SoundBuffer(src);
            buff.ReadAll();

            // And then double in length to prevent wraparound
            buff.PadTo(buff.Count * 2);
            // Pad to next higher power of two
            buff.PadToPowerOfTwo();
            int n = buff.Count;

            double wvImpulse = WeightedVolume2(buff, dbSPL, dbSPLBase);

            // compare with a Dirac pulse the same length
            CallbackSource dirac = new CallbackSource(1, src.SampleRate, delegate(long j)
            {
                if (j >= n)
                {
                    return null;
                }
                double v = 0;
                if (j == n / 2)
                {
                    v = 1;
                }
                return new Sample(v);
            });
            buff = new SoundBuffer(dirac);
            buff.ReadAll();
            double wvDirac = WeightedVolume2(buff, dbSPL, dbSPLBase);

            buff = null;
            GC.Collect();

            return wvImpulse / wvDirac;
        }
Example #3
0
        static ISoundObj SlidingLowPass(ISoundObj impulse, int peakPos)
        {
            // Filter the impulse response with a sliding lowpass filter
            // - Take the first (peakpos) samples unchanged
            // - Take the next 2.5ms (~110 samples @ 44100) unchanged
            // - Fade to a lowpass-filtered version

            uint sampleRate = impulse.SampleRate;
            int startpos1 = (int)(sampleRate * 0.0025);     // 2.5mS, 110 samples
            int startpos2 = (int)(sampleRate * 0.0050);
            int startpos3 = (int)(sampleRate * 0.0100);
            int startpos4 = (int)(sampleRate * 0.0200);
            int startpos5 = (int)(sampleRate * 0.0400);

            List<IEnumerator<ISample>> filters = null;
            int nFilter = 0;
            int nFilters = 0;
            int x = 0;
            int f0len = 0;
            ISoundObj filteredImpulse = new CallbackSource(1, sampleRate, delegate(long j)
            {
                if(j==0)
                {
                    // initialize the list of filters
                    impulse.Reset();
                    filters = new List<IEnumerator<ISample>>(6);
                    ISoundObj f0 = LowPassFiltered(impulse, 20, 0);
                    f0len = f0.Iterations;
                    filters.Add(f0.Samples);        // unfiltered
                    filters.Add(LowPassFiltered(impulse, 640, -10).Samples);      // LPF from 640Hz, kick in at +110
                    filters.Add(LowPassFiltered(impulse, 320, -20).Samples);       // LPF from 320Hz, kick in at +220 (etc)
                    filters.Add(LowPassFiltered(impulse, 160, -30).Samples);       // +440
                    filters.Add(LowPassFiltered(impulse, 80, -40).Samples);        // +880
                    filters.Add(LowPassFiltered(impulse, 40, -50).Samples);        // +1760 = 40ms
                    nFilter = 0;
                    nFilters = filters.Count;
                }

                // Move the filters along a notch.
                // (or, right at the beginning, move all the filters along by a lot, compensating for their center)
                // For perf, we don't need to keep enumerating the filters we've finished using.
                bool moreData = true;
                int nSkip = 1;
                if (j == 0)
                {
                    nSkip = (f0len/2)+1;
                }
                for (int skip = 0; skip < nSkip; skip++)
                {
                    int nStart = (nFilter == 0) ? 0 : nFilter - 1;    // Keep moving even the old filters along so mixing works later
                    for (int f = nStart; f < nFilters; f++)
                    {
                        moreData &= filters[f].MoveNext();
                    }
                }
                if (!moreData) return null;

                // Decide which filter we'll use
                x++;
                if (j == peakPos + startpos1) { nFilter = 1; x = 0; }
                if (j == peakPos + startpos2) { nFilter = 2; x = 0; }
                if (j == peakPos + startpos3) { nFilter = 3; x = 0; }
                if (j == peakPos + startpos4) { nFilter = 4; x = 0; }
                if (j == peakPos + startpos5) { nFilter = 5; x = 0; }

                // Pick the sample from the current filter
                ISample s = filters[nFilter].Current;

                // For a short region after switch-over, mix slowly with the sample from the previous filter
                int w = 20;
                if (x < w && nFilter > 0)
                {
                    ISample sPrev = filters[nFilter-1].Current;
                    for (int c = 0; c < s.NumChannels; c++)
                    {
                        s[c] = ((double)x/w)*s[c] + ((double)(w-x)/w)*sPrev[c];
                    }
                }
                return s;
            });
            return filteredImpulse;
        }
Example #4
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;
        }
Example #5
0
        static ISoundObj DecodeBFormatBinaural(ISoundObj source)
        {
            throw new NotImplementedException();

            ISoundObj input = source;
            uint sr = input.SampleRate;

            // Convolve the BFormat data with the matrix filter
            if (!String.IsNullOrEmpty(_bformatFilter))
            {
                string ignore;
                WaveReader rdr = GetAppropriateImpulseReader(_bformatFilter, out ignore);
                FastConvolver ambiConvolver = new FastConvolver(source, rdr);
                input = ambiConvolver;
            }

            // Cardioid directed at four (or six) virtual loudspeakers
            IEnumerator<ISample> src = input.Samples;
            CallbackSource bin = new CallbackSource(2, sr, delegate(long j)
            {
                if (src.MoveNext())
                {
                    ISample s = src.Current;
                    double w = s[0];
                    double x = s[1];
                    double y = s[2];
                    double z = s[3];
                    double wFactor = -0.5;
                    double left = x + y + z + (wFactor * w);
                    double right = x - y + z + (wFactor * w);
                    ISample sample = new Sample2(left, right);
                    return sample;
                }
                return null;
            });

            return bin;
        }