diff --git a/src/net/torvald/terrarum/App.java b/src/net/torvald/terrarum/App.java index b94cde6fb..cc7fdb40f 100644 --- a/src/net/torvald/terrarum/App.java +++ b/src/net/torvald/terrarum/App.java @@ -51,6 +51,7 @@ import java.io.IOException; import java.lang.reflect.Field; import java.util.*; +import static java.lang.Thread.MAX_PRIORITY; import static net.torvald.terrarum.TerrarumKt.*; /** @@ -1192,6 +1193,7 @@ public class App implements ApplicationListener { AudioMixer.INSTANCE.getMasterVolume(); audioManagerThread = new Thread(new AudioManagerRunnable(), "TerrarumAudioManager"); + audioManagerThread.setPriority(MAX_PRIORITY); // higher = more predictable; audio delay is very noticeable so it gets high priority audioManagerThread.start(); Terrarum.initialise(); diff --git a/src/net/torvald/terrarum/audio/AudioMixer.kt b/src/net/torvald/terrarum/audio/AudioMixer.kt index 0602e2463..fb09477dc 100644 --- a/src/net/torvald/terrarum/audio/AudioMixer.kt +++ b/src/net/torvald/terrarum/audio/AudioMixer.kt @@ -5,6 +5,8 @@ import com.badlogic.gdx.backends.lwjgl3.audio.Lwjgl3Audio import com.badlogic.gdx.utils.Disposable import com.jme3.math.FastMath import net.torvald.terrarum.App +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_RATED import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RATEF import net.torvald.terrarum.modulebasegame.MusicContainer @@ -27,22 +29,23 @@ object AudioMixer: Disposable { /** Returns a (master volume * bgm volume) */ val musicVolume: Double - get() = (App.getConfigDouble("bgmvolume") * App.getConfigDouble("mastervolume")) + get() = App.getConfigDouble("bgmvolume") /** Returns a (master volume * sfx volume */ val ambientVolume: Double - get() = (App.getConfigDouble("sfxvolume") * App.getConfigDouble("mastervolume")) + get() = App.getConfigDouble("sfxvolume") val tracks = Array(10) { TerrarumAudioMixerTrack( - if (it == 0) "BGM Track" - else if (it == 1) "AMB Track" - else "Audio Track #${it+1}" + if (it == 0) "BGM" + else if (it == 1) "AMB" + else "Trk${it+1}" ) } val masterTrack = TerrarumAudioMixerTrack("Master", true).also { master -> - tracks.forEach { master.addSidechainInput(it, 1.0) } + master.volume = masterVolume master.filters[0] = Buffer + tracks.forEachIndexed { i, it -> master.addSidechainInput(it, if (i == INDEX_BGM) musicVolume else if (i == INDEX_AMB) ambientVolume else 1.0) } } val musicTrack: TerrarumAudioMixerTrack @@ -59,6 +62,8 @@ object AudioMixer: Disposable { private var fadeLength = DEFAULT_FADEOUT_LEN private var fadeoutFired = false private var fadeinFired = false + private var fadeTarget = 0.0 + private var fadeStart = 0.0 private var lpAkku = 0.0 private var lpLength = 0.4 @@ -75,24 +80,28 @@ object AudioMixer: Disposable { if (fadeoutFired) { fadeAkku += delta - musicTrack.volume = (musicVolume * (1.0 - (fadeAkku / fadeLength))).coerceIn(0.0, 1.0) + val step = fadeAkku / fadeLength + musicTrack.volume = FastMath.interpolateLinear(step, fadeStart, fadeTarget) if (fadeAkku >= fadeLength) { fadeoutFired = false - musicTrack.volume = 0.0 - musicTrack.currentTrack = null + musicTrack.volume = fadeTarget + + if (fadeTarget == 0.0) + musicTrack.currentTrack = null } } else if (fadeinFired) { fadeAkku += delta - musicTrack.volume = (musicVolume * (fadeAkku / fadeLength)).coerceIn(0.0, 1.0) + val step = fadeAkku / fadeLength + musicTrack.volume = FastMath.interpolateLinear(step, fadeStart, fadeTarget) if (musicTrack.isPlaying == false) { musicTrack.play() } if (fadeAkku >= fadeLength) { - musicTrack.volume = musicVolume + musicTrack.volume = fadeTarget fadeinFired = false } } @@ -100,7 +109,7 @@ object AudioMixer: Disposable { if (lpOutFired) { lpAkku += delta - val x = (lpAkku / lpLength).coerceIn(0.0, 1.0) + val x = lpAkku / lpLength val q = 400.0 val step = (q.pow(x) - 1) / (q - 1) // https://www.desmos.com/calculator/sttaq2qhzm val cutoff = FastMath.interpolateLinear(step, SAMPLING_RATED / 100.0, SAMPLING_RATED) @@ -113,7 +122,7 @@ object AudioMixer: Disposable { } else if (lpInFired) { lpAkku += delta - val x = (lpAkku / lpLength).coerceIn(0.0, 1.0) + val x = lpAkku / lpLength val q = 400.0 val step = log((q-1) * x + 1.0, q) // https://www.desmos.com/calculator/sttaq2qhzm val cutoff = FastMath.interpolateLinear(step, SAMPLING_RATED, SAMPLING_RATED / 100.0) @@ -129,7 +138,7 @@ object AudioMixer: Disposable { if (musicTrack.isPlaying != true && musicTrack.nextTrack != null) { // printdbg(this, "Playing next music: ${nextMusic!!.name}") musicTrack.queueNext(null) - musicTrack.volume = musicVolume + musicTrack.volume = 1.0 musicTrack.play() } } @@ -145,19 +154,23 @@ object AudioMixer: Disposable { requestFadeOut(DEFAULT_FADEOUT_LEN) } - fun requestFadeOut(length: Double) { + fun requestFadeOut(length: Double, target: Double = 0.0) { if (!fadeoutFired) { fadeLength = length.coerceAtLeast(1.0/1024.0) fadeAkku = 0.0 fadeoutFired = true + fadeTarget = target + fadeStart = musicTrack.volume } } - fun requestFadeIn(length: Double) { + fun requestFadeIn(length: Double, target: Double = 1.0) { if (!fadeinFired) { fadeLength = length.coerceAtLeast(1.0/1024.0) fadeAkku = 0.0 fadeinFired = true + fadeTarget = target + fadeStart = musicTrack.volume } } diff --git a/src/net/torvald/terrarum/audio/MixerTrackProcessor.kt b/src/net/torvald/terrarum/audio/MixerTrackProcessor.kt index 5c4c923d1..aa809c6df 100644 --- a/src/net/torvald/terrarum/audio/MixerTrackProcessor.kt +++ b/src/net/torvald/terrarum/audio/MixerTrackProcessor.kt @@ -2,6 +2,8 @@ package net.torvald.terrarum.audio import com.badlogic.gdx.utils.Queue import net.torvald.reflection.forceInvoke +import net.torvald.terrarum.audio.AudioMixer.masterVolume +import net.torvald.terrarum.audio.AudioMixer.musicVolume import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RATE import kotlin.math.absoluteValue @@ -35,7 +37,8 @@ class MixerTrackProcessor(val bufferSize: Int, val rate: Int, val track: Terraru if (true) println("[AudioAdapter ${track.name}] $msg") } override fun run() { - while (running) { synchronized(pauseLock) { + while (running) { + synchronized(pauseLock) { if (!running) { // may have changed while waiting to // synchronize on pauseLock breakBomb = true @@ -90,11 +93,11 @@ class MixerTrackProcessor(val bufferSize: Int, val rate: Int, val track: Terraru if (track.isMaster) { // TEST CODE must combine all the inputs - track.sidechainInputs[0]?.let { - samplesL0 = it.first.processor.fout0[0] - samplesR0 = it.first.processor.fout0[1] - samplesL1 = it.first.processor.fout1[0] - samplesR1 = it.first.processor.fout1[1] + track.sidechainInputs[TerrarumAudioMixerTrack.INDEX_BGM]?.let { + samplesL0 = it.first.processor.fout0[0].applyVolume(musicVolume) + samplesR0 = it.first.processor.fout0[1].applyVolume(musicVolume) + samplesL1 = it.first.processor.fout1[0].applyVolume(musicVolume) + samplesR1 = it.first.processor.fout1[1].applyVolume(musicVolume) } @@ -164,7 +167,6 @@ class MixerTrackProcessor(val bufferSize: Int, val rate: Int, val track: Terraru // by this time, the output buffer is filled with processed results, pause the execution if (!track.isMaster) { - this.pause() } else { @@ -176,7 +178,9 @@ class MixerTrackProcessor(val bufferSize: Int, val rate: Int, val track: Terraru } // printdbg("PUSHE; Queue size: ${track.pcmQueue.size}") - track.pcmQueue.addLast(fout1) + val masvol = masterVolume + track.volume = masvol + track.pcmQueue.addLast(fout1.map { it.applyVolume(masvol) }) } // spin @@ -210,8 +214,11 @@ class MixerTrackProcessor(val bufferSize: Int, val rate: Int, val track: Terraru pauseLock.notifyAll() // Unblocks thread } } + + private fun FloatArray.applyVolume(musicVolume: Double) = FloatArray(this.size) { (this[it] * musicVolume).toFloat() } } + private fun Queue.removeFirstOrElse(function: () -> T): T { return if (this.isEmpty) { this.removeFirst() diff --git a/src/net/torvald/terrarum/audio/TerrarumAudioMixerTrack.kt b/src/net/torvald/terrarum/audio/TerrarumAudioMixerTrack.kt index 3c8ab1cd1..a2f10cdcc 100644 --- a/src/net/torvald/terrarum/audio/TerrarumAudioMixerTrack.kt +++ b/src/net/torvald/terrarum/audio/TerrarumAudioMixerTrack.kt @@ -7,6 +7,7 @@ import com.badlogic.gdx.utils.Queue import net.torvald.reflection.forceInvoke import net.torvald.terrarum.getHashStr import net.torvald.terrarum.modulebasegame.MusicContainer +import java.lang.Thread.MAX_PRIORITY import kotlin.math.log10 import kotlin.math.pow @@ -18,7 +19,10 @@ class TerrarumAudioMixerTrack(val name: String, val isMaster: Boolean = false): const val SAMPLING_RATE = 48000 const val SAMPLING_RATEF = 48000f const val SAMPLING_RATED = 48000.0 - const val BUFFER_SIZE = 8000 + const val BUFFER_SIZE = 12000 + + const val INDEX_BGM = 0 + const val INDEX_AMB = 1 } val hash = getHashStr() @@ -128,6 +132,7 @@ class TerrarumAudioMixerTrack(val name: String, val isMaster: Boolean = false): internal var processor = MixerTrackProcessor(BUFFER_SIZE, SAMPLING_RATE, this) private val processorThread = Thread(processor).also { + it.priority = MAX_PRIORITY // higher = more predictable; audio delay is very noticeable so it gets high priority it.start() } internal var pcmQueue = Queue>() @@ -141,6 +146,7 @@ class TerrarumAudioMixerTrack(val name: String, val isMaster: Boolean = false): if (isMaster) { queueDispatcher = FeedSamplesToAdev(BUFFER_SIZE, SAMPLING_RATE, this) queueDispatcherThread = Thread(queueDispatcher).also { + it.priority = MAX_PRIORITY // higher = more predictable; audio delay is very noticeable so it gets high priority it.start() } } diff --git a/src/net/torvald/terrarum/modulebasegame/ui/UIInventoryFull.kt b/src/net/torvald/terrarum/modulebasegame/ui/UIInventoryFull.kt index 95d2d4a63..dc6f650f3 100644 --- a/src/net/torvald/terrarum/modulebasegame/ui/UIInventoryFull.kt +++ b/src/net/torvald/terrarum/modulebasegame/ui/UIInventoryFull.kt @@ -366,6 +366,7 @@ class UIInventoryFull( INGAME.setTooltipMessage(null) AudioMixer.requestLowpassIn(0.4) + AudioMixer.requestFadeOut(0.4, 0.5) } override fun doClosing(delta: Float) { @@ -375,6 +376,7 @@ class UIInventoryFull( INGAME.setTooltipMessage(null) AudioMixer.requestLowpassOut(0.4) + AudioMixer.requestFadeIn(0.4, 1.0) } override fun endOpening(delta: Float) { diff --git a/src/net/torvald/terrarum/ui/BasicDebugInfoWindow.kt b/src/net/torvald/terrarum/ui/BasicDebugInfoWindow.kt index 738cce0bd..84dd2d7a6 100644 --- a/src/net/torvald/terrarum/ui/BasicDebugInfoWindow.kt +++ b/src/net/torvald/terrarum/ui/BasicDebugInfoWindow.kt @@ -377,7 +377,7 @@ class BasicDebugInfoWindow : UICanvas() { private val stripGap = 1 private val stripFilterHeight = 32 private val stripFaderHeight = 200 - private val stripH = stripFaderHeight + stripFilterHeight * 4 + private val stripH = stripFaderHeight + stripFilterHeight * 4 + 16 private val COL_WELL = Color(0x374854_aa) private val COL_WELL2 = Color(0x3f5360_aa) @@ -409,11 +409,22 @@ class BasicDebugInfoWindow : UICanvas() { strips.forEachIndexed { index, track -> drawStrip(batch, x + index * (stripW + stripGap), y, track, index) } } + private val dbLow = 48.0 + private fun drawStrip(batch: SpriteBatch, x: Int, y: Int, track: TerrarumAudioMixerTrack, index: Int) { // back batch.color = if (track.isMaster) COL_WELL3 else trackBack[index % 2] Toolkit.fillArea(batch, x, y, stripW, stripH) + // strip/name separator + batch.color = COL_METER_GRAD2 + Toolkit.fillArea(batch, x, y + stripH - 16, stripW, 2) + + // name text + batch.color = FILTER_NAME_ACTIVE + App.fontSmallNumbers.draw(batch, track.name, x + 3f, y + stripH - 13f) + + // filterbank back batch.color = COL_FILTER_WELL_BACK Toolkit.fillArea(batch, x, y, stripW, stripFilterHeight * 4) @@ -436,9 +447,9 @@ class BasicDebugInfoWindow : UICanvas() { // fader val dB = track.dBfs val dBstr = dB.toIntAndFrac(2,1) - val dBfs = dB.coerceIn(-96.0, 0.0).plus(96.0).div(96.0) + val dBfs = dB.coerceIn(-dbLow, 0.0).plus(dbLow).div(dbLow).toFloat() batch.color = FILTER_NAME_ACTIVE - App.fontSmallNumbers.draw(batch, dBstr, x+3f, faderY+1f) + App.fontSmallNumbers.draw(batch, dBstr, x+32f, faderY+1f) // fader trough batch.color = COL_METER_TROUGH @@ -455,7 +466,7 @@ class BasicDebugInfoWindow : UICanvas() { batch.color = FILTER_NAME_ACTIVE for (i in 0..16 step 2) { val y = faderY + 11f + i * 11 - val s = (i*6).toString().padStart(2, ' ') + val s = (i*dbLow/16).roundToInt().toString().padStart(2, ' ') App.fontSmallNumbers.draw(batch, s, x + 1f, y) } @@ -466,10 +477,17 @@ class BasicDebugInfoWindow : UICanvas() { val dBfs = fullscaleToDecibels(fs) val x = x + 19f + 7 * ch - val h = ((dBfs + 96.0) / 96.0 * -meterHeight).coerceAtMost(0.0).toFloat() + val h = ((dBfs + dbLow) / dbLow * -meterHeight).coerceAtMost(0.0).toFloat() Toolkit.fillArea(batch, x, faderY + 18f + meterHeight, 6f, h) } + // slider trough + batch.color = COL_METER_TROUGH + Toolkit.fillArea(batch, x + 48, faderY + 16, 2, meterTroughHeight) + + // slider handle + drawFaderHandle(batch, x + 48f, faderY + 18f + meterHeight - dBfs * meterHeight) + } private fun drawFilterParam(batch: SpriteBatch, x: Int, y: Int, filter: TerrarumAudioFilter, track: TerrarumAudioMixerTrack) { @@ -487,6 +505,38 @@ class BasicDebugInfoWindow : UICanvas() { } + private val FADER_HANDLE_D1 = Color(0xffffff_bb.toInt()) + private val FADER_HANDLE_D2 = Color(0xdddddd_bb.toInt()) + private val FADER_HANDLE_D3 = Color(0xeeeeee_bb.toInt()) + private val FADER_HANDLE_U1 = Color(0xffffff_bb.toInt()) + private val FADER_HANDLE_U2 = Color(0xaaaaaa_bb.toInt()) + private val FADER_HANDLE_U3 = Color(0xbbbbbb_bb.toInt()) + private val FADER_HANDLE_L = Color(0x777777_bb.toInt()) + private val FADER_HANDLE_C = Color(0x444444_bb.toInt()) + private val FADER_HANDLE_R = Color(0x666666_bb.toInt()) + private fun drawFaderHandle(batch: SpriteBatch, cx: Float, cy: Float) { + batch.color = FADER_HANDLE_U1 + Toolkit.fillArea(batch, cx - 4, cy - 6, 10f, 2f) + batch.color = FADER_HANDLE_U2 + Toolkit.fillArea(batch, cx - 4, cy - 4, 10f, 1f) + batch.color = FADER_HANDLE_U3 + Toolkit.fillArea(batch, cx - 4, cy - 3, 10f, 3f) + batch.color = FADER_HANDLE_D3 + Toolkit.fillArea(batch, cx - 4, cy + 1, 10f, 3f) + batch.color = FADER_HANDLE_D2 + Toolkit.fillArea(batch, cx - 4, cy + 4, 10f, 1f) + batch.color = FADER_HANDLE_D1 + Toolkit.fillArea(batch, cx - 4, cy + 5, 10f, 2f) + + batch.color = FADER_HANDLE_L + Toolkit.fillArea(batch, cx - 5, cy - 5, 1f, 11f) + batch.color = FADER_HANDLE_R + Toolkit.fillArea(batch, cx + 6, cy - 5, 1f, 11f) + batch.color = FADER_HANDLE_C + Toolkit.fillArea(batch, cx - 4, cy, 10f, 1f) + } + + private val colHairline = Color(0xf22100ff.toInt()) private val colGraph = Toolkit.Theme.COL_SELECTED private val colGrapi = Toolkit.Theme.COL_SELECTED.cpy().mul(0.5f, 0.5f, 0.5f, 1f)