/// <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); }
/// <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; }
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; }
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; }
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; }