void ProcessCommon(AudioReader reader, ref float[] impulse) { int targetLen = QMath.Base2Ceil((int)reader.Length) << 1; Complex[] commonSpectrum = new Complex[targetLen]; FFTCache cache = new FFTCache(targetLen); for (int ch = 0; ch < reader.ChannelCount; ++ch) { float[] channel = new float[targetLen]; WaveformUtils.ExtractChannel(impulse, channel, ch, reader.ChannelCount); Complex[] spectrum = Measurements.FFT(channel, cache); for (int band = 0; band < spectrum.Length; ++band) { commonSpectrum[band] += spectrum[band]; } } float mul = 1f / reader.ChannelCount; for (int band = 0; band < commonSpectrum.Length; ++band) { commonSpectrum[band] = (commonSpectrum[band] * mul).Invert(); } Array.Resize(ref impulse, impulse.Length << 1); for (int ch = 0; ch < reader.ChannelCount; ++ch) { Convolver filter = GetFilter(commonSpectrum, 1, reader.SampleRate); filter.Process(impulse, ch, reader.ChannelCount); } }
/// <summary> /// Gets a zero-delay convolution filter with minimally sacrificed phase that results in this EQ when applied. /// </summary> /// <param name="eq">Source <see cref="Equalizer"/></param> /// <param name="sampleRate">Sample rate of the target system the convolution filter could be used on</param> /// <param name="length">Length of the convolution filter in samples, must be a power of 2</param> /// <param name="gain">Signal voltage multiplier</param> /// <param name="initial">Custom initial spectrum to apply the EQ on - phases will be corrected, this is not convolved, /// and has to be twice the size of <paramref name="length"/></param> public static float[] GetConvolution(this Equalizer eq, int sampleRate, int length = 1024, float gain = 1, Complex[] initial = null) { length <<= 1; Complex[] filter = new Complex[length]; if (initial == null) { for (int i = 0; i < length; ++i) { filter[i].Real = gain; // FFT of DiracDelta(x) } } else { for (int i = 0; i < length; ++i) { filter[i].Real = initial[i].Magnitude * gain; } } eq.Apply(filter, sampleRate); using (FFTCache cache = new FFTCache(length)) { Measurements.MinimumPhaseSpectrum(filter, cache); filter.InPlaceIFFT(cache); } return(Measurements.GetRealPartHalf(filter)); }
void ProcessPerChannel(AudioReader reader, ref float[] impulse) { int targetLen = QMath.Base2Ceil((int)reader.Length) << 1; Convolver[] filters = new Convolver[reader.ChannelCount]; FFTCache cache = new FFTCache(targetLen); for (int ch = 0; ch < reader.ChannelCount; ++ch) { float[] channel = new float[targetLen]; WaveformUtils.ExtractChannel(impulse, channel, ch, reader.ChannelCount); Complex[] spectrum = Measurements.FFT(channel, cache); for (int band = 0; band < spectrum.Length; ++band) { spectrum[band] = spectrum[band].Invert(); } filters[ch] = GetFilter(spectrum, WaveformUtils.GetRMS(channel), reader.SampleRate); } Array.Resize(ref impulse, impulse.Length << 1); for (int ch = 0; ch < reader.ChannelCount; ++ch) { filters[ch].Process(impulse, ch, reader.ChannelCount); } }
/// <summary> /// Performs the convolution of two real signals. The FFT of the result is returned. /// </summary> /// <remarks>Requires <paramref name="excitation"/> and <paramref name="impulse"/> /// to match in a length of a power of 2.</remarks> public static Complex[] ConvolveFourier(float[] excitation, float[] impulse) { using FFTCache cache = new FFTCache(excitation.Length); Complex[] excitationFFT = excitation.FFT(cache); excitationFFT.Convolve(impulse.FFT(cache)); return(excitationFFT); }
/// <summary> /// Performs the convolution of two real signals. The real result is returned. /// </summary> /// <remarks>Requires <paramref name="excitation"/> and <paramref name="impulse"/> /// to match in a length of a power of 2.</remarks> public static float[] Convolve(float[] excitation, float[] impulse, FFTCache cache = null) { Complex[] result = cache != null? ConvolveFourier(excitation, impulse, cache) : ConvolveFourier(excitation, impulse); result.InPlaceIFFT(); return(Measurements.GetRealPart(result)); }
/// <summary> /// Constructs an optimized convolution. /// </summary> public FastConvolver(float[] impulse, int delay = 0) { int fftSize = 2 << QMath.Log2Ceil(impulse.Length); // Zero padding for the falloff to have space cache = new FFTCache(fftSize); filter = new Complex[fftSize]; for (int sample = 0; sample < impulse.Length; ++sample) { filter[sample].Real = impulse[sample]; } filter.InPlaceFFT(cache); present = new Complex[fftSize]; future = new float[fftSize + delay]; this.delay = delay; }
/// <summary> /// Constructs an optimized convolution. /// </summary> public DualConvolver(float[] impulse1, float[] impulse2, int delay1 = 0, int delay2 = 0) { int impulseLength = Math.Max(impulse1.Length, impulse2.Length); int fftSize = 2 << QMath.Log2Ceil(impulseLength); // Zero padding for the falloff to have space cache = new FFTCache(fftSize); filter = new Complex[fftSize]; for (int sample = 0; sample < impulse1.Length; ++sample) { filter[sample].Real = impulse1[sample]; } for (int sample = 0; sample < impulse2.Length; ++sample) { filter[sample].Imaginary = impulse2[sample]; } filter.InPlaceFFT(cache); present = new Complex[fftSize]; future = new Complex[fftSize + Math.Max(delay1, delay2)]; this.delay1 = delay1; this.delay2 = delay2; }
private void LoadFiles(object sender, RoutedEventArgs e) { AudioReader responseReader = Import("Response.wav"); if (responseReader == null) { return; } AudioReader impulseReader = Import("Impulse.wav"); if (impulseReader == null) { return; } float[] response = responseReader.Read(), impulse = impulseReader.Read(); if (responseReader.SampleRate != impulseReader.SampleRate) { Error("The sample rate of the two clips don't match."); return; } int responseChannels = responseReader.ChannelCount, impulseChannels = impulseReader.ChannelCount; if (impulseChannels != 1 && impulseChannels != responseChannels) { Error("The channel count of the two clips don't match. A single-channel impulse is also acceptable."); return; } int fftSize = Math.Max( QMath.Base2Ceil((int)responseReader.Length), QMath.Base2Ceil((int)impulseReader.Length) ); if (padding.IsChecked.Value) { Array.Resize(ref response, fftSize + response.Length); Array.Copy(response, 0, response, fftSize, response.Length - fftSize); Array.Clear(response, 0, fftSize); fftSize = Math.Max(fftSize, QMath.Base2Ceil(response.Length)); } Complex[] impulseFFT = new Complex[fftSize], responseFFT = new Complex[fftSize]; FFTCache cache = new FFTCache(fftSize); float[] responseChannel = new float[response.Length / responseChannels]; for (int channel = 0; channel < responseChannels; ++channel) { if (channel < impulseChannels) // After the channel count check this runs once or for each channel { float[] impulseChannel = impulse; if (impulseChannels != 1) { impulseChannel = new float[impulseReader.Length]; WaveformUtils.ExtractChannel(impulse, impulseChannel, channel, impulseChannels); Array.Clear(impulseFFT, 0, fftSize); } for (int sample = 0; sample < impulseChannel.Length; ++sample) { impulseFFT[sample].Real = impulseChannel[sample]; } Measurements.InPlaceFFT(impulseFFT, cache); } if (responseChannels == 1) { responseChannel = response; } else { WaveformUtils.ExtractChannel(response, responseChannel, channel, responseChannels); } if (channel != 1) { Array.Clear(responseFFT, 0, fftSize); } for (int sample = 0; sample < responseChannel.Length; ++sample) { responseFFT[sample].Real = responseChannel[sample]; } Measurements.InPlaceFFT(responseFFT, cache); for (int sample = 0; sample < fftSize; ++sample) { responseFFT[sample].Divide(impulseFFT[sample]); } Measurements.InPlaceIFFT(responseFFT, cache); for (int i = 0, channels = responseChannels; i < responseChannel.Length; ++i) { response[channels * i + channel] = responseFFT[i].Real; } } exporter.FileName = "Deconvolved.wav"; if (exporter.ShowDialog().Value) { BinaryWriter handler = new BinaryWriter(File.OpenWrite(exporter.FileName)); using RIFFWaveWriter writer = new RIFFWaveWriter(handler, responseChannels, responseReader.Length, responseReader.SampleRate, responseReader.Bits); writer.Write(response); } }
/// <summary> /// Performs the convolution of two real signals. The FFT of the result is returned. /// </summary> /// <remarks>Requires <paramref name="excitation"/> and <paramref name="impulse"/> /// to match in a length of a power of 2.</remarks> public static Complex[] ConvolveFourier(float[] excitation, float[] impulse, FFTCache cache) { Complex[] excitationFFT = excitation.FFT(cache); excitationFFT.Convolve(impulse.FFT(cache)); return(excitationFFT); }