audio filters moved to its own files

This commit is contained in:
minjaesong
2023-11-28 13:56:15 +09:00
parent ce6f5909a8
commit af48a171eb
22 changed files with 542 additions and 459 deletions

View File

@@ -6,10 +6,10 @@ import com.badlogic.gdx.backends.lwjgl3.audio.Lwjgl3Audio
import com.badlogic.gdx.utils.Disposable import com.badlogic.gdx.utils.Disposable
import com.jme3.math.FastMath import com.jme3.math.FastMath
import net.torvald.terrarum.App import net.torvald.terrarum.App
import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.ModMgr import net.torvald.terrarum.ModMgr
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RATE import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RATE
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RATED import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RATED
import net.torvald.terrarum.audio.dsp.*
import net.torvald.terrarum.concurrent.ThreadExecutor import net.torvald.terrarum.concurrent.ThreadExecutor
import net.torvald.terrarum.modulebasegame.MusicContainer import net.torvald.terrarum.modulebasegame.MusicContainer
import net.torvald.terrarum.tryDispose import net.torvald.terrarum.tryDispose

View File

@@ -2,10 +2,10 @@ package net.torvald.terrarum.audio
import com.badlogic.gdx.utils.Queue import com.badlogic.gdx.utils.Queue
import net.torvald.reflection.forceInvoke import net.torvald.reflection.forceInvoke
import net.torvald.terrarum.audio.dsp.NullFilter
import net.torvald.terrarum.sqr import net.torvald.terrarum.sqr
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
import kotlin.math.sqrt import kotlin.math.sqrt
import kotlin.math.tanh
/** /**
* Created by minjaesong on 2023-11-17. * Created by minjaesong on 2023-11-17.

View File

@@ -1,445 +0,0 @@
package net.torvald.terrarum.audio
import com.jme3.math.FastMath
import com.jme3.math.FastMath.sin
import net.torvald.terrarum.App.measureDebugTime
import net.torvald.terrarum.audio.AudioMixer.SPEED_OF_SOUND
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.BUFFER_SIZE
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RATEF
import net.torvald.terrarum.roundToFloat
import java.io.File
import kotlin.math.absoluteValue
import kotlin.math.roundToInt
import kotlin.math.tanh
abstract class TerrarumAudioFilter {
var bypass = false
protected abstract fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>)
operator fun invoke(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
if (bypass) {
outbuf.forEachIndexed { index, outTrack ->
System.arraycopy(inbuf[index], 0, outTrack, 0, outTrack.size)
}
}
else thru(inbuf, outbuf)
}
}
object NullFilter : TerrarumAudioFilter() {
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
outbuf.forEachIndexed { index, outTrack ->
System.arraycopy(inbuf[index], 0, outTrack, 0, outTrack.size)
}
}
}
object SoftClp : TerrarumAudioFilter() {
val downForce = arrayOf(1.0f, 1.0f)
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
downForce.fill(1.0f)
for (ch in inbuf.indices) {
val inn = inbuf[ch]
val out = outbuf[ch]
for (i in inn.indices) {
val u = inn[i] * 0.95f
val v = tanh(u)
val diff = (v.absoluteValue / u.absoluteValue)
out[i] = v
if (!diff.isNaN()) {
downForce[ch] = minOf(downForce[ch], diff)
}
}
}
}
}
class Scope : TerrarumAudioFilter() {
val backbufL = Array((4096f / BUFFER_SIZE * 4).roundToInt().coerceAtLeast(1)) { FloatArray(BUFFER_SIZE / 4) }
val backbufR = Array((4096f / BUFFER_SIZE * 4).roundToInt().coerceAtLeast(1)) { FloatArray(BUFFER_SIZE / 4) }
private val sqrt2p = 0.7071067811865475
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
// shift buffer
for (i in backbufL.lastIndex downTo 1) {
backbufL[i] = backbufL[i - 1]
backbufR[i] = backbufR[i - 1]
}
backbufL[0] = FloatArray(BUFFER_SIZE / 4)
backbufR[0] = FloatArray(BUFFER_SIZE / 4)
// plot dots
for (i in 0 until BUFFER_SIZE/4) {
val y0 = inbuf[0][i] * 0.7
val x0 = -inbuf[1][i] * 0.7 // rotate the domain by -90 deg
val x = (+x0*sqrt2p -y0*sqrt2p) * 1.414
val y = (-x0*sqrt2p -y0*sqrt2p) * 1.414 // further rotate by -45 deg then flip along the y axis
backbufL[0][i] = x.toFloat()
backbufR[0][i] = y.toFloat()
}
// copy samples over
outbuf.forEachIndexed { index, outTrack ->
System.arraycopy(inbuf[index], 0, outTrack, 0, outTrack.size)
}
}
}
class Lowpass(cutoff0: Float): TerrarumAudioFilter() {
var cutoff = cutoff0.toDouble(); private set
private var alpha: Float = 0f
init {
setCutoff(cutoff0)
}
fun setCutoff(cutoff: Float) {
val RC: Float = 1f / (cutoff * FastMath.TWO_PI)
val dt: Float = 1f / SAMPLING_RATEF
alpha = dt / (RC + dt)
this.cutoff = cutoff.toDouble()
}
fun setCutoff(cutoff: Double) {
// println("LP Cutoff: $cutoff")
val RC: Double = 1.0 / (cutoff * Math.PI * 2.0)
val dt: Double = 1.0 / SAMPLING_RATEF
alpha = (dt / (RC + dt)).toFloat()
this.cutoff = cutoff
}
val in0 = FloatArray(2)
val out0 = FloatArray(2)
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
for (ch in outbuf.indices) {
val out = outbuf[ch]
val inn = inbuf[ch]
out[0] = out0[ch] + alpha * (inn[0] - out0[ch])
for (i in 1 until outbuf[ch].size) {
out[i] = out[i-1] + alpha * (inn[i] - out[i-1])
}
out0[ch] = outbuf[ch].last()
in0[ch] = inbuf[ch].last()
}
}
}
class Highpass(cutoff0: Float): TerrarumAudioFilter() {
var cutoff = cutoff0.toDouble(); private set
private var alpha: Float = 0f
val in0 = FloatArray(2)
val out0 = FloatArray(2)
init {
setCutoff(cutoff0)
}
fun setCutoff(cutoff: Float) {
// println("LP Cutoff: $cutoff")
val RC: Float = 1f / (cutoff * FastMath.TWO_PI)
val dt: Float = 1f / SAMPLING_RATEF
alpha = RC / (RC + dt)
this.cutoff = cutoff.toDouble()
}
fun setCutoff(cutoff: Double) {
// println("LP Cutoff: $cutoff")
val RC: Double = 1.0 / (cutoff * Math.PI * 2.0)
val dt: Double = 1.0 / SAMPLING_RATEF
alpha = (RC / (RC + dt)).toFloat()
this.cutoff = cutoff
}
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
for (ch in outbuf.indices) {
val out = outbuf[ch]
val inn = inbuf[ch]
out[0] = alpha * (out0[ch] + inn[0] - in0[ch])
for (i in 1 until outbuf[ch].size) {
out[i] = alpha * (out[i-1] + inn[i] - inn[i-1])
}
out0[ch] = outbuf[ch].last()
in0[ch] = inbuf[ch].last()
}
}
}
object Buffer : TerrarumAudioFilter() {
init {
bypass = true
}
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
bypass = true
}
}
/**
* The input audio must be monaural
*
* @param pan -1 for far-left, 0 for centre, 1 for far-right
* @param soundSpeed speed of the sound in meters per seconds
* @param earDist distance between ears in meters
*/
class BinoPan(var pan: Float, var earDist: Float = 0.18f): TerrarumAudioFilter() {
private val PANNING_CONST = 3.0 // 3dB panning rule
private val delayLine = FloatArray(BUFFER_SIZE / 4)
private fun getFrom(index: Float, buf0: FloatArray, buf1: FloatArray): Float {
val index = index.toInt() // TODO resampling
return if (index >= 0) buf1[index]
else buf0[buf0.size + index]
}
private val delays = arrayOf(0f, 0f)
private val mults = arrayOf(1f, 1f)
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
val angle = pan * 1.5707963f
val timeDiffMax = earDist / SPEED_OF_SOUND * SAMPLING_RATEF
val delayInSamples = (timeDiffMax * sin(angle)).absoluteValue
val volMultDbThis = PANNING_CONST * pan.absoluteValue
val volMultFsThis = decibelsToFullscale(volMultDbThis).toFloat()
val volMUltFsOther = 1f / volMultFsThis
if (pan >= 0) {
delays[0] = delayInSamples
delays[1] = 0f
}
else {
delays[0] = 0f
delays[1] = delayInSamples
}
if (pan >= 0) {
mults[0] = volMUltFsOther
mults[1] = volMultFsThis
}
else {
mults[0] = volMultFsThis
mults[1] = volMUltFsOther
}
for (ch in 0..1) {
for (i in 0 until BUFFER_SIZE / 4) {
outbuf[ch][i] = getFrom(i - delays[ch], delayLine, inbuf[0]) * mults[ch]
}
}
push(inbuf[0], delayLine)
}
}
class Bitcrush(var steps: Int, var inputGain: Float = 1f): TerrarumAudioFilter() {
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
for (ch in outbuf.indices) {
for (i in 0 until BUFFER_SIZE / 4) {
val inn = ((inbuf[ch][i] * inputGain).coerceIn(-1f, 1f) + 1f) / 2f // 0f..1f
val stepped = (inn * (steps - 1)).roundToFloat() / (steps - 1)
val out = (stepped * 2f) - 1f // -1f..1f
outbuf[ch][i] = out
}
}
}
}
class Reverb(val delayMS: Float = 36f, var feedback: Float = 0.92f, var lowpass: Float = 1200f): TerrarumAudioFilter() {
private val highpass = 80f
private var delay = (SAMPLING_RATEF * delayMS / 1000f).roundToInt()
private val bufSize = delay + 2
private val buf = Array(2) { FloatArray(bufSize) }
private fun unshift(sample: Float, buf: FloatArray) {
for (i in bufSize - 1 downTo 1) {
buf[i] = buf[i - 1]
}
buf[0] = sample
}
private val out0 = FloatArray(2)
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
val RCLo: Float = 1f / (lowpass * FastMath.TWO_PI)
val RCHi: Float = 1f / (highpass * FastMath.TWO_PI)
val dt: Float = 1f / SAMPLING_RATEF
val alphaLo = dt / (RCLo + dt)
val alphaHi = RCHi / (RCHi + dt)
for (ch in outbuf.indices) {
for (i in 0 until BUFFER_SIZE / 4) {
val inn = inbuf[ch][i]
// reverb
val rev = buf[ch][delay - 1]
val out = inn - rev * feedback
// fill lpbuf
val lp0 = buf[ch][0]
val lp = lp0 + alphaLo * (out - lp0)
unshift(lp, buf[ch])
outbuf[ch][i] = out
}
}
}
}
class Convolv(ir: File, val gain: Float = 1f / 256f): TerrarumAudioFilter() {
val fftLen: Int
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
init {
if (!ir.exists()) {
throw IllegalArgumentException("Impulse Response file '${ir.path}' does not exist.")
}
val sampleCount = (ir.length().toInt() / 8)//.coerceAtMost(65536)
fftLen = FastMath.nextPowerOfTwo(sampleCount)
println("IR '${ir.path}' Sample Count = $sampleCount; FFT Length = $fftLen")
val conv = Array(2) { FloatArray(fftLen) }
inbuf = Array(2) { FloatArray(fftLen) }
ir.inputStream().let {
for (i in 0 until sampleCount) {
val f1 = Float.fromBits(it.read().and(255) or
it.read().and(255).shl(8) or
it.read().and(255).shl(16) or
it.read().and(255).shl(24))
val f2 = Float.fromBits(it.read().and(255) or
it.read().and(255).shl(8) or
it.read().and(255).shl(16) or
it.read().and(255).shl(24))
conv[0][i] = f1
conv[1][i] = f2
}
it.close()
}
// fourier-transform the 'conv'
convFFT = Array(2) {
FFT.fft(conv[it])
}
// println("convFFT Length = ${convFFT[0].size}")
}
/**
* https://thewolfsound.com/fast-convolution-fft-based-overlap-add-overlap-save-partitioned/
*/
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
// println("Convolv thru")
val t1 = System.nanoTime()
for (ch in outbuf.indices) {
push(inbuf[ch].applyGain(gain), this.inbuf[ch])
var inputFFT: ComplexArray? = null
var Y: ComplexArray? = null
lateinit var y: FloatArray
measureDebugTime("audio.convolve.inputFFT") { inputFFT = FFT.fft(this.inbuf[ch]) }
measureDebugTime("audio.convolve.multiply") { Y = inputFFT!! * convFFT[ch] }
measureDebugTime("audio.convolve.inputIFFT") { 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()
val ptime = (t2 - t1).toFloat()
val realtime = BLOCKSIZE / SAMPLING_RATEF * 1000000000L
processingSpeed = realtime / ptime
}
}
object XYtoMS: TerrarumAudioFilter() {
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
for (i in 0 until BUFFER_SIZE / 4) {
val X = inbuf[0][i]
val Y = inbuf[1][i]
val M = (X + Y) / 2f
val S = (X - Y) / 2f
outbuf[0][i] = M
outbuf[1][i] = S
}
}
}
object MStoXY: TerrarumAudioFilter() {
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
for (i in 0 until BUFFER_SIZE / 4) {
val M = inbuf[0][i]
val S = inbuf[1][i]
val X = M + S
val Y = M - S
outbuf[0][i] = X
outbuf[1][i] = Y
}
}
}
class Gain(var gain: Float): TerrarumAudioFilter() {
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
for (i in 0 until BUFFER_SIZE / 4) {
outbuf[0][i] = inbuf[0][i] * gain
outbuf[1][i] = inbuf[1][i] * gain
}
}
}
fun FloatArray.applyGain(gain: Float = 1f) = this.map { it * gain }.toFloatArray()
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)
}

