static bool ExecDRC(string drcfile, string target, string outfile, string infileL, string infileR, int peakPosL, int peakPosR, string stereoImpulseFile) { GC.Collect(); bool ok = false; string args; FastConvolver conv; WaveWriter wri; if (!File.Exists(drcfile)) { stderr.WriteLine(); stderr.WriteLine("{0} not found.", drcfile); return ok; } string tmpL; string tmpR; tmpL = Path.GetFileNameWithoutExtension(infileL) + ".tmp"; tmpR = Path.GetFileNameWithoutExtension(infileR) + ".tmp"; _tempFiles.Add(tmpL); _tempFiles.Add(tmpR); stderr.WriteLine("Exec DRC for {0}, left channel", drcfile); stderr.WriteLine(); ok = RunDRC(drcfile, infileL, target, tmpL, peakPosL, out args); if (ok) { stderr.WriteLine(); stderr.WriteLine("Exec DRC for {0}, right channel", drcfile); stderr.WriteLine(); ok = RunDRC(drcfile, infileR, target, tmpR, peakPosR, out args); } if (ok) { stderr.WriteLine(); if (_noSkew) { stderr.WriteLine("Creating stereo filter {0}", outfile + ".wav" ); } else { stderr.WriteLine("Creating stereo filter {0} (skew {1} samples)", outfile + ".wav", peakPosR - peakPosL); } ISoundObj stereoFilter = Splice(tmpL, peakPosL, tmpR, peakPosR, outfile + ".wav"); stderr.WriteLine(); // Convolve noise with the stereo filter /* NoiseGenerator noise = new NoiseGenerator(NoiseType.WHITE_FLAT, 2, (int)131072, stereoFilter.SampleRate, 1.0); conv = new FastConvolver(); conv.impulse = stereoFilter; conv.Input = noise; wri = new WaveWriter(drcfile + "_Test.wav"); wri.Input = conv; wri.Format = WaveFormat.IEEE_FLOAT; wri.BitsPerSample = 32; wri.SampleRate = _sampleRate; wri.Normalization = -1; wri.Run(); wri.Close(); wri = null; conv = null; noise = null; * */ // Convolve filter with the in-room impulse response WaveReader rea = new WaveReader(stereoImpulseFile); conv = new FastConvolver(); conv.impulse = rea; conv.Input = stereoFilter; if (_pcm) { _impulseFiles.Add("LCorrected_" + outfile + ".pcm: corrected test convolution, raw data (32-bit float), left channel"); _impulseFiles.Add("RCorrected_" + outfile + ".pcm: corrected test convolution, raw data (32-bit float), right channel"); WriteImpulsePCM(conv, "LCorrected_" + outfile + ".pcm", "RCorrected_" + outfile + ".pcm"); } 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; rea = null; conv = null; GC.Collect(); } return ok; }
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; }
private void init() { _running = true; ComputeImpulseFFT(); nChannels = NumChannels; // Size for work area N = _impulseLength; nImpulseChannels = _impulse.NumChannels; Nh = N << 1; K = (_partitions <= 0) ? N : (N / _partitions); L = MathUtil.NextPowerOfTwo(K << 1); P = (int)Math.Ceiling((double)N / (double)K); // Trace.WriteLine("{0} partitions of size {1}, chunk size {2}", P, L, K); // Allocate buffers for the source and output src = new Complex[nChannels][]; output = new Complex[nChannels][]; for (ushort c = 0; c < nChannels; c++) { src[c] = new Complex[Nh]; output[c] = new Complex[Nh]; } // For partitioned convolution, allocate arrays for the fft accumulators if (_partitions > 0) { accum = new Complex[nChannels][][]; for (ushort c = 0; c < nChannels; c++) { accum[c] = new Complex[P][]; for (p = 0; p < P; p++) { accum[c][p] = new Complex[L]; } } } if (IsPersistTail) { // If there's any *unprocessed* leftover from a previous convolution, // load it into prevTailSamples before we begin if (System.IO.File.Exists(_persistFile)) { WaveReader tailReader = null; try { tailReader = new WaveReader(_persistFile); prevTailSamples = new List <ISample>(tailReader.Iterations); // Trace.WriteLine("Read {0} tail samples", tailReader.Iterations); if (tailReader.NumChannels == NumChannels) { foreach (ISample s in tailReader) { prevTailSamples.Add(s); } } prevTailEnum = prevTailSamples.GetEnumerator(); } catch (Exception e) { Trace.WriteLine("Could not read tail {0}: {1}", _persistFile.Replace(_persistPath, ""), e.Message); } // finally... // ThreadPool.QueueUserWorkItem(delegate(object o) // { try { if (tailReader != null) { tailReader.Close(); tailReader = null; } System.IO.File.Delete(_persistFile); } catch (Exception) { // ignore, we're done } // }); } } // Trace.WriteLine("{0} partitions of size {1}, chunk size {2}", P, L, K); }
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 WaveReader TryGetAppropriateImpulseReader(string filePath, bool allowResample, out string actualPath) { WaveReader rdr = null; bool isExtensibleFormat = false; WaveFormat format = WaveFormat.ANY; bool needResample = false; string resamplePath = GetResampledImpulsePath(filePath); actualPath = null; // Check the impulse file. if (File.Exists(filePath)) { try { rdr = new WaveReader(filePath); actualPath = filePath; if (rdr.SampleRate != 0 && rdr.SampleRate != _inputSampleRate) { Trace.WriteLine("Can't use {0}: its sample rate is {1} not {2}", CleanPath(_dataFolder, filePath), rdr.SampleRate, _inputSampleRate); isExtensibleFormat = (rdr.FormatEx != null); format = rdr.Format; rdr.Close(); rdr = null; actualPath = null; needResample = true; } } catch (Exception e) { Trace.WriteLine("Can't use {0}: {1}", CleanPath(_dataFolder, filePath), e.Message); } } else { Trace.WriteLine("Can't use {0}: not found", CleanPath(_dataFolder, filePath)); } if (rdr == null) { // No luck there. Check for an already-resampled version. if (File.Exists(resamplePath)) { try { rdr = new WaveReader(resamplePath); if (rdr.SampleRate != 0 && rdr.SampleRate != _inputSampleRate) { // Oh dear! This shouldn't happen; after all, the resampled file's name is designed to match sample rates Trace.WriteLine("Can't use {0}: its sample rate is {1} not {2}", CleanPath(_dataFolder, resamplePath), rdr.SampleRate, _inputSampleRate); rdr.Close(); rdr = null; } else { actualPath = resamplePath; Trace.WriteLine("Using resampled version {0} instead", CleanPath(_dataFolder, resamplePath)); needResample = false; } } catch (Exception e) { Trace.WriteLine("Can't use {0}: {1}", CleanPath(_dataFolder, resamplePath), e.Message); } } else if(allowResample) { Trace.WriteLine("Can't use {0}: not found", CleanPath(_dataFolder, resamplePath)); } } if (needResample && allowResample) { // If 'sox' is available, // use it to resample the filter we're trying to use // sox <input.wav> -r <new_sample_rate> <output.wav> polyphase // // The sox application might be either // - in the same folder as this application (quite likely...), or // - somewhere else on the path // and it might be called "sox" or "sox.exe", depending on the operating system (of course). // if (true || isExtensibleFormat) { // sox can't handle the WaveFormatExtensible file type, so we need to save a clean copy of the file first // ALSO: sox goes very wrong if the original is near 0dBfs // so we normalize ALWAYS before resample. rdr = new WaveReader(filePath); string newFile = "RE" + filePath.GetHashCode().ToString("x10").ToUpperInvariant() + ".wav"; string newPath = Path.Combine(_tempFolder, newFile); WaveWriter tempWriter = new WaveWriter(newPath); tempWriter.Input = rdr; tempWriter.Format = rdr.Format; tempWriter.BitsPerSample = rdr.BitsPerSample; tempWriter.Normalization = -6; tempWriter.Run(); tempWriter.Close(); rdr.Close(); rdr = null; filePath = newPath; } // _soxExe = "sox"; string exeName = _soxExe; if(File.Exists(Path.Combine(_pluginFolder, _soxExe + ".exe"))) { exeName = "\"" + Path.Combine(_pluginFolder, _soxExe + ".exe") + "\""; } else if (File.Exists(Path.Combine(_pluginFolder, _soxExe))) { exeName = Path.Combine(_pluginFolder, _soxExe); } // _soxFmt = "\"{0}\" -r {1} \"{2}\" polyphase"; string soxArgs = String.Format(_soxFmt, filePath, _inputSampleRate, resamplePath); Trace.WriteLine("Trying {0} {1}", exeName, soxArgs); System.Diagnostics.Process soxProcess = new System.Diagnostics.Process(); System.Diagnostics.ProcessStartInfo soxInfo = new System.Diagnostics.ProcessStartInfo(); soxInfo.Arguments = soxArgs; soxInfo.FileName = exeName; soxInfo.UseShellExecute = false; soxInfo.RedirectStandardError = true; soxProcess.StartInfo = soxInfo; try { soxProcess.Start(); // Wait for the sox process to finish! string err = soxProcess.StandardError.ReadToEnd(); soxProcess.WaitForExit(500); if (soxProcess.HasExited) { int n = soxProcess.ExitCode; if (n != 0) { Trace.WriteLine("No, that didn't seem to work: {0}", err); } else { Trace.WriteLine("Yes, that seemed to work."); rdr = TryGetAppropriateImpulseReader(resamplePath, false, out actualPath); } } } catch (Exception e) { Trace.WriteLine("That didn't seem to work: {0}", e.Message); } } if (rdr == null) { Trace.WriteLine("No suitable impulse found ({0}).", filePath); } return rdr; }