mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-06-11 19:14:05 +09:00
more special purpose filters (audio samples will be added later)
This commit is contained in:
@@ -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<Pair<Int, Int>, Int>()
|
private val bufLut = HashMap<Pair<Int, Int>, Int>()
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
package net.torvald.terrarum.audio.dsp
|
package net.torvald.terrarum.audio.dsp
|
||||||
|
|
||||||
|
import com.badlogic.gdx.Gdx
|
||||||
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
||||||
|
import net.torvald.reflection.forceInvoke
|
||||||
import net.torvald.terrarum.App
|
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 java.io.File
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
import kotlin.math.tanh
|
import kotlin.math.tanh
|
||||||
@@ -15,25 +19,73 @@ import kotlin.math.tanh
|
|||||||
*
|
*
|
||||||
* Created by minjaesong on 2024-01-21.
|
* 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)
|
override val downForce = arrayOf(1.0f, 1.0f)
|
||||||
|
|
||||||
|
internal val staticSample: List<FloatArray>
|
||||||
|
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<Int>("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)
|
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<FloatArray>, outbuf: List<FloatArray>) {
|
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
|
||||||
convolver.thru(inbuf, imm)
|
staticMixThru(inbuf, immAfterStaticMix)
|
||||||
|
convolver.thru(immAfterStaticMix, immAfterConvolv)
|
||||||
|
saturatorThru(immAfterConvolv, outbuf)
|
||||||
|
}
|
||||||
|
|
||||||
for (ch in imm.indices) {
|
private fun staticMixThru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
|
||||||
val inn = imm[ch]
|
for (h in 0 until App.audioBufferSize) {
|
||||||
val out = outbuf[ch]
|
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) {
|
private fun saturatorThru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
|
||||||
val u = inn[i]
|
for (ch in inbuf.indices) {
|
||||||
val v = tanh(u)
|
for (i in inbuf[ch].indices) {
|
||||||
|
val u = inbuf[ch][i]
|
||||||
|
val v = saturate(u)
|
||||||
val diff = (v.absoluteValue / u.absoluteValue)
|
val diff = (v.absoluteValue / u.absoluteValue)
|
||||||
out[i] = v
|
outbuf[ch][i] = v
|
||||||
|
|
||||||
if (!diff.isNaN()) {
|
if (!diff.isNaN()) {
|
||||||
downForce[ch] = minOf(downForce[ch], diff)
|
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) {
|
override fun drawDebugView(batch: SpriteBatch, x: Int, y: Int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
36
src/net/torvald/terrarum/audio/dsp/Phono.kt
Normal file
36
src/net/torvald/terrarum/audio/dsp/Phono.kt
Normal file
@@ -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
|
||||||
|
)
|
||||||
@@ -6,6 +6,11 @@ import kotlin.math.pow
|
|||||||
import kotlin.math.sqrt
|
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.
|
* Created by minjaesong on 2023-11-20.
|
||||||
*/
|
*/
|
||||||
object SoftClp : TerrarumAudioFilter(), DspCompressor {
|
object SoftClp : TerrarumAudioFilter(), DspCompressor {
|
||||||
|
|||||||
@@ -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.Convolv
|
||||||
import net.torvald.terrarum.audio.dsp.LoFi
|
import net.torvald.terrarum.audio.dsp.LoFi
|
||||||
import net.torvald.terrarum.audio.dsp.NullFilter
|
import net.torvald.terrarum.audio.dsp.NullFilter
|
||||||
|
import net.torvald.terrarum.audio.dsp.Phono
|
||||||
import net.torvald.terrarum.gameactors.AVKey
|
import net.torvald.terrarum.gameactors.AVKey
|
||||||
import net.torvald.terrarum.gameitems.ItemID
|
import net.torvald.terrarum.gameitems.ItemID
|
||||||
import net.torvald.terrarum.langpack.Lang
|
import net.torvald.terrarum.langpack.Lang
|
||||||
@@ -117,7 +118,7 @@ class FixtureJukebox : Electric, PlaysMusic {
|
|||||||
|
|
||||||
App.audioMixer.requestFadeOut(App.audioMixer.musicTrack, DEFAULT_FADEOUT_LEN / 2f) {
|
App.audioMixer.requestFadeOut(App.audioMixer.musicTrack, DEFAULT_FADEOUT_LEN / 2f) {
|
||||||
startAudio(musicNowPlaying!!) {
|
startAudio(musicNowPlaying!!) {
|
||||||
it.filters[filterIndex] = LoFi(
|
it.filters[filterIndex] = Phono(
|
||||||
ModMgr.getFile(
|
ModMgr.getFile(
|
||||||
"basegame",
|
"basegame",
|
||||||
"audio/convolution/Soundwoofer - large_speaker_Marshall JVM 205C SM57 A 0 0 1.bin"
|
"audio/convolution/Soundwoofer - large_speaker_Marshall JVM 205C SM57 A 0 0 1.bin"
|
||||||
|
|||||||
Reference in New Issue
Block a user