diff --git a/src/net/torvald/terrarum/App.java b/src/net/torvald/terrarum/App.java index cc7fdb40f..7c871e709 100644 --- a/src/net/torvald/terrarum/App.java +++ b/src/net/torvald/terrarum/App.java @@ -1341,6 +1341,7 @@ public class App implements ApplicationListener { public static String customDir; /** defaultDir + "/Custom/Music" */ public static String customMusicDir; + public static String customAmbientDir; private static void getDefaultDirectory() { String OS = OSName.toUpperCase(); @@ -1376,6 +1377,7 @@ public class App implements ApplicationListener { importDir = defaultDir + "/Imports"; customDir = defaultDir + "/Custom"; customMusicDir = customDir + "/Music"; + customAmbientDir = customDir + "/Ambient"; System.out.println(String.format("os.name = %s (with identifier %s)", OSName, operationSystem)); System.out.println(String.format("os.version = %s", OSVersion)); diff --git a/src/net/torvald/terrarum/MusicGovernor.kt b/src/net/torvald/terrarum/MusicGovernor.kt index 6b5211da9..78f30cc7b 100644 --- a/src/net/torvald/terrarum/MusicGovernor.kt +++ b/src/net/torvald/terrarum/MusicGovernor.kt @@ -6,7 +6,7 @@ open class MusicGovernor { } - protected var state = 0 // 0: disabled, 1: playing, 2: waiting + protected var musicState = 0 // 0: disabled, 1: playing, 2: waiting protected var intermissionAkku = 0f protected var intermissionLength = 1f protected var musicFired = false diff --git a/src/net/torvald/terrarum/Terrarum.kt b/src/net/torvald/terrarum/Terrarum.kt index d06127b6d..d9a2c3b65 100644 --- a/src/net/torvald/terrarum/Terrarum.kt +++ b/src/net/torvald/terrarum/Terrarum.kt @@ -959,5 +959,5 @@ fun distBetween(a: ActorWithBody, bpos: Vector2): Double { val dist = min(min(bpos.distanceSquared(apos1), bpos.distanceSquared(apos2)), bpos.distanceSquared(apos3)) return dist.sqrt() } - -fun getHashStr(length: Int = 5) = (0 until length).map { "YBNDRFG8EJKMCPQXOTLVWIS2A345H769"[Math.random().times(32).toInt()] }.joinToString("") +const val hashStrMap = "YBNDRFG8EJKMCPQXOTLVWIS2A345H769" +fun getHashStr(length: Int = 5) = (0 until length).map { hashStrMap[Math.random().times(32).toInt()] }.joinToString("") diff --git a/src/net/torvald/terrarum/audio/AudioMixer.kt b/src/net/torvald/terrarum/audio/AudioMixer.kt index dafb66c76..f147bd1c8 100644 --- a/src/net/torvald/terrarum/audio/AudioMixer.kt +++ b/src/net/torvald/terrarum/audio/AudioMixer.kt @@ -15,6 +15,7 @@ import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RAT import net.torvald.terrarum.modulebasegame.MusicContainer import net.torvald.terrarum.tryDispose import java.lang.Thread.MAX_PRIORITY +import java.util.* import kotlin.math.* /** @@ -47,10 +48,18 @@ object AudioMixer: Disposable { else if (it == 2) "SFX" else if (it == 3) "GUI" else if (it == 4) "BUS1" - else "Trk${it+1}", isBus = (it == 4) + else "Trk${it+1}", isBus = (it == 4), maxVolumeFun = { + when (it) { + 0 -> { musicVolume } + 1 -> { ambientVolume } + 2 -> { sfxVolume } + 3 -> { guiVolume } + else -> { 1.0 } + } + } ) } - val masterTrack = TerrarumAudioMixerTrack("Master", true) + val masterTrack = TerrarumAudioMixerTrack("Master", true) { masterVolume } val musicTrack: TerrarumAudioMixerTrack get() = tracks[0] @@ -109,12 +118,21 @@ object AudioMixer: Disposable { } - private var fadeAkku = 0.0 - private var fadeLength = DEFAULT_FADEOUT_LEN - private var fadeoutFired = false - private var fadeinFired = false - private var fadeTarget = 0.0 - private var fadeStart = 0.0 + data class FadeRequest( + var fadeAkku: Double = 0.0, + var fadeLength: Double = DEFAULT_FADEOUT_LEN, + var fadeoutFired: Boolean = false, + var fadeinFired: Boolean = false, + var fadeTarget: Double = 0.0, + var fadeStart: Double = 0.0, + ) + + private val fadeReqs = HashMap().also { map -> + listOf(musicTrack, ambientTrack, sfxMixTrack, guiTrack, fadeBus).forEach { + map[it] = FadeRequest() + } + } + private val fadeReqsCol = fadeReqs.entries private var lpAkku = 0.0 private var lpLength = 0.4 @@ -138,34 +156,31 @@ object AudioMixer: Disposable { // process fades - if (fadeoutFired) { - fadeAkku += delta - val step = fadeAkku / fadeLength - fadeBus.volume = FastMath.interpolateLinear(step, fadeStart, fadeTarget) + fadeReqsCol.forEach { val track = it.key; val req = it.value + if (req.fadeoutFired) { + req.fadeAkku += delta + val step = req.fadeAkku / req.fadeLength + track.volume = FastMath.interpolateLinear(step, req.fadeStart, req.fadeTarget) - if (fadeAkku >= fadeLength) { - fadeoutFired = false - fadeBus.volume = fadeTarget - fadeBus.volume = fadeTarget + if (req.fadeAkku >= req.fadeLength) { + req.fadeoutFired = false + track.volume = req.fadeTarget + track.volume = req.fadeTarget - if (fadeTarget == 0.0) { - musicTrack.currentTrack = null - ambientTrack.currentTrack = null + if (req.fadeTarget == 0.0) { + track.currentTrack = null + } } } - } - else if (fadeinFired) { - fadeAkku += delta - val step = fadeAkku / fadeLength - fadeBus.volume = FastMath.interpolateLinear(step, fadeStart, fadeTarget) + else if (req.fadeinFired) { + req.fadeAkku += delta + val step = req.fadeAkku / req.fadeLength + track.volume = FastMath.interpolateLinear(step, req.fadeStart, req.fadeTarget) -// if (musicTrack.isPlaying == false) { -// musicTrack.play() -// } - - if (fadeAkku >= fadeLength) { - fadeBus.volume = fadeTarget - fadeinFired = false + if (req.fadeAkku >= req.fadeLength) { + track.volume = req.fadeTarget + req.fadeinFired = false + } } } @@ -211,32 +226,45 @@ object AudioMixer: Disposable { fun startMusic(song: MusicContainer) { if (musicTrack.isPlaying == true) { - requestFadeOut(DEFAULT_FADEOUT_LEN) + requestFadeOut(musicTrack, DEFAULT_FADEOUT_LEN) } musicTrack.nextTrack = song } fun stopMusic() { - requestFadeOut(DEFAULT_FADEOUT_LEN) + requestFadeOut(musicTrack, DEFAULT_FADEOUT_LEN) } - 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 = fadeBus.volume + fun startAmb(song: MusicContainer) { + if (ambientTrack.isPlaying == true) { + requestFadeOut(musicTrack, DEFAULT_FADEOUT_LEN) + } + ambientTrack.nextTrack = song + } + + fun stopAmb() { + requestFadeOut(musicTrack, DEFAULT_FADEOUT_LEN) + } + + fun requestFadeOut(track: TerrarumAudioMixerTrack, length: Double, target: Double = 0.0) { + val req = fadeReqs[track]!! + if (!req.fadeoutFired) { + req.fadeLength = length.coerceAtLeast(1.0/1024.0) + req.fadeAkku = 0.0 + req.fadeoutFired = true + req.fadeTarget = target * track.maxVolume + req.fadeStart = fadeBus.volume } } - 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 = fadeBus.volume + fun requestFadeIn(track: TerrarumAudioMixerTrack, length: Double, target: Double = 1.0) { + val req = fadeReqs[track]!! + if (!req.fadeinFired) { + req.fadeLength = length.coerceAtLeast(1.0/1024.0) + req.fadeAkku = 0.0 + req.fadeinFired = true + req.fadeTarget = target * track.maxVolume + req.fadeStart = fadeBus.volume } } diff --git a/src/net/torvald/terrarum/audio/TerrarumAudioMixerTrack.kt b/src/net/torvald/terrarum/audio/TerrarumAudioMixerTrack.kt index b54d878c6..16fb7379d 100644 --- a/src/net/torvald/terrarum/audio/TerrarumAudioMixerTrack.kt +++ b/src/net/torvald/terrarum/audio/TerrarumAudioMixerTrack.kt @@ -6,6 +6,7 @@ import com.badlogic.gdx.utils.Disposable import com.badlogic.gdx.utils.Queue import net.torvald.reflection.forceInvoke import net.torvald.terrarum.getHashStr +import net.torvald.terrarum.hashStrMap import net.torvald.terrarum.modulebasegame.MusicContainer import java.lang.Thread.MAX_PRIORITY import kotlin.math.log10 @@ -13,7 +14,7 @@ import kotlin.math.pow typealias TrackVolume = Double -class TerrarumAudioMixerTrack(val name: String, val isMaster: Boolean = false, val isBus: Boolean = false): Disposable { +class TerrarumAudioMixerTrack(val name: String, val isMaster: Boolean = false, val isBus: Boolean = false, private val maxVolumeFun: () -> Double): Disposable { companion object { const val SAMPLING_RATE = 48000 @@ -26,6 +27,9 @@ class TerrarumAudioMixerTrack(val name: String, val isMaster: Boolean = false, v } val hash = getHashStr() + private val hashCode0 = hash.map { hashStrMap.indexOf(it) }.foldIndexed(0) { i, acc, c -> + acc or (c shl (5*i)) + } var currentTrack: MusicContainer? = null var nextTrack: MusicContainer? = null @@ -37,6 +41,9 @@ class TerrarumAudioMixerTrack(val name: String, val isMaster: Boolean = false, v currentTrack?.gdxMusic?.volume = volume.toFloat() } + val maxVolume: Double + get() = maxVolumeFun() + var pan = 0.0 var dBfs: Double @@ -152,6 +159,7 @@ class TerrarumAudioMixerTrack(val name: String, val isMaster: Boolean = false, v }*/ } + override fun hashCode() = hashCode0 } fun fullscaleToDecibels(fs: Double) = 20.0 * log10(fs) diff --git a/src/net/torvald/terrarum/modulebasegame/TerrarumMusicGovernor.kt b/src/net/torvald/terrarum/modulebasegame/TerrarumMusicGovernor.kt index 5fb453eab..15edf7519 100644 --- a/src/net/torvald/terrarum/modulebasegame/TerrarumMusicGovernor.kt +++ b/src/net/torvald/terrarum/modulebasegame/TerrarumMusicGovernor.kt @@ -31,9 +31,8 @@ class TerrarumMusicGovernor : MusicGovernor() { MusicContainer( it.nameWithoutExtension.replace('_', ' ').split(" ").map { it.capitalize() }.joinToString(" "), it, - Gdx.audio.newMusic(Gdx.files.absolute(it.absolutePath)), - { stopMusic() } - ) + Gdx.audio.newMusic(Gdx.files.absolute(it.absolutePath)) + ) { stopMusic() } } catch (e: GdxRuntimeException) { e.printStackTrace() @@ -43,6 +42,25 @@ class TerrarumMusicGovernor : MusicGovernor() { private var musicBin: ArrayList = ArrayList(songs.indices.toList().shuffled()) + private val ambients: List = + File(App.customAmbientDir).listFiles()?.mapNotNull { + printdbg(this, "Ambient: ${it.absolutePath}") + try { + MusicContainer( + it.nameWithoutExtension.replace('_', ' ').split(" ").map { it.capitalize() }.joinToString(" "), + it, + Gdx.audio.newMusic(Gdx.files.absolute(it.absolutePath)) + ) { stopMusic() } + } + catch (e: GdxRuntimeException) { + e.printStackTrace() + null + } + } ?: emptyList() // TODO test code + + private var ambientsBin: ArrayList = ArrayList(ambients.indices.toList().shuffled()) + + init { songs.forEach { @@ -59,10 +77,11 @@ class TerrarumMusicGovernor : MusicGovernor() { private val STATE_PLAYING = 2 private val STATE_INTERMISSION = 3 + protected var ambState = 0 + protected var ambFired = false private fun stopMusic() { -// AudioManager.stopMusic() // music will stop itself; with this line not commented, the stop-callback from the already disposed musicgovernor will stop the music queued by the new musicgovernor instance - state = STATE_INTERMISSION + musicState = STATE_INTERMISSION intermissionAkku = 0f intermissionLength = 30f + 30f * Math.random().toFloat() // 30s-60s musicFired = false @@ -73,7 +92,23 @@ class TerrarumMusicGovernor : MusicGovernor() { AudioMixer.startMusic(song) printdbg(this, "Now playing: $song") INGAME.sendNotification("Now Playing $EMDASH ${song.name}") - state = STATE_PLAYING + musicState = STATE_PLAYING + } + + + private fun stopAmbient() { + ambState = STATE_INTERMISSION + intermissionAkku = 0f + intermissionLength = 30f + 30f * Math.random().toFloat() // 30s-60s + ambFired = false + printdbg(this, "Intermission: $intermissionLength seconds") + } + + private fun startAmbient(song: MusicContainer) { + AudioMixer.startAmb(song) + printdbg(this, "Now playing: $song") + INGAME.sendNotification("Now Playing $EMDASH ${song.name}") + ambState = STATE_PLAYING } @@ -87,10 +122,10 @@ class TerrarumMusicGovernor : MusicGovernor() { } // val ingame = ingame as TerrarumIngame - if (state == 0) state = STATE_INTERMISSION + if (musicState == 0) musicState = STATE_INTERMISSION - when (state) { + when (musicState) { STATE_FIREPLAY -> { if (!musicFired) { musicFired = true @@ -112,15 +147,39 @@ class TerrarumMusicGovernor : MusicGovernor() { if (intermissionAkku >= intermissionLength) { intermissionAkku = 0f - state = 1 + musicState = STATE_FIREPLAY } } } + when (ambState) { + STATE_FIREPLAY -> { + if (!ambFired) { + ambFired = true + + val song = ambients[ambientsBin.removeAt(0)] + // prevent same song to play twice + if (ambientsBin.isEmpty()) { + ambientsBin = ArrayList(ambients.indices.toList().shuffled()) + } + + startAmbient(song) + } + } + STATE_PLAYING -> { + // stopMusic() will be called when the music finishes; it's on the setOnCompletionListener + } + STATE_INTERMISSION -> { + ambState = STATE_FIREPLAY + } + } + + } override fun dispose() { - AudioMixer.stopMusic() // explicit call for fade-out when the game instance quits + AudioMixer.requestFadeOut(AudioMixer.fadeBus, AudioMixer.DEFAULT_FADEOUT_LEN) // explicit call for fade-out when the game instance quits stopMusic() + stopAmbient() } } diff --git a/src/net/torvald/terrarum/modulebasegame/ui/UIInventoryFull.kt b/src/net/torvald/terrarum/modulebasegame/ui/UIInventoryFull.kt index e6e745787..0d1918bcf 100644 --- a/src/net/torvald/terrarum/modulebasegame/ui/UIInventoryFull.kt +++ b/src/net/torvald/terrarum/modulebasegame/ui/UIInventoryFull.kt @@ -366,7 +366,7 @@ class UIInventoryFull( INGAME.setTooltipMessage(null) AudioMixer.requestLowpassIn(0.25) - AudioMixer.requestFadeOut(0.25, 0.5) + AudioMixer.requestFadeOut(AudioMixer.fadeBus, 0.25, 0.5) } override fun doClosing(delta: Float) { @@ -376,7 +376,7 @@ class UIInventoryFull( INGAME.setTooltipMessage(null) AudioMixer.requestLowpassOut(0.25) - AudioMixer.requestFadeIn(0.25, 1.0) + AudioMixer.requestFadeIn(AudioMixer.fadeBus, 0.25, 1.0) } override fun endOpening(delta: Float) {