View File

@@ -4,12 +4,12 @@ import com.badlogic.gdx.Gdx
import com.badlogic.gdx.backends.lwjgl3.audio.OpenALLwjgl3Audio import com.badlogic.gdx.backends.lwjgl3.audio.OpenALLwjgl3Audio
import com.badlogic.gdx.utils.Disposable import com.badlogic.gdx.utils.Disposable
import com.badlogic.gdx.utils.Queue import com.badlogic.gdx.utils.Queue
import net.torvald.reflection.forceInvoke
import net.torvald.terrarum.App import net.torvald.terrarum.App
import net.torvald.terrarum.audio.dsp.NullFilter
import net.torvald.terrarum.audio.dsp.TerrarumAudioFilter
import net.torvald.terrarum.getHashStr import net.torvald.terrarum.getHashStr
import net.torvald.terrarum.hashStrMap import net.torvald.terrarum.hashStrMap
import net.torvald.terrarum.modulebasegame.MusicContainer import net.torvald.terrarum.modulebasegame.MusicContainer
import java.lang.Thread.MAX_PRIORITY
import kotlin.math.log10 import kotlin.math.log10
import kotlin.math.pow import kotlin.math.pow

View File

@@ -0,0 +1,65 @@
package net.torvald.terrarum.audio.dsp
import com.jme3.math.FastMath
import net.torvald.terrarum.audio.AudioMixer
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack
import net.torvald.terrarum.audio.decibelsToFullscale
import kotlin.math.absoluteValue
/**
* The input audio must be monaural
*
* @param pan -1 for far-left, 0 for centre, 1 for far-right
* @param soundSpeed speed of the sound in meters per seconds
* @param earDist distance between ears in meters
*/
class BinoPan(var pan: Float, var earDist: Float = 0.18f): TerrarumAudioFilter() {
private val PANNING_CONST = 3.0 // 3dB panning rule
private val delayLine = FloatArray(TerrarumAudioMixerTrack.BUFFER_SIZE / 4)
private fun getFrom(index: Float, buf0: FloatArray, buf1: FloatArray): Float {
val index = index.toInt() // TODO resampling
return if (index >= 0) buf1[index]
else buf0[buf0.size + index]
}
private val delays = arrayOf(0f, 0f)
private val mults = arrayOf(1f, 1f)
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
val angle = pan * 1.5707963f
val timeDiffMax = earDist / AudioMixer.SPEED_OF_SOUND * TerrarumAudioMixerTrack.SAMPLING_RATEF
val delayInSamples = (timeDiffMax * FastMath.sin(angle)).absoluteValue
val volMultDbThis = PANNING_CONST * pan.absoluteValue
val volMultFsThis = decibelsToFullscale(volMultDbThis).toFloat()
val volMUltFsOther = 1f / volMultFsThis
if (pan >= 0) {
delays[0] = delayInSamples
delays[1] = 0f
}
else {
delays[0] = 0f
delays[1] = delayInSamples
}
if (pan >= 0) {
mults[0] = volMUltFsOther
mults[1] = volMultFsThis
}
else {
mults[0] = volMultFsThis
mults[1] = volMUltFsOther
}
for (ch in 0..1) {
for (i in 0 until TerrarumAudioMixerTrack.BUFFER_SIZE / 4) {
outbuf[ch][i] = getFrom(i - delays[ch], delayLine, inbuf[0]) * mults[ch]
}
}
push(inbuf[0], delayLine)
}
}

