fix: RAM music should loop as they should now

This commit is contained in:
minjaesong
2024-04-06 00:22:03 +09:00
parent 7416f15def
commit 3ef3f3f653
11 changed files with 180 additions and 51 deletions

View File

@@ -376,7 +376,7 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() {
App.audioMixer.requestFadeOut(App.audioMixer.musicTrack, AudioMixer.DEFAULT_FADEOUT_LEN / 3f) App.audioMixer.requestFadeOut(App.audioMixer.musicTrack, AudioMixer.DEFAULT_FADEOUT_LEN / 3f)
App.audioMixer.musicTrack.nextTrack = null App.audioMixer.musicTrack.nextTrack = null
ingame.musicGovernor.stopMusic(this) ingame.musicGovernor.stopMusic(this)
thisMusic?.let { ingame.musicGovernor.queueMusicToPlayNext(it) } if (thisMusic is MusicContainer) thisMusic.let { ingame.musicGovernor.queueMusicToPlayNext(it) }
iHitTheStopButton = true iHitTheStopButton = true
} }
else if (!shouldPlayerBeDisabled) { else if (!shouldPlayerBeDisabled) {
@@ -472,7 +472,7 @@ class MusicPlayer(private val ingame: TerrarumIngame) : UICanvas() {
iHitTheStopButton = false iHitTheStopButton = false
stopRequested = false stopRequested = false
} }
resetPlaylistScroll(App.audioMixer.musicTrack.nextTrack) resetPlaylistScroll(App.audioMixer.musicTrack.nextTrack as? MusicContainer)
} }
} }
} }

View File

