diff --git a/NoiseCapture/src/commonMain/kotlin/org/noise_planet/noisecapture/signal/FFT.kt b/NoiseCapture/src/commonMain/kotlin/org/noise_planet/noisecapture/signal/FFT.kt deleted file mode 100644 index af7d3f9..0000000 --- a/NoiseCapture/src/commonMain/kotlin/org/noise_planet/noisecapture/signal/FFT.kt +++ /dev/null @@ -1,204 +0,0 @@ -package org.noise_planet.noisecapture.signal - -import kotlin.math.* - -class FFT { - fun fft(length: Int, riArray: DoubleArray) { - val n = (2.0.pow(length) + 0.5).toInt() - var wCos: Double - var wSin: Double - var uCos: Double - var uSin: Double - val n2 = n shr 1 - var l1 = n - var l2: Int - - for (l in 0 until length) { - l2 = l1 shr 1 - wCos = 1.0 - wSin = 0.0 - uCos = cos(PI / l2) - uSin = -sin(PI / l2) - - for (j in 0 until l2) { - for (i in j until n step l1) { - val ir = 2 * i - val ii = ir + 1 - val l22 = 2 * l2 - val sumRe = riArray[ir] + riArray[ir + l22] - val sumIm = riArray[ii] + riArray[ii + l22] - val diffRe = riArray[ir] - riArray[ir + l22] - val diffIm = riArray[ii] - riArray[ii + l22] - riArray[ir + l22] = diffRe * wCos - diffIm * wSin - riArray[ii + l22] = diffRe * wSin + diffIm * wCos - riArray[ir] = sumRe - riArray[ii] = sumIm - } - val w = wCos * uCos - wSin * uSin - wSin = wCos * uSin + wSin * uCos - wCos = w - } - l1 = l1 shr 1 - } - - var k: Int - var j = 0 - val n1 = n - 1 - - for (i in 0 until n1) { - if (i < j) { - val jr = 2 * j - val ji = jr + 1 - val ir = 2 * i - val ii = ir + 1 - val tre = riArray[jr] - val tim = riArray[ji] - riArray[jr] = riArray[ir] - riArray[ji] = riArray[ii] - riArray[ir] = tre - riArray[ii] = tim - } - k = n2 - while (k <= j) { - j -= k - k = k shr 1 - } - j += k - } - } - - fun iFFT(length: Int, riArray: DoubleArray) { - val n = (2.0.pow(length) + 0.5).toInt() - var wCos: Double - var wSin: Double - var uCos: Double - var uSin: Double - val n2 = n shr 1 - var l1 = n - var l2: Int - - for (l in 0 until length) { - l2 = l1 shr 1 - wCos = 1.0 - wSin = 0.0 - uCos = cos(PI / l2) - uSin = sin(PI / l2) - - for (j in 0 until l2) { - for (i in j until n step l1) { - val ir = 2 * i - val ii = ir + 1 - val l22 = 2 * l2 - val sumRe = 0.5 * (riArray[ir] + riArray[ir + l22]) - val sumIm = 0.5 * (riArray[ii] + riArray[ii + l22]) - val diffRe = 0.5 * (riArray[ir] - riArray[ir + l22]) - val diffIm = 0.5 * (riArray[ii] - riArray[ii + l22]) - riArray[ir + l22] = diffRe * wCos - diffIm * wSin - riArray[ii + l22] = diffRe * wSin + diffIm * wCos - riArray[ir] = sumRe - riArray[ii] = sumIm - } - val w = wCos * uCos - wSin * uSin - wSin = wCos * uSin + wSin * uCos - wCos = w - } - l1 = l1 shr 1 - } - - var k: Int - var j = 0 - val n1 = n - 1 - - for (i in 0 until n1) { - if (i < j) { - val jr = 2 * j - val ji = jr + 1 - val ir = 2 * i - val ii = ir + 1 - val tre = riArray[jr] - val tim = riArray[ji] - riArray[jr] = riArray[ir] - riArray[ji] = riArray[ii] - riArray[ir] = tre - riArray[ii] = tim - } - k = n2 - while (k <= j) { - j -= k - k = k shr 1 - } - j += k - } - } - - fun realFFT(length: Int, realArray: DoubleArray) { - fft(length - 1, realArray) - val n = (2.0.pow(length) + 0.5).toInt() - val a = DoubleArray(n) - val b = DoubleArray(n) - - for (i in 0 until n / 2) { - a[2 * i] = 0.5 * (1 - sin(2 * PI / n * i)) - a[2 * i + 1] = -0.5 * cos(2 * PI / n * i) - b[2 * i] = 0.5 * (1 + sin(2 * PI / n * i)) - b[2 * i + 1] = 0.5 * cos(2 * PI / n * i) - } - - for (k in 1 until n / 4 + 1) { - val k2 = 2 * k - val xr = - realArray[k2] * a[k2] - realArray[k2 + 1] * a[k2 + 1] + realArray[n - k2] * b[k2] + realArray[n - k2 + 1] * b[k2 + 1] - val xi = - realArray[k2] * a[k2 + 1] + realArray[k2 + 1] * a[k2] + realArray[n - k2] * b[k2 + 1] - realArray[n - k2 + 1] * b[k2] - val xrN = - realArray[n - k2] * a[n - k2] - realArray[n - k2 + 1] * a[n - k2 + 1] + realArray[k2] * b[n - k2] + realArray[k2 + 1] * b[n - k2 + 1] - val xiN = - realArray[n - k2] * a[n - k2 + 1] + realArray[n - k2 + 1] * a[n - k2] + realArray[k2] * b[n - k2 + 1] - realArray[k2 + 1] * b[n - k2] - realArray[k2] = xr - realArray[k2 + 1] = xi - realArray[n - k2] = xrN - realArray[n - k2 + 1] = xiN - } - - val temp = realArray[0] - realArray[0] = realArray[0] + realArray[1] - realArray[n] = temp - realArray[1] - realArray[1] = 0.0 - realArray[n + 1] = 0.0 - } - - fun realIFFT(length: Int, realArray: DoubleArray) { - val n = (2.0.pow(length) + 0.5).toInt() - val a = DoubleArray(n) - val b = DoubleArray(n) - - for (i in 0 until n / 2) { - a[2 * i] = 0.5 * (1 - sin(2 * PI / n * i)) - a[2 * i + 1] = -0.5 * cos(2 * PI / n * i) - b[2 * i] = 0.5 * (1 + sin(2 * PI / n * i)) - b[2 * i + 1] = 0.5 * cos(2 * PI / n * i) - } - - for (k in 1 until n / 4 + 1) { - val k2 = 2 * k - val xr = - realArray[k2] * a[k2] + realArray[k2 + 1] * a[k2 + 1] + realArray[n - k2] * b[k2] - realArray[n - k2 + 1] * b[k2 + 1] - val xi = - -realArray[k2] * a[k2 + 1] + realArray[k2 + 1] * a[k2] - realArray[n - k2] * b[k2 + 1] - realArray[n - k2 + 1] * b[k2] - val xrN = - realArray[n - k2] * a[n - k2] + realArray[n - k2 + 1] * a[n - k2 + 1] + realArray[k2] * b[n - k2] - realArray[k2 + 1] * b[n - k2 + 1] - val xiN = - -realArray[n - k2] * a[n - k2 + 1] + realArray[n - k2 + 1] * a[n - k2] - realArray[k2] * b[n - k2 + 1] - realArray[k2 + 1] * b[n - k2] - realArray[k2] = xr - realArray[k2 + 1] = xi - realArray[n - k2] = xrN - realArray[n - k2 + 1] = xiN - } - - val temp = realArray[0] - realArray[0] = 0.5 * realArray[0] + 0.5 * realArray[n] - realArray[1] = 0.5 * temp - 0.5 * realArray[n] - iFFT(length - 1, realArray) - } - -} \ No newline at end of file diff --git a/NoiseCapture/src/commonMain/kotlin/org/noise_planet/noisecapture/signal/fft.kt b/NoiseCapture/src/commonMain/kotlin/org/noise_planet/noisecapture/signal/fft.kt new file mode 100644 index 0000000..37977df --- /dev/null +++ b/NoiseCapture/src/commonMain/kotlin/org/noise_planet/noisecapture/signal/fft.kt @@ -0,0 +1,74 @@ +package org.noise_planet.noisecapture.signal + +import kotlin.math.PI +import kotlin.math.cos +import kotlin.math.pow +import kotlin.math.sin +import kotlin.math.log2 + + +fun fft(length: Int, riArray: DoubleArray) { + val m = log2(length.toDouble()).toInt() + val n = (2.0.pow(m) + 0.5).toInt() + require(n <= riArray.size) + var wCos: Double + var wSin: Double + var uCos: Double + var uSin: Double + val n2 = n shr 1 + var l1 = n + var l2: Int + + for (l in 0 until m) { + l2 = l1 shr 1 + wCos = 1.0 + wSin = 0.0 + uCos = cos(PI / l2) + uSin = -sin(PI / l2) + + for (j in 0 until l2) { + for (i in j until n step l1) { + val ir = 2 * i + val ii = ir + 1 + val l22 = 2 * l2 + val sumRe = riArray[ir] + riArray[ir + l22] + val sumIm = riArray[ii] + riArray[ii + l22] + val diffRe = riArray[ir] - riArray[ir + l22] + val diffIm = riArray[ii] - riArray[ii + l22] + riArray[ir + l22] = diffRe * wCos - diffIm * wSin + riArray[ii + l22] = diffRe * wSin + diffIm * wCos + riArray[ir] = sumRe + riArray[ii] = sumIm + } + val w = wCos * uCos - wSin * uSin + wSin = wCos * uSin + wSin * uCos + wCos = w + } + l1 = l1 shr 1 + } + + var k: Int + var j = 0 + val n1 = n - 1 + + for (i in 0 until n1) { + if (i < j) { + val jr = 2 * j + val ji = jr + 1 + val ir = 2 * i + val ii = ir + 1 + val tre = riArray[jr] + val tim = riArray[ji] + riArray[jr] = riArray[ir] + riArray[ji] = riArray[ii] + riArray[ir] = tre + riArray[ii] = tim + } + k = n2 + while (k <= j) { + j -= k + k = k shr 1 + } + j += k + } +} diff --git a/NoiseCapture/src/commonMain/kotlin/org/noise_planet/noisecapture/signal/ifft.kt b/NoiseCapture/src/commonMain/kotlin/org/noise_planet/noisecapture/signal/ifft.kt new file mode 100644 index 0000000..2324b51 --- /dev/null +++ b/NoiseCapture/src/commonMain/kotlin/org/noise_planet/noisecapture/signal/ifft.kt @@ -0,0 +1,73 @@ +package org.noise_planet.noisecapture.signal + +import kotlin.math.PI +import kotlin.math.cos +import kotlin.math.log2 +import kotlin.math.pow +import kotlin.math.sin + +fun iFFT(length: Int, riArray: DoubleArray) { + val m = log2(length.toDouble()).toInt() + val n = (2.0.pow(m) + 0.5).toInt() + require(n <= riArray.size) + var wCos: Double + var wSin: Double + var uCos: Double + var uSin: Double + val n2 = n shr 1 + var l1 = n + var l2: Int + + for (l in 0 until m) { + l2 = l1 shr 1 + wCos = 1.0 + wSin = 0.0 + uCos = cos(PI / l2) + uSin = sin(PI / l2) + + for (j in 0 until l2) { + for (i in j until n step l1) { + val ir = 2 * i + val ii = ir + 1 + val l22 = 2 * l2 + val sumRe = 0.5 * (riArray[ir] + riArray[ir + l22]) + val sumIm = 0.5 * (riArray[ii] + riArray[ii + l22]) + val diffRe = 0.5 * (riArray[ir] - riArray[ir + l22]) + val diffIm = 0.5 * (riArray[ii] - riArray[ii + l22]) + riArray[ir + l22] = diffRe * wCos - diffIm * wSin + riArray[ii + l22] = diffRe * wSin + diffIm * wCos + riArray[ir] = sumRe + riArray[ii] = sumIm + } + val w = wCos * uCos - wSin * uSin + wSin = wCos * uSin + wSin * uCos + wCos = w + } + l1 = l1 shr 1 + } + + var k: Int + var j = 0 + val n1 = n - 1 + + for (i in 0 until n1) { + if (i < j) { + val jr = 2 * j + val ji = jr + 1 + val ir = 2 * i + val ii = ir + 1 + val tre = riArray[jr] + val tim = riArray[ji] + riArray[jr] = riArray[ir] + riArray[ji] = riArray[ii] + riArray[ir] = tre + riArray[ii] = tim + } + k = n2 + while (k <= j) { + j -= k + k = k shr 1 + } + j += k + } +} diff --git a/NoiseCapture/src/commonMain/kotlin/org/noise_planet/noisecapture/signal/realfft.kt b/NoiseCapture/src/commonMain/kotlin/org/noise_planet/noisecapture/signal/realfft.kt new file mode 100644 index 0000000..57599c2 --- /dev/null +++ b/NoiseCapture/src/commonMain/kotlin/org/noise_planet/noisecapture/signal/realfft.kt @@ -0,0 +1,40 @@ +package org.noise_planet.noisecapture.signal + +import kotlin.math.PI +import kotlin.math.cos +import kotlin.math.log2 +import kotlin.math.pow +import kotlin.math.sin + +fun realFFT(length: Int, realArray: DoubleArray) { + val m = log2(length.toDouble()).toInt() + val n = (2.0.pow(m) + 0.5).toInt() + require(n <= realArray.size) + fft(n/2, realArray) + val a = DoubleArray(n) + val b = DoubleArray(n) + + for (i in 0 until n / 2) { + a[2 * i] = 0.5 * (1 - sin(2 * PI / n * i)) + a[2 * i + 1] = -0.5 * cos(2 * PI / n * i) + b[2 * i] = 0.5 * (1 + sin(2 * PI / n * i)) + b[2 * i + 1] = 0.5 * cos(2 * PI / n * i) + } + + for (k in 1 until n / 4 + 1) { + val k2 = 2 * k + val xr = + realArray[k2] * a[k2] - realArray[k2 + 1] * a[k2 + 1] + realArray[n - k2] * b[k2] + realArray[n - k2 + 1] * b[k2 + 1] + val xi = + realArray[k2] * a[k2 + 1] + realArray[k2 + 1] * a[k2] + realArray[n - k2] * b[k2 + 1] - realArray[n - k2 + 1] * b[k2] + val xrN = + realArray[n - k2] * a[n - k2] - realArray[n - k2 + 1] * a[n - k2 + 1] + realArray[k2] * b[n - k2] + realArray[k2 + 1] * b[n - k2 + 1] + val xiN = + realArray[n - k2] * a[n - k2 + 1] + realArray[n - k2 + 1] * a[n - k2] + realArray[k2] * b[n - k2 + 1] - realArray[k2 + 1] * b[n - k2] + realArray[k2] = xr + realArray[k2 + 1] = xi + realArray[n - k2] = xrN + realArray[n - k2 + 1] = xiN + } +} + diff --git a/NoiseCapture/src/commonMain/kotlin/org/noise_planet/noisecapture/signal/realifft.kt b/NoiseCapture/src/commonMain/kotlin/org/noise_planet/noisecapture/signal/realifft.kt new file mode 100644 index 0000000..f550f83 --- /dev/null +++ b/NoiseCapture/src/commonMain/kotlin/org/noise_planet/noisecapture/signal/realifft.kt @@ -0,0 +1,43 @@ +package org.noise_planet.noisecapture.signal + +import kotlin.math.PI +import kotlin.math.cos +import kotlin.math.log2 +import kotlin.math.pow +import kotlin.math.sin + +fun realIFFT(length: Int, realArray: DoubleArray) { + val m = log2(length.toDouble()).toInt() + val n = (2.0.pow(m) + 0.5).toInt() + require(n <= realArray.size) + val a = DoubleArray(n) + val b = DoubleArray(n) + + for (i in 0 until n / 2) { + a[2 * i] = 0.5 * (1 - sin(2 * PI / n * i)) + a[2 * i + 1] = -0.5 * cos(2 * PI / n * i) + b[2 * i] = 0.5 * (1 + sin(2 * PI / n * i)) + b[2 * i + 1] = 0.5 * cos(2 * PI / n * i) + } + + for (k in 1 until n / 4 + 1) { + val k2 = 2 * k + val xr = + realArray[k2] * a[k2] + realArray[k2 + 1] * a[k2 + 1] + realArray[n - k2] * b[k2] - realArray[n - k2 + 1] * b[k2 + 1] + val xi = + -realArray[k2] * a[k2 + 1] + realArray[k2 + 1] * a[k2] - realArray[n - k2] * b[k2 + 1] - realArray[n - k2 + 1] * b[k2] + val xrN = + realArray[n - k2] * a[n - k2] + realArray[n - k2 + 1] * a[n - k2 + 1] + realArray[k2] * b[n - k2] - realArray[k2 + 1] * b[n - k2 + 1] + val xiN = + -realArray[n - k2] * a[n - k2 + 1] + realArray[n - k2 + 1] * a[n - k2] - realArray[k2] * b[n - k2 + 1] - realArray[k2 + 1] * b[n - k2] + realArray[k2] = xr + realArray[k2 + 1] = xi + realArray[n - k2] = xrN + realArray[n - k2 + 1] = xiN + } + + val temp = realArray[0] + realArray[0] = 0.5 * realArray[0] + 0.5 * realArray[n-1] + realArray[1] = 0.5 * temp - 0.5 * realArray[n-1] + iFFT(n/2, realArray) +} diff --git a/NoiseCapture/src/commonTest/kotlin/org/noise_planet/noisecapture/Test.kt b/NoiseCapture/src/commonTest/kotlin/org/noise_planet/noisecapture/Test.kt index 3ed8a27..bb93c06 100644 --- a/NoiseCapture/src/commonTest/kotlin/org/noise_planet/noisecapture/Test.kt +++ b/NoiseCapture/src/commonTest/kotlin/org/noise_planet/noisecapture/Test.kt @@ -10,3 +10,4 @@ class CommonGreetingTest { assertTrue(Greeting().greet().contains("Hello"), "Check 'Hello' is mentioned") } } + diff --git a/NoiseCapture/src/commonTest/kotlin/org/noise_planet/noisecapture/signal/TestFFT.kt b/NoiseCapture/src/commonTest/kotlin/org/noise_planet/noisecapture/signal/TestFFT.kt new file mode 100644 index 0000000..90373b2 --- /dev/null +++ b/NoiseCapture/src/commonTest/kotlin/org/noise_planet/noisecapture/signal/TestFFT.kt @@ -0,0 +1,34 @@ +package org.noise_planet.noisecapture.signal + +import kotlin.test.Test +import kotlin.math.* +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals + +class TestFFT { + private fun generateSinusoidalSignal(frequency: Double, sampleRate: Double, duration: Double): DoubleArray { + val numSamples = (duration * sampleRate).toInt() + val signal = DoubleArray(numSamples) + + val angularFrequency = 2.0 * PI * frequency / sampleRate + + for (i in 0 until numSamples) { + signal[i] = sin(i * angularFrequency) + } + + return signal + } + @Test + fun testRFFTSinus() { + val frequency = 8.0 // Hz + val sampleRate = 64.0 // Hz + val duration = 2.0.pow(ceil(log2(sampleRate))) / sampleRate + val samples = generateSinusoidalSignal(frequency, sampleRate, duration) + val fftResult = samples.copyOf() + realFFT(samples.size, fftResult) + realIFFT(samples.size, fftResult) + fftResult.forEachIndexed { + index, value -> assertEquals(value, samples[index], 1e-8) + } + } +} \ No newline at end of file