mirror of
https://github.com/xdsopl/robot36.git
synced 2025-12-06 07:12:07 +01:00
experimenting with STFT
This commit is contained in:
parent
7f503ecef3
commit
f081bbddfd
353
app/src/main/java/xdsopl/robot36/FastFourierTransform.java
Normal file
353
app/src/main/java/xdsopl/robot36/FastFourierTransform.java
Normal file
|
|
@ -0,0 +1,353 @@
|
||||||
|
/*
|
||||||
|
Fast Fourier Transform
|
||||||
|
|
||||||
|
Copyright 2025 Ahmet Inan <xdsopl@gmail.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package xdsopl.robot36;
|
||||||
|
|
||||||
|
public class FastFourierTransform {
|
||||||
|
private final Complex[] tf;
|
||||||
|
private final Complex tmpA, tmpB, tmpC, tmpD, tmpE, tmpF, tmpG, tmpH, tmpI, tmpJ, tmpK, tmpL, tmpM;
|
||||||
|
private final Complex tin0, tin1, tin2, tin3, tin4, tin5, tin6;
|
||||||
|
|
||||||
|
FastFourierTransform(int length) {
|
||||||
|
int rest = length;
|
||||||
|
while (rest > 1) {
|
||||||
|
if (rest % 2 == 0)
|
||||||
|
rest /= 2;
|
||||||
|
else if (rest % 3 == 0)
|
||||||
|
rest /= 3;
|
||||||
|
else if (rest % 5 == 0)
|
||||||
|
rest /= 5;
|
||||||
|
else if (rest % 7 == 0)
|
||||||
|
rest /= 7;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (rest != 1)
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Transform length must be a composite of 2, 3, 5 and 7, but was: "
|
||||||
|
+ length);
|
||||||
|
tf = new Complex[length];
|
||||||
|
for (int i = 0; i < length; ++i) {
|
||||||
|
double x = -(2.0 * Math.PI * i) / length;
|
||||||
|
float a = (float) Math.cos(x);
|
||||||
|
float b = (float) Math.sin(x);
|
||||||
|
tf[i] = new Complex(a, b);
|
||||||
|
}
|
||||||
|
tmpA = new Complex();
|
||||||
|
tmpB = new Complex();
|
||||||
|
tmpC = new Complex();
|
||||||
|
tmpD = new Complex();
|
||||||
|
tmpE = new Complex();
|
||||||
|
tmpF = new Complex();
|
||||||
|
tmpG = new Complex();
|
||||||
|
tmpH = new Complex();
|
||||||
|
tmpI = new Complex();
|
||||||
|
tmpJ = new Complex();
|
||||||
|
tmpK = new Complex();
|
||||||
|
tmpL = new Complex();
|
||||||
|
tmpM = new Complex();
|
||||||
|
tin0 = new Complex();
|
||||||
|
tin1 = new Complex();
|
||||||
|
tin2 = new Complex();
|
||||||
|
tin3 = new Complex();
|
||||||
|
tin4 = new Complex();
|
||||||
|
tin5 = new Complex();
|
||||||
|
tin6 = new Complex();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isPowerOfTwo(int n) {
|
||||||
|
return n > 0 && (n & (n - 1)) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isPowerOfFour(int n) {
|
||||||
|
return isPowerOfTwo(n) && (n & 0x55555555) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float cos(int n, int N) {
|
||||||
|
return (float) Math.cos(n * 2.0 * Math.PI / N);
|
||||||
|
}
|
||||||
|
|
||||||
|
private float sin(int n, int N) {
|
||||||
|
return (float) Math.sin(n * 2.0 * Math.PI / N);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dft2(Complex out0, Complex out1, Complex in0, Complex in1) {
|
||||||
|
out0.set(in0).add(in1);
|
||||||
|
out1.set(in0).sub(in1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void radix2(Complex[] out, Complex[] in, int O, int I, int N, int S, boolean F) {
|
||||||
|
if (N == 2) {
|
||||||
|
dft2(out[O], out[O + 1], in[I], in[I + S]);
|
||||||
|
} else {
|
||||||
|
int Q = N / 2;
|
||||||
|
dit(out, in, O, I, Q, 2 * S, F);
|
||||||
|
dit(out, in, O + Q, I + S, Q, 2 * S, F);
|
||||||
|
for (int k0 = O, k1 = O + Q, l1 = 0; k0 < O + Q; ++k0, ++k1, l1 += S) {
|
||||||
|
tin1.set(tf[l1]);
|
||||||
|
if (!F)
|
||||||
|
tin1.conj();
|
||||||
|
tin0.set(out[k0]);
|
||||||
|
tin1.mul(out[k1]);
|
||||||
|
dft2(out[k0], out[k1], tin0, tin1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fwd3(Complex out0, Complex out1, Complex out2, Complex in0, Complex in1, Complex in2) {
|
||||||
|
tmpA.set(in1).add(in2);
|
||||||
|
tmpB.set(in1.imag - in2.imag, in2.real - in1.real);
|
||||||
|
tmpC.set(tmpA).mul(cos(1, 3));
|
||||||
|
tmpD.set(tmpB).mul(sin(1, 3));
|
||||||
|
out0.set(in0).add(tmpA);
|
||||||
|
out1.set(in0).add(tmpC).add(tmpD);
|
||||||
|
out2.set(in0).add(tmpC).sub(tmpD);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void radix3(Complex[] out, Complex[] in, int O, int I, int N, int S, boolean F) {
|
||||||
|
if (N == 3) {
|
||||||
|
if (F)
|
||||||
|
fwd3(out[O], out[O + 1], out[O + 2],
|
||||||
|
in[I], in[I + S], in[I + 2 * S]);
|
||||||
|
else
|
||||||
|
fwd3(out[O], out[O + 2], out[O + 1],
|
||||||
|
in[I], in[I + S], in[I + 2 * S]);
|
||||||
|
} else {
|
||||||
|
int Q = N / 3;
|
||||||
|
dit(out, in, O, I, Q, 3 * S, F);
|
||||||
|
dit(out, in, O + Q, I + S, Q, 3 * S, F);
|
||||||
|
dit(out, in, O + 2 * Q, I + 2 * S, Q, 3 * S, F);
|
||||||
|
for (int k0 = O, k1 = O + Q, k2 = O + 2 * Q, l1 = 0, l2 = 0;
|
||||||
|
k0 < O + Q; ++k0, ++k1, ++k2, l1 += S, l2 += 2 * S) {
|
||||||
|
tin1.set(tf[l1]);
|
||||||
|
tin2.set(tf[l2]);
|
||||||
|
if (!F) {
|
||||||
|
tin1.conj();
|
||||||
|
tin2.conj();
|
||||||
|
}
|
||||||
|
tin0.set(out[k0]);
|
||||||
|
tin1.mul(out[k1]);
|
||||||
|
tin2.mul(out[k2]);
|
||||||
|
if (F)
|
||||||
|
fwd3(out[k0], out[k1], out[k2], tin0, tin1, tin2);
|
||||||
|
else
|
||||||
|
fwd3(out[k0], out[k2], out[k1], tin0, tin1, tin2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fwd4(Complex out0, Complex out1, Complex out2, Complex out3,
|
||||||
|
Complex in0, Complex in1, Complex in2, Complex in3) {
|
||||||
|
tmpA.set(in0).add(in2);
|
||||||
|
tmpB.set(in0).sub(in2);
|
||||||
|
tmpC.set(in1).add(in3);
|
||||||
|
tmpD.set(in1.imag - in3.imag, in3.real - in1.real);
|
||||||
|
out0.set(tmpA).add(tmpC);
|
||||||
|
out1.set(tmpB).add(tmpD);
|
||||||
|
out2.set(tmpA).sub(tmpC);
|
||||||
|
out3.set(tmpB).sub(tmpD);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void radix4(Complex[] out, Complex[] in, int O, int I, int N, int S, boolean F) {
|
||||||
|
if (N == 4) {
|
||||||
|
if (F)
|
||||||
|
fwd4(out[O], out[O + 1], out[O + 2], out[O + 3],
|
||||||
|
in[I], in[I + S], in[I + 2 * S], in[I + 3 * S]);
|
||||||
|
else
|
||||||
|
fwd4(out[O], out[O + 3], out[O + 2], out[O + 1],
|
||||||
|
in[I], in[I + S], in[I + 2 * S], in[I + 3 * S]);
|
||||||
|
} else {
|
||||||
|
int Q = N / 4;
|
||||||
|
radix4(out, in, O, I, Q, 4 * S, F);
|
||||||
|
radix4(out, in, O + Q, I + S, Q, 4 * S, F);
|
||||||
|
radix4(out, in, O + 2 * Q, I + 2 * S, Q, 4 * S, F);
|
||||||
|
radix4(out, in, O + 3 * Q, I + 3 * S, Q, 4 * S, F);
|
||||||
|
for (int k0 = O, k1 = O + Q, k2 = O + 2 * Q, k3 = O + 3 * Q, l1 = 0, l2 = 0, l3 = 0;
|
||||||
|
k0 < O + Q; ++k0, ++k1, ++k2, ++k3, l1 += S, l2 += 2 * S, l3 += 3 * S) {
|
||||||
|
tin1.set(tf[l1]);
|
||||||
|
tin2.set(tf[l2]);
|
||||||
|
tin3.set(tf[l3]);
|
||||||
|
if (!F) {
|
||||||
|
tin1.conj();
|
||||||
|
tin2.conj();
|
||||||
|
tin3.conj();
|
||||||
|
}
|
||||||
|
tin0.set(out[k0]);
|
||||||
|
tin1.mul(out[k1]);
|
||||||
|
tin2.mul(out[k2]);
|
||||||
|
tin3.mul(out[k3]);
|
||||||
|
if (F)
|
||||||
|
fwd4(out[k0], out[k1], out[k2], out[k3], tin0, tin1, tin2, tin3);
|
||||||
|
else
|
||||||
|
fwd4(out[k0], out[k3], out[k2], out[k1], tin0, tin1, tin2, tin3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fwd5(Complex out0, Complex out1, Complex out2, Complex out3, Complex out4,
|
||||||
|
Complex in0, Complex in1, Complex in2, Complex in3, Complex in4) {
|
||||||
|
tmpA.set(in1).add(in4);
|
||||||
|
tmpB.set(in2).add(in3);
|
||||||
|
tmpC.set(in1.imag - in4.imag, in4.real - in1.real);
|
||||||
|
tmpD.set(in2.imag - in3.imag, in3.real - in2.real);
|
||||||
|
tmpF.set(tmpA).mul(cos(1, 5)).add(tmpE.set(tmpB).mul(cos(2, 5)));
|
||||||
|
tmpG.set(tmpC).mul(sin(1, 5)).add(tmpE.set(tmpD).mul(sin(2, 5)));
|
||||||
|
tmpH.set(tmpA).mul(cos(2, 5)).add(tmpE.set(tmpB).mul(cos(1, 5)));
|
||||||
|
tmpI.set(tmpC).mul(sin(2, 5)).sub(tmpE.set(tmpD).mul(sin(1, 5)));
|
||||||
|
out0.set(in0).add(tmpA).add(tmpB);
|
||||||
|
out1.set(in0).add(tmpF).add(tmpG);
|
||||||
|
out2.set(in0).add(tmpH).add(tmpI);
|
||||||
|
out3.set(in0).add(tmpH).sub(tmpI);
|
||||||
|
out4.set(in0).add(tmpF).sub(tmpG);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void radix5(Complex[] out, Complex[] in, int O, int I, int N, int S, boolean F) {
|
||||||
|
if (N == 5) {
|
||||||
|
if (F)
|
||||||
|
fwd5(out[O], out[O + 1], out[O + 2], out[O + 3], out[O + 4],
|
||||||
|
in[I], in[I + S], in[I + 2 * S], in[I + 3 * S], in[I + 4 * S]);
|
||||||
|
else
|
||||||
|
fwd5(out[O], out[O + 4], out[O + 3], out[O + 2], out[O + 1],
|
||||||
|
in[I], in[I + S], in[I + 2 * S], in[I + 3 * S], in[I + 4 * S]);
|
||||||
|
} else {
|
||||||
|
int Q = N / 5;
|
||||||
|
dit(out, in, O, I, Q, 5 * S, F);
|
||||||
|
dit(out, in, O + Q, I + S, Q, 5 * S, F);
|
||||||
|
dit(out, in, O + 2 * Q, I + 2 * S, Q, 5 * S, F);
|
||||||
|
dit(out, in, O + 3 * Q, I + 3 * S, Q, 5 * S, F);
|
||||||
|
dit(out, in, O + 4 * Q, I + 4 * S, Q, 5 * S, F);
|
||||||
|
for (int k0 = O, k1 = O + Q, k2 = O + 2 * Q, k3 = O + 3 * Q, k4 = O + 4 * Q, l1 = 0, l2 = 0, l3 = 0, l4 = 0;
|
||||||
|
k0 < O + Q; ++k0, ++k1, ++k2, ++k3, ++k4, l1 += S, l2 += 2 * S, l3 += 3 * S, l4 += 4 * S) {
|
||||||
|
tin1.set(tf[l1]);
|
||||||
|
tin2.set(tf[l2]);
|
||||||
|
tin3.set(tf[l3]);
|
||||||
|
tin4.set(tf[l4]);
|
||||||
|
if (!F) {
|
||||||
|
tin1.conj();
|
||||||
|
tin2.conj();
|
||||||
|
tin3.conj();
|
||||||
|
tin4.conj();
|
||||||
|
}
|
||||||
|
tin0.set(out[k0]);
|
||||||
|
tin1.mul(out[k1]);
|
||||||
|
tin2.mul(out[k2]);
|
||||||
|
tin3.mul(out[k3]);
|
||||||
|
tin4.mul(out[k4]);
|
||||||
|
if (F)
|
||||||
|
fwd5(out[k0], out[k1], out[k2], out[k3], out[k4], tin0, tin1, tin2, tin3, tin4);
|
||||||
|
else
|
||||||
|
fwd5(out[k0], out[k4], out[k3], out[k2], out[k1], tin0, tin1, tin2, tin3, tin4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fwd7(Complex out0, Complex out1, Complex out2, Complex out3, Complex out4, Complex out5, Complex out6,
|
||||||
|
Complex in0, Complex in1, Complex in2, Complex in3, Complex in4, Complex in5, Complex in6) {
|
||||||
|
tmpA.set(in1).add(in6);
|
||||||
|
tmpB.set(in2).add(in5);
|
||||||
|
tmpC.set(in3).add(in4);
|
||||||
|
tmpD.set(in1.imag - in6.imag, in6.real - in1.real);
|
||||||
|
tmpE.set(in2.imag - in5.imag, in5.real - in2.real);
|
||||||
|
tmpF.set(in3.imag - in4.imag, in4.real - in3.real);
|
||||||
|
tmpH.set(tmpA).mul(cos(1, 7)).add(tmpG.set(tmpB).mul(cos(2, 7))).add(tmpG.set(tmpC).mul(cos(3, 7)));
|
||||||
|
tmpI.set(tmpD).mul(sin(1, 7)).add(tmpG.set(tmpE).mul(sin(2, 7))).add(tmpG.set(tmpF).mul(sin(3, 7)));
|
||||||
|
tmpJ.set(tmpA).mul(cos(2, 7)).add(tmpG.set(tmpB).mul(cos(3, 7))).add(tmpG.set(tmpC).mul(cos(1, 7)));
|
||||||
|
tmpK.set(tmpD).mul(sin(2, 7)).sub(tmpG.set(tmpE).mul(sin(3, 7))).sub(tmpG.set(tmpF).mul(sin(1, 7)));
|
||||||
|
tmpL.set(tmpA).mul(cos(3, 7)).add(tmpG.set(tmpB).mul(cos(1, 7))).add(tmpG.set(tmpC).mul(cos(2, 7)));
|
||||||
|
tmpM.set(tmpD).mul(sin(3, 7)).sub(tmpG.set(tmpE).mul(sin(1, 7))).add(tmpG.set(tmpF).mul(sin(2, 7)));
|
||||||
|
out0.set(in0).add(tmpA).add(tmpB).add(tmpC);
|
||||||
|
out1.set(in0).add(tmpH).add(tmpI);
|
||||||
|
out2.set(in0).add(tmpJ).add(tmpK);
|
||||||
|
out3.set(in0).add(tmpL).add(tmpM);
|
||||||
|
out4.set(in0).add(tmpL).sub(tmpM);
|
||||||
|
out5.set(in0).add(tmpJ).sub(tmpK);
|
||||||
|
out6.set(in0).add(tmpH).sub(tmpI);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void radix7(Complex[] out, Complex[] in, int O, int I, int N, int S, boolean F) {
|
||||||
|
if (N == 7) {
|
||||||
|
if (F)
|
||||||
|
fwd7(out[O], out[O + 1], out[O + 2], out[O + 3], out[O + 4], out[O + 5], out[O + 6],
|
||||||
|
in[I], in[I + S], in[I + 2 * S], in[I + 3 * S], in[I + 4 * S], in[I + 5 * S], in[I + 6 * S]);
|
||||||
|
else
|
||||||
|
fwd7(out[O], out[O + 6], out[O + 5], out[O + 4], out[O + 3], out[O + 2], out[O + 1],
|
||||||
|
in[I], in[I + S], in[I + 2 * S], in[I + 3 * S], in[I + 4 * S], in[I + 5 * S], in[I + 6 * S]);
|
||||||
|
} else {
|
||||||
|
int Q = N / 7;
|
||||||
|
dit(out, in, O, I, Q, 7 * S, F);
|
||||||
|
dit(out, in, O + Q, I + S, Q, 7 * S, F);
|
||||||
|
dit(out, in, O + 2 * Q, I + 2 * S, Q, 7 * S, F);
|
||||||
|
dit(out, in, O + 3 * Q, I + 3 * S, Q, 7 * S, F);
|
||||||
|
dit(out, in, O + 4 * Q, I + 4 * S, Q, 7 * S, F);
|
||||||
|
dit(out, in, O + 5 * Q, I + 5 * S, Q, 7 * S, F);
|
||||||
|
dit(out, in, O + 6 * Q, I + 6 * S, Q, 7 * S, F);
|
||||||
|
for (int k0 = O, k1 = O + Q, k2 = O + 2 * Q, k3 = O + 3 * Q, k4 = O + 4 * Q, k5 = O + 5 * Q, k6 = O + 6 * Q, l1 = 0, l2 = 0, l3 = 0, l4 = 0, l5 = 0, l6 = 0;
|
||||||
|
k0 < O + Q; ++k0, ++k1, ++k2, ++k3, ++k4, ++k5, ++k6, l1 += S, l2 += 2 * S, l3 += 3 * S, l4 += 4 * S, l5 += 5 * S, l6 += 6 * S) {
|
||||||
|
tin1.set(tf[l1]);
|
||||||
|
tin2.set(tf[l2]);
|
||||||
|
tin3.set(tf[l3]);
|
||||||
|
tin4.set(tf[l4]);
|
||||||
|
tin5.set(tf[l5]);
|
||||||
|
tin6.set(tf[l6]);
|
||||||
|
if (!F) {
|
||||||
|
tin1.conj();
|
||||||
|
tin2.conj();
|
||||||
|
tin3.conj();
|
||||||
|
tin4.conj();
|
||||||
|
tin5.conj();
|
||||||
|
tin6.conj();
|
||||||
|
}
|
||||||
|
tin0.set(out[k0]);
|
||||||
|
tin1.mul(out[k1]);
|
||||||
|
tin2.mul(out[k2]);
|
||||||
|
tin3.mul(out[k3]);
|
||||||
|
tin4.mul(out[k4]);
|
||||||
|
tin5.mul(out[k5]);
|
||||||
|
tin6.mul(out[k6]);
|
||||||
|
if (F)
|
||||||
|
fwd7(out[k0], out[k1], out[k2], out[k3], out[k4], out[k5], out[k6], tin0, tin1, tin2, tin3, tin4, tin5, tin6);
|
||||||
|
else
|
||||||
|
fwd7(out[k0], out[k6], out[k5], out[k4], out[k3], out[k2], out[k1], tin0, tin1, tin2, tin3, tin4, tin5, tin6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dit(Complex[] out, Complex[] in, int O, int I, int N, int S, boolean F) {
|
||||||
|
if (N == 1)
|
||||||
|
out[O].set(in[I]);
|
||||||
|
else if (isPowerOfFour(N))
|
||||||
|
radix4(out, in, O, I, N, S, F);
|
||||||
|
else if (N % 7 == 0)
|
||||||
|
radix7(out, in, O, I, N, S, F);
|
||||||
|
else if (N % 5 == 0)
|
||||||
|
radix5(out, in, O, I, N, S, F);
|
||||||
|
else if (N % 3 == 0)
|
||||||
|
radix3(out, in, O, I, N, S, F);
|
||||||
|
else if (N % 2 == 0)
|
||||||
|
radix2(out, in, O, I, N, S, F);
|
||||||
|
}
|
||||||
|
|
||||||
|
void forward(Complex[] out, Complex[] in) {
|
||||||
|
if (in.length != tf.length)
|
||||||
|
throw new IllegalArgumentException("Input array length (" + in.length
|
||||||
|
+ ") must be equal to Transform length (" + tf.length + ")");
|
||||||
|
if (out.length != tf.length)
|
||||||
|
throw new IllegalArgumentException("Output array length (" + out.length
|
||||||
|
+ ") must be equal to Transform length (" + tf.length + ")");
|
||||||
|
dit(out, in, 0, 0, tf.length, 1, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
void backward(Complex[] out, Complex[] in) {
|
||||||
|
if (in.length != tf.length)
|
||||||
|
throw new IllegalArgumentException("Input array length (" + in.length
|
||||||
|
+ ") must be equal to Transform length (" + tf.length + ")");
|
||||||
|
if (out.length != tf.length)
|
||||||
|
throw new IllegalArgumentException("Output array length (" + out.length
|
||||||
|
+ ") must be equal to Transform length (" + tf.length + ")");
|
||||||
|
dit(out, in, 0, 0, tf.length, 1, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
app/src/main/java/xdsopl/robot36/Hann.java
Normal file
13
app/src/main/java/xdsopl/robot36/Hann.java
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
/*
|
||||||
|
Hann window
|
||||||
|
|
||||||
|
Copyright 2025 Ahmet Inan <xdsopl@gmail.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package xdsopl.robot36;
|
||||||
|
|
||||||
|
public class Hann {
|
||||||
|
static double window(int n, int N) {
|
||||||
|
return 0.5 * (1.0 - Math.cos((2.0 * Math.PI * n) / (N - 1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -71,6 +71,7 @@ public class MainActivity extends AppCompatActivity {
|
||||||
private PixelBuffer peakMeterBuffer;
|
private PixelBuffer peakMeterBuffer;
|
||||||
private ImageView peakMeterView;
|
private ImageView peakMeterView;
|
||||||
private PixelBuffer imageBuffer;
|
private PixelBuffer imageBuffer;
|
||||||
|
private ShortTimeFourierTransform stft;
|
||||||
private short[] shortBuffer;
|
private short[] shortBuffer;
|
||||||
private float[] recordBuffer;
|
private float[] recordBuffer;
|
||||||
private AudioRecord audioRecord;
|
private AudioRecord audioRecord;
|
||||||
|
|
@ -78,6 +79,7 @@ public class MainActivity extends AppCompatActivity {
|
||||||
private Menu menu;
|
private Menu menu;
|
||||||
private String currentMode;
|
private String currentMode;
|
||||||
private String language;
|
private String language;
|
||||||
|
private Complex input;
|
||||||
private int recordRate;
|
private int recordRate;
|
||||||
private int recordChannel;
|
private int recordChannel;
|
||||||
private int audioSource;
|
private int audioSource;
|
||||||
|
|
@ -137,8 +139,9 @@ public class MainActivity extends AppCompatActivity {
|
||||||
recordBuffer[i] = .000030517578125f * shortBuffer[i];
|
recordBuffer[i] = .000030517578125f * shortBuffer[i];
|
||||||
}
|
}
|
||||||
processPeakMeter();
|
processPeakMeter();
|
||||||
|
processSpectrogram();
|
||||||
boolean newLines = decoder.process(recordBuffer, recordChannel);
|
boolean newLines = decoder.process(recordBuffer, recordChannel);
|
||||||
processFreqPlot();
|
//processFreqPlot();
|
||||||
if (newLines) {
|
if (newLines) {
|
||||||
processScope();
|
processScope();
|
||||||
processImage();
|
processImage();
|
||||||
|
|
@ -161,6 +164,77 @@ public class MainActivity extends AppCompatActivity {
|
||||||
peakMeterView.invalidate();
|
peakMeterView.invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private double clamp(double x) {
|
||||||
|
return Math.min(Math.max(x, 0), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int argb(double a, double r, double g, double b) {
|
||||||
|
a = clamp(a);
|
||||||
|
r = clamp(r);
|
||||||
|
g = clamp(g);
|
||||||
|
b = clamp(b);
|
||||||
|
r *= a;
|
||||||
|
g *= a;
|
||||||
|
b *= a;
|
||||||
|
r = Math.sqrt(r);
|
||||||
|
g = Math.sqrt(g);
|
||||||
|
b = Math.sqrt(b);
|
||||||
|
int A = (int) Math.rint(255 * a);
|
||||||
|
int R = (int) Math.rint(255 * r);
|
||||||
|
int G = (int) Math.rint(255 * g);
|
||||||
|
int B = (int) Math.rint(255 * b);
|
||||||
|
return (A << 24) | (R << 16) | (G << 8) | B;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int rainbow(double v) {
|
||||||
|
v = clamp(v);
|
||||||
|
double t = 4 * v - 2;
|
||||||
|
return argb(4 * v, t, 1 - Math.abs(t), -t);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processSpectrogram() {
|
||||||
|
boolean process = false;
|
||||||
|
int channels = recordChannel > 0 ? 2 : 1;
|
||||||
|
for (int j = 0; j < recordBuffer.length / channels; ++j) {
|
||||||
|
switch (recordChannel) {
|
||||||
|
case 1:
|
||||||
|
input.set(recordBuffer[2 * j]);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
input.set(recordBuffer[2 * j + 1]);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
input.set(recordBuffer[2 * j] + recordBuffer[2 * j + 1]);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
input.set(recordBuffer[2 * j], recordBuffer[2 * j + 1]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
input.set(recordBuffer[j]);
|
||||||
|
}
|
||||||
|
if (stft.push(input)) {
|
||||||
|
process = true;
|
||||||
|
int stride = freqPlotBuffer.width;
|
||||||
|
int line = stride * freqPlotBuffer.line;
|
||||||
|
double lowest = Math.log(0.000001);
|
||||||
|
double highest = Math.log(1);
|
||||||
|
double range = highest - lowest;
|
||||||
|
for (int i = 0; i < stride; ++i)
|
||||||
|
freqPlotBuffer.pixels[line + i] = rainbow((Math.log(stft.power[i]) - lowest) / range);
|
||||||
|
System.arraycopy(freqPlotBuffer.pixels, line, freqPlotBuffer.pixels, line + stride * (freqPlotBuffer.height / 2), stride);
|
||||||
|
freqPlotBuffer.line = (freqPlotBuffer.line + 1) % (freqPlotBuffer.height / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (process) {
|
||||||
|
int width = freqPlotBitmap.getWidth();
|
||||||
|
int height = freqPlotBitmap.getHeight();
|
||||||
|
int stride = freqPlotBuffer.width;
|
||||||
|
int offset = stride * (freqPlotBuffer.line + freqPlotBuffer.height / 2 - height);
|
||||||
|
freqPlotBitmap.setPixels(freqPlotBuffer.pixels, offset, stride, 0, 0, width, height);
|
||||||
|
freqPlotView.invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void processFreqPlot() {
|
private void processFreqPlot() {
|
||||||
int width = freqPlotBitmap.getWidth();
|
int width = freqPlotBitmap.getWidth();
|
||||||
int height = freqPlotBitmap.getHeight();
|
int height = freqPlotBitmap.getHeight();
|
||||||
|
|
@ -238,6 +312,7 @@ public class MainActivity extends AppCompatActivity {
|
||||||
if (rateChanged) {
|
if (rateChanged) {
|
||||||
decoder = new Decoder(scopeBuffer, imageBuffer, getString(R.string.raw_mode), recordRate);
|
decoder = new Decoder(scopeBuffer, imageBuffer, getString(R.string.raw_mode), recordRate);
|
||||||
decoder.setMode(currentMode);
|
decoder.setMode(currentMode);
|
||||||
|
stft = new ShortTimeFourierTransform(recordRate / 10, 3);
|
||||||
}
|
}
|
||||||
startListening();
|
startListening();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -458,6 +533,7 @@ public class MainActivity extends AppCompatActivity {
|
||||||
freqPlotBuffer = new PixelBuffer(256, 2 * 256);
|
freqPlotBuffer = new PixelBuffer(256, 2 * 256);
|
||||||
peakMeterBuffer = new PixelBuffer(1, 16);
|
peakMeterBuffer = new PixelBuffer(1, 16);
|
||||||
imageBuffer = new PixelBuffer(800, 616);
|
imageBuffer = new PixelBuffer(800, 616);
|
||||||
|
input = new Complex();
|
||||||
createScope(config);
|
createScope(config);
|
||||||
createFreqPlot(config);
|
createFreqPlot(config);
|
||||||
createPeakMeter();
|
createPeakMeter();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
Short Time Fourier Transform
|
||||||
|
|
||||||
|
Copyright 2025 Ahmet Inan <xdsopl@gmail.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package xdsopl.robot36;
|
||||||
|
|
||||||
|
public class ShortTimeFourierTransform {
|
||||||
|
private final FastFourierTransform fft;
|
||||||
|
private final Complex[] prev, fold, freq;
|
||||||
|
private final float[] weight;
|
||||||
|
private final Complex temp;
|
||||||
|
private int index;
|
||||||
|
|
||||||
|
public final float[] power;
|
||||||
|
|
||||||
|
ShortTimeFourierTransform(int length, int overlap) {
|
||||||
|
fft = new FastFourierTransform(length);
|
||||||
|
prev = new Complex[length * overlap];
|
||||||
|
for (int i = 0; i < length * overlap; ++i)
|
||||||
|
prev[i] = new Complex();
|
||||||
|
fold = new Complex[length];
|
||||||
|
for (int i = 0; i < length; ++i)
|
||||||
|
fold[i] = new Complex();
|
||||||
|
freq = new Complex[length];
|
||||||
|
for (int i = 0; i < length; ++i)
|
||||||
|
freq[i] = new Complex();
|
||||||
|
temp = new Complex();
|
||||||
|
power = new float[length];
|
||||||
|
weight = new float[length * overlap];
|
||||||
|
for (int i = 0; i < length * overlap; ++i)
|
||||||
|
weight[i] = (float)(Filter.lowPass(1, length, i, length * overlap) * Hann.window(i, length * overlap));
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean push(Complex input) {
|
||||||
|
prev[index].set(input);
|
||||||
|
index = (index + 1) % prev.length;
|
||||||
|
if (index % fold.length != 0)
|
||||||
|
return false;
|
||||||
|
for (int i = 0; i < fold.length; ++i, index = (index + 1) % prev.length)
|
||||||
|
fold[i].set(prev[index]).mul(weight[i]);
|
||||||
|
for (int i = fold.length; i < prev.length; ++i, index = (index + 1) % prev.length)
|
||||||
|
fold[i % fold.length].add(temp.set(prev[index]).mul(weight[i]));
|
||||||
|
fft.forward(freq, fold);
|
||||||
|
for (int i = 0; i < power.length; ++i)
|
||||||
|
power[i] = freq[i].norm();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue