mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-07 12:21:52 +09:00
mixer: room for dynamic sources
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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<TerrarumAudioMixerTrack, FadeRequest>().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>().gain = musicVolume.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()
|
||||
|
||||
|
||||
|
||||
@@ -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<Int>("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 <T> Queue<T>.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()
|
||||
|
||||
@@ -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 <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 }
|
||||
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,
|
||||
|
||||
@@ -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"),
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user