View File

@@ -0,0 +1,17 @@
package net.torvald.terrarum.audio.dsp
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack
import net.torvald.terrarum.roundToFloat
class Bitcrush(var steps: Int, var inputGain: Float = 1f): TerrarumAudioFilter() {
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
for (ch in outbuf.indices) {
for (i in 0 until TerrarumAudioMixerTrack.BUFFER_SIZE / 4) {
val inn = ((inbuf[ch][i] * inputGain).coerceIn(-1f, 1f) + 1f) / 2f // 0f..1f
val stepped = (inn * (steps - 1)).roundToFloat() / (steps - 1)
val out = (stepped * 2f) - 1f // -1f..1f
outbuf[ch][i] = out
}
}
}
}

View File

@@ -0,0 +1,11 @@
package net.torvald.terrarum.audio.dsp
object Buffer : TerrarumAudioFilter() {
init {
bypass = true
}
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
bypass = true
}
}

View File

@@ -0,0 +1,154 @@
package net.torvald.terrarum.audio.dsp
import com.jme3.math.FastMath
import net.torvald.terrarum.App.measureDebugTime
import net.torvald.terrarum.audio.*
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.BUFFER_SIZE
import java.io.File
class Convolv(ir: File, val gain: Float = 1f / 256f): TerrarumAudioFilter() {
val fftLen: Int
private val convFFT: Array<ComplexArray>
private val convFFTpartd: Array<Array<ComplexArray>> // index: Channel, partition, frequencies
private val inputPartd: Array<Array<FloatArray>> // index: Channel, partition, frequencies
private val inbuf: Array<FloatArray>
private val BLOCKSIZE = TerrarumAudioMixerTrack.BUFFER_SIZE / 4
var processingSpeed = 1f; private set
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)//.coerceAtMost(65536)
fftLen = FastMath.nextPowerOfTwo(sampleCount)
println("IR '${ir.path}' Sample Count = $sampleCount; FFT Length = $fftLen")
val conv = Array(2) { FloatArray(fftLen) }
inbuf = Array(2) { FloatArray(fftLen) }
ir.inputStream().let {
for (i in 0 until sampleCount) {
val f1 = Float.fromBits(it.read().and(255) or
it.read().and(255).shl(8) or
it.read().and(255).shl(16) or
it.read().and(255).shl(24))
val f2 = Float.fromBits(it.read().and(255) or
it.read().and(255).shl(8) or
it.read().and(255).shl(16) or
it.read().and(255).shl(24))
conv[0][i] = f1
conv[1][i] = f2
}
it.close()
}
// fourier-transform the 'conv'
convFFT = Array(2) {
FFT.fft(conv[it])
}
// println("convFFT Length = ${convFFT[0].size}")
// fill up part* dictionary
// define "master" array
var c = BUFFER_SIZE / 4
val master0 = arrayListOf(c)
while (c < fftLen) {
master0.add(c)
c *= 2
}
partSizes = master0.toIntArray()
partOffsets = master0.toIntArray().also { it[0] = 0 }
convFFTpartd = Array(2) {
Array(partSizes.size) {
ComplexArray(FloatArray(2*partSizes[it]))
}
}
inputPartd = Array(2) {
Array(partSizes.size) {
FloatArray(partSizes[it])
}
}
fillUnevenly(convFFT[0], convFFTpartd[0])
fillUnevenly(convFFT[1], convFFTpartd[1])
}
private fun fillUnevenly(source: ComplexArray, dest: Array<ComplexArray>) {
for (i in partSizes.indices) {
val len = 2*partSizes[i]
val offset = 2*partOffsets[i]
System.arraycopy(source.reim, offset, dest[i].reim, 0, len)
}
}
private fun fillUnevenly(source: FloatArray, dest: Array<FloatArray>) {
for (i in partSizes.indices) {
val len = partSizes[i]
val offset = partOffsets[i]
System.arraycopy(source, offset, dest[i], 0, len)
}
}
private fun concatParts(source: List<ComplexArray>, dest: ComplexArray) {
for (i in partSizes.indices) {
val len = 2*partSizes[i]
val offset = 2*partOffsets[i]
System.arraycopy(source[i].reim, 0, dest.reim, offset, len)
}
}
private val targetY = ComplexArray(FloatArray(fftLen * 2))
/**
* https://thewolfsound.com/fast-convolution-fft-based-overlap-add-overlap-save-partitioned/
*/
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
// println("Convolv thru")
val t1 = System.nanoTime()
for (ch in outbuf.indices) {
push(inbuf[ch].applyGain(gain), this.inbuf[ch])
lateinit var u: FloatArray
measureDebugTime("audio.convolve") {
val inputFFT = FFT.fft(this.inbuf[ch])
val Y = inputFFT * convFFT[ch]
val y = FFT.ifftAndGetReal(Y)
u = y.takeLast(BLOCKSIZE).toFloatArray()
}
// doesn't work AND slightly slower than the lines above
/*measureDebugTime("audio.convolve") {
// orthodox uneven-partitioning
fillUnevenly(this.inbuf[ch], inputPartd[ch])
val partY = inputPartd[ch].mapIndexed { i, inputSamples ->
FFT.fft(inputSamples) * convFFTpartd[ch][i]
}
concatParts(partY, targetY)
val y = FFT.ifftAndGetReal(targetY)
u = y.takeLast(BLOCKSIZE).toFloatArray()
}*/
System.arraycopy(u, 0, outbuf[ch], 0, BLOCKSIZE)
}
val t2 = System.nanoTime()
val ptime = (t2 - t1).toFloat()
val realtime = BLOCKSIZE / TerrarumAudioMixerTrack.SAMPLING_RATEF * 1000000000L
processingSpeed = realtime / ptime
}
}

