individual fadein/out req for tracks

This commit is contained in:
minjaesong
2023-11-20 21:08:16 +09:00
parent 91a24cae55
commit 6fabe555df
7 changed files with 160 additions and 63 deletions

View File

@@ -1341,6 +1341,7 @@ public class App implements ApplicationListener {
public static String customDir; public static String customDir;
/** defaultDir + "/Custom/Music" */ /** defaultDir + "/Custom/Music" */
public static String customMusicDir; public static String customMusicDir;
public static String customAmbientDir;
private static void getDefaultDirectory() { private static void getDefaultDirectory() {
String OS = OSName.toUpperCase(); String OS = OSName.toUpperCase();
@@ -1376,6 +1377,7 @@ public class App implements ApplicationListener {
importDir = defaultDir + "/Imports"; importDir = defaultDir + "/Imports";
customDir = defaultDir + "/Custom"; customDir = defaultDir + "/Custom";
customMusicDir = customDir + "/Music"; 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.name = %s (with identifier %s)", OSName, operationSystem));
System.out.println(String.format("os.version = %s", OSVersion)); System.out.println(String.format("os.version = %s", OSVersion));

View File

@@ -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 intermissionAkku = 0f
protected var intermissionLength = 1f protected var intermissionLength = 1f
protected var musicFired = false protected var musicFired = false

View File

@@ -959,5 +959,5 @@ fun distBetween(a: ActorWithBody, bpos: Vector2): Double {
val dist = min(min(bpos.distanceSquared(apos1), bpos.distanceSquared(apos2)), bpos.distanceSquared(apos3)) val dist = min(min(bpos.distanceSquared(apos1), bpos.distanceSquared(apos2)), bpos.distanceSquared(apos3))
return dist.sqrt() return dist.sqrt()
} }
const val hashStrMap = "YBNDRFG8EJKMCPQXOTLVWIS2A345H769"
fun getHashStr(length: Int = 5) = (0 until length).map { "YBNDRFG8EJKMCPQXOTLVWIS2A345H769"[Math.random().times(32).toInt()] }.joinToString("") fun getHashStr(length: Int = 5) = (0 until length).map { hashStrMap[Math.random().times(32).toInt()] }.joinToString("")

View File

@@ -15,6 +15,7 @@ import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RAT
import net.torvald.terrarum.modulebasegame.MusicContainer import net.torvald.terrarum.modulebasegame.MusicContainer
import net.torvald.terrarum.tryDispose import net.torvald.terrarum.tryDispose
import java.lang.Thread.MAX_PRIORITY import java.lang.Thread.MAX_PRIORITY
import java.util.*
import kotlin.math.* import kotlin.math.*
/** /**
@@ -47,10 +48,18 @@ object AudioMixer: Disposable {
else if (it == 2) "SFX" else if (it == 2) "SFX"
else if (it == 3) "GUI" else if (it == 3) "GUI"
else if (it == 4) "BUS1" 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 val musicTrack: TerrarumAudioMixerTrack
get() = tracks[0] get() = tracks[0]
@@ -109,12 +118,21 @@ object AudioMixer: Disposable {
} }
private var fadeAkku = 0.0 data class FadeRequest(
private var fadeLength = DEFAULT_FADEOUT_LEN var fadeAkku: Double = 0.0,
private var fadeoutFired = false var fadeLength: Double = DEFAULT_FADEOUT_LEN,
private var fadeinFired = false var fadeoutFired: Boolean = false,
private var fadeTarget = 0.0 var fadeinFired: Boolean = false,
private var fadeStart = 0.0 var fadeTarget: Double = 0.0,
var fadeStart: Double = 0.0,
)
private val fadeReqs = HashMap<TerrarumAudioMixerTrack, FadeRequest>().also { map ->
listOf(musicTrack, ambientTrack, sfxMixTrack, guiTrack, fadeBus).forEach {
map[it] = FadeRequest()
}
}
private val fadeReqsCol = fadeReqs.entries
private var lpAkku = 0.0 private var lpAkku = 0.0
private var lpLength = 0.4 private var lpLength = 0.4
@@ -138,34 +156,31 @@ object AudioMixer: Disposable {
// process fades // process fades
if (fadeoutFired) { fadeReqsCol.forEach { val track = it.key; val req = it.value
fadeAkku += delta if (req.fadeoutFired) {
val step = fadeAkku / fadeLength req.fadeAkku += delta
fadeBus.volume = FastMath.interpolateLinear(step, fadeStart, fadeTarget) val step = req.fadeAkku / req.fadeLength
track.volume = FastMath.interpolateLinear(step, req.fadeStart, req.fadeTarget)
if (fadeAkku >= fadeLength) { if (req.fadeAkku >= req.fadeLength) {
fadeoutFired = false req.fadeoutFired = false
fadeBus.volume = fadeTarget track.volume = req.fadeTarget
fadeBus.volume = fadeTarget track.volume = req.fadeTarget
if (fadeTarget == 0.0) { if (req.fadeTarget == 0.0) {
musicTrack.currentTrack = null track.currentTrack = null
ambientTrack.currentTrack = null }
} }
} }
} else if (req.fadeinFired) {
else if (fadeinFired) { req.fadeAkku += delta
fadeAkku += delta val step = req.fadeAkku / req.fadeLength
val step = fadeAkku / fadeLength track.volume = FastMath.interpolateLinear(step, req.fadeStart, req.fadeTarget)
fadeBus.volume = FastMath.interpolateLinear(step, fadeStart, fadeTarget)
// if (musicTrack.isPlaying == false) { if (req.fadeAkku >= req.fadeLength) {
// musicTrack.play() track.volume = req.fadeTarget
// } req.fadeinFired = false
}
if (fadeAkku >= fadeLength) {
fadeBus.volume = fadeTarget
fadeinFired = false
} }
} }
@@ -211,32 +226,45 @@ object AudioMixer: Disposable {
fun startMusic(song: MusicContainer) { fun startMusic(song: MusicContainer) {
if (musicTrack.isPlaying == true) { if (musicTrack.isPlaying == true) {
requestFadeOut(DEFAULT_FADEOUT_LEN) requestFadeOut(musicTrack, DEFAULT_FADEOUT_LEN)
} }
musicTrack.nextTrack = song musicTrack.nextTrack = song
} }
fun stopMusic() { fun stopMusic() {
requestFadeOut(DEFAULT_FADEOUT_LEN) requestFadeOut(musicTrack, DEFAULT_FADEOUT_LEN)
} }
fun requestFadeOut(length: Double, target: Double = 0.0) { fun startAmb(song: MusicContainer) {
if (!fadeoutFired) { if (ambientTrack.isPlaying == true) {
fadeLength = length.coerceAtLeast(1.0/1024.0) requestFadeOut(musicTrack, DEFAULT_FADEOUT_LEN)
fadeAkku = 0.0 }
fadeoutFired = true ambientTrack.nextTrack = song
fadeTarget = target }
fadeStart = fadeBus.volume
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) { fun requestFadeIn(track: TerrarumAudioMixerTrack, length: Double, target: Double = 1.0) {
if (!fadeinFired) { val req = fadeReqs[track]!!
fadeLength = length.coerceAtLeast(1.0/1024.0) if (!req.fadeinFired) {
fadeAkku = 0.0 req.fadeLength = length.coerceAtLeast(1.0/1024.0)
fadeinFired = true req.fadeAkku = 0.0
fadeTarget = target req.fadeinFired = true
fadeStart = fadeBus.volume req.fadeTarget = target * track.maxVolume
req.fadeStart = fadeBus.volume
} }
} }

View File

@@ -6,6 +6,7 @@ import com.badlogic.gdx.utils.Disposable
import com.badlogic.gdx.utils.Queue import com.badlogic.gdx.utils.Queue
import net.torvald.reflection.forceInvoke import net.torvald.reflection.forceInvoke
import net.torvald.terrarum.getHashStr import net.torvald.terrarum.getHashStr
import net.torvald.terrarum.hashStrMap
import net.torvald.terrarum.modulebasegame.MusicContainer import net.torvald.terrarum.modulebasegame.MusicContainer
import java.lang.Thread.MAX_PRIORITY import java.lang.Thread.MAX_PRIORITY
import kotlin.math.log10 import kotlin.math.log10
@@ -13,7 +14,7 @@ import kotlin.math.pow
typealias TrackVolume = Double 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 { companion object {
const val SAMPLING_RATE = 48000 const val SAMPLING_RATE = 48000
@@ -26,6 +27,9 @@ class TerrarumAudioMixerTrack(val name: String, val isMaster: Boolean = false, v
} }
val hash = getHashStr() 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 currentTrack: MusicContainer? = null
var nextTrack: 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() currentTrack?.gdxMusic?.volume = volume.toFloat()
} }
val maxVolume: Double
get() = maxVolumeFun()
var pan = 0.0 var pan = 0.0
var dBfs: Double 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) fun fullscaleToDecibels(fs: Double) = 20.0 * log10(fs)

View File

@@ -31,9 +31,8 @@ class TerrarumMusicGovernor : MusicGovernor() {
MusicContainer( MusicContainer(
it.nameWithoutExtension.replace('_', ' ').split(" ").map { it.capitalize() }.joinToString(" "), it.nameWithoutExtension.replace('_', ' ').split(" ").map { it.capitalize() }.joinToString(" "),
it, it,
Gdx.audio.newMusic(Gdx.files.absolute(it.absolutePath)), Gdx.audio.newMusic(Gdx.files.absolute(it.absolutePath))
{ stopMusic() } ) { stopMusic() }
)
} }
catch (e: GdxRuntimeException) { catch (e: GdxRuntimeException) {
e.printStackTrace() e.printStackTrace()
@@ -43,6 +42,25 @@ class TerrarumMusicGovernor : MusicGovernor() {
private var musicBin: ArrayList<Int> = ArrayList(songs.indices.toList().shuffled()) private var musicBin: ArrayList<Int> = ArrayList(songs.indices.toList().shuffled())
private val ambients: List<MusicContainer> =
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<Int> = ArrayList(ambients.indices.toList().shuffled())
init { init {
songs.forEach { songs.forEach {
@@ -59,10 +77,11 @@ class TerrarumMusicGovernor : MusicGovernor() {
private val STATE_PLAYING = 2 private val STATE_PLAYING = 2
private val STATE_INTERMISSION = 3 private val STATE_INTERMISSION = 3
protected var ambState = 0
protected var ambFired = false
private fun stopMusic() { 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 musicState = STATE_INTERMISSION
state = STATE_INTERMISSION
intermissionAkku = 0f intermissionAkku = 0f
intermissionLength = 30f + 30f * Math.random().toFloat() // 30s-60s intermissionLength = 30f + 30f * Math.random().toFloat() // 30s-60s
musicFired = false musicFired = false
@@ -73,7 +92,23 @@ class TerrarumMusicGovernor : MusicGovernor() {
AudioMixer.startMusic(song) AudioMixer.startMusic(song)
printdbg(this, "Now playing: $song") printdbg(this, "Now playing: $song")
INGAME.sendNotification("Now Playing $EMDASH ${song.name}") 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 // val ingame = ingame as TerrarumIngame
if (state == 0) state = STATE_INTERMISSION if (musicState == 0) musicState = STATE_INTERMISSION
when (state) { when (musicState) {
STATE_FIREPLAY -> { STATE_FIREPLAY -> {
if (!musicFired) { if (!musicFired) {
musicFired = true musicFired = true
@@ -112,15 +147,39 @@ class TerrarumMusicGovernor : MusicGovernor() {
if (intermissionAkku >= intermissionLength) { if (intermissionAkku >= intermissionLength) {
intermissionAkku = 0f 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() { 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() stopMusic()
stopAmbient()
} }
} }

View File

@@ -366,7 +366,7 @@ class UIInventoryFull(
INGAME.setTooltipMessage(null) INGAME.setTooltipMessage(null)
AudioMixer.requestLowpassIn(0.25) AudioMixer.requestLowpassIn(0.25)
AudioMixer.requestFadeOut(0.25, 0.5) AudioMixer.requestFadeOut(AudioMixer.fadeBus, 0.25, 0.5)
} }
override fun doClosing(delta: Float) { override fun doClosing(delta: Float) {
@@ -376,7 +376,7 @@ class UIInventoryFull(
INGAME.setTooltipMessage(null) INGAME.setTooltipMessage(null)
AudioMixer.requestLowpassOut(0.25) AudioMixer.requestLowpassOut(0.25)
AudioMixer.requestFadeIn(0.25, 1.0) AudioMixer.requestFadeIn(AudioMixer.fadeBus, 0.25, 1.0)
} }
override fun endOpening(delta: Float) { override fun endOpening(delta: Float) {