audio: no longer holds prevsamples globally

This commit is contained in:
minjaesong
2023-11-25 22:50:59 +09:00
parent 46f93660d0
commit 49100289d3
8 changed files with 125 additions and 92 deletions

View File

@@ -105,15 +105,16 @@ object AudioMixer: Disposable {
// musicTrack.filters[0] = BinoPan((Math.random() * 2.0 - 1.0).toFloat()) // musicTrack.filters[0] = BinoPan((Math.random() * 2.0 - 1.0).toFloat())
// musicTrack.filters[1] = Reverb(36f, 0.92f, 1200f) // musicTrack.filters[1] = Reverb(36f, 0.92f, 1200f)
// masterTrack.filters[0] = SoftClp masterTrack.filters[0] = SoftClp
masterTrack.filters[1] = Buffer masterTrack.filters[1] = Buffer
masterTrack.filters[2] = Scope() masterTrack.filters[2] = Scope()
fadeBus.addSidechainInput(musicTrack, 1.0) fadeBus.addSidechainInput(musicTrack, 1.0)
fadeBus.addSidechainInput(ambientTrack, 1.0) fadeBus.addSidechainInput(ambientTrack, 1.0)
fadeBus.addSidechainInput(sfxMixTrack, 1.0) fadeBus.addSidechainInput(sfxMixTrack, 1.0)
fadeBus.filters[0] = Lowpass(SAMPLING_RATE / 2f) fadeBus.filters[0] = Convolv(ModMgr.getFile("basegame", "audio/convolution/EchoThief - CedarCreekWinery.bin"))
fadeBus.filters[1] = Convolv(ModMgr.getFile("basegame", "audio/convolution/WoodruffLane.bin")) fadeBus.filters[1] = Gain(16f)
fadeBus.filters[3] = Lowpass(SAMPLING_RATE / 2f)
masterTrack.addSidechainInput(fadeBus, 1.0) masterTrack.addSidechainInput(fadeBus, 1.0)
masterTrack.addSidechainInput(guiTrack, 1.0) masterTrack.addSidechainInput(guiTrack, 1.0)
@@ -207,20 +208,20 @@ object AudioMixer: Disposable {
if (lpOutFired) { if (lpOutFired) {
lpAkku += delta lpAkku += delta
val cutoff = linPercToLog(lpAkku / lpLength, lpStart, lpTarget) val cutoff = linPercToLog(lpAkku / lpLength, lpStart, lpTarget)
(fadeBus.filters[0] as Lowpass).setCutoff(cutoff) fadeBus.getFilter<Lowpass>().setCutoff(cutoff)
if (lpAkku >= lpLength) { if (lpAkku >= lpLength) {
lpOutFired = false lpOutFired = false
(fadeBus.filters[0] as Lowpass).setCutoff(lpTarget) fadeBus.getFilter<Lowpass>().setCutoff(lpTarget)
} }
} }
else if (lpInFired) { else if (lpInFired) {
lpAkku += delta lpAkku += delta
val cutoff = linPercToLog(lpAkku / lpLength, lpStart, lpTarget) val cutoff = linPercToLog(lpAkku / lpLength, lpStart, lpTarget)
(fadeBus.filters[0] as Lowpass).setCutoff(cutoff) fadeBus.getFilter<Lowpass>().setCutoff(cutoff)
if (lpAkku >= lpLength) { if (lpAkku >= lpLength) {
(fadeBus.filters[0] as Lowpass).setCutoff(lpTarget) fadeBus.getFilter<Lowpass>().setCutoff(lpTarget)
lpInFired = false lpInFired = false
} }
} }
@@ -290,7 +291,7 @@ object AudioMixer: Disposable {
lpLength = length.coerceAtLeast(1.0/1024.0) lpLength = length.coerceAtLeast(1.0/1024.0)
lpAkku = 0.0 lpAkku = 0.0
lpOutFired = true lpOutFired = true
lpStart = (fadeBus.filters[0] as Lowpass).cutoff lpStart = fadeBus.getFilter<Lowpass>().cutoff
lpTarget = SAMPLING_RATED / 2.0 lpTarget = SAMPLING_RATED / 2.0
} }
} }
@@ -300,7 +301,7 @@ object AudioMixer: Disposable {
lpLength = length.coerceAtLeast(1.0/1024.0) lpLength = length.coerceAtLeast(1.0/1024.0)
lpAkku = 0.0 lpAkku = 0.0
lpInFired = true lpInFired = true
lpStart = (fadeBus.filters[0] as Lowpass).cutoff lpStart = fadeBus.getFilter<Lowpass>().cutoff
lpTarget = SAMPLING_RATED / 100.0 lpTarget = SAMPLING_RATED / 100.0
} }
} }

