예제 #1
0
 static FilterProfile bandpass(uint centerFreq, uint sr)
 {
     FilterProfile lfg = new FilterProfile();
     lfg.Add(new FreqGain(centerFreq * 0.5, -190));
     lfg.Add(new FreqGain(centerFreq * 1.00, 0));
     lfg.Add(new FreqGain(Math.Min(centerFreq * 2,sr), -190));
     return lfg;
 }
예제 #2
0
 public static FilterProfile DifferentialSPL(double phon0, double phon1, double scale)
 {
     FilterProfile spl = new FilterProfile();
     FilterProfile spl0 = Loudness.SPL(phon0);
     FilterProfile spl1 = Loudness.SPL(phon1);
     for (int j = 0; j < spl1.Count; j++)
     {
         FreqGain fg = spl1[j];
         fg.Gain = scale * ( spl0[j].Gain - fg.Gain );
         spl.Add(fg);
     }
     return spl;
 }
예제 #3
0
        /// <summary>
        /// Create a list of dB SPL equal-loudness values for a given 'phon' loudness
        /// (from zero, threshold, to 90)
        /// </summary>
        /// <param name="phon"></param>
        /// <returns>list of {frequency Hz, dB SPL}</returns>
        public static FilterProfile SPL(double phon)
        {
            FilterProfile lfg = new FilterProfile();
            if ((phon < 0) | (phon > 120))
            {
                throw new ArgumentException("Phon value out of bounds!");
            }
            // Setup user-defined values for equation
            double Ln = phon;

            for (int j = 0; j < f.Length; j++)
            {
                // Deriving sound pressure level from loudness level (iso226 sect 4.1)
                double Af = 4.47E-3 * Math.Pow(10, (0.025 * Ln) - 1.15) + Math.Pow(0.4 * Math.Pow(10, (((Tf[j] + Lu[j]) / 10) - 9)), af[j]);
                double Lp = ((10 / af[j]) * Math.Log10(Af)) - Lu[j] + 94;

                // Return user data
                FreqGain fg = new FreqGain(f[j], Lp);
                lfg.Add(fg);
            }
            return lfg;
        }
예제 #4
0
        /// <summary>
        /// Return a list of freq/gain, only including inflection points (where the curve is flat).
        /// </summary>
        /// <param name="data"></param>
        /// <param name="sr"></param>
        /// <returns></returns>
        public static FilterProfile inflections(double[] data, uint sr)
        {
            // Differentiate
            double bins = data.Length + 1;
            double[] diff = new double[data.Length];

            double n = data[0];
            for (int j = 0; j < data.Length; j++)
            {
                double d = data[j];
                diff[j] = d - n;
                n = d;
            }

            // Look for zero-crossings of the first derivative of data[]
            // (always include [0] and [end])
            FilterProfile pts = new FilterProfile();

            int bin;
            double pt = 0.1;
            int last = -1;
            double freq;
            double lastfreq = -1;

            // Always start with points for zero and 10 Hz
            pts.Add(new FreqGain(0, MathUtil.dB(data[0])));

            freq = 10;
            bin = (int)f2bin(freq,sr,bins);
            pts.Add(new FreqGain(freq, MathUtil.dB(data[bin])));
            pt = diff[bin];

            for (int j = bin + 1; j < data.Length; j++)
            {
                if ((pt > 0 && diff[j] <= 0) || (pt < 0 && diff[j] >= 0))
                {
                    freq = bin2f(j,sr,bins);
                    pts.Add(new FreqGain(freq, MathUtil.dB(data[j])));
                    last = j;
                    lastfreq = freq;
                }
                pt = diff[j];
            }
            // Fill in the last few target samples
            if (lastfreq < (sr / 2) - 2050)
            {
                freq = (sr / 2) - 2050;
                bin = (int)f2bin(freq,sr,bins);
                pts.Add(new FreqGain(freq, MathUtil.dB(data[bin])));
            }
            if (lastfreq < (sr / 2) - 1550)
            {
                freq = (sr / 2) - 1550;
                bin = (int)f2bin(freq,sr,bins);
                pts.Add(new FreqGain(freq, MathUtil.dB(data[bin])));
            }
            if (lastfreq < sr / 2)
            {
                freq = sr / 2;
                pts.Add(new FreqGain(freq, MathUtil.dB(data[data.Length - 1])));
            }

            return pts;
        }