View File

@@ -0,0 +1,12 @@
package net.torvald.terrarum.audio.dsp
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack
class Gain(var gain: Float): TerrarumAudioFilter() {
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
for (i in 0 until TerrarumAudioMixerTrack.BUFFER_SIZE / 4) {
outbuf[0][i] = inbuf[0][i] * gain
outbuf[1][i] = inbuf[1][i] * gain
}
}
}

View File

@@ -0,0 +1,50 @@
package net.torvald.terrarum.audio.dsp
import com.jme3.math.FastMath
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack
class Highpass(cutoff0: Float): TerrarumAudioFilter() {
var cutoff = cutoff0.toDouble(); private set
private var alpha: Float = 0f
val in0 = FloatArray(2)
val out0 = FloatArray(2)
init {
setCutoff(cutoff0)
}
fun setCutoff(cutoff: Float) {
// println("LP Cutoff: $cutoff")
val RC: Float = 1f / (cutoff * FastMath.TWO_PI)
val dt: Float = 1f / TerrarumAudioMixerTrack.SAMPLING_RATEF
alpha = RC / (RC + dt)
this.cutoff = cutoff.toDouble()
}
fun setCutoff(cutoff: Double) {
// println("LP Cutoff: $cutoff")
val RC: Double = 1.0 / (cutoff * Math.PI * 2.0)
val dt: Double = 1.0 / TerrarumAudioMixerTrack.SAMPLING_RATEF
alpha = (RC / (RC + dt)).toFloat()
this.cutoff = cutoff
}
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
for (ch in outbuf.indices) {
val out = outbuf[ch]
val inn = inbuf[ch]
out[0] = alpha * (out0[ch] + inn[0] - in0[ch])
for (i in 1 until outbuf[ch].size) {
out[i] = alpha * (out[i-1] + inn[i] - inn[i-1])
}
out0[ch] = outbuf[ch].last()
in0[ch] = inbuf[ch].last()
}
}
}

