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); } }
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> /// Convert a float array to complex a size that's ready for FFT. /// </summary> public static Complex[] ParseForFFT(this float[] source) { Complex[] result = new Complex[QMath.Base2Ceil(source.Length)]; for (int i = 0; i < source.Length; ++i) { result[i].Real = source[i]; } return(result); }
void ProcessImpulse(object sender, RoutedEventArgs e) { if (browser.ShowDialog().Value) { AudioReader reader = AudioReader.Open(browser.FileName); float[] impulse = reader.Read(); float gain = 1; if (keepGain.IsChecked.Value) { gain = WaveformUtils.GetPeak(impulse); } if (commonEQ.IsChecked.Value) { ProcessCommon(reader, ref impulse); } else { ProcessPerChannel(reader, ref impulse); } if (keepGain.IsChecked.Value) { WaveformUtils.Gain(impulse, gain / WaveformUtils.GetPeak(impulse)); } BitDepth bits = reader.Bits; if (forceFloat.IsChecked.Value) { bits = BitDepth.Float32; } int targetLen = QMath.Base2Ceil((int)reader.Length); if (separateExport.IsChecked.Value) { ReferenceChannel[] channels = ChannelPrototype.GetStandardMatrix(reader.ChannelCount); for (int ch = 0; ch < reader.ChannelCount; ++ch) { string exportName = Path.GetFileName(browser.FileName); int idx = exportName.LastIndexOf('.'); string channelName = ChannelPrototype.Mapping[(int)channels[ch]].Name; exporter.FileName = $"{exportName[..idx]} - {channelName}{exportName[idx..]}";
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); } }