all-float FFT and convolution

This commit is contained in:
minjaesong
2023-11-25 21:46:49 +09:00
parent 51d1501267
commit 46f93660d0
6 changed files with 437 additions and 660 deletions

View File

@@ -221,6 +221,67 @@ as well as the JVMCI and VisualVM modules, are released under version 2 of the
GNU General Public License with the “Classpath” Exception.
Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
$BULLET Jdsp
Copyright (c) 2019 Sambit Paul
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
$BULLET Apache Commons Codec
Copyright 2002-2023 The Apache Software Foundation
This product includes software developed at
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
The Apache Software Foundation (https://www.apache.org/).
$BULLET Apache Commons Math
Copyright 2001-2022 The Apache Software Foundation
This product includes software developed at
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
""").split('\n')

View File

@@ -0,0 +1,304 @@
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 real: Float = 0f, var imaginary: Float = 0f) {
operator fun times(other: FComplex) = FComplex(
this.real * other.real - this.imaginary * other.imaginary,
this.real * other.imaginary + this.imaginary * other.real
)
}
/**
* Modification of the code form JDSP and Apache Commons Math
*
* Created by minjaesong on 2023-11-25.
*/
object FFT {
// org.apache.commons.math3.transform.FastFouriesTransformer.java:370
fun fft(signal: FloatArray): Array<FComplex> {
val dataRI = arrayOf(signal.copyOf(), FloatArray(signal.size))
transformInPlace(dataRI, DftNormalization.STANDARD, TransformType.FORWARD)
val output = dataRI.toComplexArray()
return getComplex(output, false)
}
// 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].real
dataRI[1][i] = y[i].imaginary
}
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
}
// org.apache.commons.math3.transform.FastFouriesTransformer.java:214
/**
* Computes the standard transform of the specified complex data. The
* computation is done in place. The input data is laid out as follows
* <ul>
* <li>{@code dataRI[0][i]} is the real part of the {@code i}-th data point,</li>
* <li>{@code dataRI[1][i]} is the imaginary part of the {@code i}-th data point.</li>
* </ul>
*
* @param dataRI the two dimensional array of real and imaginary parts of the data
* @param normalization the normalization to be applied to the transformed data
* @param type the type of transform (forward, inverse) to be performed
* @throws DimensionMismatchException if the number of rows of the specified
* array is not two, or the array is not rectangular
* @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]
val n = dataR.size
if (n == 1) {
return
}
else if (n == 2) {
val srcR0 = dataR[0]
val srcI0 = dataI[0]
val srcR1 = dataR[1]
val srcI1 = dataI[1]
// X_0 = x_0 + x_1
dataR[0] = srcR0 + srcR1
dataI[0] = srcI0 + srcI1
// X_1 = x_0 - x_1
dataR[1] = srcR0 - srcR1
dataI[1] = srcI0 - srcI1
normalizeTransformedData(dataRI, normalization, type)
return
}
bitReversalShuffle2(dataR, dataI)
// Do 4-term DFT.
// Do 4-term DFT.
if (type == TransformType.INVERSE) {
var i0 = 0
while (i0 < n) {
val i1 = i0 + 1
val i2 = i0 + 2
val i3 = i0 + 3
val srcR0 = dataR[i0]
val srcI0 = dataI[i0]
val srcR1 = dataR[i2]
val srcI1 = dataI[i2]
val srcR2 = dataR[i1]
val srcI2 = dataI[i1]
val srcR3 = dataR[i3]
val srcI3 = dataI[i3]
// 4-term DFT
// X_0 = x_0 + x_1 + x_2 + x_3
dataR[i0] = srcR0 + srcR1 + srcR2 + srcR3
dataI[i0] = srcI0 + srcI1 + srcI2 + srcI3
// X_1 = x_0 - x_2 + j * (x_3 - x_1)
dataR[i1] = srcR0 - srcR2 + (srcI3 - srcI1)
dataI[i1] = srcI0 - srcI2 + (srcR1 - srcR3)
// X_2 = x_0 - x_1 + x_2 - x_3
dataR[i2] = srcR0 - srcR1 + srcR2 - srcR3
dataI[i2] = srcI0 - srcI1 + srcI2 - srcI3
// X_3 = x_0 - x_2 + j * (x_1 - x_3)
dataR[i3] = srcR0 - srcR2 + (srcI1 - srcI3)
dataI[i3] = srcI0 - srcI2 + (srcR3 - srcR1)
i0 += 4
}
}
else {
var i0 = 0
while (i0 < n) {
val i1 = i0 + 1
val i2 = i0 + 2
val i3 = i0 + 3
val srcR0 = dataR[i0]
val srcI0 = dataI[i0]
val srcR1 = dataR[i2]
val srcI1 = dataI[i2]
val srcR2 = dataR[i1]
val srcI2 = dataI[i1]
val srcR3 = dataR[i3]
val srcI3 = dataI[i3]
// 4-term DFT
// X_0 = x_0 + x_1 + x_2 + x_3
dataR[i0] = srcR0 + srcR1 + srcR2 + srcR3
dataI[i0] = srcI0 + srcI1 + srcI2 + srcI3
// X_1 = x_0 - x_2 + j * (x_3 - x_1)
dataR[i1] = srcR0 - srcR2 + (srcI1 - srcI3)
dataI[i1] = srcI0 - srcI2 + (srcR3 - srcR1)
// X_2 = x_0 - x_1 + x_2 - x_3
dataR[i2] = srcR0 - srcR1 + srcR2 - srcR3
dataI[i2] = srcI0 - srcI1 + srcI2 - srcI3
// X_3 = x_0 - x_2 + j * (x_1 - x_3)
dataR[i3] = srcR0 - srcR2 + (srcI3 - srcI1)
dataI[i3] = srcI0 - srcI2 + (srcR1 - srcR3)
i0 += 4
}
}
var lastN0 = 4
var lastLogN0 = 2
while (lastN0 < n) {
val n0 = lastN0 shl 1
val logN0 = lastLogN0 + 1
val wSubN0R = W_SUB_N_R[logN0]
var wSubN0I = W_SUB_N_I[logN0]
if (type == TransformType.INVERSE) {
wSubN0I = -wSubN0I
}
// Combine even/odd transforms of size lastN0 into a transform of size N0 (lastN0 * 2).
var destEvenStartIndex = 0
while (destEvenStartIndex < n) {
val destOddStartIndex = destEvenStartIndex + lastN0
var wSubN0ToRR = 1f
var wSubN0ToRI = 0f
for (r in 0 until lastN0) {
val grR = dataR[destEvenStartIndex + r]
val grI = dataI[destEvenStartIndex + r]
val hrR = dataR[destOddStartIndex + r]
val hrI = dataI[destOddStartIndex + r]
// dest[destEvenStartIndex + r] = Gr + WsubN0ToR * Hr
dataR[destEvenStartIndex + r] = grR + wSubN0ToRR * hrR - wSubN0ToRI * hrI
dataI[destEvenStartIndex + r] = grI + wSubN0ToRR * hrI + wSubN0ToRI * hrR
// dest[destOddStartIndex + r] = Gr - WsubN0ToR * Hr
dataR[destOddStartIndex + r] = grR - (wSubN0ToRR * hrR - wSubN0ToRI * hrI)
dataI[destOddStartIndex + r] = grI - (wSubN0ToRR * hrI + wSubN0ToRI * hrR)
// WsubN0ToR *= WsubN0R
val nextWsubN0ToRR = wSubN0ToRR * wSubN0R - wSubN0ToRI * wSubN0I
val nextWsubN0ToRI = wSubN0ToRR * wSubN0I + wSubN0ToRI * wSubN0R
wSubN0ToRR = nextWsubN0ToRR
wSubN0ToRI = nextWsubN0ToRI
}
destEvenStartIndex += n0
}
lastN0 = n0
lastLogN0 = logN0
}
normalizeTransformedData(dataRI, normalization, type)
}
/**
* Applies the proper normalization to the specified transformed data.
*
* @param dataRI the unscaled transformed data
* @param normalization the normalization to be applied
* @param type the type of transform (forward, inverse) which resulted in the specified data
*/
private fun normalizeTransformedData(
dataRI: Array<FloatArray>,
normalization: DftNormalization, type: TransformType
) {
val dataR = dataRI[0]
val dataI = dataRI[1]
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++
}
}
DftNormalization.UNITARY -> {
val scaleFactor = (1.0 / FastMath.sqrt(n.toDouble())).toFloat()
var i = 0
while (i < n) {
dataR[i] *= scaleFactor
dataI[i] *= scaleFactor
i++
}
}
else -> throw MathIllegalStateException()
}
}
/**
* Performs identical index bit reversal shuffles on two arrays of identical
* size. Each element in the array is swapped with another element based on
* the bit-reversal of the index. For example, in an array with length 16,
* item at binary index 0011 (decimal 3) would be swapped with the item at
* binary index 1100 (decimal 12).
*
* @param a the first array to be shuffled
* @param b the second array to be shuffled
*/
private fun bitReversalShuffle2(a: FloatArray, b: FloatArray) {
val n = a.size
assert(b.size == n)
val halfOfN = n shr 1
var j = 0
for (i in 0 until n) {
if (i < j) {
// swap indices i & j
var temp = a[i]
a[i] = a[j]
a[j] = temp
temp = b[i]
b[i] = b[j]
b[j] = temp
}
var k = halfOfN
while (k in 1..j) {
j -= k
k = k shr 1
}
j += k
}
}
private val W_SUB_N_R = FFTConsts.W_SUB_N_R.map { it.toFloat() }.toFloatArray()
private val W_SUB_N_I = FFTConsts.W_SUB_N_I.map { it.toFloat() }.toFloatArray()
}

View File

@@ -0,0 +1,55 @@
package net.torvald.terrarum.audio;
/**
* Created by minjaesong on 2023-11-25.
*/
public class FFTConsts {
// org.apache.commons.math3.transform.FastFouriesTransformer.java:58
/**
* {@code W_SUB_N_R[i]} is the real part of
* {@code exp(- 2 * i * pi / n)}:
* {@code W_SUB_N_R[i] = cos(2 * pi/ n)}, where {@code n = 2^i}.
*/
public static final double[] W_SUB_N_R =
{ 0x1.0p0, -0x1.0p0, 0x1.1a62633145c07p-54, 0x1.6a09e667f3bcdp-1
, 0x1.d906bcf328d46p-1, 0x1.f6297cff75cbp-1, 0x1.fd88da3d12526p-1, 0x1.ff621e3796d7ep-1
, 0x1.ffd886084cd0dp-1, 0x1.fff62169b92dbp-1, 0x1.fffd8858e8a92p-1, 0x1.ffff621621d02p-1
, 0x1.ffffd88586ee6p-1, 0x1.fffff62161a34p-1, 0x1.fffffd8858675p-1, 0x1.ffffff621619cp-1
, 0x1.ffffffd885867p-1, 0x1.fffffff62161ap-1, 0x1.fffffffd88586p-1, 0x1.ffffffff62162p-1
, 0x1.ffffffffd8858p-1, 0x1.fffffffff6216p-1, 0x1.fffffffffd886p-1, 0x1.ffffffffff621p-1
, 0x1.ffffffffffd88p-1, 0x1.fffffffffff62p-1, 0x1.fffffffffffd9p-1, 0x1.ffffffffffff6p-1
, 0x1.ffffffffffffep-1, 0x1.fffffffffffffp-1, 0x1.0p0, 0x1.0p0
, 0x1.0p0, 0x1.0p0, 0x1.0p0, 0x1.0p0
, 0x1.0p0, 0x1.0p0, 0x1.0p0, 0x1.0p0
, 0x1.0p0, 0x1.0p0, 0x1.0p0, 0x1.0p0
, 0x1.0p0, 0x1.0p0, 0x1.0p0, 0x1.0p0
, 0x1.0p0, 0x1.0p0, 0x1.0p0, 0x1.0p0
, 0x1.0p0, 0x1.0p0, 0x1.0p0, 0x1.0p0
, 0x1.0p0, 0x1.0p0, 0x1.0p0, 0x1.0p0
, 0x1.0p0, 0x1.0p0, 0x1.0p0 };
/**
* {@code W_SUB_N_I[i]} is the imaginary part of
* {@code exp(- 2 * i * pi / n)}:
* {@code W_SUB_N_I[i] = -sin(2 * pi/ n)}, where {@code n = 2^i}.
*/
public static final double[] W_SUB_N_I =
{ 0x1.1a62633145c07p-52, -0x1.1a62633145c07p-53, -0x1.0p0, -0x1.6a09e667f3bccp-1
, -0x1.87de2a6aea963p-2, -0x1.8f8b83c69a60ap-3, -0x1.917a6bc29b42cp-4, -0x1.91f65f10dd814p-5
, -0x1.92155f7a3667ep-6, -0x1.921d1fcdec784p-7, -0x1.921f0fe670071p-8, -0x1.921f8becca4bap-9
, -0x1.921faaee6472dp-10, -0x1.921fb2aecb36p-11, -0x1.921fb49ee4ea6p-12, -0x1.921fb51aeb57bp-13
, -0x1.921fb539ecf31p-14, -0x1.921fb541ad59ep-15, -0x1.921fb5439d73ap-16, -0x1.921fb544197ap-17
, -0x1.921fb544387bap-18, -0x1.921fb544403c1p-19, -0x1.921fb544422c2p-20, -0x1.921fb54442a83p-21
, -0x1.921fb54442c73p-22, -0x1.921fb54442cefp-23, -0x1.921fb54442d0ep-24, -0x1.921fb54442d15p-25
, -0x1.921fb54442d17p-26, -0x1.921fb54442d18p-27, -0x1.921fb54442d18p-28, -0x1.921fb54442d18p-29
, -0x1.921fb54442d18p-30, -0x1.921fb54442d18p-31, -0x1.921fb54442d18p-32, -0x1.921fb54442d18p-33
, -0x1.921fb54442d18p-34, -0x1.921fb54442d18p-35, -0x1.921fb54442d18p-36, -0x1.921fb54442d18p-37
, -0x1.921fb54442d18p-38, -0x1.921fb54442d18p-39, -0x1.921fb54442d18p-40, -0x1.921fb54442d18p-41
, -0x1.921fb54442d18p-42, -0x1.921fb54442d18p-43, -0x1.921fb54442d18p-44, -0x1.921fb54442d18p-45
, -0x1.921fb54442d18p-46, -0x1.921fb54442d18p-47, -0x1.921fb54442d18p-48, -0x1.921fb54442d18p-49
, -0x1.921fb54442d18p-50, -0x1.921fb54442d18p-51, -0x1.921fb54442d18p-52, -0x1.921fb54442d18p-53
, -0x1.921fb54442d18p-54, -0x1.921fb54442d18p-55, -0x1.921fb54442d18p-56, -0x1.921fb54442d18p-57
, -0x1.921fb54442d18p-58, -0x1.921fb54442d18p-59, -0x1.921fb54442d18p-60 };
}

View File

@@ -1,7 +1,5 @@
package net.torvald.terrarum.audio
import com.github.psambit9791.jdsp.transform.FastFourier
import com.github.psambit9791.jdsp.transform.InverseFastFourier
import com.jme3.math.FastMath
import com.jme3.math.FastMath.sin
import net.torvald.terrarum.audio.AudioMixer.SPEED_OF_SOUND
@@ -9,7 +7,6 @@ import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.BUFFER_SIZE
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RATED
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RATEF
import net.torvald.terrarum.roundToFloat
import org.apache.commons.math3.complex.Complex
import java.io.File
import kotlin.math.absoluteValue
import kotlin.math.roundToInt
@@ -298,12 +295,11 @@ class Reverb(val delayMS: Float = 36f, var feedback: Float = 0.92f, var lowpass:
}
}
class Convolv(ir: File, val gain: Float = decibelsToFullscale(-12.0).toFloat()): TerrarumAudioFilter() {
class Convolv(ir: File, val gain: Float = decibelsToFullscale(-24.0).toFloat()): TerrarumAudioFilter() {
private val fftLen: Int
private val convFFT: Array<Array<Complex>>
private val inbuf: Array<DoubleArray>
// private val outbuf: Array<DoubleArray>
private val convFFT: Array<Array<FComplex>>
private val inbuf: Array<FloatArray>
private val BLOCKSIZE = BUFFER_SIZE / 4
@@ -317,8 +313,8 @@ class Convolv(ir: File, val gain: Float = decibelsToFullscale(-12.0).toFloat()):
println("IR Sample Count = $sampleCount; FFT Length = $fftLen")
val conv = Array(2) { DoubleArray(fftLen) }
inbuf = Array(2) { DoubleArray(fftLen) }
val conv = Array(2) { FloatArray(fftLen) }
inbuf = Array(2) { FloatArray(fftLen) }
// outbuf = Array(2) { DoubleArray(fftLen) }
ir.inputStream().let {
@@ -331,8 +327,8 @@ class Convolv(ir: File, val gain: Float = decibelsToFullscale(-12.0).toFloat()):
it.read().and(255).shl(8) or
it.read().and(255).shl(16) or
it.read().and(255).shl(24))
conv[0][i] = f1.toDouble()
conv[1][i] = f2.toDouble()
conv[0][i] = f1
conv[1][i] = f2
}
it.close()
@@ -340,7 +336,7 @@ class Convolv(ir: File, val gain: Float = decibelsToFullscale(-12.0).toFloat()):
// fourier-transform the 'conv'
convFFT = Array(2) {
FastFourier(conv[it]).let { it.transform(); it.getComplex(false) }
FFT.fft(conv[it])
}
println("convFFT Length = ${convFFT[0].size}")
@@ -354,72 +350,33 @@ class Convolv(ir: File, val gain: Float = decibelsToFullscale(-12.0).toFloat()):
val t1 = System.nanoTime()
for (ch in outbuf1.indices) {
push(inbuf1[ch].applyGain(gain), inbuf[ch])
push(inbuf1[ch].toDoubleArray(gain), inbuf[ch])
val inputFFT = FastFourier(inbuf[ch]).let { it.transform(); it.getComplex(false) }
val Ny = inputFFT.size// + convFFT[ch].size - 1
// println("inputFFT.size=${inputFFT.size} convFFT[ch].size=${convFFT[ch].size} Ny=$Ny")
val inputFFT = FFT.fft(inbuf[ch])
val Y = multiply(inputFFT, convFFT[ch])
val y = real(ifft(Y))
val y = FFT.ifftAndGetReal(Y)
// val u = y.sliceArray(Ny - BLOCKSIZE until Ny).toFloatArray(gain) // y size == Ny
val u = y.takeLast(BLOCKSIZE).map { it.toFloat() }.toFloatArray()
// println("y size: ${y.size}; Ny=$Ny")
val u = y.takeLast(BLOCKSIZE).toFloatArray()
System.arraycopy(u, 0, outbuf1[ch], 0, BLOCKSIZE)
}
val t2 = System.nanoTime()
val ptime = (t2 - t1).toDouble()
val realtime = BLOCKSIZE / SAMPLING_RATED * 1000000000L
if (realtime >= ptime) {
// println("Processing speed: ${realtime / ptime}x FASTER than realtime")
}
else {
// println("Processing speed: ${ptime / realtime}x SLOWER than realtime")
}
println("Processing speed: ${realtime / ptime}x")
}
private fun real(cs: Array<Complex>): DoubleArray {
return cs.map { it.real }.toDoubleArray()
private fun multiply(X: Array<FComplex>, H: Array<FComplex>): Array<FComplex> {
return Array(X.size) { X[it] * H[it] }
}
private fun ifft(y: Array<Complex>): Array<Complex> {
return InverseFastFourier(y, false).let { it.transform(); it.complex }
}
private fun multiply(X: Array<Complex>, H: Array<Complex>): Array<Complex> {
if (X.size != H.size) throw IllegalArgumentException()
return Array(X.size) {
//X[it].multiply(H[it])
// following is a snippet of the code from org.apache.commons.math3.complex.multiply,
// to remove the non-necessary sanity checks
val a = X[it]
val b = H[it]
val re = a.real * b.real - a.imaginary * b.imaginary
val im = a.real * b.imaginary + a.imaginary * b.real
Complex(re, im)
}
}
private fun push(sample: Double, buf: DoubleArray) {
System.arraycopy(buf, 1, buf, 0, buf.size - 1)
buf[buf.lastIndex] = sample
}
private fun push(samples: DoubleArray, buf: DoubleArray) {
private fun push(samples: FloatArray, buf: FloatArray) {
System.arraycopy(buf, samples.size, buf, 0, buf.size - samples.size)
System.arraycopy(samples, 0, buf, buf.size - samples.size, samples.size)
}
private fun FloatArray.toDoubleArray(gain: Float = 1f) = this.map { it.toDouble() * gain }.toDoubleArray()
private fun DoubleArray.toFloatArray(gain: Float = 1f) = this.map { it.toFloat() * gain }.toFloatArray()
private fun FloatArray.applyGain(gain: Float = 1f) = this.map { it * gain }.toFloatArray()
}
object XYtoMS: TerrarumAudioFilter() {

View File

@@ -21,9 +21,6 @@ class TerrarumAudioMixerTrack(val name: String, val isMaster: Boolean = false, v
const val SAMPLING_RATEF = 48000f
const val SAMPLING_RATED = 48000.0
const val BUFFER_SIZE = 512 // n ms -> 384 * n
const val INDEX_BGM = 0
const val INDEX_AMB = 1
}
val hash = getHashStr()