mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-06-13 03:54:06 +09:00
Compare commits
16 Commits
v0.4.0-23w
...
v0.4.0-23w
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6025d8b13c | ||
|
|
d0eb5428e8 | ||
|
|
3233e57680 | ||
|
|
be35d1b4e0 | ||
|
|
b79232cd9b | ||
|
|
95225b56b4 | ||
|
|
4be7e728c9 | ||
|
|
ceb2b0693b | ||
|
|
f15ad227b1 | ||
|
|
39737616e8 | ||
|
|
b0149bcba3 | ||
|
|
82e89452d6 | ||
|
|
96c9aed25a | ||
|
|
949376b26a | ||
|
|
cc7f7b11d8 | ||
|
|
55789a3671 |
@@ -26,10 +26,10 @@ import kotlin.math.roundToInt
|
|||||||
/**
|
/**
|
||||||
* Created by minjaesong on 2023-06-24.
|
* Created by minjaesong on 2023-06-24.
|
||||||
*/
|
*/
|
||||||
class SavegameCollection(files0: List<DiskSkimmer>, prefiltered: Boolean) {
|
class SavegameCollection(files0: List<DiskSkimmer>) {
|
||||||
|
|
||||||
/** Sorted in reverse by the last modified time of the files, index zero being the most recent */
|
/** Sorted in reverse by the last modified time of the files, index zero being the most recent */
|
||||||
val files = if (prefiltered) files0 else files0.sortedBy { it.diskFile.name }.sortedByDescending {
|
val files = files0.sortedBy { it.diskFile.name }.sortedByDescending {
|
||||||
it.getLastModifiedTime().shl(2) or
|
it.getLastModifiedTime().shl(2) or
|
||||||
it.diskFile.extension.matches(Regex("^[abc]${'$'}")).toLong(1) or
|
it.diskFile.extension.matches(Regex("^[abc]${'$'}")).toLong(1) or
|
||||||
it.diskFile.extension.isBlank().toLong(0)
|
it.diskFile.extension.isBlank().toLong(0)
|
||||||
@@ -54,14 +54,14 @@ class SavegameCollection(files0: List<DiskSkimmer>, prefiltered: Boolean) {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun collectFromBaseFilename(skimmers: List<DiskSkimmer>, name: String): SavegameCollection {
|
fun collectFromBaseFilename(skimmers: List<DiskSkimmer>, name: String): SavegameCollection {
|
||||||
return SavegameCollection(skimmers, true)
|
return SavegameCollection(skimmers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun collectFromBaseFilename(dir: File, name: String): SavegameCollection {
|
fun collectFromBaseFilename(dir: File, name: String): SavegameCollection {
|
||||||
val files = dir.listFiles().filter { it.name.startsWith(name) }
|
val files = dir.listFiles().filter { it.name.startsWith(name) }
|
||||||
.mapNotNull { try { DiskSkimmer(it, true) } catch (e: Throwable) { null } }
|
.mapNotNull { try { DiskSkimmer(it, true) } catch (e: Throwable) { null } }
|
||||||
return SavegameCollection(files, false)
|
return SavegameCollection(files)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -309,10 +309,11 @@ object AudioMixer: Disposable {
|
|||||||
|
|
||||||
// stop streaming if fadeBus is muted
|
// stop streaming if fadeBus is muted
|
||||||
if (req.fadeTarget == 0.0 && track == fadeBus) {
|
if (req.fadeTarget == 0.0 && track == fadeBus) {
|
||||||
|
musicTrack.stop()
|
||||||
musicTrack.currentTrack = null
|
musicTrack.currentTrack = null
|
||||||
musicTrack.streamPlaying = false
|
|
||||||
|
ambientTrack.stop()
|
||||||
ambientTrack.currentTrack = null
|
ambientTrack.currentTrack = null
|
||||||
ambientTrack.streamPlaying = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,64 +1,194 @@
|
|||||||
package net.torvald.terrarum.audio
|
package net.torvald.terrarum.audio
|
||||||
|
|
||||||
|
import com.jme3.math.FastMath
|
||||||
|
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.AUDIO_BUFFER_SIZE
|
||||||
|
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RATE
|
||||||
|
import net.torvald.terrarum.ceilToInt
|
||||||
|
import net.torvald.terrarum.floorToInt
|
||||||
import net.torvald.terrarum.serialise.toUint
|
import net.torvald.terrarum.serialise.toUint
|
||||||
|
import org.dyn4j.Epsilon
|
||||||
|
import kotlin.math.PI
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
import kotlin.math.sin
|
||||||
|
|
||||||
|
private data class Frac(var nom: Int, val denom: Int) {
|
||||||
|
fun toDouble() = nom.toDouble() / denom.toDouble()
|
||||||
|
fun toFloat() = this.toDouble().toFloat()
|
||||||
|
private val denomStrLen = denom.toString().length
|
||||||
|
override fun toString() = "${nom.toString().padStart(denomStrLen)} / $denom"
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Audio is assumed to be 16 bits
|
* Audio is assumed to be 2 channels, 16 bits
|
||||||
*
|
*
|
||||||
* Created by minjaesong on 2023-11-17.
|
* Created by minjaesong on 2023-11-17.
|
||||||
*/
|
*/
|
||||||
class AudioProcessBuf(val size: Int) {
|
class AudioProcessBuf(inputSamplingRate: Int, val audioReadFun: (ByteArray) -> Int?, val onAudioFinished: () -> Unit) {
|
||||||
|
|
||||||
var buf0 = ByteArray(size); private set
|
private val doResample = inputSamplingRate != SAMPLING_RATE
|
||||||
var buf1 = ByteArray(size); private set
|
|
||||||
|
|
||||||
var fbuf0 = FloatArray(size / 2); private set
|
companion object {
|
||||||
var fbuf1 = FloatArray(size / 2); private set
|
private val epsilon: Double = Epsilon.E
|
||||||
|
|
||||||
private fun shift(): ByteArray {
|
private val TAPS = 4 // 2*a tap lanczos intp. Lower = greater artefacts
|
||||||
buf0 = buf1
|
|
||||||
buf1 = ByteArray(size)
|
private val Lcache = HashMap<Long, Double>(1048576)
|
||||||
return buf1
|
fun L(x: Double): Double {
|
||||||
|
return Lcache.getOrPut(x.toBits()) { // converting double to longbits allows faster cache lookup?!
|
||||||
|
if (x.absoluteValue < epsilon)
|
||||||
|
1.0
|
||||||
|
else if (-TAPS <= x && x < TAPS)
|
||||||
|
(TAPS * sin(PI * x) * sin(PI * x / TAPS)) / (PI * PI * x * x)
|
||||||
|
else
|
||||||
|
0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val BS = AUDIO_BUFFER_SIZE
|
||||||
|
private val MP3_CHUNK_SIZE = 1152 // 1152 for 32k-48k, 576 for 16k-24k, 384 for 8k-12k
|
||||||
|
|
||||||
|
|
||||||
|
private val bufLut = HashMap<Pair<Int, Int>, Int>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
val bl = arrayOf(
|
||||||
|
1152,1380,1814,1792,2304,2634,3502,3456,4608,5141,6874,6912,
|
||||||
|
1280,1508,1942,1920,2304,2762,3630,3584,4608,5267,7004,6912,
|
||||||
|
1536,1764,2198,2176,2560,3018,3886,3840,4608,5519,7260,7168,
|
||||||
|
2048,2276,2710,2688,3072,3530,4398,4352,5120,6023,7772,7680,
|
||||||
|
4096,4554,5421,5376,6144,7056,8796,8704,10240,12078,15544,15360
|
||||||
|
)
|
||||||
|
|
||||||
|
arrayOf(48000,44100,32768,32000,24000,22050,16384,16000,12000,11025,8192,8000).forEachIndexed { ri, r ->
|
||||||
|
arrayOf(128,256,512,1024,2048).forEachIndexed { bi, b ->
|
||||||
|
bufLut[b to r] = bl[bi * 12 + ri]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getOptimalBufferSize(rate: Int) = bufLut[BS to rate]!!
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateFloats() {
|
private val q = inputSamplingRate.toDouble() / SAMPLING_RATE // <= 1.0
|
||||||
fbuf0 = fbuf1
|
|
||||||
fbuf1 = FloatArray(size / 2) {
|
private val fetchSize = (BS.toFloat() / MP3_CHUNK_SIZE).ceilToInt() * MP3_CHUNK_SIZE // fetchSize is always multiple of MP3_CHUNK_SIZE, even if the audio is NOT MP3
|
||||||
val i16 = (buf1[2*it].toUint() or buf1[2*it+1].toUint().shl(8)).toShort()
|
private val internalBufferSize = getOptimalBufferSize(inputSamplingRate)// fetchSize * 3
|
||||||
i16 / 32767f
|
|
||||||
|
private val PADSIZE = TAPS + 1
|
||||||
|
|
||||||
|
private fun resampleBlock(innL: FloatArray, innR: FloatArray, outL: FloatArray, outR: FloatArray, outSampleCount: Int) {
|
||||||
|
for (sampleIdx in 0 until outSampleCount) {
|
||||||
|
val t = sampleIdx.toDouble() * q
|
||||||
|
val leftBound = maxOf(0, (t - TAPS + 1).floorToInt())
|
||||||
|
val rightBound = minOf(innL.size - 1, (t + TAPS).ceilToInt())
|
||||||
|
|
||||||
|
|
||||||
|
var akkuL = 0.0
|
||||||
|
var akkuR = 0.0
|
||||||
|
var weightedSum = 0.0
|
||||||
|
|
||||||
|
for (j in leftBound..rightBound) {
|
||||||
|
val w = L(t - j.toDouble())
|
||||||
|
akkuL += innL[j] * w
|
||||||
|
akkuR += innR[j] * w
|
||||||
|
weightedSum += w
|
||||||
|
}
|
||||||
|
|
||||||
|
outL[sampleIdx] = (akkuL / weightedSum).toFloat()
|
||||||
|
outR[sampleIdx] = (akkuR / weightedSum).toFloat()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fetchBytes(action: (ByteArray) -> Unit) {
|
var validSamplesInBuf = 0
|
||||||
action(shift())
|
|
||||||
updateFloats()
|
private val finL = FloatArray(fetchSize + 2 * PADSIZE)
|
||||||
|
private val finR = FloatArray(fetchSize + 2 * PADSIZE)
|
||||||
|
private val fmidL = FloatArray((fetchSize / q + 1.0).toInt())
|
||||||
|
private val fmidR = FloatArray((fetchSize / q + 1.0).toInt())
|
||||||
|
private val foutL = FloatArray(internalBufferSize) // 640 for (44100, 48000), 512 for (48000, 48000) with BUFFER_SIZE = 512 * 4
|
||||||
|
private val foutR = FloatArray(internalBufferSize) // 640 for (44100, 48000), 512 for (48000, 48000) with BUFFER_SIZE = 512 * 4
|
||||||
|
private val readBuf = ByteArray(fetchSize * 4)
|
||||||
|
|
||||||
|
private fun shift(array: FloatArray, size: Int) {
|
||||||
|
System.arraycopy(array, size, array, 0, array.size - size)
|
||||||
|
for (i in array.size - size until array.size) { array[i] = 0f }
|
||||||
}
|
}
|
||||||
|
|
||||||
// reusing a buffer causes tons of blips in the sound? how??
|
fun fetchBytes() {
|
||||||
/*private val L0buf = FloatArray(size / 4)
|
val readCount = if (validSamplesInBuf < BS) fetchSize else 0
|
||||||
private val R0buf = FloatArray(size / 4)
|
val writeCount = (readCount / q).roundToInt()
|
||||||
private val L1buf = FloatArray(size / 4)
|
|
||||||
private val R1buf = FloatArray(size / 4)
|
|
||||||
|
|
||||||
fun getL0(volume: Double): FloatArray {
|
fun getFromReadBuf(i: Int, bytesRead: Int) = if (i < bytesRead) readBuf[i].toUint() else 0
|
||||||
for (i in L0buf.indices) { L0buf[i] = (volume * fbuf0[2*i]).toFloat() }
|
|
||||||
return L0buf
|
|
||||||
}
|
|
||||||
fun getR0(volume: Double): FloatArray {
|
|
||||||
for (i in R0buf.indices) { R0buf[i] = (volume * fbuf0[2*i+1]).toFloat() }
|
|
||||||
return R0buf
|
|
||||||
}
|
|
||||||
fun getL1(volume: Double): FloatArray {
|
|
||||||
for (i in L1buf.indices) { L1buf[i] = (volume * fbuf1[2*i]).toFloat() }
|
|
||||||
return L1buf
|
|
||||||
}
|
|
||||||
fun getR1(volume: Double): FloatArray {
|
|
||||||
for (i in R1buf.indices) { R1buf[i] = (volume * fbuf1[2*i+1]).toFloat() }
|
|
||||||
return R1buf
|
|
||||||
}*/
|
|
||||||
fun getL0(volume: Double) = FloatArray(size / 4) { (volume * fbuf0[2*it]).toFloat() }
|
|
||||||
fun getR0(volume: Double) = FloatArray(size / 4) { (volume * fbuf0[2*it+1]).toFloat() }
|
|
||||||
fun getL1(volume: Double) = FloatArray(size / 4) { (volume * fbuf1[2*it]).toFloat() }
|
|
||||||
fun getR1(volume: Double) = FloatArray(size / 4) { (volume * fbuf1[2*it+1]).toFloat() }
|
|
||||||
|
|
||||||
|
if (readCount > 0) {
|
||||||
|
try {
|
||||||
|
shift(finL, readCount)
|
||||||
|
shift(finR, readCount)
|
||||||
|
|
||||||
|
val bytesRead = audioReadFun(readBuf)
|
||||||
|
// printdbg(this, "Reading audio $readCount samples, got ${bytesRead?.div(4)} samples")
|
||||||
|
|
||||||
|
if (bytesRead == null || bytesRead <= 0) {
|
||||||
|
// printdbg(this, "Music finished; bytesRead = $bytesRead")
|
||||||
|
|
||||||
|
onAudioFinished()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (c in 0 until readCount) {
|
||||||
|
val sl = (getFromReadBuf(4 * c + 0, bytesRead) or getFromReadBuf(4 * c + 1, bytesRead).shl(8)).toShort()
|
||||||
|
val sr = (getFromReadBuf(4 * c + 2, bytesRead) or getFromReadBuf(4 * c + 3, bytesRead).shl(8)).toShort()
|
||||||
|
|
||||||
|
val fl = sl / 32767f
|
||||||
|
val fr = sr / 32767f
|
||||||
|
|
||||||
|
finL[2 * PADSIZE + c] = fl
|
||||||
|
finR[2 * PADSIZE + c] = fr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e: Throwable) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (doResample) {
|
||||||
|
// perform resampling
|
||||||
|
resampleBlock(finL, finR, fmidL, fmidR, writeCount)
|
||||||
|
|
||||||
|
// fill in the output buffers
|
||||||
|
System.arraycopy(fmidL, 0, foutL, validSamplesInBuf, writeCount)
|
||||||
|
System.arraycopy(fmidR, 0, foutR, validSamplesInBuf, writeCount)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// fill in the output buffers
|
||||||
|
System.arraycopy(finL, 0, foutL, validSamplesInBuf, writeCount)
|
||||||
|
System.arraycopy(finR, 0, foutR, validSamplesInBuf, writeCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
validSamplesInBuf += writeCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// printdbg(this, "Reading audio zero samples; Buffer: $validSamplesInBuf / $internalBufferSize samples")
|
||||||
|
}
|
||||||
|
|
||||||
|
// printdbg(this, "phase = $fPhaseL")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLR(volume: Double): Pair<FloatArray, FloatArray> {
|
||||||
|
// copy into the out
|
||||||
|
val outL = FloatArray(BS) { (foutL[it] * volume).toFloat() }
|
||||||
|
val outR = FloatArray(BS) { (foutR[it] * volume).toFloat() }
|
||||||
|
// shift bytes in the fout
|
||||||
|
System.arraycopy(foutL, BS, foutL, 0, validSamplesInBuf - BS)
|
||||||
|
System.arraycopy(foutR, BS, foutR, 0, validSamplesInBuf - BS)
|
||||||
|
for (i in validSamplesInBuf until BS) {
|
||||||
|
foutL[i] = 0f
|
||||||
|
foutR[i] = 0f
|
||||||
|
}
|
||||||
|
// decrement necessary variables
|
||||||
|
validSamplesInBuf -= BS
|
||||||
|
|
||||||
|
return outL to outR
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,7 @@ import kotlin.math.*
|
|||||||
/**
|
/**
|
||||||
* Created by minjaesong on 2023-11-17.
|
* Created by minjaesong on 2023-11-17.
|
||||||
*/
|
*/
|
||||||
class MixerTrackProcessor(val bufferSize: Int, val rate: Int, val track: TerrarumAudioMixerTrack): Runnable {
|
class MixerTrackProcessor(val buffertaille: Int, val rate: Int, val track: TerrarumAudioMixerTrack): Runnable {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val BACK_BUF_COUNT = 1
|
val BACK_BUF_COUNT = 1
|
||||||
@@ -24,11 +24,10 @@ class MixerTrackProcessor(val bufferSize: Int, val rate: Int, val track: Terraru
|
|||||||
private val pauseLock = java.lang.Object()
|
private val pauseLock = java.lang.Object()
|
||||||
|
|
||||||
|
|
||||||
private val emptyBuf = FloatArray(bufferSize / 4)
|
private val emptyBuf = FloatArray(buffertaille)
|
||||||
|
|
||||||
|
|
||||||
internal val streamBuf = AudioProcessBuf(bufferSize)
|
internal var streamBuf: AudioProcessBuf? = null
|
||||||
internal val sideChainBufs = Array(track.sidechainInputs.size) { AudioProcessBuf(bufferSize) }
|
|
||||||
|
|
||||||
private var fout1 = listOf(emptyBuf, emptyBuf)
|
private var fout1 = listOf(emptyBuf, emptyBuf)
|
||||||
|
|
||||||
@@ -43,6 +42,22 @@ class MixerTrackProcessor(val bufferSize: Int, val rate: Int, val track: Terraru
|
|||||||
private fun printdbg(msg: Any) {
|
private fun printdbg(msg: Any) {
|
||||||
if (true) App.printdbg("AudioAdapter ${track.name}", msg)
|
if (true) App.printdbg("AudioAdapter ${track.name}", msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun allocateStreamBuf(track: TerrarumAudioMixerTrack) {
|
||||||
|
printdbg("Allocating a StreamBuf with rate ${track.currentTrack!!.samplingRate}")
|
||||||
|
streamBuf = AudioProcessBuf(track.currentTrack!!.samplingRate, {
|
||||||
|
val l = track.currentTrack?.gdxMusic?.forceInvoke<Int>("read", arrayOf(it))
|
||||||
|
track.currentTrack?.let {
|
||||||
|
it.samplesRead += l!! / 4
|
||||||
|
}
|
||||||
|
l
|
||||||
|
}, {
|
||||||
|
track.stop()
|
||||||
|
this.streamBuf = null
|
||||||
|
printdbg("StreamBuf is now null")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
override fun run() {
|
override fun run() {
|
||||||
// while (running) { // uncomment to multithread
|
// while (running) { // uncomment to multithread
|
||||||
/*synchronized(pauseLock) { // uncomment to multithread
|
/*synchronized(pauseLock) { // uncomment to multithread
|
||||||
@@ -91,20 +106,8 @@ class MixerTrackProcessor(val bufferSize: Int, val rate: Int, val track: Terraru
|
|||||||
|
|
||||||
// fetch deviceBufferSize amount of sample from the disk
|
// fetch deviceBufferSize amount of sample from the disk
|
||||||
if (track.trackType != TrackType.MASTER && track.trackType != TrackType.BUS && track.streamPlaying) {
|
if (track.trackType != TrackType.MASTER && track.trackType != TrackType.BUS && track.streamPlaying) {
|
||||||
streamBuf.fetchBytes {
|
if (streamBuf == null && track.currentTrack != null) allocateStreamBuf(track)
|
||||||
try {
|
streamBuf!!.fetchBytes()
|
||||||
val bytesRead = track.currentTrack?.gdxMusic?.forceInvoke<Int>("read", arrayOf(it))
|
|
||||||
if (bytesRead == null || bytesRead <= 0) { // some class (namely Mp3) may return 0 instead of negative value
|
|
||||||
// printdbg("Finished reading audio stream")
|
|
||||||
track.stop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (e: Throwable) {
|
|
||||||
e.printStackTrace()
|
|
||||||
it.fill(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var samplesL1: FloatArray
|
var samplesL1: FloatArray
|
||||||
@@ -115,8 +118,8 @@ class MixerTrackProcessor(val bufferSize: Int, val rate: Int, val track: Terraru
|
|||||||
// get samples and apply the fader
|
// get samples and apply the fader
|
||||||
if (track.trackType == TrackType.MASTER || track.trackType == TrackType.BUS) {
|
if (track.trackType == TrackType.MASTER || track.trackType == TrackType.BUS) {
|
||||||
// combine all the inputs
|
// combine all the inputs
|
||||||
samplesL1 = FloatArray(bufferSize / 4)
|
samplesL1 = FloatArray(buffertaille)
|
||||||
samplesR1 = FloatArray(bufferSize / 4)
|
samplesR1 = FloatArray(buffertaille)
|
||||||
|
|
||||||
val sidechains = track.sidechainInputs
|
val sidechains = track.sidechainInputs
|
||||||
// add all up
|
// add all up
|
||||||
@@ -129,15 +132,17 @@ class MixerTrackProcessor(val bufferSize: Int, val rate: Int, val track: Terraru
|
|||||||
}
|
}
|
||||||
// source channel: skip processing if there's no active input
|
// source channel: skip processing if there's no active input
|
||||||
// else if (track.getSidechains().any { it != null && !it.isBus && !it.isMaster && !it.streamPlaying } && !track.streamPlaying) {
|
// else if (track.getSidechains().any { it != null && !it.isBus && !it.isMaster && !it.streamPlaying } && !track.streamPlaying) {
|
||||||
else if (!track.streamPlaying) {
|
else if (!track.streamPlaying || streamBuf == null) {
|
||||||
samplesL1 = emptyBuf
|
samplesL1 = emptyBuf
|
||||||
samplesR1 = emptyBuf
|
samplesR1 = emptyBuf
|
||||||
|
|
||||||
bufEmpty = true
|
bufEmpty = true
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
samplesL1 = streamBuf.getL1(track.volume)
|
streamBuf!!.getLR(track.volume).let {
|
||||||
samplesR1 = streamBuf.getR1(track.volume)
|
samplesL1 = it.first
|
||||||
|
samplesR1 = it.second
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bufEmpty) {
|
if (!bufEmpty) {
|
||||||
@@ -149,13 +154,13 @@ class MixerTrackProcessor(val bufferSize: Int, val rate: Int, val track: Terraru
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var fin1 = listOf(samplesL1, samplesR1)
|
var fin1 = listOf(samplesL1, samplesR1)
|
||||||
fout1 = listOf(FloatArray(bufferSize / 4), FloatArray(bufferSize / 4))
|
fout1 = listOf(FloatArray(buffertaille), FloatArray(buffertaille))
|
||||||
|
|
||||||
filterStack.forEachIndexed { index, it ->
|
filterStack.forEachIndexed { index, it ->
|
||||||
it(fin1, fout1)
|
it(fin1, fout1)
|
||||||
fin1 = fout1
|
fin1 = fout1
|
||||||
if (index < filterStack.lastIndex) {
|
if (index < filterStack.lastIndex) {
|
||||||
fout1 = listOf(FloatArray(bufferSize / 4), FloatArray(bufferSize / 4))
|
fout1 = listOf(FloatArray(buffertaille), FloatArray(buffertaille))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,7 +171,7 @@ class MixerTrackProcessor(val bufferSize: Int, val rate: Int, val track: Terraru
|
|||||||
maxSigLevel[index] = fl.toDouble()
|
maxSigLevel[index] = fl.toDouble()
|
||||||
}
|
}
|
||||||
fout1.map { it.sumOf { it.sqr().toDouble() } }.forEachIndexed { index, fl ->
|
fout1.map { it.sumOf { it.sqr().toDouble() } }.forEachIndexed { index, fl ->
|
||||||
maxRMS[index] = sqrt(fl / (bufferSize / 4))
|
maxRMS[index] = sqrt(fl / (buffertaille))
|
||||||
}
|
}
|
||||||
hasClipping.fill(false)
|
hasClipping.fill(false)
|
||||||
fout1.forEachIndexed { index, floats ->
|
fout1.forEachIndexed { index, floats ->
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import net.torvald.terrarum.App
|
|||||||
import net.torvald.terrarum.audio.dsp.NullFilter
|
import net.torvald.terrarum.audio.dsp.NullFilter
|
||||||
import net.torvald.terrarum.audio.dsp.TerrarumAudioFilter
|
import net.torvald.terrarum.audio.dsp.TerrarumAudioFilter
|
||||||
import net.torvald.terrarum.gameactors.Actor
|
import net.torvald.terrarum.gameactors.Actor
|
||||||
import net.torvald.terrarum.gameactors.ActorWithBody
|
|
||||||
import net.torvald.terrarum.getHashStr
|
import net.torvald.terrarum.getHashStr
|
||||||
import net.torvald.terrarum.hashStrMap
|
import net.torvald.terrarum.hashStrMap
|
||||||
import net.torvald.terrarum.modulebasegame.MusicContainer
|
import net.torvald.terrarum.modulebasegame.MusicContainer
|
||||||
@@ -29,7 +28,7 @@ class TerrarumAudioMixerTrack(val name: String, val trackType: TrackType, var ma
|
|||||||
const val SAMPLING_RATE = 48000
|
const val SAMPLING_RATE = 48000
|
||||||
const val SAMPLING_RATEF = 48000f
|
const val SAMPLING_RATEF = 48000f
|
||||||
const val SAMPLING_RATED = 48000.0
|
const val SAMPLING_RATED = 48000.0
|
||||||
val BUFFER_SIZE = 4 * App.getConfigInt("audio_buffer_size") // n ms -> 384 * n
|
val AUDIO_BUFFER_SIZE = App.getConfigInt("audio_buffer_size") // n ms -> 384 * n
|
||||||
}
|
}
|
||||||
|
|
||||||
val hash = getHashStr()
|
val hash = getHashStr()
|
||||||
@@ -139,6 +138,7 @@ class TerrarumAudioMixerTrack(val name: String, val trackType: TrackType, var ma
|
|||||||
override fun equals(other: Any?) = this.hash == (other as TerrarumAudioMixerTrack).hash
|
override fun equals(other: Any?) = this.hash == (other as TerrarumAudioMixerTrack).hash
|
||||||
|
|
||||||
fun stop() {
|
fun stop() {
|
||||||
|
currentTrack?.samplesRead = 0L
|
||||||
currentTrack?.gdxMusic?.forceInvoke<Int>("reset", arrayOf())
|
currentTrack?.gdxMusic?.forceInvoke<Int>("reset", arrayOf())
|
||||||
streamPlaying = false
|
streamPlaying = false
|
||||||
// playStartedTime = 0L
|
// playStartedTime = 0L
|
||||||
@@ -151,6 +151,7 @@ class TerrarumAudioMixerTrack(val name: String, val trackType: TrackType, var ma
|
|||||||
// fireSoundFinishHook()
|
// fireSoundFinishHook()
|
||||||
|
|
||||||
trackingTarget = null
|
trackingTarget = null
|
||||||
|
processor.streamBuf = null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fireSongFinishHook() {
|
fun fireSongFinishHook() {
|
||||||
@@ -160,7 +161,7 @@ class TerrarumAudioMixerTrack(val name: String, val trackType: TrackType, var ma
|
|||||||
|
|
||||||
// 1st ring of the hell: the THREADING HELL //
|
// 1st ring of the hell: the THREADING HELL //
|
||||||
|
|
||||||
internal var processor = MixerTrackProcessor(BUFFER_SIZE, SAMPLING_RATE, this)
|
internal var processor = MixerTrackProcessor(AUDIO_BUFFER_SIZE, SAMPLING_RATE, this)
|
||||||
/*private val processorThread = Thread(processor).also { // uncomment to multithread
|
/*private val processorThread = Thread(processor).also { // uncomment to multithread
|
||||||
it.priority = MAX_PRIORITY // higher = more predictable; audio delay is very noticeable so it gets high priority
|
it.priority = MAX_PRIORITY // higher = more predictable; audio delay is very noticeable so it gets high priority
|
||||||
it.start()
|
it.start()
|
||||||
@@ -170,8 +171,8 @@ class TerrarumAudioMixerTrack(val name: String, val trackType: TrackType, var ma
|
|||||||
private lateinit var queueDispatcherThread: Thread
|
private lateinit var queueDispatcherThread: Thread
|
||||||
|
|
||||||
init {
|
init {
|
||||||
pcmQueue.addLast(listOf(FloatArray(BUFFER_SIZE / 4), FloatArray(BUFFER_SIZE / 4)))
|
pcmQueue.addLast(listOf(FloatArray(AUDIO_BUFFER_SIZE), FloatArray(AUDIO_BUFFER_SIZE)))
|
||||||
pcmQueue.addLast(listOf(FloatArray(BUFFER_SIZE / 4), FloatArray(BUFFER_SIZE / 4)))
|
pcmQueue.addLast(listOf(FloatArray(AUDIO_BUFFER_SIZE), FloatArray(AUDIO_BUFFER_SIZE)))
|
||||||
|
|
||||||
/*if (isMaster) { // uncomment to multithread
|
/*if (isMaster) { // uncomment to multithread
|
||||||
queueDispatcher = FeedSamplesToAdev(BUFFER_SIZE, SAMPLING_RATE, this)
|
queueDispatcher = FeedSamplesToAdev(BUFFER_SIZE, SAMPLING_RATE, this)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class BinoPan(var pan: Float, var earDist: Float = 0.18f): TerrarumAudioFilter()
|
|||||||
|
|
||||||
private val PANNING_CONST = 3.0 // 3dB panning rule
|
private val PANNING_CONST = 3.0 // 3dB panning rule
|
||||||
|
|
||||||
private val delayLine = FloatArray(TerrarumAudioMixerTrack.BUFFER_SIZE / 4)
|
private val delayLine = FloatArray(TerrarumAudioMixerTrack.AUDIO_BUFFER_SIZE)
|
||||||
|
|
||||||
private fun getFrom(index: Float, buf0: FloatArray, buf1: FloatArray): Float {
|
private fun getFrom(index: Float, buf0: FloatArray, buf1: FloatArray): Float {
|
||||||
val index = index.toInt() // TODO resampling
|
val index = index.toInt() // TODO resampling
|
||||||
@@ -55,7 +55,7 @@ class BinoPan(var pan: Float, var earDist: Float = 0.18f): TerrarumAudioFilter()
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (ch in 0..1) {
|
for (ch in 0..1) {
|
||||||
for (i in 0 until TerrarumAudioMixerTrack.BUFFER_SIZE / 4) {
|
for (i in 0 until TerrarumAudioMixerTrack.AUDIO_BUFFER_SIZE) {
|
||||||
outbuf[ch][i] = getFrom(i - delays[ch], delayLine, inbuf[0]) * mults[ch]
|
outbuf[ch][i] = getFrom(i - delays[ch], delayLine, inbuf[0]) * mults[ch]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import net.torvald.terrarum.roundToFloat
|
|||||||
class Bitcrush(var steps: Int, var inputGain: Float = 1f): TerrarumAudioFilter() {
|
class Bitcrush(var steps: Int, var inputGain: Float = 1f): TerrarumAudioFilter() {
|
||||||
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
|
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
|
||||||
for (ch in outbuf.indices) {
|
for (ch in outbuf.indices) {
|
||||||
for (i in 0 until TerrarumAudioMixerTrack.BUFFER_SIZE / 4) {
|
for (i in 0 until TerrarumAudioMixerTrack.AUDIO_BUFFER_SIZE) {
|
||||||
val inn = ((inbuf[ch][i] * inputGain).coerceIn(-1f, 1f) + 1f) / 2f // 0f..1f
|
val inn = ((inbuf[ch][i] * inputGain).coerceIn(-1f, 1f) + 1f) / 2f // 0f..1f
|
||||||
val stepped = (inn * (steps - 1)).roundToFloat() / (steps - 1)
|
val stepped = (inn * (steps - 1)).roundToFloat() / (steps - 1)
|
||||||
val out = (stepped * 2f) - 1f // -1f..1f
|
val out = (stepped * 2f) - 1f // -1f..1f
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
package net.torvald.terrarum.audio.dsp
|
package net.torvald.terrarum.audio.dsp
|
||||||
|
|
||||||
import com.jme3.math.FastMath
|
import com.jme3.math.FastMath
|
||||||
import net.torvald.terrarum.App.measureDebugTime
|
|
||||||
import net.torvald.terrarum.App.setDebugTime
|
import net.torvald.terrarum.App.setDebugTime
|
||||||
import net.torvald.terrarum.audio.*
|
import net.torvald.terrarum.audio.*
|
||||||
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.BUFFER_SIZE
|
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.AUDIO_BUFFER_SIZE
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class Convolv(ir: File, val gain: Float = 1f / 256f): TerrarumAudioFilter() {
|
class Convolv(ir: File, val gain: Float = 1f / 256f): TerrarumAudioFilter() {
|
||||||
@@ -13,7 +12,7 @@ class Convolv(ir: File, val gain: Float = 1f / 256f): TerrarumAudioFilter() {
|
|||||||
private val convFFT: Array<ComplexArray>
|
private val convFFT: Array<ComplexArray>
|
||||||
private val inbuf: Array<ComplexArray>
|
private val inbuf: Array<ComplexArray>
|
||||||
|
|
||||||
private val BLOCKSIZE = TerrarumAudioMixerTrack.BUFFER_SIZE / 4
|
private val BLOCKSIZE = TerrarumAudioMixerTrack.AUDIO_BUFFER_SIZE
|
||||||
|
|
||||||
var processingSpeed = 1f; private set
|
var processingSpeed = 1f; private set
|
||||||
|
|
||||||
@@ -60,7 +59,7 @@ class Convolv(ir: File, val gain: Float = 1f / 256f): TerrarumAudioFilter() {
|
|||||||
|
|
||||||
// fill up part* dictionary
|
// fill up part* dictionary
|
||||||
// define "master" array
|
// define "master" array
|
||||||
var c = BUFFER_SIZE / 4
|
var c = AUDIO_BUFFER_SIZE
|
||||||
val master0 = arrayListOf(c)
|
val master0 = arrayListOf(c)
|
||||||
while (c < fftLen) {
|
while (c < fftLen) {
|
||||||
master0.add(c)
|
master0.add(c)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import net.torvald.terrarum.audio.TerrarumAudioMixerTrack
|
|||||||
|
|
||||||
class Gain(var gain: Float): TerrarumAudioFilter() {
|
class Gain(var gain: Float): TerrarumAudioFilter() {
|
||||||
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
|
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
|
||||||
for (i in 0 until TerrarumAudioMixerTrack.BUFFER_SIZE / 4) {
|
for (i in 0 until TerrarumAudioMixerTrack.AUDIO_BUFFER_SIZE) {
|
||||||
outbuf[0][i] = inbuf[0][i] * gain
|
outbuf[0][i] = inbuf[0][i] * gain
|
||||||
outbuf[1][i] = inbuf[1][i] * gain
|
outbuf[1][i] = inbuf[1][i] * gain
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class Reverb(val delayMS: Float = 36f, var feedback: Float = 0.92f, var lowpass:
|
|||||||
val alphaHi = RCHi / (RCHi + dt)
|
val alphaHi = RCHi / (RCHi + dt)
|
||||||
|
|
||||||
for (ch in outbuf.indices) {
|
for (ch in outbuf.indices) {
|
||||||
for (i in 0 until TerrarumAudioMixerTrack.BUFFER_SIZE / 4) {
|
for (i in 0 until TerrarumAudioMixerTrack.AUDIO_BUFFER_SIZE) {
|
||||||
val inn = inbuf[ch][i]
|
val inn = inbuf[ch][i]
|
||||||
|
|
||||||
// reverb
|
// reverb
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import net.torvald.terrarum.audio.TerrarumAudioMixerTrack
|
|||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class Scope : TerrarumAudioFilter() {
|
class Scope : TerrarumAudioFilter() {
|
||||||
val backbufL = Array((4096f / TerrarumAudioMixerTrack.BUFFER_SIZE * 4).roundToInt().coerceAtLeast(1)) { FloatArray(
|
val backbufL = Array((4096f / TerrarumAudioMixerTrack.AUDIO_BUFFER_SIZE).roundToInt().coerceAtLeast(1)) { FloatArray(
|
||||||
TerrarumAudioMixerTrack.BUFFER_SIZE / 4) }
|
TerrarumAudioMixerTrack.AUDIO_BUFFER_SIZE) }
|
||||||
val backbufR = Array((4096f / TerrarumAudioMixerTrack.BUFFER_SIZE * 4).roundToInt().coerceAtLeast(1)) { FloatArray(
|
val backbufR = Array((4096f / TerrarumAudioMixerTrack.AUDIO_BUFFER_SIZE).roundToInt().coerceAtLeast(1)) { FloatArray(
|
||||||
TerrarumAudioMixerTrack.BUFFER_SIZE / 4) }
|
TerrarumAudioMixerTrack.AUDIO_BUFFER_SIZE) }
|
||||||
|
|
||||||
private val sqrt2p = 0.7071067811865475
|
private val sqrt2p = 0.7071067811865475
|
||||||
|
|
||||||
@@ -17,11 +17,11 @@ class Scope : TerrarumAudioFilter() {
|
|||||||
backbufL[i] = backbufL[i - 1]
|
backbufL[i] = backbufL[i - 1]
|
||||||
backbufR[i] = backbufR[i - 1]
|
backbufR[i] = backbufR[i - 1]
|
||||||
}
|
}
|
||||||
backbufL[0] = FloatArray(TerrarumAudioMixerTrack.BUFFER_SIZE / 4)
|
backbufL[0] = FloatArray(TerrarumAudioMixerTrack.AUDIO_BUFFER_SIZE)
|
||||||
backbufR[0] = FloatArray(TerrarumAudioMixerTrack.BUFFER_SIZE / 4)
|
backbufR[0] = FloatArray(TerrarumAudioMixerTrack.AUDIO_BUFFER_SIZE)
|
||||||
|
|
||||||
// plot dots
|
// plot dots
|
||||||
for (i in 0 until TerrarumAudioMixerTrack.BUFFER_SIZE /4) {
|
for (i in 0 until TerrarumAudioMixerTrack.AUDIO_BUFFER_SIZE) {
|
||||||
val y0 = inbuf[0][i] * 0.7
|
val y0 = inbuf[0][i] * 0.7
|
||||||
val x0 = -inbuf[1][i] * 0.7 // rotate the domain by -90 deg
|
val x0 = -inbuf[1][i] * 0.7 // rotate the domain by -90 deg
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import net.torvald.terrarum.audio.TerrarumAudioMixerTrack
|
|||||||
|
|
||||||
object XYtoMS: TerrarumAudioFilter() {
|
object XYtoMS: TerrarumAudioFilter() {
|
||||||
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
|
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
|
||||||
for (i in 0 until TerrarumAudioMixerTrack.BUFFER_SIZE / 4) {
|
for (i in 0 until TerrarumAudioMixerTrack.AUDIO_BUFFER_SIZE) {
|
||||||
val X = inbuf[0][i]
|
val X = inbuf[0][i]
|
||||||
val Y = inbuf[1][i]
|
val Y = inbuf[1][i]
|
||||||
val M = (X + Y) / 2f
|
val M = (X + Y) / 2f
|
||||||
@@ -17,7 +17,7 @@ object XYtoMS: TerrarumAudioFilter() {
|
|||||||
|
|
||||||
object MStoXY: TerrarumAudioFilter() {
|
object MStoXY: TerrarumAudioFilter() {
|
||||||
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
|
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
|
||||||
for (i in 0 until TerrarumAudioMixerTrack.BUFFER_SIZE / 4) {
|
for (i in 0 until TerrarumAudioMixerTrack.AUDIO_BUFFER_SIZE) {
|
||||||
val M = inbuf[0][i]
|
val M = inbuf[0][i]
|
||||||
val S = inbuf[1][i]
|
val S = inbuf[1][i]
|
||||||
val X = M + S
|
val X = M + S
|
||||||
|
|||||||
@@ -2,12 +2,23 @@ package net.torvald.terrarum.modulebasegame
|
|||||||
|
|
||||||
import com.badlogic.gdx.Gdx
|
import com.badlogic.gdx.Gdx
|
||||||
import com.badlogic.gdx.audio.Music
|
import com.badlogic.gdx.audio.Music
|
||||||
|
import com.badlogic.gdx.backends.lwjgl3.audio.Mp3
|
||||||
|
import com.badlogic.gdx.backends.lwjgl3.audio.Ogg
|
||||||
|
import com.badlogic.gdx.backends.lwjgl3.audio.OggInputStream
|
||||||
|
import com.badlogic.gdx.backends.lwjgl3.audio.Wav
|
||||||
|
import com.badlogic.gdx.backends.lwjgl3.audio.Wav.WavInputStream
|
||||||
import com.badlogic.gdx.utils.GdxRuntimeException
|
import com.badlogic.gdx.utils.GdxRuntimeException
|
||||||
|
import com.jcraft.jorbis.VorbisFile
|
||||||
|
import javazoom.jl.decoder.Bitstream
|
||||||
|
import net.torvald.reflection.extortField
|
||||||
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.AudioMixer
|
import net.torvald.terrarum.audio.AudioMixer
|
||||||
|
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RATE
|
||||||
import net.torvald.unicode.EMDASH
|
import net.torvald.unicode.EMDASH
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import javax.sound.sampled.AudioSystem
|
||||||
|
|
||||||
data class MusicContainer(
|
data class MusicContainer(
|
||||||
val name: String,
|
val name: String,
|
||||||
@@ -15,8 +26,107 @@ data class MusicContainer(
|
|||||||
val gdxMusic: Music,
|
val gdxMusic: Music,
|
||||||
val songFinishedHook: (Music) -> Unit
|
val songFinishedHook: (Music) -> Unit
|
||||||
) {
|
) {
|
||||||
|
val samplingRate: Int
|
||||||
|
val codec: String
|
||||||
|
|
||||||
|
var samplesRead = 0L; internal set
|
||||||
|
val samplesTotal: Long
|
||||||
|
|
||||||
init {
|
init {
|
||||||
gdxMusic.setOnCompletionListener(songFinishedHook)
|
gdxMusic.setOnCompletionListener(songFinishedHook)
|
||||||
|
|
||||||
|
samplingRate = when (gdxMusic) {
|
||||||
|
is Wav.Music -> {
|
||||||
|
val rate = gdxMusic.extortField<WavInputStream>("input")!!.sampleRate
|
||||||
|
|
||||||
|
printdbg(this, "music $name is WAV; rate = $rate")
|
||||||
|
rate
|
||||||
|
}
|
||||||
|
is Ogg.Music -> {
|
||||||
|
val rate = gdxMusic.extortField<OggInputStream>("input")!!.sampleRate
|
||||||
|
|
||||||
|
printdbg(this, "music $name is OGG; rate = $rate")
|
||||||
|
rate
|
||||||
|
}
|
||||||
|
is Mp3.Music -> {
|
||||||
|
val tempMusic = Gdx.audio.newMusic(Gdx.files.absolute(file.absolutePath))
|
||||||
|
val bitstream = tempMusic.extortField<Bitstream>("bitstream")!!
|
||||||
|
val header = bitstream.readFrame()
|
||||||
|
val rate = header.sampleRate
|
||||||
|
tempMusic.dispose()
|
||||||
|
|
||||||
|
// val bitstream = gdxMusic.extortField<Bitstream>("bitstream")!!
|
||||||
|
// val header = bitstream.readFrame()
|
||||||
|
// val rate = header.sampleRate
|
||||||
|
// gdxMusic.reset()
|
||||||
|
|
||||||
|
printdbg(this, "music $name is MP3; rate = $rate")
|
||||||
|
rate
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
printdbg(this, "music $name is ${gdxMusic::class.qualifiedName}; rate = default")
|
||||||
|
SAMPLING_RATE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
codec = gdxMusic::class.qualifiedName!!.split('.').let {
|
||||||
|
if (it.last() == "Music") it.dropLast(1).last() else it.last()
|
||||||
|
}
|
||||||
|
|
||||||
|
samplesTotal = when (gdxMusic) {
|
||||||
|
is Wav.Music -> getWavFileSampleCount(file)
|
||||||
|
is Ogg.Music -> getOggFileSampleCount(file)
|
||||||
|
is Mp3.Music -> getMp3FileSampleCount(file)
|
||||||
|
else -> Long.MAX_VALUE
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getWavFileSampleCount(file: File): Long {
|
||||||
|
return try {
|
||||||
|
val ais = AudioSystem.getAudioInputStream(file)
|
||||||
|
val r = ais.frameLength
|
||||||
|
ais.close()
|
||||||
|
r
|
||||||
|
}
|
||||||
|
catch (e: Throwable) {
|
||||||
|
Long.MAX_VALUE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getOggFileSampleCount(file: File): Long {
|
||||||
|
return try {
|
||||||
|
val vorbisFile = VorbisFile(file.absolutePath)
|
||||||
|
vorbisFile.pcm_total(0)
|
||||||
|
}
|
||||||
|
catch (e: Throwable) {
|
||||||
|
Long.MAX_VALUE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getMp3FileSampleCount(file: File): Long {
|
||||||
|
return try {
|
||||||
|
val fis = FileInputStream(file)
|
||||||
|
val bs = Bitstream(fis)
|
||||||
|
|
||||||
|
var header = bs.readFrame()
|
||||||
|
val rate = header.frequency()
|
||||||
|
var totalSamples = 0L
|
||||||
|
|
||||||
|
while (header != null) {
|
||||||
|
totalSamples += (header.ms_per_frame() * rate / 1000).toLong()
|
||||||
|
bs.closeFrame()
|
||||||
|
header = bs.readFrame()
|
||||||
|
}
|
||||||
|
|
||||||
|
bs.close()
|
||||||
|
fis.close()
|
||||||
|
|
||||||
|
totalSamples
|
||||||
|
}
|
||||||
|
catch (_: Throwable) {
|
||||||
|
Long.MAX_VALUE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString() = if (name.isEmpty()) file.nameWithoutExtension else name
|
override fun toString() = if (name.isEmpty()) file.nameWithoutExtension else name
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import net.torvald.terrarum.Terrarum.mouseTileY
|
|||||||
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE
|
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE
|
||||||
import net.torvald.terrarum.audio.*
|
import net.torvald.terrarum.audio.*
|
||||||
import net.torvald.terrarum.audio.AudioMixer.dynamicSourceCount
|
import net.torvald.terrarum.audio.AudioMixer.dynamicSourceCount
|
||||||
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.BUFFER_SIZE
|
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.AUDIO_BUFFER_SIZE
|
||||||
import net.torvald.terrarum.audio.dsp.*
|
import net.torvald.terrarum.audio.dsp.*
|
||||||
import net.torvald.terrarum.controller.TerrarumController
|
import net.torvald.terrarum.controller.TerrarumController
|
||||||
import net.torvald.terrarum.gameworld.GameWorld
|
import net.torvald.terrarum.gameworld.GameWorld
|
||||||
@@ -396,10 +396,16 @@ class BasicDebugInfoWindow : UICanvas() {
|
|||||||
private val COL_FILTER_WELL_BACK = Color(0x222325_aa)
|
private val COL_FILTER_WELL_BACK = Color(0x222325_aa)
|
||||||
private val COL_MIXER_BACK = Color(0x0f110c_aa)
|
private val COL_MIXER_BACK = Color(0x0f110c_aa)
|
||||||
private val COL_METER_TROUGH = Color(0x232527_aa)
|
private val COL_METER_TROUGH = Color(0x232527_aa)
|
||||||
|
|
||||||
private val COL_METER_GRAD = Color(0x1c5075_aa)
|
private val COL_METER_GRAD = Color(0x1c5075_aa)
|
||||||
private val COL_METER_GRAD2 = Color(0x25a0f2_aa)
|
private val COL_METER_GRAD2 = Color(0x25a0f2_aa)
|
||||||
private val COL_SENDS_GRAD = Color(0x50751c_aa)
|
|
||||||
private val COL_SENDS_GRAD2 = Color(0xa0f225_aa.toInt())
|
private val COL_SENDS_GRAD = Color(0x5b711e_aa)
|
||||||
|
private val COL_SENDS_GRAD2 = Color(0xbff12a_aa.toInt())
|
||||||
|
|
||||||
|
private val COL_PROGRESS_GRAD = Color(0x762340_aa.toInt())
|
||||||
|
private val COL_PROGRESS_GRAD2 = Color(0xf22e7b_aa.toInt())
|
||||||
|
|
||||||
private val COL_METER_GRAD_YELLOW = Color(0x62471c_aa)
|
private val COL_METER_GRAD_YELLOW = Color(0x62471c_aa)
|
||||||
private val COL_METER_GRAD2_YELLOW = Color(0xc68f24_aa.toInt())
|
private val COL_METER_GRAD2_YELLOW = Color(0xc68f24_aa.toInt())
|
||||||
private val COL_METER_GRAD_RED = Color(0x921c34_aa.toInt())
|
private val COL_METER_GRAD_RED = Color(0x921c34_aa.toInt())
|
||||||
@@ -454,7 +460,7 @@ class BasicDebugInfoWindow : UICanvas() {
|
|||||||
private val oldRMS = Array(trackCount) { arrayOf(0.0, 0.0) }
|
private val oldRMS = Array(trackCount) { arrayOf(0.0, 0.0) }
|
||||||
private val oldComp = Array(trackCount) { arrayOf(0.0, 0.0) }
|
private val oldComp = Array(trackCount) { arrayOf(0.0, 0.0) }
|
||||||
|
|
||||||
private fun getSmoothingFactor(sampleCount: Int) = 1.0 - (BUFFER_SIZE / (4.0 * sampleCount))
|
private fun getSmoothingFactor(sampleCount: Int) = 1.0 - (AUDIO_BUFFER_SIZE.toDouble() / sampleCount)
|
||||||
private val PEAK_SMOOTHING_FACTOR = getSmoothingFactor(640)
|
private val PEAK_SMOOTHING_FACTOR = getSmoothingFactor(640)
|
||||||
private val LAMP_SMOOTHING_FACTOR = getSmoothingFactor(3200)
|
private val LAMP_SMOOTHING_FACTOR = getSmoothingFactor(3200)
|
||||||
private val RMS_SMOOTHING_FACTOR = getSmoothingFactor(12000)
|
private val RMS_SMOOTHING_FACTOR = getSmoothingFactor(12000)
|
||||||
@@ -541,7 +547,34 @@ class BasicDebugInfoWindow : UICanvas() {
|
|||||||
val faderY = y + stripFilterHeight * numberOfFilters
|
val faderY = y + stripFilterHeight * numberOfFilters
|
||||||
|
|
||||||
// receives (opposite of "sends")
|
// receives (opposite of "sends")
|
||||||
if (track != AudioMixer.sfxSumBus) {
|
if (track.trackType == TrackType.STATIC_SOURCE && track.sidechainInputs.isEmpty()) {
|
||||||
|
// show sample rate and codec info instead
|
||||||
|
listOf(
|
||||||
|
(track.currentTrack?.name ?: " -----").let {
|
||||||
|
if (it.length > 7) it.replace(" ", "").let { it.substring(0 until minOf(it.length, 7)) }
|
||||||
|
else it
|
||||||
|
},
|
||||||
|
"C:${track.currentTrack?.codec ?: ""}",
|
||||||
|
"R:${track.currentTrack?.samplingRate ?: ""}",
|
||||||
|
).forEachIndexed { i, s ->
|
||||||
|
// gauge background
|
||||||
|
batch.color = COL_METER_TROUGH
|
||||||
|
Toolkit.fillArea(batch, x.toFloat(), faderY - (i + 1) * 16f, stripW.toFloat(), 14f)
|
||||||
|
|
||||||
|
// fill the song title line with a progress bar
|
||||||
|
if (i == 0 && track.currentTrack != null) {
|
||||||
|
val perc = (track.currentTrack!!.samplesRead.toFloat() / track.currentTrack!!.samplesTotal).coerceAtMost(1f)
|
||||||
|
batch.color = COL_PROGRESS_GRAD2
|
||||||
|
Toolkit.fillArea(batch, x.toFloat(), faderY - (i + 1) * 16f, stripW * perc, 14f)
|
||||||
|
batch.color = COL_PROGRESS_GRAD
|
||||||
|
Toolkit.fillArea(batch, x.toFloat(), faderY - (i + 1) * 16f + 14f, stripW * perc, 2f)
|
||||||
|
}
|
||||||
|
|
||||||
|
batch.color = FILTER_NAME_ACTIVE
|
||||||
|
App.fontSmallNumbers.draw(batch, s, x + 3f, faderY - (i + 1) * 16f + 1f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (track != AudioMixer.sfxSumBus) {
|
||||||
track.sidechainInputs.reversed().forEachIndexed { i, (side, mix) ->
|
track.sidechainInputs.reversed().forEachIndexed { i, (side, mix) ->
|
||||||
val mixDb = fullscaleToDecibels(mix)
|
val mixDb = fullscaleToDecibels(mix)
|
||||||
val perc = ((mixDb + 24.0).coerceAtLeast(0.0) / 24.0).toFloat()
|
val perc = ((mixDb + 24.0).coerceAtLeast(0.0) / 24.0).toFloat()
|
||||||
@@ -729,7 +762,7 @@ class BasicDebugInfoWindow : UICanvas() {
|
|||||||
}
|
}
|
||||||
is Buffer -> {
|
is Buffer -> {
|
||||||
batch.color = FILTER_NAME_ACTIVE
|
batch.color = FILTER_NAME_ACTIVE
|
||||||
App.fontSmallNumbers.draw(batch, "Bs:${BUFFER_SIZE/4}", x+3f, y+1f)
|
App.fontSmallNumbers.draw(batch, "Bs:${AUDIO_BUFFER_SIZE}", x+3f, y+1f)
|
||||||
}
|
}
|
||||||
is Convolv -> {
|
is Convolv -> {
|
||||||
// processing speed bar
|
// processing speed bar
|
||||||
@@ -741,7 +774,7 @@ class BasicDebugInfoWindow : UICanvas() {
|
|||||||
Toolkit.fillArea(batch, x.toFloat(), y+14f, stripW * perc, 2f)
|
Toolkit.fillArea(batch, x.toFloat(), y+14f, stripW * perc, 2f)
|
||||||
|
|
||||||
// filter length bar
|
// filter length bar
|
||||||
val g = FastMath.intLog2(BUFFER_SIZE / 4)
|
val g = FastMath.intLog2(AUDIO_BUFFER_SIZE)
|
||||||
val perc2 = (FastMath.intLog2(filter.fftLen).minus(g).toFloat() / (16f - g)).coerceIn(0f, 1f)
|
val perc2 = (FastMath.intLog2(filter.fftLen).minus(g).toFloat() / (16f - g)).coerceIn(0f, 1f)
|
||||||
batch.color = COL_METER_GRAD2
|
batch.color = COL_METER_GRAD2
|
||||||
Toolkit.fillArea(batch, x.toFloat(), y + 16f, stripW * perc2, 14f)
|
Toolkit.fillArea(batch, x.toFloat(), y + 16f, stripW * perc2, 14f)
|
||||||
|
|||||||
BIN
work_files/graphics/terrain/geode.kra
LFS
Normal file
BIN
work_files/graphics/terrain/geode.kra
LFS
Normal file
Binary file not shown.
Reference in New Issue
Block a user