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