/// <summary> /// Fast convolution via FFT for arrays of samples (maximally in-place). /// This version is best suited for block processing when memory needs to be reused. /// Input arrays must have size equal to the size of FFT. /// </summary> /// <param name="real1">Real parts of the 1st signal (zero-padded)</param> /// <param name="imag1">Imaginary parts of the 1st signal (zero-padded)</param> /// <param name="real2">Real parts of the 2nd signal (zero-padded)</param> /// <param name="imag2">Imaginary parts of the 2nd signal (zero-padded)</param> /// <param name="res">Real parts of resulting convolution (zero-padded if center == 0)</param> /// <param name="center"> /// Position of central sample for the case of 2*M-1 convolution /// (if it is set then resulting array has length of M) /// </param> public static void Convolve(float[] real1, float[] imag1, float[] real2, float[] imag2, float[] res, int center = 0) { var fftSize = real1.Length; var fft = new Fft(fftSize); // 1) do FFT of both signals fft.Direct(real1, imag1); fft.Direct(real2, imag2); // 2) do complex multiplication of spectra and normalize for (var i = 0; i < fftSize; i++) { var re = real1[i] * real2[i] - imag1[i] * imag2[i]; var im = real1[i] * imag2[i] + imag1[i] * real2[i]; real1[i] = re / fftSize; imag1[i] = im / fftSize; } // 3) do inverse FFT of resulting spectrum fft.Inverse(real1, imag1); // 4) return output array if (center > 0) { real1.FastCopyTo(res, center, center - 1); } else { real1.FastCopyTo(res, fftSize); } }
public double[] Spectrum(double[] input, bool scale) { int length = input.Length; var fft = new Fft(length); var re = new float[length]; var im = new float[length]; for (int i = 0; i < length; i++) { re[i] = (float)input[i]; im[i] = 0f; } fft.Direct(re, im); var spectrum = Helper.ComputeSpectrum(length, re, im); fft.Inverse(re, im); for (int i = 0; i < length; i++) { input[i] = re[i]; } return(spectrum); }
/// <summary> /// Process one frame (block) /// </summary> public void ProcessFrame() { var M = _kernel.Length; _zeroblock.FastCopyTo(_blockIm, _fftSize); _zeroblock.FastCopyTo(_blockRe, M - 1, 0, HopSize); _fft.Direct(_blockRe, _blockIm); for (var j = 0; j < _fftSize; j++) { _convRe[j] = (_blockRe[j] * _kernelSpectrumRe[j] - _blockIm[j] * _kernelSpectrumIm[j]) / _fftSize; _convIm[j] = (_blockRe[j] * _kernelSpectrumIm[j] + _blockIm[j] * _kernelSpectrumRe[j]) / _fftSize; } _fft.Inverse(_convRe, _convIm); for (var j = 0; j < M - 1; j++) { _convRe[j] += _lastSaved[j]; } _convRe.FastCopyTo(_lastSaved, M - 1, HopSize); _outputBufferOffset = 0; _bufferOffset = 0; }
/// <summary> /// Fast convolution via FFT for arrays of samples (maximally in-place). /// This version is best suited for block processing when memory needs to be reused. /// Input arrays must have size equal to the size of FFT. /// FFT size MUST be set properly in constructor! /// </summary> /// <param name="input">Real parts of the 1st signal (zero-padded)</param> /// <param name="kernel">Real parts of the 2nd signal (zero-padded)</param> /// <param name="output">Real parts of resulting convolution (zero-padded)</param> public void Convolve(float[] input, float[] kernel, float[] output) { _zeroblock.FastCopyTo(_real1, _fftSize); _zeroblock.FastCopyTo(_real2, _fftSize); _zeroblock.FastCopyTo(_imag1, _fftSize); _zeroblock.FastCopyTo(_imag2, _fftSize); input.FastCopyTo(_real1, input.Length); kernel.FastCopyTo(_real2, kernel.Length); // 1) do FFT of both signals _fft.Direct(_real1, _imag1); _fft.Direct(_real2, _imag2); // 2) do complex multiplication of spectra and normalize for (var i = 0; i < _fftSize; i++) { var re = _real1[i] * _real2[i] - _imag1[i] * _imag2[i]; var im = _real1[i] * _imag2[i] + _imag1[i] * _real2[i]; output[i] = re / _fftSize; _imag1[i] = im / _fftSize; } // 3) do inverse FFT of resulting spectrum _fft.Inverse(output, _imag1); }
public override DiscreteSignal ApplyTo(DiscreteSignal signal, FilteringMethod method = FilteringMethod.Auto) { var input = signal.Samples; var output = new float[input.Length]; var posSynthesis = 0; for (var posAnalysis = 0; posAnalysis + _fftSize < input.Length; posAnalysis += _hopSize) { input.FastCopyTo(_re, _fftSize, posAnalysis); _zeroblock.FastCopyTo(_im, _fftSize); _re.ApplyWindow(_window); _fft.Direct(_re, _im); for (var j = 0; j < _fftSize / 2 + 1; j++) { var mag = Math.Sqrt(_re[j] * _re[j] + _im[j] * _im[j]); var phase = Math.Atan2(_im[j], _re[j]); _re[j] = (float)mag; _im[j] = 0; } for (var j = _fftSize / 2 + 1; j < _fftSize; j++) { _re[j] = _im[j] = 0.0f; } _fft.Inverse(_re, _im); for (var j = 0; j < _re.Length; j++) { output[posSynthesis + j] += _re[j] * _window[j]; } for (var j = 0; j < _hopSize; j++) { output[posSynthesis + j] *= _gain; output[j] = Wet * output[j] + Dry * input[j]; } posSynthesis += _hopSize; } for (; posSynthesis < output.Length; posSynthesis++) { output[posSynthesis] *= _gain; output[posSynthesis] = Wet * output[posSynthesis] + Dry * input[posSynthesis]; } return(new DiscreteSignal(signal.SamplingRate, output)); }
public void FFT(bool forward) { if (forward) { fft.Direct(re, im); } else { fft.Inverse(re, im); } }
/// <summary> /// Phase Vocoder algorithm /// </summary> /// <param name="signal"></param> /// <param name="method"></param> /// <returns></returns> public DiscreteSignal ApplyTo(DiscreteSignal signal, FilteringMethod method = FilteringMethod.Auto) { var input = signal.Samples; var output = new float[(int)(input.Length * _stretch) + _fftSize]; var posSynthesis = 0; for (var posAnalysis = 0; posAnalysis + _fftSize < input.Length; posAnalysis += _hopAnalysis) { input.FastCopyTo(_re, _fftSize, posAnalysis); _zeroblock.FastCopyTo(_im, _fftSize); _re.ApplyWindow(_window); _fft.Direct(_re, _im); for (var j = 0; j < _fftSize / 2 + 1; j++) { var mag = Math.Sqrt(_re[j] * _re[j] + _im[j] * _im[j]); var phase = 2 * Math.PI * _rand.NextDouble(); _re[j] = (float)(mag * Math.Cos(phase)); _im[j] = (float)(mag * Math.Sin(phase)); } for (var j = _fftSize / 2 + 1; j < _fftSize; j++) { _re[j] = _im[j] = 0.0f; } _fft.Inverse(_re, _im); for (var j = 0; j < _re.Length; j++) { output[posSynthesis + j] += _re[j] * _window[j]; } for (var j = 0; j < _hopSynthesis; j++) { output[posSynthesis + j] *= _gain; } posSynthesis += _hopSynthesis; } for (; posSynthesis < output.Length; posSynthesis++) { output[posSynthesis] *= _gain; } return(new DiscreteSignal(signal.SamplingRate, output)); }
public void TestInverseFft() { float[] re = { 1, 5, 3, 7, 2, 3, 0, 7 }; float[] im = new float[re.Length]; var fft = new Fft(8); fft.Direct(re, im); fft.Inverse(re, im); Assert.That(re, Is.EqualTo(new[] { 8, 40, 24, 56, 16, 24, 0, 56 }).Within(1e-5)); // re[i] * 8, i = 0..7 }
/// <summary> /// Process one frame (block) /// </summary> public void ProcessFrame() { _zeroblock.FastCopyTo(_im1, _fftSize); _zeroblock.FastCopyTo(_im2, _fftSize); _dl1.FastCopyTo(_re1, _fftSize); _dl2.FastCopyTo(_re2, _fftSize); _re1.ApplyWindow(_window); _re2.ApplyWindow(_window); _fft.Direct(_re1, _im1); _fft.Direct(_re2, _im2); for (var j = 0; j <= _fftSize / 2; j++) { var mag1 = Math.Sqrt(_re1[j] * _re1[j] + _im1[j] * _im1[j]); var phase2 = Math.Atan2(_im2[j], _re2[j]); _filteredRe[j] = (float)(mag1 * Math.Cos(phase2)); _filteredIm[j] = (float)(mag1 * Math.Sin(phase2)); } for (var j = _fftSize / 2 + 1; j < _fftSize; j++) { _filteredRe[j] = _filteredIm[j] = 0.0f; } _fft.Inverse(_filteredRe, _filteredIm); _filteredRe.ApplyWindow(_window); for (var j = 0; j < _overlapSize; j++) { _filteredRe[j] += _lastSaved[j]; } _filteredRe.FastCopyTo(_lastSaved, _overlapSize, _hopSize); for (var i = 0; i < _filteredRe.Length; i++) // Wet / Dry mix { _filteredRe[i] *= Wet / _fftSize; _filteredRe[i] += _dl2[i] * Dry; } _dl1.FastCopyTo(_dl1, _overlapSize, _hopSize); _dl2.FastCopyTo(_dl2, _overlapSize, _hopSize); _inOffset = _overlapSize; _outOffset = 0; }
/// <summary> /// Process one frame (block) /// </summary> public void ProcessFrame() { float k = (AlphaMin - AlphaMax) / (SnrMax - SnrMin); float b = AlphaMax - k * SnrMin; _zeroblock.FastCopyTo(_im, _fftSize); _dl.FastCopyTo(_re, _fftSize); _re.ApplyWindow(_window); _fft.Direct(_re, _im); for (var j = 0; j <= _fftSize / 2; j++) { var power = _re[j] * _re[j] + _im[j] * _im[j]; var phase = Math.Atan2(_im[j], _re[j]); var noisePower = _noiseEstimate[j]; var snr = 10 * Math.Log10(power / noisePower); var alpha = Math.Max(Math.Min(k * snr + b, AlphaMax), AlphaMin); var diff = power - alpha * noisePower; var mag = Math.Sqrt(Math.Max(diff, Beta * noisePower)); _filteredRe[j] = (float)(mag * Math.Cos(phase)); _filteredIm[j] = (float)(mag * Math.Sin(phase)); } for (var j = _fftSize / 2 + 1; j < _fftSize; j++) { _filteredRe[j] = _filteredIm[j] = 0.0f; } _fft.Inverse(_filteredRe, _filteredIm); _filteredRe.ApplyWindow(_window); for (var j = 0; j < _overlapSize; j++) { _filteredRe[j] += _lastSaved[j]; } _filteredRe.FastCopyTo(_lastSaved, _overlapSize, _hopSize); _dl.FastCopyTo(_dl, _overlapSize, _hopSize); _inOffset = _overlapSize; _outOffset = 0; }
/// <summary> /// Process one frame (block) /// </summary> public void ProcessFrame() { _zeroblock.FastCopyTo(_im, _fftSize); _dl.FastCopyTo(_re, _fftSize); _re.ApplyWindow(_window); _fft.Direct(_re, _im); for (var j = 0; j <= _fftSize / 2; j++) { var mag = Math.Sqrt(_re[j] * _re[j] + _im[j] * _im[j]); var phase = 2 * Math.PI * _rand.NextDouble(); _filteredRe[j] = (float)(mag * Math.Cos(phase)); _filteredIm[j] = (float)(mag * Math.Sin(phase)); } for (var j = _fftSize / 2 + 1; j < _fftSize; j++) { _filteredRe[j] = _filteredIm[j] = 0.0f; } _fft.Inverse(_filteredRe, _filteredIm); _filteredRe.ApplyWindow(_window); for (var j = 0; j < _overlapSize; j++) { _filteredRe[j] += _lastSaved[j]; } _filteredRe.FastCopyTo(_lastSaved, _overlapSize, _hopSize); for (var i = 0; i < _filteredRe.Length; i++) // Wet / Dry mix { _filteredRe[i] *= Wet * 2 / _fftSize; _filteredRe[i] += _dl[i] * Dry; } _dl.FastCopyTo(_dl, _overlapSize, _hopSize); _inOffset = _overlapSize; _outOffset = 0; }
/// <summary> /// Inverse transform /// </summary> /// <param name="re"></param> public void Inverse(float[] re) { _im[0] = 0; for (var i = 1; i <= re.Length / 2; i++) { var x = (re[Size - i] - re[i]) * 0.5f; _im[i] = x; _im[Size - i] = -x; x = (re[i] + re[Size - i]) * 0.5f; re[i] = re[Size - i] = x; } _fft.Inverse(re, _im); for (var i = 0; i < re.Length; i++) { re[i] /= Size; } }
/// <summary> /// Fast convolution via FFT of real-valued signals. /// </summary> /// <param name="signal1"></param> /// <param name="signal2"></param> /// <returns></returns> public static DiscreteSignal Convolve(DiscreteSignal signal1, DiscreteSignal signal2) { // prepare blocks in memory: var length = signal1.Length + signal2.Length - 1; var fftSize = MathUtils.NextPowerOfTwo(length); var fft = new Fft(fftSize); var real1 = new float[fftSize]; var imag1 = new float[fftSize]; var real2 = new float[fftSize]; var imag2 = new float[fftSize]; signal1.Samples.FastCopyTo(real1, signal1.Length); signal2.Samples.FastCopyTo(real2, signal2.Length); // 1) do FFT of both signals fft.Direct(real1, imag1); fft.Direct(real2, imag2); // 2) do complex multiplication of spectra and normalize for (var i = 0; i < fftSize; i++) { var re = real1[i] * real2[i] - imag1[i] * imag2[i]; var im = real1[i] * imag2[i] + imag1[i] * real2[i]; real1[i] = re / fftSize; imag1[i] = im / fftSize; } // 3) do inverse FFT of resulting spectrum fft.Inverse(real1, imag1); // 4) return resulting meaningful part of the signal (truncate size to N + M - 1) return(new DiscreteSignal(signal1.SamplingRate, real1).First(length)); }
/// <summary> /// FIR filter design using frequency sampling method (32-bit precision) /// </summary> /// <param name="order">Filter order</param> /// <param name="magnitudeResponse">Magnitude response</param> /// <param name="phaseResponse">Phase response</param> /// <param name="window">Window</param> /// <returns>FIR filter kernel</returns> public static double[] Fir(int order, float[] magnitudeResponse, float[] phaseResponse = null, WindowTypes window = WindowTypes.Blackman) { Guard.AgainstEvenNumber(order, "The order of the filter"); var fftSize = MathUtils.NextPowerOfTwo(magnitudeResponse.Length); var real = phaseResponse == null? magnitudeResponse.PadZeros(fftSize) : magnitudeResponse.Zip(phaseResponse, (m, p) => (float)(m * Math.Cos(p))).ToArray(); var imag = phaseResponse == null ? new float[fftSize] : magnitudeResponse.Zip(phaseResponse, (m, p) => (float)(m * Math.Sin(p))).ToArray(); var fft = new Fft(fftSize); fft.Inverse(real, imag); var kernel = new double[order]; var compensation = 2.0 / fftSize; var middle = order / 2; for (var i = 0; i <= middle; i++) { kernel[i] = real[middle - i] * compensation; kernel[i + middle] = real[i] * compensation; } kernel.ApplyWindow(window); return(kernel); }
/// <summary> /// Phase Vocoder algorithm /// </summary> /// <param name="signal"></param> /// <param name="method"></param> /// <returns></returns> public DiscreteSignal ApplyTo(DiscreteSignal signal, FilteringMethod method = FilteringMethod.Auto) { var input = signal.Samples; var output = new float[(int)(input.Length * _stretch) + _fftSize]; var posSynthesis = 0; for (var posAnalysis = 0; posAnalysis + _fftSize < input.Length; posAnalysis += _hopAnalysis) { input.FastCopyTo(_re, _fftSize, posAnalysis); _zeroblock.FastCopyTo(_im, _fftSize); _re.ApplyWindow(_window); _fft.Direct(_re, _im); for (var j = 0; j < _fftSize / 2 + 1; j++) { var mag = Math.Sqrt(_re[j] * _re[j] + _im[j] * _im[j]); var phase = Math.Atan2(_im[j], _re[j]); var delta = phase - _prevPhase[j]; var deltaUnwrapped = delta - _hopAnalysis * _omega[j]; var deltaWrapped = MathUtils.Mod(deltaUnwrapped + Math.PI, 2 * Math.PI) - Math.PI; var freq = _omega[j] + deltaWrapped / _hopAnalysis; _phaseTotal[j] += _hopSynthesis * freq; _prevPhase[j] = phase; _re[j] = (float)(mag * Math.Cos(_phaseTotal[j])); _im[j] = (float)(mag * Math.Sin(_phaseTotal[j])); } for (var j = _fftSize / 2 + 1; j < _fftSize; j++) { _re[j] = _im[j] = 0.0f; } _fft.Inverse(_re, _im); for (var j = 0; j < _re.Length; j++) { output[posSynthesis + j] += _re[j] * _window[j]; } for (var j = 0; j < _hopSynthesis; j++) { output[posSynthesis + j] *= _gain; } posSynthesis += _hopSynthesis; } for (; posSynthesis < output.Length; posSynthesis++) { output[posSynthesis] *= _gain; } return(new DiscreteSignal(signal.SamplingRate, output)); }
/// <summary> /// Phase Vocoder algorithm /// </summary> /// <param name="signal"></param> /// <param name="filteringOptions"></param> /// <returns></returns> public DiscreteSignal ApplyTo(DiscreteSignal signal, FilteringOptions filteringOptions = FilteringOptions.Auto) { if (_phaseLocking) { return(PhaseLocking(signal)); } var input = signal.Samples; var output = new float[(int)(input.Length * _stretch) + _fftSize]; var windowSum = new float[output.Length]; var re = new float[_fftSize]; var im = new float[_fftSize]; var zeroblock = new float[_fftSize]; var prevPhase = new double[_fftSize / 2 + 1]; var phaseTotal = new double[_fftSize / 2 + 1]; var posSynthesis = 0; for (var posAnalysis = 0; posAnalysis + _fftSize < input.Length; posAnalysis += _hopAnalysis) { input.FastCopyTo(re, _fftSize, posAnalysis); zeroblock.FastCopyTo(im, _fftSize); re.ApplyWindow(_window); _fft.Direct(re, im); for (var j = 0; j < _fftSize / 2 + 1; j++) { var mag = Math.Sqrt(re[j] * re[j] + im[j] * im[j]); var phase = Math.Atan2(im[j], re[j]); var delta = phase - prevPhase[j]; var deltaUnwrapped = delta - _hopAnalysis * _omega[j]; var deltaWrapped = MathUtils.Mod(deltaUnwrapped + Math.PI, 2 * Math.PI) - Math.PI; var freq = _omega[j] + deltaWrapped / _hopAnalysis; phaseTotal[j] += _hopSynthesis * freq; prevPhase[j] = phase; re[j] = (float)(mag * Math.Cos(phaseTotal[j])); im[j] = (float)(mag * Math.Sin(phaseTotal[j])); } for (var j = _fftSize / 2 + 1; j < _fftSize; j++) { re[j] = im[j] = 0.0f; } _fft.Inverse(re, im); for (var j = 0; j < re.Length; j++) { output[posSynthesis + j] += re[j] * _window[j]; windowSum[posSynthesis + j] += _windowSquared[j]; } posSynthesis += _hopSynthesis; } for (var j = 0; j < output.Length; j++) { if (windowSum[j] < 1e-3) { continue; } output[j] /= (windowSum[j] * _fftSize / 2); } return(new DiscreteSignal(signal.SamplingRate, output)); }
public DiscreteSignal ApplyTo(DiscreteSignal signal1, DiscreteSignal signal2, FilteringMethod method = FilteringMethod.Auto) { Guard.AgainstInequality(signal1.SamplingRate, signal2.SamplingRate, "1st signal sampling rate", "2nd signal sampling rate"); var input1 = signal1.Samples; var input2 = signal2.Samples; var output = new float[input1.Length]; var windowSum = new float[output.Length]; var posMorph = 0; var endMorph = signal2.Length - _fftSize; var posSynthesis = 0; for (var posAnalysis = 0; posAnalysis + _fftSize < input1.Length; posAnalysis += _hopSize, posMorph += _hopSize) { input1.FastCopyTo(_re1, _fftSize, posAnalysis); _zeroblock.FastCopyTo(_im1, _fftSize); if (posMorph > endMorph) { posMorph = 0; } input2.FastCopyTo(_re2, _fftSize, posMorph); _zeroblock.FastCopyTo(_im2, _fftSize); _re1.ApplyWindow(_window); _re2.ApplyWindow(_window); _fft.Direct(_re1, _im1); _fft.Direct(_re2, _im2); for (var j = 0; j < _fftSize / 2 + 1; j++) { var mag1 = Math.Sqrt(_re1[j] * _re1[j] + _im1[j] * _im1[j]); var phase1 = Math.Atan2(_im1[j], _re1[j]); var mag2 = Math.Sqrt(_re2[j] * _re2[j] + _im2[j] * _im2[j]); var phase2 = Math.Atan2(_im2[j], _re2[j]); _re1[j] = (float)(mag1 * Math.Cos(phase2)); _im1[j] = (float)(mag1 * Math.Sin(phase2)); } for (var j = _fftSize / 2 + 1; j < _fftSize; j++) { _re1[j] = _im1[j] = 0.0f; } _fft.Inverse(_re1, _im1); for (var j = 0; j < _re1.Length; j++) { output[posSynthesis + j] += _re1[j] * _window[j]; } posSynthesis += _hopSize; } posMorph = 0; for (var j = 0; j < output.Length; j++, posMorph++) { output[j] /= _fftSize; if (posMorph > endMorph) { posMorph = 0; } output[j] = Wet * output[j] + Dry * signal2[posMorph]; } return(new DiscreteSignal(signal1.SamplingRate, output)); }
/// <summary> /// Method implements block convolution of signals (using either OLA or OLS algorithm) /// </summary> /// <param name="signal"></param> /// <param name="kernel"></param> /// <param name="fftSize"></param> /// <param name="method"></param> /// <returns></returns> public static DiscreteSignal BlockConvolve(DiscreteSignal signal, DiscreteSignal kernel, int fftSize, BlockConvolution method = BlockConvolution.OverlapAdd) { var m = kernel.Length; if (m > fftSize) { throw new ArgumentException("Kernel length must not exceed the size of FFT!"); } var fft = new Fft(fftSize); // pre-compute kernel's FFT: var kernelReal = kernel.Samples.PadZeros(fftSize); var kernelImag = new float[fftSize]; fft.Direct(kernelReal, kernelImag); // reserve space for current signal block: var blockReal = new float[fftSize]; var blockImag = new float[fftSize]; var zeroblock = new float[fftSize]; // reserve space for resulting spectrum at each step: var spectrumReal = new float[fftSize]; var spectrumImag = new float[fftSize]; var filtered = new DiscreteSignal(signal.SamplingRate, signal.Length); var hopSize = fftSize - m + 1; if (method == BlockConvolution.OverlapAdd) { var i = 0; while (i < signal.Length) { // ============================== FFT CONVOLUTION SECTION ================================= // for better performance we inline FFT convolution here; // alternatively, we could simply write: // // var res = Convolve(signal[i, i + hopSize], kernel); // // ...but that would require unnecessary memory allocations // and recalculating of kernel FFT at each step. zeroblock.FastCopyTo(blockReal, fftSize); zeroblock.FastCopyTo(blockImag, fftSize); signal.Samples.FastCopyTo(blockReal, Math.Min(hopSize, signal.Length - i), i); // 1) do FFT of a signal block: fft.Direct(blockReal, blockImag); // 2) do complex multiplication of spectra for (var j = 0; j < fftSize; j++) { spectrumReal[j] = (blockReal[j] * kernelReal[j] - blockImag[j] * kernelImag[j]) / fftSize; spectrumImag[j] = (blockReal[j] * kernelImag[j] + blockImag[j] * kernelReal[j]) / fftSize; } // 3) do inverse FFT of resulting spectrum fft.Inverse(spectrumReal, spectrumImag); // ======================================================================================== for (var j = 0; j < m - 1 && i + j < filtered.Length; j++) { filtered[i + j] += spectrumReal[j]; } for (var j = m - 1; j < spectrumReal.Length && i + j < filtered.Length; j++) { filtered[i + j] = spectrumReal[j]; } i += hopSize; } return(filtered); } else { signal = new DiscreteSignal(signal.SamplingRate, m - 1).Concatenate(signal); var i = 0; while (i < signal.Length) { // ============================== FFT CONVOLUTION SECTION ================================= signal.Samples.FastCopyTo(blockReal, Math.Min(fftSize, signal.Length - i), i); zeroblock.FastCopyTo(blockImag, fftSize); // 1) do FFT of a signal block: fft.Direct(blockReal, blockImag); // 2) do complex multiplication of spectra for (var j = 0; j < fftSize; j++) { spectrumReal[j] = (blockReal[j] * kernelReal[j] - blockImag[j] * kernelImag[j]) / fftSize; spectrumImag[j] = (blockReal[j] * kernelImag[j] + blockImag[j] * kernelReal[j]) / fftSize; } // 3) do inverse FFT of resulting spectrum fft.Inverse(spectrumReal, spectrumImag); // ======================================================================================== for (var j = 0; j + m - 1 < spectrumReal.Length && i + j < filtered.Length; j++) { filtered[i + j] = spectrumReal[j + m - 1]; } i += hopSize; } return(filtered); } }
/// <summary> /// Spectral subtraction algorithm according to /// /// [1979] M. Berouti, R. Schwartz, J. Makhoul /// "Enhancement of Speech Corrupted by Acoustic Noise". /// /// </summary> /// <param name="signal"></param> /// <param name="noise"></param> /// <param name="fftSize"></param> /// <param name="hopSize"></param> /// <returns></returns> public static DiscreteSignal SpectralSubtract(DiscreteSignal signal, DiscreteSignal noise, int fftSize = 1024, int hopSize = 410) { var input = signal.Samples; var output = new float[input.Length]; const float beta = 0.009f; const float alphaMin = 2f; const float alphaMax = 5f; const float snrMin = -5f; const float snrMax = 20f; const float k = (alphaMin - alphaMax) / (snrMax - snrMin); const float b = alphaMax - k * snrMin; var fft = new Fft(fftSize); var hannWindow = Window.OfType(WindowTypes.Hann, fftSize); var windowSquared = hannWindow.Select(w => w * w).ToArray(); var windowSum = new float[output.Length]; var re = new float[fftSize]; var im = new float[fftSize]; var zeroblock = new float[fftSize]; // estimate noise power spectrum var noiseAcc = new float[fftSize / 2 + 1]; var noiseEstimate = new float[fftSize / 2 + 1]; var numFrames = 0; var pos = 0; for (; pos + fftSize < noise.Length; pos += hopSize, numFrames++) { noise.Samples.FastCopyTo(re, fftSize, pos); zeroblock.FastCopyTo(im, fftSize); fft.Direct(re, im); for (var j = 0; j <= fftSize / 2; j++) { noiseAcc[j] += re[j] * re[j] + im[j] * im[j]; } } // (including smoothing) for (var j = 1; j < fftSize / 2; j++) { noiseEstimate[j] = (noiseAcc[j - 1] + noiseAcc[j] + noiseAcc[j + 1]) / (3 * numFrames); } noiseEstimate[0] /= numFrames; noiseEstimate[fftSize / 2] /= numFrames; // spectral subtraction for (pos = 0; pos + fftSize < input.Length; pos += hopSize) { input.FastCopyTo(re, fftSize, pos); zeroblock.FastCopyTo(im, fftSize); re.ApplyWindow(hannWindow); fft.Direct(re, im); for (var j = 0; j <= fftSize / 2; j++) { var power = re[j] * re[j] + im[j] * im[j]; var phase = Math.Atan2(im[j], re[j]); var noisePower = noiseEstimate[j]; var snr = 10 * Math.Log10(power / noisePower); var alpha = Math.Max(Math.Min(k * snr + b, alphaMax), alphaMin); var diff = power - alpha * noisePower; var mag = Math.Sqrt(Math.Max(diff, beta * noisePower)); re[j] = (float)(mag * Math.Cos(phase)); im[j] = (float)(mag * Math.Sin(phase)); } for (var j = fftSize / 2 + 1; j < fftSize; j++) { re[j] = im[j] = 0.0f; } fft.Inverse(re, im); for (var j = 0; j < re.Length; j++) { output[pos + j] += re[j] * hannWindow[j]; windowSum[pos + j] += windowSquared[j]; } } for (var j = 0; j < output.Length; j++) { if (windowSum[j] < 1e-3) { continue; } output[j] /= (windowSum[j] * fftSize / 2); } return(new DiscreteSignal(signal.SamplingRate, output)); }
/// <summary> /// Spectral subtraction /// </summary> /// <param name="signal"></param> /// <param name="method"></param> /// <returns></returns> public DiscreteSignal ApplyTo(DiscreteSignal signal, FilteringMethod method = FilteringMethod.Auto) { var input = signal.Samples; var output = new float[input.Length]; const float beta = 0.009f; const float alphaMin = 2f; const float alphaMax = 5f; const float snrMin = -5f; const float snrMax = 20f; const float k = (alphaMin - alphaMax) / (snrMax - snrMin); const float b = alphaMax - k * snrMin; var pos = 0; for (; pos + _fftSize < input.Length; pos += _hopSize) { input.FastCopyTo(_re, _fftSize, pos); _zeroblock.FastCopyTo(_im, _fftSize); _re.ApplyWindow(_window); _fft.Direct(_re, _im); for (var j = 0; j <= _fftSize / 2; j++) { var power = _re[j] * _re[j] + _im[j] * _im[j]; var phase = Math.Atan2(_im[j], _re[j]); var noisePower = _noiseEstimate[j]; var snr = 10 * Math.Log10(power / noisePower); var alpha = Math.Max(Math.Min(k * snr + b, alphaMax), alphaMin); var diff = power - alpha * noisePower; var mag = Math.Sqrt(Math.Max(diff, beta * noisePower)); _re[j] = (float)(mag * Math.Cos(phase)); _im[j] = (float)(mag * Math.Sin(phase)); } for (var j = _fftSize / 2 + 1; j < _fftSize; j++) { _re[j] = _im[j] = 0.0f; } _fft.Inverse(_re, _im); for (var j = 0; j < _re.Length; j++) { output[pos + j] += _re[j] * _window[j]; } for (var j = 0; j < _hopSize; j++) { output[pos + j] *= _gain; } } for (; pos < output.Length; pos++) { output[pos] *= _gain; } return(new DiscreteSignal(signal.SamplingRate, output)); }
/// <summary> /// Phase locking procedure /// </summary> /// <param name="signal"></param> /// <returns></returns> public DiscreteSignal ApplyTo(DiscreteSignal signal, FilteringMethod method = FilteringMethod.Auto) { var input = signal.Samples; var output = new float[(int)(input.Length * _stretch) + _fftSize]; var peakCount = 0; var posSynthesis = 0; for (var posAnalysis = 0; posAnalysis + _fftSize < input.Length; posAnalysis += _hopAnalysis) { input.FastCopyTo(_re, _fftSize, posAnalysis); _zeroblock.FastCopyTo(_im, _fftSize); _re.ApplyWindow(_window); _fft.Direct(_re, _im); for (var j = 0; j < _mag.Length; j++) { _mag[j] = Math.Sqrt(_re[j] * _re[j] + _im[j] * _im[j]); _phase[j] = Math.Atan2(_im[j], _re[j]); } // spectral peaks in magnitude spectrum peakCount = 0; for (var j = 2; j < _mag.Length - 3; j++) { if (_mag[j] <= _mag[j - 1] || _mag[j] <= _mag[j - 2] || _mag[j] <= _mag[j + 1] || _mag[j] <= _mag[j + 2]) { continue; // if not a peak } _peaks[peakCount++] = j; } _peaks[peakCount++] = _mag.Length - 1; // assign phases at peaks to all neighboring frequency bins var leftPos = 0; for (var j = 0; j < peakCount - 1; j++) { var peakPos = _peaks[j]; var peakPhase = _phase[peakPos]; _delta[peakPos] = peakPhase - _prevPhase[peakPos]; var deltaUnwrapped = _delta[peakPos] - _hopAnalysis * _omega[peakPos]; var deltaWrapped = MathUtils.Mod(deltaUnwrapped + Math.PI, 2 * Math.PI) - Math.PI; var freq = _omega[peakPos] + deltaWrapped / _hopAnalysis; _phaseTotal[peakPos] = _phaseTotal[peakPos] + _hopSynthesis * freq; var rightPos = (_peaks[j] + _peaks[j + 1]) / 2; for (var k = leftPos; k < rightPos; k++) { _phaseTotal[k] = _phaseTotal[peakPos] + _phase[k] - _phase[peakPos]; _prevPhase[k] = _phase[k]; _re[k] = (float)(_mag[k] * Math.Cos(_phaseTotal[k])); _im[k] = (float)(_mag[k] * Math.Sin(_phaseTotal[k])); } leftPos = rightPos; } for (var j = _fftSize / 2 + 1; j < _fftSize; j++) { _re[j] = _im[j] = 0.0f; } _fft.Inverse(_re, _im); for (var j = 0; j < _re.Length; j++) { output[posSynthesis + j] += _re[j] * _window[j]; } for (var j = 0; j < _hopSynthesis; j++) { output[posSynthesis + j] *= _gain; } posSynthesis += _hopSynthesis; } for (; posSynthesis < output.Length; posSynthesis++) { output[posSynthesis] *= _gain; } return(new DiscreteSignal(signal.SamplingRate, output)); }
/// <summary> /// Process one frame (block) /// </summary> public void ProcessFrame() { _zeroblock.FastCopyTo(_im, _fftSize); _dl.FastCopyTo(_re, _fftSize); _re.ApplyWindow(_window); _fft.Direct(_re, _im); var nextPhase = (float)(2 * Math.PI * _hopSize / _fftSize); for (var j = 0; j < _fftSize / 2 + 1; j++) { _mag[j] = (float)Math.Sqrt(_re[j] * _re[j] + _im[j] * _im[j]); _phase[j] = (float)Math.Atan2(_im[j], _re[j]); var delta = _phase[j] - _prevPhase[j]; _prevPhase[j] = _phase[j]; delta -= j * nextPhase; var deltaWrapped = MathUtils.Mod(delta + Math.PI, 2 * Math.PI) - Math.PI; _phase[j] = _freqResolution * (j + (float)deltaWrapped / nextPhase); } _zeroblock.FastCopyTo(_re, _fftSize); _zeroblock.FastCopyTo(_im, _fftSize); // "stretch" spectrum: var stretchPos = 0; for (var j = 0; j <= _fftSize / 2 && stretchPos <= _fftSize / 2; j++) { _re[stretchPos] += _mag[j]; _im[stretchPos] = _phase[j] * _shift; stretchPos = (int)(j * _shift); } for (var j = 0; j <= _fftSize / 2; j++) { var mag = _re[j]; var freqIndex = (_im[j] - j * _freqResolution) / _freqResolution; _phaseTotal[j] += nextPhase * (freqIndex + j); _filteredRe[j] = (float)(mag * Math.Cos(_phaseTotal[j])); _filteredIm[j] = (float)(mag * Math.Sin(_phaseTotal[j])); } for (var j = _fftSize / 2 + 1; j < _fftSize; j++) { _filteredRe[j] = _filteredIm[j] = 0.0f; } _fft.Inverse(_filteredRe, _filteredIm); _filteredRe.ApplyWindow(_window); for (var j = 0; j < _overlapSize; j++) { _filteredRe[j] += _lastSaved[j]; } _filteredRe.FastCopyTo(_lastSaved, _overlapSize, _hopSize); for (var i = 0; i < _filteredRe.Length; i++) // Wet / Dry mix { _filteredRe[i] *= Wet * _gain; _filteredRe[i] += _dl[i] * Dry; } _dl.FastCopyTo(_dl, _overlapSize, _hopSize); _inOffset = _overlapSize; _outOffset = 0; }
private void AssertTimeFreq(Complex[] time, Complex[] freq) { Assert.That(fft.Forward(time), Is.EqualTo(freq).Using(Complex.Within(1E-16))); Assert.That(fft.Inverse(freq), Is.EqualTo(time).Using(Complex.Within(1E-16))); }
/// <summary> /// Phase Vocoder algorithm /// </summary> /// <param name="signal"></param> /// <param name="filteringOptions"></param> /// <returns></returns> public DiscreteSignal ApplyTo(DiscreteSignal signal, FilteringOptions filteringOptions = FilteringOptions.Auto) { var stretch = (float)_hopSynthesis / _hopAnalysis; var input = signal.Samples; var output = new float[(int)(input.Length * stretch) + _fftSize]; var fft = new Fft(_fftSize); var hannWindow = Window.OfType(WindowTypes.Hann, _fftSize); var ratio = _fftSize / (2.0f * _hopAnalysis); var norm = 4.0f / (_fftSize * ratio); var omega = Enumerable.Range(0, _fftSize / 2 + 1) .Select(f => 2 * Math.PI * f / _fftSize) .ToArray(); var re = new float[_fftSize]; var im = new float[_fftSize]; var zeroblock = new float[_fftSize]; var prevPhase = new double[_fftSize / 2 + 1]; var phaseTotal = new double[_fftSize / 2 + 1]; var posSynthesis = 0; for (var posAnalysis = 0; posAnalysis + _fftSize < input.Length; posAnalysis += _hopAnalysis) { input.FastCopyTo(re, _fftSize, posAnalysis); zeroblock.FastCopyTo(im, _fftSize); re.ApplyWindow(hannWindow); fft.Direct(re, im); for (var j = 0; j < _fftSize / 2 + 1; j++) { var mag = Math.Sqrt(re[j] * re[j] + im[j] * im[j]); var phase = Math.Atan2(im[j], re[j]); var delta = phase - prevPhase[j]; var deltaUnwrapped = delta - _hopAnalysis * omega[j]; var deltaWrapped = MathUtils.Mod(deltaUnwrapped + Math.PI, 2 * Math.PI) - Math.PI; var freq = omega[j] + deltaWrapped / _hopAnalysis; phaseTotal[j] += _hopSynthesis * freq; prevPhase[j] = phase; re[j] = (float)(mag * Math.Cos(phaseTotal[j])); im[j] = (float)(mag * Math.Sin(phaseTotal[j])); } for (var j = _fftSize / 2 + 1; j < _fftSize; j++) { re[j] = im[j] = 0.0f; } fft.Inverse(re, im); for (var j = 0; j < re.Length; j++) { output[posSynthesis + j] += re[j] * hannWindow[j] * norm; } posSynthesis += _hopSynthesis; } return(new DiscreteSignal(signal.SamplingRate, output)); }