mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-07 12:21:52 +09:00
transaction-based music playback managing wip
This commit is contained in:
8
.idea/kotlinc.xml
generated
8
.idea/kotlinc.xml
generated
@@ -1,13 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Kotlin2JvmCompilerArguments">
|
||||
<option name="jvmTarget" value="17" />
|
||||
<option name="jvmTarget" value="21" />
|
||||
</component>
|
||||
<component name="KotlinCommonCompilerArguments">
|
||||
<option name="apiVersion" value="1.8" />
|
||||
<option name="languageVersion" value="1.8" />
|
||||
<option name="apiVersion" value="1.9" />
|
||||
<option name="languageVersion" value="1.9" />
|
||||
</component>
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="1.8.0" />
|
||||
<option name="version" value="1.9.23-release-779" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,17 +1,16 @@
|
||||
package net.torvald.terrarum.musicplayer
|
||||
|
||||
import net.torvald.terrarum.IngameInstance
|
||||
import net.torvald.terrarum.ModMgr
|
||||
import net.torvald.terrarum.ModuleEntryPoint
|
||||
import net.torvald.terrarum.modulebasegame.TerrarumIngame
|
||||
import net.torvald.terrarum.musicplayer.gui.MusicPlayer
|
||||
import net.torvald.terrarum.musicplayer.gui.MusicPlayerControl
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2023-12-23.
|
||||
*/
|
||||
class EntryPoint : ModuleEntryPoint() {
|
||||
override fun invoke() {
|
||||
ModMgr.GameExtraGuiLoader.register { ingame: TerrarumIngame -> MusicPlayer(ingame) }
|
||||
ModMgr.GameExtraGuiLoader.register { ingame: TerrarumIngame -> MusicPlayerControl(ingame) }
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
|
||||
@@ -10,7 +10,6 @@ import com.badlogic.gdx.utils.JsonValue
|
||||
import com.jme3.math.FastMath
|
||||
import net.torvald.reflection.extortField
|
||||
import net.torvald.terrarum.*
|
||||
import net.torvald.terrarum.App.printdbg
|
||||
import net.torvald.terrarum.audio.*
|
||||
import net.torvald.terrarum.audio.audiobank.MusicContainer
|
||||
import net.torvald.terrarum.gameworld.fmod
|
||||
@@ -33,7 +32,7 @@ import kotlin.math.*
|
||||
*
|
||||
* Created by minjaesong on 2023-12-23.
|
||||
*/
|
||||
class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() {
|
||||
class MusicPlayerControl(private val ingame: TerrarumIngame) : UICanvas() {
|
||||
|
||||
private val STRIP_W = 9f
|
||||
private val METERS_WIDTH = 2 * STRIP_W
|
||||
@@ -96,20 +95,21 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() {
|
||||
|
||||
/** Returns the internal playlist of the MusicGovernor */
|
||||
private val songsInGovernor: List<MusicContainer>
|
||||
get() = ingame.musicGovernor.extortField<List<MusicContainer>>("songs")!!
|
||||
get() = ingame.backgroundMusicPlayer.extortField<List<MusicContainer>>("songs")!!
|
||||
|
||||
private val shouldPlayerBeDisabled: Boolean
|
||||
/*private val shouldPlayerBeDisabled: Boolean
|
||||
get() {
|
||||
return App.audioMixer.dynamicTracks.any { it.isPlaying && it.trackingTarget is PlaysMusic }
|
||||
}
|
||||
return MusicService.transactionLocked
|
||||
//return App.audioMixer.dynamicTracks.any { it.isPlaying && it.trackingTarget is PlaysMusic }
|
||||
}*/
|
||||
|
||||
/** Returns the playlist name from the MusicGovernor. Getting the value from the MusicGovernor
|
||||
* is recommended as an ingame interaction may cancel the playback from the playlist from the MusicPlayer
|
||||
* (e.g. interacting with a jukebox) */
|
||||
private val internalPlaylistName: String
|
||||
get() = ingame.musicGovernor.playlistName
|
||||
get() = ingame.backgroundMusicPlayer.playlistName
|
||||
|
||||
fun registerPlaylist(path: String, fileToName: JsonValue?, shuffled: Boolean, diskJockeyingMode: String) {
|
||||
fun registerPlaylist(path: String, fileToName: JsonValue?, shuffled: Boolean, diskJockeyingMode: String): TerrarumMusicPlaylist {
|
||||
fun String.isNum(): Boolean {
|
||||
try {
|
||||
this.toInt()
|
||||
@@ -120,7 +120,7 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() {
|
||||
}
|
||||
}
|
||||
|
||||
ingame.musicGovernor.queueDirectory(path, shuffled, diskJockeyingMode) { filename ->
|
||||
val playlist = ingame.backgroundMusicPlayer.queueDirectory(path, shuffled, diskJockeyingMode, false) { filename ->
|
||||
fileToName?.get(filename).let {
|
||||
if (it == null)
|
||||
filename.substringBeforeLast('.').replace('_', ' ').split(" ").map { it.capitalize() }.let {
|
||||
@@ -136,19 +136,21 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() {
|
||||
}
|
||||
}
|
||||
|
||||
ingame.musicGovernor.addMusicStartHook { music ->
|
||||
ingame.backgroundMusicPlayer.addMusicStartHook { music ->
|
||||
setMusicName(music.name)
|
||||
if (mode <= MODE_PLAYING)
|
||||
transitionRequest = MODE_PLAYING
|
||||
}
|
||||
|
||||
ingame.musicGovernor.addMusicStopHook { music ->
|
||||
ingame.backgroundMusicPlayer.addMusicStopHook { music ->
|
||||
setIntermission()
|
||||
if (mode <= MODE_PLAYING)
|
||||
transitionRequest = MODE_IDLE
|
||||
}
|
||||
|
||||
setPlaylistDisplayVars(songsInGovernor)
|
||||
|
||||
return playlist
|
||||
}
|
||||
|
||||
private var currentMusicName = ""
|
||||
@@ -186,7 +188,7 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() {
|
||||
private var mouseOnList: Int? = null
|
||||
|
||||
override fun updateImpl(delta: Float) {
|
||||
val shouldPlayerBeDisabled = shouldPlayerBeDisabled
|
||||
val transactionLocked = MusicService.transactionLocked
|
||||
|
||||
// process transition request
|
||||
if (transitionRequest != null) {
|
||||
@@ -345,18 +347,12 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() {
|
||||
1 -> { // prev
|
||||
// prev song
|
||||
if (mode < MODE_SHOW_LIST) {
|
||||
getPrevSongFromPlaylist()?.let { ingame.musicGovernor.unshiftPlaylist(it) }
|
||||
if (!shouldPlayerBeDisabled) {
|
||||
App.audioMixer.requestFadeOut(
|
||||
App.audioMixer.musicTrack,
|
||||
AudioMixer.DEFAULT_FADEOUT_LEN / 3f
|
||||
) {
|
||||
ingame.musicGovernor.startMusic(this) // required for "intermittent" mode
|
||||
MusicService.playPrevSongInPlaylist {
|
||||
ingame.backgroundMusicPlayer.startMusic(this) // required for "intermittent" mode
|
||||
iHitTheStopButton = false
|
||||
stopRequested = false
|
||||
}
|
||||
}
|
||||
}
|
||||
// prev page in the playlist
|
||||
else if (listViewPanelScroll == 1f) {
|
||||
val scrollMax = ((currentlySelectedAlbum?.length
|
||||
@@ -372,36 +368,44 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() {
|
||||
|
||||
2 -> { // stop
|
||||
if (mode < MODE_SHOW_LIST) { // disable stop button entirely on MODE_SHOW_LIST
|
||||
// when the button is STOP
|
||||
if (App.audioMixer.musicTrack.isPlaying) {
|
||||
val thisMusic = App.audioMixer.musicTrack.currentTrack
|
||||
// FIXME the olde way -- must be replaced with one that utilises MusicService
|
||||
/*val thisMusic = App.audioMixer.musicTrack.currentTrack
|
||||
App.audioMixer.requestFadeOut(App.audioMixer.musicTrack, AudioMixer.DEFAULT_FADEOUT_LEN / 3f)
|
||||
App.audioMixer.musicTrack.nextTrack = null
|
||||
ingame.musicGovernor.stopMusic(this)
|
||||
if (thisMusic is MusicContainer) thisMusic.let { ingame.musicGovernor.queueMusicToPlayNext(it) }
|
||||
ingame.backgroundMusicPlayer.stopMusic(this)
|
||||
if (thisMusic is MusicContainer) thisMusic.let { ingame.backgroundMusicPlayer.queueMusicToPlayNext(it) }
|
||||
iHitTheStopButton = true*/
|
||||
|
||||
MusicService.stopPlaylistPlayback {
|
||||
iHitTheStopButton = true
|
||||
}
|
||||
else if (!shouldPlayerBeDisabled) {
|
||||
ingame.musicGovernor.startMusic(this)
|
||||
}
|
||||
// when the button is PLAY
|
||||
else if (!App.audioMixer.musicTrack.isPlaying) {
|
||||
// FIXME the olde way -- must be replaced with one that utilises MusicService
|
||||
/*ingame.backgroundMusicPlayer.startMusic(this)
|
||||
iHitTheStopButton = false
|
||||
stopRequested = false*/
|
||||
|
||||
MusicService.resumePlaylistPlayback {
|
||||
iHitTheStopButton = false
|
||||
stopRequested = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3 -> { // next
|
||||
// next song
|
||||
if (mode < MODE_SHOW_LIST) {
|
||||
if (!shouldPlayerBeDisabled) {
|
||||
App.audioMixer.requestFadeOut(
|
||||
App.audioMixer.musicTrack,
|
||||
AudioMixer.DEFAULT_FADEOUT_LEN / 3f
|
||||
) {
|
||||
ingame.musicGovernor.startMusic(this) // required for "intermittent" mode, does seemingly nothing on "continuous" mode
|
||||
MusicService.playNextSongInPlaylist {
|
||||
ingame.backgroundMusicPlayer.startMusic(this) // required for "intermittent" mode
|
||||
iHitTheStopButton = false
|
||||
stopRequested = false
|
||||
}
|
||||
}
|
||||
}
|
||||
// next page in the playlist
|
||||
else if (listViewPanelScroll == 1f) {
|
||||
val scrollMax = ((currentlySelectedAlbum?.length
|
||||
@@ -435,28 +439,34 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() {
|
||||
}
|
||||
}
|
||||
}
|
||||
// make playlist clicking work
|
||||
// make playlist clicking (change song within the playlist) work
|
||||
else if (listViewPanelScroll == 1f && mouseOnList != null) {
|
||||
val index = playlistScroll + mouseOnList!!
|
||||
val list = songsInGovernor
|
||||
if (index < list.size) {
|
||||
// if selected song != currently playing
|
||||
if (App.audioMixer.musicTrack.currentTrack == null || list[index] != App.audioMixer.musicTrack.currentTrack) {
|
||||
// FIXME the olde way -- must be replaced with one that utilises MusicService
|
||||
// rebuild playlist
|
||||
ingame.musicGovernor.queueIndexFromPlaylist(index)
|
||||
|
||||
//ingame.backgroundMusicPlayer.queueIndexFromPlaylist(index)
|
||||
// fade out
|
||||
App.audioMixer.requestFadeOut(App.audioMixer.musicTrack, AudioMixer.DEFAULT_FADEOUT_LEN / 3f) {
|
||||
/*App.audioMixer.requestFadeOut(App.audioMixer.musicTrack, AudioMixer.DEFAULT_FADEOUT_LEN / 3f) {
|
||||
if (!shouldPlayerBeDisabled) {
|
||||
ingame.musicGovernor.startMusic(this) // required for "intermittent" mode
|
||||
ingame.backgroundMusicPlayer.startMusic(this) // required for "intermittent" mode
|
||||
iHitTheStopButton = false
|
||||
stopRequested = false
|
||||
}
|
||||
}*/
|
||||
|
||||
MusicService.playNthSongInPlaylist(index) {
|
||||
ingame.backgroundMusicPlayer.startMusic(this) // required for "intermittent" mode
|
||||
iHitTheStopButton = false
|
||||
stopRequested = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// make album list clicking work
|
||||
// make album list clicking (send new playlist to the MusicService) work
|
||||
else if (listViewPanelScroll == 0f && mouseOnList != null) {
|
||||
val index = albumlistScroll + mouseOnList!!
|
||||
val list = albumsList//.map { albumPropCache[it] }
|
||||
@@ -464,16 +474,23 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() {
|
||||
if (index < list.size) {
|
||||
// if selected album is not the same album currently playing, queue that album immediately
|
||||
// (navigating into the selected album involves too much complication :p)
|
||||
if (ingame.musicGovernor.playlistSource != albumsList[index].canonicalPath) {
|
||||
if (ingame.backgroundMusicPlayer.playlistSource != albumsList[index].canonicalPath) {
|
||||
// FIXME the olde way -- must be replaced with one that utilises MusicService
|
||||
// fade out
|
||||
App.audioMixer.requestFadeOut(App.audioMixer.musicTrack, AudioMixer.DEFAULT_FADEOUT_LEN / 3f) {
|
||||
/*App.audioMixer.requestFadeOut(App.audioMixer.musicTrack, AudioMixer.DEFAULT_FADEOUT_LEN / 3f) {
|
||||
loadNewAlbum(albumsList[index])
|
||||
if (!shouldPlayerBeDisabled) {
|
||||
ingame.musicGovernor.startMusic(this) // required for "intermittent" mode
|
||||
ingame.backgroundMusicPlayer.startMusic(this) // required for "intermittent" mode
|
||||
iHitTheStopButton = false
|
||||
stopRequested = false
|
||||
}
|
||||
resetPlaylistScroll(App.audioMixer.musicTrack.nextTrack as? MusicContainer)
|
||||
}*/
|
||||
|
||||
val playlist = loadNewAlbum(albumsList[index])
|
||||
MusicService.putNewPlaylist(playlist) {
|
||||
resetPlaylistScroll(App.audioMixer.musicTrack.nextTrack as? MusicContainer)
|
||||
App.audioMixer.startMusic(playlist.getCurrent())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -487,16 +504,16 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() {
|
||||
|
||||
// printdbg(this, "mode = $mode; req = $transitionRequest")
|
||||
|
||||
if (shouldPlayerBeDisabled || iHitTheStopButton) {
|
||||
/*if (shouldPlayerBeDisabled || iHitTheStopButton) {
|
||||
if (!stopRequested) {
|
||||
stopRequested = true
|
||||
ingame.musicGovernor.stopMusic(this)
|
||||
ingame.backgroundMusicPlayer.stopMusic(this)
|
||||
}
|
||||
}
|
||||
else if (ingame.musicGovernor.playCaller is PlaysMusic && !jukeboxStopMonitorAlert && !App.audioMixer.musicTrack.isPlaying) {
|
||||
else*/ if (ingame.backgroundMusicPlayer.playCaller is PlaysMusic && !jukeboxStopMonitorAlert && !App.audioMixer.musicTrack.isPlaying) {
|
||||
jukeboxStopMonitorAlert = true
|
||||
val interval = ingame.musicGovernor.getRandomMusicInterval()
|
||||
ingame.musicGovernor.stopMusic(this, false, interval)
|
||||
val interval = ingame.backgroundMusicPlayer.getRandomMusicInterval()
|
||||
ingame.backgroundMusicPlayer.stopMusic(this, false, interval)
|
||||
}
|
||||
else if (App.audioMixer.musicTrack.isPlaying) {
|
||||
jukeboxStopMonitorAlert = false
|
||||
@@ -508,7 +525,7 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() {
|
||||
private var stopRequested = false
|
||||
|
||||
private fun resetAlbumlistScroll() {
|
||||
val currentlyPlaying = albumsList.indexOfFirst { it.canonicalPath.replace('\\', '/') == ingame.musicGovernor.playlistSource }
|
||||
val currentlyPlaying = albumsList.indexOfFirst { it.canonicalPath.replace('\\', '/') == ingame.backgroundMusicPlayer.playlistSource }
|
||||
if (currentlyPlaying >= 0) {
|
||||
albumlistScroll = (currentlyPlaying / PLAYLIST_LINES) * PLAYLIST_LINES
|
||||
}
|
||||
@@ -527,7 +544,7 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPrevSongFromPlaylist(): MusicContainer? {
|
||||
/*private fun getPrevSongFromPlaylist(): MusicContainer? {
|
||||
val list = songsInGovernor.slice(songsInGovernor.indices) // make copy of the list
|
||||
val nowPlaying = App.audioMixer.musicTrack.currentTrack ?: return null
|
||||
|
||||
@@ -537,7 +554,7 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() {
|
||||
|
||||
val prevIndex = (currentIndex - 1).fmod(list.size)
|
||||
return list[prevIndex]
|
||||
}
|
||||
}*/
|
||||
|
||||
// private fun smoothstep(x: Float) = (x*x*(3f-2f*x)).coerceIn(0f, 1f)
|
||||
// private fun smootherstep(x: Float) = (x*x*x*(x*(6f*x-15f)+10f)).coerceIn(0f, 1f)
|
||||
@@ -919,7 +936,7 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() {
|
||||
val pnum = i + albumlistScroll
|
||||
|
||||
val currentlyPlaying = if (pnum in albumsList.indices) {
|
||||
val m1 = ingame.musicGovernor.playlistSource
|
||||
val m1 = ingame.backgroundMusicPlayer.playlistSource
|
||||
val m2 = albumsList[pnum].canonicalPath.replace('\\', '/')
|
||||
(m1 == m2)
|
||||
}
|
||||
@@ -1279,14 +1296,14 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() {
|
||||
val albumArt: TextureRegion? = null
|
||||
)
|
||||
|
||||
private fun loadNewAlbum(albumDir: File) {
|
||||
private fun loadNewAlbum(albumDir: File): TerrarumMusicPlaylist {
|
||||
val albumProp = albumPropCache[albumDir]
|
||||
|
||||
App.audioMixer.musicTrack.let { track ->
|
||||
track.doGaplessPlayback = (albumProp.diskJockeyingMode == "continuous")
|
||||
if (track.doGaplessPlayback) {
|
||||
track.pullNextTrack = {
|
||||
track.currentTrack = ingame.musicGovernor.pullNextMusicTrack(true)
|
||||
track.currentTrack = ingame.backgroundMusicPlayer.pullNextMusicTrack(true)
|
||||
setMusicName(track.currentTrack?.name ?: "")
|
||||
}
|
||||
}
|
||||
@@ -1294,7 +1311,7 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() {
|
||||
|
||||
currentlySelectedAlbum = albumProp
|
||||
|
||||
registerPlaylist(albumDir.absolutePath, albumProp.fileToName, albumProp.shuffled, albumProp.diskJockeyingMode)
|
||||
return registerPlaylist(albumDir.absolutePath, albumProp.fileToName, albumProp.shuffled, albumProp.diskJockeyingMode)
|
||||
|
||||
// scroll playlist to the page current song is
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package net.torvald.terrarum
|
||||
|
||||
open class MusicGovernor {
|
||||
open class BackgroundMusicPlayer {
|
||||
|
||||
open fun update(ingameInstance: IngameInstance, delta: Float) {
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
package net.torvald.terrarum
|
||||
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.Input
|
||||
import com.badlogic.gdx.utils.Disposable
|
||||
import net.torvald.terrarum.App.printdbg
|
||||
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE
|
||||
import net.torvald.terrarum.audio.audiobank.MusicContainer
|
||||
import net.torvald.terrarum.audio.dsp.BinoPan
|
||||
import net.torvald.terrarum.gameactors.Actor
|
||||
import net.torvald.terrarum.gameactors.ActorID
|
||||
import net.torvald.terrarum.gameactors.ActorWithBody
|
||||
@@ -37,7 +33,6 @@ import java.nio.file.Files
|
||||
import java.nio.file.StandardCopyOption
|
||||
import java.util.*
|
||||
import java.util.concurrent.locks.Lock
|
||||
import java.util.function.Consumer
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
@@ -586,7 +581,7 @@ open class IngameInstance(val batch: FlippingSpriteBatch, val isMultiplayer: Boo
|
||||
noticelet.sendNotification(itemID, itemCount)
|
||||
}
|
||||
|
||||
open val musicGovernor: MusicGovernor = MusicGovernor()
|
||||
open val backgroundMusicPlayer: BackgroundMusicPlayer = BackgroundMusicPlayer()
|
||||
}
|
||||
|
||||
inline fun Lock.lock(body: () -> Unit) {
|
||||
|
||||
259
src/net/torvald/terrarum/MusicService.kt
Normal file
259
src/net/torvald/terrarum/MusicService.kt
Normal file
@@ -0,0 +1,259 @@
|
||||
package net.torvald.terrarum
|
||||
|
||||
import net.torvald.terrarum.audio.AudioMixer.Companion.DEFAULT_FADEOUT_LEN
|
||||
import net.torvald.terrarum.transaction.Transaction
|
||||
import net.torvald.terrarum.transaction.TransactionListener
|
||||
import net.torvald.terrarum.transaction.TransactionState
|
||||
|
||||
/**
|
||||
* To play the music, create a transaction then pass it to the `runTransaction(Transaction)`
|
||||
*
|
||||
* Created by minjaesong on 2024-06-28.
|
||||
*/
|
||||
object MusicService : TransactionListener() {
|
||||
|
||||
private var currentPlaylist: TerrarumMusicPlaylist? = null
|
||||
|
||||
override fun getCurrentStatusForTransaction(): TransactionState {
|
||||
return TransactionState(
|
||||
mutableMapOf(
|
||||
"currentPlaylist" to currentPlaylist
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun commitTransaction(state: TransactionState) {
|
||||
this.currentPlaylist = state["currentPlaylist"] as TerrarumMusicPlaylist?
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the given playlist to this object if the transaction successes. If the given playlist is same as the
|
||||
* current playlist, the transaction will successfully finish immediately; otherwise the given playlist will
|
||||
* be reset as soon as the transaction starts. Note that the resetting behaviour is NOT atomic. (the given
|
||||
* playlist will stay in reset state even if the transaction fails)
|
||||
*
|
||||
* The old playlist will be disposed of if and only if the transaction was successful.
|
||||
*
|
||||
* @param playlist An instance of a [TerrarumMusicPlaylist] to be changed into
|
||||
* @param onSuccess What to do after the transaction. Default behaviour is: `App.audioMixer.startMusic(playlist.getCurrent())`
|
||||
*/
|
||||
private fun createTransactionPlaylistChange(playlist: TerrarumMusicPlaylist, onSuccess: () -> Unit = {
|
||||
App.audioMixer.startMusic(playlist.getCurrent())
|
||||
}): Transaction {
|
||||
return object : Transaction {
|
||||
var oldPlaylist: TerrarumMusicPlaylist? = null
|
||||
|
||||
override fun start(state: TransactionState) {
|
||||
oldPlaylist = state["currentPlaylist"] as TerrarumMusicPlaylist?
|
||||
|
||||
if (oldPlaylist == playlist) return
|
||||
|
||||
playlist.reset()
|
||||
|
||||
// request fadeout
|
||||
if (App.audioMixer.musicTrack.isPlaying) {
|
||||
var fadedOut = false
|
||||
|
||||
App.audioMixer.requestFadeOut(App.audioMixer.musicTrack) {
|
||||
// put new playlist
|
||||
state["currentPlaylist"] = playlist
|
||||
|
||||
fadedOut = true
|
||||
}
|
||||
|
||||
waitUntil { fadedOut }
|
||||
}
|
||||
else {
|
||||
/* do nothing */
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSuccess(state: TransactionState) {
|
||||
oldPlaylist?.dispose()
|
||||
onSuccess()
|
||||
}
|
||||
override fun onFailure(e: Throwable, state: TransactionState) {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createTransactionForNextMusicInPlaylist(onSuccess: () -> Unit): Transaction {
|
||||
return object : Transaction {
|
||||
override fun start(state: TransactionState) {
|
||||
var fadedOut = false
|
||||
// request fadeout
|
||||
App.audioMixer.requestFadeOut(App.audioMixer.musicTrack) {
|
||||
// callback: play next song in the playlist
|
||||
App.audioMixer.startMusic((state["currentPlaylist"] as TerrarumMusicPlaylist).getNext())
|
||||
fadedOut = true
|
||||
}
|
||||
|
||||
waitUntil { fadedOut }
|
||||
}
|
||||
|
||||
override fun onSuccess(state: TransactionState) {
|
||||
onSuccess()
|
||||
}
|
||||
override fun onFailure(e: Throwable, state: TransactionState) {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createTransactionForPrevMusicInPlaylist(onSuccess: () -> Unit): Transaction {
|
||||
return object : Transaction {
|
||||
override fun start(state: TransactionState) {
|
||||
var fadedOut = false
|
||||
// request fadeout
|
||||
App.audioMixer.requestFadeOut(App.audioMixer.musicTrack) {
|
||||
// callback: play prev song in the playlist
|
||||
App.audioMixer.startMusic((state["currentPlaylist"] as TerrarumMusicPlaylist).getPrev())
|
||||
fadedOut = true
|
||||
}
|
||||
|
||||
waitUntil { fadedOut }
|
||||
}
|
||||
|
||||
override fun onSuccess(state: TransactionState) {
|
||||
onSuccess()
|
||||
}
|
||||
override fun onFailure(e: Throwable, state: TransactionState) {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createTransactionForNthMusicInPlaylist(index: Int, onSuccess: () -> Unit): Transaction {
|
||||
return object : Transaction {
|
||||
override fun start(state: TransactionState) {
|
||||
var fadedOut = false
|
||||
// request fadeout
|
||||
App.audioMixer.requestFadeOut(App.audioMixer.musicTrack) {
|
||||
// callback: play prev song in the playlist
|
||||
App.audioMixer.startMusic((state["currentPlaylist"] as TerrarumMusicPlaylist).getNthSong(index))
|
||||
fadedOut = true
|
||||
}
|
||||
|
||||
waitUntil { fadedOut }
|
||||
}
|
||||
|
||||
override fun onSuccess(state: TransactionState) { onSuccess() }
|
||||
override fun onFailure(e: Throwable, state: TransactionState) {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createTransactionForPlaylistStop(onSuccess: () -> Unit): Transaction {
|
||||
return object : Transaction {
|
||||
override fun start(state: TransactionState) {
|
||||
var fadedOut = false
|
||||
// request fadeout
|
||||
App.audioMixer.requestFadeOut(App.audioMixer.musicTrack) {
|
||||
fadedOut = true
|
||||
}
|
||||
|
||||
waitUntil { fadedOut }
|
||||
}
|
||||
|
||||
override fun onSuccess(state: TransactionState) { onSuccess() }
|
||||
override fun onFailure(e: Throwable, state: TransactionState) {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createTransactionForPlaylistResume(onSuccess: () -> Unit): Transaction {
|
||||
return object : Transaction {
|
||||
override fun start(state: TransactionState) {
|
||||
App.audioMixer.startMusic((state["currentPlaylist"] as TerrarumMusicPlaylist).getCurrent())
|
||||
}
|
||||
|
||||
override fun onSuccess(state: TransactionState) { onSuccess() }
|
||||
override fun onFailure(e: Throwable, state: TransactionState) {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createTransactionPausePlaylistForMusicalFixture(
|
||||
action: () -> Unit,
|
||||
musicFinished: () -> Boolean,
|
||||
onSuccess: () -> Unit,
|
||||
onFailure: (Throwable) -> Unit
|
||||
): Transaction {
|
||||
return object : Transaction {
|
||||
override fun start(state: TransactionState) {
|
||||
var fadedOut = false
|
||||
// request fadeout
|
||||
App.audioMixer.requestFadeOut(App.audioMixer.musicTrack, DEFAULT_FADEOUT_LEN / 2.0) {
|
||||
// callback: let the caller actually take care of playing the audio
|
||||
action()
|
||||
|
||||
fadedOut = true
|
||||
}
|
||||
|
||||
waitUntil { fadedOut }
|
||||
|
||||
// wait until the interjected music finishes
|
||||
waitUntil { musicFinished() }
|
||||
}
|
||||
|
||||
override fun onSuccess(state: TransactionState) { onSuccess() }
|
||||
override fun onFailure(e: Throwable, state: TransactionState) { onFailure(e) }
|
||||
}
|
||||
|
||||
// note to self: wait() and notify() using a lock object is impractical as the Java thread can wake up
|
||||
// randomly regardless of the notify(), which results in the common pattern of
|
||||
// while (!condition) { lock.wait() }
|
||||
// and if we need extra condition (i.e. musicFinished()), it's just a needlessly elaborate way of spinning,
|
||||
// UNLESS THE THING MUST BE SYNCHRONISED WITH SOMETHING
|
||||
}
|
||||
|
||||
private fun waitUntil(escapeCondition: () -> Boolean) {
|
||||
while (!escapeCondition()) {
|
||||
Thread.sleep(4L)
|
||||
}
|
||||
}
|
||||
|
||||
fun putNewPlaylist(playlist: TerrarumMusicPlaylist, onSuccess: (() -> Unit)? = null) {
|
||||
if (onSuccess != null)
|
||||
runTransaction(createTransactionPlaylistChange(playlist, onSuccess))
|
||||
else
|
||||
runTransaction(createTransactionPlaylistChange(playlist))
|
||||
}
|
||||
fun putNewPlaylist(playlist: TerrarumMusicPlaylist, onSuccess: () -> Unit, onFinally: () -> Unit) {
|
||||
runTransaction(createTransactionPlaylistChange(playlist, onSuccess), onFinally)
|
||||
}
|
||||
|
||||
fun playMusicalFixture(action: () -> Unit, musicFinished: () -> Boolean, onSuccess: () -> Unit, onFailure: (Throwable) -> Unit) {
|
||||
runTransaction(createTransactionPausePlaylistForMusicalFixture(action, musicFinished, onSuccess, onFailure))
|
||||
}
|
||||
fun playMusicalFixture(action: () -> Unit, musicFinished: () -> Boolean, onSuccess: () -> Unit, onFailure: (Throwable) -> Unit, onFinally: () -> Unit = {}) {
|
||||
runTransaction(createTransactionPausePlaylistForMusicalFixture(action, musicFinished, onSuccess, onFailure), onFinally)
|
||||
}
|
||||
|
||||
fun playNextSongInPlaylist(onSuccess: () -> Unit) {
|
||||
runTransaction(createTransactionForNextMusicInPlaylist(onSuccess))
|
||||
}
|
||||
fun playNextSongInPlaylist(onSuccess: () -> Unit, onFinally: () -> Unit) {
|
||||
runTransaction(createTransactionForNextMusicInPlaylist(onSuccess), onFinally)
|
||||
}
|
||||
|
||||
fun playPrevSongInPlaylist(onSuccess: () -> Unit) {
|
||||
runTransaction(createTransactionForPrevMusicInPlaylist(onSuccess))
|
||||
}
|
||||
fun playPrevSongInPlaylist(onSuccess: () -> Unit, onFinally: () -> Unit) {
|
||||
runTransaction(createTransactionForPrevMusicInPlaylist(onSuccess), onFinally)
|
||||
}
|
||||
|
||||
fun playNthSongInPlaylist(index: Int, onSuccess: () -> Unit) {
|
||||
runTransaction(createTransactionForNthMusicInPlaylist(index, onSuccess))
|
||||
}
|
||||
fun playNthSongInPlaylist(index: Int, onSuccess: () -> Unit, onFinally: () -> Unit) {
|
||||
runTransaction(createTransactionForNthMusicInPlaylist(index, onSuccess), onFinally)
|
||||
}
|
||||
|
||||
fun stopPlaylistPlayback(onSuccess: () -> Unit) {
|
||||
runTransaction(createTransactionForPlaylistStop(onSuccess))
|
||||
}
|
||||
fun stopPlaylistPlayback(onSuccess: () -> Unit, onFinally: () -> Unit) {
|
||||
runTransaction(createTransactionForPlaylistStop(onSuccess), onFinally)
|
||||
}
|
||||
|
||||
fun resumePlaylistPlayback(onSuccess: () -> Unit) {
|
||||
runTransaction(createTransactionForPlaylistResume(onSuccess))
|
||||
}
|
||||
fun resumePlaylistPlayback(onSuccess: () -> Unit, onFinally: () -> Unit) {
|
||||
runTransaction(createTransactionForPlaylistResume(onSuccess), onFinally)
|
||||
}
|
||||
}
|
||||
142
src/net/torvald/terrarum/TerrarumMusicPlaylist.kt
Normal file
142
src/net/torvald/terrarum/TerrarumMusicPlaylist.kt
Normal file
@@ -0,0 +1,142 @@
|
||||
package net.torvald.terrarum
|
||||
|
||||
import com.badlogic.gdx.utils.Disposable
|
||||
import com.badlogic.gdx.utils.GdxRuntimeException
|
||||
import net.torvald.terrarum.App.printdbg
|
||||
import net.torvald.terrarum.audio.audiobank.MusicContainer
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* The `musicList` never (read: should not) gets changed, only the `internalIndices` are being changed as
|
||||
* the songs are being played.
|
||||
*
|
||||
* Created by minjaesong on 2024-06-29.
|
||||
*/
|
||||
class TerrarumMusicPlaylist(
|
||||
/** list of files */
|
||||
val musicList: List<MusicContainer>,
|
||||
/** name of the album/playlist shown in the [net.torvald.terrarum.musicplayer.gui.MusicPlayer] */
|
||||
val name: String,
|
||||
/** canonicalPath with path separators converted to forward slash */
|
||||
val source: String,
|
||||
/** "continuous", "intermittent"; not used by the Playlist itself but by the BackgroundMusicPlayer (aka you are the one who make it actually work) */
|
||||
val diskJockeyingMode: String,
|
||||
/** if set, the `internalIndices` will be shuffled accordingly, and this happens automatically. (aka you don't need to worry about) */
|
||||
val shuffled: Boolean
|
||||
): Disposable {
|
||||
|
||||
private val internalIndices = ArrayList<Int>()
|
||||
private var currentIndexCursor = musicList.size
|
||||
|
||||
init {
|
||||
reset()
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
internalIndices.clear()
|
||||
refillInternalIndices()
|
||||
refillInternalIndices()
|
||||
currentIndexCursor = musicList.size
|
||||
}
|
||||
|
||||
private fun checkRefill() {
|
||||
if (internalIndices.size < currentIndexCursor + 1)
|
||||
refillInternalIndices()
|
||||
}
|
||||
|
||||
fun getCurrent(): MusicContainer {
|
||||
checkRefill()
|
||||
|
||||
return musicList[currentIndexCursor]
|
||||
}
|
||||
|
||||
fun getNext(): MusicContainer {
|
||||
checkRefill()
|
||||
currentIndexCursor += 1
|
||||
|
||||
return musicList[currentIndexCursor]
|
||||
}
|
||||
|
||||
fun getPrev(): MusicContainer {
|
||||
if (currentIndexCursor == 0) {
|
||||
if (shuffled) {
|
||||
musicList.indices.toMutableList().also { if (shuffled) it.shuffle() }.reversed().forEach {
|
||||
internalIndices.add(0, it)
|
||||
}
|
||||
currentIndexCursor += musicList.size
|
||||
}
|
||||
else {
|
||||
musicList.indices.reversed().forEach {
|
||||
internalIndices.add(0, it)
|
||||
}
|
||||
currentIndexCursor += musicList.size
|
||||
}
|
||||
}
|
||||
|
||||
currentIndexCursor -= 1
|
||||
|
||||
return musicList[currentIndexCursor]
|
||||
}
|
||||
|
||||
|
||||
private fun refillInternalIndices() {
|
||||
internalIndices.addAll(musicList.indices.toMutableList().also { if (shuffled) it.shuffle() })
|
||||
}
|
||||
|
||||
inline fun getNthSong(n: Int) = musicList[n]
|
||||
|
||||
override fun dispose() {
|
||||
musicList.forEach {
|
||||
it.tryDispose()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Adding songFinishedHook to the songs is a responsibility of the caller.
|
||||
*/
|
||||
fun fromDirectory(musicDir: String, shuffled: Boolean, diskJockeyingMode: String, fileToName: ((String) -> String) = { name: String ->
|
||||
name.substringBeforeLast('.').replace('_', ' ').split(" ").map { it.capitalize() }.joinToString(" ")
|
||||
}): TerrarumMusicPlaylist {
|
||||
val musicDir = musicDir.replace('\\', '/')
|
||||
val playlistSource = musicDir
|
||||
printdbg(this, "registerSongsFromDir $musicDir")
|
||||
|
||||
val playlistName = musicDir.substringAfterLast('/')
|
||||
|
||||
val playlist = File(musicDir).listFiles()?.sortedBy { it.name }?.mapNotNull {
|
||||
printdbg(this, "Music: ${it.absolutePath}")
|
||||
try {
|
||||
MusicContainer(
|
||||
fileToName(it.name),
|
||||
it
|
||||
)/*.also { muscon ->
|
||||
|
||||
printdbg(this, "MusicTitle: ${muscon.name}")
|
||||
|
||||
muscon.songFinishedHook = {
|
||||
if (App.audioMixer.musicTrack.currentTrack == it) {
|
||||
stopMusic(this, true, getRandomMusicInterval())
|
||||
}
|
||||
}
|
||||
}*/
|
||||
// adding songFinishedHook must be done by the caller
|
||||
}
|
||||
catch (e: GdxRuntimeException) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
} ?: emptyList() // TODO test code
|
||||
|
||||
return TerrarumMusicPlaylist(
|
||||
playlist,
|
||||
playlistName,
|
||||
musicDir,
|
||||
diskJockeyingMode,
|
||||
shuffled
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -529,7 +529,11 @@ class AudioMixer : Disposable {
|
||||
// fade will be processed by the update()
|
||||
}
|
||||
|
||||
fun requestFadeOut(track: TerrarumAudioMixerTrack, length: Double = DEFAULT_FADEOUT_LEN, target: Double = 0.0, source: Double? = null, jobAfterFadeout: () -> Unit = {}) {
|
||||
/**
|
||||
* Preferably, audio apps should NOT call this function directly to change music, [MusicService] must be used
|
||||
* to control the music playback instead.
|
||||
*/
|
||||
internal fun requestFadeOut(track: TerrarumAudioMixerTrack, length: Double = DEFAULT_FADEOUT_LEN, target: Double = 0.0, source: Double? = null, jobAfterFadeout: () -> Unit = {}) {
|
||||
val req = fadeReqs[track]!!
|
||||
if (!req.fadeoutFired) {
|
||||
req.fadeLength = length.coerceAtLeast(1.0/1024.0)
|
||||
@@ -541,7 +545,11 @@ class AudioMixer : Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
fun requestFadeIn(track: TerrarumAudioMixerTrack, length: Double, target: Double = 1.0, source: Double? = null, jobAfterFadeout: () -> Unit = {}) {
|
||||
/**
|
||||
* Preferably, audio apps should NOT call this function directly to change music, [MusicService] must be used
|
||||
* to control the music playback instead.
|
||||
*/
|
||||
internal fun requestFadeIn(track: TerrarumAudioMixerTrack, length: Double, target: Double = 1.0, source: Double? = null, jobAfterFadeout: () -> Unit = {}) {
|
||||
// printdbg(this, "fadein called by")
|
||||
// printStackTrace(this)
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ class BuildingMaker(batch: FlippingSpriteBatch) : IngameInstance(batch) {
|
||||
|
||||
lateinit var gameWorld: GameWorld
|
||||
|
||||
override val musicGovernor = TerrarumMusicGovernor()
|
||||
override val backgroundMusicPlayer = TerrarumBackgroundMusicPlayer()
|
||||
|
||||
init {
|
||||
gameUpdateGovernor = ConsistentUpdateRate
|
||||
@@ -396,7 +396,7 @@ class BuildingMaker(batch: FlippingSpriteBatch) : IngameInstance(batch) {
|
||||
}
|
||||
|
||||
|
||||
musicGovernor.update(this, delta)
|
||||
backgroundMusicPlayer.update(this, delta)
|
||||
}
|
||||
|
||||
|
||||
@@ -495,7 +495,7 @@ class BuildingMaker(batch: FlippingSpriteBatch) : IngameInstance(batch) {
|
||||
// blockMarkings.dispose()
|
||||
uiPenMenu.dispose()
|
||||
uiGetPoiName.dispose()
|
||||
musicGovernor.dispose()
|
||||
backgroundMusicPlayer.dispose()
|
||||
}
|
||||
|
||||
fun getPoiNameForExport(w: Int, h: Int, callback: (String) -> Unit) {
|
||||
|
||||
@@ -11,7 +11,7 @@ import net.torvald.terrarum.audio.audiobank.MusicContainer
|
||||
import net.torvald.terrarum.gameworld.WorldTime.Companion.DAY_LENGTH
|
||||
import java.io.File
|
||||
|
||||
class TerrarumMusicGovernor : MusicGovernor() {
|
||||
class TerrarumBackgroundMusicPlayer : BackgroundMusicPlayer() {
|
||||
private val STATE_INIT = 0
|
||||
private val STATE_FIREPLAY = 1
|
||||
private val STATE_PLAYING = 2
|
||||
@@ -22,7 +22,7 @@ class TerrarumMusicGovernor : MusicGovernor() {
|
||||
musicState = STATE_INTERMISSION
|
||||
}
|
||||
|
||||
private var songs: List<MusicContainer> = emptyList()
|
||||
private var playlist: List<MusicContainer> = emptyList()
|
||||
var playlistName = ""; private set
|
||||
/** canonicalPath with path separators converted to forward slash */
|
||||
var playlistSource = "" ; private set
|
||||
@@ -42,7 +42,7 @@ class TerrarumMusicGovernor : MusicGovernor() {
|
||||
|
||||
playlistName = musicDir.substringAfterLast('/')
|
||||
|
||||
songs = File(musicDir).listFiles()?.sortedBy { it.name }?.mapNotNull {
|
||||
playlist = File(musicDir).listFiles()?.sortedBy { it.name }?.mapNotNull {
|
||||
printdbg(this, "Music: ${it.absolutePath}")
|
||||
try {
|
||||
MusicContainer(
|
||||
@@ -67,77 +67,91 @@ class TerrarumMusicGovernor : MusicGovernor() {
|
||||
}
|
||||
|
||||
private fun restockMusicBin() {
|
||||
musicBin = ArrayList(if (shuffled) songs.shuffled() else songs.slice(songs.indices))
|
||||
musicBin = ArrayList(if (shuffled) playlist.shuffled() else playlist.slice(playlist.indices))
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the playlist to the MusicService to be played at the other play commands.
|
||||
*
|
||||
* @param musicDir where the music files are. Absolute path.
|
||||
* @param shuffled if the tracks are to be shuffled
|
||||
* @param diskJockeyingMode `intermittent` to give random gap between tracks, `continuous` for continuous playback
|
||||
*/
|
||||
fun queueDirectory(musicDir: String, shuffled: Boolean, diskJockeyingMode: String, fileToName: ((String) -> String)? = null) {
|
||||
if (musicState != STATE_INIT && musicState != STATE_INTERMISSION) {
|
||||
fun queueDirectory(musicDir: String, shuffled: Boolean, diskJockeyingMode: String, playImmediately: Boolean, fileToName: ((String) -> String) = { name: String ->
|
||||
name.substringBeforeLast('.').replace('_', ' ').split(" ").map { it.capitalize() }.joinToString(" ")
|
||||
}): TerrarumMusicPlaylist {
|
||||
// FIXME the olde way -- must be replaced with one that utilises MusicService
|
||||
/*if (musicState != STATE_INIT && musicState != STATE_INTERMISSION) {
|
||||
App.audioMixer.requestFadeOut(App.audioMixer.fadeBus, AudioMixer.DEFAULT_FADEOUT_LEN) // explicit call for fade-out when the game instance quits
|
||||
stopMusic0(App.audioMixer.musicTrack.currentTrack)
|
||||
}
|
||||
|
||||
songs.forEach { it.tryDispose() }
|
||||
playlist.forEach { it.tryDispose() }
|
||||
registerSongsFromDir(musicDir, fileToName)
|
||||
|
||||
this.shuffled = shuffled
|
||||
this.diskJockeyingMode = diskJockeyingMode
|
||||
|
||||
restockMusicBin()
|
||||
restockMusicBin()*/
|
||||
|
||||
val playlist = TerrarumMusicPlaylist.fromDirectory(musicDir, shuffled, diskJockeyingMode, fileToName)
|
||||
|
||||
if (!playImmediately)
|
||||
MusicService.putNewPlaylist(playlist) {}
|
||||
else
|
||||
MusicService.putNewPlaylist(playlist)
|
||||
|
||||
return playlist
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a song to the head of the internal playlist (`musicBin`)
|
||||
*/
|
||||
fun queueMusicToPlayNext(music: MusicContainer) {
|
||||
fun xxxqueueMusicToPlayNext(music: MusicContainer) {
|
||||
musicBin.add(0, music)
|
||||
}
|
||||
|
||||
/**
|
||||
* Unshifts an internal playlist (`musicBin`). The `music` argument must be the song that exists on the `songs`.
|
||||
*/
|
||||
fun unshiftPlaylist(music: MusicContainer) {
|
||||
val indexAtMusicBin = songs.indexOf(music)
|
||||
fun xxxunshiftPlaylist(music: MusicContainer) {
|
||||
val indexAtMusicBin = playlist.indexOf(music)
|
||||
if (indexAtMusicBin < 0) throw IllegalArgumentException("The music does not exist on the internal songs list ($music)")
|
||||
|
||||
// rewrite musicBin
|
||||
val newMusicBin = if (shuffled) songs.shuffled().toTypedArray().also {
|
||||
val newMusicBin = if (shuffled) playlist.shuffled().toTypedArray().also {
|
||||
// if shuffled,
|
||||
// 1. create a shuffled version of songlist
|
||||
// 2. swap two songs such that the songs[indexAtMusicBin] comes first
|
||||
val swapTo = it.indexOf(songs[indexAtMusicBin])
|
||||
val swapTo = it.indexOf(playlist[indexAtMusicBin])
|
||||
val tmp = it[swapTo]
|
||||
it[swapTo] = it[0]
|
||||
it[0] = tmp
|
||||
}
|
||||
else Array(songs.size - indexAtMusicBin) { offset ->
|
||||
else Array(playlist.size - indexAtMusicBin) { offset ->
|
||||
val k = offset + indexAtMusicBin
|
||||
songs[k]
|
||||
playlist[k]
|
||||
}
|
||||
|
||||
musicBin = ArrayList(newMusicBin.toList())
|
||||
}
|
||||
|
||||
fun queueIndexFromPlaylist(indexAtMusicBin: Int) {
|
||||
if (indexAtMusicBin !in songs.indices) throw IndexOutOfBoundsException("The index is outside of the internal songs list ($indexAtMusicBin/${songs.size})")
|
||||
fun xxxqueueIndexFromPlaylist(indexAtMusicBin: Int) {
|
||||
if (indexAtMusicBin !in playlist.indices) throw IndexOutOfBoundsException("The index is outside of the internal songs list ($indexAtMusicBin/${playlist.size})")
|
||||
|
||||
// rewrite musicBin
|
||||
val newMusicBin = if (shuffled) songs.shuffled().toTypedArray().also {
|
||||
val newMusicBin = if (shuffled) playlist.shuffled().toTypedArray().also {
|
||||
// if shuffled,
|
||||
// 1. create a shuffled version of songlist
|
||||
// 2. swap two songs such that the songs[indexAtMusicBin] comes first
|
||||
val swapTo = it.indexOf(songs[indexAtMusicBin])
|
||||
val swapTo = it.indexOf(playlist[indexAtMusicBin])
|
||||
val tmp = it[swapTo]
|
||||
it[swapTo] = it[0]
|
||||
it[0] = tmp
|
||||
}
|
||||
else Array(songs.size - indexAtMusicBin) { offset ->
|
||||
else Array(playlist.size - indexAtMusicBin) { offset ->
|
||||
val k = offset + indexAtMusicBin
|
||||
songs[k]
|
||||
playlist[k]
|
||||
}
|
||||
|
||||
musicBin = ArrayList(newMusicBin.toList())
|
||||
@@ -162,7 +176,7 @@ class TerrarumMusicGovernor : MusicGovernor() {
|
||||
private val musicStopHooks = ArrayList<(MusicContainer) -> Unit>()
|
||||
|
||||
init {
|
||||
queueDirectory(App.customMusicDir, true, "intermittent")
|
||||
queueDirectory(App.customMusicDir, true, "intermittent", true)
|
||||
}
|
||||
|
||||
|
||||
@@ -175,7 +189,7 @@ class TerrarumMusicGovernor : MusicGovernor() {
|
||||
}
|
||||
|
||||
init {
|
||||
songs.forEach {
|
||||
playlist.forEach {
|
||||
App.disposables.add(it)
|
||||
}
|
||||
ambients.forEach { (k, v) ->
|
||||
@@ -218,7 +232,7 @@ class TerrarumMusicGovernor : MusicGovernor() {
|
||||
val timeNow = System.currentTimeMillis()
|
||||
val trackThis = App.audioMixer.musicTrack.currentTrack
|
||||
|
||||
if (caller is TerrarumMusicGovernor) {
|
||||
if (caller is TerrarumBackgroundMusicPlayer) {
|
||||
if (stopCaller == null) {
|
||||
// printdbg(this, "Caller: this, prev caller: $stopCaller, len: $pauseLen, obliging stop request")
|
||||
stopMusic0(trackThis, callStopMusicHook, pauseLen)
|
||||
@@ -316,7 +330,7 @@ class TerrarumMusicGovernor : MusicGovernor() {
|
||||
// start the song queueing if there is one to play
|
||||
if (firstTime) {
|
||||
firstTime = false
|
||||
if (songs.isNotEmpty()) musicState = STATE_INTERMISSION
|
||||
if (playlist.isNotEmpty()) musicState = STATE_INTERMISSION
|
||||
if (ambients.isNotEmpty()) ambState = STATE_INTERMISSION
|
||||
}
|
||||
|
||||
@@ -334,7 +348,7 @@ class TerrarumMusicGovernor : MusicGovernor() {
|
||||
STATE_INTERMISSION -> {
|
||||
intermissionAkku += delta
|
||||
|
||||
if (intermissionAkku >= intermissionLength && songs.isNotEmpty()) {
|
||||
if (intermissionAkku >= intermissionLength && playlist.isNotEmpty()) {
|
||||
intermissionAkku = 0f
|
||||
musicState = STATE_FIREPLAY
|
||||
}
|
||||
@@ -262,7 +262,7 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
|
||||
override var gameFullyLoaded = false
|
||||
internal set
|
||||
|
||||
override val musicGovernor = TerrarumMusicGovernor()
|
||||
override val backgroundMusicPlayer = TerrarumBackgroundMusicPlayer()
|
||||
|
||||
|
||||
//////////////
|
||||
@@ -1006,7 +1006,7 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
|
||||
oldSelectedWireRenderClass = selectedWireRenderClass
|
||||
}
|
||||
|
||||
musicGovernor.update(this, delta)
|
||||
backgroundMusicPlayer.update(this, delta)
|
||||
|
||||
////////////////////////
|
||||
// ui-related updates //
|
||||
@@ -1795,7 +1795,7 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
|
||||
catch (e: IllegalArgumentException) {}
|
||||
}
|
||||
|
||||
musicGovernor.dispose()
|
||||
backgroundMusicPlayer.dispose()
|
||||
super.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameactors
|
||||
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
||||
import com.jme3.math.FastMath
|
||||
@@ -10,7 +9,6 @@ import net.torvald.terrarum.*
|
||||
import net.torvald.terrarum.App.printdbg
|
||||
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE
|
||||
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZED
|
||||
import net.torvald.terrarum.audio.AudioMixer.Companion.DEFAULT_FADEOUT_LEN
|
||||
import net.torvald.terrarum.audio.audiobank.MusicContainer
|
||||
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack
|
||||
import net.torvald.terrarum.audio.dsp.NullFilter
|
||||
@@ -21,7 +19,7 @@ import net.torvald.terrarum.gameactors.Hitbox
|
||||
import net.torvald.terrarum.gameactors.Lightbox
|
||||
import net.torvald.terrarum.gameitems.ItemID
|
||||
import net.torvald.terrarum.langpack.Lang
|
||||
import net.torvald.terrarum.modulebasegame.TerrarumMusicGovernor
|
||||
import net.torvald.terrarum.modulebasegame.TerrarumBackgroundMusicPlayer
|
||||
import net.torvald.terrarum.modulebasegame.gameitems.FixtureItemBase
|
||||
import net.torvald.terrarum.modulebasegame.gameitems.ItemFileRef
|
||||
import net.torvald.terrarum.modulebasegame.gameitems.MusicDiscHelper
|
||||
@@ -108,7 +106,7 @@ class FixtureJukebox : Electric, PlaysMusic {
|
||||
|
||||
// supress the normal background music playback
|
||||
if (musicIsPlaying && !flagDespawn) {
|
||||
(INGAME.musicGovernor as TerrarumMusicGovernor).stopMusic(this, true)
|
||||
(INGAME.backgroundMusicPlayer as TerrarumBackgroundMusicPlayer).stopMusic(this, true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,27 +125,49 @@ class FixtureJukebox : Electric, PlaysMusic {
|
||||
|
||||
printdbg(this, "Title: $title, artist: $artist")
|
||||
|
||||
musicNowPlaying = MusicContainer(title, musicFile.file()) {
|
||||
val returnToInitialState = {
|
||||
unloadEffector(musicNowPlaying)
|
||||
discCurrentlyPlaying = null
|
||||
musicNowPlaying?.tryDispose()
|
||||
musicNowPlaying = null
|
||||
|
||||
printdbg(this, "Stop music $title - $artist")
|
||||
|
||||
// can't call stopDiscPlayback() because of the recursion
|
||||
|
||||
(INGAME.musicGovernor as TerrarumMusicGovernor).stopMusic(this, pauseLen = (INGAME.musicGovernor as TerrarumMusicGovernor).getRandomMusicInterval())
|
||||
|
||||
backLamp.currentFrame = 0
|
||||
playMech.currentFrame = 0
|
||||
}
|
||||
|
||||
musicNowPlaying = MusicContainer(title, musicFile.file()) {
|
||||
printdbg(this, "Stop music $title - $artist")
|
||||
|
||||
// can't call stopDiscPlayback() because of the recursion
|
||||
(INGAME.backgroundMusicPlayer as TerrarumBackgroundMusicPlayer).stopMusic(this, pauseLen = (INGAME.backgroundMusicPlayer as TerrarumBackgroundMusicPlayer).getRandomMusicInterval())
|
||||
}
|
||||
|
||||
discCurrentlyPlaying = index
|
||||
|
||||
App.audioMixer.requestFadeOut(App.audioMixer.musicTrack, DEFAULT_FADEOUT_LEN / 2f) {
|
||||
// FIXME the olde way -- must be replaced with one that utilises MusicService
|
||||
/*App.audioMixer.requestFadeOut(App.audioMixer.musicTrack, DEFAULT_FADEOUT_LEN / 2f) {
|
||||
startAudio(musicNowPlaying!!) { loadEffector(it) }
|
||||
}
|
||||
}*/
|
||||
|
||||
MusicService.playMusicalFixture(
|
||||
// action: () -> Unit
|
||||
{
|
||||
startAudio(musicNowPlaying!!) { loadEffector(it) }
|
||||
},
|
||||
// musicFinished: () -> Boolean
|
||||
{
|
||||
!musicIsPlaying
|
||||
},
|
||||
// onSuccess: () -> Unit
|
||||
{
|
||||
|
||||
},
|
||||
// onFailure: (Throwable) -> Unit
|
||||
{
|
||||
|
||||
},
|
||||
// onFinally: () -> Unit
|
||||
returnToInitialState
|
||||
)
|
||||
|
||||
|
||||
backLamp.currentFrame = 1 + (index / 2)
|
||||
@@ -165,7 +185,7 @@ class FixtureJukebox : Electric, PlaysMusic {
|
||||
*/
|
||||
fun stopGracefully() {
|
||||
stopDiscPlayback()
|
||||
(INGAME.musicGovernor as TerrarumMusicGovernor).stopMusic(this, pauseLen = (INGAME.musicGovernor as TerrarumMusicGovernor).getRandomMusicInterval())
|
||||
(INGAME.backgroundMusicPlayer as TerrarumBackgroundMusicPlayer).stopMusic(this, pauseLen = (INGAME.backgroundMusicPlayer as TerrarumBackgroundMusicPlayer).getRandomMusicInterval())
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import net.torvald.terrarum.gameactors.AVKey
|
||||
import net.torvald.terrarum.gameitems.GameItem
|
||||
import net.torvald.terrarum.gameitems.ItemID
|
||||
import net.torvald.terrarum.langpack.Lang
|
||||
import net.torvald.terrarum.modulebasegame.TerrarumMusicGovernor
|
||||
import net.torvald.terrarum.modulebasegame.TerrarumBackgroundMusicPlayer
|
||||
import net.torvald.terrarum.modulebasegame.gameitems.FixtureItemBase
|
||||
import net.torvald.terrarum.modulebasegame.gameitems.ItemFileRef
|
||||
import net.torvald.terrarum.modulebasegame.gameitems.MusicDiscHelper
|
||||
@@ -96,7 +96,7 @@ class FixtureMusicalTurntable : Electric, PlaysMusic {
|
||||
|
||||
// supress the normal background music playback
|
||||
if (musicIsPlaying && !flagDespawn) {
|
||||
(INGAME.musicGovernor as TerrarumMusicGovernor).stopMusic(this, true)
|
||||
(INGAME.backgroundMusicPlayer as TerrarumBackgroundMusicPlayer).stopMusic(this, true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,21 +111,47 @@ class FixtureMusicalTurntable : Electric, PlaysMusic {
|
||||
|
||||
App.printdbg(this, "Title: $title, artist: $artist")
|
||||
|
||||
musicNowPlaying = MusicContainer(title, musicFile.file()) {
|
||||
val returnToInitialState = {
|
||||
unloadEffector(musicNowPlaying)
|
||||
musicNowPlaying?.tryDispose()
|
||||
musicNowPlaying = null
|
||||
|
||||
}
|
||||
|
||||
musicNowPlaying = MusicContainer(title, musicFile.file()) {
|
||||
App.printdbg(this, "Stop music $title - $artist")
|
||||
|
||||
// can't call stopDiscPlayback() because of the recursion
|
||||
|
||||
(INGAME.musicGovernor as TerrarumMusicGovernor).stopMusic(this, pauseLen = (INGAME.musicGovernor as TerrarumMusicGovernor).getRandomMusicInterval())
|
||||
(INGAME.backgroundMusicPlayer as TerrarumBackgroundMusicPlayer).stopMusic(this, pauseLen = (INGAME.backgroundMusicPlayer as TerrarumBackgroundMusicPlayer).getRandomMusicInterval())
|
||||
}
|
||||
|
||||
App.audioMixer.requestFadeOut(App.audioMixer.musicTrack, AudioMixer.DEFAULT_FADEOUT_LEN / 2f) {
|
||||
|
||||
// FIXME the olde way -- must be replaced with one that utilises MusicService
|
||||
/*App.audioMixer.requestFadeOut(App.audioMixer.musicTrack, AudioMixer.DEFAULT_FADEOUT_LEN / 2f) {
|
||||
startAudio(musicNowPlaying!!) { loadEffector(it) }
|
||||
}
|
||||
}*/
|
||||
|
||||
MusicService.playMusicalFixture(
|
||||
// action: () -> Unit
|
||||
{
|
||||
startAudio(musicNowPlaying!!) { loadEffector(it) }
|
||||
},
|
||||
// musicFinished: () -> Boolean
|
||||
{
|
||||
!musicIsPlaying
|
||||
},
|
||||
// onSuccess: () -> Unit
|
||||
{
|
||||
|
||||
},
|
||||
// onFailure: (Throwable) -> Unit
|
||||
{
|
||||
|
||||
},
|
||||
// onFinally: () -> Unit
|
||||
returnToInitialState
|
||||
)
|
||||
|
||||
|
||||
(sprite as SheetSpriteAnimation).currentRow = 0
|
||||
}
|
||||
@@ -137,7 +163,7 @@ class FixtureMusicalTurntable : Electric, PlaysMusic {
|
||||
*/
|
||||
fun stopGracefully() {
|
||||
stopDiscPlayback()
|
||||
(INGAME.musicGovernor as TerrarumMusicGovernor).stopMusic(this, pauseLen = (INGAME.musicGovernor as TerrarumMusicGovernor).getRandomMusicInterval())
|
||||
(INGAME.backgroundMusicPlayer as TerrarumBackgroundMusicPlayer).stopMusic(this, pauseLen = (INGAME.backgroundMusicPlayer as TerrarumBackgroundMusicPlayer).getRandomMusicInterval())
|
||||
|
||||
}
|
||||
|
||||
|
||||
76
src/net/torvald/terrarum/transaction/Transaction.kt
Normal file
76
src/net/torvald/terrarum/transaction/Transaction.kt
Normal file
@@ -0,0 +1,76 @@
|
||||
package net.torvald.terrarum.transaction
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2024-06-28.
|
||||
*/
|
||||
interface Transaction {
|
||||
|
||||
/**
|
||||
* Call this function to begin the transaction.
|
||||
*
|
||||
* When started using [TransactionListener.runTransaction], the transaction runs on a separate thread,
|
||||
* and thus any operation that requires GL Context will fail.
|
||||
*/
|
||||
fun start(state: TransactionState)
|
||||
|
||||
/**
|
||||
* Called by [TransactionListener.runTransaction], when the transaction was successful.
|
||||
*/
|
||||
fun onSuccess(state: TransactionState)
|
||||
|
||||
/**
|
||||
* Called by [TransactionListener.runTransaction], when the transaction failed.
|
||||
*/
|
||||
fun onFailure(e: Throwable, state: TransactionState)
|
||||
|
||||
}
|
||||
|
||||
abstract class TransactionListener {
|
||||
|
||||
/** `null` if not locked, a class that acquired the lock if locked */
|
||||
var transactionLockedBy: Any? = null; private set
|
||||
val transactionLocked: Boolean; get() = (transactionLockedBy != null)
|
||||
|
||||
|
||||
/**
|
||||
* Transaction modifies a given state to a new state, then applies the new state to the object.
|
||||
* The given `transaction` may only modify values which is passed to it.
|
||||
*/
|
||||
fun runTransaction(transaction: Transaction, onFinally: () -> Unit = {}) {
|
||||
Thread { synchronized(this) {
|
||||
val state = getCurrentStatusForTransaction()
|
||||
if (!transactionLocked) {
|
||||
try {
|
||||
transaction.start(state)
|
||||
// if successful:
|
||||
commitTransaction(state)
|
||||
// notify the success
|
||||
transaction.onSuccess(state)
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
// if failed, notify the failure
|
||||
transaction.onFailure(e, state)
|
||||
}
|
||||
finally {
|
||||
onFinally()
|
||||
}
|
||||
}
|
||||
else {
|
||||
transaction.onFailure(LockedException(this, transactionLockedBy), state)
|
||||
}
|
||||
} }.start()
|
||||
}
|
||||
|
||||
protected abstract fun getCurrentStatusForTransaction(): TransactionState
|
||||
protected abstract fun commitTransaction(state: TransactionState)
|
||||
}
|
||||
|
||||
class LockedException(listener: TransactionListener, lockedBy: Any?) :
|
||||
Exception("Transaction is rejected because the class '${listener.javaClass.canonicalName}' is locked by '${lockedBy?.javaClass?.canonicalName}'")
|
||||
|
||||
@JvmInline value class TransactionState(val valueTable: MutableMap<String, Any?>) {
|
||||
operator fun get(key: String) = valueTable[key]
|
||||
operator fun set(key: String, value: Any?) {
|
||||
valueTable[key] = value
|
||||
}
|
||||
}
|
||||
@@ -96,11 +96,11 @@ class ArrayListMap<K, V> : MutableMap<K, V> {
|
||||
return super.computeIfAbsent(key, mappingFunction)
|
||||
}
|
||||
|
||||
override fun computeIfPresent(key: K, remappingFunction: BiFunction<in K, in V, out V?>): V? {
|
||||
override fun computeIfPresent(key: K, remappingFunction: BiFunction<in K, in V & Any, out V?>): V? {
|
||||
return super.computeIfPresent(key, remappingFunction)
|
||||
}
|
||||
|
||||
override fun merge(key: K, value: V, remappingFunction: BiFunction<in V, in V, out V?>): V? {
|
||||
override fun merge(key: K, value: V & Any, remappingFunction: BiFunction<in V & Any, in V & Any, out V?>): V? {
|
||||
return super.merge(key, value, remappingFunction)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user