View File

@@ -0,0 +1,50 @@
package net.torvald.terrarum.audio.dsp
import com.jme3.math.FastMath
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack
class Lowpass(cutoff0: Float): TerrarumAudioFilter() {
var cutoff = cutoff0.toDouble(); private set
private var alpha: Float = 0f
init {
setCutoff(cutoff0)
}
fun setCutoff(cutoff: Float) {
val RC: Float = 1f / (cutoff * FastMath.TWO_PI)
val dt: Float = 1f / TerrarumAudioMixerTrack.SAMPLING_RATEF
alpha = dt / (RC + dt)
this.cutoff = cutoff.toDouble()
}
fun setCutoff(cutoff: Double) {
// println("LP Cutoff: $cutoff")
val RC: Double = 1.0 / (cutoff * Math.PI * 2.0)
val dt: Double = 1.0 / TerrarumAudioMixerTrack.SAMPLING_RATEF
alpha = (dt / (RC + dt)).toFloat()
this.cutoff = cutoff
}
val in0 = FloatArray(2)
val out0 = FloatArray(2)
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
for (ch in outbuf.indices) {
val out = outbuf[ch]
val inn = inbuf[ch]
out[0] = out0[ch] + alpha * (inn[0] - out0[ch])
for (i in 1 until outbuf[ch].size) {
out[i] = out[i-1] + alpha * (inn[i] - out[i-1])
}
out0[ch] = outbuf[ch].last()
in0[ch] = inbuf[ch].last()
}
}
}

