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 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 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 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 ISoundObj GetSignalGenerator(double dBfs, out string desc) { double gain = MathUtil.gain(dBfs); ISoundObj signalGenerator = null; Sequencer seq; string description = "Unknown"; switch (_siggen) { case "IDENT": // Left-right identification: embedded resource Assembly ass = Assembly.GetExecutingAssembly(); foreach (string s in ass.GetManifestResourceNames()) { if (s.Contains("LeftRight")) { Stream res = ass.GetManifestResourceStream(s); WaveReader rdr = new WaveReader(res); // The stream is stereo, but we want to alternate seq = new Sequencer(); for (int j = 0; j < 10; j++) { seq.Add(rdr, new List<double>(new double[] { gain, 0 })); seq.Add(new NoiseGenerator(NoiseType.SILENCE, 2, 1.0, _inputSampleRate, 0.0, false)); seq.Add(rdr, new List<double>(new double[] { 0, gain })); seq.Add(new NoiseGenerator(NoiseType.SILENCE, 2, 1.0, _inputSampleRate, 0.0, false)); } signalGenerator = seq; break; } } /* // Left-right identification signal: morse code MorseCode envL = new MorseCode(" " + _sigparamA, 10, true); ISoundObj sigL = new SweepGenerator(1, envL.LengthSeconds * 5, 220, 7040, _inputSampleRate, 0, false, gain, true); envL.Input = sigL; MorseCode envR = new MorseCode(" " + _sigparamB, 10, true); ISoundObj sigR = new SweepGenerator(1, envR.LengthSeconds * 5, 7040, 220, _inputSampleRate, 0, false, gain, true); envR.Input = sigR; signalGenerator = new ChannelSplicer(); (signalGenerator as ChannelSplicer).Add(envL); (signalGenerator as ChannelSplicer).Add(envR); */ description = String.Format("Left/Right channel identification"); break; case "SWEEP": seq = new Sequencer(); if (_sigparam1 == 0) { _sigparam1 = 45; } int lengthSamples = (int)(_sigparam1 * _inputSampleRate); if (lengthSamples < 8388608) { // High-accuracy logarithmic sweep starting at 10Hz int fade = (int)(_inputSampleRate / 20); FFTSweepGenerator sg = new FFTSweepGenerator(2, lengthSamples, 10, _inputSampleRate / 2, _inputSampleRate, gain, false); seq.Add(sg); description = String.Format("Logarithmic sine sweep 10Hz to {0}Hz in {1} seconds", _inputSampleRate / 2, _sigparam1); } else { // Simple logarithmic sweep starting at 10Hz, windowed (uses much less memory!) int fade = (int)(_inputSampleRate / 20); BlackmanHarris bhwf = new BlackmanHarris(lengthSamples / 2, fade, (int)((lengthSamples / 2) - fade)); SweepGenerator sg = new SweepGenerator(2, lengthSamples, 10, _inputSampleRate / 2, _inputSampleRate, gain, false); bhwf.Input = sg; seq.Add(bhwf); description = String.Format("Log sine sweep 10Hz to {0}Hz in {1} seconds", _inputSampleRate / 2, _sigparam1); } // Follow by 3 seconds of silence seq.Add(new NoiseGenerator(NoiseType.SILENCE, 2, 3.0, _inputSampleRate, 0.0, false)); signalGenerator = seq; break; case "SINE": signalGenerator = new SineGenerator(2, _inputSampleRate, _sigparam1, gain); description = String.Format("{0}Hz sine", _sigparam1); break; case "QUAD": signalGenerator = new SineQuadGenerator(2, _inputSampleRate, _sigparam1, gain); description = String.Format("{0}Hz quadrature", _sigparam1); break; case "SQUARE": signalGenerator = new SquareGenerator(2, _inputSampleRate, _sigparam1, gain); description = String.Format("{0}Hz non-bandlimited square", _sigparam1); break; case "BLSQUARE": signalGenerator = new BandLimitedSquareGenerator(2, _inputSampleRate, _sigparam1, gain); description = String.Format("{0}Hz bandlimited square", _sigparam1); break; case "TRIANGLE": signalGenerator = new TriangleGenerator(2, _inputSampleRate, _sigparam1, gain); description = String.Format("{0}Hz non-bandlimited triangle", _sigparam1); break; case "BLTRIANGLE": signalGenerator = new BandLimitedTriangleGenerator(2, _inputSampleRate, _sigparam1, gain); description = String.Format("{0}Hz bandlimited triangle", _sigparam1); break; case "SAWTOOTH": signalGenerator = new SawtoothGenerator(2, _inputSampleRate, _sigparam1, gain); description = String.Format("{0}Hz non-bandlimited sawtooth", _sigparam1); break; case "BLSAWTOOTH": signalGenerator = new BandLimitedSawtoothGenerator(2, _inputSampleRate, _sigparam1, gain); description = String.Format("{0}Hz bandlimited sawtooth", _sigparam1); break; case "WHITE": signalGenerator = new NoiseGenerator(NoiseType.WHITE, 2, int.MaxValue, _inputSampleRate, gain, true); description = String.Format("White noise"); break; case "PINK": bool mono = (_sigparam1 != 0 ? true : false); signalGenerator = new NoiseGenerator(NoiseType.PINK, 2, int.MaxValue, _inputSampleRate, gain, mono); description = String.Format("Pink noise {0}", mono ? "(mono)" : "(stereo)" ); break; case "INTERMODULATION": double n = 1; description = String.Format("Intermodulation test {0}Hz", _sigparam1); if (_sigparam2 != 0) { n++; description = description + " + " + _sigparam2 + "Hz"; } if (_sigparam3 != 0) { n++; description = description + " + " + _sigparam3 + "Hz"; } signalGenerator = new Mixer(); (signalGenerator as Mixer).Add(new SineGenerator(2, _inputSampleRate, _sigparam1, gain), 1/n); if (_sigparam2 != 0) (signalGenerator as Mixer).Add(new SineGenerator(2, _inputSampleRate, _sigparam2, gain), 1 / n); if (_sigparam3 != 0) (signalGenerator as Mixer).Add(new SineGenerator(2, _inputSampleRate, _sigparam3, gain), 1 / n); break; case "SHAPEDBURST": description = String.Format("{0}Hz windowed (Blackman) over {1} cycles", _sigparam1, _sigparam2); throw new NotImplementedException(); //break; default: _siggen = null; break; } if (IsSigGenEQ()) { if (IsSigGenEQBoth()) { description = description + ", with EQ processing"; } else if (IsSigGenEQL()) { description = description + ", with EQ processing in left channel"; } else if (IsSigGenEQR()) { description = description + ", with EQ processing in right channel"; } else { description = description + ", with EQ processing"; } } desc = description; return signalGenerator; }
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); }
private static ISoundObj GetFlatnessFilter(string impulsePath, ISoundObj impulseObj, double nFlatness) { ISoundObj filter = null; if (impulseObj != null && impulsePath != null && nFlatness!=100) { uint sr = impulseObj.SampleRate; if (_debug) { Trace.WriteLine(FlatnessFilterName(impulsePath, sr, nFlatness)); } // Do we already have a cached flatness-filter? // (Building these things is expensive...) string sFile = FlatnessFilterPath(impulsePath, sr, nFlatness); if (File.Exists(sFile)) { try { filter = new WaveReader(sFile); } catch (Exception e) { if (_debug) { Trace.WriteLine("GetFlatnessFilter: " + e.Message); } } } if (filter == null) { // Generate a new flatness filter then return GenerateFlatnessFilter(impulsePath, impulseObj, nFlatness); } } return filter; }
static SoundObj GetEQImpulse(ISoundObj mainImpulse, uint sampleRate, out string filterName) { DateTime dtStart = DateTime.Now; SoundObj filterImpulse = null; // Construct a string describing the filter string filterDescription = "EQ" + _eqBands + "_" + _inputSampleRate + "_" + _eqLoudness + "_" + FlatnessFilterPath(_impulsePath, sampleRate, _eqFlatness); bool nothingToDo = (_eqLoudness==0); nothingToDo &= (_impulsePath == null) || (_eqFlatness == 100); List<string> fgd = new List<string>(); foreach (FreqGain fg in _eqValues) { fgd.Add(fg.Freq + "@" + fg.Gain); nothingToDo &= (fg.Gain == 0); } filterDescription = filterDescription + String.Join("_", fgd.ToArray()); filterDescription = filterDescription + "_IM_" + _impulsePath; // Cached filters are named by hash of this string filterName = "EQ" + filterDescription.GetHashCode().ToString("x10").ToUpperInvariant(); if (nothingToDo) { Trace.WriteLine("EQ flat"); WriteJSON(_eqValues, null, null); return null; } else { Trace.WriteLine(filterName); } string filterFile = Path.Combine(_tempFolder, filterName + ".filter"); // Does the cached filter exist? if (File.Exists(filterFile)) { try { // Just read the cached EQ filter from disk filterImpulse = new WaveReader(filterFile); } catch (Exception e) { if (_debug) { Trace.WriteLine("GetEQImpulse1: " + e.Message); } } } if(filterImpulse==null) { // Construct a filter impulse from the list of EQ values SoundObj eqFilter = new FilterImpulse(0, _eqValues, FilterInterpolation.COSINE, _inputSampleRate); filterImpulse = eqFilter; ISoundObj qtFilter = GetQuietnessFilter(_inputSampleRate, _eqLoudness); if (qtFilter != null) { // Convolve the two, to create a EQ-and-loudness filter FastConvolver tmpConvolver = new FastConvolver(); tmpConvolver.partitions = 0; tmpConvolver.impulse = qtFilter; tmpConvolver.Input = eqFilter; filterImpulse = tmpConvolver; } ISoundObj ftFilter = GetFlatnessFilter(_impulsePath, mainImpulse, _eqFlatness); if (ftFilter != null) { // Convolve the two, to create a EQ-and-loudness filter FastConvolver tmpConvolver2 = new FastConvolver(); tmpConvolver2.partitions = 0; tmpConvolver2.impulse = filterImpulse; tmpConvolver2.Input = ftFilter; filterImpulse = tmpConvolver2; } // Blackman window to make the filter smaller? try { // Write the filter impulse to disk WaveWriter wri = new WaveWriter(filterFile); wri.Input = filterImpulse; wri.Format = WaveFormat.IEEE_FLOAT; wri.BitsPerSample = 64; wri.Run(); wri.Close(); if (_debug) { // DEBUG: Write the filter impulse as wav16 wri = new WaveWriter(filterFile + ".wav"); wri.Input = filterImpulse; wri.Format = WaveFormat.PCM; wri.BitsPerSample = 16; wri.Normalization = -1.0; wri.Dither = DitherType.NONE;//.TRIANGULAR; wri.Run(); wri.Close(); } // Write a JSON description of the filter WriteJSON(_eqValues, filterImpulse, filterName); } catch (Exception e) { if (_debug) { Trace.WriteLine("GetEQImpulse2: " + e.Message); } } } filterImpulse.Reset(); if (_debug) { TimeSpan ts = DateTime.Now.Subtract(dtStart); Trace.WriteLine("GetEQImpulse " + ts.TotalMilliseconds); } // Copy the filter's JSON description (if available) into "current.json" CopyJSON(filterName); return filterImpulse; }
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; }
static void LoadImpulse() { DateTime dtStart = DateTime.Now; string theImpulsePath = null; string theEQImpulseName = null; ISoundObj main = GetMainImpulse(out theImpulsePath); uint sr = (main == null ? _inputSampleRate : main.SampleRate); if (sr == 0) { if (_debug) { Trace.WriteLine("oops: no sample rate!"); } sr = 44100; } ISoundObj eq = GetEQImpulse(main, sr, out theEQImpulseName); ISoundObj combinedFilter = null; if (main == null && eq != null) { combinedFilter = eq; } else if (main != null && eq == null) { combinedFilter = main; } else if (main != null && eq != null) { // Check whether we have (and can load) a cached version of the combined filter string tempString = theEQImpulseName + "_" + theImpulsePath; string filterName = "CC" + tempString.GetHashCode().ToString("x10").ToUpperInvariant(); string filterFile = Path.Combine(_tempFolder, filterName + ".filter"); if (_debug) { Trace.WriteLine(filterName); } if (File.Exists(filterFile)) { try { // Just read the cached EQ filter from disk combinedFilter = new WaveReader(filterFile); } catch (Exception e) { if (_debug) { Trace.WriteLine("LoadImpulse1: " + e.Message); } } } if (combinedFilter == null) { // Convolve the room-correction impulse with the EQ impulse to make just one. // (this is quite slow) FastConvolver temp = new FastConvolver(); temp.partitions = 0; temp.impulse = eq; temp.Input = main; combinedFilter = temp; try { // Write the combined impulse temp.Reset(); WaveWriter tempWriter = new WaveWriter(filterFile); tempWriter.Format = WaveFormat.IEEE_FLOAT; tempWriter.BitsPerSample = 64; tempWriter.Input = temp; tempWriter.Run(); tempWriter.Close(); if (_debug) { // DEBUG: Write the combined impulse as WAV16 temp.Reset(); tempWriter = new WaveWriter(filterFile + ".wav"); tempWriter.Format = WaveFormat.PCM; tempWriter.BitsPerSample = 16; tempWriter.Gain = 0.1; tempWriter.Dither = DitherType.NONE;//.TRIANGULAR; tempWriter.Input = temp; tempWriter.Run(); tempWriter.Close(); } } catch (Exception e) { if (_debug) { Trace.WriteLine("LoadImpulse2: " + e.Message); } } } } _MainConvolver.impulse = combinedFilter; if (combinedFilter != null) { // Calculate loudness-adjusted volume of each channel of the impulse _impulseVolumes.Clear(); for (ushort j = 0; j < combinedFilter.NumChannels; j++) { double v = Loudness.WeightedVolume(combinedFilter.Channel(j)); _impulseVolumes.Add(v); if (_debug) { Trace.WriteLine("WV{0}: {1}", j, v); } } } combinedFilter = null; if (_debug) { TimeSpan ts = DateTime.Now.Subtract(dtStart); Trace.WriteLine("Loadmpulse " + ts.TotalMilliseconds); } }
static void InguzDSP(bool doRun) { DateTime dtStartRun = DateTime.Now; string sigdesc = null; // We need a few convolvers, of course if (_slow) { _MatrixConvolver = new SlowConvolver(); _MainConvolver = new SlowConvolver(); _EQConvolver = new SlowConvolver(); } else { _MatrixConvolver = new FastConvolver(); _MainConvolver = new FastConvolver(); _EQConvolver = new FastConvolver(); } // Shuffler _widthShuffler = new Shuffler(); // Two skewers _depthSkewer = new Skewer(true); _skewSkewer = new Skewer(true); // Writer if (_outPath == null) { _writer = new WaveWriter(); // stdout } else { _writer = new WaveWriter(_outPath); } _writer.NumChannels = _outChannels; if (_debug) { TimeSpan ts = DateTime.Now.Subtract(dtStartRun); Trace.WriteLine("Setup " + ts.TotalMilliseconds); } /* DateTime exp = DSPUtil.DSPUtil.EXPIRY; if (exp != null) { if (DateTime.Now.CompareTo(exp) >= 0) { Trace.WriteLine("**** THIS EVALUATION VERSION EXPIRED {0}", DSPUtil.DSPUtil.EXPIRY); Trace.WriteLine("**** SEE http://www.inguzaudio.com/DSP/ FOR DETAILS"); _MatrixConvolver.Enabled = false; _MainConvolver.Enabled = false; _EQConvolver.Enabled = false; Show("", "InguzDSP has expired.", 2); } else { Trace.WriteLine("This evaluation version will expire {0}", DSPUtil.DSPUtil.EXPIRY); } } */ // Read the configuration file doRun = LoadConfig2(); // Do any cleanup required before we start CleanUp(); // The main convolver should persist and re-use its leftovers between invocations // under this user's (=squeezebox's) ID _MainConvolver.partitions = _partitions; if (_tail && !IsSigGenNonEQ()) { _MainConvolver.PersistPath = _tempFolder; _MainConvolver.PersistTail = _userID; } // Construct a second convolver for the "tone" (EQ) control. _EQConvolver.partitions = _partitions; if (_tail && !IsSigGenNonEQ()) { _EQConvolver.PersistPath = _tempFolder; _EQConvolver.PersistTail = _userID + ".eq"; } // Make a reader // _inPath is the data stream WaveReader inputReader = null; bool ok = false; try { if (_inStream != null) { inputReader = new WaveReader(_inStream); } else if (_isRawIn) { inputReader = new WaveReader(_inPath, _rawtype, _rawbits, _rawchan, _startTime); } else { inputReader = new WaveReader(_inPath, _startTime); } inputReader.BigEndian = _bigEndian; ok = true; } catch (Exception e) { Trace.WriteLine("Unable to read: " + e.Message); // Just stop (no need to report the stack) } if (ok) { if (inputReader.IsSPDIF) { // The wave file is just a SPDIF (IEC61937) stream, we shouldn't touch it _inputSPDIF = true; _isBypass = true; } if (_isBypass) { // The settings file says bypass, we shouldn't touch it _gain = 0; _dither = DitherType.NONE; } uint sr = _inputSampleRate; // Yes, the commandline overrides the source-file... if (sr == 0) { sr = inputReader.SampleRate; } if (sr == 0) { sr = 44100; } _inputSampleRate = sr; if (WaveFormatEx.AMBISONIC_B_FORMAT_IEEE_FLOAT.Equals(inputReader.FormatEx) || WaveFormatEx.AMBISONIC_B_FORMAT_PCM.Equals(inputReader.FormatEx)) { _isBFormat = true; } } ISoundObj source = inputReader; if (IsSigGen()) { // Signal-generator source instead of music. _isBFormat = false; source = GetSignalGenerator(-12, out sigdesc); Show("Test signal", sigdesc, 20); } if (IsSigGenNonEQ() || _isBypass) { // Signal-generator mode. Overrides everything else! _writer.Input = source; } else { if (ok) { // Load the room correction impulse to the convolver // (NB: don't do this until we've created the reader, otherwise we can't be user of the samplerate yet...) LoadImpulse(); GC.Collect(); } if (ok && _isBFormat) { source = DecodeBFormat(source); } if (ok) { ISoundObj nextSrc; // Perform width (matrix) processing on the signal // - Shuffle the channels // - Convolve if there's a filter // - Apply gain to boost or cut the 'side' channel // - Shuffle back _widthShuffler.Input = source; nextSrc = _widthShuffler; // Use a convolver for the matrix filter if (_matrixFilter != null) { LoadMatrixFilter(); _MatrixConvolver.Input = TwoChannel(nextSrc); nextSrc = _MatrixConvolver as ISoundObj; } // if (_depth != 0) // { // // Time-alignment between the LR and MS // _depthSkewer.Input = nextSrc; // nextSrc = _depthSkewer; // } // Shuffle back again Shuffler shMSLR = new Shuffler(); shMSLR.Input = nextSrc; nextSrc = shMSLR; // Do the room-correction convolution _MainConvolver.Input = TwoChannel(shMSLR); nextSrc = _MainConvolver; if (_skew != 0) { // time-alignment between left and right _skewSkewer.Input = nextSrc; nextSrc = _skewSkewer; } // Splice EQ and non-EQ channels if (IsSigGenEQ()) { ChannelSplicer splice = new ChannelSplicer(); if (IsSigGenEQL()) { splice.Add(new SingleChannel(nextSrc, 0)); } else { splice.Add(new SingleChannel(source, 0)); } if (IsSigGenEQR()) { splice.Add(new SingleChannel(nextSrc, 1)); } else { splice.Add(new SingleChannel(source, 1)); } nextSrc = splice; } // Process externally with aften or equivalent? if (_aftenNeeded && !_isBypass) { nextSrc = AftenProcess(nextSrc); _outFormat = WaveFormat.PCM; _outBits = 16; _dither = DitherType.NONE; } // Finally pipe this to the writer _writer.Input = nextSrc; } } if (ok) { //dt = System.DateTime.Now; // time to here is approx 300ms // Dither and output raw-format override anything earlier in the chain _writer.Dither = _isBypass ? DitherType.NONE : _dither; _writer.Raw = _isRawOut; _writer.Format = (_outFormat == WaveFormat.ANY) ? inputReader.Format : _outFormat; _writer.BitsPerSample = (_outBits == 0 || _isBypass) ? inputReader.BitsPerSample : _outBits; _writer.SampleRate = (_outRate == 0 || _isBypass) ? _inputSampleRate : _outRate; SetWriterGain(); if (IsSigGen()) { Trace.WriteLine("Test signal: {0}, -12dBfs, {1}/{2} {3} {4}", sigdesc, _writer.BitsPerSample, _writer.SampleRate, _writer.Format, _writer.Dither); } string amb1 = ""; string amb2 = ""; if(_isBFormat) { amb1 = "B-Format "; amb2 = _ambiType + " "; } string big = inputReader.BigEndian ? "(Big-Endian) " : ""; if (_inputSPDIF) { Trace.WriteLine("Stream is SPDIF-wrapped; passing through"); } else if (_isBypass) { Trace.WriteLine("Processing is disabled; passing through"); } Trace.WriteLine("{0}/{1} {2}{3} {4}=> {5}/{6} {7}{8} {9}, gain {10} dB", inputReader.BitsPerSample, _inputSampleRate, amb1, inputReader.Format, big, _writer.BitsPerSample, _writer.SampleRate, amb2, _writer.Format, _writer.Dither, _gain); TimeSpan elapsedInit = System.DateTime.Now.Subtract(dtStartRun); int n = _writer.Run(); TimeSpan elapsedTotal = System.DateTime.Now.Subtract(dtStartRun); double realtime = n / _writer.SampleRate; double runtime = elapsedTotal.TotalMilliseconds / 1000; Trace.WriteLine("{0} samples, {1} ms ({2} init), {3} * realtime, peak {4} dBfs", n, elapsedTotal.TotalMilliseconds, elapsedInit.TotalMilliseconds, Math.Round(realtime / runtime, 4), Math.Round(_writer.dbfsPeak, 4)); StopConfigListening(); _writer.Close(); } }