예제 #5
0
 /// <summary>
 /// Return a list of freq/gain, at the ERB centers
 /// Assuming you smoothed the data...
 /// </summary>
 /// <param name="data">data from ERB.smooth</param>
 /// <param name="sr">sample rate</param>
 /// <param name="scaleFactor">1.0 for ~38 bands.  0.5 for twice as many...</param>
 /// <returns></returns>
 public static FilterProfile profile(double[] data, uint sr, double scaleFactor)
 {
     FilterProfile pts = new FilterProfile();
     int dl = data.Length;
     for (double j = 1; j < ERB.ERBVal(sr / 2) + 1; j += scaleFactor)
     {
         double f = ERB.invERBVal(j);
         double n = f * 2 * dl / sr;
         if (n < dl)
         {
             double g = data[(int)n];
             pts.Add(new FreqGain(f, g));
         }
     }
     return pts;
 }
예제 #6
0
        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();
        }
예제 #7
0
 static ISoundObj LowPassFiltered(ISoundObj input, double freqStart, double gainEnd)
 {
     uint sampleRate = input.SampleRate;
     FilterProfile lfg = new FilterProfile();
     lfg.Add(new FreqGain(freqStart, 0));
     lfg.Add(new FreqGain(0.499*sampleRate, gainEnd));
     FastConvolver conv = new FastConvolver();
     conv.Input = input;
     conv.impulse = new FilterImpulse(8192, lfg, FilterInterpolation.COSINE, sampleRate);
     return conv;
 }
예제 #8
0
        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;
        }
예제 #9
0
        private static IEnumerator<double> Arbitrary(int length, FilterProfile coeffs, uint sampleRate)
        {
            // Generate random-phase with specified magnitudes in the frequency domain, then IFFT.
            // This noise is periodic - length (next power of 2 from 'n')

            int l = MathUtil.NextPowerOfTwo(length);
            Complex[] data = new Complex[l];

            int n = 0;
            double freq1 = coeffs[n].Freq * 2 * l / sampleRate;
            double freq2 = coeffs[n + 1].Freq * 2 * l / sampleRate;
            double gain1 = coeffs[n].Gain;
            double gain2 = coeffs[n + 1].Gain;

            double logn = Math.Log(l);
            for (int j = 0; j < l; j++)
            {
                double gainDb;
                double gain;
                if (j > freq2)
                {
                    // Move to the next coefficient
                    n++;
                    if (n < coeffs.Count-1)
                    {
                        freq1 = coeffs[n].Freq * 2 * l / sampleRate;
                        freq2 = coeffs[n + 1].Freq * 2 * l / sampleRate;
                        gain1 = coeffs[n].Gain;
                        gain2 = coeffs[n + 1].Gain;
                    }
                }
                if (j < freq1)
                {
                    gainDb = gain1;
                }
                else if (j > freq2)
                {
                    gainDb = gain2;
                }
                else
                {
                    // Raised Cosine: 0.5* ( cos(phi) + 1 ), from phi=pi to 2pi
                    //
                    double frac = (double)(j - freq1) / (double)(freq2 - freq1);
                    double ph = Math.PI * (1 + frac);
                    double rcos = (1 + Math.Cos(ph)) / 2;
                    gainDb = gain1 + rcos * (gain2 - gain1);
                }
                gain = MathUtil.gain(gainDb);

                // Create a random phase value from 0 to 2pi
                double phi = NextRandom() * 2 * Math.PI;
                // Magnitude is 1, so just trig
                double re = Math.Cos(phi);
                double im = Math.Sin(phi);
                data[j] = new Complex(gain * logn * re, gain * logn * im);
            }

            // IFFT
            Fourier.IFFT((int)l, data);

            // Return the real component
            int k = 0;
            while (true)
            {
                yield return data[k].Re * logn;
                k++;
                if (k >= l)
                {
                    k = 0;
                }
            }
        }
