diff --git a/MusicPlayer/src/net/torvald/terrarum/musicplayer/gui/MusicPlayer.kt b/MusicPlayer/src/net/torvald/terrarum/musicplayer/gui/MusicPlayer.kt index ca05e34e0..4fcc87f2a 100644 --- a/MusicPlayer/src/net/torvald/terrarum/musicplayer/gui/MusicPlayer.kt +++ b/MusicPlayer/src/net/torvald/terrarum/musicplayer/gui/MusicPlayer.kt @@ -1,5 +1,6 @@ package net.torvald.terrarum.musicplayer.gui +import com.badlogic.gdx.Gdx import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.OrthographicCamera import com.badlogic.gdx.graphics.Pixmap @@ -174,6 +175,7 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() { } private var mouseOnButton: Int? = null + private var mouseOnList: Int? = null override fun updateUI(delta: Float) { // process transition request @@ -265,7 +267,7 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() { } // mouse is over which button? - if (mode >= MODE_MOUSE_UP && relativeMouseY.toFloat() in posYforControls + 10f .. posYforControls + 10f + BUTTON_HEIGHT) { + if (mode >= MODE_MOUSE_UP && relativeMouseY.toFloat() in posYforControls + 12f .. posYforControls + 12f + BUTTON_HEIGHT) { mouseOnButton = if (relativeMouseX.toFloat() in Toolkit.hdrawWidthf - 120f .. Toolkit.hdrawWidthf - 120f + 5 * BUTTON_WIDTH) { ((relativeMouseX.toFloat() - (Toolkit.hdrawWidthf - 120f)) / BUTTON_WIDTH).toInt() } @@ -275,6 +277,15 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() { mouseOnButton = null } + // mouse is over which list + mouseOnList = if (mode >= MODE_SHOW_LIST && + relativeMouseY.toFloat() in _posY + 9.._posY + 9 + PLAYLIST_LINES*PLAYLIST_LINE_HEIGHT && + relativeMouseX.toFloat() in _posX.._posX + width) { + + ((relativeMouseY - (_posY + 9)) / PLAYLIST_LINE_HEIGHT).toInt() + } + else null + // make button work if (!playControlButtonLatched && mouseOnButton != null && Terrarum.mouseDown) { @@ -297,24 +308,42 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() { } } 1 -> { // prev - getPrevSongFromPlaylist()?.let { ingame.musicGovernor.unshiftPlaylist(it) } - AudioMixer.requestFadeOut(AudioMixer.musicTrack, AudioMixer.DEFAULT_FADEOUT_LEN / 3f) + // prev song + if (mode < MODE_SHOW_LIST) { + getPrevSongFromPlaylist()?.let { ingame.musicGovernor.unshiftPlaylist(it) } + AudioMixer.requestFadeOut(AudioMixer.musicTrack, AudioMixer.DEFAULT_FADEOUT_LEN / 3f) + } + // prev page in the list + else { + val scrollMax = ((currentlySelectedAlbum?.length ?: 0).toFloat() / PLAYLIST_LINES).ceilToInt() * PLAYLIST_LINES + playlistScroll = (playlistScroll - PLAYLIST_LINES) fmod scrollMax + } } 2 -> { // stop - if (AudioMixer.musicTrack.isPlaying) { - val thisMusic = AudioMixer.musicTrack.currentTrack - AudioMixer.requestFadeOut(AudioMixer.musicTrack, AudioMixer.DEFAULT_FADEOUT_LEN / 3f) - AudioMixer.musicTrack.nextTrack = null - ingame.musicGovernor.stopMusic() - thisMusic?.let { ingame.musicGovernor.queueMusicToPlayNext(it) } - } - else { - ingame.musicGovernor.startMusic() + if (mode < MODE_SHOW_LIST) { // disable stop button entirely on MODE_SHOW_LIST + if (AudioMixer.musicTrack.isPlaying) { + val thisMusic = AudioMixer.musicTrack.currentTrack + AudioMixer.requestFadeOut(AudioMixer.musicTrack, AudioMixer.DEFAULT_FADEOUT_LEN / 3f) + AudioMixer.musicTrack.nextTrack = null + ingame.musicGovernor.stopMusic() + thisMusic?.let { ingame.musicGovernor.queueMusicToPlayNext(it) } + } + else { + ingame.musicGovernor.startMusic() + } } } 3 -> { // next - AudioMixer.requestFadeOut(AudioMixer.musicTrack, AudioMixer.DEFAULT_FADEOUT_LEN / 3f) { + // next song + if (mode < MODE_SHOW_LIST) { + AudioMixer.requestFadeOut(AudioMixer.musicTrack, AudioMixer.DEFAULT_FADEOUT_LEN / 3f) { // ingame.musicGovernor.startMusic() // it works without this? + } + } + // next page in the list + else { + val scrollMax = ((currentlySelectedAlbum?.length ?: 0).toFloat() / PLAYLIST_LINES).ceilToInt() * PLAYLIST_LINES + playlistScroll = (playlistScroll + PLAYLIST_LINES) fmod scrollMax } } 4 -> { // playlist @@ -322,6 +351,14 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() { if (!transitionOngoing) { transitionRequest = MODE_SHOW_LIST currentListMode = 1 + // reset list scroll + val currentlyPlaying = playlist.indexOf(AudioMixer.musicTrack.currentTrack) + if (currentlyPlaying >= 0) { + playlistScroll = (currentlyPlaying / PLAYLIST_LINES) * PLAYLIST_LINES + } + else { + playlistScroll = 0 + } } } else if (currentListMode != 1) { @@ -474,7 +511,7 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() { get() = relativeMouseX.toFloat() in _posX-capsuleMosaicSize .. _posX+width+capsuleMosaicSize && relativeMouseY.toFloat() in _posY .. _posY+height - override fun renderUI(batch: SpriteBatch, camera: OrthographicCamera) { + override fun renderUI(frameDelta: Float, batch: SpriteBatch, camera: OrthographicCamera) { widthForFreqMeter = if (transitionOngoing && modeNext >= MODE_MOUSE_UP || mode >= MODE_MOUSE_UP) uiWidthFromTextWidth(nameLength) else @@ -513,13 +550,20 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() { drawBaloon(batch, _posX, _posY, width.toFloat(), (height - capsuleHeight.toFloat()).coerceAtLeast(0f)) drawText(batch, posXforMusicLine, posYforMusicLine) drawFreqMeter(batch, posXforMusicLine + widthForFreqMeter - 18f, _posY + height - (capsuleHeight / 2) + 1f) - drawControls(App.UPDATE_RATE, batch, _posX, posYforControls) - drawList(camera, App.UPDATE_RATE, batch, _posX, _posY) + drawControls(frameDelta, batch, _posX, posYforControls) + drawList(camera, frameDelta, batch, _posX, _posY) batch.color = Color.WHITE } - private val playListAnimAkku = FloatArray(8) // how many lines on the list view? + private val widthForList = 320 + + private val PLAYLIST_LEFT_GAP = METERS_WIDTH.toInt() + maskOffWidth + private val PLAYLIST_NAME_LEN = widthForList + private val PLAYLIST_LINE_HEIGHT = 28f + private val PLAYLIST_LINES = App.getConfigInt("musicplayer:playlistlines").let { if (it < 4) 4 else it } + + private val playListAnimAkku = FloatArray(PLAYLIST_LINES) // how many control buttons? private val playListAnimLength = 0.2f private fun drawList(camera: OrthographicCamera, delta: Float, batch: SpriteBatch, x: Float, y: Float) { @@ -557,7 +601,7 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() { // update playListAnimAkku for (i in playListAnimAkku.indices) { - if (mouseOnButton == i && mode >= MODE_MOUSE_UP && modeNext >= MODE_MOUSE_UP) + if (mouseOnList == i && mode >= MODE_SHOW_LIST && modeNext >= MODE_SHOW_LIST) playListAnimAkku[i] = (playListAnimAkku[i] + (delta / playListAnimLength)).coerceIn(0f, 1f) else playListAnimAkku[i] = (playListAnimAkku[i] - (delta / playListAnimLength)).coerceIn(0f, 1f) @@ -565,12 +609,6 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() { } } - private val widthForList = 320 - - private val PLAYLIST_LEFT_GAP = METERS_WIDTH.toInt() + maskOffWidth - private val PLAYLIST_NAME_LEN = widthForList - private val PLAYLIST_LINE_HEIGHT = 28f - private val PLAYLIST_LINES = 7 private val widthForMouseUp = (nameStrMaxLen + METERS_WIDTH + maskOffWidth).toInt() private val heightThin = 28 @@ -607,6 +645,7 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() { batch.end() playlistFBOs.forEachIndexed { i, it -> + val pnum = i + playlistScroll it.inAction(camera, batch) { batch.inUse { batch.color = Color.WHITE @@ -614,7 +653,7 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() { blendNormalStraightAlpha(batch) // draw text - App.fontGameFBO.draw(batch, if (i + playlistScroll in playlist.indices) playlist[i].name else "", maskOffWidth - playlistNameScrolls[i + playlistScroll], (PLAYLIST_LINE_HEIGHT - 24) / 2) + App.fontGameFBO.draw(batch, if (pnum in playlist.indices) playlist[pnum].name else "", maskOffWidth - playlistNameScrolls[pnum], (PLAYLIST_LINE_HEIGHT - 24) / 2) // mask off the area batch.color = Color.WHITE @@ -633,7 +672,10 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() { blendNormalStraightAlpha(batch) if (alpha > 0f) { playlistFBOs.forEachIndexed { i, it -> - val m1 = playlist.getOrNull(i + playlistScroll) + val alpha2 = alpha + (playListAnimAkku[i] * 0.2f) + val pnum = i + playlistScroll + + val m1 = playlist.getOrNull(pnum) val m2 = AudioMixer.musicTrack.currentTrack val currentlyPlaying = if (m1 == null || m2 == null) false else (m1 == m2) @@ -641,25 +683,25 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() { // print bars instead of numbers if the song is currently being played if (currentlyPlaying) { - val xoff = 5 + val xoff = 6 val yoff = 5 + 7 + (PLAYLIST_LINE_HEIGHT - 24) / 2 // it will set the colour on its own drawFreqMeter(batch, x + xoff, y + yoff + PLAYLIST_LINE_HEIGHT * i * scale, alpha) } else { - val xoff = maskOffWidth + (if (i < 9) 3 else 0) + val xoff = maskOffWidth + (if (pnum < 9) 3 else 0) val yoff = 7 + (PLAYLIST_LINE_HEIGHT - 24) / 2 batch.color = Color(1f, 1f, 1f, alpha * 0.75f) App.fontSmallNumbers.draw( batch, - if (i + playlistScroll in playlist.indices) "${i + playlistScroll + 1}" else "", + if (pnum in playlist.indices) "${pnum + 1}" else "", x + xoff, y + yoff + PLAYLIST_LINE_HEIGHT * i * scale ) } // print the name - batch.color = Color(1f, 1f, 1f, alpha) + batch.color = Color(1f, 1f, 1f, alpha2) batch.draw(it.colorBufferTexture, x + PLAYLIST_LEFT_GAP * scale, y + PLAYLIST_LINE_HEIGHT * i * scale, it.width * scale, it.height * scale) // separator @@ -717,7 +759,15 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() { 1f to false else 0f to false - // TODO fade away the << o >>, replacing with the page buttons + + val p = transitionAkku / TRANSITION_LENGTH + val buttonFadePerc = + if (modeNext == MODE_SHOW_LIST) + p + else if (mode == MODE_SHOW_LIST && modeNext == MODE_MOUSE_UP) + 1f - p + else + 0f if (alpha > 0f) { val alpha0 = alpha.coerceIn(0f, 1f).organicOvershoot().coerceAtMost(1f) @@ -726,15 +776,48 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() { val anchorX = Toolkit.hdrawWidthf val posY = posY + 12f for (i in 0..4) { - batch.color = Color(1f, 1f, 1f, - 0.75f * (if (reverse) 1f - alpha0 else alpha0).pow(3f) + (playControlAnimAkku[i].pow(2f) * 1.2f) - ) + val alphaBase = 0.75f * (if (reverse) 1f - alpha0 else alpha0).pow(3f) + (playControlAnimAkku[i] * 0.2f) + val alphaBase2 = 0.75f * (if (reverse) 1f - alpha0 else alpha0).pow(3f) val offset = i - 2 val posX = anchorX + offset * separation - val iconY = if (!AudioMixer.musicTrack.isPlaying && i == 2) 1 else 0 - batch.draw(controlButtons.get(i, iconY), (posX - BUTTON_WIDTH / 2).roundToFloat(), posY.roundToFloat()) + + val btnX = (posX - BUTTON_WIDTH / 2).roundToFloat() + val btnY = posY.roundToFloat() + + // actually draw icon + // prev/next button + if (i == 1 || i == 3) { + // prev/next song button + batch.color = Color(1f, 1f, 1f, alphaBase * (1f - buttonFadePerc)) + batch.draw(controlButtons.get(i, 0), btnX, btnY) + // prev/next page button + batch.color = Color(1f, 1f, 1f, alphaBase * buttonFadePerc) + batch.draw(controlButtons.get(i, 1), btnX, btnY) + } + // stop button + else if (i == 2) { + // get correct stop/play button + val iconY = if (!AudioMixer.musicTrack.isPlaying) 1 else 0 + // fade if avaliable + batch.color = Color(1f, 1f, 1f, alphaBase * (1f - buttonFadePerc)) + batch.draw(controlButtons.get(i, iconY), btnX, btnY) + // page number + batch.color = Color(1f, 1f, 1f, alphaBase2 * buttonFadePerc) // don't use mouse-up effect + Toolkit.drawTextCentered( + batch, App.fontSmallNumbers, + "${(playlistScroll.div(PLAYLIST_LINES).plus(1).toString().padStart(4,' '))}/" + + "${((currentlySelectedAlbum?.length ?: 0).toFloat().div(PLAYLIST_LINES).ceilToInt().toString().padEnd(4,' '))}", + 120, anchorX.toInt() - 60, btnY.toInt() + 14 + ) + } + // else button + else { + batch.color = Color(1f, 1f, 1f, alphaBase) + batch.draw(controlButtons.get(i, 0), btnX, btnY) + } + // update playControlAnimAkku if (mouseOnButton == i && mode >= MODE_MOUSE_UP && modeNext >= MODE_MOUSE_UP) @@ -884,8 +967,11 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() { return out } + private val permittedExts = hashSetOf("WAV", "OGG", "MP3") + private fun getAlbumProp(albumDir: File): AlbumProp { if (!albumDir.exists()) throw IllegalArgumentException("Album dir does not exist: $albumDir") + val fileCount = albumDir.listFiles { _, name -> permittedExts.contains(name.substringAfterLast('.').uppercase()) }?.size ?: 0 val playlistFile = File(albumDir, "playlist.json") if (playlistFile.exists()) { val playlistFile = JsonFetcher.invoke(playlistFile) @@ -893,16 +979,17 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() { val diskJockeyingMode = playlistFile.get("diskJockeyingMode").asString() val shuffled = playlistFile.get("shuffled").asBoolean() val fileToName = playlistFile.get("titles") - return AlbumProp(albumDir, albumName, diskJockeyingMode, shuffled, fileToName) + return AlbumProp(albumDir, albumName, diskJockeyingMode, shuffled, fileToName, fileCount) } else { - return AlbumProp(albumDir, albumDir.name, "intermittent", true, null) + return AlbumProp(albumDir, albumDir.name, "intermittent", true, null, fileCount) } } private data class AlbumProp( val ref: File, val name: String, val diskJockeyingMode: String, val shuffled: Boolean, val fileToName: JsonValue?, + val length: Int, val albumArt: TextureRegion? = null ) diff --git a/assets/graphics/fonts/7x13_Tamzen7x14b.tga b/assets/graphics/fonts/7x13_Tamzen7x14b.tga index 62c6b7aba..e1582e063 100644 --- a/assets/graphics/fonts/7x13_Tamzen7x14b.tga +++ b/assets/graphics/fonts/7x13_Tamzen7x14b.tga @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1524f71e550bc4c51790d6dbedcf0c2875fb7831c7149793322acacf16e355d1 +oid sha256:478762a5f7b5b29ff87624ebc1323dba00d0147dc3b9a41b481704f50aad5657 size 93202 diff --git a/assets/mods/musicplayer/README_for_the_best_audio_quality.md b/assets/mods/musicplayer/README_for_the_best_audio_quality.md index 8816f3d52..7e3d3bc19 100644 --- a/assets/mods/musicplayer/README_for_the_best_audio_quality.md +++ b/assets/mods/musicplayer/README_for_the_best_audio_quality.md @@ -1,20 +1,22 @@ ## Sampling Rate -The basegame is build assuming the sampling rate of 48000 Hz. +The audio engine of the basegame has the fixed sampling rate of 48000 Hz. Any audio files with lower sampling rate will be resampled on-the-fly by the game's audio engine, -but doing so may introduce artefacts, most notably fast periodic clicks, which may be audible in certain -circumstances. For the best results, please resample your audio files to 48000 Hz beforehand. +but doing so may introduce artefacts, most notably fast periodic clicks, which is only audible in certain +artificial cases such as high-pitched monotonic sine wave. If you are concerned about the artefacts, +please resample your audio files to 48000 Hz beforehand. This is easily achievable using free software +such as [Audacity](https://www.audacityteam.org/) or [FFmpeg](https://ffmpeg.org/download.html). ## Mono Incompatibility -The audio engine does not support monaural audio. Please convert your mono audio file to stereo beforehand. +The audio engine does not read monaural audio. Please convert your mono audio files to stereo beforehand. ## Gapless Playback -The basegame (and by the extension this music player) does support the Gapless Playback. +The basegame (and by extension this music player) does support the Gapless Playback. However, because of the inherent limitation of the MP3 format, the Gapless Playback is not achievable without extensive hacks. If you do care, please convert your MP3 files into WAV or OGG format.