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