audio device changes

This commit is contained in:
minjaesong
2026-04-16 15:04:44 +09:00
parent 2ac084acd7
commit 6aa2542bb8
11 changed files with 153 additions and 59 deletions

View File

@@ -5,6 +5,23 @@ import net.torvald.tsvm.peripheral.AudioAdapter
import net.torvald.tsvm.peripheral.MP2Env
/**
* Each playhead is separate OpenAL device with its own PCM sample buffers.
* Media decoders (MP2, TAD) are independent to the playheads and there is only one.
*
* NOTES:
* 1. tracker mode is currently unimplemented.
* 2. PCM upload buffer (accessed by `putPcmDataByPtr`) is shared between four playheads
*
* ## How to upload PCM audio into a playhead
*
* 1. prepare PCM data
* 2. queue up PCM data by `audio.putPcmDataByPtr(pcmDataPtr, pcmDataLength, playhead)`
* 3. specify PCM upload length by `audio.setSampleUploadLength(playhead, pcmDataLength)`
* 4. start uploading `audio.startSampleUpload(playhead)`
* 5. sample will be ready after a few microseconds.
*
* Uploaded samples will be queued by the playhead for gapless playback
*
* Created by minjaesong on 2022-12-31.
*/
class AudioJSR223Delegate(private val vm: VM) {
@@ -51,16 +68,16 @@ class AudioJSR223Delegate(private val vm: VM) {
fun setTickRate(playhead: Int, rate: Int) { getPlayhead(playhead)?.tickRate = rate and 255 }
fun getTickRate(playhead: Int) = getPlayhead(playhead)?.tickRate
fun putPcmDataByPtr(ptr: Int, length: Int, destOffset: Int) {
fun putPcmDataByPtr(playhead: Int, ptr: Int, length: Int, destOffset: Int) {
getFirstSnd()?.let {
val vkMult = if (ptr >= 0) 1 else -1
for (k in 0L until length) {
val vk = k * vkMult
it.pcmBin[k + destOffset] = vm.peek(ptr + vk)!!
it.pcmBin[playhead][k + destOffset] = vm.peek(ptr + vk)!!
}
}
}
fun getPcmData(index: Int) = getFirstSnd()?.pcmBin?.get(index.toLong())
fun getPcmData(playhead: Int, index: Int) = getFirstSnd()?.pcmBin?.get(playhead)?.get(index.toLong())
fun setPcmQueueCapacityIndex(playhead: Int, index: Int) { getPlayhead(playhead)?.pcmQueueSizeIndex = index }
fun getPcmQueueCapacityIndex(playhead: Int) { getPlayhead(playhead)?.pcmQueueSizeIndex }

View File

@@ -763,7 +763,7 @@ class VM(
else if (dev is AudioAdapter) {
if (relPtrInDev(fromRel, len, 64, 2367)) dev.mediaDecodedBin.ptr + fromRel - 64
else if (relPtrInDev(fromRel, len, 2368, 4096)) dev.mediaFrameBin.ptr + fromRel - 2368
else if (relPtrInDev(fromRel, len, 65536, 131072)) dev.pcmBin.ptr + fromRel - 65536
else if (relPtrInDev(fromRel, len, 65536, 131072)) dev.pcmBin[dev.selectedPcmBin].ptr + fromRel - 65536
else null
}
else if (dev is GraphicsAdapter) {

View File

@@ -122,13 +122,20 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
internal val playdata = Array(256) { Array(64) { TaudPlayData(0,0,0,0,0,0,0,0) } }
internal val playheads: Array<Playhead>
internal val cueSheet = Array(2048) { PlayCue() }
internal val pcmBin = UnsafeHelper.allocate(65536L, this)
internal val pcmBin = arrayOf(
UnsafeHelper.allocate(65536L, this),
UnsafeHelper.allocate(65536L, this),
UnsafeHelper.allocate(65536L, this),
UnsafeHelper.allocate(65536L, this),
)
internal val mediaFrameBin = UnsafeHelper.allocate(1728, this)
internal val mediaDecodedBin = UnsafeHelper.allocate(2304, this)
@Volatile private var mp2Busy = false
@Volatile var selectedPcmBin = 0
// TAD (Terrarum Advanced Audio) decoder buffers
internal val tadInputBin = UnsafeHelper.allocate(65536L, this) // Input: compressed TAD chunk (max 64KB)
internal val tadDecodedBin = UnsafeHelper.allocate(65536L, this) // Output: PCMu8 stereo (32768 samples * 2 channels)
@@ -241,7 +248,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
renderRunnables = Array(4) { RenderRunnable(playheads[it]) }
renderThreads = Array(4) { Thread(renderThreadGroup, renderRunnables[it], "AudioRenderHead${it+1}!$hash") }
writeQueueingRunnables = Array(4) { WriteQueueingRunnable(playheads[it], pcmBin) }
writeQueueingRunnables = Array(4) { WriteQueueingRunnable(playheads[it], pcmBin[it]) }
writeQueueingThreads = Array(4) { Thread(writeQueueingGroup, writeQueueingRunnables[it], "AudioQueueingHead${it+1}!$hash") }
// printdbg("AudioAdapter latency: ${audioDevice.latency}")
@@ -315,13 +322,14 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
42 -> -1 // TAD control (write-only)
43 -> tadQuality.toByte()
44 -> tadBusy.toInt().toByte()
45 -> selectedPcmBin.toByte()
in 64..2367 -> mediaDecodedBin[addr - 64]
in 2368..4095 -> mediaFrameBin[addr - 2368]
in 4096..4097 -> 0
in 32768..65535 -> (adi - 32768).let {
cueSheet[it / 16].read(it % 15)
}
in 65536..131071 -> pcmBin[addr - 65536]
in 65536..131071 -> pcmBin[selectedPcmBin][addr - 65536]
else -> {
println("[AudioAdapter] Bus mirroring on mmio_reading while trying to read address $addr")
mmio_read(addr % 131072)
@@ -349,12 +357,13 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
// TAD quality (0-5)
tadQuality = bi.coerceIn(0, 5)
}
45 -> selectedPcmBin = bi % 4
in 64..2367 -> { mediaDecodedBin[addr - 64] = byte }
in 2368..4095 -> { mediaFrameBin[addr - 2368] = byte }
in 32768..65535 -> { (adi - 32768).let {
cueSheet[it / 16].write(it % 15, bi)
} }
in 65536..131071 -> { pcmBin[addr - 65536] = byte }
in 65536..131071 -> { pcmBin[selectedPcmBin][addr - 65536] = byte }
}
}
@@ -368,7 +377,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
writeQueueingGroup.interrupt()
playheads.forEach { it.dispose() }
sampleBin.destroy()
pcmBin.destroy()
pcmBin.forEach { it.destroy() }
mediaFrameBin.destroy()
mediaDecodedBin.destroy()
tadInputBin.destroy()