private void ProcessorFrequencySkew(SpectrumStage stage, Strengths strengths) { var scaler = stage.FrequencySkewTransformed; var pinToTop = stage.PinToHighFrequency; var magnitude = fftSignal.Select(x => x.Abs).ToArray(); for (int i = strengths.FMin; i <= strengths.FMax; i++) { double k; if (pinToTop) { k = strengths.FMax - (strengths.FMax - i) * scaler; if (k < strengths.FMin) { k = strengths.FMin; } } else { k = (i - strengths.FMin) * scaler + strengths.FMin; if (k > strengths.FMax) { k = strengths.FMax; } } var interpolated = AudioLib.Interpolate.Spline(k, magnitude, false); fftSignal[i].Abs = interpolated; } }
private void ProcessApply(SpectrumStage stage, Strengths strengths, Dictionary <ImpulseConfig, Complex[]> stageOutputs) { if (stage.SelectedApplySource == null) { return; } if (stageOutputs == null) { return; } var idx = stage.SelectedApplySource.Index; if (idx < 0 || idx >= config.Index) // can only apply things with lower index, because otherwise they haven't been computed yet! { return; } var applyFftSignal = stageOutputs.SingleOrDefault(x => x.Key.Index == idx); if (applyFftSignal.Value == null) { return; // Probably should never happen! } for (int i = 1; i < fftSignal.Length / 2; i++) { var stren = strengths.Strength[i]; fftSignal[i] *= (1 - (Complex)stren) + ((Complex)stren * applyFftSignal.Value[i]); fftSignal[fftSignal.Length - i] *= (1 - (Complex)stren) + ((Complex)stren * applyFftSignal.Value[fftSignal.Length - i]); } }
private void ProcessGain(SpectrumStage stage, Strengths strengths) { for (int i = strengths.FMin; i <= strengths.FMax; i++) { var amount = AudioLib.Utils.DB2gain(stage.GainTransformed * strengths.Strength[i]); fftSignal[i] *= (Complex)amount; fftSignal[fftSignal.Length - i] *= (Complex)amount; } }
private void ProcessMinimumPhase(SpectrumStage stage, Strengths strengths) { // https://dsp.stackexchange.com/questions/7872/derive-minimum-phase-from-magnitude // https://ccrma.stanford.edu/~jos/sasp/Minimum_Phase_Filter_Design.html // https://uk.mathworks.com/matlabcentral/newsreader/view_thread/17748 // https://stackoverflow.com/questions/11942855/matlab-hilbert-transform-in-c /* Matlab example that achieves what we want: * t=-15:0.25:15; * x = pi*t+j*1e-9; * h = real(sin(x)./x); % simple linear phase FIR * H = fft(h,128); * reaLogH = log(abs(H)); * hilb = hilbert(reaLogH); * H2 = exp(hilb); * hMinPhase = real(fliplr(ifft(H2))); * hMinPhase = hMinPhase(1:length(h)); * * figure(1) * plot(t,h,t,hMinPhase) * xlabel('time') * ylabel('Amplitude') */ // hilbert function is the only problematic one, but I've translated that over to C# // This is still pretty much numbers voodoo to me, could someone in the f*****g field write up a pragmatic description // of just what the f**k the hilbert transform does without using pages and pages of obscure math? // f**k I hate academics... if (!stage.MinimumPhase) { return; } var H = fftSignal; var reaLogH = new double[H.Length]; for (int i = 0; i < fftSignal.Length; i++) { reaLogH[i] = Math.Log(H[i].Abs); } var hilb = Hilbert(reaLogH); var H2 = hilb // translate over to System.Numerics as I don't have a Complex.Exp(Complex) function in my library... fail on me .Select(x => new System.Numerics.Complex(x.Real, x.Imag)) .Select(x => System.Numerics.Complex.Exp(x)) .Select(x => new Complex(x.Real, x.Imaginary)) .ToArray(); for (int i = 0; i < fftSignal.Length; i++) { fftSignal[i] = H2[i]; } }
private void ProcessDelay(SpectrumStage stage, Strengths strengths) { var delaySamples = stage.DelayMillisTransformed / 1000.0 * samplerate; for (int i = strengths.FMin; i <= strengths.FMax; i++) { var amount = delaySamples / (double)ImpulseConfig.MaxSampleLength; amount = amount * strengths.Strength[i]; var newVal = fftSignal[i] * Complex.CExp(-2 * Math.PI * i * amount); fftSignal[i] = newVal; fftSignal[fftSignal.Length - i].Arg = -newVal.Arg; } }
private void ProcessRandomGain(SpectrumStage stage, Strengths strengths) { var rand = new Random(stage.RandomGainSeedTransformed); for (int i = 0; i < stage.RandomGainShiftTransformed; i++) { rand.NextDouble(); // pop off x number of samples, "shifting" the sequence forward } var filterCount = stage.RandomGainFilteringTransformed; var gainAmount = stage.RandomGainAmountTransformed; var randCount = strengths.Strength.Length + 2 * filterCount; var mode = stage.RandomGainModeTransformed; var skew = stage.RandomSkewAmountTransformed; var noise = Enumerable.Range(0, randCount).Select(x => rand.NextDouble() * 2 - 1).ToArray(); var filteredNoise = new double[strengths.Strength.Length]; for (int i = filterCount; i < noise.Length - filterCount; i++) { var sum = 0.0; for (int j = -filterCount; j <= filterCount; j++) { var idx = i + j; sum += noise[idx]; } filteredNoise[i - filterCount] = sum / Math.Sqrt(2 * filterCount + 1); } for (int i = strengths.FMin; i <= strengths.FMax; i++) { var skewedNoise = Math.Pow(Math.Abs(filteredNoise[i]), skew) * Math.Sign(filteredNoise[i]); var gf = gainAmount * skewedNoise; var dbGain = gf * strengths.Strength[i]; var scaler = AudioLib.Utils.DB2gain(dbGain); if (mode == ApplyMode.Amplify && scaler < 1) { scaler = 1; } if (mode == ApplyMode.Reduce && scaler > 1) { scaler = 1; } fftSignal[i] *= (Complex)scaler; fftSignal[fftSignal.Length - i] *= (Complex)scaler; } }
public void ProcessStage(SpectrumStage stage, Dictionary <ImpulseConfig, Complex[]> impulseConfigOutputs) { lock (locker) { if (!stage.IsEnabled) { return; } var strengths = GetStrengths(stage); ProcessApply(stage, strengths, impulseConfigOutputs); ProcessGain(stage, strengths); ProcessGainVariation(stage, strengths); ProcessRandomGain(stage, strengths); ProcessorFrequencySkew(stage, strengths); ProcessMinimumPhase(stage, strengths); ProcessDelay(stage, strengths); ProcessPhaseBands(stage, strengths); } }
private void ProcessPhaseBands(SpectrumStage stage, Strengths strengths) { var bands = stage.PhaseBandsTransformed; var shift = stage.PhaseBandFreqShiftTransformed; var sections = GetBands(bands, shift, samplerate, fftSignal.Length); var rand = new Random(stage.PhaseBandSeedTransformed); int pb = 0; var delaySamplesMain = stage.PhaseBandDelayMillisTransformed / 1000 * samplerate; foreach (var section in sections) { var delaySamples = rand.NextDouble() * delaySamplesMain; var k = pb / (double)(sections.Count - 1); var amountOfTracking = Math.Abs(stage.PhaseBandFreqTrackTransformed); if (stage.PhaseBandFreqTrackTransformed < 0) { k = 1 - k; var track = k * amountOfTracking + (1 - amountOfTracking); delaySamples *= track; } else { var track = k * amountOfTracking + (1 - amountOfTracking); delaySamples *= track; } var amount = delaySamples / (double)ImpulseConfig.MaxSampleLength; for (int i = section[0]; i <= section[1]; i++) { amount = amount * strengths.Strength[i]; var newVal = fftSignal[i] * Complex.CExp(-2 * Math.PI * i * amount); fftSignal[i] = newVal; fftSignal[fftSignal.Length - i].Arg = -newVal.Arg; } pb++; } }
private Strengths GetStrengths(SpectrumStage stage) { var nyquist = samplerate / 2; var absMaxIndex = fftSignal.Length / 2; var minFreq = stage.MinFreqTransformed; var maxFreq = stage.MaxFreqTransformed; // slight hack, because we used a fixed frequency range, we don't pin it relative to the sampling frequency, then the absolute highest frequency // that can be selected is 22Khz (set in the MaxFreqTransformed getter) - IF this frequency is used, we stretch it up so that to the nyquist frequency // to prevent a jump at 22Khz when using high sampling frequencies if (Math.Abs(stage.MinFreqTransformed - ImpulseConfig.MaxFrequency) < 0.01) { minFreq = nyquist; } if (Math.Abs(stage.MaxFreqTransformed - ImpulseConfig.MaxFrequency) < 0.01) { maxFreq = nyquist; } var fMin = Math.Round(minFreq / (double)nyquist * absMaxIndex); var fMax = Math.Round(maxFreq / (double)nyquist * absMaxIndex); if (minFreq >= maxFreq) { return new Strengths { FMax = 1, FMin = 1, Strength = new double[absMaxIndex + 1] } } ; if (fMin < 1) { fMin = 1; } if (fMax < 1) { fMax = 1; } if (fMin >= absMaxIndex) { fMin = absMaxIndex; } if (fMax >= absMaxIndex) { fMax = absMaxIndex; } var fBlendMin = Math.Round(Math.Pow(2, stage.LowBlendOctsTransformed) * minFreq / (double)nyquist * absMaxIndex); var fBlendMax = Math.Round(Math.Pow(2, -stage.HighBlendOctsTransformed) * maxFreq / (double)nyquist * absMaxIndex); if (fBlendMin < 1) { fBlendMin = 1; } if (fBlendMax < 1) { fBlendMax = 1; } if (fBlendMin >= absMaxIndex) { fBlendMin = absMaxIndex; } if (fBlendMax >= absMaxIndex) { fBlendMax = absMaxIndex; } var blendIn = new double[absMaxIndex + 1]; for (int i = (int)fMin; i <= (int)fMax; i++) { var octaveDistance = (i - fMin) / (fBlendMin - fMin) * stage.LowBlendOctsTransformed; var value = (Math.Log(1 + octaveDistance, 2)) / (Math.Log(1 + stage.LowBlendOctsTransformed, 2)); if (fBlendMin == fMin) { blendIn[i] = 1.0; } else if (i <= fBlendMin) { blendIn[i] = value; } else { blendIn[i] = 1.0; } } var blendOut = new double[absMaxIndex + 1]; for (int i = (int)fMin; i <= (int)fMax; i++) { var octaveDistance = (i - fBlendMax) / (fMax - fBlendMax) * stage.HighBlendOctsTransformed; var value = (Math.Log(1 + octaveDistance)) / (Math.Log(1 + stage.HighBlendOctsTransformed)); if (fMax == fBlendMax) { blendOut[i] = 0.0; } else if (i <= fBlendMax) { blendOut[i] = 0; } else { blendOut[i] = value; } } // mix the two together so that out cuts against in for (int i = 0; i < blendIn.Length; i++) { blendIn[i] = blendIn[i] - blendOut[i]; if (blendIn[i] < 0) { blendIn[i] = 0; } } return(new Strengths { FMin = (int)fMin, FMax = (int)fMax, Strength = blendIn }); }
private void ProcessGainVariation(SpectrumStage stage, Strengths strengths) { var absMaxIndex = fftSignal.Length / 2; var gain = new double[strengths.Strength.Length]; var amt = stage.GainSmoothingAmountTransformed; var octavesToSmooth = stage.GainSmoothingOctavesTransformed; var hzPerPartial = 1 / (double)absMaxIndex * samplerate / 2; var mode = stage.GainSmoothingModeTransformed; for (int i = 1; i <= fftSignal.Length / 2; i++) { var freq = i * hzPerPartial; var lowFreq = freq * Math.Pow(2, -octavesToSmooth); var highFreq = freq * Math.Pow(2, octavesToSmooth); var iBelow = (freq - lowFreq) / hzPerPartial; var iAbove = (highFreq - freq) / hzPerPartial; var avgSum = 0.0; int count = 0; for (int j = -(int)Math.Round(iBelow); j <= Math.Round(iAbove); j++) { var idx = i + j; if (idx <= 0) { continue; } if (idx > absMaxIndex) { continue; } count++; var sample = fftSignal[idx].Abs; avgSum += sample; } avgSum /= count; var avgDb = AudioLib.Utils.Gain2DB(avgSum); var partialDb = AudioLib.Utils.Gain2DB(fftSignal[i].Abs); var diffDb = partialDb - avgDb; var stren = strengths.Strength[i]; var newMagDb = avgDb + diffDb * (amt * stren + 1 * (1 - stren)); var newGain = AudioLib.Utils.DB2gain(newMagDb) / fftSignal[i].Abs; gain[i] = newGain; if (mode == ApplyMode.Amplify && newGain < 1) { gain[i] = 1; } else if (mode == ApplyMode.Reduce && newGain > 1) { gain[i] = 1; } } for (int i = strengths.FMin; i <= strengths.FMax; i++) { var g = gain[i]; fftSignal[i] *= (Complex)g; fftSignal[fftSignal.Length - i] *= (Complex)g; } }