softlimiter filter for master

This commit is contained in:
minjaesong
2023-11-20 22:02:57 +09:00
parent 6fabe555df
commit 1a49921c77
6 changed files with 53 additions and 40 deletions

View File

@@ -5,13 +5,8 @@ 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.audio.MixerTrackProcessor.Companion.BACK_BUF_COUNT
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.BUFFER_SIZE
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.INDEX_AMB
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.INDEX_BGM
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.TerrarumAudioMixerTrack.Companion.SAMPLING_RATEF
import net.torvald.terrarum.modulebasegame.MusicContainer import net.torvald.terrarum.modulebasegame.MusicContainer
import net.torvald.terrarum.tryDispose import net.torvald.terrarum.tryDispose
import java.lang.Thread.MAX_PRIORITY import java.lang.Thread.MAX_PRIORITY
@@ -100,7 +95,8 @@ object AudioMixer: Disposable {
init { init {
masterTrack.filters[0] = Buffer masterTrack.filters[0] = SoftLim
masterTrack.filters[1] = Buffer
fadeBus.addSidechainInput(musicTrack, 1.0) fadeBus.addSidechainInput(musicTrack, 1.0)
fadeBus.addSidechainInput(ambientTrack, 1.0) fadeBus.addSidechainInput(ambientTrack, 1.0)
@@ -165,10 +161,13 @@ object AudioMixer: Disposable {
if (req.fadeAkku >= req.fadeLength) { if (req.fadeAkku >= req.fadeLength) {
req.fadeoutFired = false req.fadeoutFired = false
track.volume = req.fadeTarget track.volume = req.fadeTarget
track.volume = req.fadeTarget
if (req.fadeTarget == 0.0) { // stop streaming if fadeBus is muted
track.currentTrack = null if (req.fadeTarget == 0.0 && track == fadeBus) {
musicTrack.currentTrack = null
musicTrack.streamPlaying = false
ambientTrack.currentTrack = null
ambientTrack.streamPlaying = false
} }
} }
} }
@@ -216,12 +215,17 @@ object AudioMixer: Disposable {
} }
if (musicTrack.isPlaying != true && musicTrack.nextTrack != null) { if (!musicTrack.isPlaying && musicTrack.nextTrack != null) {
// printdbg(this, "Playing next music: ${nextMusic!!.name}")
musicTrack.queueNext(null) musicTrack.queueNext(null)
fadeBus.volume = 1.0 fadeBus.volume = 1.0
musicTrack.play() musicTrack.play()
} }
if (!ambientTrack.isPlaying && ambientTrack.nextTrack != null) {
ambientTrack.queueNext(null)
requestFadeIn(ambientTrack, DEFAULT_FADEOUT_LEN * 4, 1.0, 0.00001)
ambientTrack.play()
}
} }
fun startMusic(song: MusicContainer) { fun startMusic(song: MusicContainer) {
@@ -240,31 +244,32 @@ object AudioMixer: Disposable {
requestFadeOut(musicTrack, DEFAULT_FADEOUT_LEN) requestFadeOut(musicTrack, DEFAULT_FADEOUT_LEN)
} }
ambientTrack.nextTrack = song ambientTrack.nextTrack = song
// fade will be processed by the update()
} }
fun stopAmb() { fun stopAmb() {
requestFadeOut(musicTrack, DEFAULT_FADEOUT_LEN) requestFadeOut(ambientTrack, DEFAULT_FADEOUT_LEN * 4)
} }
fun requestFadeOut(track: TerrarumAudioMixerTrack, length: Double, target: Double = 0.0) { fun requestFadeOut(track: TerrarumAudioMixerTrack, length: Double, target: Double = 0.0, source: Double? = null) {
val req = fadeReqs[track]!! val req = fadeReqs[track]!!
if (!req.fadeoutFired) { if (!req.fadeoutFired) {
req.fadeLength = length.coerceAtLeast(1.0/1024.0) req.fadeLength = length.coerceAtLeast(1.0/1024.0)
req.fadeAkku = 0.0 req.fadeAkku = 0.0
req.fadeoutFired = true req.fadeoutFired = true
req.fadeTarget = target * track.maxVolume req.fadeTarget = target * track.maxVolume
req.fadeStart = fadeBus.volume req.fadeStart = source ?: fadeBus.volume
} }
} }
fun requestFadeIn(track: TerrarumAudioMixerTrack, length: Double, target: Double = 1.0) { fun requestFadeIn(track: TerrarumAudioMixerTrack, length: Double, target: Double = 1.0, source: Double? = null) {
val req = fadeReqs[track]!! val req = fadeReqs[track]!!
if (!req.fadeinFired) { if (!req.fadeinFired) {
req.fadeLength = length.coerceAtLeast(1.0/1024.0) req.fadeLength = length.coerceAtLeast(1.0/1024.0)
req.fadeAkku = 0.0 req.fadeAkku = 0.0
req.fadeinFired = true req.fadeinFired = true
req.fadeTarget = target * track.maxVolume req.fadeTarget = target * track.maxVolume
req.fadeStart = fadeBus.volume req.fadeStart = source ?: fadeBus.volume
} }
} }

