Files
Terrarum/src/net/torvald/terrarum/TerrarumMusicPlaylist.kt
2024-07-08 18:06:44 +09:00

166 lines
5.5 KiB
Kotlin

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 - 1
init {
reset()
}
fun reset() {
internalIndices.clear()
refillInternalIndices()
refillInternalIndices()
currentIndexCursor = musicList.size - 1
}
private fun checkRefill() {
if (currentIndexCursor >= internalIndices.size - 2)
refillInternalIndices()
}
fun getCurrent(): MusicContainer {
checkRefill()
return musicList[internalIndices[currentIndexCursor]]
}
/**
* For Gapless playback, this function is called by track's pullNextTrack callback (defined in [MusicService.createTransactionPlaylistChange])
*
* For intermittent playback, this function is called by the transaction defined in [MusicService.update]
*/
fun queueNext(): MusicContainer {
checkRefill()
currentIndexCursor += 1
return musicList[internalIndices[currentIndexCursor]]
}
fun queuePrev(): 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[internalIndices[currentIndexCursor]]
}
fun queueNthSong(n: Int): MusicContainer {
if (shuffled) {
internalIndices.clear()
refillInternalIndices()
internalIndices.add(n)
refillInternalIndices()
currentIndexCursor = musicList.size - 1
}
else {
internalIndices.clear()
refillInternalIndices()
refillInternalIndices()
currentIndexCursor = musicList.size - 1 + n
checkRefill()
}
return musicList[internalIndices[currentIndexCursor]]
}
private fun refillInternalIndices() {
internalIndices.addAll(musicList.indices.toMutableList().also { if (shuffled) it.shuffle() })
}
override fun dispose() {
musicList.forEach {
it.tryDispose()
}
}
companion object {
private val validMusicExtensions = hashSetOf("mp3", "wav", "ogg")
/**
* 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 }?.filter { Companion.validMusicExtensions.contains(it.extension.lowercase()) }?.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
)
}
}
}