예제 #10
0
        private static ISoundObj GenerateFlatnessFilter(string impulsePath, ISoundObj impulseObj, double nFlatness)
        {
            // Flatness-filters are single channel
            // nFlatness is from 0 to 100
            // 0: follow the contours of the impulse
            // 100: completely flat

            DateTime dtStart = DateTime.Now;
            ISoundObj filterImpulse = null;
            uint nSR = impulseObj.SampleRate;
            uint nSR2 = nSR / 2;
            string sPath = FlatnessFilterPath(impulsePath, nSR, nFlatness);

            // Low flatness values (to 0) => very un-smooth
            // High flatness values (to 100) => very smooth
            double detail = (nFlatness / 50) + 0.05;

            // Get profile of the impulse
            FilterProfile lfg = new FilterProfile(impulseObj, detail);

            // Scale by the flatness values
            lfg = lfg * ((100 - nFlatness) / 100);

            // Invert
            lfg = lfg.Inverse(20);

            // Zero at HF
            lfg.Add(new FreqGain(nSR2 - 100, 0));

            // Build the flatness filter
            filterImpulse = new FilterImpulse(8192, lfg, FilterInterpolation.COSINE, nSR);

            try
            {
                // Write the filter impulse to disk
                WaveWriter wri = new WaveWriter(sPath);
                wri.Input = filterImpulse;
                wri.Format = WaveFormat.IEEE_FLOAT;
                wri.BitsPerSample = 64;
                wri.Run();
                wri.Close();

                if (_debug)
                {
                    // DEBUG: Write the flatness impulse as wav16
                    wri = new WaveWriter(sPath + ".wav");
                    wri.Input = filterImpulse;
                    wri.Format = WaveFormat.PCM;
                    wri.BitsPerSample = 16;
                    wri.Normalization = -1.0;
                    wri.Dither = DitherType.NONE;//.TRIANGULAR;
                    wri.Run();
                    wri.Close();
                }
            }
            catch (Exception e)
            {
                if (_debug)
                {
                    Trace.WriteLine("GenerateFlatnessFilter: " + e.Message);
                }
            }

            impulseObj.Reset();
            if (_debug)
            {
                TimeSpan ts = DateTime.Now.Subtract(dtStart);
                Trace.WriteLine("GenerateFlatnessFilter " + ts.TotalMilliseconds);
            }
            return filterImpulse;
        }
예제 #11
0
        /// <summary>
        /// Write the JSON description of the given filter impulse
        /// </summary>
        /// <param name="filterImpulse">ISoundObj</param>
        /// <param name="filterName">base filename, no extension</param>
        private static void WriteJSON(FilterProfile eqValues, ISoundObj filterImpulse, string filterName)
        {
            FilterProfile lfg;
            if (filterImpulse == null && filterName == null)
            {
                // If both parameters are null,
                // write a null "current.json"
                filterName = _userID.Replace(':', '_') + ".current";
                lfg = new FilterProfile();
                lfg.Add(new FreqGain(20, 0));
                lfg.Add(new FreqGain(22050, 0));
                if (_debug)
                {
                    Trace.WriteLine("WriteJSON {0} (null)", filterName);
                }
            }
            else
            {
                if (_debug)
                {
                    Trace.WriteLine("WriteJSON {0}", filterName);
                }

                // FFT the impulse
                // Smooth its magnitude response by ERB bands
                // Write the result to filterName.json
                lfg = new FilterProfile(filterImpulse, 0.5);
            }

            // Write to JSON
            string jsonFile = Path.Combine(_tempFolder, filterName + ".json");
            FileStream fs = new FileStream(jsonFile, FileMode.Create);
            StreamWriter sw = new StreamWriter(fs);
            sw.WriteLine("{");
            sw.Write(eqValues.ToJSONString("EQ", "User-set equalizer values") + ",");
            sw.Write(lfg.ToJSONString("Points", "Combined loudness, flatness and EQ filter"));
            sw.WriteLine("}");
            sw.Close();
            fs.Close();
        }
