mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-12 23:54:04 +09:00
TAD: kotlin side update
This commit is contained in:
@@ -13,6 +13,7 @@ import net.torvald.tsvm.VM
|
|||||||
import net.torvald.tsvm.getHashStr
|
import net.torvald.tsvm.getHashStr
|
||||||
import net.torvald.tsvm.toInt
|
import net.torvald.tsvm.toInt
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
private class RenderRunnable(val playhead: AudioAdapter.Playhead) : Runnable {
|
private class RenderRunnable(val playhead: AudioAdapter.Playhead) : Runnable {
|
||||||
private fun printdbg(msg: Any) {
|
private fun printdbg(msg: Any) {
|
||||||
@@ -133,6 +134,12 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
|||||||
internal var tadQuality = 2 // Quality level used during encoding (0-5)
|
internal var tadQuality = 2 // Quality level used during encoding (0-5)
|
||||||
@Volatile private var tadBusy = false
|
@Volatile private var tadBusy = false
|
||||||
|
|
||||||
|
// TAD decoder constants
|
||||||
|
private val TAD_COEFF_SCALAR = 1024.0f
|
||||||
|
|
||||||
|
// Dither state for noise shaping (2 channels, 2 history samples each)
|
||||||
|
private val ditherError = Array(2) { FloatArray(2) }
|
||||||
|
|
||||||
private val renderRunnables: Array<RenderRunnable>
|
private val renderRunnables: Array<RenderRunnable>
|
||||||
private val renderThreads: Array<Thread>
|
private val renderThreads: Array<Thread>
|
||||||
private val writeQueueingRunnables: Array<WriteQueueingRunnable>
|
private val writeQueueingRunnables: Array<WriteQueueingRunnable>
|
||||||
@@ -333,6 +340,56 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
|||||||
// TAD (Terrarum Advanced Audio) Decoder
|
// TAD (Terrarum Advanced Audio) Decoder
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
|
|
||||||
|
// Uniform random in [0, 1)
|
||||||
|
private fun frand01(): Float {
|
||||||
|
return Math.random().toFloat()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TPDF (Triangular Probability Density Function) noise in [-1, +1)
|
||||||
|
private fun tpdf1(): Float {
|
||||||
|
return frand01() - frand01()
|
||||||
|
}
|
||||||
|
|
||||||
|
// M/S stereo correlation with noise-shaped dithering (matches C implementation)
|
||||||
|
private fun msCorrelate(mid: FloatArray, side: FloatArray, sampleCount: Int) {
|
||||||
|
val b1 = 1.5f // 1st feedback coefficient
|
||||||
|
val b2 = -0.75f // 2nd feedback coefficient
|
||||||
|
val scale = 127.5f
|
||||||
|
val bias = 128
|
||||||
|
|
||||||
|
for (i in 0 until sampleCount) {
|
||||||
|
// Decode M/S → L/R
|
||||||
|
val m = mid[i]
|
||||||
|
val s = side[i]
|
||||||
|
val l = (m + s).coerceIn(-1.0f, 1.0f)
|
||||||
|
val r = (m - s).coerceIn(-1.0f, 1.0f)
|
||||||
|
|
||||||
|
// --- LEFT channel ---
|
||||||
|
val feedbackL = b1 * ditherError[0][0] + b2 * ditherError[0][1]
|
||||||
|
val ditherL = 0.5f * tpdf1() // ±0.5 LSB TPDF
|
||||||
|
val shapedL = (l + feedbackL + ditherL / scale).coerceIn(-1.0f, 1.0f)
|
||||||
|
|
||||||
|
val qL = (shapedL * scale).roundToInt().coerceIn(-128, 127)
|
||||||
|
tadDecodedBin[i * 2L] = (qL + bias).toByte()
|
||||||
|
|
||||||
|
val qerrL = shapedL - qL.toFloat() / scale
|
||||||
|
ditherError[0][1] = ditherError[0][0] // shift history
|
||||||
|
ditherError[0][0] = qerrL
|
||||||
|
|
||||||
|
// --- RIGHT channel ---
|
||||||
|
val feedbackR = b1 * ditherError[1][0] + b2 * ditherError[1][1]
|
||||||
|
val ditherR = 0.5f * tpdf1()
|
||||||
|
val shapedR = (r + feedbackR + ditherR / scale).coerceIn(-1.0f, 1.0f)
|
||||||
|
|
||||||
|
val qR = (shapedR * scale).roundToInt().coerceIn(-128, 127)
|
||||||
|
tadDecodedBin[i * 2L + 1] = (qR + bias).toByte()
|
||||||
|
|
||||||
|
val qerrR = shapedR - qR.toFloat() / scale
|
||||||
|
ditherError[1][1] = ditherError[1][0]
|
||||||
|
ditherError[1][0] = qerrR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun decodeTad() {
|
private fun decodeTad() {
|
||||||
tadBusy = true
|
tadBusy = true
|
||||||
try {
|
try {
|
||||||
@@ -350,7 +407,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
|||||||
((tadInputBin[offset++].toInt() and 0xFF) shl 24)
|
((tadInputBin[offset++].toInt() and 0xFF) shl 24)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Decompress payload if needed
|
// Decompress payload
|
||||||
val compressed = ByteArray(payloadSize)
|
val compressed = ByteArray(payloadSize)
|
||||||
UnsafeHelper.memcpyRaw(null, tadInputBin.ptr + offset, compressed, UnsafeHelper.getArrayOffset(compressed), payloadSize.toLong())
|
UnsafeHelper.memcpyRaw(null, tadInputBin.ptr + offset, compressed, UnsafeHelper.getArrayOffset(compressed), payloadSize.toLong())
|
||||||
|
|
||||||
@@ -360,7 +417,8 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
|||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
println("ERROR: Zstd decompression failed: ${e.message}")
|
println("ERROR: Zstd decompression failed: ${e.message}")
|
||||||
} as ByteArray
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Decode significance maps
|
// Decode significance maps
|
||||||
val quantMid = ShortArray(sampleCount)
|
val quantMid = ShortArray(sampleCount)
|
||||||
@@ -375,39 +433,19 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
|||||||
// Calculate DWT levels from sample count
|
// Calculate DWT levels from sample count
|
||||||
val dwtLevels = calculateDwtLevels(sampleCount)
|
val dwtLevels = calculateDwtLevels(sampleCount)
|
||||||
|
|
||||||
// Dequantize
|
// Dequantize to Float32
|
||||||
val dwtMid = FloatArray(sampleCount)
|
val dwtMid = FloatArray(sampleCount)
|
||||||
val dwtSide = FloatArray(sampleCount)
|
val dwtSide = FloatArray(sampleCount)
|
||||||
dequantizeDwtCoefficients(quantMid, dwtMid, sampleCount, tadQuality, dwtLevels)
|
dequantizeDwtCoefficients(quantMid, dwtMid, sampleCount, tadQuality, dwtLevels)
|
||||||
dequantizeDwtCoefficients(quantSide, dwtSide, sampleCount, tadQuality, dwtLevels)
|
dequantizeDwtCoefficients(quantSide, dwtSide, sampleCount, tadQuality, dwtLevels)
|
||||||
|
|
||||||
// Inverse DWT
|
// Inverse DWT (produces Float32 samples in range [-1.0, 1.0])
|
||||||
dwtDD4InverseMultilevel(dwtMid, sampleCount, dwtLevels)
|
dwtDD4InverseMultilevel(dwtMid, sampleCount, dwtLevels)
|
||||||
dwtDD4InverseMultilevel(dwtSide, sampleCount, dwtLevels)
|
dwtDD4InverseMultilevel(dwtSide, sampleCount, dwtLevels)
|
||||||
|
|
||||||
// Convert to signed PCM8
|
// M/S to L/R correlation with noise-shaped dithering
|
||||||
val pcm8Mid = ByteArray(sampleCount)
|
// Output is PCMu8 stereo written directly to tadDecodedBin
|
||||||
val pcm8Side = ByteArray(sampleCount)
|
msCorrelate(dwtMid, dwtSide, sampleCount)
|
||||||
for (i in 0 until sampleCount) {
|
|
||||||
pcm8Mid[i] = dwtMid[i].coerceIn(-128f, 127f).toInt().toByte()
|
|
||||||
pcm8Side[i] = dwtSide[i].coerceIn(-128f, 127f).toInt().toByte()
|
|
||||||
}
|
|
||||||
|
|
||||||
// M/S to L/R correlation and write to tadDecodedBin
|
|
||||||
for (i in 0 until sampleCount) {
|
|
||||||
val m = pcm8Mid[i].toInt()
|
|
||||||
val s = pcm8Side[i].toInt()
|
|
||||||
var l = m + s
|
|
||||||
var r = m - s
|
|
||||||
|
|
||||||
if (l < -128) l = -128
|
|
||||||
if (l > 127) l = 127
|
|
||||||
if (r < -128) r = -128
|
|
||||||
if (r > 127) r = 127
|
|
||||||
|
|
||||||
tadDecodedBin[i * 2L] = (l + 128).toByte() // Left (PCMu8)
|
|
||||||
tadDecodedBin[i * 2L + 1] = (r + 128).toByte() // Right (PCMu8)
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
@@ -487,7 +525,8 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
|||||||
/*15*/floatArrayOf(0.2f, 0.2f, 0.8f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.25f, 1.5f, 1.5f),
|
/*15*/floatArrayOf(0.2f, 0.2f, 0.8f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.25f, 1.5f, 1.5f),
|
||||||
/*16*/floatArrayOf(0.2f, 0.2f, 0.8f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.25f, 1.5f)
|
/*16*/floatArrayOf(0.2f, 0.2f, 0.8f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.25f, 1.5f)
|
||||||
)
|
)
|
||||||
val qualityScale = 1.0f + ((3 - quality) * 0.5f).coerceAtLeast(0.0f)
|
// Updated quality scale to match C implementation
|
||||||
|
val qualityScale = 4.0f + ((3 - quality) * 0.5f).coerceIn(0.0f, 1000.0f)
|
||||||
return FloatArray(dwtLevels) { i -> (baseWeights[dwtLevels][i.coerceIn(0, 15)] * qualityScale).coerceAtLeast(1.0f) }
|
return FloatArray(dwtLevels) { i -> (baseWeights[dwtLevels][i.coerceIn(0, 15)] * qualityScale).coerceAtLeast(1.0f) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -514,7 +553,8 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
|||||||
|
|
||||||
val weightIdx = if (sideband == 0) 0 else sideband - 1
|
val weightIdx = if (sideband == 0) 0 else sideband - 1
|
||||||
val weight = weights[weightIdx.coerceIn(0, dwtLevels - 1)]
|
val weight = weights[weightIdx.coerceIn(0, dwtLevels - 1)]
|
||||||
coeffs[i] = quantized[i].toFloat() * weight
|
// Updated to match C implementation: divide by TAD_COEFF_SCALAR
|
||||||
|
coeffs[i] = quantized[i].toFloat() * weight / TAD_COEFF_SCALAR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user