/// <summary> /// Simulates a realtime audio stream by generating a sine wave in realtime and ingesting it into the FIFO buffer. /// </summary> private static void StartSineWaveRealtimeGenerator(AudioProperties audioProperties, IAudioWriterStream targetStream) { // Create a stream that generates 1 second of a sine wave var sineWaveStream = new SineGeneratorStream(audioProperties.SampleRate, 440, new TimeSpan(0, 0, 1)); // Store the sine wave in a buffer // We can concatenate this buffer over and over again to create an infinitely long sine wave var sineWaveBuffer = new byte[sineWaveStream.Length]; var bytesRead = sineWaveStream.Read(sineWaveBuffer, 0, sineWaveBuffer.Length); if (bytesRead < sineWaveBuffer.Length) { throw new Exception("incomplete buffer read"); } Task.Factory.StartNew(() => { // Each realtime second, write the 1-second sine wave to the target stream to // simulate an infinitely long realtime sine wave stream. // // For low-latency processing use-cases, writes would ideally be shorter and happen // more frequently to keep the delay between input and output of the FIFO stream // as low as possible. while (true) { Thread.Sleep(1000); Console.WriteLine("Writing 1 second into buffer"); targetStream.Write(sineWaveBuffer, 0, sineWaveBuffer.Length); _dataGenerated += sineWaveBuffer.Length; } }); }
public void MonoOverlapAddRectangle() { var properties = new AudioProperties(1, 44100, 32, AudioFormat.IEEE); var sourceStream = new SineGeneratorStream(44100, 440, TimeSpan.FromSeconds(1)); var targetStream = new MemoryWriterStream(new System.IO.MemoryStream(), properties); // Rectangular window (samples unchanged) with no overlap should just reconstruct the original stream int windowSize = 100; int hopSize = 100; var window = WindowType.Rectangle; var sw = new StreamWindower(sourceStream, windowSize, hopSize, window); var ola = new OLA(targetStream, windowSize, hopSize); var frameBuffer = new float[windowSize]; while (sw.HasNext()) { sw.ReadFrame(frameBuffer); ola.WriteFrame(frameBuffer); } ola.Flush(); Assert.AreEqual(sourceStream.Length, targetStream.Length); // Validate ola target stream content sourceStream.Position = targetStream.Position = 0; long similarFloats = StreamUtil.CompareFloats(sourceStream, targetStream); Assert.AreEqual(sourceStream.Length / sourceStream.SampleBlockSize, similarFloats); }
public void CheckMultiSourceRandomSeeking() { var stream1 = new SineGeneratorStream(44100, 440, new TimeSpan(0, 0, 1)); var stream2 = new SineGeneratorStream(44100, 440, new TimeSpan(0, 0, 1)); var cc = new ConcatenationStream(stream1, stream2); int seekOffset = 200 * stream1.SampleBlockSize; // Check seek into first segment cc.Position = seekOffset; Assert.AreEqual(seekOffset, cc.Position); var bytesRead = StreamUtil.ReadAllAndCount(cc); Assert.AreEqual(cc.Length - seekOffset, bytesRead); // Check seek into second segment cc.Position = stream1.Length + seekOffset; Assert.AreEqual(stream1.Length + seekOffset, cc.Position); bytesRead = StreamUtil.ReadAllAndCount(cc); Assert.AreEqual(cc.Length - stream1.Length - seekOffset, bytesRead); // Check seek back into first segment cc.Position = seekOffset; Assert.AreEqual(seekOffset, cc.Position); bytesRead = StreamUtil.ReadAllAndCount(cc); Assert.AreEqual(cc.Length - seekOffset, bytesRead); }
public void CheckMultiSourceSeekToEnd() { var stream1 = new SineGeneratorStream(44100, 440, new TimeSpan(0, 0, 1)); var stream2 = new SineGeneratorStream(44100, 440, new TimeSpan(0, 0, 1)); var cc = new ConcatenationStream(stream1, stream2); // Seek to the end where zero bytes are expected to be read cc.Position = cc.Length; var bytesRead = StreamUtil.ReadAllAndCount(cc); Assert.AreEqual(0, bytesRead); }
public void CheckSeek() { var sine = new SineGeneratorStream(44100, 440, new TimeSpan(0, 0, 1)); int seek = 100 * 4; sine.Position = seek; Assert.AreEqual(sine.Position, seek); long bytesRead = StreamUtil.ReadAllAndCount(sine); Assert.AreEqual(sine.Length - seek, bytesRead); }
public void CheckSingleSourceLength() { var stream = new SineGeneratorStream(44100, 440, new TimeSpan(0, 0, 1)); var cc = new ConcatenationStream(stream); // Ensure that the lengths match Assert.AreEqual(stream.Length, cc.Length); var bytesRead = StreamUtil.ReadAllAndCount(cc); // Ensure that the read bytes equals the length Assert.AreEqual(stream.Length, bytesRead); }
public void MonoOverlapAddHann() { var properties = new AudioProperties(1, 44100, 32, AudioFormat.IEEE); IAudioStream sourceStream = new SineGeneratorStream(44100, 440, TimeSpan.FromSeconds(1)); IAudioWriterStream targetStream = new MemoryWriterStream(new System.IO.MemoryStream(), properties); // 50% overlap with a Hann window is an optimal combination, Hann window is optimally uneven with a 1 as middle point int windowSize = 21; int hopSize = 11; var window = WindowType.Hann; // Adjust source length to window/hop size so no samples remain at the end // (a potential last incomplete frame is not returned by the stream windower) // With remaining samples, the source and target length cannot be compared sourceStream = new CropStream(sourceStream, 0, sourceStream.Length - (sourceStream.Length - windowSize * sourceStream.SampleBlockSize) % (hopSize * sourceStream.SampleBlockSize)); var sw = new StreamWindower(sourceStream, windowSize, hopSize, window); var ola = new OLA(targetStream, windowSize, hopSize); var frameBuffer = new float[windowSize]; while (sw.HasNext()) { sw.ReadFrame(frameBuffer); ola.WriteFrame(frameBuffer); } ola.Flush(); Assert.AreEqual(sourceStream.Length, targetStream.Length); // Validate ola target stream content // Crop the streams to ignore windowed start and end when no overlap-add is performed and content definitely differs var sourceStreamCropped = new CropStream(sourceStream, hopSize * sourceStream.SampleBlockSize * 2, sourceStream.Length - hopSize * sourceStream.SampleBlockSize); var targetStreamCropped = new CropStream(targetStream, hopSize * sourceStream.SampleBlockSize * 2, sourceStream.Length - hopSize * sourceStream.SampleBlockSize); sourceStreamCropped.Position = targetStreamCropped.Position = 0; long similarFloats = StreamUtil.CompareFloats(sourceStreamCropped, targetStreamCropped); Assert.AreEqual(sourceStreamCropped.Length / sourceStreamCropped.SampleBlockSize, similarFloats); }
public void CheckLength() { int sampleRate = 44100; int seconds = 1; long expectedLength = sampleRate * seconds * 4; // 4 bytes per 32bit ieee sample var sine = new SineGeneratorStream(sampleRate, 440, new TimeSpan(0, 0, seconds)); Assert.AreEqual(expectedLength, sine.Length); long bytesRead = StreamUtil.ReadAllAndCount(sine); Assert.AreEqual(expectedLength, bytesRead); }
public void CheckSingleSourceSeeking() { var stream = new SineGeneratorStream(44100, 440, new TimeSpan(0, 0, 1)); var cc = new ConcatenationStream(stream); int seek = 200 * stream.SampleBlockSize; cc.Position = seek; // Ensure that the position is correct Assert.AreEqual(seek, cc.Position); var bytesRead = StreamUtil.ReadAllAndCount(cc); // Ensure that the expected remaining bytes match the seek position Assert.AreEqual(cc.Length - seek, bytesRead); }
public void TransformForwardAndBackward() { int size = 4096; var pffft = new PFFFT(size, Transform.Real); var dataIn = new float[size]; var dataOut = new float[size]; var dataBack = new float[size]; var generator = new SineGeneratorStream(4096, 100, TimeSpan.FromSeconds(1)); generator.Read(dataIn, 0, size); pffft.Forward(dataIn, dataOut); pffft.Backward(dataOut, dataBack); CollectionAssert.AreEqual(dataIn, dataBack, new FFTComparer()); }
/// <summary> /// Calculates a normalization offset for the current window function. Every window function leads to /// a different FFT result peak value, and since the peak value of each windowed FFT means 0dB, this /// offset can be used to adjust the calculated dB values. /// </summary> private void CalculateWindowFunctionNormalizationOffset() { if (windowFunction == null) { windowFunctionNormalizationDecibelOffset = 0; } else { SineGeneratorStream sine = new SineGeneratorStream(1024, 16, new TimeSpan(0, 0, 1)); float[] input = new float[WindowSize]; float[] output = new float[input.Length / 2]; WindowFunction wf = WindowUtil.GetFunction(windowFunction.Type, input.Length); sine.Read(input, 0, input.Length); wf.Apply(input); fft.Forward(input); int maxIndex = FFTUtil.Results(input, output); float maxValue = output[maxIndex]; windowFunctionNormalizationDecibelOffset = 1f - maxValue; } }
public void CheckMultiSourceMatchingFormat() { var stream1 = new SineGeneratorStream(44100, 440, new TimeSpan(0, 0, 1)); var stream2 = new SineGeneratorStream(96000, 440, new TimeSpan(0, 0, 1)); var cc = new ConcatenationStream(stream1, stream2); }
private void Generate(FFTLibrary fftLib) { // FFT resources: // http://www.mathworks.de/support/tech-notes/1700/1702.html // http://stackoverflow.com/questions/6627288/audio-spectrum-analysis-using-fft-algorithm-in-java // http://stackoverflow.com/questions/1270018/explain-the-fft-to-me // http://stackoverflow.com/questions/6666807/how-to-scale-fft-output-of-wave-file /* * FFT max value test: * 16Hz, 1024 samplerate, 512 samples, rectangle window * -> input min/max: -1/1 * -> FFT max: 256 * -> FFT result max: ~1 * -> FFT dB result max: ~1 * * source: "Windowing Functions Improve FFT Results, Part I" * => kann zum Normalisieren für Fensterfunktionen verwendet werden */ float frequency = float.Parse(frequencyTextBox.Text); int ws = (int)windowSize.Value; float frequencyFactor = ws / frequency; int sampleRate = int.Parse(sampleRateTextBox.Text); SineGeneratorStream sine = new SineGeneratorStream(sampleRate, frequency, new TimeSpan(0, 0, 1)); float[] input = new float[ws]; sine.Read(input, 0, ws); //// add second frequency //SineGeneratorStream sine2 = new SineGeneratorStream(44100, 700, new TimeSpan(0, 0, 1)); //float[] input2 = new float[ws]; //sine2.Read(input2, 0, ws); //for (int x = 0; x < input.Length; x++) { // input[x] += input2[x]; // input[x] /= 2; //} //// add dc offset //for (int x = 0; x < input.Length; x++) { // input[x] += 0.5f; //} inputGraph.Values = input; WindowFunction wf = WindowUtil.GetFunction((WindowType)windowTypes.SelectedValue, ws); float[] window = (float[])input.Clone(); wf.Apply(window); input2Graph.Values = window; float[] fftIO = (float[])window.Clone(); if (fftLib == FFTLibrary.ExocortexDSP) { // This function call indirection is needed to allow this method execute even when the // Exocortex FFT assembly is missing. ApplyExocortexDSP(fftIO); } else if (fftLib == FFTLibrary.FFTW) { FFTW.FFTW fftw = new FFTW.FFTW(fftIO.Length); fftw.Execute(fftIO); } else if (fftLib == FFTLibrary.PFFFT) { PFFFT.PFFFT pffft = new PFFFT.PFFFT(fftIO.Length, PFFFT.Transform.Real); pffft.Forward(fftIO, fftIO); } //// convert real input to complex input with im part set to zero //float[] fftIO = new float[ws * 2]; //int i = 0; //for (int j = 0; j < window.Length; j++) { // fftIO[i] = window[j]; // i += 2; //} //Fourier.FFT(fftIO, fftIO.Length / 2, FourierDirection.Forward); fftOutputGraph.Values = fftIO; float[] fftResult = new float[ws / 2]; //FFTUtil.Results(fftIO, fftResult); // transform complex output to magnitudes float sum = 0; int y = 0; for (int x = 0; x < fftIO.Length; x += 2) // / 2 { fftResult[y] = FFTUtil.CalculateMagnitude(fftIO[x], fftIO[x + 1]) / ws * 2; sum += fftResult[y++]; } //FFTUtil.Results(fftIO, fftResult); //// adjust values for the sum to become 1 //float sum2 = 0; //for (int x = 0; x < fftResult.Length; x++) { // fftResult[x] /= sum; // sum2 += fftResult[x]; //} //Debug.WriteLine("sum / sum2: {0} / {1}", sum, sum2); fftNormalizedOutputGraph.Values = fftResult; // convert magnitudes to decibel float[] fftResultdB = new float[fftResult.Length]; //for (int x = 0; x < fftResult.Length; x++) { // fftResultdB[x] = (float)VolumeUtil.LinearToDecibel(fftResult[x]); //} FFTUtil.Results(fftIO, fftResultdB); fftdBOutputGraph.Values = fftResultdB; summary.Text = String.Format( "input min/max: {0}/{1} -> window min/max: {2}/{3} -> fft min/max: {4}/{5} -> result min/max/bins/sum: {6}/{7}/{8}/{9} -> dB min/max: {10:0.000000}/{11:0.000000}", input.Min(), input.Max(), window.Min(), window.Max(), fftIO.Min(), fftIO.Max(), fftResult.Min(), fftResult.Max(), fftResult.Length, sum, fftResultdB.Min(), fftResultdB.Max()); }