Create FFT (backed by radix-2 algorithm) function in smnp.dsp

This commit is contained in:
2020-03-26 19:46:22 +01:00
parent d4577fa4b6
commit 71362e2b38
5 changed files with 93 additions and 1 deletions

View File

@@ -55,6 +55,10 @@ extend list {
return output;
}
function sublist(beginIndex: int, endIndex: int) {
return this as (item, index) ^ item % index >= beginIndex and index < endIndex;
}
function isEmpty() {
return this.size == 0;
}

View File

@@ -1,10 +1,11 @@
package io.smnp.ext.dsp
import io.smnp.ext.dsp.function.FftFunction
import io.smnp.ext.dsp.function.PlotFunction
import io.smnp.ext.provider.NativeModuleProvider
import org.pf4j.Extension
@Extension
class DspModule : NativeModuleProvider("smnp.dsp") {
override fun functions() = listOf(PlotFunction())
override fun functions() = listOf(PlotFunction(), FftFunction())
}

View File

@@ -0,0 +1,19 @@
package io.smnp.ext.dsp.function
import io.smnp.callable.function.Function
import io.smnp.callable.function.FunctionDefinitionTool
import io.smnp.callable.signature.Signature.Companion.simple
import io.smnp.ext.dsp.lib.fft.FourierTransform.fft
import io.smnp.type.enumeration.DataType.FLOAT
import io.smnp.type.enumeration.DataType.INT
import io.smnp.type.matcher.Matcher.Companion.listOf
import io.smnp.type.model.Value
class FftFunction : Function("fft") {
override fun define(new: FunctionDefinitionTool) {
new function simple(listOf(INT, FLOAT)) body { _, (signal) ->
val x = (signal.unwrap() as List<Number>).map { it.toDouble() }
Value.list(fft(x).map { Value.float(it.mod.toFloat()) })
}
}
}

View File

@@ -0,0 +1,26 @@
package io.smnp.ext.dsp.lib.complex
import kotlin.math.atan2
import kotlin.math.pow
data class Complex(val re: Double, val im: Double) {
constructor(a: Double) : this(kotlin.math.cos(a), kotlin.math.sin(a))
val mod: Double
get() = squaresSum.pow(0.5)
private val squaresSum: Double
get() = re.pow(2.0) + im.pow(2.0)
val arg: Double
get() = atan2(im, re)
override fun toString() = "$re${if(im > 0) "+$im" else "$im"}"
operator fun plus(z: Complex) = Complex(re + z.re, im + z.im)
operator fun minus(z: Complex) = Complex(re - z.re, im - z.im)
operator fun times(z: Complex) = Complex(re * z.re - im * z.im, re * z.im + im * z.re)
}

View File

@@ -0,0 +1,42 @@
package io.smnp.ext.dsp.lib.fft
import io.smnp.ext.dsp.lib.complex.Complex
import kotlin.math.abs
import kotlin.math.ceil
import kotlin.math.log
import kotlin.math.pow
object FourierTransform {
fun fft(signal: List<Double>): List<Complex> {
return radix2(padSignal(signal).map { Complex(it, 0.0) })
}
private fun padSignal(signal: List<Double>): List<Double> {
val totalSignalSize = 2.0.pow(nextPow2(signal.size.toDouble()))
val paddingSize = (totalSignalSize - signal.size).toInt()
return signal + List(paddingSize) { 0.0 }
}
private fun nextPow2(x: Double) = ceil(log(abs(x), 2.0))
private fun radix2(signal: List<Complex>): List<Complex> {
val N = signal.size
if(N == 1) return listOf(signal[0])
if(N % 2 != 0) throw IllegalArgumentException("The number of samples should be power of 2")
val even = radix2(signal.filterIndexed { index, _ -> index % 2 == 0 })
val odd = radix2(signal.filterIndexed { index, _ -> index % 2 != 0 })
val output = MutableList(N) { Complex(0.0, 0.0) }
even.zip(odd).forEachIndexed { k, (e, o) ->
val z = Complex(-2 * k * Math.PI / N)
output[k] = e + z * o
output[k+N/2] = e - z * o
}
return output
}
}