View File

@@ -0,0 +1,9 @@
package net.torvald.terrarum.audio.dsp
object NullFilter : TerrarumAudioFilter() {
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
outbuf.forEachIndexed { index, outTrack ->
System.arraycopy(inbuf[index], 0, outTrack, 0, outTrack.size)
}
}
}

View File

@@ -0,0 +1,49 @@
package net.torvald.terrarum.audio.dsp
import com.jme3.math.FastMath
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack
import kotlin.math.roundToInt
class Reverb(val delayMS: Float = 36f, var feedback: Float = 0.92f, var lowpass: Float = 1200f): TerrarumAudioFilter() {
private val highpass = 80f
private var delay = (TerrarumAudioMixerTrack.SAMPLING_RATEF * delayMS / 1000f).roundToInt()
private val bufSize = delay + 2
private val buf = Array(2) { FloatArray(bufSize) }
private fun unshift(sample: Float, buf: FloatArray) {
for (i in bufSize - 1 downTo 1) {
buf[i] = buf[i - 1]
}
buf[0] = sample
}
private val out0 = FloatArray(2)
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
val RCLo: Float = 1f / (lowpass * FastMath.TWO_PI)
val RCHi: Float = 1f / (highpass * FastMath.TWO_PI)
val dt: Float = 1f / TerrarumAudioMixerTrack.SAMPLING_RATEF
val alphaLo = dt / (RCLo + dt)
val alphaHi = RCHi / (RCHi + dt)
for (ch in outbuf.indices) {
for (i in 0 until TerrarumAudioMixerTrack.BUFFER_SIZE / 4) {
val inn = inbuf[ch][i]
// reverb
val rev = buf[ch][delay - 1]
val out = inn - rev * feedback
// fill lpbuf
val lp0 = buf[ch][0]
val lp = lp0 + alphaLo * (out - lp0)
unshift(lp, buf[ch])
outbuf[ch][i] = out
}
}
}
}

