mixer: room for dynamic sources

This commit is contained in:
minjaesong
2023-12-02 13:51:48 +09:00
parent 8b95c0aa0e
commit 9a3ab64383
6 changed files with 70 additions and 41 deletions

View File

@@ -21,7 +21,9 @@ object DefaultConfig {
"screenheight" to TerrarumScreenSize.defaultH, "screenheight" to TerrarumScreenSize.defaultH,
"fullscreen" to false, "fullscreen" to false,
"atlastexsize" to 2048, "atlastexsize" to 2048,
"audiobuffersize" to 512,
"audio_buffer_size" to 512,
"audio_dynamic_source_max" to 128,
"language" to App.getSysLang(), "language" to App.getSysLang(),
"notificationshowuptime" to 4000, // 4s "notificationshowuptime" to 4000, // 4s

View File

@@ -45,17 +45,19 @@ object AudioMixer: Disposable {
val guiVolume: Double val guiVolume: Double
get() = App.getConfigDouble("guivolume") get() = App.getConfigDouble("guivolume")
val dynamicSourceCount: Int
get() = App.getConfigInt("audio_dynamic_source_max")
val tracks = Array(8) { TerrarumAudioMixerTrack( val tracks = Array(8) { TerrarumAudioMixerTrack(
if (it == 0) "Music" if (it == 0) "Music"
else if (it == 1) "Ambient" else if (it == 1) "Ambient"
else if (it == 2) "Player" else if (it == 2) "GUI"
else if (it == 3) "GUI" else if (it == 3) "\u00E4SFX"
else if (it == 4) "\u00F0 \u00E4 \u00F0" // summation else if (it == 4) "\u00F0 \u00E4 \u00F0" // summation
else if (it == 5) "\u00D9Open\u00D9" // convolution1 else if (it == 5) "\u00D9Open\u00D9" // convolution1
else if (it == 6) "\u00D9Cave\u00D9" // convolution2 else if (it == 6) "\u00D9Cave\u00D9" // convolution2
else if (it == 7) "\u00F0 \u00DA \u00F0" // fade 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) { when (it) {
0 -> { musicVolume } 0 -> { musicVolume }
1 -> { ambientVolume } 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 val musicTrack: TerrarumAudioMixerTrack
get() = tracks[0] get() = tracks[0]
val ambientTrack: TerrarumAudioMixerTrack val ambientTrack: TerrarumAudioMixerTrack
get() = tracks[1] get() = tracks[1]
val sfxMixTrack: TerrarumAudioMixerTrack
get() = tracks[2]
val guiTrack: TerrarumAudioMixerTrack val guiTrack: TerrarumAudioMixerTrack
get() = tracks[2]
val sfxSumTrack: TerrarumAudioMixerTrack
get() = tracks[3] get() = tracks[3]
val sumBus: TerrarumAudioMixerTrack val sumBus: TerrarumAudioMixerTrack
@@ -145,10 +152,7 @@ object AudioMixer: Disposable {
init { init {
// initialise audio paths // // initialise audio paths //
// musicTrack.filters[1] = BinoPan(0f) listOf(musicTrack, ambientTrack, sfxSumTrack, guiTrack).forEach {
// musicTrack.filters[2] = Reverb(36f, 0.92f, 1200f)
listOf(musicTrack, ambientTrack, sfxMixTrack, guiTrack).forEach {
it.filters[0] = Gain(1f) it.filters[0] = Gain(1f)
} }
@@ -159,7 +163,7 @@ object AudioMixer: Disposable {
listOf(sumBus, convolveBusOpen, convolveBusCave).forEach { listOf(sumBus, convolveBusOpen, convolveBusCave).forEach {
it.addSidechainInput(musicTrack, 1.0) it.addSidechainInput(musicTrack, 1.0)
it.addSidechainInput(ambientTrack, 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")) 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(fadeBus, 1.0)
masterTrack.addSidechainInput(guiTrack, 1.0) masterTrack.addSidechainInput(guiTrack, 1.0)
dynamicTracks.forEach {
it.filters[0] = BinoPan(0f)
sfxSumTrack.addSidechainInput(it, 1.0)
}
parallelProcessingSchedule = arrayOf( parallelProcessingSchedule = arrayOf(
arrayOf(musicTrack, ambientTrack, sfxMixTrack, guiTrack), arrayOf(musicTrack, ambientTrack, guiTrack),
dynamicTracks,
arrayOf(sumBus, convolveBusOpen, convolveBusCave), arrayOf(sumBus, convolveBusOpen, convolveBusCave),
arrayOf(fadeBus), arrayOf(fadeBus),
arrayOf(masterTrack) arrayOf(masterTrack)
@@ -204,7 +213,7 @@ object AudioMixer: Disposable {
) )
private val fadeReqs = HashMap<TerrarumAudioMixerTrack, FadeRequest>().also { map -> private val fadeReqs = HashMap<TerrarumAudioMixerTrack, FadeRequest>().also { map ->
listOf(musicTrack, ambientTrack, sfxMixTrack, guiTrack, fadeBus).forEach { listOf(musicTrack, ambientTrack, guiTrack, fadeBus).forEach {
map[it] = FadeRequest() map[it] = FadeRequest()
} }
} }
@@ -264,7 +273,7 @@ object AudioMixer: Disposable {
masterTrack.volume = masterVolume masterTrack.volume = masterVolume
musicTrack.getFilter<Gain>().gain = musicVolume.toFloat() musicTrack.getFilter<Gain>().gain = musicVolume.toFloat()
ambientTrack.getFilter<Gain>().gain = ambientVolume.toFloat() ambientTrack.getFilter<Gain>().gain = ambientVolume.toFloat()
sfxMixTrack.getFilter<Gain>().gain = sfxVolume.toFloat() sfxSumTrack.getFilter<Gain>().gain = sfxVolume.toFloat()
guiTrack.getFilter<Gain>().gain = guiVolume.toFloat() guiTrack.getFilter<Gain>().gain = guiVolume.toFloat()

View File

@@ -68,7 +68,7 @@ class MixerTrackProcessor(val bufferSize: Int, val rate: Int, val track: Terraru
// Your code here // Your code here
// fetch deviceBufferSize amount of sample from the disk // 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 { streamBuf.fetchBytes {
val bytesRead = track.currentTrack?.gdxMusic?.forceInvoke<Int>("read", arrayOf(it)) val bytesRead = track.currentTrack?.gdxMusic?.forceInvoke<Int>("read", arrayOf(it))
if (bytesRead == null || bytesRead <= 0) { // some class (namely Mp3) may return 0 instead of negative value 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 var bufEmpty = false
// get samples and apply the fader // get samples and apply the fader
if (track.isMaster || track.isBus) { if (track.trackType == TrackType.MASTER || track.trackType == TrackType.BUS) {
// combine all the inputs // combine all the inputs
samplesL1 = FloatArray(bufferSize / 4) samplesL1 = FloatArray(bufferSize / 4)
samplesR1 = 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 // by this time, the output buffer is filled with processed results, pause the execution
if (!track.isMaster) { if (track.trackType != TrackType.MASTER) {
this.pause() this.pause()
} }
else { else {
@@ -246,7 +246,7 @@ private fun <T> Queue<T>.removeFirstOrElse(function: () -> T): T {
class FeedSamplesToAdev(val bufferSize: Int, val rate: Int, val track: TerrarumAudioMixerTrack) : Runnable { class FeedSamplesToAdev(val bufferSize: Int, val rate: Int, val track: TerrarumAudioMixerTrack) : Runnable {
init { 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() val sleepTime = (1000000000.0 * ((bufferSize / 4.0) / TerrarumAudioMixerTrack.SAMPLING_RATED)).toLong()

View File

@@ -15,13 +15,17 @@ import kotlin.math.pow
typealias TrackVolume = Double 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 { companion object {
const val SAMPLING_RATE = 48000 const val SAMPLING_RATE = 48000
const val SAMPLING_RATEF = 48000f const val SAMPLING_RATEF = 48000f
const val SAMPLING_RATED = 48000.0 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() val hash = getHashStr()
@@ -52,10 +56,10 @@ class TerrarumAudioMixerTrack(val name: String, val isMaster: Boolean = false, v
inline fun <reified T> getFilter() = filters.filterIsInstance<T>().first()!! inline fun <reified T> getFilter() = filters.filterIsInstance<T>().first()!!
internal val sidechainInputs = Array<Pair<TerrarumAudioMixerTrack, TrackVolume>?>(16) { null } internal val sidechainInputs = ArrayList<Pair<TerrarumAudioMixerTrack, TrackVolume>?>()
internal fun getSidechains(): List<TerrarumAudioMixerTrack?> = sidechainInputs.map { it?.first } internal fun getSidechains(): List<TerrarumAudioMixerTrack?> = sidechainInputs.map { it?.first }
fun addSidechainInput(input: TerrarumAudioMixerTrack, inputVolume: TrackVolume) { fun addSidechainInput(input: TerrarumAudioMixerTrack, inputVolume: TrackVolume) {
if (input.isMaster) if (input.trackType == TrackType.MASTER)
throw IllegalArgumentException("Cannot add master track as a sidechain") throw IllegalArgumentException("Cannot add master track as a sidechain")
if (sidechainInputs.map { it?.first }.any { it?.hash == input.hash }) 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") throw IllegalArgumentException("The track '${input.hash}' contains current track (${this.hash}) as its sidechain")
val emptySpot = sidechainInputs.indexOf(null)
if (emptySpot != -1) { sidechainInputs.add(input to inputVolume)
sidechainInputs[emptySpot] = (input to inputVolume)
}
else {
throw IllegalStateException("Sidechain is full (${sidechainInputs.size})!")
}
} }
@@ -87,7 +86,7 @@ class TerrarumAudioMixerTrack(val name: String, val isMaster: Boolean = false, v
it.get(Gdx.audio) as Int it.get(Gdx.audio) as Int
} }
internal val adev: OpenALBufferedAudioDevice? = internal val adev: OpenALBufferedAudioDevice? =
if (isMaster) { if (trackType == TrackType.MASTER) {
OpenALBufferedAudioDevice( OpenALBufferedAudioDevice(
Gdx.audio as OpenALLwjgl3Audio, Gdx.audio as OpenALLwjgl3Audio,
SAMPLING_RATE, SAMPLING_RATE,

View File

@@ -28,7 +28,7 @@ class UISoundControlPanel(remoCon: UIRemoCon?) : UICanvas() {
arrayOf("", { "" }, "pp"), arrayOf("", { "" }, "pp"),
arrayOf("guivolume", { Lang["MENU_LABEL_INTERFACE"] }, "sliderd,0,1"), arrayOf("guivolume", { Lang["MENU_LABEL_INTERFACE"] }, "sliderd,0,1"),
arrayOf("", { Lang["MENU_LABEL_HARDWARE"] }, "h1"), 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_RESTART_REQUIRED"]})" }, "p"),
arrayOf("", { "${Lang["MENU_LABEL_AUDIO_BUFFER_INSTRUCTION"]}" }, "p"), arrayOf("", { "${Lang["MENU_LABEL_AUDIO_BUFFER_INSTRUCTION"]}" }, "p"),

View File

@@ -13,6 +13,7 @@ import net.torvald.terrarum.Terrarum.mouseTileX
import net.torvald.terrarum.Terrarum.mouseTileY import net.torvald.terrarum.Terrarum.mouseTileY
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE
import net.torvald.terrarum.audio.* 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.TerrarumAudioMixerTrack.Companion.BUFFER_SIZE
import net.torvald.terrarum.audio.dsp.* import net.torvald.terrarum.audio.dsp.*
import net.torvald.terrarum.controller.TerrarumController 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) { private fun drawStrip(batch: SpriteBatch, x: Int, y: Int, track: TerrarumAudioMixerTrack, index: Int) {
// back // 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) Toolkit.fillArea(batch, x, y, stripW, stripH)
// strip/name separator // strip/name separator
@@ -485,21 +486,39 @@ class BasicDebugInfoWindow : UICanvas() {
val faderY = y + stripFilterHeight * numberOfFilters val faderY = y + stripFilterHeight * numberOfFilters
// receives (opposite of "sends") // receives (opposite of "sends")
track.sidechainInputs.filterNotNull().reversed().forEachIndexed { i, (side, mix) -> if (track != AudioMixer.sfxSumTrack) {
val mixDb = fullscaleToDecibels(mix) track.sidechainInputs.filterNotNull().reversed().forEachIndexed { i, (side, mix) ->
val perc = ((mixDb + 24.0).coerceAtLeast(0.0) / 24.0).toFloat() 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 // gauge background
batch.color = COL_METER_TROUGH 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 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 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 // label
batch.color = FILTER_NAME_ACTIVE batch.color = FILTER_NAME_ACTIVE
App.fontSmallNumbers.draw(batch, "\u00C0", x.toFloat(), faderY - (i+1)*16f + 1f) 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, "DS($dynamicSourceCount)", x + 10f, faderY - (i + 1) * 16f + 1f)
} }
// fader // fader