diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index a65ed9525..010304a53 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -1,13 +1,13 @@
-
+
-
-
+
+
-
+
\ No newline at end of file
diff --git a/MusicPlayer/src/net/torvald/terrarum/musicplayer/EntryPoint.kt b/MusicPlayer/src/net/torvald/terrarum/musicplayer/EntryPoint.kt
index 3f79a4614..a90f88051 100644
--- a/MusicPlayer/src/net/torvald/terrarum/musicplayer/EntryPoint.kt
+++ b/MusicPlayer/src/net/torvald/terrarum/musicplayer/EntryPoint.kt
@@ -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() {
diff --git a/MusicPlayer/src/net/torvald/terrarum/musicplayer/gui/MusicPlayer.kt b/MusicPlayer/src/net/torvald/terrarum/musicplayer/gui/MusicPlayerControl.kt
similarity index 91%
rename from MusicPlayer/src/net/torvald/terrarum/musicplayer/gui/MusicPlayer.kt
rename to MusicPlayer/src/net/torvald/terrarum/musicplayer/gui/MusicPlayerControl.kt
index f754de1f1..52b0375a4 100644
--- a/MusicPlayer/src/net/torvald/terrarum/musicplayer/gui/MusicPlayer.kt
+++ b/MusicPlayer/src/net/torvald/terrarum/musicplayer/gui/MusicPlayerControl.kt
@@ -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
- get() = ingame.musicGovernor.extortField>("songs")!!
+ get() = ingame.backgroundMusicPlayer.extortField>("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,16 +347,10 @@ 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
- iHitTheStopButton = false
- stopRequested = false
- }
+ MusicService.playPrevSongInPlaylist {
+ ingame.backgroundMusicPlayer.startMusic(this) // required for "intermittent" mode
+ iHitTheStopButton = false
+ stopRequested = false
}
}
// prev page in the playlist
@@ -372,18 +368,31 @@ 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) }
- iHitTheStopButton = true
+ 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
+ stopRequested = false*/
+
+ MusicService.resumePlaylistPlayback {
+ iHitTheStopButton = false
+ stopRequested = false
+ }
}
}
}
@@ -391,15 +400,10 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() {
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
- iHitTheStopButton = false
- stopRequested = false
- }
+ MusicService.playNextSongInPlaylist {
+ ingame.backgroundMusicPlayer.startMusic(this) // required for "intermittent" mode
+ iHitTheStopButton = false
+ stopRequested = false
}
}
// next page in the playlist
@@ -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
}
diff --git a/src/net/torvald/terrarum/MusicGovernor.kt b/src/net/torvald/terrarum/BackgroundMusicPlayer.kt
similarity index 90%
rename from src/net/torvald/terrarum/MusicGovernor.kt
rename to src/net/torvald/terrarum/BackgroundMusicPlayer.kt
index 78f30cc7b..963b64c70 100644
--- a/src/net/torvald/terrarum/MusicGovernor.kt
+++ b/src/net/torvald/terrarum/BackgroundMusicPlayer.kt
@@ -1,6 +1,6 @@
package net.torvald.terrarum
-open class MusicGovernor {
+open class BackgroundMusicPlayer {
open fun update(ingameInstance: IngameInstance, delta: Float) {
diff --git a/src/net/torvald/terrarum/IngameInstance.kt b/src/net/torvald/terrarum/IngameInstance.kt
index c74a1ee3f..238be60e4 100644
--- a/src/net/torvald/terrarum/IngameInstance.kt
+++ b/src/net/torvald/terrarum/IngameInstance.kt
@@ -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) {
diff --git a/src/net/torvald/terrarum/MusicService.kt b/src/net/torvald/terrarum/MusicService.kt
new file mode 100644
index 000000000..95542d739
--- /dev/null
+++ b/src/net/torvald/terrarum/MusicService.kt
@@ -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)
+ }
+}
\ No newline at end of file
diff --git a/src/net/torvald/terrarum/TerrarumMusicPlaylist.kt b/src/net/torvald/terrarum/TerrarumMusicPlaylist.kt
new file mode 100644
index 000000000..4d1b9547d
--- /dev/null
+++ b/src/net/torvald/terrarum/TerrarumMusicPlaylist.kt
@@ -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,
+ /** 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()
+ 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
+ )
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/net/torvald/terrarum/audio/AudioMixer.kt b/src/net/torvald/terrarum/audio/AudioMixer.kt
index a88651973..ba7f92ff9 100644
--- a/src/net/torvald/terrarum/audio/AudioMixer.kt
+++ b/src/net/torvald/terrarum/audio/AudioMixer.kt
@@ -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)
diff --git a/src/net/torvald/terrarum/modulebasegame/BuildingMaker.kt b/src/net/torvald/terrarum/modulebasegame/BuildingMaker.kt
index d31d0a7ca..865a34540 100644
--- a/src/net/torvald/terrarum/modulebasegame/BuildingMaker.kt
+++ b/src/net/torvald/terrarum/modulebasegame/BuildingMaker.kt
@@ -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) {
diff --git a/src/net/torvald/terrarum/modulebasegame/TerrarumMusicGovernor.kt b/src/net/torvald/terrarum/modulebasegame/TerrarumBackgroundMusicPlayer.kt
similarity index 87%
rename from src/net/torvald/terrarum/modulebasegame/TerrarumMusicGovernor.kt
rename to src/net/torvald/terrarum/modulebasegame/TerrarumBackgroundMusicPlayer.kt
index e0c59b2cd..d54d97984 100644
--- a/src/net/torvald/terrarum/modulebasegame/TerrarumMusicGovernor.kt
+++ b/src/net/torvald/terrarum/modulebasegame/TerrarumBackgroundMusicPlayer.kt
@@ -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 = emptyList()
+ private var playlist: List = 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
}
diff --git a/src/net/torvald/terrarum/modulebasegame/TerrarumIngame.kt b/src/net/torvald/terrarum/modulebasegame/TerrarumIngame.kt
index b155f16eb..65fdbaf29 100644
--- a/src/net/torvald/terrarum/modulebasegame/TerrarumIngame.kt
+++ b/src/net/torvald/terrarum/modulebasegame/TerrarumIngame.kt
@@ -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()
}
}
diff --git a/src/net/torvald/terrarum/modulebasegame/gameactors/FixtureJukebox.kt b/src/net/torvald/terrarum/modulebasegame/gameactors/FixtureJukebox.kt
index 1697c1a9a..8c8b2e8c7 100644
--- a/src/net/torvald/terrarum/modulebasegame/gameactors/FixtureJukebox.kt
+++ b/src/net/torvald/terrarum/modulebasegame/gameactors/FixtureJukebox.kt
@@ -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())
}
diff --git a/src/net/torvald/terrarum/modulebasegame/gameactors/FixtureMusicalTurntable.kt b/src/net/torvald/terrarum/modulebasegame/gameactors/FixtureMusicalTurntable.kt
index d180cd573..d5964b70f 100644
--- a/src/net/torvald/terrarum/modulebasegame/gameactors/FixtureMusicalTurntable.kt
+++ b/src/net/torvald/terrarum/modulebasegame/gameactors/FixtureMusicalTurntable.kt
@@ -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())
}
diff --git a/src/net/torvald/terrarum/transaction/Transaction.kt b/src/net/torvald/terrarum/transaction/Transaction.kt
new file mode 100644
index 000000000..8fd9b98a1
--- /dev/null
+++ b/src/net/torvald/terrarum/transaction/Transaction.kt
@@ -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) {
+ operator fun get(key: String) = valueTable[key]
+ operator fun set(key: String, value: Any?) {
+ valueTable[key] = value
+ }
+}
\ No newline at end of file
diff --git a/src/net/torvald/util/ArrayListMap.kt b/src/net/torvald/util/ArrayListMap.kt
index 411aa7de7..45a5e1504 100644
--- a/src/net/torvald/util/ArrayListMap.kt
+++ b/src/net/torvald/util/ArrayListMap.kt
@@ -96,11 +96,11 @@ class ArrayListMap : MutableMap {
return super.computeIfAbsent(key, mappingFunction)
}
- override fun computeIfPresent(key: K, remappingFunction: BiFunction): V? {
+ override fun computeIfPresent(key: K, remappingFunction: BiFunction): V? {
return super.computeIfPresent(key, remappingFunction)
}
- override fun merge(key: K, value: V, remappingFunction: BiFunction): V? {
+ override fun merge(key: K, value: V & Any, remappingFunction: BiFunction): V? {
return super.merge(key, value, remappingFunction)
}