View File

@@ -0,0 +1,40 @@
package net.torvald.terrarum.audio.dsp
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack
import kotlin.math.roundToInt
class Scope : TerrarumAudioFilter() {
val backbufL = Array((4096f / TerrarumAudioMixerTrack.BUFFER_SIZE * 4).roundToInt().coerceAtLeast(1)) { FloatArray(
TerrarumAudioMixerTrack.BUFFER_SIZE / 4) }
val backbufR = Array((4096f / TerrarumAudioMixerTrack.BUFFER_SIZE * 4).roundToInt().coerceAtLeast(1)) { FloatArray(
TerrarumAudioMixerTrack.BUFFER_SIZE / 4) }
private val sqrt2p = 0.7071067811865475
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
// shift buffer
for (i in backbufL.lastIndex downTo 1) {
backbufL[i] = backbufL[i - 1]
backbufR[i] = backbufR[i - 1]
}
backbufL[0] = FloatArray(TerrarumAudioMixerTrack.BUFFER_SIZE / 4)
backbufR[0] = FloatArray(TerrarumAudioMixerTrack.BUFFER_SIZE / 4)
// plot dots
for (i in 0 until TerrarumAudioMixerTrack.BUFFER_SIZE /4) {
val y0 = inbuf[0][i] * 0.7
val x0 = -inbuf[1][i] * 0.7 // rotate the domain by -90 deg
val x = (+x0*sqrt2p -y0*sqrt2p) * 1.414
val y = (-x0*sqrt2p -y0*sqrt2p) * 1.414 // further rotate by -45 deg then flip along the y axis
backbufL[0][i] = x.toFloat()
backbufR[0][i] = y.toFloat()
}
// copy samples over
outbuf.forEachIndexed { index, outTrack ->
System.arraycopy(inbuf[index], 0, outTrack, 0, outTrack.size)
}
}
}

View File

@@ -0,0 +1,28 @@
package net.torvald.terrarum.audio.dsp
import kotlin.math.absoluteValue
import kotlin.math.tanh
object SoftClp : TerrarumAudioFilter() {
val downForce = arrayOf(1.0f, 1.0f)
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
downForce.fill(1.0f)
for (ch in inbuf.indices) {
val inn = inbuf[ch]
val out = outbuf[ch]
for (i in inn.indices) {
val u = inn[i] * 0.95f
val v = tanh(u)
val diff = (v.absoluteValue / u.absoluteValue)
out[i] = v
if (!diff.isNaN()) {
downForce[ch] = minOf(downForce[ch], diff)
}
}
}
}
}

View File

@@ -0,0 +1,20 @@
package net.torvald.terrarum.audio.dsp
abstract class TerrarumAudioFilter {
var bypass = false
protected abstract fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>)
operator fun invoke(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
if (bypass) {
outbuf.forEachIndexed { index, outTrack ->
System.arraycopy(inbuf[index], 0, outTrack, 0, outTrack.size)
}
}
else thru(inbuf, outbuf)
}
}
fun FloatArray.applyGain(gain: Float = 1f) = this.map { it * gain }.toFloatArray()
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)
}

View File

