more transaction-based music disk jockeying wip

This commit is contained in:
minjaesong
2024-07-06 20:58:23 +09:00
parent ff433703f4
commit 90f7e82325
11 changed files with 59 additions and 91 deletions

View File

@@ -16,6 +16,7 @@ import net.torvald.terrarum.gameworld.fmod
import net.torvald.terrarum.modulebasegame.TerrarumIngame import net.torvald.terrarum.modulebasegame.TerrarumIngame
import net.torvald.terrarum.modulebasegame.gameactors.PlaysMusic import net.torvald.terrarum.modulebasegame.gameactors.PlaysMusic
import net.torvald.terrarum.ui.BasicDebugInfoWindow import net.torvald.terrarum.ui.BasicDebugInfoWindow
import net.torvald.terrarum.ui.BasicDebugInfoWindow.Companion.toIntAndFrac
import net.torvald.terrarum.ui.MouseLatch import net.torvald.terrarum.ui.MouseLatch
import net.torvald.terrarum.ui.Toolkit import net.torvald.terrarum.ui.Toolkit
import net.torvald.terrarum.ui.UICanvas import net.torvald.terrarum.ui.UICanvas
@@ -191,7 +192,6 @@ class MusicPlayerControl(private val ingame: TerrarumIngame) : UICanvas() {
private var mouseOnList: Int? = null private var mouseOnList: Int? = null
override fun updateImpl(delta: Float) { override fun updateImpl(delta: Float) {
val transactionLocked = MusicService.transactionLocked
val currentPlaylist = getCurrentPlaylist() val currentPlaylist = getCurrentPlaylist()
// process transition request // process transition request
@@ -322,7 +322,7 @@ class MusicPlayerControl(private val ingame: TerrarumIngame) : UICanvas() {
else null else null
// make button work // make buttons work
if (mouseUp) mouseLatch.latch { if (mouseUp) mouseLatch.latch {
if (mouseOnButton != null) { if (mouseOnButton != null) {
when (mouseOnButton) { when (mouseOnButton) {
@@ -495,7 +495,7 @@ class MusicPlayerControl(private val ingame: TerrarumIngame) : UICanvas() {
val playlist = loadNewAlbum(albumsList[index]) val playlist = loadNewAlbum(albumsList[index])
MusicService.putNewPlaylist(playlist) { MusicService.putNewPlaylist(playlist) {
resetPlaylistScroll(App.audioMixer.musicTrack.nextTrack as? MusicContainer) resetPlaylistScroll(App.audioMixer.musicTrack.nextTrack as? MusicContainer)
App.audioMixer.startMusic(playlist.getCurrent()) MusicService.resumePlaylistPlayback({}, {})
} }
} }
} }
@@ -723,7 +723,9 @@ class MusicPlayerControl(private val ingame: TerrarumIngame) : UICanvas() {
} }
batch.color = Color.WHITE batch.color = Color.WHITE
Toolkit.drawTextCentered(batch, App.fontSmallNumbers, "State: ${MusicService.currentPlaybackState.get()}", Toolkit.drawWidth, 0, _posY.toInt() + height + 18) val musicState = MusicService.currentPlaybackState.get()
val str = "State: $musicState Wait: ${MusicService.waitAkku.toIntAndFrac(2)}/${MusicService.waitTime}"
Toolkit.drawTextCentered(batch, App.fontSmallNumbers, str, Toolkit.drawWidth, 0, _posY.toInt() + height + 18)
// end of debug codes // end of debug codes

View File

@@ -1,5 +1,6 @@
package net.torvald.terrarum package net.torvald.terrarum
import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.audio.AudioBank import net.torvald.terrarum.audio.AudioBank
import net.torvald.terrarum.audio.AudioMixer.Companion.DEFAULT_FADEOUT_LEN import net.torvald.terrarum.audio.AudioMixer.Companion.DEFAULT_FADEOUT_LEN
import net.torvald.terrarum.transaction.Transaction import net.torvald.terrarum.transaction.Transaction
@@ -39,18 +40,18 @@ object MusicService : TransactionListener() {
private const val STATE_PLAYING = 2 private const val STATE_PLAYING = 2
val currentPlaybackState = AtomicInteger(STATE_INTERMISSION) val currentPlaybackState = AtomicInteger(STATE_INTERMISSION)
private var waitAkku = 0f var waitAkku = 0f; private set
private var waitTime = 10f var waitTime = 10f; private set
private fun enterSTATE_INTERMISSION(waitFor: Float) { private fun enterSTATE_INTERMISSION(waitFor: Float) {
currentPlaybackState.set(STATE_INTERMISSION) currentPlaybackState.set(STATE_INTERMISSION)
waitTime = waitFor waitTime = waitFor
waitAkku = 0f waitAkku = 0f
playTransactionOngoing = false
} }
private fun enterSTATE_FIREPLAY() { private fun enterSTATE_FIREPLAY() {
val state = currentPlaybackState.get() val state = currentPlaybackState.get()
if (state == STATE_FIREPLAY) throw IllegalStateException("Cannot change state FIREPLAY -> FIREPLAY")
if (state == STATE_PLAYING) throw IllegalStateException("Cannot change state PLAYING -> FIREPLAY") if (state == STATE_PLAYING) throw IllegalStateException("Cannot change state PLAYING -> FIREPLAY")
waitAkku = 0f waitAkku = 0f
@@ -81,9 +82,7 @@ object MusicService : TransactionListener() {
} }
fun onMusicFinishing(audio: AudioBank) { fun onMusicFinishing(audio: AudioBank) {
synchronized(this) { enterIntermissionAndWaitForPlaylist()
enterIntermissionAndWaitForPlaylist()
}
} }
private var playTransactionOngoing = false private var playTransactionOngoing = false
@@ -120,6 +119,9 @@ object MusicService : TransactionListener() {
} }
) )
} }
else {
println("PLAYing is deferred: playTransaction is ongoing")
}
} }
STATE_PLAYING -> { STATE_PLAYING -> {
// onMusicFinishing() will be called when the music finishes; it's on the setOnCompletionListener // onMusicFinishing() will be called when the music finishes; it's on the setOnCompletionListener
@@ -136,26 +138,22 @@ object MusicService : TransactionListener() {
fun enterScene(id: String) { fun enterScene(id: String) {
synchronized(this) { /*val playlist = when (id) {
/*val playlist = when (id) { "title" -> getTitlePlaylist()
"title" -> getTitlePlaylist() "ingame" -> getIngameDefaultPlaylist()
"ingame" -> getIngameDefaultPlaylist() else -> getIngameDefaultPlaylist()
else -> getIngameDefaultPlaylist()
}
putNewPlaylist(playlist) {
// after the fadeout, we'll...
enterSTATE_FIREPLAY()
}*/
stopPlaylistPlayback { }
} }
putNewPlaylist(playlist) {
// after the fadeout, we'll...
enterSTATE_FIREPLAY()
}*/
stopPlaylistPlayback { }
} }
fun leaveScene() { fun leaveScene() {
synchronized(this) { stopPlaylistPlayback {}
stopPlaylistPlayback {}
}
} }
/** /**
@@ -363,10 +361,8 @@ object MusicService : TransactionListener() {
override fun start(state: TransactionState) { override fun start(state: TransactionState) {
var fadedOut = false var fadedOut = false
var err: Throwable? = null var err: Throwable? = null
println("createTransactionPausePlaylistForMusicalFixture start")
// request fadeout // request fadeout
App.audioMixer.requestFadeOut(App.audioMixer.musicTrack, DEFAULT_FADEOUT_LEN / 2.0) { App.audioMixer.requestFadeOut(App.audioMixer.musicTrack, DEFAULT_FADEOUT_LEN / 2.0) {
println("createTransactionPausePlaylistForMusicalFixture fadeout end")
try { try {
// callback: let the caller actually take care of playing the audio // callback: let the caller actually take care of playing the audio
action() action()
@@ -383,11 +379,9 @@ object MusicService : TransactionListener() {
if (err != null) throw err!! if (err != null) throw err!!
// enter intermission state // enter intermission state
println("createTransactionPausePlaylistForMusicalFixture fadeout waiting end, entering INTERMISSION state")
enterSTATE_INTERMISSION(Float.POSITIVE_INFINITY) enterSTATE_INTERMISSION(Float.POSITIVE_INFINITY)
// wait until the interjected music finishes // wait until the interjected music finishes
println("createTransactionPausePlaylistForMusicalFixture waiting for musicFinished()")
waitUntil { musicFinished() } waitUntil { musicFinished() }
} }

View File

@@ -6,10 +6,10 @@ open class MusicStreamer {
} }
protected var musicState = 0 // 0: disabled, 1: playing, 2: waiting // protected var musicState = 0 // 0: disabled, 1: playing, 2: waiting
protected var intermissionAkku = 0f // protected var intermissionAkku = 0f
protected var intermissionLength = 1f // protected var intermissionLength = 1f
protected var musicFired = false // protected var musicFired = false
open fun dispose() { open fun dispose() {

View File

@@ -339,7 +339,6 @@ class AudioMixer : Disposable {
map[it] = FadeRequest() map[it] = FadeRequest()
} }
} }
private val fadeReqsCol = fadeReqs.entries
private var lpAkku = 0.0 private var lpAkku = 0.0
private var lpLength = 0.4 private var lpLength = 0.4
@@ -402,7 +401,7 @@ class AudioMixer : Disposable {
// process fades // process fades
fadeReqsCol.forEach { val track = it.key; val req = it.value fadeReqs.entries.forEach { val track = it.key; val req = it.value
if (req.fadeoutFired) { if (req.fadeoutFired) {
req.fadeAkku += delta req.fadeAkku += delta
val step = req.fadeAkku / req.fadeLength val step = req.fadeAkku / req.fadeLength
@@ -484,18 +483,26 @@ class AudioMixer : Disposable {
private var ambientStopped = true private var ambientStopped = true
fun startMusic(song: AudioBank) { /**
* Don't call this function from the application (fixture, user-made music player mod, etc.) directly!
* Use transactions from the MusicService.
*/
internal fun startMusic(song: AudioBank) {
if (musicTrack.isPlaying) { if (musicTrack.isPlaying) {
requestFadeOut(musicTrack, DEFAULT_FADEOUT_LEN) requestFadeOut(musicTrack, DEFAULT_FADEOUT_LEN)
} }
musicTrack.nextTrack = song musicTrack.nextTrack = song
} }
fun stopMusic() { /**
* Don't call this function from the application (fixture, user-made music player mod, etc.) directly!
* Use transactions from the MusicService.
*/
internal fun stopMusic() {
requestFadeOut(musicTrack, DEFAULT_FADEOUT_LEN) requestFadeOut(musicTrack, DEFAULT_FADEOUT_LEN)
} }
fun startAmb(song: AudioBank) { internal fun startAmb(song: AudioBank) {
val ambientTrack = if (!ambientTrack1.streamPlaying.get()) val ambientTrack = if (!ambientTrack1.streamPlaying.get())
ambientTrack1 ambientTrack1
else if (!ambientTrack2.streamPlaying.get()) else if (!ambientTrack2.streamPlaying.get())
@@ -512,7 +519,7 @@ class AudioMixer : Disposable {
// fade will be processed by the update() // fade will be processed by the update()
} }
fun startAmb1(song: AudioBank) { internal fun startAmb1(song: AudioBank) {
if (ambientTrack1.isPlaying == true) { if (ambientTrack1.isPlaying == true) {
requestFadeOut(musicTrack, DEFAULT_FADEOUT_LEN) requestFadeOut(musicTrack, DEFAULT_FADEOUT_LEN)
} }
@@ -520,7 +527,7 @@ class AudioMixer : Disposable {
// fade will be processed by the update() // fade will be processed by the update()
} }
fun startAmb2(song: AudioBank) { internal fun startAmb2(song: AudioBank) {
if (ambientTrack2.isPlaying == true) { if (ambientTrack2.isPlaying == true) {
requestFadeOut(musicTrack, DEFAULT_FADEOUT_LEN) requestFadeOut(musicTrack, DEFAULT_FADEOUT_LEN)
} }
@@ -584,7 +591,7 @@ class AudioMixer : Disposable {
} }
} }
fun reset() { internal fun reset() {
ambientStopped = true ambientStopped = true
dynamicTracks.forEach { it.stop() } dynamicTracks.forEach { it.stop() }
guiTracks.forEach { it.stop() } guiTracks.forEach { it.stop() }

View File

@@ -192,7 +192,6 @@ class TerrarumAudioMixerTrack(
} }
fireSongFinishHook() fireSongFinishHook()
// fireSoundFinishHook()
trackingTarget = null trackingTarget = null
processor.streamBuf = null processor.streamBuf = null

View File

@@ -62,7 +62,7 @@ class BuildingMaker(batch: FlippingSpriteBatch) : IngameInstance(batch) {
lateinit var gameWorld: GameWorld lateinit var gameWorld: GameWorld
override val musicStreamer = TerrarumMusicStreamer() override val musicStreamer = TerrarumMusicAndAmbientStreamer()
init { init {
gameUpdateGovernor = ConsistentUpdateRate gameUpdateGovernor = ConsistentUpdateRate

View File

@@ -262,7 +262,7 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
override var gameFullyLoaded = false override var gameFullyLoaded = false
internal set internal set
override val musicStreamer = TerrarumMusicStreamer() override val musicStreamer = TerrarumMusicAndAmbientStreamer()
////////////// //////////////

View File

@@ -4,14 +4,11 @@ import com.badlogic.gdx.utils.GdxRuntimeException
import com.jme3.math.FastMath import com.jme3.math.FastMath
import net.torvald.terrarum.* import net.torvald.terrarum.*
import net.torvald.terrarum.App.printdbg import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.audio.AudioBank
import net.torvald.terrarum.audio.AudioMixer
import net.torvald.terrarum.audio.audiobank.MusicContainer import net.torvald.terrarum.audio.audiobank.MusicContainer
import net.torvald.terrarum.gameworld.WorldTime.Companion.DAY_LENGTH import net.torvald.terrarum.gameworld.WorldTime.Companion.DAY_LENGTH
import java.io.File
class TerrarumMusicStreamer : MusicStreamer() { class TerrarumMusicAndAmbientStreamer : MusicStreamer() {
private val STATE_INIT = 0 private val STATE_INIT = 0
private val STATE_FIREPLAY = 1 private val STATE_FIREPLAY = 1
private val STATE_PLAYING = 2 private val STATE_PLAYING = 2
@@ -19,7 +16,6 @@ class TerrarumMusicStreamer : MusicStreamer() {
init { init {
musicState = STATE_INTERMISSION
} }
private var playlist: List<MusicContainer> = emptyList() private var playlist: List<MusicContainer> = emptyList()
@@ -198,40 +194,9 @@ class TerrarumMusicStreamer : MusicStreamer() {
// start the song queueing if there is one to play // start the song queueing if there is one to play
if (firstTime) { if (firstTime) {
firstTime = false firstTime = false
if (playlist.isNotEmpty()) musicState = STATE_INTERMISSION
if (ambients.isNotEmpty()) ambState = STATE_INTERMISSION if (ambients.isNotEmpty()) ambState = STATE_INTERMISSION
} }
when (musicState) {
STATE_FIREPLAY -> {
if (!musicFired) {
MusicService.resumePlaylistPlayback(
// onSuccess: () -> Unit
{
musicFired = true
musicState = STATE_PLAYING
},
// onFailure: (Throwable) -> Unit
{
musicFired = false
musicState = STATE_INTERMISSION
},
)
}
}
STATE_PLAYING -> {
// stopMusic() will be called when the music finishes; it's on the setOnCompletionListener
}
STATE_INTERMISSION -> {
intermissionAkku += delta
if (intermissionAkku >= intermissionLength && playlist.isNotEmpty()) {
intermissionAkku = 0f
musicState = STATE_FIREPLAY
}
}
}
val season = ingame.world.worldTime.ecologicalSeason val season = ingame.world.worldTime.ecologicalSeason
val isAM = (ingame.world.worldTime.todaySeconds < DAY_LENGTH / 2) // 0 until DAY_LENGTH (86400) val isAM = (ingame.world.worldTime.todaySeconds < DAY_LENGTH / 2) // 0 until DAY_LENGTH (86400)
val solarElevDeg = ingame.world.worldTime.solarElevationDeg val solarElevDeg = ingame.world.worldTime.solarElevationDeg

View File

@@ -19,7 +19,6 @@ import net.torvald.terrarum.gameactors.Hitbox
import net.torvald.terrarum.gameactors.Lightbox import net.torvald.terrarum.gameactors.Lightbox
import net.torvald.terrarum.gameitems.ItemID import net.torvald.terrarum.gameitems.ItemID
import net.torvald.terrarum.langpack.Lang import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.modulebasegame.TerrarumMusicStreamer
import net.torvald.terrarum.modulebasegame.gameitems.FixtureItemBase import net.torvald.terrarum.modulebasegame.gameitems.FixtureItemBase
import net.torvald.terrarum.modulebasegame.gameitems.ItemFileRef import net.torvald.terrarum.modulebasegame.gameitems.ItemFileRef
import net.torvald.terrarum.modulebasegame.gameitems.MusicDiscHelper import net.torvald.terrarum.modulebasegame.gameitems.MusicDiscHelper

View File

@@ -9,7 +9,6 @@ import net.torvald.terrarum.gameactors.AVKey
import net.torvald.terrarum.gameitems.GameItem import net.torvald.terrarum.gameitems.GameItem
import net.torvald.terrarum.gameitems.ItemID import net.torvald.terrarum.gameitems.ItemID
import net.torvald.terrarum.langpack.Lang import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.modulebasegame.TerrarumMusicStreamer
import net.torvald.terrarum.modulebasegame.gameitems.FixtureItemBase import net.torvald.terrarum.modulebasegame.gameitems.FixtureItemBase
import net.torvald.terrarum.modulebasegame.gameitems.ItemFileRef import net.torvald.terrarum.modulebasegame.gameitems.ItemFileRef
import net.torvald.terrarum.modulebasegame.gameitems.MusicDiscHelper import net.torvald.terrarum.modulebasegame.gameitems.MusicDiscHelper
@@ -125,7 +124,6 @@ class FixtureMusicalTurntable : Electric, PlaysMusic {
MusicService.playMusicalFixture( MusicService.playMusicalFixture(
/* action: () -> Unit */ { /* action: () -> Unit */ {
App.printdbg(this, "call startAudio(${musicNowPlaying?.name})")
startAudio(musicNowPlaying!!) { loadEffector(it) } startAudio(musicNowPlaying!!) { loadEffector(it) }
}, },
/* musicFinished: () -> Boolean */ { /* musicFinished: () -> Boolean */ {
@@ -151,9 +149,12 @@ class FixtureMusicalTurntable : Electric, PlaysMusic {
fun stopGracefully() { fun stopGracefully() {
stopDiscPlayback() stopDiscPlayback()
try { try {
MusicService.enterIntermission() if (musicIsPlaying)
MusicService.enterIntermission()
}
catch (e: Throwable) {
e.printStackTrace()
} }
catch (_: Throwable) {}
} }
private fun stopDiscPlayback() { private fun stopDiscPlayback() {

View File

@@ -30,7 +30,7 @@ interface Transaction {
abstract class TransactionListener { abstract class TransactionListener {
/** `null` if not locked, a class that acquired the lock if locked */ /** `null` if not locked, a class that acquired the lock if locked */
private val transactionLockingClass: AtomicReference<Transaction?> = AtomicReference(null) val transactionLockingClass: AtomicReference<Transaction?> = AtomicReference(null)
val transactionLocked: Boolean; get() = (transactionLockingClass.get() != null) val transactionLocked: Boolean; get() = (transactionLockingClass.get() != null)
@@ -43,9 +43,10 @@ abstract class TransactionListener {
*/ */
fun runTransaction(transaction: Transaction, onFinally: () -> Unit = {}) { fun runTransaction(transaction: Transaction, onFinally: () -> Unit = {}) {
printdbg(this, "Accepting transaction $transaction") printdbg(this, "Accepting transaction $transaction")
Thread { synchronized(this) { Thread {
val state = getCurrentStatusForTransaction() val state = getCurrentStatusForTransaction()
if (!transactionLocked) { val currentLock = transactionLockingClass.get()
if (currentLock == null) {
transactionLockingClass.set(transaction) transactionLockingClass.set(transaction)
try { try {
transaction.start(state) transaction.start(state)
@@ -66,9 +67,9 @@ abstract class TransactionListener {
} }
else { else {
System.err.println("Transaction failure: locked") System.err.println("Transaction failure: locked")
transaction.onFailure(LockedException(this, transactionLockingClass.get()), state) transaction.onFailure(LockedException(this, currentLock), state)
} }
} }.start() }.start()
} }
protected abstract fun getCurrentStatusForTransaction(): TransactionState protected abstract fun getCurrentStatusForTransaction(): TransactionState