View File

@@ -2,7 +2,6 @@ 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 org.apache.commons.math3.special.Erf.erf
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
import kotlin.math.tanh import kotlin.math.tanh
@@ -105,20 +104,6 @@ class MixerTrackProcessor(val bufferSize: Int, val rate: Int, val track: Terraru
samplesR1!![i] += side.processor.fout1[1][i] * (mix * track.volume).toFloat() samplesR1!![i] += side.processor.fout1[1][i] * (mix * track.volume).toFloat()
} }
} }
// de-clip using sigmoid function
for (i in samplesL0.indices) {
samplesL0[i] = erf(samplesL0[i].toDouble()).toFloat()
samplesR0[i] = erf(samplesR0[i].toDouble()).toFloat()
samplesL1[i] = erf(samplesL1[i].toDouble()).toFloat()
samplesR1[i] = erf(samplesR1[i].toDouble()).toFloat()
}
/*track.sidechainInputs[TerrarumAudioMixerTrack.INDEX_BGM]?.let { (side, mix) ->
samplesL0 = side.processor.fout0[0].applyVolume((mix * track.volume).toFloat()) // must not applyVolumeInline
samplesR0 = side.processor.fout0[1].applyVolume((mix * track.volume).toFloat())
samplesL1 = side.processor.fout1[0].applyVolume((mix * track.volume).toFloat())
samplesR1 = side.processor.fout1[1].applyVolume((mix * track.volume).toFloat())
}*/
} }
// source channel: skip processing if there's no active input // source channel: skip processing if there's no active input
// else if (track.getSidechains().any { it != null && !it.isBus && !it.isMaster && !it.streamPlaying } && !track.streamPlaying) { // else if (track.getSidechains().any { it != null && !it.isBus && !it.isMaster && !it.streamPlaying } && !track.streamPlaying) {

View File