예제 #12
0
        static bool TryReadConfig(bool firstTime)
        {
            lock (_lockReadConfig)
            {
                XmlNode node;
                _configDocument.Load(_configFile);

                int oldDepth = _depth;
                string oldImpulsePath = _impulsePath;
                int oldBands = _eqBands;
                double oldLoudness = _eqLoudness;
                double oldFlatness = _eqFlatness;
                string oldMatrixFilter = _matrixFilter;
                string oldBFormatFilter = _bformatFilter;
                FilterProfile oldValues = new FilterProfile(_eqValues);

                _isBypass = nodeValueBool(_configDocument, "//InguzEQSettings/Client/@Bypass");

                // SignalGenerator mode: if there's a "SignalGenerator" element it overrides normal processing.
                _siggen = null;
                node = _configDocument.SelectSingleNode("//InguzEQSettings/Client/SignalGenerator");
                if (node != null)
                {
                    _siggenUseEQ = ChannelFlag.NONE;
                    string useEQ = nodeValue(_configDocument, "//InguzEQSettings/Client/SignalGenerator/@UseEQ");
                    if (!String.IsNullOrEmpty(useEQ))
                    {
                        switch (useEQ.ToUpperInvariant())
                        {
                            case "1":
                            case "YES":
                            case "TRUE":
                            case "BOTH":
                                _siggenUseEQ = ChannelFlag.BOTH;
                                break;
                            case "L":
                                _siggenUseEQ = ChannelFlag.LEFT;
                                break;
                            case "R":
                                _siggenUseEQ = ChannelFlag.RIGHT;
                                break;
                        }
                    }

                    _siggen = nodeValueUpper(_configDocument, "//InguzEQSettings/Client/SignalGenerator/@Type");
                    if (_siggen != null)
                    {
                        switch (_siggen)
                        {
                            case "IDENT":
                                _sigparamA = nodeValue(_configDocument, "//InguzEQSettings/Client/SignalGenerator/@L");
                                _sigparamB = nodeValue(_configDocument, "//InguzEQSettings/Client/SignalGenerator/@R");
                                break;
                            case "SWEEP":
                                _sigparam1 = nodeValueInt(_configDocument, "//InguzEQSettings/Client/SignalGenerator/@Length");
                                break;
                            case "SINE":
                            case "QUAD":
                            case "SQUARE":
                            case "BLSQUARE":
                            case "TRIANGLE":
                            case "BLTRIANGLE":
                            case "SAWTOOTH":
                            case "BLSAWTOOTH":
                                _sigparam1 = nodeValueInt(_configDocument, "//InguzEQSettings/Client/SignalGenerator/@Freq");
                                break;
                            case "WHITE":
                                break;
                            case "PINK":
                                // "Mono" param defaults to true for backward compatibility
                                _sigparam1 = 1;
                                string mono = nodeValue(_configDocument, "//InguzEQSettings/Client/SignalGenerator/@Mono");
                                if (!String.IsNullOrEmpty(mono))
                                {
                                    _sigparam1 = bool.Parse(mono) ? 1 : 0;
                                }
                                break;
                            case "INTERMODULATION":
                                _sigparam1 = nodeValueInt(_configDocument, "//InguzEQSettings/Client/SignalGenerator/@Freq1");
                                _sigparam2 = nodeValueInt(_configDocument, "//InguzEQSettings/Client/SignalGenerator/@Freq2");
                                _sigparam3 = nodeValueInt(_configDocument, "//InguzEQSettings/Client/SignalGenerator/@Freq3");
                                break;
                            case "SHAPEDBURST":
                                _sigparam1 = nodeValueInt(_configDocument, "//InguzEQSettings/Client/SignalGenerator/@Freq");
                                _sigparam2 = nodeValueInt(_configDocument, "//InguzEQSettings/Client/SignalGenerator/@Cycles");
                                break;
                            default:
                                _siggen = null;
                                break;
                        }
                    }
                }

                // Impulse path can be changed at runtime. Null or "" or "-" are all OK (null) values.
                node = _configDocument.SelectSingleNode("//InguzEQSettings/Client/Filter"); // @URL");
                _impulsePath = node == null ? "" : node.InnerText;

                // Same for stereo-width ('matrix') filter
                node = _configDocument.SelectSingleNode("//InguzEQSettings/Client/Matrix"); // @URL");
                _matrixFilter = node == null ? "" : node.InnerText;

                // Same for ambisonic filter (not documented)
                node = _configDocument.SelectSingleNode("//InguzEQSettings/Client/BFormat"); // @URL");
                _bformatFilter = node == null ? "" : node.InnerText;

                node = _configDocument.SelectSingleNode("//InguzEQSettings/Client/EQ/@Bands");
                _eqBands = (node == null) ? 0 : int.Parse(node.Value, CultureInfo.InvariantCulture);

                _eqValues.Clear();
                XmlNodeList nodes = _configDocument.SelectNodes("//InguzEQSettings/Client/EQ/Band");
                if (nodes != null)
                {
                    foreach (XmlNode bnode in nodes)
                    {
                        string att;
                        att = ((XmlElement)bnode).GetAttribute("Freq");
                        double freq = (att == null) ? 0 : double.Parse(att, CultureInfo.InvariantCulture);
                        if (freq >= 10 && freq < 48000)
                        {
                            att = ((XmlElement)bnode).InnerText; //.GetAttribute("dB");
                            double gain = (att == null) ? 0 : double.Parse(att, CultureInfo.InvariantCulture);
                            if (gain < -20) gain = -20;
                            if (gain > 20) gain = 20;
                            _eqValues.Add(new FreqGain(freq, gain));
                        }
                    }
                }

                _eqLoudness = nodeValuePercentage(_configDocument, "//InguzEQSettings/Client/Quietness", 0);

                _eqFlatness = nodeValuePercentage(_configDocument, "//InguzEQSettings/Client/Flatness", 100);

                node = _configDocument.SelectSingleNode("//InguzEQSettings/Client/Width");
                _width = (node == null) ? 0 : double.Parse(node.InnerText, CultureInfo.InvariantCulture);
                double fracWid = _width / 2;
                _widthShuffler.SigmaGain = MathUtil.gain(-fracWid);
                _widthShuffler.DeltaGain = MathUtil.gain(fracWid);     // UI specifies width in dB

                // Depth not documented (and not really working either)
                node = _configDocument.SelectSingleNode("//InguzEQSettings/Client/Depth");
                _depth = (node == null) ? 0 : int.Parse(node.InnerText, CultureInfo.InvariantCulture);
            //              _depthSkewer.Skew = _depth;

                node = _configDocument.SelectSingleNode("//InguzEQSettings/Client/Balance");
                _balance = (node == null) ? 0 : double.Parse(node.InnerText, CultureInfo.InvariantCulture);
                SetWriterGain();

                node = _configDocument.SelectSingleNode("//InguzEQSettings/Client/Skew");
                _skew = (node == null) ? 0 : int.Parse(node.InnerText, CultureInfo.InvariantCulture);
                _skewSkewer.Skew = _skew;

                // <AmbisonicDecode Type="XXX" Cardioid="N" Angle="N" File="path">
                //
                // For 2ch Ambisonic decodes (stereo UHJ, stereo Blumlein, etc), the type is specified in a parameter
                _ambiType = nodeValue(_configDocument, "//InguzEQSettings/Client/AmbisonicDecode/@Type");
                if (!String.IsNullOrEmpty(_ambiType) && _ambiType.ToUpperInvariant() == "MATRIX")
                {
                    _aftenNeeded = true;
                }
                _ambiCardioid = nodeValueDouble(_configDocument, "//InguzEQSettings/Client/AmbisonicDecode/@Cardioid");
                _ambiMicAngle = nodeValueDouble(_configDocument, "//InguzEQSettings/Client/AmbisonicDecode/@Angle");

                _ambiRotateX = nodeValueDouble(_configDocument, "//InguzEQSettings/Client/AmbisonicDecode/@RotateX");
                _ambiRotateY = nodeValueDouble(_configDocument, "//InguzEQSettings/Client/AmbisonicDecode/@RotateY");
                _ambiRotateZ = nodeValueDouble(_configDocument, "//InguzEQSettings/Client/AmbisonicDecode/@RotateZ");

                // For other Ambisonic decodes, the full decode matrix
                // is defined in a file, specified in config
                _ambiMatrixFile = nodeText(_configDocument, "//InguzEQSettings/Client/AmbisonicDecode/@File");

                if (!firstTime)
                {
                    bool eqChanged = false;
                    if ((_eqBands != oldBands) || (_eqLoudness != oldLoudness) || (_eqFlatness != oldFlatness))
                    {
                        eqChanged = true;
                    }
                    else
                    {
                        for (int n = 0; n < _eqValues.Count; n++)
                        {
                            if (_eqValues[n].Gain != oldValues[n].Gain || _eqValues[n].Gain != oldValues[n].Gain)
                            {
                                eqChanged = true;
                                break;
                            }
                        }
                    }
                    if (eqChanged || (oldImpulsePath != _impulsePath))
                    {
                        // Notify the convolver that there's a new impulse file
                        LoadImpulse();
                    }
                    if ((oldMatrixFilter != _matrixFilter) || (_depth != oldDepth))
                    {
                        LoadMatrixFilter();
                    }
                    if (oldBFormatFilter != _bformatFilter)
                    {
                        LoadBFormatFilter();
                    }
                }
                return true;
            }
        }