mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-07 20:31:51 +09:00
fft: data struct optimisation
This commit is contained in:
Binary file not shown.
Binary file not shown.
BIN
assets/mods/basegame/audio/convolution/EchoThief - PurgatoryChasm.bin
LFS
Normal file
BIN
assets/mods/basegame/audio/convolution/EchoThief - PurgatoryChasm.bin
LFS
Normal file
Binary file not shown.
BIN
assets/mods/basegame/audio/convolution/EchoThief - WaterplacePark-trimmed.bin
LFS
Normal file
BIN
assets/mods/basegame/audio/convolution/EchoThief - WaterplacePark-trimmed.bin
LFS
Normal file
Binary file not shown.
Binary file not shown.
@@ -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')
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user