From 9a3ab64383ad9d2a5004e195769f38d1374aeece Mon Sep 17 00:00:00 2001 From: minjaesong Date: Sat, 2 Dec 2023 13:51:48 +0900 Subject: [PATCH] mixer: room for dynamic sources --- src/net/torvald/terrarum/DefaultConfig.kt | 4 +- src/net/torvald/terrarum/audio/AudioMixer.kt | 37 ++++++++++++------- .../terrarum/audio/MixerTrackProcessor.kt | 8 ++-- .../terrarum/audio/TerrarumAudioMixerTrack.kt | 23 ++++++------ .../modulebasegame/ui/UISoundControlPanel.kt | 2 +- .../terrarum/ui/BasicDebugInfoWindow.kt | 37 ++++++++++++++----- 6 files changed, 70 insertions(+), 41 deletions(-) diff --git a/src/net/torvald/terrarum/DefaultConfig.kt b/src/net/torvald/terrarum/DefaultConfig.kt index 7712004f8..2182abb6a 100644 --- a/src/net/torvald/terrarum/DefaultConfig.kt +++ b/src/net/torvald/terrarum/DefaultConfig.kt @@ -21,7 +21,9 @@ object DefaultConfig { "screenheight" to TerrarumScreenSize.defaultH, "fullscreen" to false, "atlastexsize" to 2048, - "audiobuffersize" to 512, + + "audio_buffer_size" to 512, + "audio_dynamic_source_max" to 128, "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 296c3c67e..fddc2034e 100644 --- a/src/net/torvald/terrarum/audio/AudioMixer.kt +++ b/src/net/torvald/terrarum/audio/AudioMixer.kt @@ -45,17 +45,19 @@ object AudioMixer: Disposable { val guiVolume: Double get() = App.getConfigDouble("guivolume") + val dynamicSourceCount: Int + get() = App.getConfigInt("audio_dynamic_source_max") val tracks = Array(8) { TerrarumAudioMixerTrack( if (it == 0) "Music" else if (it == 1) "Ambient" - else if (it == 2) "Player" - else if (it == 3) "GUI" + else if (it == 2) "GUI" + else if (it == 3) "\u00E4SFX" else if (it == 4) "\u00F0 \u00E4 \u00F0" // summation else if (it == 5) "\u00D9Open\u00D9" // convolution1 else if (it == 6) "\u00D9Cave\u00D9" // convolution2 else if (it == 7) "\u00F0 \u00DA \u00F0" // fade - else "Trk${it+1}", isBus = (it >= 4), maxVolumeFun = { + else "Trk${it+1}", trackType = if (it >= 3) TrackType.BUS else TrackType.STATIC_SOURCE, maxVolumeFun = { when (it) { 0 -> { musicVolume } 1 -> { ambientVolume } @@ -66,15 +68,20 @@ object AudioMixer: Disposable { } ) } - val masterTrack = TerrarumAudioMixerTrack("\u00DBMASTER", true) { masterVolume } + val dynamicTracks = Array(dynamicSourceCount) { TerrarumAudioMixerTrack( + "DS${(it + 1).toString().padStart(3, '0')}", + TrackType.DYNAMIC_SOURCE + ) } + + val masterTrack = TerrarumAudioMixerTrack("\u00DBMASTER", TrackType.MASTER) { masterVolume } val musicTrack: TerrarumAudioMixerTrack get() = tracks[0] val ambientTrack: TerrarumAudioMixerTrack get() = tracks[1] - val sfxMixTrack: TerrarumAudioMixerTrack - get() = tracks[2] val guiTrack: TerrarumAudioMixerTrack + get() = tracks[2] + val sfxSumTrack: TerrarumAudioMixerTrack get() = tracks[3] val sumBus: TerrarumAudioMixerTrack @@ -145,10 +152,7 @@ object AudioMixer: Disposable { init { // initialise audio paths // -// musicTrack.filters[1] = BinoPan(0f) -// musicTrack.filters[2] = Reverb(36f, 0.92f, 1200f) - - listOf(musicTrack, ambientTrack, sfxMixTrack, guiTrack).forEach { + listOf(musicTrack, ambientTrack, sfxSumTrack, guiTrack).forEach { it.filters[0] = Gain(1f) } @@ -159,7 +163,7 @@ object AudioMixer: Disposable { listOf(sumBus, convolveBusOpen, convolveBusCave).forEach { it.addSidechainInput(musicTrack, 1.0) it.addSidechainInput(ambientTrack, 1.0) - it.addSidechainInput(sfxMixTrack, 1.0) + it.addSidechainInput(sfxSumTrack, 1.0) } convolveBusOpen.filters[1] = Convolv(ModMgr.getFile("basegame", "audio/convolution/EchoThief - PurgatoryChasm.bin")) @@ -178,9 +182,14 @@ object AudioMixer: Disposable { masterTrack.addSidechainInput(fadeBus, 1.0) masterTrack.addSidechainInput(guiTrack, 1.0) + dynamicTracks.forEach { + it.filters[0] = BinoPan(0f) + sfxSumTrack.addSidechainInput(it, 1.0) + } parallelProcessingSchedule = arrayOf( - arrayOf(musicTrack, ambientTrack, sfxMixTrack, guiTrack), + arrayOf(musicTrack, ambientTrack, guiTrack), + dynamicTracks, arrayOf(sumBus, convolveBusOpen, convolveBusCave), arrayOf(fadeBus), arrayOf(masterTrack) @@ -204,7 +213,7 @@ object AudioMixer: Disposable { ) private val fadeReqs = HashMap().also { map -> - listOf(musicTrack, ambientTrack, sfxMixTrack, guiTrack, fadeBus).forEach { + listOf(musicTrack, ambientTrack, guiTrack, fadeBus).forEach { map[it] = FadeRequest() } } @@ -264,7 +273,7 @@ object AudioMixer: Disposable { masterTrack.volume = masterVolume musicTrack.getFilter().gain = musicVolume.toFloat() ambientTrack.getFilter().gain = ambientVolume.toFloat() - sfxMixTrack.getFilter().gain = sfxVolume.toFloat() + sfxSumTrack.getFilter().gain = sfxVolume.toFloat() guiTrack.getFilter().gain = guiVolume.toFloat() diff --git a/src/net/torvald/terrarum/audio/MixerTrackProcessor.kt b/src/net/torvald/terrarum/audio/MixerTrackProcessor.kt index 026a19237..bb14a1731 100644 --- a/src/net/torvald/terrarum/audio/MixerTrackProcessor.kt +++ b/src/net/torvald/terrarum/audio/MixerTrackProcessor.kt @@ -68,7 +68,7 @@ class MixerTrackProcessor(val bufferSize: Int, val rate: Int, val track: Terraru // Your code here // fetch deviceBufferSize amount of sample from the disk - if (!track.isMaster && !track.isBus && track.streamPlaying) { + if (track.trackType != TrackType.MASTER && track.trackType != TrackType.BUS && track.streamPlaying) { streamBuf.fetchBytes { val bytesRead = track.currentTrack?.gdxMusic?.forceInvoke("read", arrayOf(it)) if (bytesRead == null || bytesRead <= 0) { // some class (namely Mp3) may return 0 instead of negative value @@ -87,7 +87,7 @@ class MixerTrackProcessor(val bufferSize: Int, val rate: Int, val track: Terraru var bufEmpty = false // get samples and apply the fader - if (track.isMaster || track.isBus) { + if (track.trackType == TrackType.MASTER || track.trackType == TrackType.BUS) { // combine all the inputs samplesL1 = FloatArray(bufferSize / 4) samplesR1 = FloatArray(bufferSize / 4) @@ -164,7 +164,7 @@ 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) { + if (track.trackType != TrackType.MASTER) { this.pause() } else { @@ -246,7 +246,7 @@ private fun Queue.removeFirstOrElse(function: () -> T): T { class FeedSamplesToAdev(val bufferSize: Int, val rate: Int, val track: TerrarumAudioMixerTrack) : Runnable { init { - if (!track.isMaster) throw IllegalArgumentException("Track is not master") + if (track.trackType != TrackType.MASTER) throw IllegalArgumentException("Track is not master") } val sleepTime = (1000000000.0 * ((bufferSize / 4.0) / TerrarumAudioMixerTrack.SAMPLING_RATED)).toLong() diff --git a/src/net/torvald/terrarum/audio/TerrarumAudioMixerTrack.kt b/src/net/torvald/terrarum/audio/TerrarumAudioMixerTrack.kt index c9c75ddc3..ca46cd545 100644 --- a/src/net/torvald/terrarum/audio/TerrarumAudioMixerTrack.kt +++ b/src/net/torvald/terrarum/audio/TerrarumAudioMixerTrack.kt @@ -15,13 +15,17 @@ import kotlin.math.pow typealias TrackVolume = Double -class TerrarumAudioMixerTrack(val name: String, val isMaster: Boolean = false, val isBus: Boolean = false, private val maxVolumeFun: () -> Double): Disposable { +enum class TrackType { + STATIC_SOURCE, DYNAMIC_SOURCE, BUS, MASTER +} + +class TerrarumAudioMixerTrack(val name: String, val trackType: TrackType, private val maxVolumeFun: () -> Double = {1.0}): Disposable { companion object { const val SAMPLING_RATE = 48000 const val SAMPLING_RATEF = 48000f const val SAMPLING_RATED = 48000.0 - val BUFFER_SIZE = 4 * App.getConfigInt("audiobuffersize") // n ms -> 384 * n + val BUFFER_SIZE = 4 * App.getConfigInt("audio_buffer_size") // n ms -> 384 * n } val hash = getHashStr() @@ -52,10 +56,10 @@ class TerrarumAudioMixerTrack(val name: String, val isMaster: Boolean = false, v inline fun getFilter() = filters.filterIsInstance().first()!! - internal val sidechainInputs = Array?>(16) { null } + internal val sidechainInputs = ArrayList?>() internal fun getSidechains(): List = sidechainInputs.map { it?.first } fun addSidechainInput(input: TerrarumAudioMixerTrack, inputVolume: TrackVolume) { - if (input.isMaster) + if (input.trackType == TrackType.MASTER) throw IllegalArgumentException("Cannot add master track as a sidechain") if (sidechainInputs.map { it?.first }.any { it?.hash == input.hash }) @@ -67,13 +71,8 @@ class TerrarumAudioMixerTrack(val name: String, val isMaster: Boolean = false, v }) throw IllegalArgumentException("The track '${input.hash}' contains current track (${this.hash}) as its sidechain") - val emptySpot = sidechainInputs.indexOf(null) - if (emptySpot != -1) { - sidechainInputs[emptySpot] = (input to inputVolume) - } - else { - throw IllegalStateException("Sidechain is full (${sidechainInputs.size})!") - } + + sidechainInputs.add(input to inputVolume) } @@ -87,7 +86,7 @@ class TerrarumAudioMixerTrack(val name: String, val isMaster: Boolean = false, v it.get(Gdx.audio) as Int } internal val adev: OpenALBufferedAudioDevice? = - if (isMaster) { + if (trackType == TrackType.MASTER) { OpenALBufferedAudioDevice( Gdx.audio as OpenALLwjgl3Audio, SAMPLING_RATE, diff --git a/src/net/torvald/terrarum/modulebasegame/ui/UISoundControlPanel.kt b/src/net/torvald/terrarum/modulebasegame/ui/UISoundControlPanel.kt index 81a1410e4..ba31ae41d 100644 --- a/src/net/torvald/terrarum/modulebasegame/ui/UISoundControlPanel.kt +++ b/src/net/torvald/terrarum/modulebasegame/ui/UISoundControlPanel.kt @@ -28,7 +28,7 @@ class UISoundControlPanel(remoCon: UIRemoCon?) : UICanvas() { arrayOf("", { "" }, "pp"), arrayOf("guivolume", { Lang["MENU_LABEL_INTERFACE"] }, "sliderd,0,1"), arrayOf("", { Lang["MENU_LABEL_HARDWARE"] }, "h1"), - arrayOf("audiobuffersize", { Lang["MENU_OPTIONS_AUDIO_BUFFER_SIZE"] }, "spinnersel,128,256,512,1024,2048"), + arrayOf("audio_buffer_size", { Lang["MENU_OPTIONS_AUDIO_BUFFER_SIZE"] }, "spinnersel,128,256,512,1024,2048"), arrayOf("", { "(${Lang["MENU_LABEL_RESTART_REQUIRED"]})" }, "p"), arrayOf("", { "${Lang["MENU_LABEL_AUDIO_BUFFER_INSTRUCTION"]}" }, "p"), diff --git a/src/net/torvald/terrarum/ui/BasicDebugInfoWindow.kt b/src/net/torvald/terrarum/ui/BasicDebugInfoWindow.kt index f40595b23..51d72a69a 100644 --- a/src/net/torvald/terrarum/ui/BasicDebugInfoWindow.kt +++ b/src/net/torvald/terrarum/ui/BasicDebugInfoWindow.kt @@ -13,6 +13,7 @@ import net.torvald.terrarum.Terrarum.mouseTileX import net.torvald.terrarum.Terrarum.mouseTileY import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE import net.torvald.terrarum.audio.* +import net.torvald.terrarum.audio.AudioMixer.dynamicSourceCount import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.BUFFER_SIZE import net.torvald.terrarum.audio.dsp.* import net.torvald.terrarum.controller.TerrarumController @@ -450,7 +451,7 @@ class BasicDebugInfoWindow : UICanvas() { private fun drawStrip(batch: SpriteBatch, x: Int, y: Int, track: TerrarumAudioMixerTrack, index: Int) { // back - batch.color = if (track.isMaster) COL_WELL4 else if (track.isBus) COL_WELL3 else trackBack[index % 2] + batch.color = if (track.trackType == TrackType.MASTER) COL_WELL4 else if (track.trackType == TrackType.BUS) COL_WELL3 else trackBack[index % 2] Toolkit.fillArea(batch, x, y, stripW, stripH) // strip/name separator @@ -485,21 +486,39 @@ class BasicDebugInfoWindow : UICanvas() { val faderY = y + stripFilterHeight * numberOfFilters // receives (opposite of "sends") - track.sidechainInputs.filterNotNull().reversed().forEachIndexed { i, (side, mix) -> - val mixDb = fullscaleToDecibels(mix) - val perc = ((mixDb + 24.0).coerceAtLeast(0.0) / 24.0).toFloat() + if (track != AudioMixer.sfxSumTrack) { + track.sidechainInputs.filterNotNull().reversed().forEachIndexed { i, (side, mix) -> + val mixDb = fullscaleToDecibels(mix) + val perc = ((mixDb + 24.0).coerceAtLeast(0.0) / 24.0).toFloat() + // gauge background + batch.color = COL_METER_TROUGH + Toolkit.fillArea(batch, x.toFloat(), faderY - (i + 1) * 16f, stripW.toFloat(), 14f) + batch.color = COL_SENDS_GRAD2 + Toolkit.fillArea(batch, x.toFloat(), faderY - (i + 1) * 16f, stripW * perc, 14f) + batch.color = COL_SENDS_GRAD + Toolkit.fillArea(batch, x.toFloat(), faderY - (i + 1) * 16f + 14f, stripW * perc, 2f) + + // label + batch.color = FILTER_NAME_ACTIVE + App.fontSmallNumbers.draw(batch, "\u00C0", x.toFloat(), faderY - (i + 1) * 16f + 1f) + App.fontSmallNumbers.draw(batch, side.name, x + 10f, faderY - (i + 1) * 16f + 1f) + } + } + else { + val i = 0 + val perc = 1f // gauge background batch.color = COL_METER_TROUGH - Toolkit.fillArea(batch, x.toFloat(), faderY - (i+1)*16f, stripW.toFloat(), 14f) + Toolkit.fillArea(batch, x.toFloat(), faderY - (i + 1) * 16f, stripW.toFloat(), 14f) batch.color = COL_SENDS_GRAD2 - Toolkit.fillArea(batch, x.toFloat(), faderY - (i+1)*16f, stripW * perc, 14f) + Toolkit.fillArea(batch, x.toFloat(), faderY - (i + 1) * 16f, stripW * perc, 14f) batch.color = COL_SENDS_GRAD - Toolkit.fillArea(batch, x.toFloat(), faderY - (i+1)*16f + 14f, stripW * perc, 2f) + Toolkit.fillArea(batch, x.toFloat(), faderY - (i + 1) * 16f + 14f, stripW * perc, 2f) // label batch.color = FILTER_NAME_ACTIVE - App.fontSmallNumbers.draw(batch, "\u00C0", x.toFloat(), faderY - (i+1)*16f + 1f) - App.fontSmallNumbers.draw(batch, side.name, x + 10f, faderY - (i+1)*16f + 1f) + App.fontSmallNumbers.draw(batch, "\u00C0", x.toFloat(), faderY - (i + 1) * 16f + 1f) + App.fontSmallNumbers.draw(batch, "DS($dynamicSourceCount)", x + 10f, faderY - (i + 1) * 16f + 1f) } // fader