diff --git a/src/net/torvald/terrarum/DefaultConfig.kt b/src/net/torvald/terrarum/DefaultConfig.kt index 791a5ab52..3c19611dd 100644 --- a/src/net/torvald/terrarum/DefaultConfig.kt +++ b/src/net/torvald/terrarum/DefaultConfig.kt @@ -26,6 +26,7 @@ object DefaultConfig { "audio_buffer_size" to 512, "audio_dynamic_source_max" to 128, "audio_speaker_setup" to "headphone", // "headphone" or "stereo" + "audio_dsp_compressor_ratio" to "none", "language" to App.getSysLang(), "notificationshowuptime" to 4000, // 4s diff --git a/src/net/torvald/terrarum/audio/AudioMixer.kt b/src/net/torvald/terrarum/audio/AudioMixer.kt index 6b057344f..d35a3bb1e 100644 --- a/src/net/torvald/terrarum/audio/AudioMixer.kt +++ b/src/net/torvald/terrarum/audio/AudioMixer.kt @@ -259,8 +259,9 @@ class AudioMixer : Disposable { } masterTrack.filters[0] = SoftClp - masterTrack.filters[1] = Buffer -// masterTrack.filters[1] = Comp(-24f, 5f, 0.5f) + masterTrack.filters[1] = Comp(-36f, 1f, 12f).also { + it.bypass = true + } masterTrack.filters[2] = Vecto(1.4142f) masterTrack.filters[3] = Spectro() @@ -298,6 +299,7 @@ class AudioMixer : Disposable { fadeBus.addSidechainInput(convolveBusCave, 2.0 / 3.0) fadeBus.filters[0] = Lowpass(SAMPLING_RATE / 2f) + masterTrack.addSidechainInput(fadeBus, 1.0) masterTrack.addSidechainInput(guiTrack, 1.0) diff --git a/src/net/torvald/terrarum/audio/dsp/Comp.kt b/src/net/torvald/terrarum/audio/dsp/Comp.kt index 369fee742..292c89aea 100644 --- a/src/net/torvald/terrarum/audio/dsp/Comp.kt +++ b/src/net/torvald/terrarum/audio/dsp/Comp.kt @@ -5,6 +5,7 @@ import com.jme3.math.FastMath import net.torvald.terrarum.App import net.torvald.terrarum.audio.decibelsToFullscale import net.torvald.terrarum.audio.fullscaleToDecibels +import net.torvald.terrarum.sqr import net.torvald.terrarum.ui.BasicDebugInfoWindow.Companion.COL_METER_GRAD2_YELLOW import net.torvald.terrarum.ui.BasicDebugInfoWindow.Companion.COL_METER_GRAD_YELLOW import net.torvald.terrarum.ui.BasicDebugInfoWindow.Companion.FILTER_NAME_ACTIVE @@ -12,57 +13,61 @@ import net.torvald.terrarum.ui.BasicDebugInfoWindow.Companion.STRIP_W import net.torvald.terrarum.ui.Toolkit import kotlin.math.absoluteValue import kotlin.math.roundToInt +import kotlin.math.sqrt /** * Created by minjaesong on 2024-12-24. */ class Comp( - val thresholdDB: Float, - val ratio: Float, + thresholdDB: Float, + ratio0: Float, /** Knee is defined as the multiplier of the threshold. For example: if threshold is -12dB and kneeRatio is 0.5, the knee will be -24dB to -6dB */ - val kneeRaio: Float + kneeDB: Float ): TerrarumAudioFilter(), DspCompressor { - private val thresholdLinear = decibelsToFullscale(thresholdDB.toDouble()) - private val kneeLinear = thresholdLinear * kneeRaio//decibelsToFullscale(kneeDB.toDouble()) - private val makeupGain = (1.0 - 1.0 / ratio) * (-thresholdDB) - private val makeupGainLinear = decibelsToFullscale(makeupGain) + var threshold = thresholdDB + var knee = kneeDB + var ratio = ratio0 + set(value) { + field = value + r = value.toDouble() + makeupGain = (1.0 - 1.0 / ratio) * (-threshold) * 0.5 + makeupGainLinear = decibelsToFullscale(makeupGain) + + if (ratio <= 1.0) + bypass = true + else + bypass = false + } + + private val t = threshold.toDouble() + private var r = ratio.toDouble() + private val k = knee.toDouble() + + private var makeupGain = (1.0 - 1.0 / ratio) * (-threshold) * 0.5 + private var makeupGainLinear = decibelsToFullscale(makeupGain) private var internalGainKnob = 1.0 private var internalGainKnobWithoutGain = 1.0 private var outputGainKnob = 1.0 private var outputGainKnobWithoutGain = 1.0 // used for debug view only - private val gainKnobWeight = 0.93 // outputGainKnob is updated per sample (48000 times per second) + private val gainKnobWeight = 0.99 // outputGainKnob is updated per sample (48000 times per second) - private fun calcGainMinusM(sampleL: Float, sampleR: Float, thresholdLinear: Double, ratio: Float, kneeLinear: Double): Double { - // https://www.desmos.com/calculator/p3wufeoioi + private fun calcGainMinusM(sampleL: Float, sampleR: Float, t: Double, r: Double, k: Double): Double { + // https://www.desmos.com/calculator/o3uwcy3bzj - val x = maxOf(sampleL.absoluteValue, sampleR.absoluteValue).toDouble() - val t = thresholdLinear - val k = kneeLinear - val r = ratio.toDouble() -// val M = (r-1) / r + val x = fullscaleToDecibels(maxOf(sampleL.absoluteValue, sampleR.absoluteValue).toDouble()) val rx = r*x val rt = r*t - val rtt = r*t*t - val kk = k*k - val kkr = k*k*r val krt = k*r*t - val kr = k*r - val kt = k*t - val kx = k*x - val tt = t*t - val tx = t*x - val xx = x*x val halfk = k/2 val fx = 1.0 - val gx = (rx-r+t-x)/(rt-r) - val K = (kkr-kk+4*krt-8*kr+4*kt+4*rtt-4*tt)/(8*krt-8*kr) - val hx = ((r-1)*(kx-2*tx+xx))/(2*krt-2*kr)+K - + val gx = (rx+t-x)/(rt) + val K = (((r-1)*(k-2*t).sqr())/(8*krt))+1 + val hx = (((r-1)*x*(k-2*t+x))/(2*krt))+K return if (x < t-halfk) fx @@ -71,7 +76,7 @@ class Comp( else if (t-halfk <= x && x <= t+halfk) hx else - (1/r) + 1/r } @@ -93,13 +98,13 @@ class Comp( val sampleL = innL[i] val sampleR = innR[i] - internalGainKnobWithoutGain = calcGainMinusM(sampleL, sampleR, thresholdLinear, ratio, kneeLinear) + internalGainKnobWithoutGain = calcGainMinusM(sampleL, sampleR, t, r, k) internalGainKnob = internalGainKnobWithoutGain * makeupGainLinear outputGainKnobWithoutGain = FastMath.interpolateLinear(gainKnobWeight, internalGainKnobWithoutGain, outputGainKnobWithoutGain) outputGainKnob = FastMath.interpolateLinear(gainKnobWeight, internalGainKnob, outputGainKnob) - outL[i] = (sampleL * internalGainKnob).toFloat() - outR[i] = (sampleR * internalGainKnob).toFloat() + outL[i] = (sampleL * outputGainKnob).toFloat() + outR[i] = (sampleR * outputGainKnob).toFloat() // calculate the downforce maxReduction = minOf(maxReduction, outputGainKnobWithoutGain) @@ -110,21 +115,27 @@ class Comp( } override fun drawDebugView(batch: SpriteBatch, x: Int, y: Int) { - val reductionDB = fullscaleToDecibels(maxReduction) - val perc = (reductionDB / (-0.5*makeupGain)).toFloat().coerceIn(0f, 1f) - batch.color = COL_METER_GRAD2_YELLOW - Toolkit.fillArea(batch, x.toFloat() + STRIP_W, y.toFloat(), -STRIP_W * perc, 14f) - batch.color = COL_METER_GRAD_YELLOW - Toolkit.fillArea(batch, x.toFloat() + STRIP_W, y+14f, -STRIP_W * perc, 2f) +// val reductionDB = fullscaleToDecibels(maxReduction) +// val perc = (reductionDB / (-0.5*makeupGain)).toFloat().coerceIn(0f, 1f) +// batch.color = COL_METER_GRAD2_YELLOW +// Toolkit.fillArea(batch, x.toFloat() + STRIP_W, y.toFloat(), -STRIP_W * perc, 14f) +// batch.color = COL_METER_GRAD_YELLOW +// Toolkit.fillArea(batch, x.toFloat() + STRIP_W, y+14f, -STRIP_W * perc, 2f) batch.color = FILTER_NAME_ACTIVE - App.fontSmallNumbers.draw(batch, "C:${reductionDB.absoluteValue.times(100).roundToInt().div(100f)}", x+3f, y+1f) + App.fontSmallNumbers.draw(batch, "T:${threshold.absoluteValue.times(100).roundToInt().div(100f)}", x+3f, y+1f) + App.fontSmallNumbers.draw(batch, "K:${knee.absoluteValue.times(100).roundToInt().div(100f)}", x+3f, y+17f) + App.fontSmallNumbers.draw(batch, "R:${ratio.absoluteValue.times(100).roundToInt().div(100f)}:1", x+3f, y+33f) } - override val debugViewHeight: Int = 16 + override val debugViewHeight: Int = 48 override fun copyParamsFrom(other: TerrarumAudioFilter) { - TODO() + if (other is Comp) { + this.threshold = other.threshold + this.knee = other.knee + this.ratio = other.ratio + } } private fun Double.unNaN(d: Double): Double { diff --git a/src/net/torvald/terrarum/modulebasegame/ui/UISoundControlPanel.kt b/src/net/torvald/terrarum/modulebasegame/ui/UISoundControlPanel.kt index 3a820aa39..e069c82ab 100644 --- a/src/net/torvald/terrarum/modulebasegame/ui/UISoundControlPanel.kt +++ b/src/net/torvald/terrarum/modulebasegame/ui/UISoundControlPanel.kt @@ -5,6 +5,7 @@ import com.badlogic.gdx.graphics.g2d.SpriteBatch import net.torvald.terrarum.App import net.torvald.terrarum.audio.TerrarumAudioMixerTrack import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RATE +import net.torvald.terrarum.audio.dsp.Comp import net.torvald.terrarum.audio.dsp.Lowpass import net.torvald.terrarum.langpack.Lang import net.torvald.terrarum.ui.UICanvas @@ -35,17 +36,33 @@ class UISoundControlPanel(remoCon: UIRemoCon?) : UICanvas() { arrayOf("audio_speaker_setup", { Lang["MENU_OPTIONS_SPEAKER_SETUP", true] }, "textsel,headphone=MENU_OPTIONS_SPEAKER_HEADPHONE,stereo=MENU_OPTIONS_SPEAKER_STEREO"), arrayOf("audio_buffer_size", { Lang["MENU_OPTIONS_AUDIO_BUFFER_SIZE", true] }, "spinnersel,128,256,512,1024,2048"), arrayOf("", { "${Lang["MENU_LABEL_AUDIO_BUFFER_INSTRUCTION"]}" }, "p"), + //arrayOf("audio_dsp_compressor_ratio", { Lang["MENU_OPTIONS_AUDIO_COMP", true] }, "textsel,none=MENU_OPTIONS_DISABLE,light=MENU_OPTIONS_LIGHT,heavy=MENU_OPTIONS_STRONG") )) } + private val compDict = mapOf( + "none" to 1f, + "light" to 2f, + "heavy" to 5f + ) + override var height = ControlPanelCommon.getMenuHeight("basegame.soundcontrolpanel") private var oldBufferSize = App.getConfigInt("audio_buffer_size") + private var oldCompRatio = App.getConfigString("audio_dsp_compressor_ratio") override fun updateImpl(delta: Float) { uiItems.forEach { it.update(delta) } + App.getConfigString("audio_dsp_compressor_ratio").let { + if (it != oldCompRatio) { + oldCompRatio = it + + App.audioMixer.masterTrack.getFilter().ratio = compDict[it] ?: 1f + } + } + App.getConfigInt("audio_buffer_size").let { if (it != oldBufferSize) { oldBufferSize = it diff --git a/src/net/torvald/terrarum/ui/BasicDebugInfoWindow.kt b/src/net/torvald/terrarum/ui/BasicDebugInfoWindow.kt index b0ecb8fbd..82c011c0a 100644 --- a/src/net/torvald/terrarum/ui/BasicDebugInfoWindow.kt +++ b/src/net/torvald/terrarum/ui/BasicDebugInfoWindow.kt @@ -789,19 +789,24 @@ class BasicDebugInfoWindow : UICanvas() { } // comp marker - track.filters.filterIsInstance().firstOrNull()?.let { + val compSumDB = mutableListOf(0.0, 0.0) + track.filters.filter { !it.bypass }.filterIsInstance().forEach { for (ch in 0..1) { val downForceNow = it.downForce[ch] * 1.0 - if (downForceNow != 0.0) { - val down = FastMath.interpolateLinear(PEAK_SMOOTHING_FACTOR, downForceNow, oldComp[index][ch]) - val dBfs = fullscaleToDecibels(down) + compSumDB[ch] += fullscaleToDecibels(downForceNow) + } + } + for (ch in 0..1) { + val downForceNow = compSumDB[ch] + if (downForceNow != 0.0) { + val down = FastMath.interpolateLinear(PEAK_SMOOTHING_FACTOR, downForceNow, oldComp[index][ch]) + val dBfs = down//fullscaleToDecibels(down) - val h = meterHeight + ((dBfs + dbLow) / dbLow * -meterHeight).coerceAtMost(0.0).toFloat() - batch.color = COL_METER_COMP_BAR - Toolkit.fillArea(batch, x + 16f + ch * 17, faderY + 19f, 2f, h) + val h = meterHeight + ((dBfs + dbLow) / dbLow * -meterHeight).coerceAtMost(0.0).toFloat() + batch.color = COL_METER_COMP_BAR + Toolkit.fillArea(batch, x + 16f + ch * 17, faderY + 19f, 2f, h) - oldComp[index][ch] = down - } + oldComp[index][ch] = down } }