@@ -0,0 +1,29 @@
package net.torvald.terrarum.audio.dsp
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack
object XYtoMS: TerrarumAudioFilter() {
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
for (i in 0 until TerrarumAudioMixerTrack.BUFFER_SIZE / 4) {
val X = inbuf[0][i]
val Y = inbuf[1][i]
val M = (X + Y) / 2f
val S = (X - Y) / 2f
outbuf[0][i] = M
outbuf[1][i] = S
}
}
}
object MStoXY: TerrarumAudioFilter() {
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
for (i in 0 until TerrarumAudioMixerTrack.BUFFER_SIZE / 4) {
val M = inbuf[0][i]
val S = inbuf[1][i]
val X = M + S
val Y = M - S
outbuf[0][i] = X
outbuf[1][i] = Y
}
}
}

View File

@@ -8,7 +8,7 @@ import com.badlogic.gdx.graphics.g2d.TextureRegion
import net.torvald.terrarum.* import net.torvald.terrarum.*
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE
import net.torvald.terrarum.audio.AudioMixer import net.torvald.terrarum.audio.AudioMixer
import net.torvald.terrarum.audio.Lowpass import net.torvald.terrarum.audio.dsp.Lowpass
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack import net.torvald.terrarum.audio.TerrarumAudioMixerTrack
import net.torvald.terrarum.blockproperties.Block import net.torvald.terrarum.blockproperties.Block
import net.torvald.terrarum.blockproperties.BlockPropUtil import net.torvald.terrarum.blockproperties.BlockPropUtil

View File

@@ -13,8 +13,7 @@ import net.torvald.terrarum.Terrarum.getWorldSaveFiledesc
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZED import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZED
import net.torvald.terrarum.audio.AudioMixer import net.torvald.terrarum.audio.AudioMixer
import net.torvald.terrarum.audio.Lowpass import net.torvald.terrarum.audio.dsp.Lowpass
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RATEF import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RATEF
import net.torvald.terrarum.blockproperties.BlockPropUtil import net.torvald.terrarum.blockproperties.BlockPropUtil
import net.torvald.terrarum.blockstats.MinimapComposer import net.torvald.terrarum.blockstats.MinimapComposer

View File

@@ -18,7 +18,7 @@ import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZED import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZED
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZEF import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZEF
import net.torvald.terrarum.audio.AudioMixer import net.torvald.terrarum.audio.AudioMixer
import net.torvald.terrarum.audio.Lowpass import net.torvald.terrarum.audio.dsp.Lowpass
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack import net.torvald.terrarum.audio.TerrarumAudioMixerTrack
import net.torvald.terrarum.clut.Skybox import net.torvald.terrarum.clut.Skybox
import net.torvald.terrarum.console.CommandDict import net.torvald.terrarum.console.CommandDict

View File

@@ -1,6 +1,5 @@
package net.torvald.terrarum.modulebasegame.ui package net.torvald.terrarum.modulebasegame.ui
import com.badlogic.gdx.graphics.Camera
import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.OrthographicCamera import com.badlogic.gdx.graphics.OrthographicCamera
import com.badlogic.gdx.graphics.g2d.SpriteBatch import com.badlogic.gdx.graphics.g2d.SpriteBatch
@@ -8,10 +7,6 @@ import com.jme3.math.FastMath
import net.torvald.terrarum.* import net.torvald.terrarum.*
import net.torvald.terrarum.App.* import net.torvald.terrarum.App.*
import net.torvald.terrarum.audio.AudioMixer import net.torvald.terrarum.audio.AudioMixer
import net.torvald.terrarum.audio.Lowpass
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RATE
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RATEF
import net.torvald.terrarum.langpack.Lang import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.modulebasegame.gameactors.ActorHumanoid import net.torvald.terrarum.modulebasegame.gameactors.ActorHumanoid
import net.torvald.terrarum.ui.Toolkit import net.torvald.terrarum.ui.Toolkit
@@ -20,7 +15,6 @@ import net.torvald.terrarum.ui.UIHandler
import net.torvald.terrarum.ui.UIItemHorizontalFadeSlide import net.torvald.terrarum.ui.UIItemHorizontalFadeSlide
import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
import net.torvald.unicode.* import net.torvald.unicode.*
import kotlin.math.pow
/** /**
* Created by minjaesong on 2017-10-21. * Created by minjaesong on 2017-10-21.

View File

@@ -14,6 +14,7 @@ import net.torvald.terrarum.Terrarum.mouseTileY
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE
import net.torvald.terrarum.audio.* import net.torvald.terrarum.audio.*
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.BUFFER_SIZE import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.BUFFER_SIZE
import net.torvald.terrarum.audio.dsp.*
import net.torvald.terrarum.controller.TerrarumController import net.torvald.terrarum.controller.TerrarumController
import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.gameworld.fmod import net.torvald.terrarum.gameworld.fmod