public static IEnumerable<float[]> DownSample(IEnumerable<float[]> samplesEnumerator, int numChannels, int windowSize, Action<float[]> highSampleCallback) { var fft = new MathNet.Numerics.Transformations.ComplexFourierTransformation (); var determineVolumeSource = new Complex[windowSize]; for (var sampleCtr = 0; sampleCtr < windowSize; sampleCtr++) determineVolumeSource[sampleCtr] = new Complex(1, 0); fft.TransformForward(determineVolumeSource); var determineVolumeDestination = new Complex[] { determineVolumeSource[0], Complex.FromModulusArgument(0,0), Complex.FromModulusArgument(0,0), Complex.FromModulusArgument(0,0) }; fft.TransformBackward(determineVolumeDestination); var dcMultiplier = determineVolumeDestination[0].Modulus; var ratio = (1d / Convert.ToDouble(windowSize)) * 2d * Math.PI; for (var sampleCtr = 0; sampleCtr < windowSize; sampleCtr++) { var sampleCtrDouble = Convert.ToDouble(sampleCtr); determineVolumeSource[sampleCtr] = new Complex(Math.Cos(sampleCtrDouble * ratio), 0); } fft.TransformForward(determineVolumeSource); determineVolumeDestination = new Complex[] { Complex.FromModulusArgument(0,0), determineVolumeSource[1], Complex.FromModulusArgument(0,0), determineVolumeSource[determineVolumeSource.Length - 1], }; fft.TransformBackward(determineVolumeDestination); var midMultiplier = determineVolumeDestination.Select(s => s.Modulus).Max(); /*// This only works when windowsize is 8 for (var sampleCtr = 0; sampleCtr < windowSize; sampleCtr++) { var abs = sampleCtr % 2 == 1 ? 1 : 0; var sign = sampleCtr % 4 > 1 ? 1 : -1; determineVolumeSource[sampleCtr] = new Complex(abs * sign, 0); }*/ ratio = (1d / Convert.ToDouble(windowSize)) * 4d * Math.PI; for (var sampleCtr = 0; sampleCtr < windowSize; sampleCtr++) { var sampleCtrDouble = Convert.ToDouble(sampleCtr); determineVolumeSource[sampleCtr] = new Complex(Math.Cos(sampleCtrDouble * ratio), 0); } fft.TransformForward(determineVolumeSource); determineVolumeDestination = new Complex[] { Complex.FromModulusArgument(0,0), Complex.FromModulusArgument(0,0), determineVolumeSource[2], Complex.FromModulusArgument(0,0), }; fft.TransformBackward(determineVolumeDestination); var highMultiplier = determineVolumeDestination.Select(s => s.Modulus).Max(); var sampleQueue = new Queue<float[]>(); for (var sampleCtr = 0; sampleCtr < windowSize / 4; sampleCtr++) sampleQueue.Enqueue(new float[numChannels]); var maxOriginalSample = 0.0; var maxLowFrequencySample = 0.0; using (var sampleEnumerator = samplesEnumerator.GetEnumerator()) { int samplesFromSource; do { samplesFromSource = 0; while (sampleQueue.Count < windowSize) { if (sampleEnumerator.MoveNext()) { sampleQueue.Enqueue(sampleEnumerator.Current.ToArray()); samplesFromSource++; } else sampleQueue.Enqueue(new float[numChannels]); } var simplified = new float[][] { new float[numChannels], new float[numChannels] }; var filtered = new float[windowSize / 2][]; for (var sampleCtr = 0; sampleCtr < windowSize / 2; sampleCtr++) filtered[sampleCtr] = new float[numChannels]; for (var channelCtr = 0; channelCtr < numChannels; channelCtr++) { maxOriginalSample = Math.Max( maxOriginalSample, sampleQueue.Select(s => Math.Abs(s[channelCtr])).Max()); var samplesToTransform = sampleQueue.Select (s => new Complex (s [channelCtr], 0)).ToArray (); fft.TransformForward (samplesToTransform); var samplesToTransformBack = new Complex[] { samplesToTransform[0], samplesToTransform[1], samplesToTransform[2], samplesToTransform[samplesToTransform.Length - 1] }; samplesToTransformBack[0].Modulus /= dcMultiplier; samplesToTransformBack[1].Modulus /= midMultiplier; samplesToTransformBack[2].Modulus /= highMultiplier; samplesToTransformBack[3].Modulus /= midMultiplier; fft.TransformBackward(samplesToTransformBack); simplified[0][channelCtr] = Convert.ToSingle(samplesToTransformBack[1].Real); simplified[1][channelCtr] = Convert.ToSingle(samplesToTransformBack[2].Real); for (var filterCtr = 3; filterCtr < (samplesToTransform.Length - 1); filterCtr++) samplesToTransform[filterCtr] = Complex.FromModulusArgument(0, 0); fft.TransformBackward(samplesToTransform); for (var sampleCtr = 0; sampleCtr < windowSize / 2; sampleCtr++) filtered[sampleCtr][channelCtr] = Convert.ToSingle(samplesToTransform[sampleCtr + (windowSize / 4)].Real); } maxLowFrequencySample = Math.Max( maxLowFrequencySample, simplified.SelectMany(s => s).Select(Math.Abs).Max()); foreach (var samples in simplified) yield return samples; foreach (var samples in filtered) highSampleCallback(samples); while (sampleQueue.Count > windowSize / 2) sampleQueue.Dequeue(); } while (samplesFromSource > (windowSize / 4)); } /*Console.WriteLine("Loudest sample in source: {0}", maxOriginalSample); Console.WriteLine("Loudest sample in low frequency: {0}", maxLowFrequencySample); //Console.WriteLine("Loudest sample in high frequency: {0}", maxHighFrequencySample); Console.WriteLine();*/ }
private static TableInfo ConvertTable(float[][] wavetable, Dictionary<int, int> noteToPartials) { var partials = noteToPartials.Select(x => x.Value).Distinct().OrderByDescending(x => x).ToList(); var transform = new Transform(wavetable.First().Length); var output = new TableInfo(); output.MidiToTable = new int[128]; output.RenderedTable = new float[partials.Count][][]; Action<int, Complex[]> LimitPartials = (max, data) => { for (int i = 0; i < data.Length; i++) { if (i > max && (data.Length - i) > max) { data[i].Real = 0; data[i].Imag = 0; } } }; for (int partialIndex = 0; partialIndex < partials.Count; partialIndex++) { var partial = partials[partialIndex]; noteToPartials.Where(x => x.Value == partial).Select(x => x.Key).Foreach(x => output.MidiToTable[x] = partialIndex); output.RenderedTable[partialIndex] = new float[wavetable.Length][]; for (int tableIndex = 0; tableIndex < wavetable.Length; tableIndex++) { var table = wavetable[tableIndex]; var input = table.Select(x => new Complex(x, 0)).ToArray(); var fft = new Complex[input.Length]; var ifft = new Complex[input.Length]; transform.FFT(input, fft); LimitPartials(partial, fft); transform.IFFT(fft, ifft); var signal = ifft.Select(x => (float)x.Real).ToArray(); output.RenderedTable[partialIndex][tableIndex] = signal; } } return output; }