mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-07 12:21:52 +09:00
a working compressor this time
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<Comp>().ratio = compDict[it] ?: 1f
|
||||
}
|
||||
}
|
||||
|
||||
App.getConfigInt("audio_buffer_size").let {
|
||||
if (it != oldBufferSize) {
|
||||
oldBufferSize = it
|
||||
|
||||
@@ -789,12 +789,18 @@ class BasicDebugInfoWindow : UICanvas() {
|
||||
}
|
||||
|
||||
// comp marker
|
||||
track.filters.filterIsInstance<DspCompressor>().firstOrNull()?.let {
|
||||
val compSumDB = mutableListOf(0.0, 0.0)
|
||||
track.filters.filter { !it.bypass }.filterIsInstance<DspCompressor>().forEach {
|
||||
for (ch in 0..1) {
|
||||
val downForceNow = it.downForce[ch] * 1.0
|
||||
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 = fullscaleToDecibels(down)
|
||||
val dBfs = down//fullscaleToDecibels(down)
|
||||
|
||||
val h = meterHeight + ((dBfs + dbLow) / dbLow * -meterHeight).coerceAtMost(0.0).toFloat()
|
||||
batch.color = COL_METER_COMP_BAR
|
||||
@@ -803,7 +809,6 @@ class BasicDebugInfoWindow : UICanvas() {
|
||||
oldComp[index][ch] = down
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// slider trough
|
||||
batch.color = COL_METER_TROUGH
|
||||
|
||||
Reference in New Issue
Block a user