diff --git a/assets/mods/basegame/audio/convolution/EchoThief - CedarCreekWinery.bin b/assets/mods/basegame/audio/convolution/EchoThief - CedarCreekWinery.bin deleted file mode 100644 index c630ccbf1..000000000 Binary files a/assets/mods/basegame/audio/convolution/EchoThief - CedarCreekWinery.bin and /dev/null differ diff --git a/assets/mods/basegame/audio/convolution/EchoThief - Cranbrook Art Museum.bin b/assets/mods/basegame/audio/convolution/EchoThief - Cranbrook Art Museum.bin deleted file mode 100644 index 4133b39e6..000000000 --- a/assets/mods/basegame/audio/convolution/EchoThief - Cranbrook Art Museum.bin +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2008760876a186741e201c752beb957b9e1e91f219e455adf72557abe09c600a -size 578760 diff --git a/assets/mods/basegame/audio/convolution/EchoThief - PurgatoryChasm.bin b/assets/mods/basegame/audio/convolution/EchoThief - PurgatoryChasm.bin new file mode 100644 index 000000000..ef2c85262 --- /dev/null +++ b/assets/mods/basegame/audio/convolution/EchoThief - PurgatoryChasm.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:046106dc38f21455ad5f8101f748385901fb25546c9c93ce8ad1f58332bd0684 +size 234672 diff --git a/assets/mods/basegame/audio/convolution/EchoThief - WaterplacePark-trimmed.bin b/assets/mods/basegame/audio/convolution/EchoThief - WaterplacePark-trimmed.bin new file mode 100644 index 000000000..0d93912d8 --- /dev/null +++ b/assets/mods/basegame/audio/convolution/EchoThief - WaterplacePark-trimmed.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:69791eadb78c723dab7d732a2acfe6af1edaaa55d70290406640332d72677180 +size 524288 diff --git a/assets/mods/basegame/audio/convolution/EchoThief - WoodruffLane.bin b/assets/mods/basegame/audio/convolution/EchoThief - WoodruffLane.bin deleted file mode 100644 index d4d2f1564..000000000 Binary files a/assets/mods/basegame/audio/convolution/EchoThief - WoodruffLane.bin and /dev/null differ diff --git a/src/net/torvald/terrarum/CreditSingleton.kt b/src/net/torvald/terrarum/CreditSingleton.kt index 25c9bfe6e..ed941118d 100644 --- a/src/net/torvald/terrarum/CreditSingleton.kt +++ b/src/net/torvald/terrarum/CreditSingleton.kt @@ -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 - - - - - $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 + + + + + +$BULLET EchoThief Impulse Response Library + +Copyright 2013-2023 Chris Warren """).split('\n') diff --git a/src/net/torvald/terrarum/audio/AudioMixer.kt b/src/net/torvald/terrarum/audio/AudioMixer.kt index 3e9db0e1c..5b3dea110 100644 --- a/src/net/torvald/terrarum/audio/AudioMixer.kt +++ b/src/net/torvald/terrarum/audio/AudioMixer.kt @@ -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) diff --git a/src/net/torvald/terrarum/audio/FFT.kt b/src/net/torvald/terrarum/audio/FFT.kt index c0fe64e8d..204071495 100644 --- a/src/net/torvald/terrarum/audio/FFT.kt +++ b/src/net/torvald/terrarum/audio/FFT.kt @@ -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 { - 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): FloatArray { - val dataRI = Array(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.toComplexArray(): Array { - 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, onlyPositive: Boolean): Array { - val dftout: Array = if (onlyPositive) { - val numBins: Int = output.size / 2 + 1 - Array(numBins) { FComplex() } - } - else { - Array(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, 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, + 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() - } + }*/ } /** diff --git a/src/net/torvald/terrarum/audio/TerrarumAudioFilter.kt b/src/net/torvald/terrarum/audio/TerrarumAudioFilter.kt index 9b9861bab..9e9898d1d 100644 --- a/src/net/torvald/terrarum/audio/TerrarumAudioFilter.kt +++ b/src/net/torvald/terrarum/audio/TerrarumAudioFilter.kt @@ -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> - private val convFFTpartd: Array>> // index: Channel, partition, frequencies + private val convFFT: Array +// private val convFFTpartd: Array> // index: Channel, partition, frequencies private val inbuf: Array 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() - 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, H: Array): Array { - return Array(X.size) { X[it] * H[it] } - } - } object XYtoMS: TerrarumAudioFilter() { diff --git a/src/net/torvald/terrarum/audio/TerrarumAudioMixerTrack.kt b/src/net/torvald/terrarum/audio/TerrarumAudioMixerTrack.kt index 1aa622f8d..dee69c3d3 100644 --- a/src/net/torvald/terrarum/audio/TerrarumAudioMixerTrack.kt +++ b/src/net/torvald/terrarum/audio/TerrarumAudioMixerTrack.kt @@ -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()