From 4ea2f7d421f50b2f96c772b7dad3ac31ee015596 Mon Sep 17 00:00:00 2001 From: Sibo Van Gool Date: Thu, 4 Nov 2021 17:30:44 +0100 Subject: [PATCH 01/10] [fixes #20] Rename 'absolute' to 'magnitude' --- .../transform/InverseDiscreteFourier.java | 4 ++-- .../jdsp/TestInverseDiscreteFourier.java | 24 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/github/psambit9791/jdsp/transform/InverseDiscreteFourier.java b/src/main/java/com/github/psambit9791/jdsp/transform/InverseDiscreteFourier.java index e45b2a0a..4a51f38e 100644 --- a/src/main/java/com/github/psambit9791/jdsp/transform/InverseDiscreteFourier.java +++ b/src/main/java/com/github/psambit9791/jdsp/transform/InverseDiscreteFourier.java @@ -93,11 +93,11 @@ public double[] getRealSignal() throws ExceptionInInitializerError { } /** - * This method returns the absolute value of the generated signal. + * This method returns the magnitude value of the IDFT result. * @throws java.lang.ExceptionInInitializerError if called before executing idft() method * @return double[] The signal (absolute) */ - public double[] getAbsoluteSignal() throws ExceptionInInitializerError { + public double[] getMagnitudeSignal() throws ExceptionInInitializerError { if (this.signal == null) { throw new ExceptionInInitializerError("Execute idft() function before returning result"); } diff --git a/src/test/java/com/github/psambit9791/jdsp/TestInverseDiscreteFourier.java b/src/test/java/com/github/psambit9791/jdsp/TestInverseDiscreteFourier.java index e8a720cd..98a75661 100644 --- a/src/test/java/com/github/psambit9791/jdsp/TestInverseDiscreteFourier.java +++ b/src/test/java/com/github/psambit9791/jdsp/TestInverseDiscreteFourier.java @@ -61,7 +61,7 @@ public void testInverseFourierFullComplex() { 0.372, 0.409, 0.44 , 0.51 , 0.615, 0.702, 0.688, 0.504, 0.133, -0.373, -0.894, -1.281, -1.401, -1.192, -0.684, -0.0}; - double[] resultAbs = {0.0, 0.684, 1.192, 1.401, 1.281, 0.894, 0.373, 0.133, 0.504, 0.688, 0.702, 0.615, 0.51, + double[] resultMag = {0.0, 0.684, 1.192, 1.401, 1.281, 0.894, 0.373, 0.133, 0.504, 0.688, 0.702, 0.615, 0.51, 0.44, 0.409, 0.372, 0.263, 0.033, 0.319, 0.726, 1.074, 1.236, 1.121, 0.714, 0.093, 0.593, 1.163, 1.464, 1.42, 1.053, 0.476, 0.147, 0.658, 0.948, 0.991, 0.839, 0.588, 0.338, 0.154, 0.041, 0.041, 0.154, 0.338, 0.588, 0.839, 0.991, 0.948, 0.658, 0.147, 0.476, 1.053, 1.42, 1.464, 1.163, 0.593, 0.093, 0.714, 1.121, @@ -82,7 +82,7 @@ public void testInverseFourierFullComplex() { InverseDiscreteFourier transformer = new InverseDiscreteFourier(this.seqCompFull, false); transformer.idft(); double[] outReal = transformer.getRealSignal(); - double[] outAbsolute = transformer.getAbsoluteSignal(); + double[] outMagnitude = transformer.getMagnitudeSignal(); double[][] out = transformer.getComplexSignal(); double[] outR = new double[out.length]; @@ -94,7 +94,7 @@ public void testInverseFourierFullComplex() { } Assertions.assertArrayEquals(resultReal, outReal, 0.001); - Assertions.assertArrayEquals(resultAbs, outAbsolute, 0.001); + Assertions.assertArrayEquals(resultMag, outMagnitude, 0.001); Assertions.assertArrayEquals(resultComplex[0], outR, 0.001); Assertions.assertArrayEquals(resultComplex[1], outI, 0.001); } @@ -109,7 +109,7 @@ public void testInverseFourierPositiveComplex() { 0.372, 0.409, 0.44 , 0.51 , 0.615, 0.702, 0.688, 0.504, 0.133, -0.373, -0.894, -1.281, -1.401, -1.192, -0.684, -0.0}; - double[] resultAbs = {0.0, 0.684, 1.192, 1.401, 1.281, 0.894, 0.373, 0.133, 0.504, 0.688, 0.702, 0.615, 0.51, + double[] resultMag = {0.0, 0.684, 1.192, 1.401, 1.281, 0.894, 0.373, 0.133, 0.504, 0.688, 0.702, 0.615, 0.51, 0.44, 0.409, 0.372, 0.263, 0.033, 0.319, 0.726, 1.074, 1.236, 1.121, 0.714, 0.093, 0.593, 1.163, 1.464, 1.42, 1.053, 0.476, 0.147, 0.658, 0.948, 0.991, 0.839, 0.588, 0.338, 0.154, 0.041, 0.041, 0.154, 0.338, 0.588, 0.839, 0.991, 0.948, 0.658, 0.147, 0.476, 1.053, 1.42, 1.464, 1.163, 0.593, 0.093, 0.714, 1.121, @@ -130,7 +130,7 @@ public void testInverseFourierPositiveComplex() { InverseDiscreteFourier transformer = new InverseDiscreteFourier(this.seqCompPositive, true); transformer.idft(); double[] outReal = transformer.getRealSignal(); - double[] outAbsolute = transformer.getAbsoluteSignal(); + double[] outMagnitude = transformer.getMagnitudeSignal(); double[][] out = transformer.getComplexSignal(); double[] outR = new double[out.length]; @@ -142,7 +142,7 @@ public void testInverseFourierPositiveComplex() { } Assertions.assertArrayEquals(resultReal, outReal, 0.001); - Assertions.assertArrayEquals(resultAbs, outAbsolute, 0.001); + Assertions.assertArrayEquals(resultMag, outMagnitude, 0.001); Assertions.assertArrayEquals(resultComplex[0], outR, 0.001); Assertions.assertArrayEquals(resultComplex[1], outI, 0.001); } @@ -156,7 +156,7 @@ public void testInverseFourierFullReal() { -0.285, -0.343, -0.311, -0.203, -0.058, 0.081, 0.174, 0.204, 0.176, 0.115, 0.054, 0.018, 0.016, 0.035, 0.053, 0.043, -0.007, -0.092, -0.186, -0.253, -0.261, -0.193, -0.06, 0.105, 0.254, 0.342}; - double[] resultAbs = {0.0, 0.342, 0.254, 0.105, 0.06, 0.193, 0.261, 0.253, 0.186, 0.092, 0.007, 0.043, 0.053, + double[] resultMag = {0.0, 0.342, 0.254, 0.105, 0.06, 0.193, 0.261, 0.253, 0.186, 0.092, 0.007, 0.043, 0.053, 0.035, 0.016, 0.018, 0.054, 0.115, 0.176, 0.204, 0.174, 0.081, 0.058, 0.203, 0.311, 0.343, 0.285, 0.151, 0.022, 0.184, 0.288, 0.312, 0.255, 0.145, 0.022, 0.076, 0.125, 0.125, 0.092, 0.056, 0.041, 0.056, 0.092, 0.125, 0.125, 0.076, 0.022, 0.145, 0.255, 0.312, 0.288, 0.184, 0.022, 0.151, 0.285, 0.343, 0.311, 0.203, @@ -178,7 +178,7 @@ public void testInverseFourierFullReal() { InverseDiscreteFourier transformer = new InverseDiscreteFourier(this.seqRealFull, false); transformer.idft(); double[] outReal = transformer.getRealSignal(); - double[] outAbsolute = transformer.getAbsoluteSignal(); + double[] outMagnitude = transformer.getMagnitudeSignal(); double[][] out = transformer.getComplexSignal(); double[] outR = new double[out.length]; @@ -190,7 +190,7 @@ public void testInverseFourierFullReal() { } Assertions.assertArrayEquals(resultReal, outReal, 0.001); - Assertions.assertArrayEquals(resultAbs, outAbsolute, 0.001); + Assertions.assertArrayEquals(resultMag, outMagnitude, 0.001); Assertions.assertArrayEquals(resultComplex[0], outR, 0.001); Assertions.assertArrayEquals(resultComplex[1], outI, 0.001); } @@ -204,7 +204,7 @@ public void testInverseFourierPositiveReal() { -0.285, -0.343, -0.311, -0.203, -0.058, 0.081, 0.174, 0.204, 0.176, 0.115, 0.054, 0.018, 0.016, 0.035, 0.053, 0.043, -0.007, -0.092, -0.186, -0.253, -0.261, -0.193, -0.06, 0.105, 0.254, 0.342}; - double[] resultAbs = {0.0, 0.342, 0.254, 0.105, 0.06, 0.193, 0.261, 0.253, 0.186, 0.092, 0.007, 0.043, 0.053, + double[] resultMag = {0.0, 0.342, 0.254, 0.105, 0.06, 0.193, 0.261, 0.253, 0.186, 0.092, 0.007, 0.043, 0.053, 0.035, 0.016, 0.018, 0.054, 0.115, 0.176, 0.204, 0.174, 0.081, 0.058, 0.203, 0.311, 0.343, 0.285, 0.151, 0.022, 0.184, 0.288, 0.312, 0.255, 0.145, 0.022, 0.076, 0.125, 0.125, 0.092, 0.056, 0.041, 0.056, 0.092, 0.125, 0.125, 0.076, 0.022, 0.145, 0.255, 0.312, 0.288, 0.184, 0.022, 0.151, 0.285, 0.343, 0.311, 0.203, @@ -226,7 +226,7 @@ public void testInverseFourierPositiveReal() { InverseDiscreteFourier transformer = new InverseDiscreteFourier(this.seqRealPositive, true); transformer.idft(); double[] outReal = transformer.getRealSignal(); - double[] outAbsolute = transformer.getAbsoluteSignal(); + double[] outMagnitude = transformer.getMagnitudeSignal(); double[][] out = transformer.getComplexSignal(); double[] outR = new double[out.length]; @@ -238,7 +238,7 @@ public void testInverseFourierPositiveReal() { } Assertions.assertArrayEquals(resultReal, outReal, 0.001); - Assertions.assertArrayEquals(resultAbs, outAbsolute, 0.001); + Assertions.assertArrayEquals(resultMag, outMagnitude, 0.001); Assertions.assertArrayEquals(resultComplex[0], outR, 0.001); Assertions.assertArrayEquals(resultComplex[1], outI, 0.001); } From 0803f14f58b903013d3e93d9f8549ccb84973361 Mon Sep 17 00:00:00 2001 From: Sibo Van Gool Date: Fri, 5 Nov 2021 11:23:28 +0100 Subject: [PATCH 02/10] [fixes #20] Implement phase return for iDFT --- .../jdsp/transform/InverseDiscreteFourier.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/main/java/com/github/psambit9791/jdsp/transform/InverseDiscreteFourier.java b/src/main/java/com/github/psambit9791/jdsp/transform/InverseDiscreteFourier.java index 4a51f38e..0eb2df7e 100644 --- a/src/main/java/com/github/psambit9791/jdsp/transform/InverseDiscreteFourier.java +++ b/src/main/java/com/github/psambit9791/jdsp/transform/InverseDiscreteFourier.java @@ -12,6 +12,8 @@ import org.apache.commons.math3.complex.Complex; +import java.util.Arrays; + /** *

Inverse Discrete Fourier Transform

* The InverseDiscreteFourier class applies the inverse discrete fourier transform on the input sequence (real/complex) and @@ -108,6 +110,22 @@ public double[] getMagnitudeSignal() throws ExceptionInInitializerError { return ret; } + /** + * This method returns the phase value of the IDFT result. + * @throws java.lang.ExceptionInInitializerError if called before executing idft() method + * @return double[] phase of the signal + */ + public double[] getPhaseSignal() throws ExceptionInInitializerError { + if (this.signal == null) { + throw new ExceptionInInitializerError("Execute idft() function before returning result"); + } + double[] ret = new double[this.signal.length]; + for (int i=0; i Date: Fri, 5 Nov 2021 15:42:45 +0100 Subject: [PATCH 03/10] [fixes #20] Add warning for no stft calculation --- .../jdsp/transform/ShortTimeFourier.java | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/github/psambit9791/jdsp/transform/ShortTimeFourier.java b/src/main/java/com/github/psambit9791/jdsp/transform/ShortTimeFourier.java index 24a7bf0e..a335c4bc 100644 --- a/src/main/java/com/github/psambit9791/jdsp/transform/ShortTimeFourier.java +++ b/src/main/java/com/github/psambit9791/jdsp/transform/ShortTimeFourier.java @@ -209,6 +209,7 @@ public double[][] getPhaseDeg(boolean onlyPositive) { } public DiscreteFourier[] getOutput() { + checkOutput(); return output; } @@ -218,9 +219,7 @@ public DiscreteFourier[] getOutput() { * @return Complex[][] STFT result matrix; row = frequency frame; column = time frame */ public Complex[][] getComplex(boolean onlyPositive) { - if (this.output == null) { - stft(); - } + checkOutput(); Complex[][] result = new Complex[this.output[0].getComplex(onlyPositive).length][this.output.length]; @@ -241,9 +240,7 @@ public Complex[][] getComplex(boolean onlyPositive) { * @return double[] array of all the time frames */ public double[] getFrequencyAxis(boolean onlyPositive) { - if (this.output == null) { - throw new ExceptionInInitializerError("No STFT calculated yet"); - } + checkOutput(); double[] axis = new double[this.output[0].getComplex(onlyPositive).length]; @@ -259,9 +256,7 @@ public double[] getFrequencyAxis(boolean onlyPositive) { * @return double[] array of all the frequency frames */ public double[] getTimeAxis() { - if (this.output == null) { - throw new ExceptionInInitializerError("No STFT calculated yet"); - } + checkOutput(); double[] axis = new double[this.output.length]; for (int i = 0; i < axis.length; i++) { @@ -269,5 +264,15 @@ public double[] getTimeAxis() { } return axis; } + + /** + * Checks whether the STFT has been calculated yet + * @throws ExceptionInInitializerError if result hasn't been calculated yet + */ + private void checkOutput() { + if (this.output == null) { + throw new ExceptionInInitializerError("Execute stft() function before returning result"); + } + } } From 6710624fabec38421e1730aab65fc3cb5d05bfd6 Mon Sep 17 00:00:00 2001 From: Sibo Van Gool Date: Sat, 6 Nov 2021 20:39:08 +0100 Subject: [PATCH 04/10] [fixes #20] Implement applyInverseWindow Sometimes you want to undo the windowing done on a signal and thus apply the inverse of a window on a signal --- .../psambit9791/jdsp/windows/_Window.java | 38 +++- .../github/psambit9791/jdsp/TestWindows.java | 193 ++++++++++++++++++ 2 files changed, 230 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/github/psambit9791/jdsp/windows/_Window.java b/src/main/java/com/github/psambit9791/jdsp/windows/_Window.java index c05d179e..fe2b4598 100644 --- a/src/main/java/com/github/psambit9791/jdsp/windows/_Window.java +++ b/src/main/java/com/github/psambit9791/jdsp/windows/_Window.java @@ -70,8 +70,10 @@ public double[] truncate(double[] arr) { public abstract double[] getWindow(); /** - * Apply the window to the input data and return the output. Throws an exception if the array dimensions don't match. + * Apply the window to the input data and return the output. * @param input input data + * @throws NullPointerException if window not initialized + * @throws IllegalArgumentException if window and input dimensions don't match * @return double[] windowed input data */ public double[] applyWindow(double[] input) { @@ -89,4 +91,38 @@ public double[] applyWindow(double[] input) { } return out; } + + /** + * Apply the inverse of the window ("undo" the windowing on a signal) to the input data and return the output. + * @param input input data + * @throws NullPointerException if window not initialized + * @throws IllegalArgumentException if window and input dimensions don't match + * @return double[] windowed input data + */ + public double[] applyInverseWindow(double[] input) { + double[] window = getWindow(); + double[] out = new double[input.length]; + // Flag that checks whether the window contained a zero-value. If true, a warning message will be print about + // irretrievable loss of the signal. + boolean dataLost = false; + + if (window == null) { + throw new NullPointerException("Window not initialized"); + } + if (input.length != window.length) { + throw new IllegalArgumentException("Input data dimensions and window dimensions don't match"); + } + + for (int i = 0; i < input.length; i++) { + if (window[i] == 0) { + dataLost = true; + continue; + } + out[i] = input[i]/window[i]; + } + if (dataLost) { + System.err.println("The original window function contained a zero-element, which causes some of the data to be irretrievably lost."); + } + return out; + } } diff --git a/src/test/java/com/github/psambit9791/jdsp/TestWindows.java b/src/test/java/com/github/psambit9791/jdsp/TestWindows.java index 9f2abb4b..bac6dd7d 100644 --- a/src/test/java/com/github/psambit9791/jdsp/TestWindows.java +++ b/src/test/java/com/github/psambit9791/jdsp/TestWindows.java @@ -5,6 +5,10 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.Arrays; + public class TestWindows { private double[] signal = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; @@ -38,6 +42,15 @@ public void RectangularApplyTest() { Assertions.assertArrayEquals(result, out, 0.001); } + @Test + public void RectangularApplyInverseTest() { + double[] result = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + _Window w = new Rectangular(signal.length); + double[] out = w.applyWindow(signal); + out = w.applyInverseWindow(out); + Assertions.assertArrayEquals(result, out, 0.001); + } + @Test public void GeneralCosineSymTest() { int len = 10; @@ -69,6 +82,23 @@ public void GeneralCosineApplyTest() { Assertions.assertArrayEquals(result, out, 0.001); } + @Test + public void GeneralCosineApplyInverseTest() { + double[] result = {0, 2, 3, 4, 5, 6, 7, 8, 9, 0}; + double[] weights = {1, 2, 2, 1}; + _Window w = new GeneralCosine(signal.length, weights); + double[] out = w.applyWindow(signal); + // We don't want the System.err.println message + PrintStream stderr = System.err; // Save standard stderr + System.setErr(new PrintStream(new OutputStream() { + @Override + public void write(int b) { } + })); + out = w.applyInverseWindow(out); + System.setErr(stderr); // Reset to standard stderr + Assertions.assertArrayEquals(result, out, 0.001); + } + @Test public void HammingSymTest() { int len = 10; @@ -107,6 +137,15 @@ public void HammingApplyTest() { Assertions.assertArrayEquals(result, out, 0.001); } + @Test + public void HammingApplyInverseTest() { + double[] result = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + _Window w = new Hamming(signal.length); + double[] out = w.applyWindow(signal); + out = w.applyInverseWindow(out); + Assertions.assertArrayEquals(result, out, 0.001); + } + @Test public void HanningSymTest() { int len = 10; @@ -143,6 +182,22 @@ public void HanningApplyTest() { Assertions.assertArrayEquals(result, out, 0.001); } + @Test + public void HanningApplyInverseTest() { + double[] result = {0, 2, 3, 4, 5, 6, 7, 8, 9, 0}; + _Window w = new Hanning(signal.length); + double[] out = w.applyWindow(signal); + // We don't want the System.err.println message + PrintStream stderr = System.err; // Save standard stderr + System.setErr(new PrintStream(new OutputStream() { + @Override + public void write(int b) { } + })); + out = w.applyInverseWindow(out); + System.setErr(stderr); // Reset to standard stderr + Assertions.assertArrayEquals(result, out, 0.001); + } + @Test public void BlackmanSymTest() { int len = 10; @@ -179,6 +234,15 @@ public void BlackmanApplyTest() { Assertions.assertArrayEquals(result, out, 0.001); } + @Test + public void BlackmanApplyInverseTest() { + double[] result = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + _Window w = new Blackman(signal.length); + double[] out = w.applyWindow(signal); + out = w.applyInverseWindow(out); + Assertions.assertArrayEquals(result, out, 0.001); + } + @Test public void BlackmanHarrisSymTest() { int len = 10; @@ -215,6 +279,15 @@ public void BlackmanHarrisApplyTest() { Assertions.assertArrayEquals(result, out, 0.001); } + @Test + public void BlackmanHarrisApplyInverseTest() { + double[] result = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + _Window w = new BlackmanHarris(signal.length); + double[] out = w.applyWindow(signal); + out = w.applyInverseWindow(out); + Assertions.assertArrayEquals(result, out, 0.001); + } + @Test public void PoissonSymTest() { int len = 10; @@ -251,6 +324,15 @@ public void PoissonApplyTest() { Assertions.assertArrayEquals(result, out, 0.001); } + @Test + public void PoissonApplyInverseTest() { + double[] result = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + _Window w = new Poisson(signal.length); + double[] out = w.applyWindow(signal); + out = w.applyInverseWindow(out); + Assertions.assertArrayEquals(result, out, 0.001); + } + @Test public void FlatTopSymTest() { int len = 10; @@ -287,6 +369,15 @@ public void FlatTopApplyTest() { Assertions.assertArrayEquals(result, out, 0.001); } + @Test + public void FlatTopApplyInverseTest() { + double[] result = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + _Window w = new FlatTop(signal.length); + double[] out = w.applyWindow(signal); + out = w.applyInverseWindow(out); + Assertions.assertArrayEquals(result, out, 0.001); + } + @Test public void NuttallSymTest() { int len = 10; @@ -323,6 +414,15 @@ public void NuttallApplyTest() { Assertions.assertArrayEquals(result, out, 0.001); } + @Test + public void NuttallApplyInverseTest() { + double[] result = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + _Window w = new Nuttall(signal.length); + double[] out = w.applyWindow(signal); + out = w.applyInverseWindow(out); + Assertions.assertArrayEquals(result, out, 0.001); + } + @Test public void GaussianSymTest() { int len = 10; @@ -354,6 +454,16 @@ public void GaussianApplyTest() { Assertions.assertArrayEquals(result, out, 0.001); } + @Test + public void GaussianApplyInverseTest() { + double[] result = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + double std = 2.0; + _Window w = new Gaussian(signal.length, std); + double[] out = w.applyWindow(signal); + out = w.applyInverseWindow(out); + Assertions.assertArrayEquals(result, out, 0.001); + } + @Test public void TukeySymTest() { int len = 10; @@ -385,6 +495,23 @@ public void TukeyApplyTest() { Assertions.assertArrayEquals(result, out, 0.001); } + @Test + public void TukeyApplyInverseTest() { + double[] result = {0, 2, 3, 4, 5, 6, 7, 8, 9, 0}; + double alpha = 0.5; + _Window w = new Tukey(signal.length, alpha); + double[] out = w.applyWindow(signal); + // We don't want the System.err.println message + PrintStream stderr = System.err; // Save standard stderr + System.setErr(new PrintStream(new OutputStream() { + @Override + public void write(int b) { } + })); + out = w.applyInverseWindow(out); + System.setErr(stderr); // Reset to standard stderr + Assertions.assertArrayEquals(result, out, 0.001); + } + @Test public void TriangularEvenSymTest() { int len = 10; @@ -439,6 +566,15 @@ public void TriangularEvenApplyTest() { Assertions.assertArrayEquals(result, out, 0.001); } + @Test + public void TriangularEvenApplyInverseTest() { + double[] result = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + _Window w = new Triangular(signal.length); + double[] out = w.applyWindow(signal); + out = w.applyInverseWindow(out); + Assertions.assertArrayEquals(result, out, 0.001); + } + @Test public void BartlettSymTest() { int len = 10; @@ -475,6 +611,22 @@ public void BartlettApplyTest() { Assertions.assertArrayEquals(result, out, 0.001); } + @Test + public void BartlettApplyInverseTest() { + double[] result = {0, 2, 3, 4, 5, 6, 7, 8, 9, 0}; + _Window w = new Bartlett(signal.length); + double[] out = w.applyWindow(signal); + // We don't want the System.err.println message + PrintStream stderr = System.err; // Save standard stderr + System.setErr(new PrintStream(new OutputStream() { + @Override + public void write(int b) { } + })); + out = w.applyInverseWindow(out); + System.setErr(stderr); // Reset to standard stderr + Assertions.assertArrayEquals(result, out, 0.001); + } + @Test public void BartlettHannSymTest() { int len = 10; @@ -511,6 +663,22 @@ public void BartlettHannApplyTest() { Assertions.assertArrayEquals(result, out, 0.001); } + @Test + public void BartlettHannApplyInverseTest() { + double[] result = {0, 2, 3, 4, 5, 6, 7, 8, 9, 0}; + _Window w = new BartlettHann(signal.length); + double[] out = w.applyWindow(signal); + // We don't want the System.err.println message + PrintStream stderr = System.err; // Save standard stderr + System.setErr(new PrintStream(new OutputStream() { + @Override + public void write(int b) { } + })); + out = w.applyInverseWindow(out); + System.setErr(stderr); // Reset to standard stderr + Assertions.assertArrayEquals(result, out, 0.001); + } + @Test public void BohmanSymTest() { int len = 10; @@ -547,6 +715,22 @@ public void BohmanApplyTest() { Assertions.assertArrayEquals(result, out, 0.001); } + @Test + public void BohmanApplyInverseTest() { + double[] result = {0, 2, 3, 4, 5, 6, 7, 8, 9, 0}; + _Window w = new Bohman(signal.length); + double[] out = w.applyWindow(signal); + // We don't want the System.err.println message + PrintStream stderr = System.err; // Save standard stderr + System.setErr(new PrintStream(new OutputStream() { + @Override + public void write(int b) { } + })); + out = w.applyInverseWindow(out); + System.setErr(stderr); // Reset to standard stderr + Assertions.assertArrayEquals(result, out, 0.001); + } + @Test public void KaiserSymTest() { int len = 10; @@ -574,4 +758,13 @@ public void KaiserApplyTest() { double[] out = w.applyWindow(signal); Assertions.assertArrayEquals(result, out, 0.001); } + + @Test + public void KaiserApplyInverseTest() { + double[] result = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + _Window w = new Kaiser(signal.length, 14); + double[] out = w.applyWindow(signal); + out = w.applyInverseWindow(out); + Assertions.assertArrayEquals(result, out, 0.001); + } } From 90a346b678dabac64bea4b40960b927b8bdba1af Mon Sep 17 00:00:00 2001 From: Sibo Van Gool Date: Sat, 6 Nov 2021 20:48:40 +0100 Subject: [PATCH 05/10] [fixes #20] Add extra argument clauses for STFT --- .../psambit9791/jdsp/transform/ShortTimeFourier.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/com/github/psambit9791/jdsp/transform/ShortTimeFourier.java b/src/main/java/com/github/psambit9791/jdsp/transform/ShortTimeFourier.java index a335c4bc..e763f2d7 100644 --- a/src/main/java/com/github/psambit9791/jdsp/transform/ShortTimeFourier.java +++ b/src/main/java/com/github/psambit9791/jdsp/transform/ShortTimeFourier.java @@ -38,12 +38,21 @@ public class ShortTimeFourier { * Value greater than frameLength => frame gets zero padded */ public ShortTimeFourier(double[] signal, int frameLength, int overlap, double Fs, _Window window, int fourierLength) { + if (signal == null) { + throw new IllegalArgumentException("Signal can not be null"); + } + if (frameLength < 1) { + throw new IllegalArgumentException("Frame length must be greater than 0"); + } if (overlap >= frameLength) { throw new IllegalArgumentException("Overlap size should be smaller than the frame length"); } if (fourierLength < frameLength) { throw new IllegalArgumentException("Fourier length should be equal to or greater than the frame length"); } + if (window == null) { + throw new IllegalArgumentException("Window can not be null"); + } this.signal = signal; this.frameLength = frameLength; this.overlap = overlap; From 59e865e5929fb95db051eaddd339168efd82ec79 Mon Sep 17 00:00:00 2001 From: Sibo Van Gool Date: Sun, 7 Nov 2021 01:57:05 +0100 Subject: [PATCH 06/10] [fixes #20] Rework STFT parameters --- .../jdsp/transform/ShortTimeFourier.java | 51 +++++++----- .../jdsp/TestShortTimeFourier.java | 77 ++++++++----------- 2 files changed, 63 insertions(+), 65 deletions(-) diff --git a/src/main/java/com/github/psambit9791/jdsp/transform/ShortTimeFourier.java b/src/main/java/com/github/psambit9791/jdsp/transform/ShortTimeFourier.java index e763f2d7..2dbaf629 100644 --- a/src/main/java/com/github/psambit9791/jdsp/transform/ShortTimeFourier.java +++ b/src/main/java/com/github/psambit9791/jdsp/transform/ShortTimeFourier.java @@ -32,12 +32,12 @@ public class ShortTimeFourier { * @param signal Signal for which to compute the STFT * @param frameLength Number of samples that each FFT-frame should have * @param overlap Number of samples that overlap between frames - * @param Fs Sampling frequency of the signal - * @param window Windowing function to perform on each STFT frame * @param fourierLength Number of samples used in the Fourier analysis of each frame * Value greater than frameLength => frame gets zero padded + * @param window Windowing function to perform on each STFT frame + * @param Fs Sampling frequency of the signal (in Hz) */ - public ShortTimeFourier(double[] signal, int frameLength, int overlap, double Fs, _Window window, int fourierLength) { + public ShortTimeFourier(double[] signal, int frameLength, int overlap, int fourierLength, _Window window, double Fs) { if (signal == null) { throw new IllegalArgumentException("Signal can not be null"); } @@ -56,62 +56,75 @@ public ShortTimeFourier(double[] signal, int frameLength, int overlap, double Fs this.signal = signal; this.frameLength = frameLength; this.overlap = overlap; - this.Fs = Fs; - this.window = window; this.fourierLength = fourierLength; + this.window = window; + this.Fs = Fs; } /** * Compute the Short-Time Fourier Transform for a time signal, with windowing. + * Defaults sampling frequency to 1. * * @param signal Signal for which to compute the STFT * @param frameLength Number of samples that each FFT-frame should have * @param overlap Number of samples that overlap between frames - * @param Fs Sampling frequency of the signal + * @param fourierLength Number of samples used in the Fourier analysis of each frame + * Value greater than frameLength => frame gets zero padded * @param window Windowing function to perform on each STFT frame */ - public ShortTimeFourier(double[] signal, int frameLength, int overlap, double Fs, _Window window) { - this(signal, frameLength, overlap, Fs, window, frameLength); + public ShortTimeFourier(double[] signal, int frameLength, int overlap, int fourierLength, _Window window) { + this(signal, frameLength, overlap, fourierLength, window, 1); } /** - * Compute the Short-Time Fourier Transform for a time signal, with windowing. + * Compute the Short-Time Fourier Transform for a time signal. + * Defaults window to rectangular window (= no windowing) and sampling frequency to 1. * + * @param signal Signal for which to compute the STFT * @param frameLength Number of samples that each FFT-frame should have * @param overlap Number of samples that overlap between frames + * @param fourierLength Number of samples used in the Fourier analysis of each frame + * Value greater than frameLength => frame gets zero padded */ - public ShortTimeFourier(double[] signal, int frameLength, int overlap, _Window window) { - this(signal, frameLength, overlap, 1, window); + public ShortTimeFourier(double[] signal, int frameLength, int overlap, int fourierLength) { + this(signal, frameLength, overlap, fourierLength, new Rectangular(frameLength), 1); } /** - * Defaults window as a rectangular window (= no windowing performed) + * Compute the Short-Time Fourier Transform for a time signal, with windowing. + * Defaults fourier length to frameLength and sampling frequency to 1. + * * @param signal Signal for which to compute the STFT * @param frameLength Number of samples that each FFT-frame should have * @param overlap Number of samples that overlap between frames - * @param Fs Sampling frequency of the signal + * @param window Windowing function to perform on each STFT frame */ - public ShortTimeFourier(double[] signal, int frameLength, int overlap, double Fs) { - this(signal, frameLength, overlap, Fs, new Rectangular(frameLength)); + public ShortTimeFourier(double[] signal, int frameLength, int overlap, _Window window) { + this(signal, frameLength, overlap, frameLength, window, 1); } /** - * Defaults Fs to 1 and as window a rectangular window (= no windowing performed) + * Compute the Short-Time Fourier Transform for a time signal. + * Defaults window to rectangular window (= no windowing), fourier length to frameLength and sampling frequency to 1. + * * @param signal Signal for which to compute the STFT * @param frameLength Number of samples that each FFT-frame should have * @param overlap Number of samples that overlap between frames */ public ShortTimeFourier(double[] signal, int frameLength, int overlap) { - this(signal, frameLength, overlap, 1, new Rectangular(frameLength)); + this(signal, frameLength, overlap, frameLength, new Rectangular(frameLength), 1); } /** - * Defaults Fs to 1 and as window a rectangular window (= no windowing performed) and 50% overlap + * Compute the Short-Time Fourier Transform for a time signal. + * Defaults overlap to 50% (half of the samples of frameLength), window to rectangular window (= no windowing), + * fourier length to frameLength and sampling frequency to 1. + * * @param signal Signal for which to compute the STFT * @param frameLength Number of samples that each FFT-frame should have */ public ShortTimeFourier(double[] signal, int frameLength) { - this(signal, frameLength, frameLength/2, 1, new Rectangular(frameLength)); + this(signal, frameLength, frameLength/2, frameLength, new Rectangular(frameLength), 1); } diff --git a/src/test/java/com/github/psambit9791/jdsp/TestShortTimeFourier.java b/src/test/java/com/github/psambit9791/jdsp/TestShortTimeFourier.java index 1b6bb5d6..4e91fe03 100644 --- a/src/test/java/com/github/psambit9791/jdsp/TestShortTimeFourier.java +++ b/src/test/java/com/github/psambit9791/jdsp/TestShortTimeFourier.java @@ -69,11 +69,9 @@ public void testSTFT1() { int frameLength = 5; int overlap = 2; - double Fs = 100; - _Window window = new Rectangular(frameLength); - - ShortTimeFourier stft = new ShortTimeFourier(signal1, frameLength, overlap, Fs, window); + ShortTimeFourier stft = new ShortTimeFourier(signal1, frameLength, overlap); + stft.stft(); Complex[][] out = stft.getComplex(false); for (int c = 0; c < out[0].length; c++) { @@ -100,11 +98,9 @@ public void testSTFT2() { int frameLength = 5; int overlap = 2; - double Fs = 100; - _Window window = new Rectangular(frameLength); - - ShortTimeFourier stft = new ShortTimeFourier(signal2, frameLength, overlap, Fs, window); + ShortTimeFourier stft = new ShortTimeFourier(signal2, frameLength, overlap); + stft.stft(); Complex[][] out = stft.getComplex(false); for (int c = 0; c < out[0].length; c++) { @@ -127,11 +123,9 @@ public void testSTFTPositive1() { int frameLength = 5; int overlap = 2; - double Fs = 100; - _Window window = new Rectangular(frameLength); - - ShortTimeFourier stft = new ShortTimeFourier(signal1, frameLength, overlap, Fs, window); + ShortTimeFourier stft = new ShortTimeFourier(signal1, frameLength, overlap); + stft.stft(); Complex[][] out = stft.getComplex(true); for (int c = 0; c < out[0].length; c++) { @@ -154,11 +148,9 @@ public void testSTFTPositive2() { int frameLength = 5; int overlap = 2; - double Fs = 100; - _Window window = new Rectangular(frameLength); - - ShortTimeFourier stft = new ShortTimeFourier(signal2, frameLength, overlap, Fs, window); + ShortTimeFourier stft = new ShortTimeFourier(signal2, frameLength, overlap); + stft.stft(); Complex[][] out = stft.getComplex(true); for (int c = 0; c < out[0].length; c++) { @@ -170,7 +162,7 @@ public void testSTFTPositive2() { } @Test - public void testSTFTPositiveHanning2() throws IOException { + public void testSTFTPositiveHanning2() { // Results calculated with MATLAB R2020b Update 4 9.9.0.1570001 // STFT result with Hanning window double[][] resultReal = {{2.884570e-02,1.632510e-01,4.102850e-01,7.584200e-01,1.175675e+00,1.596045e+00,1.910295e+00,1.973270e+00,1.641875e+00,8.522050e-01,-2.806675e-01,-1.398130e+00,-1.963815e+00,-1.531605e+00,-1.488520e-01,1.401120e+00,1.923850e+00,7.909860e-01,-1.138920e+00,-1.903460e+00,-4.631350e-01,1.587670e+00,1.475165e+00,-8.288486e-01,-1.818350e+00,2.933800e-01,1.862805e+00,-1.714030e-01,-1.831990e+00,4.708300e-01,1.665700e+00,-1.109751e+00}, @@ -182,11 +174,10 @@ public void testSTFTPositiveHanning2() throws IOException { int frameLength = 5; int overlap = 2; - double Fs = 100; _Window window = new Hanning(frameLength); - ShortTimeFourier stft = new ShortTimeFourier(signal2, frameLength, overlap, Fs, window); - + ShortTimeFourier stft = new ShortTimeFourier(signal2, frameLength, overlap, window); + stft.stft(); Complex[][] out = stft.getComplex(true); for (int c = 0; c < out[0].length; c++) { @@ -224,11 +215,9 @@ public void testSTFTZeroPad1() { int frameLength = 5; int overlap = 2; int fourierLength = 10; - double Fs = 100; - _Window window = new Rectangular(frameLength); - - ShortTimeFourier stft = new ShortTimeFourier(signal1, frameLength, overlap, Fs, window, fourierLength); + ShortTimeFourier stft = new ShortTimeFourier(signal1, frameLength, overlap, fourierLength); + stft.stft(); Complex[][] out = stft.getComplex(false); for (int c = 0; c < out[0].length; c++) { @@ -241,7 +230,7 @@ public void testSTFTZeroPad1() { @Test - public void testSpectrogram1() throws IOException { + public void testSpectrogram1() { // Results calculated with MATLAB R2020b Update 4 9.9.0.1570001 double[][] result = {{9.241823e-03,1.863796e-01,1.096377e+00,3.651806e+00,8.646658e+00,1.574979e+01,2.231089e+01,2.352134e+01,1.605180e+01,4.223415e+00,4.921934e-01,1.142007e+01,2.203326e+01,1.309521e+01,1.086111e-01,1.076116e+01,1.970978e+01,3.206829e+00,6.710887e+00,1.808240e+01,1.011956e+00,1.204437e+01,9.998324e+00,3.135050e+00,1.437729e+01,3.800229e-01,1.414572e+01,1.242619e-01,1.271443e+01,8.272812e-01,9.679615e+00,4.155246e+00}, {3.323504e-03,1.876349e-02,4.561394e-02,7.634659e-02,9.352740e-02,7.528352e-02,2.407124e-02,1.104424e-02,1.642750e-01,5.010167e-01,7.171670e-01,4.366268e-01,3.405834e-02,4.965162e-01,1.335685e+00,7.564458e-01,1.306034e-01,1.602582e+00,1.385886e+00,2.214512e-01,2.359549e+00,9.740138e-01,1.350965e+00,2.558534e+00,6.028153e-01,3.457613e+00,5.255550e-01,3.936524e+00,7.201615e-01,4.134963e+00,1.486898e+00,3.313898e+00}, @@ -251,10 +240,9 @@ public void testSpectrogram1() throws IOException { int frameLength = 5; int overlap = 2; - double Fs = 100; - _Window window = new Rectangular(frameLength); - ShortTimeFourier stft = new ShortTimeFourier(signal1, frameLength, overlap, Fs, window); + ShortTimeFourier stft = new ShortTimeFourier(signal1, frameLength, overlap); + stft.stft(); double[][] out = stft.spectrogram(false); for (int i = 0; i < result.length; i++) { @@ -263,7 +251,7 @@ public void testSpectrogram1() throws IOException { } @Test - public void testSpectrogram2() throws IOException { + public void testSpectrogram2() { // Results calculated with MATLAB R2020b Update 4 9.9.0.1570001 double[][] result = {{9.241823e-03,1.863796e-01,1.096377e+00,3.651806e+00,8.646658e+00,1.574979e+01,2.231089e+01,2.352134e+01,1.605180e+01,4.223415e+00,4.921934e-01,1.142007e+01,2.203326e+01,1.309521e+01,1.086111e-01,1.076116e+01,1.970978e+01,3.206829e+00,6.710887e+00,1.808240e+01,1.011956e+00,1.204437e+01,9.998324e+00,3.135050e+00,1.437729e+01,3.800229e-01,1.414572e+01,1.242619e-01,1.271443e+01,8.272812e-01,9.679615e+00,4.155246e+00}, {3.323504e-03,1.876349e-02,4.561394e-02,7.634659e-02,9.352740e-02,7.528352e-02,2.407124e-02,1.104424e-02,1.642750e-01,5.010167e-01,7.171670e-01,4.366268e-01,3.405834e-02,4.965162e-01,1.335685e+00,7.564458e-01,1.306034e-01,1.602582e+00,1.385886e+00,2.214512e-01,2.359549e+00,9.740138e-01,1.350965e+00,2.558534e+00,6.028153e-01,3.457613e+00,5.255550e-01,3.936524e+00,7.201615e-01,4.134963e+00,1.486898e+00,3.313898e+00}, @@ -273,10 +261,9 @@ public void testSpectrogram2() throws IOException { int frameLength = 5; int overlap = 2; - double Fs = 100; - _Window window = new Rectangular(frameLength); - ShortTimeFourier stft = new ShortTimeFourier(signal2, frameLength, overlap, Fs, window); + ShortTimeFourier stft = new ShortTimeFourier(signal2, frameLength, overlap); + stft.stft(); double[][] out = stft.spectrogram(false); for (int i = 0; i < result.length; i++) { @@ -285,7 +272,7 @@ public void testSpectrogram2() throws IOException { } @Test - public void testSpectrogramPositive1() throws IOException { + public void testSpectrogramPositive1() { // Results calculated with MATLAB R2020b Update 4 9.9.0.1570001 double[][] result = {{9.241823e-03,1.863796e-01,1.096377e+00,3.651806e+00,8.646658e+00,1.574979e+01,2.231089e+01,2.352134e+01,1.605180e+01,4.223415e+00,4.921934e-01,1.142007e+01,2.203326e+01,1.309521e+01,1.086111e-01,1.076116e+01,1.970978e+01,3.206829e+00,6.710887e+00,1.808240e+01,1.011956e+00,1.204437e+01,9.998324e+00,3.135050e+00,1.437729e+01,3.800229e-01,1.414572e+01,1.242619e-01,1.271443e+01,8.272812e-01,9.679615e+00,4.155246e+00}, {3.323504e-03,1.876349e-02,4.561394e-02,7.634659e-02,9.352740e-02,7.528352e-02,2.407124e-02,1.104424e-02,1.642750e-01,5.010167e-01,7.171670e-01,4.366268e-01,3.405834e-02,4.965162e-01,1.335685e+00,7.564458e-01,1.306034e-01,1.602582e+00,1.385886e+00,2.214512e-01,2.359549e+00,9.740138e-01,1.350965e+00,2.558534e+00,6.028153e-01,3.457613e+00,5.255550e-01,3.936524e+00,7.201615e-01,4.134963e+00,1.486898e+00,3.313898e+00}, @@ -293,10 +280,9 @@ public void testSpectrogramPositive1() throws IOException { int frameLength = 5; int overlap = 2; - double Fs = 100; - _Window window = new Rectangular(frameLength); - ShortTimeFourier stft = new ShortTimeFourier(signal1, frameLength, overlap, Fs, window); + ShortTimeFourier stft = new ShortTimeFourier(signal1, frameLength, overlap); + stft.stft(); double[][] out = stft.spectrogram(true); for (int i = 0; i < result.length; i++) { @@ -305,7 +291,7 @@ public void testSpectrogramPositive1() throws IOException { } @Test - public void testSpectrogramPositive2() throws IOException { + public void testSpectrogramPositive2() { // Results calculated with MATLAB R2020b Update 4 9.9.0.1570001 double[][] result = {{9.241823e-03,1.863796e-01,1.096377e+00,3.651806e+00,8.646658e+00,1.574979e+01,2.231089e+01,2.352134e+01,1.605180e+01,4.223415e+00,4.921934e-01,1.142007e+01,2.203326e+01,1.309521e+01,1.086111e-01,1.076116e+01,1.970978e+01,3.206829e+00,6.710887e+00,1.808240e+01,1.011956e+00,1.204437e+01,9.998324e+00,3.135050e+00,1.437729e+01,3.800229e-01,1.414572e+01,1.242619e-01,1.271443e+01,8.272812e-01,9.679615e+00,4.155246e+00}, {3.323504e-03,1.876349e-02,4.561394e-02,7.634659e-02,9.352740e-02,7.528352e-02,2.407124e-02,1.104424e-02,1.642750e-01,5.010167e-01,7.171670e-01,4.366268e-01,3.405834e-02,4.965162e-01,1.335685e+00,7.564458e-01,1.306034e-01,1.602582e+00,1.385886e+00,2.214512e-01,2.359549e+00,9.740138e-01,1.350965e+00,2.558534e+00,6.028153e-01,3.457613e+00,5.255550e-01,3.936524e+00,7.201615e-01,4.134963e+00,1.486898e+00,3.313898e+00}, @@ -313,10 +299,9 @@ public void testSpectrogramPositive2() throws IOException { int frameLength = 5; int overlap = 2; - double Fs = 100; - _Window window = new Rectangular(frameLength); - ShortTimeFourier stft = new ShortTimeFourier(signal2, frameLength, overlap, Fs, window); + ShortTimeFourier stft = new ShortTimeFourier(signal2, frameLength, overlap); + stft.stft(); double[][] out = stft.spectrogram(true); for (int i = 0; i < result.length; i++) { @@ -333,7 +318,7 @@ public void testFrequencyAxis1() { double Fs = 100; _Window window = new Rectangular(frameLength); - ShortTimeFourier stft = new ShortTimeFourier(signal1, frameLength, overlap, Fs, window); + ShortTimeFourier stft = new ShortTimeFourier(signal1, frameLength, overlap, frameLength, window, Fs); stft.stft(); double[] out = stft.getFrequencyAxis(false); @@ -349,7 +334,7 @@ public void testFrequencyAxis2() { double Fs = 100; _Window window = new Rectangular(frameLength); - ShortTimeFourier stft = new ShortTimeFourier(signal2, frameLength, overlap, Fs, window); + ShortTimeFourier stft = new ShortTimeFourier(signal2, frameLength, overlap, frameLength, window, Fs); stft.stft(); double[] out = stft.getFrequencyAxis(false); @@ -365,7 +350,7 @@ public void testFrequencyAxisPositive1() { double Fs = 100; _Window window = new Rectangular(frameLength); - ShortTimeFourier stft = new ShortTimeFourier(signal1, frameLength, overlap, Fs, window); + ShortTimeFourier stft = new ShortTimeFourier(signal1, frameLength, overlap, frameLength, window, Fs); stft.stft(); double[] out = stft.getFrequencyAxis(true); @@ -381,7 +366,7 @@ public void testFrequencyAxisPositive2() { double Fs = 100; _Window window = new Rectangular(frameLength); - ShortTimeFourier stft = new ShortTimeFourier(signal2, frameLength, overlap, Fs, window); + ShortTimeFourier stft = new ShortTimeFourier(signal2, frameLength, overlap, frameLength, window, Fs); stft.stft(); double[] out = stft.getFrequencyAxis(true); @@ -397,7 +382,7 @@ public void testTimeAxis1() { double Fs = 100; _Window window = new Rectangular(frameLength); - ShortTimeFourier stft = new ShortTimeFourier(signal1, frameLength, overlap, Fs, window); + ShortTimeFourier stft = new ShortTimeFourier(signal1, frameLength, overlap, frameLength, window, Fs); stft.stft(); double[] out = stft.getTimeAxis(); @@ -413,7 +398,7 @@ public void testTimeAxis2() { double Fs = 100; _Window window = new Rectangular(frameLength); - ShortTimeFourier stft = new ShortTimeFourier(signal2, frameLength, overlap, Fs, window); + ShortTimeFourier stft = new ShortTimeFourier(signal2, frameLength, overlap, frameLength, window, Fs); stft.stft(); double[] out = stft.getTimeAxis(); From b36ec55f245ad6b947740287fe765f1f7ba24699 Mon Sep 17 00:00:00 2001 From: Sibo Van Gool Date: Sun, 7 Nov 2021 19:19:54 +0100 Subject: [PATCH 07/10] [fixes #20] Change naming of InverseDiscreteFourier return methods The naming of this commit seems more standard on concise to me --- .../psambit9791/jdsp/filter/FIRWin2.java | 2 +- .../psambit9791/jdsp/signal/Resample.java | 2 +- .../psambit9791/jdsp/transform/Hilbert.java | 2 +- .../transform/InverseDiscreteFourier.java | 88 ++++++++++--------- .../jdsp/TestInverseDiscreteFourier.java | 24 ++--- 5 files changed, 63 insertions(+), 55 deletions(-) diff --git a/src/main/java/com/github/psambit9791/jdsp/filter/FIRWin2.java b/src/main/java/com/github/psambit9791/jdsp/filter/FIRWin2.java index 84993ce8..bbfea200 100644 --- a/src/main/java/com/github/psambit9791/jdsp/filter/FIRWin2.java +++ b/src/main/java/com/github/psambit9791/jdsp/filter/FIRWin2.java @@ -217,7 +217,7 @@ public double[] computeCoefficients(double[] cutoff, double[] gain) { // Perform the Inverse DFT InverseDiscreteFourier transform = new InverseDiscreteFourier(fx2D, true); transform.idft(); - double[] outFull = transform.getRealSignal(); + double[] outFull = transform.getReal(); Hamming w = new Hamming(this.numTaps); double[] window = w.getWindow(); diff --git a/src/main/java/com/github/psambit9791/jdsp/signal/Resample.java b/src/main/java/com/github/psambit9791/jdsp/signal/Resample.java index 245716f9..500494ae 100644 --- a/src/main/java/com/github/psambit9791/jdsp/signal/Resample.java +++ b/src/main/java/com/github/psambit9791/jdsp/signal/Resample.java @@ -195,7 +195,7 @@ private void resample_fft() { InverseDiscreteFourier idf = new InverseDiscreteFourier(Y, true); idf.idft(); - double[] y = idf.getRealSignal(); + double[] y = idf.getReal(); this.output = MathArrays.scale((float)this.num/(float)Nx, y); } diff --git a/src/main/java/com/github/psambit9791/jdsp/transform/Hilbert.java b/src/main/java/com/github/psambit9791/jdsp/transform/Hilbert.java index be33b0cd..e5e39dca 100644 --- a/src/main/java/com/github/psambit9791/jdsp/transform/Hilbert.java +++ b/src/main/java/com/github/psambit9791/jdsp/transform/Hilbert.java @@ -76,7 +76,7 @@ public void hilbertTransform() { InverseDiscreteFourier idft = new InverseDiscreteFourier(modOut, false); idft.idft(); - this.output = idft.getAsComplex(); + this.output = idft.getComplex(); } /** diff --git a/src/main/java/com/github/psambit9791/jdsp/transform/InverseDiscreteFourier.java b/src/main/java/com/github/psambit9791/jdsp/transform/InverseDiscreteFourier.java index 0eb2df7e..84348146 100644 --- a/src/main/java/com/github/psambit9791/jdsp/transform/InverseDiscreteFourier.java +++ b/src/main/java/com/github/psambit9791/jdsp/transform/InverseDiscreteFourier.java @@ -17,7 +17,7 @@ /** *

Inverse Discrete Fourier Transform

* The InverseDiscreteFourier class applies the inverse discrete fourier transform on the input sequence (real/complex) and - * provides different representations of the reconstructed signal to be returned (real signal, complex signal, absolute signal) + * provides different representations of the reconstructed signal to be returned (real signal, complex signal, ...) *

* * @author Sambit Paul @@ -79,84 +79,86 @@ public void idft() { } /** - * This method returns the real part of the generated signal. + * This method returns the complex value of the generated signal as a Complex array. * @throws java.lang.ExceptionInInitializerError if called before executing idft() method - * @return double[] The signal (real part) + * @return double[] The signal (complex) */ - public double[] getRealSignal() throws ExceptionInInitializerError { - if (this.signal == null) { - throw new ExceptionInInitializerError("Execute idft() function before returning result"); - } - double[] ret = new double[this.signal.length]; + protected Complex[] getComplex() throws ExceptionInInitializerError { + checkOutput(); + return this.signal; + } + + /** + * This method returns the complex value of the generated signal as a 2D matrix. + * @throws java.lang.ExceptionInInitializerError if called before executing idft() method + * @return double[][] The signal (complex) + */ + public double[][] getComplex2D() throws ExceptionInInitializerError { + checkOutput(); + double[][] ret = new double[this.signal.length][2]; for (int i=0; i Date: Sun, 7 Nov 2021 19:21:51 +0100 Subject: [PATCH 08/10] [fixes #20] Change naming of DiscreteFourier getFull method The naming of this commit seems more standard on concise to me --- .../java/com/github/psambit9791/jdsp/signal/Resample.java | 2 +- .../psambit9791/jdsp/transform/DiscreteFourier.java | 4 ++-- .../com/github/psambit9791/jdsp/transform/Hilbert.java | 2 +- .../com/github/psambit9791/jdsp/TestDiscreteFourier.java | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/github/psambit9791/jdsp/signal/Resample.java b/src/main/java/com/github/psambit9791/jdsp/signal/Resample.java index 500494ae..66647c2f 100644 --- a/src/main/java/com/github/psambit9791/jdsp/signal/Resample.java +++ b/src/main/java/com/github/psambit9791/jdsp/signal/Resample.java @@ -170,7 +170,7 @@ private void resample_fft() { DiscreteFourier df = new DiscreteFourier(this.signal); df.dft(); - double[][] X = df.getFull(true); + double[][] X = df.getComplex2D(true); double[][] Y = new double[this.num/2+1][2]; int N = Math.min(this.num, Nx); diff --git a/src/main/java/com/github/psambit9791/jdsp/transform/DiscreteFourier.java b/src/main/java/com/github/psambit9791/jdsp/transform/DiscreteFourier.java index 7e558833..f7d743a2 100644 --- a/src/main/java/com/github/psambit9791/jdsp/transform/DiscreteFourier.java +++ b/src/main/java/com/github/psambit9791/jdsp/transform/DiscreteFourier.java @@ -134,12 +134,12 @@ public double[][] getMagPhaseDeg(boolean onlyPositive) throws ExceptionInInitial } /** - * Returns the complex value of the discrete fourier transformed sequence + * Returns the complex value of the discrete fourier transformed sequence as a 2D matrix * @param onlyPositive Set to True if non-mirrored output is required * @throws java.lang.ExceptionInInitializerError if called before executing dft() method * @return double[][] The complex DFT output; first array column = real part; second array column = imaginary part */ - public double[][] getFull(boolean onlyPositive) throws ExceptionInInitializerError { + public double[][] getComplex2D(boolean onlyPositive) throws ExceptionInInitializerError { Complex[] dftout = getComplex(onlyPositive); return UtilMethods.complexTo2D(dftout); } diff --git a/src/main/java/com/github/psambit9791/jdsp/transform/Hilbert.java b/src/main/java/com/github/psambit9791/jdsp/transform/Hilbert.java index e5e39dca..d3ccd5d1 100644 --- a/src/main/java/com/github/psambit9791/jdsp/transform/Hilbert.java +++ b/src/main/java/com/github/psambit9791/jdsp/transform/Hilbert.java @@ -65,7 +65,7 @@ private void fillH() { public void hilbertTransform() { DiscreteFourier dft = new DiscreteFourier(this.signal); dft.dft(); - double[][] dftOut = dft.getFull(false); + double[][] dftOut = dft.getComplex2D(false); double[][] modOut = new double[dftOut.length][dftOut[0].length]; diff --git a/src/test/java/com/github/psambit9791/jdsp/TestDiscreteFourier.java b/src/test/java/com/github/psambit9791/jdsp/TestDiscreteFourier.java index 10a44968..9bb25c9f 100644 --- a/src/test/java/com/github/psambit9791/jdsp/TestDiscreteFourier.java +++ b/src/test/java/com/github/psambit9791/jdsp/TestDiscreteFourier.java @@ -264,7 +264,7 @@ public void testFourierFullPositive1(){ DiscreteFourier fft1 = new DiscreteFourier(this.signal1); fft1.dft(); - double[][] out = fft1.getFull(true); + double[][] out = fft1.getComplex2D(true); double[] outReal = new double[out.length]; double[] outIm = new double[out.length]; @@ -296,7 +296,7 @@ public void testFourierFullPositive2(){ DiscreteFourier fft1 = new DiscreteFourier(this.signal2); fft1.dft(); - double[][] out = fft1.getFull(true); + double[][] out = fft1.getComplex2D(true); double[] outReal = new double[out.length]; double[] outIm = new double[out.length]; @@ -328,7 +328,7 @@ public void testFourierFull1(){ DiscreteFourier fft1 = new DiscreteFourier(this.signal1); fft1.dft(); - double[][] out = fft1.getFull(false); + double[][] out = fft1.getComplex2D(false); double[] outReal = new double[out.length]; double[] outIm = new double[out.length]; @@ -373,7 +373,7 @@ public void testFourierFull2(){ DiscreteFourier fft1 = new DiscreteFourier(this.signal2); fft1.dft(); - double[][] out = fft1.getFull(false); + double[][] out = fft1.getComplex2D(false); double[] outReal = new double[out.length]; double[] outIm = new double[out.length]; From a5ba82d871eb376929bded7ad771aaa9b057666a Mon Sep 17 00:00:00 2001 From: Sibo Van Gool Date: Sun, 7 Nov 2021 20:20:54 +0100 Subject: [PATCH 09/10] [fixes #20] Fix documentation error --- .../psambit9791/jdsp/transform/InverseDiscreteFourier.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/github/psambit9791/jdsp/transform/InverseDiscreteFourier.java b/src/main/java/com/github/psambit9791/jdsp/transform/InverseDiscreteFourier.java index 84348146..e7cf746e 100644 --- a/src/main/java/com/github/psambit9791/jdsp/transform/InverseDiscreteFourier.java +++ b/src/main/java/com/github/psambit9791/jdsp/transform/InverseDiscreteFourier.java @@ -81,7 +81,7 @@ public void idft() { /** * This method returns the complex value of the generated signal as a Complex array. * @throws java.lang.ExceptionInInitializerError if called before executing idft() method - * @return double[] The signal (complex) + * @return Complex[] The signal (complex) */ protected Complex[] getComplex() throws ExceptionInInitializerError { checkOutput(); From 06b8cd271514be11dd599681e07b3e3765d0aee8 Mon Sep 17 00:00:00 2001 From: Sibo Van Gool Date: Sun, 7 Nov 2021 20:23:39 +0100 Subject: [PATCH 10/10] [fixes #20] Implement ISTFT --- .../transform/InverseShortTimeFourier.java | 225 +++++++++++++++ .../jdsp/TestInverseShortTimeFourier.java | 260 ++++++++++++++++++ 2 files changed, 485 insertions(+) create mode 100644 src/main/java/com/github/psambit9791/jdsp/transform/InverseShortTimeFourier.java create mode 100644 src/test/java/com/github/psambit9791/jdsp/TestInverseShortTimeFourier.java diff --git a/src/main/java/com/github/psambit9791/jdsp/transform/InverseShortTimeFourier.java b/src/main/java/com/github/psambit9791/jdsp/transform/InverseShortTimeFourier.java new file mode 100644 index 00000000..ec318cac --- /dev/null +++ b/src/main/java/com/github/psambit9791/jdsp/transform/InverseShortTimeFourier.java @@ -0,0 +1,225 @@ +package com.github.psambit9791.jdsp.transform; + +import com.github.psambit9791.jdsp.misc.UtilMethods; +import com.github.psambit9791.jdsp.windows.Rectangular; +import com.github.psambit9791.jdsp.windows._Window; +import org.apache.commons.math3.complex.Complex; + +import java.util.Arrays; + +// TODO: once FFT is implemented, replace all DFT stuff in here with DFT +/** + *

Inverse Short Time Fourier Transform

+ * The InversShortTimeFourier class applies the inverse short time fourier transform on an input sequence and + * provides different representations of the reconstructed signal to be returned (real signal, complex signal, ...) + *

+ * + * @author Sibo Van Gool + * @version 1.0 + */ +public class InverseShortTimeFourier { + private final DiscreteFourier[] signal; + private final int overlap; + private final int frameLength; + private final _Window window; + private Complex[] output; + + /** + * This constructor initialises the prerequisites required to use InverseShortTimeFourier. + * @param signal STFT signal to be converted + * @param frameLength length of the frame used in the STFT + * if this is smaller than the DiscreteFourier length of signal, then this means that + * the signal was zero-padded in the STFT + * @param overlap number of overlap frames used in the STFT + * @param window window used in the STFT + */ + public InverseShortTimeFourier(DiscreteFourier[] signal, int frameLength, int overlap, _Window window) { + if (signal == null) { + throw new IllegalArgumentException("Signal can not be null"); + } + if (frameLength > signal[0].getComplex(false).length) { + throw new IllegalArgumentException("Frame length can not be larger than signal Fourier length"); + } + if (overlap >= frameLength) { + throw new IllegalArgumentException("Overlap must be smaller than frame length"); + } + if (window == null) { + throw new IllegalArgumentException("Window can not be null"); + } + if (window.getWindow().length != frameLength) { + throw new IllegalArgumentException("Window and frame dimensions must match"); + } + this.signal = signal; + this.frameLength = frameLength; + this.overlap = overlap; + this.window = window; + } + + /** + * This constructor initialises the prerequisites required to use InverseShortTimeFourier. + * Defaults window to rectangular window (= no windowing effect) + * @param signal STFT signal to be converted + * @param frameLength length of the frame used in the STFT + * if this is smaller than the DiscreteFourier length of signal, then this means that + * the signal was zero-padded in the STFT + * @param overlap number of overlap frames used in the STFT + */ + public InverseShortTimeFourier(DiscreteFourier[] signal, int frameLength, int overlap) { + this(signal, frameLength, overlap, new Rectangular(frameLength)); + } + + /** + * This constructor initialises the prerequisites required to use InverseShortTimeFourier. + * Defaults window to rectangular window (= no windowing effect) and overlap to 50% (= 1/2 of the number of samples + * in frameLength) + * @param signal STFT signal to be converted + * @param frameLength length of the frame used in the STFT + * if this is smaller than the DiscreteFourier length of signal, then this means that + * the signal was zero-padded in the STFT + */ + public InverseShortTimeFourier(DiscreteFourier[] signal, int frameLength) { + this(signal, frameLength, frameLength/2, + new Rectangular(frameLength)); + } + + /** + * This constructor initialises the prerequisites required to use InverseShortTimeFourier. + * Defaults window to rectangular window (= no windowing effect) and overlap to 50% (= 1/2 of the number of samples + * in frameLength), and frame length to the DiscreteFourier length of signal + * @param signal STFT signal to be converted + */ + public InverseShortTimeFourier(DiscreteFourier[] signal) { + this(signal, signal[0].getComplex(false).length, signal[0].getComplex(false).length/2, + new Rectangular(signal[0].getComplex(false).length)); + } + + /** + * This function performs the inverse discrete fourier transform on the input sequence + */ + public void istft() { + int signalLength = (int)Math.floor((this.signal.length - 1) * (frameLength - this.overlap) + frameLength); + this.output = new Complex[signalLength]; + + int[] averageDivisor = new int[signalLength]; // Stores output average divisor + Arrays.fill(averageDivisor, 1); + int arrPastePosition = 0; + + // Flag that checks whether the window contained a zero-value. If true, a warning message will be print about + // irretrievable loss of the signal. + boolean dataLost = false; + + for (DiscreteFourier dtft : this.signal) { + double[][] seq = UtilMethods.complexTo2D(dtft.getComplex(false)); + InverseDiscreteFourier idft = new InverseDiscreteFourier(seq, false); + idft.idft(); + Complex[] idft_result = idft.getComplex(); + + // Fill in output signal + for (int i = 0; i < this.frameLength; i++) { // Last elements of idft_result are zeroes from zero-padding, so only parse until this.frameLength + double windowVal = this.window.getWindow()[i]; + double real = idft_result[i].getReal(); + double imaginary = idft_result[i].getImaginary(); + + // Inverse the windowing effect + if (windowVal != 0) { + real = real / windowVal; + } + else { + dataLost = true; + } + + // Do a summation of output-values, for overlapping frames on the same output-element + if (this.output[i + arrPastePosition] != null) { + averageDivisor[i + arrPastePosition] += 1; + real = real + this.output[i + arrPastePosition].getReal(); + imaginary = imaginary + this.output[i + arrPastePosition].getImaginary(); + } + + this.output[i + arrPastePosition] = new Complex(real, imaginary); + } + arrPastePosition += frameLength - overlap; + } + + // Overlapping frames sum their value on the same output-element, which needs to be averaged in this step + for (int i = 0; i < averageDivisor.length; i++) { + if (averageDivisor[i] > 1) { + double real = this.output[i].getReal()/averageDivisor[i]; + double imaginary = this.output[i].getImaginary()/averageDivisor[i]; + this.output[i] = new Complex(real, imaginary); + } + } + + if (dataLost) { + System.err.println("The original window function contained a zero-element, which causes some of the data to be irretrievably lost."); + } + } + + /** + * This method returns the complex value of the generated time signal as a Complex array. + * @throws java.lang.ExceptionInInitializerError if called before executing istft() method + * @return Complex[] The inverse STFT time signal + */ + public Complex[] getComplex() throws ExceptionInInitializerError { + checkOutput(); + return this.output; + } + + /** + * This method returns the complex value of the generated time signal as a 2D matrix. + * @throws java.lang.ExceptionInInitializerError if called before executing istft() method + * @return double[][] The inverse STFT time signal; first array column = real part; second array column = imaginary part + */ + public double[][] getComplex2D() throws ExceptionInInitializerError { + return UtilMethods.complexTo2D(getComplex()); + } + + /** + * This method returns the real part of the generated time signal. + * @throws java.lang.ExceptionInInitializerError if called before executing istft() method + * @return double[] The real part of the inverse STFT time signal + */ + public double[] getReal() throws ExceptionInInitializerError { + checkOutput(); + return Arrays.stream(this.output).mapToDouble(Complex :: getReal).toArray(); + } + + /** + * This method returns the imaginary part of the generated time signal. + * @throws java.lang.ExceptionInInitializerError if called before executing istft() method + * @return double[] The imaginary part of the inverse STFT time signal + */ + public double[] getImaginary() throws ExceptionInInitializerError { + checkOutput(); + return Arrays.stream(this.output).mapToDouble(Complex :: getImaginary).toArray(); + } + + /** + * This method returns the magnitude of the generated time signal. + * @throws java.lang.ExceptionInInitializerError if called before executing istft() method + * @return double[] The magnitude of the inverse STFT time signal + */ + public double[] getMagnitude() throws ExceptionInInitializerError { + checkOutput(); + return Arrays.stream(this.output).mapToDouble(Complex :: abs).toArray(); + } + + /** + * This method returns the phase of the generated time signal. + * @throws java.lang.ExceptionInInitializerError if called before executing istft() method + * @return double[] The phase of the inverse STFT time signal + */ + public double[] getPhase() throws ExceptionInInitializerError { + checkOutput(); + return Arrays.stream(this.output).mapToDouble(Complex :: getArgument).toArray(); + } + + /** + * Checks whether the ISTFT has been calculated yet + * @throws ExceptionInInitializerError if result hasn't been calculated yet + */ + private void checkOutput() throws ExceptionInInitializerError { + if (this.output == null) { + throw new ExceptionInInitializerError("Execute istft() function before returning result"); + } + } +} diff --git a/src/test/java/com/github/psambit9791/jdsp/TestInverseShortTimeFourier.java b/src/test/java/com/github/psambit9791/jdsp/TestInverseShortTimeFourier.java new file mode 100644 index 00000000..47cebba6 --- /dev/null +++ b/src/test/java/com/github/psambit9791/jdsp/TestInverseShortTimeFourier.java @@ -0,0 +1,260 @@ +package com.github.psambit9791.jdsp; + +import com.github.psambit9791.jdsp.transform.DiscreteFourier; +import com.github.psambit9791.jdsp.transform.InverseShortTimeFourier; +import com.github.psambit9791.jdsp.transform.ShortTimeFourier; +import com.github.psambit9791.jdsp.windows.Bartlett; +import com.github.psambit9791.jdsp.windows.Hamming; +import com.github.psambit9791.jdsp.windows._Window; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.OutputStream; +import java.io.PrintStream; + +public class TestInverseShortTimeFourier { + private final double[] signal1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}; + + // 20Hz Sine + 200Hz Sine sampled @ 100Hz with Nyquist of 50Hz + private final double[] signal2 = {0. , 0.658, 0.293, -0.04 , 0.634, 1.212, 0.756, 0.402, 1.035, 1.482, 0.901, + 0.496, 1.062, 1.362, 0.655, 0.208, 0.718, 0.895, 0.1 , -0.346, 0.154, 0.26 , -0.562, -0.943, + -0.396, -0.302, -1.085, -1.344, -0.703, -0.576, -1.279, -1.384, -0.632, -0.463, -1.08 , -1.04 , -0.2 , + -0.017, -0.579, -0.437, 0.437, 0.579, 0.017, 0.2 , 1.04 , 1.08 , 0.463, 0.632, 1.384, 1.279, + 0.576, 0.703, 1.344, 1.085, 0.302, 0.396, 0.943, 0.562, -0.26 , -0.154, 0.346, -0.1 , -0.895, -0.718, + -0.208, -0.655, -1.362, -1.062, -0.496, -0.901, -1.482, -1.035, -0.402, -0.756, -1.212, -0.634, 0.04 , + -0.293, -0.658, 0.}; + + @Test + public void testInverseShortTimeFourierOverlap1() { + double[] expected = signal1; + + int frameLength = 10; + + ShortTimeFourier stft = new ShortTimeFourier(this.signal1, frameLength); + stft.stft(); + DiscreteFourier[] dfts = stft.getOutput(); + InverseShortTimeFourier istft = new InverseShortTimeFourier(dfts); + istft.istft(); + double[] outputReal = istft.getReal(); + + Assertions.assertArrayEquals(expected, outputReal, 0.001); + } + + @Test + public void testInverseShortTimeFourierOverlap2() { + double[] expected = signal2; + + int frameLength = 10; + + ShortTimeFourier stft = new ShortTimeFourier(this.signal2, frameLength); + stft.stft(); + DiscreteFourier[] dfts = stft.getOutput(); + InverseShortTimeFourier istft = new InverseShortTimeFourier(dfts); + istft.istft(); + double[] outputReal = istft.getReal(); + + Assertions.assertArrayEquals(expected, outputReal, 0.001); + } + + @Test + public void testInverseShortTimeFourierNoOverlap1() { + double[] expected = signal1; + + int frameLength = 10; + int overlap = 0; + + ShortTimeFourier stft = new ShortTimeFourier(this.signal1, frameLength, overlap); + stft.stft(); + DiscreteFourier[] dfts = stft.getOutput(); + InverseShortTimeFourier istft = new InverseShortTimeFourier(dfts, frameLength, overlap); + istft.istft(); + double[] outputReal = istft.getReal(); + + Assertions.assertArrayEquals(expected, outputReal, 0.001); + } + + @Test + public void testInverseShortTimeFourierNoOverlap2() { + double[] expected = signal2; + + int frameLength = 10; + int overlap = 0; + + ShortTimeFourier stft = new ShortTimeFourier(this.signal2, frameLength, overlap); + stft.stft(); + DiscreteFourier[] dfts = stft.getOutput(); + InverseShortTimeFourier istft = new InverseShortTimeFourier(dfts, frameLength, overlap); + istft.istft(); + double[] outputReal = istft.getReal(); + + Assertions.assertArrayEquals(expected, outputReal, 0.001); + } + + @Test + public void testInverseShortTimeFourierZeroPadOverlap1() { + double[] expected = signal1; + + int frameLength = 10; + int overlap = frameLength/2; + int fourierLength = 17; + + ShortTimeFourier stft = new ShortTimeFourier(this.signal1, frameLength, overlap, fourierLength); + stft.stft(); + DiscreteFourier[] dfts = stft.getOutput(); + InverseShortTimeFourier istft = new InverseShortTimeFourier(dfts, frameLength, overlap); + istft.istft(); + double[] outputReal = istft.getReal(); + + Assertions.assertArrayEquals(expected, outputReal, 0.001); + } + + @Test + public void testInverseShortTimeFourierZeroPadOverlap2() { + double[] expected = signal2; + + int frameLength = 10; + int overlap = frameLength/2; + int fourierLength = 17; + + ShortTimeFourier stft = new ShortTimeFourier(this.signal2, frameLength, overlap, fourierLength); + stft.stft(); + DiscreteFourier[] dfts = stft.getOutput(); + InverseShortTimeFourier istft = new InverseShortTimeFourier(dfts, frameLength); + istft.istft(); + double[] outputReal = istft.getReal(); + + Assertions.assertArrayEquals(expected, outputReal, 0.001); + } + + @Test + public void testInverseShortTimeFourierZeroPadNoOverlap1() { + double[] expected = signal1; + + int frameLength = 10; + int fourierLength = 17; + int overlap = 0; + + ShortTimeFourier stft = new ShortTimeFourier(this.signal1, frameLength, overlap, fourierLength); + stft.stft(); + DiscreteFourier[] dfts = stft.getOutput(); + InverseShortTimeFourier istft = new InverseShortTimeFourier(dfts, frameLength, overlap); + istft.istft(); + double[] outputReal = istft.getReal(); + + Assertions.assertArrayEquals(expected, outputReal, 0.001); + } + + @Test + public void testInverseShortTimeFourierZeroPadNoOverlap2() { + double[] expected = signal2; + + int frameLength = 10; + int fourierLength = 17; + int overlap = 0; + + ShortTimeFourier stft = new ShortTimeFourier(this.signal2, frameLength, overlap, fourierLength); + stft.stft(); + DiscreteFourier[] dfts = stft.getOutput(); + InverseShortTimeFourier istft = new InverseShortTimeFourier(dfts, frameLength, overlap); + istft.istft(); + double[] outputReal = istft.getReal(); + + Assertions.assertArrayEquals(expected, outputReal, 0.001); + } + + @Test + public void testInverseShortTimeFourierHamming1() { + double[] expected = signal1; + + int frameLength = 10; + int fourierLength = 17; + int overlap = 0; + _Window window = new Hamming(frameLength); + + ShortTimeFourier stft = new ShortTimeFourier(this.signal1, frameLength, overlap, fourierLength, window); + stft.stft(); + DiscreteFourier[] dfts = stft.getOutput(); + InverseShortTimeFourier istft = new InverseShortTimeFourier(dfts, frameLength, overlap, window); + istft.istft(); + double[] outputReal = istft.getReal(); + + Assertions.assertArrayEquals(expected, outputReal, 0.001); + } + + @Test + public void testInverseShortTimeFourierHamming2() { + double[] expected = signal2; + + int frameLength = 10; + int fourierLength = 17; + int overlap = 0; + _Window window = new Hamming(frameLength); + + ShortTimeFourier stft = new ShortTimeFourier(this.signal2, frameLength, overlap, fourierLength, window); + stft.stft(); + DiscreteFourier[] dfts = stft.getOutput(); + InverseShortTimeFourier istft = new InverseShortTimeFourier(dfts, frameLength, overlap, window); + istft.istft(); + double[] outputReal = istft.getReal(); + + Assertions.assertArrayEquals(expected, outputReal, 0.001); + } + + @Test + public void testInverseShortTimeFourierBartlett1() { + double[] expected = {0, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 12, 13, 14, 15, 16, 17, 18, 19, 0}; + + int frameLength = 10; + int fourierLength = 17; + int overlap = 0; + _Window window = new Bartlett(frameLength); + + ShortTimeFourier stft = new ShortTimeFourier(this.signal1, frameLength, overlap, fourierLength, window); + stft.stft(); + DiscreteFourier[] dfts = stft.getOutput(); + InverseShortTimeFourier istft = new InverseShortTimeFourier(dfts, frameLength, overlap, window); + // We don't want the System.err.println message + PrintStream stderr = System.err; // Save standard stderr + System.setErr(new PrintStream(new OutputStream() { + @Override + public void write(int b) { } + })); + istft.istft(); + System.setErr(stderr); // Reset to standard stderr + double[] outputReal = istft.getReal(); + + Assertions.assertArrayEquals(expected, outputReal, 0.001); + } + + @Test + public void testInverseShortTimeFourierBartlett2() { + double[] expected = {0, 0.658, 0.293, -0.04 , 0.634, 1.212, 0.756, 0.402, 1.035, 0, 0, + 0.496, 1.062, 1.362, 0.655, 0.208, 0.718, 0.895, 0.1 , 0, 0, 0.26 , -0.562, -0.943, + -0.396, -0.302, -1.085, -1.344, -0.703, 0, 0, -1.384, -0.632, -0.463, -1.08 , -1.04 , -0.2 , + -0.017, -0.579, 0, 0, 0.579, 0.017, 0.2 , 1.04 , 1.08 , 0.463, 0.632, 1.384, 0, + 0, 0.703, 1.344, 1.085, 0.302, 0.396, 0.943, 0.562, -0.26 , 0, 0, -0.1 , -0.895, -0.718, + -0.208, -0.655, -1.362, -1.062, -0.496, 0, 0, -1.035, -0.402, -0.756, -1.212, -0.634, 0.04 , + -0.293, -0.658, 0}; + + int frameLength = 10; + int fourierLength = 17; + int overlap = 0; + _Window window = new Bartlett(frameLength); + + ShortTimeFourier stft = new ShortTimeFourier(this.signal2, frameLength, overlap, fourierLength, window); + stft.stft(); + DiscreteFourier[] dfts = stft.getOutput(); + InverseShortTimeFourier istft = new InverseShortTimeFourier(dfts, frameLength, overlap, window); + // We don't want the System.err.println message + PrintStream stderr = System.err; // Save standard stderr + System.setErr(new PrintStream(new OutputStream() { + @Override + public void write(int b) { } + })); + istft.istft(); + System.setErr(stderr); // Reset to standard stderr + double[] outputReal = istft.getReal(); + + Assertions.assertArrayEquals(expected, outputReal, 0.001); + } +}