fft: data struct optimisation

This commit is contained in:
minjaesong
2023-11-26 17:33:29 +09:00
parent 3b38958a08
commit 1d727397b4
10 changed files with 94 additions and 131 deletions

View File

@@ -198,22 +198,6 @@ SOFTWARE.
$BULLET Ambient sound recordings:
- ambient_forest_01.ogg
- ambient_meadow_01.ogg
- ambient_windy_01.ogg
- ambient_woods_01.ogg
- crickets_01.ogg
- crickets_02.ogg
Copyright (C) 2012, 2013, 2015, 2016, 2017 Klankbeeld
Sound from <http://www.freesound.org/people/klankbeeld/>
$BULLET GraalVM Community Edition
GraalVM Community Edition consists of multiple modules. The software as a whole,
@@ -253,6 +237,7 @@ SOFTWARE.
$BULLET Apache Commons Codec
Copyright 2002-2023 The Apache Software Foundation
This product includes software developed at
@@ -263,6 +248,7 @@ The Apache Software Foundation (https://www.apache.org/).
$BULLET Apache Commons CSV
Copyright 2005-2023 The Apache Software Foundation
This product includes software developed at
@@ -282,6 +268,30 @@ The Apache Software Foundation (http://www.apache.org/).
This product includes software developed for Orekit by
CS Systèmes d'Information (http://www.c-s.fr/)
Copyright 2010-2012 CS Systèmes d'Information
$BULLET Ambient sound recordings:
- ambient_forest_01.ogg
- ambient_meadow_01.ogg
- ambient_windy_01.ogg
- ambient_woods_01.ogg
- crickets_01.ogg
- crickets_02.ogg
Copyright (C) 2012, 2013, 2015, 2016, 2017 Klankbeeld
Sound from <http://www.freesound.org/people/klankbeeld/>
$BULLET EchoThief Impulse Response Library
Copyright 2013-2023 Chris Warren <cwarren@sdsu.edu>
""").split('\n')

View File

@@ -131,14 +131,14 @@ object AudioMixer: Disposable {
}
convolveBusOpen.filters[0] = Highpass(80f)
convolveBusOpen.filters[1] = Convolv(ModMgr.getFile("basegame", "audio/convolution/EchoThief - Cranbrook Art Museum.bin"))
convolveBusOpen.filters[2] = Gain(decibelsToFullscale(18.0).toFloat())
convolveBusOpen.volume = 0.5
convolveBusOpen.filters[1] = Convolv(ModMgr.getFile("basegame", "audio/convolution/EchoThief - PurgatoryChasm.bin"))
convolveBusOpen.filters[2] = Gain(decibelsToFullscale(18.0).toFloat()) // don't make it too loud; it'll sound like a shit
convolveBusOpen.volume = 0.5 // will be controlled by the other updater which surveys the world
convolveBusCave.filters[0] = Highpass(80f)
convolveBusCave.filters[1] = Convolv(ModMgr.getFile("basegame", "audio/convolution/EchoThief - CedarCreekWinery.bin"))
convolveBusCave.filters[1] = Convolv(ModMgr.getFile("basegame", "audio/convolution/EchoThief - WaterplacePark-trimmed.bin"))
convolveBusCave.filters[2] = Gain(decibelsToFullscale(18.0).toFloat())
convolveBusCave.volume = 0.5
convolveBusCave.volume = 0.5 // will be controlled by the other updater which surveys the world
fadeBus.addSidechainInput(sumBus, 1.0 / 3.0)
fadeBus.addSidechainInput(convolveBusOpen, 2.0 / 3.0)

View File

@@ -1,15 +1,20 @@
package net.torvald.terrarum.audio
import org.apache.commons.math3.exception.MathIllegalStateException
import org.apache.commons.math3.transform.DftNormalization
import org.apache.commons.math3.transform.TransformType
import org.apache.commons.math3.util.FastMath
data class FComplex(var re: Float = 0f, var im: Float = 0f) {
operator fun times(other: FComplex) = FComplex(
this.re * other.re - this.im * other.im,
this.re * other.im + this.im * other.re
)
class ComplexArray(val res: FloatArray, val ims: FloatArray) {
val indices: IntProgression
get() = 0 until size
val size: Int
get() = res.size
operator fun times(other: ComplexArray): ComplexArray {
val l = size
val re = FloatArray(l) { res[it] * other.res[it] - ims[it] * other.ims[it] }
val im = FloatArray(l) { res[it] * other.ims[it] + ims[it] * other.res[it] }
return ComplexArray(re, im)
}
}
/**
@@ -20,53 +25,16 @@ data class FComplex(var re: Float = 0f, var im: Float = 0f) {
object FFT {
// org.apache.commons.math3.transform.FastFouriesTransformer.java:370
fun fft(signal: FloatArray): Array<FComplex> {
val dataRI = arrayOf(signal.copyOf(), FloatArray(signal.size))
fun fft(signal: FloatArray): ComplexArray {
val dataRI = ComplexArray(signal.copyOf(), FloatArray(signal.size))
transformInPlace(dataRI, DftNormalization.STANDARD, TransformType.FORWARD)
val output = dataRI.toComplexArray()
return getComplex(output, false)
return dataRI
}
// org.apache.commons.math3.transform.FastFouriesTransformer.java:404
fun ifftAndGetReal(y: Array<FComplex>): FloatArray {
val dataRI = Array<FloatArray>(2) { FloatArray(y.size) }
for (i in y.indices) {
dataRI[0][i] = y[i].re
dataRI[1][i] = y[i].im
}
transformInPlace(dataRI, DftNormalization.STANDARD, TransformType.INVERSE)
return dataRI[0]
}
private fun Array<FloatArray>.toComplexArray(): Array<FComplex> {
return Array(this[0].size) {
FComplex(this[0][it], this[1][it])
}
}
// com.github.psambit9791.jdsp.transform.FastFourier.java:190
/**
* Returns the complex value of the fast fourier transformed sequence
* @param onlyPositive Set to True if non-mirrored output is required
* @throws java.lang.ExceptionInInitializerError if called before executing transform() method
* @return Complex[] The complex FFT output
*/
@Throws(ExceptionInInitializerError::class)
fun getComplex(output: Array<FComplex>, onlyPositive: Boolean): Array<FComplex> {
val dftout: Array<FComplex> = if (onlyPositive) {
val numBins: Int = output.size / 2 + 1
Array<FComplex>(numBins) { FComplex() }
}
else {
Array<FComplex>(output.size) { FComplex() }
}
System.arraycopy(output, 0, dftout, 0, dftout.size)
return dftout
fun ifftAndGetReal(y: ComplexArray): FloatArray {
transformInPlace(y, DftNormalization.STANDARD, TransformType.INVERSE)
return y.res
}
// org.apache.commons.math3.transform.FastFouriesTransformer.java:214
@@ -86,12 +54,12 @@ object FFT {
* @throws MathIllegalArgumentException if the number of data points is not
* a power of two
*/
private fun transformInPlace(dataRI: Array<FloatArray>, normalization: DftNormalization, type: TransformType) {
val dataR = dataRI[0]
val dataI = dataRI[1]
private fun transformInPlace(dataRI: ComplexArray, normalization: DftNormalization, type: TransformType) {
val dataR = dataRI.res
val dataI = dataRI.ims
val n = dataR.size
if (n == 1) {
/*if (n == 1) {
return
}
else if (n == 2) {
@@ -108,7 +76,7 @@ object FFT {
dataI[1] = srcI0 - srcI1
normalizeTransformedData(dataRI, normalization, type)
return
}
}*/
bitReversalShuffle2(dataR, dataI)
@@ -230,25 +198,26 @@ object FFT {
* @param type the type of transform (forward, inverse) which resulted in the specified data
*/
private fun normalizeTransformedData(
dataRI: Array<FloatArray>,
dataRI: ComplexArray,
normalization: DftNormalization, type: TransformType
) {
val dataR = dataRI[0]
val dataI = dataRI[1]
val dataR = dataRI.res
val dataI = dataRI.ims
val n = dataR.size
assert(dataI.size == n)
when (normalization) {
DftNormalization.STANDARD -> if (type == TransformType.INVERSE) {
val scaleFactor = 1f / n.toFloat()
var i = 0
while (i < n) {
dataR[i] *= scaleFactor
dataI[i] *= scaleFactor
i++
// assert(dataI.size == n)
// when (normalization) {
// DftNormalization.STANDARD ->
if (type == TransformType.INVERSE) {
val scaleFactor = 1f / n.toFloat()
var i = 0
while (i < n) {
dataR[i] *= scaleFactor
dataI[i] *= scaleFactor
i++
}
}
}
DftNormalization.UNITARY -> {
/* DftNormalization.UNITARY -> {
val scaleFactor = (1.0 / FastMath.sqrt(n.toDouble())).toFloat()
var i = 0
while (i < n) {
@@ -259,7 +228,7 @@ object FFT {
}
else -> throw MathIllegalStateException()
}
}*/
}
/**

View File

@@ -313,30 +313,29 @@ class Reverb(val delayMS: Float = 36f, var feedback: Float = 0.92f, var lowpass:
class Convolv(ir: File, val gain: Float = 1f / 256f): TerrarumAudioFilter() {
private val fftLen: Int
private val convFFT: Array<Array<FComplex>>
private val convFFTpartd: Array<Array<Array<FComplex>>> // index: Channel, partition, frequencies
private val convFFT: Array<ComplexArray>
// private val convFFTpartd: Array<Array<ComplexArray>> // index: Channel, partition, frequencies
private val inbuf: Array<FloatArray>
private val BLOCKSIZE = BUFFER_SIZE / 4
var processingSpeed = 1f; private set
private val partSizes: IntArray
private val partOffsets: IntArray
// private val partSizes: IntArray
// private val partOffsets: IntArray
init {
if (!ir.exists()) {
throw IllegalArgumentException("Impulse Response file '${ir.path}' does not exist.")
}
val sampleCount = ir.length().toInt() / 8
val sampleCount = (ir.length().toInt() / 8)//.coerceAtMost(65536)
fftLen = FastMath.nextPowerOfTwo(sampleCount)
// println("IR Sample Count = $sampleCount; FFT Length = $fftLen")
println("IR '${ir.path}' Sample Count = $sampleCount; FFT Length = $fftLen")
val conv = Array(2) { FloatArray(fftLen) }
inbuf = Array(2) { FloatArray(fftLen) }
// outbuf = Array(2) { DoubleArray(fftLen) }
ir.inputStream().let {
for (i in 0 until sampleCount) {
@@ -362,31 +361,10 @@ class Convolv(ir: File, val gain: Float = 1f / 256f): TerrarumAudioFilter() {
// println("convFFT Length = ${convFFT[0].size}")
if (fftLen < BUFFER_SIZE) // buffer size is always 4x the samples in the buffer
throw Error("FIR size is too small (minimum: $BUFFER_SIZE samples)")
val partitions = ArrayList<Int>()
var cnt = fftLen
while (cnt > BUFFER_SIZE / 4) {
cnt /= 2
partitions.add(cnt)
}
partitions.add(cnt)
partSizes = partitions.reversed().toIntArray()
partOffsets = partSizes.clone().also { it[0] = 0 }
// allocate arrays
convFFTpartd = Array(2) { ch ->
Array(partSizes.size) { partNo ->
Array(partSizes[partNo]) {
convFFT[ch][partOffsets[partNo] + it]
}
}
}
}
/**
* https://thewolfsound.com/fast-convolution-fft-based-overlap-add-overlap-save-partitioned/
*/
@@ -398,12 +376,19 @@ class Convolv(ir: File, val gain: Float = 1f / 256f): TerrarumAudioFilter() {
push(inbuf[ch].applyGain(gain), this.inbuf[ch])
val inputFFT = FFT.fft(this.inbuf[ch])
val Y = multiply(inputFFT, convFFT[ch])
val Y = inputFFT * convFFT[ch]
val y = FFT.ifftAndGetReal(Y)
val u = y.takeLast(BLOCKSIZE).toFloatArray()
/*val inputFFTs = FFT.fft(this.inbuf[ch]).sliceUnevenly()
val u = convFFTpartd[ch].zip(inputFFTs).map { (convFFT, inputFFT) ->
val Y = multiply(inputFFT, convFFT)
FFT.ifftAndGetReal(Y)
}.last().takeLast(BLOCKSIZE).toFloatArray()*/
System.arraycopy(u, 0, outbuf[ch], 0, BLOCKSIZE)
}
val t2 = System.nanoTime()
@@ -412,10 +397,6 @@ class Convolv(ir: File, val gain: Float = 1f / 256f): TerrarumAudioFilter() {
processingSpeed = realtime / ptime
}
private fun multiply(X: Array<FComplex>, H: Array<FComplex>): Array<FComplex> {
return Array(X.size) { X[it] * H[it] }
}
}
object XYtoMS: TerrarumAudioFilter() {

View File

@@ -20,7 +20,7 @@ class TerrarumAudioMixerTrack(val name: String, val isMaster: Boolean = false, v
const val SAMPLING_RATE = 48000
const val SAMPLING_RATEF = 48000f
const val SAMPLING_RATED = 48000.0
const val BUFFER_SIZE = 65536 // n ms -> 384 * n
const val BUFFER_SIZE = 512*4 // n ms -> 384 * n
}
val hash = getHashStr()