mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-06-14 20:44:05 +09:00
audio: no longer holds prevsamples globally
This commit is contained in:
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|
||||||
|
|
||||||
|
out[0] = (out0[ch].div(16f) + alpha * (inn[0].div(16f) - out0[ch].div(16f))).times(16f)
|
||||||
|
|
||||||
for (i in 1 until outbuf1[ch].size) {
|
for (i in 1 until outbuf[ch].size) {
|
||||||
out[i] = out[i-1] + alpha * (inn[i] - out[i-1])
|
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)
|
||||||
|
}
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user