@@ -1,6 +1,7 @@
package net.torvald.terrarum.audio package net.torvald.terrarum.audio
import com.jme3.math.FastMath import com.jme3.math.FastMath
import kotlin.math.tanh
abstract class TerrarumAudioFilter { abstract class TerrarumAudioFilter {
var bypass = false var bypass = false
@@ -23,6 +24,19 @@ object NullFilter : TerrarumAudioFilter() {
} }
} }
object SoftLim : TerrarumAudioFilter() {
override fun thru(inbuf0: List<FloatArray>, inbuf1: List<FloatArray>, outbuf0: List<FloatArray>, outbuf1: List<FloatArray>) {
for (ch in inbuf1.indices) {
val inn = inbuf1[ch]
val out = outbuf1[ch]
for (i in inn.indices) {
out[i] = tanh(inn[i])
}
}
}
}
class Lowpass(cutoff0: Float, val rate: Int): TerrarumAudioFilter() { class Lowpass(cutoff0: Float, val rate: Int): TerrarumAudioFilter() {

View File

@@ -115,8 +115,8 @@ class TerrarumAudioMixerTrack(val name: String, val isMaster: Boolean = false, v
// currentTrack?.gdxMusic?.play() // currentTrack?.gdxMusic?.play()
} }
val isPlaying: Boolean? val isPlaying: Boolean
get() = currentTrack?.gdxMusic?.isPlaying get() = streamPlaying//currentTrack?.gdxMusic?.isPlaying
override fun dispose() { override fun dispose() {
/*if (isMaster) { // uncomment to multithread /*if (isMaster) { // uncomment to multithread

View File

@@ -50,7 +50,7 @@ class TerrarumMusicGovernor : MusicGovernor() {
it.nameWithoutExtension.replace('_', ' ').split(" ").map { it.capitalize() }.joinToString(" "), it.nameWithoutExtension.replace('_', ' ').split(" ").map { it.capitalize() }.joinToString(" "),
it, it,
Gdx.audio.newMusic(Gdx.files.absolute(it.absolutePath)) Gdx.audio.newMusic(Gdx.files.absolute(it.absolutePath))
) { stopMusic() } ) { stopAmbient() }
} }
catch (e: GdxRuntimeException) { catch (e: GdxRuntimeException) {
e.printStackTrace() e.printStackTrace()
@@ -123,6 +123,7 @@ class TerrarumMusicGovernor : MusicGovernor() {
// val ingame = ingame as TerrarumIngame // val ingame = ingame as TerrarumIngame
if (musicState == 0) musicState = STATE_INTERMISSION if (musicState == 0) musicState = STATE_INTERMISSION
if (ambState == 0) ambState = STATE_INTERMISSION
when (musicState) { when (musicState) {

View File

@@ -375,9 +375,9 @@ class BasicDebugInfoWindow : UICanvas() {
private val stripW = 56 private val stripW = 56
private val stripGap = 1 private val stripGap = 1
private val stripFilterHeight = 32 private val stripFilterHeight = 16
private val stripFaderHeight = 200 private val stripFaderHeight = 200
private val numberOfFilters = 5 private val numberOfFilters = 10
private val stripH = stripFaderHeight + stripFilterHeight * numberOfFilters + 16 private val stripH = stripFaderHeight + stripFilterHeight * numberOfFilters + 16
private val COL_WELL = Color(0x374854_aa) private val COL_WELL = Color(0x374854_aa)
@@ -443,17 +443,20 @@ class BasicDebugInfoWindow : UICanvas() {
batch.color = COL_FILTER_WELL_BACK batch.color = COL_FILTER_WELL_BACK
Toolkit.fillArea(batch, x, y, stripW, stripFilterHeight * numberOfFilters) Toolkit.fillArea(batch, x, y, stripW, stripFilterHeight * numberOfFilters)
var filterBankYcursor = 0
track.filters.forEachIndexed { i, filter -> if (filter !is NullFilter) { track.filters.forEachIndexed { i, filter -> if (filter !is NullFilter) {
// draw filter title back // draw filter title back
batch.color = COL_FILTER_TITLE_SHADE batch.color = COL_FILTER_TITLE_SHADE
Toolkit.fillArea(batch, x, y + stripFilterHeight * i, stripW, 16) Toolkit.fillArea(batch, x, y + filterBankYcursor, stripW, 16)
batch.color = COL_FILTER_TITLE batch.color = COL_FILTER_TITLE
Toolkit.fillArea(batch, x, y + stripFilterHeight * i, stripW, 14) Toolkit.fillArea(batch, x, y + filterBankYcursor, stripW, 14)
// draw filter name // draw filter name
batch.color = if (filter.bypass) FILTER_BYPASSED else FILTER_NAME_ACTIVE batch.color = if (filter.bypass) FILTER_BYPASSED else FILTER_NAME_ACTIVE
App.fontSmallNumbers.draw(batch, filter.javaClass.simpleName, x + 3f, y + stripFilterHeight * i + 1f) App.fontSmallNumbers.draw(batch, filter.javaClass.simpleName, x + 3f, y + filterBankYcursor + 1f)
drawFilterParam(batch, x, y + stripFilterHeight * i + 16, filter, track) drawFilterParam(batch, x, y + filterBankYcursor + stripFilterHeight, filter, track)
filterBankYcursor += stripFilterHeight + paramViewHeight.getOrDefault(filter.javaClass.simpleName, 0)
} } } }
val faderY = y + stripFilterHeight * numberOfFilters val faderY = y + stripFilterHeight * numberOfFilters
@@ -537,6 +540,11 @@ class BasicDebugInfoWindow : UICanvas() {
} }
} }
private val paramViewHeight = hashMapOf(
"Lowpass" to 16,
"Buffer" to 32,
)
private fun drawFilterParam(batch: SpriteBatch, x: Int, y: Int, filter: TerrarumAudioFilter, track: TerrarumAudioMixerTrack) { private fun drawFilterParam(batch: SpriteBatch, x: Int, y: Int, filter: TerrarumAudioFilter, track: TerrarumAudioMixerTrack) {
when (filter) { when (filter) {
is Lowpass -> { is Lowpass -> {