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 - 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 ) } } }