mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-21 03:34:04 +09:00
wav direct upload with bugfixes
This commit is contained in:
@@ -485,6 +485,28 @@ class AudioJSR223Delegate(private val vm: VM) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Synchronously copy `length` bytes of PCMu8-stereo from `ptr` and enqueue them for playback,
|
||||
* directly — like [mp2UploadDecoded]. The putPcmDataByPtr + setSampleUploadLength +
|
||||
* startSampleUpload path hands off through the single-slot pcmBin/pcmUpload handshake serviced
|
||||
* by WriteQueueingRunnable, which DROPS chunks when a caller queues several in a row (the
|
||||
* next putPcmData overwrites pcmBin / clobbers pcmUploadLength before the thread copies it).
|
||||
* Lost chunks make WAV/PCM playback skip and effectively fast-forward. Enqueue with no race. */
|
||||
fun queuePcmDataByPtr(playhead: Int, ptr: Int, length: Int) {
|
||||
if (length <= 0) return
|
||||
val snd = getFirstSnd() ?: return
|
||||
val ph = snd.playheads.getOrNull(playhead) ?: return
|
||||
val ba = ByteArray(length)
|
||||
if (ptr >= 0) {
|
||||
// user RAM — fast bulk copy
|
||||
UnsafeHelper.memcpyRaw(null, vm.usermem.ptr + ptr, ba, UnsafeHelper.getArrayOffset(ba), length.toLong())
|
||||
} else {
|
||||
// peripheral memory grows toward 0 — read backwards, like putPcmDataByPtr
|
||||
for (k in 0 until length) ba[k] = vm.peek(ptr.toLong() - k.toLong())!!
|
||||
}
|
||||
ph.pcmQueue.add(ba)
|
||||
ph.position = ph.pcmQueue.size
|
||||
}
|
||||
fun getPcmData(playhead: Int, index: Int) = getFirstSnd()?.pcmBin?.get(playhead)?.get(index.toLong())
|
||||
|
||||
fun setPcmQueueCapacityIndex(playhead: Int, index: Int) { getPlayhead(playhead)?.pcmQueueSizeIndex = index }
|
||||
|
||||
@@ -25,6 +25,11 @@ private class RenderRunnable(val playhead: AudioAdapter.Playhead) : Runnable {
|
||||
private fun printdbg(msg: Any) {
|
||||
if (AudioAdapter.DBGPRN) println("[AudioAdapter] $msg")
|
||||
}
|
||||
// Diagnostic: effective PCM playback rate. Since writeStereoSamplesUI8 is paced by the device
|
||||
// (fillBuffer blocks until OpenAL frees a buffer), frames-fed / wall-time == the device's real
|
||||
// consumption rate. Should read ~32000; ~64000 would mean the device is playing 2x.
|
||||
private var probeFrames = 0L
|
||||
private var probeT0 = 0L
|
||||
override fun run() {
|
||||
while (!Thread.currentThread().isInterrupted) {
|
||||
try {
|
||||
@@ -43,7 +48,19 @@ private class RenderRunnable(val playhead: AudioAdapter.Playhead) : Runnable {
|
||||
|
||||
playhead.position = writeQueue.size
|
||||
|
||||
playhead.audioDevice.writeSamplesUI8(samples, 0, samples.size)
|
||||
// PCM queue data is stereo-interleaved PCMu8 (L,R,L,R) — WAV decode,
|
||||
// MP2 mediaDecodedBin, TAD. Each frame is 2 bytes, so numPairs = size/2.
|
||||
playhead.audioDevice.writeStereoSamplesUI8(samples, 0, samples.size / 2)
|
||||
|
||||
if (AudioAdapter.PCM_RATE_PROBE) {
|
||||
if (probeT0 == 0L) probeT0 = System.nanoTime()
|
||||
probeFrames += samples.size / 2
|
||||
val dt = (System.nanoTime() - probeT0) / 1.0e9
|
||||
if (dt >= 2.0) {
|
||||
System.err.println("[AudioAdapter] P${playhead.index} PCM effective rate = ${(probeFrames / dt).toInt()} frames/s (expect 32000)")
|
||||
probeFrames = 0L; probeT0 = System.nanoTime()
|
||||
}
|
||||
}
|
||||
|
||||
Thread.sleep(6)
|
||||
}
|
||||
@@ -129,6 +146,10 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
|
||||
companion object {
|
||||
internal val DBGPRN = false
|
||||
// Set true to log the effective PCM playback rate to stderr (~every 2s) — should read
|
||||
// ~32000 frames/s. Diagnosed the "WAV plays 2x faster" report: rate was correct (32000),
|
||||
// so the cause was dropped queue chunks (handshake), now fixed via queuePcmDataByPtr.
|
||||
internal val PCM_RATE_PROBE = false
|
||||
const val SAMPLING_RATE = 32000
|
||||
const val TRACKER_CHUNK = 512
|
||||
// Per-voice soundscope ring-buffer length. Power of two so wrap-around is a single AND.
|
||||
@@ -457,7 +478,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
// printdbg("P${playhead.index+1} Vol ${playhead.masterVolume}; LpP ${playhead.pcmUploadLength}; start playback...")
|
||||
// printdbg(""+(0..42).joinToString { String.format("%.2f", samples[it]) })
|
||||
|
||||
playhead.audioDevice.writeSamplesUI8(samples, 0, samples.size)
|
||||
playhead.audioDevice.writeStereoSamplesUI8(samples, 0, samples.size / 2)
|
||||
|
||||
// printdbg("P${playhead.index+1} go back to spinning")
|
||||
|
||||
|
||||
@@ -139,7 +139,15 @@ class TestDiskDrive(private val vm: VM, private val driveNum: Int, theRootPath:
|
||||
|
||||
try {
|
||||
val buffer = ByteArray(BLOCK_SIZE)
|
||||
val bytesRead = stream.read(buffer)
|
||||
// Fill the whole block. InputStream.read may return a short count mid-file, but the
|
||||
// block protocol treats any block shorter than BLOCK_SIZE as the final one (EOF), so
|
||||
// a short mid-file read would truncate the file to silence. Loop until full or EOF.
|
||||
var bytesRead = 0
|
||||
while (bytesRead < BLOCK_SIZE) {
|
||||
val n = stream.read(buffer, bytesRead, BLOCK_SIZE - bytesRead)
|
||||
if (n <= 0) break
|
||||
bytesRead += n
|
||||
}
|
||||
|
||||
if (bytesRead <= 0) {
|
||||
// End of file
|
||||
@@ -171,11 +179,13 @@ class TestDiskDrive(private val vm: VM, private val driveNum: Int, theRootPath:
|
||||
blockSendBuffer = messageComposeBuffer.toByteArray()
|
||||
}
|
||||
|
||||
val sendSize = if (blockSendBuffer.size - (blockSendCount * BLOCK_SIZE) < BLOCK_SIZE)
|
||||
blockSendBuffer.size % BLOCK_SIZE
|
||||
else if (blockSendBuffer.size <= BLOCK_SIZE)
|
||||
blockSendBuffer.size
|
||||
else BLOCK_SIZE
|
||||
// Bytes still unsent in this block. The old `size % BLOCK_SIZE` was wrong once the buffer
|
||||
// was already consumed (blockSendCount past the end): for a non-BLOCK_SIZE-multiple message
|
||||
// it returned the leftover count and then indexed blockSendBuffer[blockSendCount*BLOCK_SIZE+it]
|
||||
// — e.g. a 6-byte message read one block too far → "Index 4096 out of bounds for length 6".
|
||||
// Clamp to [0, BLOCK_SIZE]; 0 sends an empty terminating block (same EOF signal as streaming).
|
||||
val remaining = blockSendBuffer.size - blockSendCount * BLOCK_SIZE
|
||||
val sendSize = remaining.coerceIn(0, BLOCK_SIZE)
|
||||
|
||||
// println("blockSendCount = ${blockSendCount}; sendSize = $sendSize; blockSendBuffer.size = ${blockSendBuffer.size}")
|
||||
|
||||
|
||||
@@ -115,11 +115,13 @@ class TevdDiskDrive(private val vm: VM, private val driveNum: Int, theTevdPath:
|
||||
blockSendBuffer = messageComposeBuffer.toByteArray()
|
||||
}
|
||||
|
||||
val sendSize = if (blockSendBuffer.size - (blockSendCount * BLOCK_SIZE) < BLOCK_SIZE)
|
||||
blockSendBuffer.size % BLOCK_SIZE
|
||||
else if (blockSendBuffer.size <= BLOCK_SIZE)
|
||||
blockSendBuffer.size
|
||||
else BLOCK_SIZE
|
||||
// Bytes still unsent in this block. The old `size % BLOCK_SIZE` was wrong once the buffer
|
||||
// was already consumed (blockSendCount past the end): for a non-BLOCK_SIZE-multiple message
|
||||
// it returned the leftover count and then indexed blockSendBuffer[blockSendCount*BLOCK_SIZE+it]
|
||||
// — e.g. a 6-byte message read one block too far → "Index 4096 out of bounds for length 6".
|
||||
// Clamp to [0, BLOCK_SIZE]; 0 sends an empty terminating block (same EOF signal as streaming).
|
||||
val remaining = blockSendBuffer.size - blockSendCount * BLOCK_SIZE
|
||||
val sendSize = remaining.coerceIn(0, BLOCK_SIZE)
|
||||
|
||||
// println("blockSendCount = ${blockSendCount}; sendSize = $sendSize; blockSendBuffer.size = ${blockSendBuffer.size}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user