From 76f7b2a145619274e26e4c13043e06a7b78f5bb4 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Wed, 24 Jan 2024 15:05:19 +0900 Subject: [PATCH] more special purpose filters (audio samples will be added later) --- .../torvald/terrarum/audio/AudioProcessBuf.kt | 2 +- src/net/torvald/terrarum/audio/dsp/LoFi.kt | 80 ++++++++++++++++--- src/net/torvald/terrarum/audio/dsp/Phono.kt | 36 +++++++++ src/net/torvald/terrarum/audio/dsp/SoftClp.kt | 5 ++ .../gameactors/FixtureJukebox.kt | 3 +- 5 files changed, 114 insertions(+), 12 deletions(-) create mode 100644 src/net/torvald/terrarum/audio/dsp/Phono.kt diff --git a/src/net/torvald/terrarum/audio/AudioProcessBuf.kt b/src/net/torvald/terrarum/audio/AudioProcessBuf.kt index dc0dac5ea..b4daf6d8d 100644 --- a/src/net/torvald/terrarum/audio/AudioProcessBuf.kt +++ b/src/net/torvald/terrarum/audio/AudioProcessBuf.kt @@ -54,7 +54,7 @@ class AudioProcessBuf(val inputSamplingRate: Int, val audioReadFun: (ByteArray) } } - private val MP3_CHUNK_SIZE = 1152 // 1152 for 32k-48k, 576 for 16k-24k, 384 for 8k-12k + const val MP3_CHUNK_SIZE = 1152 // 1152 for 32k-48k, 576 for 16k-24k, 384 for 8k-12k private val bufLut = HashMap, Int>() diff --git a/src/net/torvald/terrarum/audio/dsp/LoFi.kt b/src/net/torvald/terrarum/audio/dsp/LoFi.kt index e63425db8..92637f09a 100644 --- a/src/net/torvald/terrarum/audio/dsp/LoFi.kt +++ b/src/net/torvald/terrarum/audio/dsp/LoFi.kt @@ -1,7 +1,11 @@ package net.torvald.terrarum.audio.dsp +import com.badlogic.gdx.Gdx import com.badlogic.gdx.graphics.g2d.SpriteBatch +import net.torvald.reflection.forceInvoke import net.torvald.terrarum.App +import net.torvald.terrarum.audio.AudioProcessBuf.Companion.MP3_CHUNK_SIZE +import net.torvald.terrarum.serialise.toUint import java.io.File import kotlin.math.absoluteValue import kotlin.math.tanh @@ -15,25 +19,73 @@ import kotlin.math.tanh * * Created by minjaesong on 2024-01-21. */ -class LoFi(ir: File, val crossfeed: Float, gain: Float = 1f / 256f): TerrarumAudioFilter(), DspCompressor { +open class LoFi(static: File, ir: File, val crossfeed: Float, gain: Float = 1f / 256f): TerrarumAudioFilter(), DspCompressor { override val downForce = arrayOf(1.0f, 1.0f) + internal val staticSample: List + private var staticSamplePlayCursor = 0 + + init { + val music = Gdx.audio.newMusic(Gdx.files.absolute(static.absolutePath)) + val readbuf = ByteArray(MP3_CHUNK_SIZE * 4) + val OUTBUF_BLOCK_SIZE_IN_BYTES = (48000 * 60) * 2 * 2 + var outbuf = ByteArray(OUTBUF_BLOCK_SIZE_IN_BYTES) + var bytesRead = 0 + + fun expandOutbuf() { + val newOutBuf = ByteArray(outbuf.size + OUTBUF_BLOCK_SIZE_IN_BYTES) + System.arraycopy(outbuf, 0, newOutBuf, 0, outbuf.size) + outbuf = newOutBuf + } + + while (true) { + val readSize = music.forceInvoke("read", arrayOf(readbuf))!! + if (readSize <= 0) break + + // check if outbuf has room + if (bytesRead + readSize > outbuf.size) expandOutbuf() + + // actually copy the bytes + System.arraycopy(readbuf, 0, outbuf, bytesRead, readSize) + + bytesRead += readSize + } + + // convert bytes to float samples + staticSample = listOf(FloatArray(bytesRead / 4), FloatArray(bytesRead / 4)) + for (i in staticSample[0].indices) { + staticSample[0][i] = (outbuf[4*i+0].toUint() or outbuf[4*i+1].toUint().shl(8)).toShort() / 32767f + staticSample[1][i] = (outbuf[4*i+2].toUint() or outbuf[4*i+3].toUint().shl(8)).toShort() / 32767f + } + + } + internal val convolver = Convolv(ir, crossfeed, gain) - private val imm = listOf(FloatArray(App.audioBufferSize), FloatArray(App.audioBufferSize)) + private val immAfterStaticMix = listOf(FloatArray(App.audioBufferSize), FloatArray(App.audioBufferSize)) + private val immAfterConvolv = listOf(FloatArray(App.audioBufferSize), FloatArray(App.audioBufferSize)) override fun thru(inbuf: List, outbuf: List) { - convolver.thru(inbuf, imm) + staticMixThru(inbuf, immAfterStaticMix) + convolver.thru(immAfterStaticMix, immAfterConvolv) + saturatorThru(immAfterConvolv, outbuf) + } - for (ch in imm.indices) { - val inn = imm[ch] - val out = outbuf[ch] + private fun staticMixThru(inbuf: List, outbuf: List) { + for (h in 0 until App.audioBufferSize) { + outbuf[0][h] = inbuf[0][h] + staticSample[0][staticSamplePlayCursor] + outbuf[1][h] = inbuf[1][h] + staticSample[1][staticSamplePlayCursor] + staticSamplePlayCursor = (staticSamplePlayCursor + 1) % staticSample[0].size + } + } - for (i in inn.indices) { - val u = inn[i] - val v = tanh(u) + private fun saturatorThru(inbuf: List, outbuf: List) { + for (ch in inbuf.indices) { + for (i in inbuf[ch].indices) { + val u = inbuf[ch][i] + val v = saturate(u) val diff = (v.absoluteValue / u.absoluteValue) - out[i] = v + outbuf[ch][i] = v if (!diff.isNaN()) { downForce[ch] = minOf(downForce[ch], diff) @@ -42,6 +94,14 @@ class LoFi(ir: File, val crossfeed: Float, gain: Float = 1f / 256f): TerrarumAud } } + /** + * Saturation function aka Voltage Transfer Characteristic Curve. + * Default function is `tanh(x)` + */ + open fun saturate(v: Float): Float { + return tanh(v) + } + override fun drawDebugView(batch: SpriteBatch, x: Int, y: Int) { } diff --git a/src/net/torvald/terrarum/audio/dsp/Phono.kt b/src/net/torvald/terrarum/audio/dsp/Phono.kt new file mode 100644 index 000000000..c2aa4a938 --- /dev/null +++ b/src/net/torvald/terrarum/audio/dsp/Phono.kt @@ -0,0 +1,36 @@ +package net.torvald.terrarum.audio.dsp + +import net.torvald.terrarum.ModMgr +import java.io.File + +/** + * Crackle and pops of the phonographs. + * + * Created by minjaesong on 2024-01-24. + */ +class Phono(ir: File, crossfeed: Float, gain: Float) : LoFi( + ModMgr.getFile("basegame", "audio/effects/static/phono_pops.ogg"), + ir, crossfeed, gain +) + +/** + * Hiss of the magnetic tape. + * + * Created by minjaesong on 2024-01-24. + */ +class Tape(ir: File, crossfeed: Float, gain: Float) : LoFi( + ModMgr.getFile("basegame", "audio/effects/static/tape_hiss.ogg"), + ir, crossfeed, gain +) + +/** + * Static noise of the fictional Holotape, based on RCA Holotape and not Fallout Holotape. + * + * You can argue "high-tech storage medium like Holotape should hold digital audio" but where's the fun in that? + * + * Created by minjaesong on 2024-01-24. + */ +class Holo(ir: File, crossfeed: Float, gain: Float) : LoFi( + ModMgr.getFile("basegame", "audio/effects/static/film_pops.ogg"), + ir, crossfeed, gain +) \ No newline at end of file diff --git a/src/net/torvald/terrarum/audio/dsp/SoftClp.kt b/src/net/torvald/terrarum/audio/dsp/SoftClp.kt index 2676d5ac6..f1d42ee57 100644 --- a/src/net/torvald/terrarum/audio/dsp/SoftClp.kt +++ b/src/net/torvald/terrarum/audio/dsp/SoftClp.kt @@ -6,6 +6,11 @@ import kotlin.math.pow import kotlin.math.sqrt /** + * This filter compresses the input signal using a fixed artificial Voltage Transfer Characteristic curve, + * where the curve is linear on the intensity lower than -3dB, then up to +3.5dB the curve flattens + * (compression ratio steadily reaches infinity) gradually, using simple quadratic curve, then above + * +3.5dB the curve is completely flat and the compression ratio is infinity. + * * Created by minjaesong on 2023-11-20. */ object SoftClp : TerrarumAudioFilter(), DspCompressor { diff --git a/src/net/torvald/terrarum/modulebasegame/gameactors/FixtureJukebox.kt b/src/net/torvald/terrarum/modulebasegame/gameactors/FixtureJukebox.kt index ffe8c5b94..32e295703 100644 --- a/src/net/torvald/terrarum/modulebasegame/gameactors/FixtureJukebox.kt +++ b/src/net/torvald/terrarum/modulebasegame/gameactors/FixtureJukebox.kt @@ -13,6 +13,7 @@ import net.torvald.terrarum.audio.AudioMixer.Companion.DEFAULT_FADEOUT_LEN import net.torvald.terrarum.audio.dsp.Convolv import net.torvald.terrarum.audio.dsp.LoFi import net.torvald.terrarum.audio.dsp.NullFilter +import net.torvald.terrarum.audio.dsp.Phono import net.torvald.terrarum.gameactors.AVKey import net.torvald.terrarum.gameitems.ItemID import net.torvald.terrarum.langpack.Lang @@ -117,7 +118,7 @@ class FixtureJukebox : Electric, PlaysMusic { App.audioMixer.requestFadeOut(App.audioMixer.musicTrack, DEFAULT_FADEOUT_LEN / 2f) { startAudio(musicNowPlaying!!) { - it.filters[filterIndex] = LoFi( + it.filters[filterIndex] = Phono( ModMgr.getFile( "basegame", "audio/convolution/Soundwoofer - large_speaker_Marshall JVM 205C SM57 A 0 0 1.bin"