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 }
private ISoundObj _buff() { if (double.IsNaN(_normalization)) { return(_input); } // We've been asked to normalize SoundBuffer b = new SoundBuffer(_input); b.ReadAll(); _gain = b.Normalize(_normalization, false); return(b); }
/// <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); }
public WindowedBuffer(ISoundObj input, CosWindow window, int start, int count) { _buff = new SoundBuffer(new SampleBuffer(input).Subset(start, count)); _buff.ApplyWindow(window); }
private static double WeightedVolume2(SoundBuffer src, double dbSPL, double dbSPLBase) { double v = 0; uint sr = src.SampleRate; // Read buffer into array of complex Complex[][] data = src.ToComplexArray(); // We only have a single channel Complex[] cdata = data[0]; // FFT in place Fourier.FFT(cdata.Length, cdata); // Calculate magnitude, weighted by 80-phon loudness, for each loudness band. // These are the ISO measured points: FilterProfile lfg; if (dbSPLBase == 0) { lfg = SPL(dbSPL); } else { lfg = DifferentialSPL(dbSPL, dbSPLBase); } // lfg.Add(new FreqGain(sr / 2, lfg[lfg.Count - 1].Gain)); // Cover the ISO measured range (only...) int nStart = (int)(lfg[0].Freq * (long)cdata.Length / sr); int nEnd = (int)(lfg[lfg.Count - 1].Freq * (long)cdata.Length / sr); // Just use linear interpolation (on a dB scale; linear freq scale) of gain between each measured point int nfg = 0; int startp = nStart; int endp = (int)(lfg[nfg + 1].Freq * (long)cdata.Length / sr); // endpoint of this band double dB1 = lfg[nfg].Gain; // SPL of the ISO223 curve at this freq double dB2 = lfg[nfg+1].Gain; // ...and the next point double vThisBand = 0; int nThisBand = 0; for (int j = nStart; j < nEnd; j++) { if (j > endp) { if (nThisBand > 0) v += Math.Sqrt(vThisBand / nThisBand); // RMS while (j >= endp) { nfg++; startp = j; endp = (int)(lfg[nfg + 1].Freq * (long)cdata.Length / sr); dB1 = lfg[nfg].Gain; dB2 = lfg[nfg + 1].Gain; } vThisBand = 0; nThisBand = 0; } Complex c = cdata[j]; double dbHere = dB1 + ((dB2 - dB1) * (double)(j - startp) / (double)(endp - startp)); vThisBand += (c.Re * c.Re) / MathUtil.gain(dbHere); nThisBand++; } if(nThisBand>0) v += Math.Sqrt(vThisBand / nThisBand); return v; }
/// <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; }
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 }
private static double WeightedVolume2(SoundBuffer src, double dbSPL, double dbSPLBase) { double v = 0; uint sr = src.SampleRate; // Read buffer into array of complex Complex[][] data = src.ToComplexArray(); // We only have a single channel Complex[] cdata = data[0]; // FFT in place Fourier.FFT(cdata.Length, cdata); // Calculate magnitude, weighted by 80-phon loudness, for each loudness band. // These are the ISO measured points: FilterProfile lfg; if (dbSPLBase == 0) { lfg = SPL(dbSPL); } else { lfg = DifferentialSPL(dbSPL, dbSPLBase); } // lfg.Add(new FreqGain(sr / 2, lfg[lfg.Count - 1].Gain)); // Cover the ISO measured range (only...) int nStart = (int)(lfg[0].Freq * (long)cdata.Length / sr); int nEnd = (int)(lfg[lfg.Count - 1].Freq * (long)cdata.Length / sr); // Just use linear interpolation (on a dB scale; linear freq scale) of gain between each measured point int nfg = 0; int startp = nStart; int endp = (int)(lfg[nfg + 1].Freq * (long)cdata.Length / sr); // endpoint of this band double dB1 = lfg[nfg].Gain; // SPL of the ISO223 curve at this freq double dB2 = lfg[nfg + 1].Gain; // ...and the next point double vThisBand = 0; int nThisBand = 0; for (int j = nStart; j < nEnd; j++) { if (j > endp) { if (nThisBand > 0) { v += Math.Sqrt(vThisBand / nThisBand); // RMS } while (j >= endp) { nfg++; startp = j; endp = (int)(lfg[nfg + 1].Freq * (long)cdata.Length / sr); dB1 = lfg[nfg].Gain; dB2 = lfg[nfg + 1].Gain; } vThisBand = 0; nThisBand = 0; } Complex c = cdata[j]; double dbHere = dB1 + ((dB2 - dB1) * (double)(j - startp) / (double)(endp - startp)); vThisBand += (c.Re * c.Re) / MathUtil.gain(dbHere); nThisBand++; } if (nThisBand > 0) { v += Math.Sqrt(vThisBand / nThisBand); } return(v); }
static void Main(string[] args) { // Find where this executable is launched from string[] cargs = Environment.GetCommandLineArgs(); _thisFolder = Path.GetDirectoryName(cargs[0]); if (String.IsNullOrEmpty(_thisFolder)) { _thisFolder = Environment.CurrentDirectory; } string appData = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData); _impulsesFolder = Path.GetFullPath(Path.Combine(appData, "InguzEQ" + slash + "Impulses" + slash)); string[] inFiles = new string[4]; string inL = ""; string inR = ""; if (!DisplayInfo()) { return; } bool ok = (args.Length > 0); bool longUsage = false; for (int j = 0; ok && j < args.Length; j++) { string arg = args[j]; switch (args[j].ToUpperInvariant()) { case "/?": case "-?": case "/H": case "/HELP": ok = false; longUsage = true; break; case "/L": case "/0": inFiles[0] = args[++j]; _nInFiles = Math.Max(_nInFiles, 1); break; case "/R": case "/1": inFiles[1] = args[++j]; _nInFiles = Math.Max(_nInFiles, 2); break; case "/2": inFiles[2] = args[++j]; _nInFiles = Math.Max(_nInFiles, 3); break; case "/3": inFiles[3] = args[++j]; _nInFiles = Math.Max(_nInFiles, 4); break; case "/LENGTH": _filterLen = int.Parse(args[++j], CultureInfo.InvariantCulture); if (_filterLen < 16) { throw new Exception("Length is too small."); } break; case "/DBL": _dbl = true; break; case "/PCM": _pcm = true; break; case "/NODRC": _noDRC = true; break; case "/NOSKEW": _noSkew = true; break; case "/NONORM": // No normalization of the impulse response (undocumented) _noNorm = true; break; case "/SPLIT": _split = true; break; case "/COPY": _copy = true; break; case "/GAIN": _gain = double.Parse(args[++j], CultureInfo.InvariantCulture); break; case "/ALL": // Returns negative-time components as part of the impulse response // (experimental, to be used for THD measurement) _returnAll = true; break; case "/POWER": // Raises sweep to power n // (experimental, to be used for THD measurement) _power = int.Parse(args[++j], CultureInfo.InvariantCulture); break; case "/FMIN": // (experimental, i.e. broken) _fmin = int.Parse(args[++j], CultureInfo.InvariantCulture); _fminSpecified = true; break; case "/FMAX": // (experimental, i.e. broken) _fmax = int.Parse(args[++j], CultureInfo.InvariantCulture); _fmaxSpecified = true; break; case "/DIRECT": // Create filtered (direct-sound) filters _doDirectFilters = true; break; case "/NOSUB": // Don't apply subsonic filter to the impulse response _noSubsonicFilter = true; break; case "/NOOVER": // Don't override DRC's settings for filter type and length _noOverrideDRC = true; break; case "/KEEPTEMP": // Undocumented _keepTempFiles = true; break; case "/REFCH": // Override the reference-channel detection _refchannel = int.Parse(args[++j], CultureInfo.InvariantCulture); if (_refchannel<0 || _refchannel > _nInFiles - 1) { throw new Exception(String.Format("RefCh can only be from 0 to {0}.", _nInFiles-1)); } break; case "/ENV": // Undocumented. Save the Hilbert envelope _env = true; break; case "-": // ignore break; default: ok = false; break; } } if (!ok) { DisplayUsage(longUsage); } else { try { if (!_noDRC) { if (!File.Exists(GetDRCExe())) { stderr.WriteLine("Denis Sbragion's DRC (http://drc-fir.sourceforge.net/) was not found."); stderr.WriteLine("Only the impulse response will be calculated, not correction filters."); stderr.WriteLine(""); _noDRC = true; } } if (!_noDRC) { FileInfo[] drcfiles = new DirectoryInfo(_thisFolder).GetFiles("*.drc"); if (drcfiles.Length == 0) { stderr.WriteLine("No .drc files were found in the current folder."); stderr.WriteLine("Only the impulse response will be calculated, not correction filters."); stderr.WriteLine(""); _noDRC = true; } } for(int i=0; i<_nInFiles; i++) { string inFile = inFiles[i]; if (String.IsNullOrEmpty(inFile)) { stderr.WriteLine("Error: The {0} input file was not specified.", FileDescription(i)); return; } if (!File.Exists(inFile)) { stderr.WriteLine("Error: The {0} input file {1} was not found.", FileDescription(i), inFile); return; } for (int j = 0; j < i; j++) { if (inFile.Equals(inFiles[j])) { stderr.WriteLine("Warning: The same input file ({0}) was specified for both {1} and {2}!", inFile, FileDescription(j), FileDescription(i)); //stderr.WriteLine(); } } } // Temporary if (_nInFiles != 2) { stderr.WriteLine("Error: Two input files must be specified."); return; } inL = inFiles[0]; inR = inFiles[1]; // end temporary uint sampleRate; List<SoundObj> impulses; List<ISoundObj> filteredImpulses; List<string> impDirects; List<Complex[]> impulseFFTs; List<double> maxs; SoundObj impulseL; SoundObj impulseR; ISoundObj filteredImpulseL = null; ISoundObj filteredImpulseR = null; string impDirectL = null; string impDirectR = null; Complex[] impulseLFFT; Complex[] impulseRFFT; WaveWriter writer; ISoundObj buff; double g; if (!_keepTempFiles) { _tempFiles.Add("rps.pcm"); _tempFiles.Add("rtc.pcm"); } // Find the left impulse stderr.WriteLine("Processing left measurement ({0})...", inL); impulseL = Deconvolve(inL, out impulseLFFT, out _peakPosL); sampleRate = impulseL.SampleRate; _sampleRate = sampleRate; double peakM = Math.Round(MathUtil.Metres(_peakPosL, sampleRate), 2); double peakFt = Math.Round(MathUtil.Feet(_peakPosL, sampleRate), 2); stderr.WriteLine(" Impulse peak at sample {0} ({1}m, {2}ft)", _peakPosL, peakM, peakFt); // Write to PCM string impFileL = Path.GetFileNameWithoutExtension(inL) + "_imp" + ".pcm"; if (!_keepTempFiles) { _tempFiles.Add(impFileL); } writer = new WaveWriter(impFileL); writer.Input = impulseL; writer.Format = WaveFormat.IEEE_FLOAT; writer.BitsPerSample = 32; writer.SampleRate = _sampleRate; writer.Raw = true; writer.Run(); writer.Close(); // Write the impulseFFT to disk int L = impulseLFFT.Length; string impTempL = Path.GetFileNameWithoutExtension(inL) + "_imp" + ".dat"; _tempFiles.Add(impTempL); writer = new WaveWriter(impTempL); writer.Input = new CallbackSource(2, sampleRate, delegate(long j) { if (j >= L / 2) { return null; } Complex si = impulseLFFT[j]; // +impulseLFFT[L - j - 1]; ISample s = new Sample2(); s[0] = si.Magnitude; s[1] = si.Phase / Math.PI; return s; }); writer.Format = WaveFormat.IEEE_FLOAT; writer.BitsPerSample = 32; writer.SampleRate = _sampleRate; writer.Raw = false; writer.Run(); writer.Close(); writer = null; impulseLFFT = null; GC.Collect(); if (_doDirectFilters) { // Sliding low-pass filter over the impulse stderr.WriteLine(" Filtering..."); filteredImpulseL = SlidingLowPass(impulseL, _peakPosL); // Write PCM for the filtered impulse impDirectL = Path.GetFileNameWithoutExtension(inL) + "_impfilt" + ".pcm"; if (!_keepTempFiles) { _tempFiles.Add(impDirectL); } writer = new WaveWriter(impDirectL); writer.Input = filteredImpulseL; writer.Format = WaveFormat.IEEE_FLOAT; writer.SampleRate = _sampleRate; writer.BitsPerSample = 32; writer.Raw = false; writer.Run(); writer.Close(); writer = null; filteredImpulseL.Reset(); } GC.Collect(); stderr.WriteLine(" Deconvolution: left impulse done."); stderr.WriteLine(); // Find the right impulse stderr.WriteLine("Processing right measurement ({0})...", inR); impulseR = Deconvolve(inR, out impulseRFFT, out _peakPosR); peakM = Math.Round(MathUtil.Metres(_peakPosR, sampleRate), 2); peakFt = Math.Round(MathUtil.Feet(_peakPosR, sampleRate), 2); stderr.WriteLine(" Impulse peak at sample {0} ({1}m, {2}ft)", _peakPosR, peakM, peakFt); // Write to PCM string impFileR = Path.GetFileNameWithoutExtension(inR) + "_imp" + ".pcm"; if (!_keepTempFiles) { _tempFiles.Add(impFileR); } writer = new WaveWriter(impFileR); writer.Input = impulseR; writer.Format = WaveFormat.IEEE_FLOAT; writer.BitsPerSample = 32; writer.SampleRate = _sampleRate; writer.Raw = true; writer.Run(); writer.Close(); // Write the impulseFFT magnitude to disk L = impulseRFFT.Length; string impTempR = Path.GetFileNameWithoutExtension(inR) + "_imp" + ".dat"; _tempFiles.Add(impTempR); writer = new WaveWriter(impTempR); writer.Input = new CallbackSource(2, impulseR.SampleRate, delegate(long j) { if (j >= L / 2) { return null; } Complex si = impulseRFFT[j]; // +impulseRFFT[L - j - 1]; ISample s = new Sample2(); s[0] = si.Magnitude; s[1] = si.Phase / Math.PI; return s; }); writer.Format = WaveFormat.IEEE_FLOAT; writer.BitsPerSample = 32; writer.SampleRate = _sampleRate; writer.Raw = false; writer.Run(); writer.Close(); writer = null; impulseRFFT = null; GC.Collect(); if (_doDirectFilters) { // Sliding low-pass filter over the impulse stderr.WriteLine(" Filtering..."); filteredImpulseR = SlidingLowPass(impulseR, _peakPosR); // Write PCM for the filtered impulse impDirectR = Path.GetFileNameWithoutExtension(inR) + "_impfilt" + ".pcm"; if (!_keepTempFiles) { _tempFiles.Add(impDirectR); } writer = new WaveWriter(impDirectR); writer.Input = filteredImpulseR; writer.Format = WaveFormat.IEEE_FLOAT; writer.BitsPerSample = 32; writer.SampleRate = _sampleRate; writer.Raw = false; writer.Run(); writer.Close(); writer = null; filteredImpulseR.Reset(); } GC.Collect(); stderr.WriteLine(" Deconvolution: right impulse done."); stderr.WriteLine(); // Join the left and right impulse files (truncated at 65536) into a WAV // and normalize loudness for each channel stderr.WriteLine("Splicing and normalizing (1)"); ChannelSplicer longstereoImpulse = new ChannelSplicer(); // (Don't normalize each channel's volume separately if _returnAll, it's just too expensive) if (_returnAll) { buff = impulseL; } else { buff = new SoundBuffer(new SampleBuffer(impulseL).Subset(0, 131071)); g = Loudness.WeightedVolume(buff); (buff as SoundBuffer).ApplyGain(1 / g); } longstereoImpulse.Add(buff); if (_returnAll) { buff = impulseR; } else { buff = new SoundBuffer(new SampleBuffer(impulseR).Subset(0, 131071)); g = Loudness.WeightedVolume(buff); (buff as SoundBuffer).ApplyGain(1 / g); } longstereoImpulse.Add(buff); ISoundObj stereoImpulse = longstereoImpulse; _impulseFiles.Add("Impulse_Response_Measured.wav: stereo impulse response from measurements"); writer = new WaveWriter("Impulse_Response_Measured.wav"); writer.Input = longstereoImpulse; writer.Format = WaveFormat.IEEE_FLOAT; writer.BitsPerSample = 32; writer.SampleRate = _sampleRate; writer.Normalization = -1; writer.Raw = false; writer.Run(); writer.Close(); writer = null; if (_env) { // Also save the Hilbert envelope HilbertEnvelope env = new HilbertEnvelope(8191); env.Input = longstereoImpulse; _impulseFiles.Add("Impulse_Response_Envelope.wav: Hilbert envelope of the impulse response"); writer = new WaveWriter("Impulse_Response_Envelope.wav"); writer.Input = env; writer.Format = WaveFormat.IEEE_FLOAT; writer.BitsPerSample = 32; writer.SampleRate = _sampleRate; writer.Normalization = -1; writer.Raw = false; writer.Run(); writer.Close(); writer = null; } if (_dbl) { // Create DBL files for Acourate _impulseFiles.Add("PulseL.dbl: impulse response, raw data (64-bit float), left channel "); _impulseFiles.Add("PulseR.dbl: impulse response, raw data (64-bit float), right channel"); _impulseFiles.Add(" (use skew=" + (_peakPosL - _peakPosR) + " for time alignment)"); WriteImpulseDBL(stereoImpulse, "PulseL.dbl", "PulseR.dbl"); } if (_pcm) { // Create PCM files for Octave (etc) _impulseFiles.Add("LUncorrected.pcm: impulse response, raw data (32-bit float), left channel"); _impulseFiles.Add("RUncorrected.pcm: impulse response, raw data (32-bit float), right channel"); WriteImpulsePCM(stereoImpulse, "LUncorrected.pcm", "RUncorrected.pcm"); } stereoImpulse = null; longstereoImpulse = null; buff = null; GC.Collect(); if (_doDirectFilters) { // Same for the filtered impulse response stderr.WriteLine("Splicing and normalizing (2)"); ChannelSplicer longstereoImpulseF = new ChannelSplicer(); buff = new SoundBuffer(new SampleBuffer(filteredImpulseL).Subset(0, 131071)); double gL = Loudness.WeightedVolume(buff); (buff as SoundBuffer).ApplyGain(1 / gL); longstereoImpulseF.Add(buff); FilterProfile lfgDirectL = new FilterProfile(buff, 0.5); buff = new SoundBuffer(new SampleBuffer(filteredImpulseR).Subset(0, 131071)); double gR = Loudness.WeightedVolume(buff); (buff as SoundBuffer).ApplyGain(1 / gR); longstereoImpulseF.Add(buff); FilterProfile lfgDirectR = new FilterProfile(buff, 0.5); _impulseFiles.Add("Impulse_Response_Filtered.wav: approximation to direct-sound impulse response"); writer = new WaveWriter("Impulse_Response_Filtered.wav"); writer.Input = longstereoImpulseF; writer.Format = WaveFormat.IEEE_FLOAT; writer.BitsPerSample = 32; writer.SampleRate = _sampleRate; writer.Normalization = -1; writer.Raw = false; writer.Run(); writer.Close(); double gg = writer.Gain; writer = null; longstereoImpulseF = null; ChannelSplicer longstereoImpulseD = new ChannelSplicer(); Mixer diffuse = new Mixer(); diffuse.Add(impulseL, 1.0); diffuse.Add(filteredImpulseL, -1.0); buff = new SoundBuffer(new SampleBuffer(diffuse).Subset(0, 131071)); (buff as SoundBuffer).ApplyGain(1 / gL); longstereoImpulseD.Add(buff); FilterProfile lfgDiffuseL = new FilterProfile(buff, 0.5); diffuse = new Mixer(); diffuse.Add(impulseR, 1.0); diffuse.Add(filteredImpulseR, -1.0); buff = new SoundBuffer(new SampleBuffer(diffuse).Subset(0, 131071)); (buff as SoundBuffer).ApplyGain(1 / gR); longstereoImpulseD.Add(buff); FilterProfile lfgDiffuseR = new FilterProfile(buff, 0.5); _impulseFiles.Add("Impulse_Response_Diffuse.wav: approximation to diffuse-field remnant"); writer = new WaveWriter("Impulse_Response_Diffuse.wav"); writer.Input = longstereoImpulseD; writer.Format = WaveFormat.IEEE_FLOAT; writer.BitsPerSample = 32; writer.SampleRate = _sampleRate; writer.Gain = gg; writer.Raw = false; writer.Run(); writer.Close(); writer = null; // Filter the diffuse-field curve against double the diffuse-field curve FilterImpulse fiDiffuse = new FilterImpulse(8192, HRTF.diffuseDiff0() * 2, FilterInterpolation.COSINE, sampleRate); FastConvolver co = new FastConvolver(longstereoImpulseD, fiDiffuse); SoundBuffer buffd = new SoundBuffer(co); _impulseFiles.Add("Impulse_Response_Diffuse_Comp.wav: filtered diffuse-field remnant"); writer = new WaveWriter("Impulse_Response_Diffuse_Comp.wav"); writer.Input = buffd.Subset(4096); writer.Format = WaveFormat.IEEE_FLOAT; writer.BitsPerSample = 32; writer.SampleRate = _sampleRate; writer.Gain = gg; writer.Raw = false; writer.Run(); writer.Close(); writer = null; longstereoImpulseD = null; bool any = false; string jsonFile = "Diff.json"; FileStream fs = new FileStream(jsonFile, FileMode.Create); StreamWriter sw = new StreamWriter(fs); sw.WriteLine("{"); FilterProfile lfgDiffL = lfgDirectL - lfgDiffuseL; if (lfgDiffL != null) { if (any) sw.WriteLine(","); any = true; sw.Write(lfgDiffL.ToJSONString("DiffL", "Diffuse field relative to direct, left channel")); } FilterProfile lfgDiffR = lfgDirectR - lfgDiffuseR; if (lfgDiffR != null) { if (any) sw.WriteLine(","); any = true; sw.Write(lfgDiffR.ToJSONString("DiffR", "Diffuse field relative to direct, right channel")); } sw.WriteLine("}"); sw.Close(); fs.Close(); } buff = null; GC.Collect(); System.Console.Error.WriteLine(); if (!_noDRC) { // Analyze the freq response // and create targets // target_full.txt and target_half.txt stderr.WriteLine("Analyzing response curves."); Prep(impTempL, impTempR, "Impulse_Response_Measured.wav", "NoCorrection"); // Call DRC to create the filters // then splice the DRC left & right output files together stderr.WriteLine("Preparing for DRC."); if (DoDRC(impFileL, impFileR, impDirectL, impDirectR, _peakPosL, _peakPosR, "Impulse_Response_Measured.wav", "Impulse_Response_Filtered.wav")) { stderr.WriteLine("Success!"); } } // Report names of the impulse files created if (_impulseFiles.Count == 0) { System.Console.Error.WriteLine("No impulse response files were created."); } if (_impulseFiles.Count > 0) { System.Console.Error.WriteLine("Impulse response files were created:"); foreach (string f in _impulseFiles) { string s = " " + f; System.Console.Error.WriteLine(s); } } // Report names of the filter files created if (_filterFiles.Count == 0 && !_noDRC) { System.Console.Error.WriteLine("No correction filter files were created."); } if (_filterFiles.Count > 0) { System.Console.Error.WriteLine("Correction filter files were created:"); foreach (string f in _filterFiles) { string s = " " + f; if (_copy) { try { File.Copy(f, Path.Combine(_impulsesFolder, f), true); s += " (copied)"; } catch (Exception e) { s += " (not copied: " + e.Message + ")"; } } System.Console.Error.WriteLine(s); } } if (_peakPosL == _peakPosR) { System.Console.Error.WriteLine(); System.Console.Error.WriteLine("Zero time difference between channels. Are you sure the recordings are correct?"); } } catch (Exception e) { stderr.WriteLine(); stderr.WriteLine(e.Message); stderr.WriteLine(e.StackTrace); } finally { foreach (string tempFile in _tempFiles) { try { File.Delete(tempFile); } catch (Exception) { /* ignore */ } } } } stderr.Flush(); }
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 Splice(string infileL, int peakPosL, string infileR, int peakPosR, string outfile) { //int tmp1; //bool tmp2; WaveReader reader1 = new WaveReader(infileL, WaveFormat.IEEE_FLOAT, 32, 1); // reader1.Skip(peakPosL, out tmp1, out tmp2); SoundBuffer buff1 = new SoundBuffer(reader1); buff1.ReadAll(); // Normalize loudness for each channel double g = Loudness.WeightedVolume(buff1); buff1.ApplyGain(1/g); WaveReader reader2 = new WaveReader(infileR, WaveFormat.IEEE_FLOAT, 32, 1); // reader2.Skip(peakPosR, out tmp1, out tmp2); SoundBuffer buff2 = new SoundBuffer(reader2); buff2.ReadAll(); g = Loudness.WeightedVolume(buff2); buff2.ApplyGain(1/g); ChannelSplicer splicer = new ChannelSplicer(); splicer.Add(buff1); splicer.Add(buff2); // General-purpose: // Find the extremities of the DRC impulse, // window asymmetrically. // // But, since we specifically used linear-phase filters on target and mic, // we know that the impulse is centered. // We want an impulse length (_filterLen) // so window the 2048 samples at each end (which are pretty low to begin with - less than -100dB) ISoundObj output; int nCount = (int)(buff1.Count / 2); if (nCount > _filterLen/2) { BlackmanHarris bhw = new BlackmanHarris(nCount, 2048, (nCount / 2) - 2048); bhw.Input = splicer; SampleBuffer sb = new SampleBuffer(bhw); output = sb.Subset(_filterLen/2, _filterLen); } else { output = splicer; } ISoundObj result = output; if (!_noSkew) { // Apply skew to compensate for time alignment in the impulse responses Skewer skewer = new Skewer(true); skewer.Input = output; skewer.Skew = peakPosL - peakPosR; result = skewer; } WaveWriter writer = new WaveWriter(outfile); writer.Input = result; writer.Dither = DitherType.NONE; writer.Format = WaveFormat.IEEE_FLOAT; writer.BitsPerSample = 32; writer.SampleRate = _sampleRate; writer.NumChannels = splicer.NumChannels; if (Double.IsNaN(_gain)) { writer.Normalization = 0; } else { writer.Gain = MathUtil.gain(_gain); } writer.Run(); writer.Close(); reader1.Close(); reader2.Close(); return result; }
static void Prep(string infileL, string infileR, string stereoImpulseFile, string outFile) { // Input files are complex // 0=mag, 1=phase/pi (so it looks OK in a wave editor!) // FFTs of the room impulse response // Take two half-FFT-of-impulse WAV files // Average them, into an array int n; SoundBuffer buff; WaveWriter wri; // NoiseGenerator noise; FastConvolver conv; /* // Convolve noise with the in-room impulse noise = new NoiseGenerator(NoiseType.WHITE_FLAT, 2, (int)131072, stereoImpulse.SampleRate, 1.0); conv = new FastConvolver(); conv.impulse = stereoImpulse; conv.Input = noise; wri = new WaveWriter("ImpulseResponse_InRoom.wav"); wri.Input = conv; wri.Format = WaveFormat.IEEE_FLOAT; wri.BitsPerSample = 32; wri.Normalization = 0; wri.Run(); wri.Close(); wri = null; conv = null; */ WaveReader rdrL = new WaveReader(infileL); buff = new SoundBuffer(rdrL); n = (int)buff.ReadAll(); uint sampleRate = buff.SampleRate; uint nyquist = sampleRate / 2; double binw = (nyquist / (double)n); WaveReader rdrR = new WaveReader(infileR); IEnumerator<ISample> enumL = buff.Samples; IEnumerator<ISample> enumR = rdrR.Samples; // For easier processing and visualisation // read this in to an ERB-scale (not quite log-scale) array // then we can smooth by convolving with a single half-cosine. // int nn = (int)ERB.f2bin(nyquist, sampleRate) + 1; double[] muff = new double[nn]; int prevbin = 0; int nbin = 0; double v = 0; int j = 0; while (true) { double f = (double)j * binw; // equiv freq, Hz int bin = (int)ERB.f2bin(f, sampleRate); // the bin we drop this sample in if (bin > nn) { // One of the channels has more, but we're overrun so stop now break; } j++; bool more = false; more |= enumL.MoveNext(); more |= enumR.MoveNext(); if (!more) { muff[prevbin] = v / nbin; break; } v += enumL.Current[0]; // magnitude v += enumR.Current[0]; // magnitude nbin++; if (bin > prevbin) { muff[prevbin] = v / nbin; v = 0; nbin = 0; prevbin = bin; } } double[] smoo = ERB.smooth(muff, 38); // Pull out the freq response at ERB centers FilterProfile lfg = ERB.profile(smoo, sampleRate); // Write this response as a 'target' file /* FileStream fs = new FileStream("target_full.txt", FileMode.Create); StreamWriter sw = new StreamWriter(fs, Encoding.ASCII); foreach (FreqGain fg in lfg) { sw.WriteLine("{0} {1:f4}", Math.Round(fg.Freq), fg.Gain); } sw.Close(); */ /* fs = new FileStream("target_half.txt", FileMode.Create); sw = new StreamWriter(fs, Encoding.ASCII); foreach (FreqGain fg in lfg) { sw.WriteLine("{0} {1:f4}", Math.Round(fg.Freq), fg.Gain/2); } sw.Close(); */ // Create a filter to invert this response FilterProfile ifg = new FilterProfile(); foreach (FreqGain fg in lfg) { ifg.Add(new FreqGain(fg.Freq, -fg.Gain)); } ISoundObj filterImpulse = new FilterImpulse(0, ifg, FilterInterpolation.COSINE, sampleRate); filterImpulse.SampleRate = sampleRate; // Write the filter impulse to disk string sNoCorr = outFile + ".wav"; wri = new WaveWriter(sNoCorr); wri.Input = filterImpulse; // invertor; wri.Format = WaveFormat.IEEE_FLOAT; wri.BitsPerSample = 32; wri.SampleRate = _sampleRate; wri.Normalization = -1; wri.Run(); wri.Close(); _filterFiles.Add(sNoCorr); /* // Convolve noise with the NoCorrection filter noise = new NoiseGenerator(NoiseType.WHITE_FLAT, 2, (int)131072, stereoImpulse.SampleRate, 1.0); conv = new FastConvolver(); conv.impulse = invertor; conv.Input = noise; wri = new WaveWriter("NoCorrection_Test.wav"); wri.Input = conv; wri.Format = WaveFormat.IEEE_FLOAT; wri.BitsPerSample = 32; wri.SampleRate = _sampleRate; wri.Normalization = 0; wri.Run(); wri.Close(); wri = null; conv = null; */ // Convolve this with the in-room impulse response WaveReader rea = new WaveReader(outFile + ".wav"); conv = new FastConvolver(); conv.impulse = rea; conv.Input = new WaveReader(stereoImpulseFile); wri = new WaveWriter(outFile + "_TestConvolution.wav"); wri.Input = conv; wri.Format = WaveFormat.PCM; wri.Dither = DitherType.TRIANGULAR; wri.BitsPerSample = 16; wri.SampleRate = _sampleRate; wri.Normalization = -1; wri.Run(); wri.Close(); rea.Close(); wri = null; conv = null; }
static SoundObj GetMainImpulse(out string actualPath) { DateTime dtStart = DateTime.Now; if (_impulsePath == "") _impulsePath = null; if (_impulsePath == "-") _impulsePath = null; if (_matrixFilter == "") _matrixFilter = null; if (_matrixFilter == "-") _matrixFilter = null; if (_bformatFilter == "") _bformatFilter = null; if (_bformatFilter == "-") _bformatFilter = null; Trace.WriteLine("Impulse {0}, matrix {1}", CleanPath(_dataFolder, _impulsePath), CleanPath(_dataFolder, _matrixFilter)); // note: we window the room correction impulse if it's too long WaveReader impulseReader = null; SoundObj impulseObj = null; actualPath = null; if (!String.IsNullOrEmpty(_impulsePath)) { impulseReader = GetAppropriateImpulseReader(_impulsePath, out actualPath); } if (impulseReader != null) { if (impulseReader.Iterations > _maxImpulseLength) { // This impulse is too long. // Trim it to length. int hwid = _maxImpulseLength / 2; int qwid = _maxImpulseLength / 4; SoundBuffer buff = new SoundBuffer(impulseReader); buff.ReadAll(); int center = buff.MaxPos(); BlackmanHarris wind; int startpos; if (center < hwid) { wind = new BlackmanHarris(center, qwid, qwid); startpos = 0; } else { wind = new BlackmanHarris(hwid, qwid, qwid); startpos = center - hwid; } // int startpos = center < hwid ? 0 : (center - hwid); wind.Input = buff.Subset(startpos, _maxImpulseLength); impulseObj = wind; } else { impulseObj = impulseReader; } } if (_debug) { TimeSpan ts = DateTime.Now.Subtract(dtStart); Trace.WriteLine("GetMainImpulse " + ts.TotalMilliseconds); } return impulseObj; }
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); }
private ISoundObj _buff() { if (double.IsNaN(_normalization)) { return _input; } // We've been asked to normalize SoundBuffer b = new SoundBuffer(_input); b.ReadAll(); _gain = b.Normalize(_normalization, false); return b; }