View File

@@ -27,7 +27,6 @@ class MixerTrackProcessor(val bufferSize: Int, val rate: Int, val track: Terraru
internal val streamBuf = AudioProcessBuf(bufferSize) internal val streamBuf = AudioProcessBuf(bufferSize)
internal val sideChainBufs = Array(track.sidechainInputs.size) { AudioProcessBuf(bufferSize) } internal val sideChainBufs = Array(track.sidechainInputs.size) { AudioProcessBuf(bufferSize) }
private var fout0 = listOf(emptyBuf, emptyBuf)
private var fout1 = listOf(emptyBuf, emptyBuf) private var fout1 = listOf(emptyBuf, emptyBuf)
val maxSigLevel = arrayOf(0.0, 0.0) val maxSigLevel = arrayOf(0.0, 0.0)
@@ -101,8 +100,6 @@ class MixerTrackProcessor(val bufferSize: Int, val rate: Int, val track: Terraru
// add all up // add all up
sidechains.forEach { (side, mix) -> sidechains.forEach { (side, mix) ->
for (i in samplesL0!!.indices) { for (i in samplesL0!!.indices) {
samplesL0!![i] += side.processor.fout0[0][i] * (mix * track.volume).toFloat()
samplesR0!![i] += side.processor.fout0[1][i] * (mix * track.volume).toFloat()
samplesL1!![i] += side.processor.fout1[0][i] * (mix * track.volume).toFloat() samplesL1!![i] += side.processor.fout1[0][i] * (mix * track.volume).toFloat()
samplesR1!![i] += side.processor.fout1[1][i] * (mix * track.volume).toFloat() samplesR1!![i] += side.processor.fout1[1][i] * (mix * track.volume).toFloat()
} }
@@ -131,17 +128,13 @@ class MixerTrackProcessor(val bufferSize: Int, val rate: Int, val track: Terraru
fout1 = listOf(samplesL1!!, samplesR1!!) fout1 = listOf(samplesL1!!, samplesR1!!)
} }
else { else {
var fin0 = listOf(samplesL0!!, samplesR0!!)
var fin1 = listOf(samplesL1!!, samplesR1!!) var fin1 = listOf(samplesL1!!, samplesR1!!)
fout0 = fout1
fout1 = listOf(FloatArray(bufferSize / 4), FloatArray(bufferSize / 4)) fout1 = listOf(FloatArray(bufferSize / 4), FloatArray(bufferSize / 4))
filterStack.forEachIndexed { index, it -> filterStack.forEachIndexed { index, it ->
it(fin0, fin1, fout0, fout1) it(fin1, fout1)
fin0 = fout0
fin1 = fout1 fin1 = fout1
if (index < filterStack.lastIndex) { if (index < filterStack.lastIndex) {
fout0 = listOf(FloatArray(bufferSize / 4), FloatArray(bufferSize / 4))
fout1 = listOf(FloatArray(bufferSize / 4), FloatArray(bufferSize / 4)) fout1 = listOf(FloatArray(bufferSize / 4), FloatArray(bufferSize / 4))
} }
} }

View File

@@ -14,21 +14,21 @@ import kotlin.math.tanh
abstract class TerrarumAudioFilter { abstract class TerrarumAudioFilter {
var bypass = false var bypass = false
protected abstract fun thru(inbuf0: List<FloatArray>, inbuf1: List<FloatArray>, outbuf0: List<FloatArray>, outbuf1: List<FloatArray>) protected abstract fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>)
operator fun invoke(inbuf0: List<FloatArray>, inbuf1: List<FloatArray>, outbuf0: List<FloatArray>, outbuf1: List<FloatArray>) { operator fun invoke(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
if (bypass) { if (bypass) {
outbuf1.forEachIndexed { index, outTrack -> outbuf.forEachIndexed { index, outTrack ->
System.arraycopy(inbuf1[index], 0, outTrack, 0, outTrack.size) System.arraycopy(inbuf[index], 0, outTrack, 0, outTrack.size)
} }
} }
else thru(inbuf0, inbuf1, outbuf0, outbuf1) else thru(inbuf, outbuf)
} }
} }
object NullFilter : TerrarumAudioFilter() { object NullFilter : TerrarumAudioFilter() {
override fun thru(inbuf0: List<FloatArray>, inbuf1: List<FloatArray>, outbuf0: List<FloatArray>, outbuf1: List<FloatArray>) { override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
outbuf1.forEachIndexed { index, outTrack -> outbuf.forEachIndexed { index, outTrack ->
System.arraycopy(inbuf1[index], 0, outTrack, 0, outTrack.size) System.arraycopy(inbuf[index], 0, outTrack, 0, outTrack.size)
} }
} }
} }
@@ -36,12 +36,12 @@ object NullFilter : TerrarumAudioFilter() {
object SoftClp : TerrarumAudioFilter() { object SoftClp : TerrarumAudioFilter() {
val downForce = arrayOf(1.0f, 1.0f) val downForce = arrayOf(1.0f, 1.0f)
override fun thru(inbuf0: List<FloatArray>, inbuf1: List<FloatArray>, outbuf0: List<FloatArray>, outbuf1: List<FloatArray>) { override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
downForce.fill(1.0f) downForce.fill(1.0f)
for (ch in inbuf1.indices) { for (ch in inbuf.indices) {
val inn = inbuf1[ch] val inn = inbuf[ch]
val out = outbuf1[ch] val out = outbuf[ch]
for (i in inn.indices) { for (i in inn.indices) {
val u = inn[i] * 0.95f val u = inn[i] * 0.95f
@@ -63,7 +63,7 @@ class Scope : TerrarumAudioFilter() {
private val sqrt2p = 0.7071067811865475 private val sqrt2p = 0.7071067811865475
override fun thru(inbuf0: List<FloatArray>, inbuf1: List<FloatArray>, outbuf0: List<FloatArray>, outbuf1: List<FloatArray>) { override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
// shift buffer // shift buffer
for (i in backbufL.lastIndex downTo 1) { for (i in backbufL.lastIndex downTo 1) {
backbufL[i] = backbufL[i - 1] backbufL[i] = backbufL[i - 1]
@@ -74,8 +74,8 @@ class Scope : TerrarumAudioFilter() {
// plot dots // plot dots
for (i in 0 until BUFFER_SIZE/4) { for (i in 0 until BUFFER_SIZE/4) {
val y0 = inbuf1[0][i] * 0.7 val y0 = inbuf[0][i] * 0.7
val x0 = -inbuf1[1][i] * 0.7 // rotate the domain by -90 deg val x0 = -inbuf[1][i] * 0.7 // rotate the domain by -90 deg
val x = (+x0*sqrt2p -y0*sqrt2p) * 1.414 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 val y = (-x0*sqrt2p -y0*sqrt2p) * 1.414 // further rotate by -45 deg then flip along the y axis
@@ -85,8 +85,8 @@ class Scope : TerrarumAudioFilter() {
} }
// copy samples over // copy samples over
outbuf1.forEachIndexed { index, outTrack -> outbuf.forEachIndexed { index, outTrack ->
System.arraycopy(inbuf1[index], 0, outTrack, 0, outTrack.size) System.arraycopy(inbuf[index], 0, outTrack, 0, outTrack.size)
} }
} }
} }
@@ -116,16 +116,23 @@ class Lowpass(cutoff0: Float): TerrarumAudioFilter() {
this.cutoff = cutoff this.cutoff = cutoff
} }
override fun thru(inbuf0: List<FloatArray>, inbuf1: List<FloatArray>, outbuf0: List<FloatArray>, outbuf1: List<FloatArray>) { val in0 = FloatArray(2)
for (ch in outbuf1.indices) { val out0 = FloatArray(2)
val out = outbuf1[ch]
val inn = inbuf1[ch]
out[0] = outbuf0[ch].last() + alpha * (inn[0] - outbuf0[ch].last()) override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
for (ch in outbuf.indices) {
val out = outbuf[ch]
val inn = inbuf[ch]
for (i in 1 until outbuf1[ch].size) {
out[i] = out[i-1] + alpha * (inn[i] - out[i-1]) out[0] = (out0[ch].div(16f) + alpha * (inn[0].div(16f) - out0[ch].div(16f))).times(16f)
for (i in 1 until outbuf[ch].size) {
out[i] = (out[i-1].div(16f) + alpha * (inn[i].div(16f) - out[i-1].div(16f))).times(16f)
} }
out0[ch] = outbuf[ch].last()
in0[ch] = inbuf[ch].last()
} }
} }
@@ -137,6 +144,9 @@ class Highpass(cutoff0: Float): TerrarumAudioFilter() {
var cutoff = cutoff0.toDouble(); private set var cutoff = cutoff0.toDouble(); private set
private var alpha: Float = 0f private var alpha: Float = 0f
val in0 = FloatArray(2)
val out0 = FloatArray(2)
init { init {
setCutoff(cutoff0) setCutoff(cutoff0)
} }
@@ -157,16 +167,19 @@ class Highpass(cutoff0: Float): TerrarumAudioFilter() {
this.cutoff = cutoff this.cutoff = cutoff
} }
override fun thru(inbuf0: List<FloatArray>, inbuf1: List<FloatArray>, outbuf0: List<FloatArray>, outbuf1: List<FloatArray>) { override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
for (ch in outbuf1.indices) { for (ch in outbuf.indices) {
val out = outbuf1[ch] val out = outbuf[ch]
val inn = inbuf1[ch] val inn = inbuf[ch]
out[0] = alpha * (outbuf0[ch].last() + inn[0] - inbuf0[ch].last()) out[0] = alpha * (out0[ch] + inn[0] - in0[ch])
for (i in 1 until outbuf1[ch].size) { for (i in 1 until outbuf[ch].size) {
out[i] = alpha * (out[i-1] + inn[i] - inn[i-1]) out[i] = alpha * (out[i-1] + inn[i] - inn[i-1])
} }
out0[ch] = outbuf[ch].last()
in0[ch] = inbuf[ch].last()
} }
} }
@@ -178,7 +191,7 @@ object Buffer : TerrarumAudioFilter() {
bypass = true bypass = true
} }
override fun thru(inbuf0: List<FloatArray>, inbuf1: List<FloatArray>, outbuf0: List<FloatArray>, outbuf1: List<FloatArray>) { override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
bypass = true bypass = true
} }
} }
@@ -194,6 +207,8 @@ class BinoPan(var pan: Float, var earDist: Float = 0.18f): TerrarumAudioFilter()
private val PANNING_CONST = 3.0 // 3dB panning rule 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 { private fun getFrom(index: Float, buf0: FloatArray, buf1: FloatArray): Float {
val index = index.toInt() // TODO resampling val index = index.toInt() // TODO resampling
return if (index >= 0) buf1[index] return if (index >= 0) buf1[index]
@@ -203,8 +218,7 @@ class BinoPan(var pan: Float, var earDist: Float = 0.18f): TerrarumAudioFilter()
private val delays = arrayOf(0f, 0f) private val delays = arrayOf(0f, 0f)
private val mults = arrayOf(1f, 1f) private val mults = arrayOf(1f, 1f)
override fun thru(inbuf0: List<FloatArray>, inbuf1: List<FloatArray>, outbuf0: List<FloatArray>, outbuf1: List<FloatArray> override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
) {
val angle = pan * 1.5707963f val angle = pan * 1.5707963f
val timeDiffMax = earDist / SPEED_OF_SOUND * SAMPLING_RATEF val timeDiffMax = earDist / SPEED_OF_SOUND * SAMPLING_RATEF
val delayInSamples = (timeDiffMax * sin(angle)).absoluteValue val delayInSamples = (timeDiffMax * sin(angle)).absoluteValue
@@ -232,20 +246,22 @@ class BinoPan(var pan: Float, var earDist: Float = 0.18f): TerrarumAudioFilter()
for (ch in 0..1) { for (ch in 0..1) {
for (i in 0 until BUFFER_SIZE / 4) { for (i in 0 until BUFFER_SIZE / 4) {
outbuf1[ch][i] = getFrom(i - delays[ch], inbuf0[0], inbuf1[0]) * mults[ch] 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() { class Bitcrush(var steps: Int, var inputGain: Float = 1f): TerrarumAudioFilter() {
override fun thru(inbuf0: List<FloatArray>, inbuf1: List<FloatArray>, outbuf0: List<FloatArray>, outbuf1: List<FloatArray>) { override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
for (ch in outbuf1.indices) { for (ch in outbuf.indices) {
for (i in 0 until BUFFER_SIZE / 4) { for (i in 0 until BUFFER_SIZE / 4) {
val inn = ((inbuf1[ch][i] * inputGain).coerceIn(-1f, 1f) + 1f) / 2f // 0f..1f val inn = ((inbuf[ch][i] * inputGain).coerceIn(-1f, 1f) + 1f) / 2f // 0f..1f
val stepped = (inn * (steps - 1)).roundToFloat() / (steps - 1) val stepped = (inn * (steps - 1)).roundToFloat() / (steps - 1)
val out = (stepped * 2f) - 1f // -1f..1f val out = (stepped * 2f) - 1f // -1f..1f
outbuf1[ch][i] = out outbuf[ch][i] = out
} }
} }
} }
@@ -269,16 +285,16 @@ class Reverb(val delayMS: Float = 36f, var feedback: Float = 0.92f, var lowpass:
private val out0 = FloatArray(2) private val out0 = FloatArray(2)
override fun thru(inbuf0: List<FloatArray>, inbuf1: List<FloatArray>, outbuf0: List<FloatArray>, outbuf1: List<FloatArray>) { override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
val RCLo: Float = 1f / (lowpass * FastMath.TWO_PI) val RCLo: Float = 1f / (lowpass * FastMath.TWO_PI)
val RCHi: Float = 1f / (highpass * FastMath.TWO_PI) val RCHi: Float = 1f / (highpass * FastMath.TWO_PI)
val dt: Float = 1f / SAMPLING_RATEF val dt: Float = 1f / SAMPLING_RATEF
val alphaLo = dt / (RCLo + dt) val alphaLo = dt / (RCLo + dt)
val alphaHi = RCHi / (RCHi + dt) val alphaHi = RCHi / (RCHi + dt)
for (ch in outbuf1.indices) { for (ch in outbuf.indices) {
for (i in 0 until BUFFER_SIZE / 4) { for (i in 0 until BUFFER_SIZE / 4) {
val inn = inbuf1[ch][i] val inn = inbuf[ch][i]
// reverb // reverb
val rev = buf[ch][delay - 1] val rev = buf[ch][delay - 1]
@@ -289,13 +305,13 @@ class Reverb(val delayMS: Float = 36f, var feedback: Float = 0.92f, var lowpass:
val lp = lp0 + alphaLo * (out - lp0) val lp = lp0 + alphaLo * (out - lp0)
unshift(lp, buf[ch]) unshift(lp, buf[ch])
outbuf1[ch][i] = out outbuf[ch][i] = out
} }
} }
} }
} }
class Convolv(ir: File, val gain: Float = decibelsToFullscale(-24.0).toFloat()): TerrarumAudioFilter() { class Convolv(ir: File, val gain: Float = 1f / 256f): TerrarumAudioFilter() {
private val fftLen: Int private val fftLen: Int
private val convFFT: Array<Array<FComplex>> private val convFFT: Array<Array<FComplex>>
@@ -303,6 +319,8 @@ class Convolv(ir: File, val gain: Float = decibelsToFullscale(-24.0).toFloat()):
private val BLOCKSIZE = BUFFER_SIZE / 4 private val BLOCKSIZE = BUFFER_SIZE / 4
var processingSpeed = 1f; private set
init { init {
if (!ir.exists()) { if (!ir.exists()) {
throw IllegalArgumentException("Impulse Response file '${ir.path}' does not exist.") throw IllegalArgumentException("Impulse Response file '${ir.path}' does not exist.")
@@ -311,7 +329,7 @@ class Convolv(ir: File, val gain: Float = decibelsToFullscale(-24.0).toFloat()):
val sampleCount = ir.length().toInt() / 8 val sampleCount = ir.length().toInt() / 8
fftLen = FastMath.nextPowerOfTwo(sampleCount) fftLen = FastMath.nextPowerOfTwo(sampleCount)
println("IR Sample Count = $sampleCount; FFT Length = $fftLen") // println("IR Sample Count = $sampleCount; FFT Length = $fftLen")
val conv = Array(2) { FloatArray(fftLen) } val conv = Array(2) { FloatArray(fftLen) }
inbuf = Array(2) { FloatArray(fftLen) } inbuf = Array(2) { FloatArray(fftLen) }
@@ -339,68 +357,77 @@ class Convolv(ir: File, val gain: Float = decibelsToFullscale(-24.0).toFloat()):
FFT.fft(conv[it]) FFT.fft(conv[it])
} }
println("convFFT Length = ${convFFT[0].size}") // println("convFFT Length = ${convFFT[0].size}")
} }
/** /**
* https://thewolfsound.com/fast-convolution-fft-based-overlap-add-overlap-save-partitioned/ * https://thewolfsound.com/fast-convolution-fft-based-overlap-add-overlap-save-partitioned/
*/ */
override fun thru(inbuf0: List<FloatArray>, inbuf1: List<FloatArray>, outbuf0: List<FloatArray>, outbuf1: List<FloatArray>) { override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
// println("Convolv thru") // println("Convolv thru")
val t1 = System.nanoTime() val t1 = System.nanoTime()
for (ch in outbuf1.indices) { for (ch in outbuf.indices) {
push(inbuf1[ch].applyGain(gain), inbuf[ch]) push(inbuf[ch].applyGain(gain), this.inbuf[ch])
val inputFFT = FFT.fft(inbuf[ch]) val inputFFT = FFT.fft(this.inbuf[ch])
val Y = multiply(inputFFT, convFFT[ch]) val Y = multiply(inputFFT, convFFT[ch])
val y = FFT.ifftAndGetReal(Y) val y = FFT.ifftAndGetReal(Y)
val u = y.takeLast(BLOCKSIZE).toFloatArray() val u = y.takeLast(BLOCKSIZE).toFloatArray()
System.arraycopy(u, 0, outbuf1[ch], 0, BLOCKSIZE) System.arraycopy(u, 0, outbuf[ch], 0, BLOCKSIZE)
} }
val t2 = System.nanoTime() val t2 = System.nanoTime()
val ptime = (t2 - t1).toDouble() val ptime = (t2 - t1).toFloat()
val realtime = BLOCKSIZE / SAMPLING_RATED * 1000000000L val realtime = BLOCKSIZE / SAMPLING_RATEF * 1000000000L
println("Processing speed: ${realtime / ptime}x") processingSpeed = realtime / ptime
} }
private fun multiply(X: Array<FComplex>, H: Array<FComplex>): Array<FComplex> { private fun multiply(X: Array<FComplex>, H: Array<FComplex>): Array<FComplex> {
return Array(X.size) { X[it] * H[it] } return Array(X.size) { X[it] * H[it] }
} }
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.applyGain(gain: Float = 1f) = this.map { it * gain }.toFloatArray()
} }
object XYtoMS: TerrarumAudioFilter() { object XYtoMS: TerrarumAudioFilter() {
override fun thru(inbuf0: List<FloatArray>, inbuf1: List<FloatArray>, outbuf0: List<FloatArray>, outbuf1: List<FloatArray>) { override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
for (i in 0 until BUFFER_SIZE / 4) { for (i in 0 until BUFFER_SIZE / 4) {
val X = inbuf1[0][i] val X = inbuf[0][i]
val Y = inbuf1[1][i] val Y = inbuf[1][i]
val M = (X + Y) / 2f val M = (X + Y) / 2f
val S = (X - Y) / 2f val S = (X - Y) / 2f
outbuf1[0][i] = M outbuf[0][i] = M
outbuf1[1][i] = S outbuf[1][i] = S
} }
} }
} }
object MStoXY: TerrarumAudioFilter() { object MStoXY: TerrarumAudioFilter() {
override fun thru(inbuf0: List<FloatArray>, inbuf1: List<FloatArray>, outbuf0: List<FloatArray>, outbuf1: List<FloatArray>) { override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
for (i in 0 until BUFFER_SIZE / 4) { for (i in 0 until BUFFER_SIZE / 4) {
val M = inbuf1[0][i] val M = inbuf[0][i]
val S = inbuf1[1][i] val S = inbuf[1][i]
val X = M + S val X = M + S
val Y = M - S val Y = M - S
outbuf1[0][i] = X outbuf[0][i] = X
outbuf1[1][i] = Y outbuf[1][i] = Y
} }
} }
} }
class Gain(val gain: Float): TerrarumAudioFilter() {
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
for (i in 0 until BUFFER_SIZE / 4) {
outbuf[0][i] = inbuf[1][i] * gain
outbuf[1][i] = inbuf[0][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

@@ -20,7 +20,7 @@ class TerrarumAudioMixerTrack(val name: String, val isMaster: Boolean = false, v
const val SAMPLING_RATE = 48000 const val SAMPLING_RATE = 48000
const val SAMPLING_RATEF = 48000f const val SAMPLING_RATEF = 48000f
const val SAMPLING_RATED = 48000.0 const val SAMPLING_RATED = 48000.0
const val BUFFER_SIZE = 512 // n ms -> 384 * n const val BUFFER_SIZE = 8192 // n ms -> 384 * n
} }
val hash = getHashStr() val hash = getHashStr()
@@ -49,6 +49,8 @@ class TerrarumAudioMixerTrack(val name: String, val isMaster: Boolean = false, v
val filters: Array<TerrarumAudioFilter> = Array(4) { NullFilter } val filters: Array<TerrarumAudioFilter> = Array(4) { NullFilter }
inline fun <reified T> getFilter() = filters.filterIsInstance<T>().first()!!
internal val sidechainInputs = Array<Pair<TerrarumAudioMixerTrack, TrackVolume>?>(16) { null } internal val sidechainInputs = Array<Pair<TerrarumAudioMixerTrack, TrackVolume>?>(16) { null }
internal fun getSidechains(): List<TerrarumAudioMixerTrack?> = sidechainInputs.map { it?.first } internal fun getSidechains(): List<TerrarumAudioMixerTrack?> = sidechainInputs.map { it?.first }
fun addSidechainInput(input: TerrarumAudioMixerTrack, inputVolume: TrackVolume) { fun addSidechainInput(input: TerrarumAudioMixerTrack, inputVolume: TrackVolume) {

View File

@@ -302,7 +302,7 @@ class BuildingMaker(batch: FlippingSpriteBatch) : IngameInstance(batch) {
override fun show() { override fun show() {
Gdx.input.inputProcessor = BuildingMakerController(this) Gdx.input.inputProcessor = BuildingMakerController(this)
(AudioMixer.fadeBus.filters[0] as Lowpass).setCutoff(TerrarumAudioMixerTrack.SAMPLING_RATEF / 2) AudioMixer.fadeBus.getFilter<Lowpass>().setCutoff(TerrarumAudioMixerTrack.SAMPLING_RATEF / 2)
super.show() super.show()
} }

View File

@@ -292,7 +292,7 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
IngameRenderer.setRenderedWorld(world) IngameRenderer.setRenderedWorld(world)
blockMarkingActor.isVisible = true blockMarkingActor.isVisible = true
(AudioMixer.fadeBus.filters[0] as Lowpass).setCutoff(SAMPLING_RATEF / 2) AudioMixer.fadeBus.getFilter<Lowpass>().setCutoff(SAMPLING_RATEF / 2)
super.show() // this function sets gameInitialised = true super.show() // this function sets gameInitialised = true

View File

@@ -270,7 +270,7 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) {
App.bogoflops = maxOf(App.bogoflops, bogoflops) App.bogoflops = maxOf(App.bogoflops, bogoflops)
(AudioMixer.fadeBus.filters[0] as Lowpass).setCutoff(TerrarumAudioMixerTrack.SAMPLING_RATEF / 2) AudioMixer.fadeBus.getFilter<Lowpass>().setCutoff(TerrarumAudioMixerTrack.SAMPLING_RATEF / 2)
} }

View File

@@ -501,7 +501,7 @@ class BasicDebugInfoWindow : UICanvas() {
// slider text // slider text
val dB = track.dBfs val dB = track.dBfs
val dBstr = dB.toIntAndFrac(3,1) val dBstr = dB.toIntAndFrac(3,1)
val dBfs = dB.coerceIn(-dbLow, 0.0).plus(dbLow).div(dbLow).toFloat() val faderKnobDbFs = dB.coerceIn(-dbLow, 0.0).plus(dbLow).div(dbLow + dbOver).toFloat()
batch.color = FILTER_NAME_ACTIVE batch.color = FILTER_NAME_ACTIVE
App.fontSmallNumbers.draw(batch, dBstr, sliderX - 23f, faderY+1f) App.fontSmallNumbers.draw(batch, dBstr, sliderX - 23f, faderY+1f)
@@ -578,7 +578,7 @@ class BasicDebugInfoWindow : UICanvas() {
Toolkit.fillArea(batch, sliderX, faderY + 16, 2, meterTroughHeight) Toolkit.fillArea(batch, sliderX, faderY + 16, 2, meterTroughHeight)
// slider handle // slider handle
drawFaderHandle(batch, sliderX.toFloat(), faderY + 18f + meterHeight - dBfs * meterHeight) drawFaderHandle(batch, sliderX.toFloat(), faderY + 18f + meterHeight - faderKnobDbFs * meterHeight)
// currently streaming // currently streaming
if (track.streamPlaying) { if (track.streamPlaying) {
@@ -600,6 +600,8 @@ class BasicDebugInfoWindow : UICanvas() {
"Lowpass" to 16, "Lowpass" to 16,
"Buffer" to 16, "Buffer" to 16,
"BinoPan" to 32, "BinoPan" to 32,
"Convolv" to 16,
"Gain" to 16,
"Scope" to stripW, "Scope" to stripW,
) )
@@ -637,6 +639,14 @@ class BasicDebugInfoWindow : UICanvas() {
batch.color = FILTER_NAME_ACTIVE batch.color = FILTER_NAME_ACTIVE
App.fontSmallNumbers.draw(batch, "Bs:${BUFFER_SIZE/4}", x+3f, y+1f) App.fontSmallNumbers.draw(batch, "Bs:${BUFFER_SIZE/4}", x+3f, y+1f)
} }
is Convolv -> {
batch.color = FILTER_NAME_ACTIVE
App.fontSmallNumbers.draw(batch, "P:${filter.processingSpeed.times(100).roundToInt().div(100f)}x", x+3f, y+1f)
}
is Gain -> {
batch.color = FILTER_NAME_ACTIVE
App.fontSmallNumbers.draw(batch, "G:${fullscaleToDecibels(filter.gain.toDouble()).times(100).roundToInt().div(100f)}", x+3f, y+1f)
}
is Scope -> { is Scope -> {
// batch.color = COL_FILTER_WELL_BACK // batch.color = COL_FILTER_WELL_BACK
// Toolkit.fillArea(batch, 200, 200, 256, 256) // Toolkit.fillArea(batch, 200, 200, 256, 256)