@@ -17,6 +17,7 @@ import com.github.strikerx3.jxinput.XInputDevice;
import kotlin.jvm.functions.Function0; import kotlin.jvm.functions.Function0;
import kotlin.text.Charsets; import kotlin.text.Charsets;
import net.torvald.getcpuname.GetCpuName; import net.torvald.getcpuname.GetCpuName;
import net.torvald.terrarum.audio.AudioBank;
import net.torvald.terrarum.audio.AudioMixer; import net.torvald.terrarum.audio.AudioMixer;
import net.torvald.terrarum.audio.MusicContainer; import net.torvald.terrarum.audio.MusicContainer;
import net.torvald.terrarum.audio.dsp.BinoPan; import net.torvald.terrarum.audio.dsp.BinoPan;
@@ -554,11 +555,11 @@ public class App implements ApplicationListener {
CommonResourcePool.INSTANCE.addToLoadingList("title_health1", () -> new Texture(Gdx.files.internal("./assets/graphics/gui/health_take_a_break.tga"))); CommonResourcePool.INSTANCE.addToLoadingList("title_health1", () -> new Texture(Gdx.files.internal("./assets/graphics/gui/health_take_a_break.tga")));
CommonResourcePool.INSTANCE.addToLoadingList("title_health2", () -> new Texture(Gdx.files.internal("./assets/graphics/gui/health_distance.tga"))); CommonResourcePool.INSTANCE.addToLoadingList("title_health2", () -> new Texture(Gdx.files.internal("./assets/graphics/gui/health_distance.tga")));
CommonResourcePool.INSTANCE.addToLoadingList("sound:haptic_bup", () -> new MusicContainer("haptic_bup", Gdx.files.internal("./assets/audio/effects/haptic_bup.ogg").file(), false, true, (MusicContainer m) -> { return null; })); CommonResourcePool.INSTANCE.addToLoadingList("sound:haptic_bup", () -> new MusicContainer("haptic_bup", Gdx.files.internal("./assets/audio/effects/haptic_bup.ogg").file(), false, true, (AudioBank m) -> { return null; }));
CommonResourcePool.INSTANCE.addToLoadingList("sound:haptic_bap", () -> new MusicContainer("haptic_bap", Gdx.files.internal("./assets/audio/effects/haptic_bap.ogg").file(), false, true, (MusicContainer m) -> { return null; })); CommonResourcePool.INSTANCE.addToLoadingList("sound:haptic_bap", () -> new MusicContainer("haptic_bap", Gdx.files.internal("./assets/audio/effects/haptic_bap.ogg").file(), false, true, (AudioBank m) -> { return null; }));
CommonResourcePool.INSTANCE.addToLoadingList("sound:haptic_bop", () -> new MusicContainer("haptic_bop", Gdx.files.internal("./assets/audio/effects/haptic_bop.ogg").file(), false, true, (MusicContainer m) -> { return null; })); CommonResourcePool.INSTANCE.addToLoadingList("sound:haptic_bop", () -> new MusicContainer("haptic_bop", Gdx.files.internal("./assets/audio/effects/haptic_bop.ogg").file(), false, true, (AudioBank m) -> { return null; }));
CommonResourcePool.INSTANCE.addToLoadingList("sound:haptic_bep", () -> new MusicContainer("haptic_bep", Gdx.files.internal("./assets/audio/effects/haptic_bep.ogg").file(), false, true, (MusicContainer m) -> { return null; })); CommonResourcePool.INSTANCE.addToLoadingList("sound:haptic_bep", () -> new MusicContainer("haptic_bep", Gdx.files.internal("./assets/audio/effects/haptic_bep.ogg").file(), false, true, (AudioBank m) -> { return null; }));
CommonResourcePool.INSTANCE.addToLoadingList("sound:haptic_bip", () -> new MusicContainer("haptic_bip", Gdx.files.internal("./assets/audio/effects/haptic_bip.ogg").file(), false, true, (MusicContainer m) -> { highPrioritySoundPlaying = false; return null; })); CommonResourcePool.INSTANCE.addToLoadingList("sound:haptic_bip", () -> new MusicContainer("haptic_bip", Gdx.files.internal("./assets/audio/effects/haptic_bip.ogg").file(), false, true, (AudioBank m) -> { highPrioritySoundPlaying = false; return null; }));
// make loading list // make loading list
CommonResourcePool.INSTANCE.loadAll(); CommonResourcePool.INSTANCE.loadAll();

View File

@@ -0,0 +1,32 @@
package net.torvald.terrarum.audio
import com.badlogic.gdx.utils.Disposable
import java.io.File
/**
* Created by minjaesong on 2024-04-05.
*/
abstract class AudioBank : Disposable {
companion object {
fun fromMusic(name: String, file: File, looping: Boolean = false, toRAM: Boolean = false, songFinishedHook: (AudioBank) -> Unit = {}): AudioBank {
return MusicContainer(name, file, looping, toRAM, songFinishedHook)
}
}
protected val hash = System.nanoTime()
abstract fun makeCopy(): AudioBank
abstract val name: String
abstract val samplingRate: Int
abstract val channels: Int
abstract val totalSizeInSamples: Long
abstract fun currentPositionInSamples(): Long
abstract fun readBytes(buffer: ByteArray): Int
abstract fun reset()
abstract val songFinishedHook: (AudioBank) -> Unit
}

View File

@@ -485,7 +485,7 @@ class AudioMixer : Disposable {
private var ambientStopped = true private var ambientStopped = true
fun startMusic(song: MusicContainer) { fun startMusic(song: AudioBank) {
if (musicTrack.isPlaying) { if (musicTrack.isPlaying) {
requestFadeOut(musicTrack, DEFAULT_FADEOUT_LEN) requestFadeOut(musicTrack, DEFAULT_FADEOUT_LEN)
} }
@@ -496,7 +496,7 @@ class AudioMixer : Disposable {
requestFadeOut(musicTrack, DEFAULT_FADEOUT_LEN) requestFadeOut(musicTrack, DEFAULT_FADEOUT_LEN)
} }
fun startAmb(song: MusicContainer) { 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())
@@ -513,7 +513,7 @@ class AudioMixer : Disposable {
// fade will be processed by the update() // fade will be processed by the update()
} }
fun startAmb1(song: MusicContainer) { fun startAmb1(song: AudioBank) {
if (ambientTrack1.isPlaying == true) { if (ambientTrack1.isPlaying == true) {
requestFadeOut(musicTrack, DEFAULT_FADEOUT_LEN) requestFadeOut(musicTrack, DEFAULT_FADEOUT_LEN)
} }
@@ -521,7 +521,7 @@ class AudioMixer : Disposable {
// fade will be processed by the update() // fade will be processed by the update()
} }
fun startAmb2(song: MusicContainer) { fun startAmb2(song: AudioBank) {
if (ambientTrack2.isPlaying == true) { if (ambientTrack2.isPlaying == true) {
requestFadeOut(musicTrack, DEFAULT_FADEOUT_LEN) requestFadeOut(musicTrack, DEFAULT_FADEOUT_LEN)
} }

View File

@@ -98,12 +98,6 @@ class MixerTrackProcessor(bufferSize: Int, val rate: Int, val track: TerrarumAud
bytesRead += read0(buffer, bytesRead) bytesRead += read0(buffer, bytesRead)
} }
// if isLooping=true, do gapless sampleRead but reads from itself
else if (track.currentTrack?.looping == true && bytesRead < buffer.size) {
track.currentTrack?.reset()
bytesRead += read0(buffer, bytesRead)
}
bytesRead bytesRead
}, { purgeStreamBuf() }).also { }, { purgeStreamBuf() }).also {

View File

@@ -5,9 +5,9 @@ import net.torvald.terrarum.tryDispose
class MusicCache(val trackName: String) : Disposable { class MusicCache(val trackName: String) : Disposable {
private val cache = HashMap<String, MusicContainer>() private val cache = HashMap<String, AudioBank>()
fun getOrPut(music: MusicContainer?): MusicContainer? { fun getOrPut(music: AudioBank?): AudioBank? {
if (music != null) if (music != null)
return cache.getOrPut(music.name) { music.makeCopy() } return cache.getOrPut(music.name) { music.makeCopy() }
return null return null

View File

@@ -7,41 +7,51 @@ import com.badlogic.gdx.backends.lwjgl3.audio.Ogg
import com.badlogic.gdx.backends.lwjgl3.audio.OggInputStream import com.badlogic.gdx.backends.lwjgl3.audio.OggInputStream
import com.badlogic.gdx.backends.lwjgl3.audio.Wav import com.badlogic.gdx.backends.lwjgl3.audio.Wav
import com.badlogic.gdx.files.FileHandle import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.utils.Disposable
import com.jcraft.jorbis.VorbisFile import com.jcraft.jorbis.VorbisFile
import javazoom.jl.decoder.Bitstream import javazoom.jl.decoder.Bitstream
import net.torvald.reflection.extortField import net.torvald.reflection.extortField
import net.torvald.reflection.forceInvoke import net.torvald.reflection.forceInvoke
import net.torvald.terrarum.App.printdbg
import net.torvald.unsafe.UnsafeHelper import net.torvald.unsafe.UnsafeHelper
import net.torvald.unsafe.UnsafePtr import net.torvald.unsafe.UnsafePtr
import java.io.File import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
import javax.sound.sampled.AudioSystem import javax.sound.sampled.AudioSystem
data class MusicContainer( class MusicContainer(
val name: String, override val name: String,
val file: File, val file: File,
val looping: Boolean = false, val looping: Boolean = false,
val toRAM: Boolean = false, val toRAM: Boolean = false,
internal var songFinishedHook: (MusicContainer) -> Unit = {} override var songFinishedHook: (AudioBank) -> Unit = {}
): Disposable { ): AudioBank() {
val samplingRate: Int override val samplingRate: Int
override val channels: Int
val codec: String val codec: String
var samplesReadCount = 0L; internal set var samplesReadCount = 0L; internal set
var samplesTotal: Long override val totalSizeInSamples: Long
private val totalSizeInBytes: Long
private val gdxMusic: Music = Gdx.audio.newMusic(FileHandle(file)) private val gdxMusic: Music = Gdx.audio.newMusic(FileHandle(file))
private var soundBuf: UnsafePtr? = null; private set private var soundBuf: UnsafePtr? = null; private set
private val hash = System.nanoTime() private val bytesPerSample: Int
init { init {
gdxMusic.isLooping = looping gdxMusic.isLooping = looping
// gdxMusic.setOnCompletionListener(songFinishedHook) // gdxMusic.setOnCompletionListener(songFinishedHook)
channels = when (gdxMusic) {
is Wav.Music -> gdxMusic.extortField<Wav.WavInputStream>("input")!!.channels
is Ogg.Music -> gdxMusic.extortField<OggInputStream>("input")!!.channels
else -> 2
}
bytesPerSample = 2 * channels
samplingRate = when (gdxMusic) { samplingRate = when (gdxMusic) {
is Wav.Music -> { is Wav.Music -> {
val rate = gdxMusic.extortField<Wav.WavInputStream>("input")!!.sampleRate val rate = gdxMusic.extortField<Wav.WavInputStream>("input")!!.sampleRate
@@ -80,55 +90,147 @@ data class MusicContainer(
if (it.last() == "Music") it.dropLast(1).last() else it.last() if (it.last() == "Music") it.dropLast(1).last() else it.last()
} }
samplesTotal = when (gdxMusic) { totalSizeInSamples = when (gdxMusic) {
is Wav.Music -> getWavFileSampleCount(file) is Wav.Music -> getWavFileSampleCount(file)
is Ogg.Music -> getOggFileSampleCount(file) is Ogg.Music -> getOggFileSampleCount(file)
is Mp3.Music -> getMp3FileSampleCount(file) is Mp3.Music -> getMp3FileSampleCount(file)
else -> Long.MAX_VALUE else -> Long.MAX_VALUE
} }
totalSizeInBytes = totalSizeInSamples * 2 * channels
if (toRAM) { if (toRAM) {
if (samplesTotal == Long.MAX_VALUE) throw IllegalStateException("Could not read sample count") if (totalSizeInSamples == Long.MAX_VALUE) throw IllegalStateException("Could not read sample count")
val readSize = 8192 val readSize = 8192
var readCount = 0L var readCount = 0L
val readBuf = ByteArray(readSize) val readBuf = ByteArray(readSize)
soundBuf = UnsafeHelper.allocate(4L * samplesTotal) soundBuf = UnsafeHelper.allocate(totalSizeInBytes)
while (readCount < samplesTotal) { while (readCount < totalSizeInBytes) {
val read = gdxMusic.forceInvoke<Int>("read", arrayOf(readBuf))!!.toLong() gdxMusic.forceInvoke<Int>("read", arrayOf(readBuf))!!.toLong() // its return value will be useless for looping=true
val read = minOf(readSize.toLong(), (totalSizeInBytes - readCount))
UnsafeHelper.memcpyRaw(readBuf, UnsafeHelper.getArrayOffset(readBuf), null, soundBuf!!.ptr + readCount, read) UnsafeHelper.memcpyRaw(readBuf, UnsafeHelper.getArrayOffset(readBuf), null, soundBuf!!.ptr + readCount, read)
readCount += read readCount += read
// printdbg(this, "read $readCount/$totalSizeInBytes bytes (${readCount/(2*channels)}/$totalSizeInSamples samples)")
} }
} }
} }
fun readBytes(buffer: ByteArray): Int { private fun read0(buffer: ByteArray, bytesRead: Int): Int {
val tmpBuf = ByteArray(buffer.size - bytesRead)
val newRead = readBytes(tmpBuf)
System.arraycopy(tmpBuf, 0, buffer, bytesRead, tmpBuf.size)
return newRead
}
override fun readBytes(buffer: ByteArray): Int {
if (soundBuf == null) { if (soundBuf == null) {
val bytesRead = gdxMusic.forceInvoke<Int>("read", arrayOf(buffer)) ?: 0 val bytesRead = gdxMusic.forceInvoke<Int>("read", arrayOf(buffer)) ?: 0
samplesReadCount += bytesRead / 4 samplesReadCount += bytesRead / bytesPerSample
if (looping && bytesRead < buffer.size) {
reset()
val remainder = buffer.size - bytesRead
val fullCopyCounts = remainder / totalSizeInBytes
val partialCopyCountsInBytes = (remainder % totalSizeInBytes).toInt()
var start = UnsafeHelper.getArrayOffset(buffer).toInt() + bytesRead
val fullbuf = ByteArray(totalSizeInBytes.toInt())
// make full block copies
for (i in 0 until fullCopyCounts) {
gdxMusic.forceInvoke<Int>("read", arrayOf(fullbuf))
reset()
System.arraycopy(fullbuf, 0, buffer, start, fullbuf.size)
start += totalSizeInBytes.toInt()
}
// copy the remainders from the start of the samples
val partialBuf = ByteArray(partialCopyCountsInBytes)
gdxMusic.forceInvoke<Int>("read", arrayOf(partialBuf))
System.arraycopy(partialBuf, 0, buffer, start, partialCopyCountsInBytes)
samplesReadCount += partialCopyCountsInBytes / bytesPerSample
}
return bytesRead return bytesRead
} }
else { else {
val bytesToRead = minOf(buffer.size.toLong(), 4 * (samplesTotal - samplesReadCount)) val bytesToRead = minOf(buffer.size.toLong(), 2 * channels * (totalSizeInSamples - samplesReadCount))
if (bytesToRead <= 0) return bytesToRead.toInt()
UnsafeHelper.memcpyRaw(null, soundBuf!!.ptr + samplesReadCount * 4, buffer, UnsafeHelper.getArrayOffset(buffer), bytesToRead) if (!looping && bytesToRead <= 0) return bytesToRead.toInt()
// if (looping) printdbg(this, "toRAM music loop (bytes cursor: $samplesReadCount/$totalSizeInSamples, bytesToRead=$bytesToRead, buffer.size=${buffer.size})")
UnsafeHelper.memcpyRaw(
null,
soundBuf!!.ptr + samplesReadCount * bytesPerSample,
buffer,
UnsafeHelper.getArrayOffset(buffer),
bytesToRead
)
samplesReadCount += bytesToRead / bytesPerSample
// reached the end of the "tape"
if (looping && bytesToRead < buffer.size) {
val remainder = buffer.size - bytesToRead
val fullCopyCounts = remainder / totalSizeInBytes
val partialCopyCountsInBytes = remainder % totalSizeInBytes
var start = UnsafeHelper.getArrayOffset(buffer) + bytesToRead
// make full block copies
for (i in 0 until fullCopyCounts) {
UnsafeHelper.memcpyRaw(
null,
soundBuf!!.ptr,
buffer,
start,
totalSizeInBytes
)
start += totalSizeInBytes
}
// copy the remainders from the start of the "tape"
UnsafeHelper.memcpyRaw(
null,
soundBuf!!.ptr,
buffer,
start,
partialCopyCountsInBytes
)
samplesReadCount = partialCopyCountsInBytes / bytesPerSample
}
if (looping) return buffer.size
samplesReadCount += bytesToRead / 4
return bytesToRead.toInt() return bytesToRead.toInt()
} }
} }
fun reset() { override fun reset() {
samplesReadCount = 0L samplesReadCount = 0L
gdxMusic.forceInvoke<Int>("reset", arrayOf()) gdxMusic.forceInvoke<Int>("reset", arrayOf())
} }
override fun currentPositionInSamples() = samplesReadCount
private fun getWavFileSampleCount(file: File): Long { private fun getWavFileSampleCount(file: File): Long {
return try { return try {
val ais = AudioSystem.getAudioInputStream(file) val ais = AudioSystem.getAudioInputStream(file)
@@ -186,7 +288,7 @@ data class MusicContainer(
soundBuf?.destroy() soundBuf?.destroy()
} }
fun makeCopy(): MusicContainer { override fun makeCopy(): AudioBank {
val new = MusicContainer(name, file, looping, false, songFinishedHook) val new = MusicContainer(name, file, looping, false, songFinishedHook)
synchronized(this) { synchronized(this) {

View File

@@ -42,7 +42,7 @@ class TerrarumAudioMixerTrack(
acc or (c shl (5*i)) acc or (c shl (5*i))
} }
var currentTrack: MusicContainer? = null var currentTrack: AudioBank? = null
set(value) { set(value) {
field = if (value == null) field = if (value == null)
null null
@@ -50,7 +50,7 @@ class TerrarumAudioMixerTrack(
musicCache.getOrPut(value) musicCache.getOrPut(value)
} }
var nextTrack: MusicContainer? = null var nextTrack: AudioBank? = null
set(value) { set(value) {
field = if (value == null) field = if (value == null)
null null

View File

@@ -6,6 +6,7 @@ import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.App.printdbgerr import net.torvald.terrarum.App.printdbgerr
import net.torvald.terrarum.INGAME import net.torvald.terrarum.INGAME
import net.torvald.terrarum.Terrarum import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.audio.AudioBank
import net.torvald.terrarum.audio.MusicContainer import net.torvald.terrarum.audio.MusicContainer
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack import net.torvald.terrarum.audio.TerrarumAudioMixerTrack
import net.torvald.terrarum.audio.TrackVolume import net.torvald.terrarum.audio.TrackVolume
@@ -216,7 +217,7 @@ abstract class Actor : Comparable<Actor>, Runnable {
}*/ }*/
open @Event fun onAudioInterrupt(music: MusicContainer) { open @Event fun onAudioInterrupt(music: AudioBank) {
music.songFinishedHook(music) music.songFinishedHook(music)
} }

View File

@@ -4,6 +4,7 @@ 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.AudioMixer
import net.torvald.terrarum.audio.MusicContainer import net.torvald.terrarum.audio.MusicContainer
import net.torvald.terrarum.gameworld.WorldTime.Companion.DAY_LENGTH import net.torvald.terrarum.gameworld.WorldTime.Companion.DAY_LENGTH
@@ -199,17 +200,15 @@ class TerrarumMusicGovernor : MusicGovernor() {
var playCaller: Any? = null; private set var playCaller: Any? = null; private set
var stopCallTime: Long? = null; private set var stopCallTime: Long? = null; private set
private fun stopMusic0(song: MusicContainer?, callStopMusicHook: Boolean = true, customPauseLen: Float? = null) { private fun stopMusic0(song: AudioBank?, callStopMusicHook: Boolean = true, customPauseLen: Float? = null) {
musicState = if (customPauseLen == Float.POSITIVE_INFINITY) STATE_INIT else STATE_INTERMISSION musicState = if (customPauseLen == Float.POSITIVE_INFINITY) STATE_INIT else STATE_INTERMISSION
// printdbg(this, "stopMusic1 customLen=$customPauseLen, stateNow: $musicState, called by") // printdbg(this, "stopMusic1 customLen=$customPauseLen, stateNow: $musicState, called by")
// printStackTrace(this) // printStackTrace(this)
intermissionAkku = 0f intermissionAkku = 0f
intermissionLength = customPauseLen ?: getRandomMusicInterval() intermissionLength = customPauseLen ?: getRandomMusicInterval()
musicFired = false musicFired = false
if (callStopMusicHook && musicStopHooks.isNotEmpty()) musicStopHooks.forEach { if (callStopMusicHook && musicStopHooks.isNotEmpty() && song is MusicContainer) musicStopHooks.forEach {
if (song != null) { it(song)
it(song)
}
} }
// printdbg(this, "StopMusic Intermission: $intermissionLength seconds") // printdbg(this, "StopMusic Intermission: $intermissionLength seconds")
} }

View File

@@ -664,7 +664,7 @@ class BasicDebugInfoWindow : UICanvas() {
if (it.length > 7) it.replace(" ", "").let { it.substring(0 until minOf(it.length, 7)) } if (it.length > 7) it.replace(" ", "").let { it.substring(0 until minOf(it.length, 7)) }
else it else it
}, },
"C:${track.currentTrack?.codec ?: ""}", //"C:${track.currentTrack?.codec ?: ""}",
"R:${track.currentTrack?.samplingRate ?: ""}", "R:${track.currentTrack?.samplingRate ?: ""}",
).forEachIndexed { i, s -> ).forEachIndexed { i, s ->
// gauge background // gauge background
@@ -673,7 +673,7 @@ class BasicDebugInfoWindow : UICanvas() {
// fill the song title line with a progress bar // fill the song title line with a progress bar
if (i == 0 && track.currentTrack != null) { if (i == 0 && track.currentTrack != null) {
val perc = (track.currentTrack!!.samplesReadCount.toFloat() / track.currentTrack!!.samplesTotal).coerceAtMost(1f) val perc = (track.currentTrack!!.currentPositionInSamples().toFloat() / track.currentTrack!!.totalSizeInSamples).coerceAtMost(1f)
batch.color = COL_PROGRESS_GRAD2 batch.color = COL_PROGRESS_GRAD2
Toolkit.fillArea(batch, x.toFloat(), faderY - (i + 1) * 16f, STRIP_W * perc, 14f) Toolkit.fillArea(batch, x.toFloat(), faderY - (i + 1) * 16f, STRIP_W * perc, 14f)
batch.color = COL_PROGRESS_GRAD batch.color = COL_PROGRESS_GRAD