static ISoundObj Splice(string infileL, int peakPosL, string infileR, int peakPosR, string outfile) { //int tmp1; //bool tmp2; WaveReader reader1 = new WaveReader(infileL, WaveFormat.IEEE_FLOAT, 32, 1); // reader1.Skip(peakPosL, out tmp1, out tmp2); SoundBuffer buff1 = new SoundBuffer(reader1); buff1.ReadAll(); // Normalize loudness for each channel double g = Loudness.WeightedVolume(buff1); buff1.ApplyGain(1/g); WaveReader reader2 = new WaveReader(infileR, WaveFormat.IEEE_FLOAT, 32, 1); // reader2.Skip(peakPosR, out tmp1, out tmp2); SoundBuffer buff2 = new SoundBuffer(reader2); buff2.ReadAll(); g = Loudness.WeightedVolume(buff2); buff2.ApplyGain(1/g); ChannelSplicer splicer = new ChannelSplicer(); splicer.Add(buff1); splicer.Add(buff2); // General-purpose: // Find the extremities of the DRC impulse, // window asymmetrically. // // But, since we specifically used linear-phase filters on target and mic, // we know that the impulse is centered. // We want an impulse length (_filterLen) // so window the 2048 samples at each end (which are pretty low to begin with - less than -100dB) ISoundObj output; int nCount = (int)(buff1.Count / 2); if (nCount > _filterLen/2) { BlackmanHarris bhw = new BlackmanHarris(nCount, 2048, (nCount / 2) - 2048); bhw.Input = splicer; SampleBuffer sb = new SampleBuffer(bhw); output = sb.Subset(_filterLen/2, _filterLen); } else { output = splicer; } ISoundObj result = output; if (!_noSkew) { // Apply skew to compensate for time alignment in the impulse responses Skewer skewer = new Skewer(true); skewer.Input = output; skewer.Skew = peakPosL - peakPosR; result = skewer; } WaveWriter writer = new WaveWriter(outfile); writer.Input = result; writer.Dither = DitherType.NONE; writer.Format = WaveFormat.IEEE_FLOAT; writer.BitsPerSample = 32; writer.SampleRate = _sampleRate; writer.NumChannels = splicer.NumChannels; if (Double.IsNaN(_gain)) { writer.Normalization = 0; } else { writer.Gain = MathUtil.gain(_gain); } writer.Run(); writer.Close(); reader1.Close(); reader2.Close(); return result; }
static void Main(string[] args) { // Find where this executable is launched from string[] cargs = Environment.GetCommandLineArgs(); _thisFolder = Path.GetDirectoryName(cargs[0]); if (String.IsNullOrEmpty(_thisFolder)) { _thisFolder = Environment.CurrentDirectory; } string appData = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData); _impulsesFolder = Path.GetFullPath(Path.Combine(appData, "InguzEQ" + slash + "Impulses" + slash)); string[] inFiles = new string[4]; string inL = ""; string inR = ""; if (!DisplayInfo()) { return; } bool ok = (args.Length > 0); bool longUsage = false; for (int j = 0; ok && j < args.Length; j++) { string arg = args[j]; switch (args[j].ToUpperInvariant()) { case "/?": case "-?": case "/H": case "/HELP": ok = false; longUsage = true; break; case "/L": case "/0": inFiles[0] = args[++j]; _nInFiles = Math.Max(_nInFiles, 1); break; case "/R": case "/1": inFiles[1] = args[++j]; _nInFiles = Math.Max(_nInFiles, 2); break; case "/2": inFiles[2] = args[++j]; _nInFiles = Math.Max(_nInFiles, 3); break; case "/3": inFiles[3] = args[++j]; _nInFiles = Math.Max(_nInFiles, 4); break; case "/LENGTH": _filterLen = int.Parse(args[++j], CultureInfo.InvariantCulture); if (_filterLen < 16) { throw new Exception("Length is too small."); } break; case "/DBL": _dbl = true; break; case "/PCM": _pcm = true; break; case "/NODRC": _noDRC = true; break; case "/NOSKEW": _noSkew = true; break; case "/NONORM": // No normalization of the impulse response (undocumented) _noNorm = true; break; case "/SPLIT": _split = true; break; case "/COPY": _copy = true; break; case "/GAIN": _gain = double.Parse(args[++j], CultureInfo.InvariantCulture); break; case "/ALL": // Returns negative-time components as part of the impulse response // (experimental, to be used for THD measurement) _returnAll = true; break; case "/POWER": // Raises sweep to power n // (experimental, to be used for THD measurement) _power = int.Parse(args[++j], CultureInfo.InvariantCulture); break; case "/FMIN": // (experimental, i.e. broken) _fmin = int.Parse(args[++j], CultureInfo.InvariantCulture); _fminSpecified = true; break; case "/FMAX": // (experimental, i.e. broken) _fmax = int.Parse(args[++j], CultureInfo.InvariantCulture); _fmaxSpecified = true; break; case "/DIRECT": // Create filtered (direct-sound) filters _doDirectFilters = true; break; case "/NOSUB": // Don't apply subsonic filter to the impulse response _noSubsonicFilter = true; break; case "/NOOVER": // Don't override DRC's settings for filter type and length _noOverrideDRC = true; break; case "/KEEPTEMP": // Undocumented _keepTempFiles = true; break; case "/REFCH": // Override the reference-channel detection _refchannel = int.Parse(args[++j], CultureInfo.InvariantCulture); if (_refchannel<0 || _refchannel > _nInFiles - 1) { throw new Exception(String.Format("RefCh can only be from 0 to {0}.", _nInFiles-1)); } break; case "/ENV": // Undocumented. Save the Hilbert envelope _env = true; break; case "-": // ignore break; default: ok = false; break; } } if (!ok) { DisplayUsage(longUsage); } else { try { if (!_noDRC) { if (!File.Exists(GetDRCExe())) { stderr.WriteLine("Denis Sbragion's DRC (http://drc-fir.sourceforge.net/) was not found."); stderr.WriteLine("Only the impulse response will be calculated, not correction filters."); stderr.WriteLine(""); _noDRC = true; } } if (!_noDRC) { FileInfo[] drcfiles = new DirectoryInfo(_thisFolder).GetFiles("*.drc"); if (drcfiles.Length == 0) { stderr.WriteLine("No .drc files were found in the current folder."); stderr.WriteLine("Only the impulse response will be calculated, not correction filters."); stderr.WriteLine(""); _noDRC = true; } } for(int i=0; i<_nInFiles; i++) { string inFile = inFiles[i]; if (String.IsNullOrEmpty(inFile)) { stderr.WriteLine("Error: The {0} input file was not specified.", FileDescription(i)); return; } if (!File.Exists(inFile)) { stderr.WriteLine("Error: The {0} input file {1} was not found.", FileDescription(i), inFile); return; } for (int j = 0; j < i; j++) { if (inFile.Equals(inFiles[j])) { stderr.WriteLine("Warning: The same input file ({0}) was specified for both {1} and {2}!", inFile, FileDescription(j), FileDescription(i)); //stderr.WriteLine(); } } } // Temporary if (_nInFiles != 2) { stderr.WriteLine("Error: Two input files must be specified."); return; } inL = inFiles[0]; inR = inFiles[1]; // end temporary uint sampleRate; List<SoundObj> impulses; List<ISoundObj> filteredImpulses; List<string> impDirects; List<Complex[]> impulseFFTs; List<double> maxs; SoundObj impulseL; SoundObj impulseR; ISoundObj filteredImpulseL = null; ISoundObj filteredImpulseR = null; string impDirectL = null; string impDirectR = null; Complex[] impulseLFFT; Complex[] impulseRFFT; WaveWriter writer; ISoundObj buff; double g; if (!_keepTempFiles) { _tempFiles.Add("rps.pcm"); _tempFiles.Add("rtc.pcm"); } // Find the left impulse stderr.WriteLine("Processing left measurement ({0})...", inL); impulseL = Deconvolve(inL, out impulseLFFT, out _peakPosL); sampleRate = impulseL.SampleRate; _sampleRate = sampleRate; double peakM = Math.Round(MathUtil.Metres(_peakPosL, sampleRate), 2); double peakFt = Math.Round(MathUtil.Feet(_peakPosL, sampleRate), 2); stderr.WriteLine(" Impulse peak at sample {0} ({1}m, {2}ft)", _peakPosL, peakM, peakFt); // Write to PCM string impFileL = Path.GetFileNameWithoutExtension(inL) + "_imp" + ".pcm"; if (!_keepTempFiles) { _tempFiles.Add(impFileL); } writer = new WaveWriter(impFileL); writer.Input = impulseL; writer.Format = WaveFormat.IEEE_FLOAT; writer.BitsPerSample = 32; writer.SampleRate = _sampleRate; writer.Raw = true; writer.Run(); writer.Close(); // Write the impulseFFT to disk int L = impulseLFFT.Length; string impTempL = Path.GetFileNameWithoutExtension(inL) + "_imp" + ".dat"; _tempFiles.Add(impTempL); writer = new WaveWriter(impTempL); writer.Input = new CallbackSource(2, sampleRate, delegate(long j) { if (j >= L / 2) { return null; } Complex si = impulseLFFT[j]; // +impulseLFFT[L - j - 1]; ISample s = new Sample2(); s[0] = si.Magnitude; s[1] = si.Phase / Math.PI; return s; }); writer.Format = WaveFormat.IEEE_FLOAT; writer.BitsPerSample = 32; writer.SampleRate = _sampleRate; writer.Raw = false; writer.Run(); writer.Close(); writer = null; impulseLFFT = null; GC.Collect(); if (_doDirectFilters) { // Sliding low-pass filter over the impulse stderr.WriteLine(" Filtering..."); filteredImpulseL = SlidingLowPass(impulseL, _peakPosL); // Write PCM for the filtered impulse impDirectL = Path.GetFileNameWithoutExtension(inL) + "_impfilt" + ".pcm"; if (!_keepTempFiles) { _tempFiles.Add(impDirectL); } writer = new WaveWriter(impDirectL); writer.Input = filteredImpulseL; writer.Format = WaveFormat.IEEE_FLOAT; writer.SampleRate = _sampleRate; writer.BitsPerSample = 32; writer.Raw = false; writer.Run(); writer.Close(); writer = null; filteredImpulseL.Reset(); } GC.Collect(); stderr.WriteLine(" Deconvolution: left impulse done."); stderr.WriteLine(); // Find the right impulse stderr.WriteLine("Processing right measurement ({0})...", inR); impulseR = Deconvolve(inR, out impulseRFFT, out _peakPosR); peakM = Math.Round(MathUtil.Metres(_peakPosR, sampleRate), 2); peakFt = Math.Round(MathUtil.Feet(_peakPosR, sampleRate), 2); stderr.WriteLine(" Impulse peak at sample {0} ({1}m, {2}ft)", _peakPosR, peakM, peakFt); // Write to PCM string impFileR = Path.GetFileNameWithoutExtension(inR) + "_imp" + ".pcm"; if (!_keepTempFiles) { _tempFiles.Add(impFileR); } writer = new WaveWriter(impFileR); writer.Input = impulseR; writer.Format = WaveFormat.IEEE_FLOAT; writer.BitsPerSample = 32; writer.SampleRate = _sampleRate; writer.Raw = true; writer.Run(); writer.Close(); // Write the impulseFFT magnitude to disk L = impulseRFFT.Length; string impTempR = Path.GetFileNameWithoutExtension(inR) + "_imp" + ".dat"; _tempFiles.Add(impTempR); writer = new WaveWriter(impTempR); writer.Input = new CallbackSource(2, impulseR.SampleRate, delegate(long j) { if (j >= L / 2) { return null; } Complex si = impulseRFFT[j]; // +impulseRFFT[L - j - 1]; ISample s = new Sample2(); s[0] = si.Magnitude; s[1] = si.Phase / Math.PI; return s; }); writer.Format = WaveFormat.IEEE_FLOAT; writer.BitsPerSample = 32; writer.SampleRate = _sampleRate; writer.Raw = false; writer.Run(); writer.Close(); writer = null; impulseRFFT = null; GC.Collect(); if (_doDirectFilters) { // Sliding low-pass filter over the impulse stderr.WriteLine(" Filtering..."); filteredImpulseR = SlidingLowPass(impulseR, _peakPosR); // Write PCM for the filtered impulse impDirectR = Path.GetFileNameWithoutExtension(inR) + "_impfilt" + ".pcm"; if (!_keepTempFiles) { _tempFiles.Add(impDirectR); } writer = new WaveWriter(impDirectR); writer.Input = filteredImpulseR; writer.Format = WaveFormat.IEEE_FLOAT; writer.BitsPerSample = 32; writer.SampleRate = _sampleRate; writer.Raw = false; writer.Run(); writer.Close(); writer = null; filteredImpulseR.Reset(); } GC.Collect(); stderr.WriteLine(" Deconvolution: right impulse done."); stderr.WriteLine(); // Join the left and right impulse files (truncated at 65536) into a WAV // and normalize loudness for each channel stderr.WriteLine("Splicing and normalizing (1)"); ChannelSplicer longstereoImpulse = new ChannelSplicer(); // (Don't normalize each channel's volume separately if _returnAll, it's just too expensive) if (_returnAll) { buff = impulseL; } else { buff = new SoundBuffer(new SampleBuffer(impulseL).Subset(0, 131071)); g = Loudness.WeightedVolume(buff); (buff as SoundBuffer).ApplyGain(1 / g); } longstereoImpulse.Add(buff); if (_returnAll) { buff = impulseR; } else { buff = new SoundBuffer(new SampleBuffer(impulseR).Subset(0, 131071)); g = Loudness.WeightedVolume(buff); (buff as SoundBuffer).ApplyGain(1 / g); } longstereoImpulse.Add(buff); ISoundObj stereoImpulse = longstereoImpulse; _impulseFiles.Add("Impulse_Response_Measured.wav: stereo impulse response from measurements"); writer = new WaveWriter("Impulse_Response_Measured.wav"); writer.Input = longstereoImpulse; writer.Format = WaveFormat.IEEE_FLOAT; writer.BitsPerSample = 32; writer.SampleRate = _sampleRate; writer.Normalization = -1; writer.Raw = false; writer.Run(); writer.Close(); writer = null; if (_env) { // Also save the Hilbert envelope HilbertEnvelope env = new HilbertEnvelope(8191); env.Input = longstereoImpulse; _impulseFiles.Add("Impulse_Response_Envelope.wav: Hilbert envelope of the impulse response"); writer = new WaveWriter("Impulse_Response_Envelope.wav"); writer.Input = env; writer.Format = WaveFormat.IEEE_FLOAT; writer.BitsPerSample = 32; writer.SampleRate = _sampleRate; writer.Normalization = -1; writer.Raw = false; writer.Run(); writer.Close(); writer = null; } if (_dbl) { // Create DBL files for Acourate _impulseFiles.Add("PulseL.dbl: impulse response, raw data (64-bit float), left channel "); _impulseFiles.Add("PulseR.dbl: impulse response, raw data (64-bit float), right channel"); _impulseFiles.Add(" (use skew=" + (_peakPosL - _peakPosR) + " for time alignment)"); WriteImpulseDBL(stereoImpulse, "PulseL.dbl", "PulseR.dbl"); } if (_pcm) { // Create PCM files for Octave (etc) _impulseFiles.Add("LUncorrected.pcm: impulse response, raw data (32-bit float), left channel"); _impulseFiles.Add("RUncorrected.pcm: impulse response, raw data (32-bit float), right channel"); WriteImpulsePCM(stereoImpulse, "LUncorrected.pcm", "RUncorrected.pcm"); } stereoImpulse = null; longstereoImpulse = null; buff = null; GC.Collect(); if (_doDirectFilters) { // Same for the filtered impulse response stderr.WriteLine("Splicing and normalizing (2)"); ChannelSplicer longstereoImpulseF = new ChannelSplicer(); buff = new SoundBuffer(new SampleBuffer(filteredImpulseL).Subset(0, 131071)); double gL = Loudness.WeightedVolume(buff); (buff as SoundBuffer).ApplyGain(1 / gL); longstereoImpulseF.Add(buff); FilterProfile lfgDirectL = new FilterProfile(buff, 0.5); buff = new SoundBuffer(new SampleBuffer(filteredImpulseR).Subset(0, 131071)); double gR = Loudness.WeightedVolume(buff); (buff as SoundBuffer).ApplyGain(1 / gR); longstereoImpulseF.Add(buff); FilterProfile lfgDirectR = new FilterProfile(buff, 0.5); _impulseFiles.Add("Impulse_Response_Filtered.wav: approximation to direct-sound impulse response"); writer = new WaveWriter("Impulse_Response_Filtered.wav"); writer.Input = longstereoImpulseF; writer.Format = WaveFormat.IEEE_FLOAT; writer.BitsPerSample = 32; writer.SampleRate = _sampleRate; writer.Normalization = -1; writer.Raw = false; writer.Run(); writer.Close(); double gg = writer.Gain; writer = null; longstereoImpulseF = null; ChannelSplicer longstereoImpulseD = new ChannelSplicer(); Mixer diffuse = new Mixer(); diffuse.Add(impulseL, 1.0); diffuse.Add(filteredImpulseL, -1.0); buff = new SoundBuffer(new SampleBuffer(diffuse).Subset(0, 131071)); (buff as SoundBuffer).ApplyGain(1 / gL); longstereoImpulseD.Add(buff); FilterProfile lfgDiffuseL = new FilterProfile(buff, 0.5); diffuse = new Mixer(); diffuse.Add(impulseR, 1.0); diffuse.Add(filteredImpulseR, -1.0); buff = new SoundBuffer(new SampleBuffer(diffuse).Subset(0, 131071)); (buff as SoundBuffer).ApplyGain(1 / gR); longstereoImpulseD.Add(buff); FilterProfile lfgDiffuseR = new FilterProfile(buff, 0.5); _impulseFiles.Add("Impulse_Response_Diffuse.wav: approximation to diffuse-field remnant"); writer = new WaveWriter("Impulse_Response_Diffuse.wav"); writer.Input = longstereoImpulseD; writer.Format = WaveFormat.IEEE_FLOAT; writer.BitsPerSample = 32; writer.SampleRate = _sampleRate; writer.Gain = gg; writer.Raw = false; writer.Run(); writer.Close(); writer = null; // Filter the diffuse-field curve against double the diffuse-field curve FilterImpulse fiDiffuse = new FilterImpulse(8192, HRTF.diffuseDiff0() * 2, FilterInterpolation.COSINE, sampleRate); FastConvolver co = new FastConvolver(longstereoImpulseD, fiDiffuse); SoundBuffer buffd = new SoundBuffer(co); _impulseFiles.Add("Impulse_Response_Diffuse_Comp.wav: filtered diffuse-field remnant"); writer = new WaveWriter("Impulse_Response_Diffuse_Comp.wav"); writer.Input = buffd.Subset(4096); writer.Format = WaveFormat.IEEE_FLOAT; writer.BitsPerSample = 32; writer.SampleRate = _sampleRate; writer.Gain = gg; writer.Raw = false; writer.Run(); writer.Close(); writer = null; longstereoImpulseD = null; bool any = false; string jsonFile = "Diff.json"; FileStream fs = new FileStream(jsonFile, FileMode.Create); StreamWriter sw = new StreamWriter(fs); sw.WriteLine("{"); FilterProfile lfgDiffL = lfgDirectL - lfgDiffuseL; if (lfgDiffL != null) { if (any) sw.WriteLine(","); any = true; sw.Write(lfgDiffL.ToJSONString("DiffL", "Diffuse field relative to direct, left channel")); } FilterProfile lfgDiffR = lfgDirectR - lfgDiffuseR; if (lfgDiffR != null) { if (any) sw.WriteLine(","); any = true; sw.Write(lfgDiffR.ToJSONString("DiffR", "Diffuse field relative to direct, right channel")); } sw.WriteLine("}"); sw.Close(); fs.Close(); } buff = null; GC.Collect(); System.Console.Error.WriteLine(); if (!_noDRC) { // Analyze the freq response // and create targets // target_full.txt and target_half.txt stderr.WriteLine("Analyzing response curves."); Prep(impTempL, impTempR, "Impulse_Response_Measured.wav", "NoCorrection"); // Call DRC to create the filters // then splice the DRC left & right output files together stderr.WriteLine("Preparing for DRC."); if (DoDRC(impFileL, impFileR, impDirectL, impDirectR, _peakPosL, _peakPosR, "Impulse_Response_Measured.wav", "Impulse_Response_Filtered.wav")) { stderr.WriteLine("Success!"); } } // Report names of the impulse files created if (_impulseFiles.Count == 0) { System.Console.Error.WriteLine("No impulse response files were created."); } if (_impulseFiles.Count > 0) { System.Console.Error.WriteLine("Impulse response files were created:"); foreach (string f in _impulseFiles) { string s = " " + f; System.Console.Error.WriteLine(s); } } // Report names of the filter files created if (_filterFiles.Count == 0 && !_noDRC) { System.Console.Error.WriteLine("No correction filter files were created."); } if (_filterFiles.Count > 0) { System.Console.Error.WriteLine("Correction filter files were created:"); foreach (string f in _filterFiles) { string s = " " + f; if (_copy) { try { File.Copy(f, Path.Combine(_impulsesFolder, f), true); s += " (copied)"; } catch (Exception e) { s += " (not copied: " + e.Message + ")"; } } System.Console.Error.WriteLine(s); } } if (_peakPosL == _peakPosR) { System.Console.Error.WriteLine(); System.Console.Error.WriteLine("Zero time difference between channels. Are you sure the recordings are correct?"); } } catch (Exception e) { stderr.WriteLine(); stderr.WriteLine(e.Message); stderr.WriteLine(e.StackTrace); } finally { foreach (string tempFile in _tempFiles) { try { File.Delete(tempFile); } catch (Exception) { /* ignore */ } } } } stderr.Flush(); }
static ISoundObj DecodeBFormatUHJ(ISoundObj source) { ISoundObj input = source; uint sr = input.SampleRate; /* if (_ambiUseShelf) { // Shelf-filters // boost W at high frequencies, and boost X, Y at low frequencies FilterProfile lfgXY = new FilterProfile(); lfgXY.Add(new FreqGain(_ambiShelfFreq / 2, 0)); lfgXY.Add(new FreqGain(_ambiShelfFreq * 2, -1.25)); FilterImpulse fiXY = new FilterImpulse(0, lfgXY, FilterInterpolation.COSINE, sr); FilterProfile lfgW = new FilterProfile(); lfgW.Add(new FreqGain(_ambiShelfFreq / 2, 0)); lfgW.Add(new FreqGain(_ambiShelfFreq * 2, 1.76)); FilterImpulse fiW = new FilterImpulse(0, lfgW, FilterInterpolation.COSINE, sr); } if (_ambiUseDistance) { // Distance compensation filters // apply phase shift to X, Y at (very) low frequencies double fc = MathUtil.FcFromMetres(_ambiDistance); IIR1 discomp = new IIR1LP(sr, fc, 8192); // tbd: chain this } */ // Transformation filters // // Primary reference: // Gerzon 1985 "Ambisonics in Multichannel Broadcasting and Video" // // Coefficients from: http://en.wikipedia.org/wiki/Ambisonic_UHJ_format: // S = 0.9396926*W + 0.1855740*X // D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y // Left = (S + D)/2.0 // Right = (S - D)/2.0 // which makes // Left = (0.092787 + 0.2549302j)X + (0.4698463 - 0.17101005j)W + (0.3277258)Y // Right= (0.092787 - 0.2549302j)X + (0.4698463 + 0.17101005j)W - (0.3277258)Y // // Coefficients from: http://www.york.ac.uk/inst/mustech/3d_audio/ambis2.htm // Left = (0.0928 + 0.255j)X + (0.4699 - 0.171j)W + (0.3277)Y // Right= (0.0928 - 0.255j)X + (0.4699 + 0.171j)W - (0.3277)Y // The Mid-Side versions are simpler // L+R = (0.0928 + 0.255j)X + (0.4699 - 0.171j)W + (0.3277)Y + ((0.0928 - 0.255j)X + (0.4699 + 0.171j)W - (0.3277)Y) // = (0.1856)X + (0.9398)W // L-R = (0.0928 + 0.255j)X + (0.4699 - 0.171j)W + (0.3277)Y - ((0.0928 - 0.255j)X + (0.4699 + 0.171j)W - (0.3277)Y) // = (0.510j)X + (0.342j)W + (0.6554)Y // but since we're delaying signal via convolution anyway, not *too* much extra processing to do in LR mode... // Separate the WXY channels ISoundObj channelW = new SingleChannel(input, 0); ISoundObj channelX = new SingleChannel(input, 1, true); ISoundObj channelY = new SingleChannel(input, 2, true); // Z not used; height is discarded in UHJ conversion. // Don't assume it's there; horizontal-only .AMB files won't have a fourth channel // ISoundObj channelZ = new SingleChannel(input, 3); // Phase shift j is implemented with Hilbert transforms // so let's load up some filters, multiply by the appropriate coefficients. int len = 8191; PhaseMultiplier xl = new PhaseMultiplier(new Complex(0.0927870, 0.25493020), len, sr); PhaseMultiplier wl = new PhaseMultiplier(new Complex(0.4698463, -0.17101005), len, sr); PhaseMultiplier yl = new PhaseMultiplier(new Complex(0.3277258, 0.00000000), len, sr); PhaseMultiplier xr = new PhaseMultiplier(new Complex(0.0927870, -0.25493020), len, sr); PhaseMultiplier wr = new PhaseMultiplier(new Complex(0.4698463, 0.17101005), len, sr); PhaseMultiplier yr = new PhaseMultiplier(new Complex(-0.3277258, 0.00000000), len, sr); // The convolvers to filter FastConvolver cwl = new FastConvolver(channelW, wl); FastConvolver cxl = new FastConvolver(channelX, xl); FastConvolver cyl = new FastConvolver(channelY, yl); FastConvolver cwr = new FastConvolver(channelW, wr); FastConvolver cxr = new FastConvolver(channelX, xr); FastConvolver cyr = new FastConvolver(channelY, yr); // Sum to get the final output of these things: Mixer mixerL = new Mixer(); mixerL.Add(cwl, 1.0); mixerL.Add(cxl, 1.0); mixerL.Add(cyl, 1.0); Mixer mixerR = new Mixer(); mixerR.Add(cwr, 1.0); mixerR.Add(cxr, 1.0); mixerR.Add(cyr, 1.0); // output in stereo ChannelSplicer uhj = new ChannelSplicer(); uhj.Add(mixerL); uhj.Add(mixerR); return uhj; }
static ISoundObj DecodeBFormatCrossed(ISoundObj source) { // Stereo decode: // A shuffle of the X (front-back) and Y (left-right) channels // with some W mixed, so the effective mics become (hyper)cardioid instead of figure-8. // If _ambiCardioid=0, this is the same as Blumlein. // If _ambiCardioid=1, this is cardioid // Of any value between, for hypercardioid patterns. // Separate the WXY channels ISoundObj channelW = new SingleChannel(source, 0); ISoundObj channelX = new SingleChannel(source, 1, true); ISoundObj channelY = new SingleChannel(source, 2, true); // The _ambiMicAngle says angle between the virtual microphones (degrees) // e.g. if this is 100 // Left and right are each 50 degrees from forward. // These are implemented by mixing appropriate amounts of X and Y; // X * cos(angle/2) // Y * sin(angle/2) // // For default mic angle of 90 degrees, mulX=mulY=sqrt(2)/2 // double mulX = Math.Cos(MathUtil.Radians(_ambiMicAngle / 2)); double mulY = Math.Sin(MathUtil.Radians(_ambiMicAngle / 2)); // Mix back together, adding appropriate amounts of W. // The W channel gain is conventionally sqrt(2)/2 relative to X and Y, Mixer mixerL = new Mixer(); mixerL.Add(channelW, _ambiCardioid * MathUtil.INVSQRT2); mixerL.Add(channelX, mulX); mixerL.Add(channelY, mulY); Mixer mixerR = new Mixer(); mixerR.Add(channelW, _ambiCardioid * MathUtil.INVSQRT2); mixerR.Add(channelX, mulX); mixerR.Add(channelY, -mulY); // output in stereo ChannelSplicer stereo = new ChannelSplicer(); stereo.Add(mixerL); stereo.Add(mixerR); return stereo; }
static ISoundObj TwoChannel(ISoundObj src) { if (src.NumChannels == 2) { return src; } else if (src.NumChannels == 1) { ChannelSplicer splicer = new ChannelSplicer(); splicer.Add(src); splicer.Add(src); return splicer; } else { ChannelSplicer splicer = new ChannelSplicer(); splicer.Add(new SingleChannel(src, 0)); splicer.Add(new SingleChannel(src, 1)); return splicer; } }
static ISoundObj RotateBFormat(ISoundObj source) { // Rotate a B-Format source ISoundObj channelW = new SingleChannel(source, 0); ISoundObj channelX = new SingleChannel(source, 1, true); ISoundObj channelY = new SingleChannel(source, 2, true); ISoundObj channelZ = new SingleChannel(source, 3, true); double rx = MathUtil.Radians(_ambiRotateX); double ry = MathUtil.Radians(_ambiRotateY); double rz = MathUtil.Radians(_ambiRotateZ); // Mixer W = new Mixer(); Mixer X = new Mixer(); Mixer Y = new Mixer(); Mixer Z = new Mixer(); // W is unchanged (omni) // W.Add(channelW, 1.0); // http://www.muse.demon.co.uk/fmhrotat.html // tilt // x1 = x * 1 + y * 0 + z * 0 // y1 = x * 0 + y * Cos(rx) - z * Sin(rx) // z1 = x * 0 + y * Sin(rx) + z * Cos(rx) // tumble // x2 = x * Cos(ry) + y * 0 - z * Sin(ry) // y2 = x * 0 + y * 1 + z * 0 // z2 = x * Sin(ry) + y * 0 + z * Cos(ry) // rotate // x3 = x * Cos(rz) - y * Sin(rz) + z * 0 // y3 = x * Sin(rz) + y * Cos(rz) + z * 0 // z3 = x * 0 + y * 0 + z * 1 // (read that downwards to get:) X.Add(channelX, Math.Cos(ry) * Math.Cos(rz)); X.Add(channelY, -Math.Sin(rz)); X.Add(channelZ, -Math.Sin(ry)); Y.Add(channelX, Math.Sin(rz)); Y.Add(channelY, Math.Cos(rx) * Math.Cos(rz)); Y.Add(channelZ, -Math.Sin(rx)); Z.Add(channelX, Math.Sin(ry)); Z.Add(channelY, Math.Sin(rz)); Z.Add(channelZ, Math.Cos(rz) * Math.Cos(ry)); ChannelSplicer ret = new ChannelSplicer(); ret.Add(channelW); ret.Add(X); ret.Add(Y); ret.Add(Z); return ret; }
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(); } }