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

@@ -4,7 +4,7 @@ music.pread(samples, 65534)
audio.setPcmMode(0) audio.setPcmMode(0)
audio.setMasterVolume(0, 255) audio.setMasterVolume(0, 255)
audio.putPcmDataByPtr(samples, 65534, 0) audio.putPcmDataByPtr(0, samples, 65534, 0)
audio.setLoopPoint(0, 65534) audio.setLoopPoint(0, 65534)
audio.play(0)*/ audio.play(0)*/
@@ -127,7 +127,7 @@ while (sampleSize > 0) {
let readLength = (sampleSize < BLOCK_SIZE) ? sampleSize : BLOCK_SIZE let readLength = (sampleSize < BLOCK_SIZE) ? sampleSize : BLOCK_SIZE
readBytes(readLength, decodePtr) readBytes(readLength, decodePtr)
audio.putPcmDataByPtr(decodePtr, readLength, 0) audio.putPcmDataByPtr(0, decodePtr, readLength, 0)
audio.setSampleUploadLength(0, readLength) audio.setSampleUploadLength(0, readLength)
audio.startSampleUpload(0) audio.startSampleUpload(0)

View File

@@ -0,0 +1,5 @@
/**
* Hopper is a package manager for TSVM
* Created by CuriousTorvald on 2026-04-16
*/

View File

@@ -326,7 +326,7 @@ while (!stopPlay && seqread.getReadCount() < FILE_LENGTH) {
// RAW PCM packets (decode on the fly) // RAW PCM packets (decode on the fly)
else if (packetType == 0x1000 || packetType == 0x1001) { else if (packetType == 0x1000 || packetType == 0x1001) {
let frame = seqread.readBytes(readLength) let frame = seqread.readBytes(readLength)
audio.putPcmDataByPtr(frame, readLength, 0) audio.putPcmDataByPtr(0, frame, readLength, 0)
audio.setSampleUploadLength(0, readLength) audio.setSampleUploadLength(0, readLength)
audio.startSampleUpload(0) audio.startSampleUpload(0)
sys.free(frame) sys.free(frame)

View File

@@ -162,7 +162,7 @@ while (!stopPlay && seqread.getReadCount() < FILE_SIZE && readLength > 0) {
seqread.readBytes(readLength, readPtr) seqread.readBytes(readLength, readPtr)
audio.putPcmDataByPtr(readPtr, readLength, 0) audio.putPcmDataByPtr(0, readPtr, readLength, 0)
audio.setSampleUploadLength(0, readLength) audio.setSampleUploadLength(0, readLength)
audio.startSampleUpload(0) audio.startSampleUpload(0)

View File

@@ -18,6 +18,7 @@ const ADDRESSING_INTERNAL = 0x02
const SND_BASE_ADDR = audio.getBaseAddr() const SND_BASE_ADDR = audio.getBaseAddr()
const SND_MEM_ADDR = audio.getMemAddr() const SND_MEM_ADDR = audio.getMemAddr()
const pcm = require("pcm") const pcm = require("pcm")
const AUDIO_DEVICE = 3
const MP2_FRAME_SIZE = [144,216,252,288,360,432,504,576,720,864,1008,1152,1440,1728] const MP2_FRAME_SIZE = [144,216,252,288,360,432,504,576,720,864,1008,1152,1440,1728]
const TAV_TEMPORAL_LEVELS = 2 const TAV_TEMPORAL_LEVELS = 2
@@ -152,10 +153,10 @@ graphics.clearPixels4(0)
const gpuGraphicsMode = graphics.getGraphicsMode() const gpuGraphicsMode = graphics.getGraphicsMode()
// Initialize audio // Initialize audio
audio.resetParams(0) audio.resetParams(AUDIO_DEVICE)
audio.purgeQueue(0) audio.purgeQueue(AUDIO_DEVICE)
audio.setPcmMode(0) audio.setPcmMode(AUDIO_DEVICE)
audio.setMasterVolume(0, 255) audio.setMasterVolume(AUDIO_DEVICE, 255)
// set colour zero as half-opaque black // set colour zero as half-opaque black
graphics.setPalette(0, 0, 0, 0, 7) graphics.setPalette(0, 0, 0, 0, 7)
@@ -1152,10 +1153,10 @@ try {
else if (keyCode == 62) { // SPACE - pause/resume else if (keyCode == 62) { // SPACE - pause/resume
paused = !paused paused = !paused
if (paused) { if (paused) {
audio.stop(0) audio.stop(AUDIO_DEVICE)
serial.println(`Paused at frame ${frameCount}`) serial.println(`Paused at frame ${frameCount}`)
} else { } else {
audio.play(0) audio.play(AUDIO_DEVICE)
serial.println(`Resumed`) serial.println(`Resumed`)
} }
} }
@@ -1176,10 +1177,10 @@ try {
baseTimecodeFrameCount = 0 baseTimecodeFrameCount = 0
currentTimecodeNs = 0 currentTimecodeNs = 0
nextSubtitleEventIndex = 0 // Reset subtitle event processing nextSubtitleEventIndex = 0 // Reset subtitle event processing
audio.purgeQueue(0) audio.purgeQueue(AUDIO_DEVICE)
if (paused) { if (paused) {
audio.play(0) audio.play(AUDIO_DEVICE)
audio.stop(0) audio.stop(AUDIO_DEVICE)
} }
skipped = true skipped = true
} }
@@ -1201,10 +1202,10 @@ try {
baseTimecodeFrameCount = 0 baseTimecodeFrameCount = 0
currentTimecodeNs = 0 currentTimecodeNs = 0
nextSubtitleEventIndex = 0 // Reset subtitle event processing nextSubtitleEventIndex = 0 // Reset subtitle event processing
audio.purgeQueue(0) audio.purgeQueue(AUDIO_DEVICE)
if (paused) { if (paused) {
audio.play(0) audio.play(AUDIO_DEVICE)
audio.stop(0) audio.stop(AUDIO_DEVICE)
} }
skipped = true skipped = true
} }
@@ -1232,10 +1233,10 @@ try {
break break
} }
} }
audio.purgeQueue(0) audio.purgeQueue(AUDIO_DEVICE)
if (paused) { if (paused) {
audio.play(0) audio.play(AUDIO_DEVICE)
audio.stop(0) audio.stop(AUDIO_DEVICE)
} }
skipped = true skipped = true
} }
@@ -1271,10 +1272,10 @@ try {
break break
} }
} }
audio.purgeQueue(0) audio.purgeQueue(AUDIO_DEVICE)
if (paused) { if (paused) {
audio.play(0) audio.play(AUDIO_DEVICE)
audio.stop(0) audio.stop(AUDIO_DEVICE)
} }
skipped = true skipped = true
} else if (!seekTarget) { } else if (!seekTarget) {
@@ -1313,7 +1314,7 @@ try {
baseTimecodeFrameCount = 0 baseTimecodeFrameCount = 0
currentTimecodeNs = 0 currentTimecodeNs = 0
nextSubtitleEventIndex = 0 // Reset subtitle event processing nextSubtitleEventIndex = 0 // Reset subtitle event processing
audio.purgeQueue(0) audio.purgeQueue(AUDIO_DEVICE)
currentFileIndex++ currentFileIndex++
if (skipped) { if (skipped) {
skipped = false skipped = false
@@ -1737,7 +1738,7 @@ try {
seqread.readBytes(audioLen, SND_BASE_ADDR - 2368) seqread.readBytes(audioLen, SND_BASE_ADDR - 2368)
audio.mp2Decode() audio.mp2Decode()
audio.mp2UploadDecoded(0) audio.mp2UploadDecoded(AUDIO_DEVICE)
} }
else if (packetType === TAV_PACKET_AUDIO_TAD) { else if (packetType === TAV_PACKET_AUDIO_TAD) {
@@ -1750,7 +1751,7 @@ try {
seqread.readBytes(payloadLen, SND_MEM_ADDR - 262144) seqread.readBytes(payloadLen, SND_MEM_ADDR - 262144)
audio.tadDecode() audio.tadDecode()
audio.tadUploadDecoded(0, sampleLen) audio.tadUploadDecoded(AUDIO_DEVICE, sampleLen)
} }
else if (packetType === TAV_PACKET_AUDIO_NATIVE) { else if (packetType === TAV_PACKET_AUDIO_NATIVE) {
// PCM length must not exceed 65536 bytes! // PCM length must not exceed 65536 bytes!
@@ -1762,10 +1763,10 @@ try {
let pcmLen = gzip.decompFromTo(zstdPtr, zstdLen, pcmPtr) // <- segfaults! let pcmLen = gzip.decompFromTo(zstdPtr, zstdLen, pcmPtr) // <- segfaults!
if (pcmLen > 65536) throw Error(`PCM data too long -- got ${pcmLen} bytes`) if (pcmLen > 65536) throw Error(`PCM data too long -- got ${pcmLen} bytes`)
audio.putPcmDataByPtr(pcmPtr, pcmLen, 0) audio.putPcmDataByPtr(AUDIO_DEVICE, pcmPtr, pcmLen, 0)
audio.setSampleUploadLength(0, pcmLen) audio.setSampleUploadLength(AUDIO_DEVICE, pcmLen)
audio.startSampleUpload(0) audio.startSampleUpload(AUDIO_DEVICE)
sys.free(zstdPtr) sys.free(zstdPtr)
sys.free(pcmPtr) sys.free(pcmPtr)
@@ -2049,7 +2050,7 @@ try {
// Fire audio on first frame // Fire audio on first frame
if (!audioFired) { if (!audioFired) {
audio.play(0) audio.play(AUDIO_DEVICE)
audioFired = true audioFired = true
} }
@@ -2137,7 +2138,7 @@ try {
// Fire audio on first frame // Fire audio on first frame
if (!audioFired) { if (!audioFired) {
audio.play(0) audio.play(AUDIO_DEVICE)
audioFired = true audioFired = true
} }
@@ -2173,8 +2174,8 @@ try {
sys.memcpy(predecodedPcmBuffer + predecodedPcmOffset, SND_BASE_ADDR, uploadSize) sys.memcpy(predecodedPcmBuffer + predecodedPcmOffset, SND_BASE_ADDR, uploadSize)
// Set upload parameters and trigger upload to queue // Set upload parameters and trigger upload to queue
audio.setSampleUploadLength(0, uploadSize) audio.setSampleUploadLength(AUDIO_DEVICE, uploadSize)
audio.startSampleUpload(0) audio.startSampleUpload(AUDIO_DEVICE)
predecodedPcmOffset += uploadSize predecodedPcmOffset += uploadSize
} }
@@ -2458,8 +2459,8 @@ finally {
sys.poke(-1299460, 20) sys.poke(-1299460, 20)
sys.poke(-1299460, 21) sys.poke(-1299460, 21)
audio.stop(0) audio.stop(AUDIO_DEVICE)
audio.purgeQueue(0) audio.purgeQueue(AUDIO_DEVICE)
} }
graphics.setPalette(0, 0, 0, 0, 0) graphics.setPalette(0, 0, 0, 0, 0)

View File

@@ -289,7 +289,7 @@ while (!stopPlay && seqread.getReadCount() < FILE_SIZE - 8) {
let decodedSampleLength = decodeInfilePcm(readPtr, decodePtr, readLength) let decodedSampleLength = decodeInfilePcm(readPtr, decodePtr, readLength)
printdbg(` decodedSampleLength: ${decodedSampleLength}`) printdbg(` decodedSampleLength: ${decodedSampleLength}`)
audio.putPcmDataByPtr(decodePtr, decodedSampleLength, 0) audio.putPcmDataByPtr(0, decodePtr, decodedSampleLength, 0)
audio.setSampleUploadLength(0, decodedSampleLength) audio.setSampleUploadLength(0, decodedSampleLength)
audio.startSampleUpload(0) audio.startSampleUpload(0)

View File

@@ -292,6 +292,24 @@ function makeNoise(buf, length, offset, freq, type, op, amp, pan) {
} }
} }
function makeAliasedTriangleNES(buf, length, offset, freq, duty, op, amp, pan) {
// NES APU triangle — quantised to the authentic 32-step, 4-bit (0..15) staircase.
// The 32-step sequence is: 15,14,...,1,0, 0,1,...,14,15 (descending then ascending).
// This mirrors the real NES triangle DAC which has 32 equal-height steps per period.
// duty parameter is accepted for API symmetry but ignored (NES triangle is always symmetric).
if (op == null) op = 'add'
if (amp == null) amp = 0.5
if (pan == null) pan = 0.0
mixInto(buf, length, offset, op, amp, pan, function(i) {
const t = offset + i / HW_SAMPLING_RATE
const phase = (t * freq) % 1
const step32 = Math.floor(phase * 32) | 0 // 0..31
// step 0..15: descend from 15 to 0; step 16..31: ascend from 0 to 15
const level = (step32 < 16) ? (15 - step32) : (step32 - 16)
return level / 7.5 - 1.0 // map 0..15 → -1..+1
})
}
// ── Send to audio hardware ────────────────────────────────────────────────── // ── Send to audio hardware ──────────────────────────────────────────────────
function sendBuffer(buf, playhead, offsetSec, lengthSec, stagingPtr) { function sendBuffer(buf, playhead, offsetSec, lengthSec, stagingPtr) {
@@ -322,8 +340,49 @@ function sendBuffer(buf, playhead, offsetSec, lengthSec, stagingPtr) {
sys.poke(stagingPtr + 2 * i + 1, readU8(buf, 1, cursor + i)) sys.poke(stagingPtr + 2 * i + 1, readU8(buf, 1, cursor + i))
} }
// Wait for room in the playback queue (mirrors playwav.js idiom) // Wait for room in the playback queue (mirrors playwav.js idiom)
while (audio.getPosition(playhead) > 2) sys.sleep(2) // while (audio.getPosition(playhead) > 2) sys.sleep(2)
audio.putPcmDataByPtr(stagingPtr, take * 2, 0) audio.putPcmDataByPtr(stagingPtr, take * 2, playhead)
audio.setSampleUploadLength(playhead, take * 2)
audio.startSampleUpload(playhead)
remaining -= take
cursor += take
}
if (ownsStaging) sys.free(stagingPtr)
}
// Lazily-allocated JS-side interleave scratch; shared across sendBufferFast calls.
let _sendFastScratch = null
function sendBufferFast(buf, playhead, offsetSec, lengthSec, stagingPtr) {
// Like sendBuffer but interleaves L/R via a JS Uint8Array + one sys.pokeBytes per chunk,
// instead of ~2n sys.poke calls. Requires a non-native (JS-backed) buffer.
// Falls back to sendBuffer for native buffers.
if (isNative(buf)) { sendBuffer(buf, playhead, offsetSec, lengthSec, stagingPtr); return }
const start = (offsetSec != null) ? secToSamples(offsetSec) : 0
const total = (lengthSec != null) ? secToSamples(lengthSec) : (buf.samples - start)
const MAX_CHUNK = 32768
const ownsStaging = (stagingPtr == null)
if (ownsStaging) stagingPtr = sys.malloc(Math.min(total, MAX_CHUNK) * 2)
const scratchNeeded = Math.min(total, MAX_CHUNK) * 2
if (_sendFastScratch == null || _sendFastScratch.length < scratchNeeded) {
_sendFastScratch = new Uint8Array(scratchNeeded)
}
let remaining = total
let cursor = start
while (remaining > 0) {
const take = Math.min(remaining, MAX_CHUNK)
const L = buf[0], R = buf[1], sc = _sendFastScratch
for (let i = 0; i < take; i++) {
sc[2 * i] = L[cursor + i]
sc[2 * i + 1] = R[cursor + i]
}
sys.pokeBytes(stagingPtr, sc.subarray(0, take * 2), take * 2)
// while (audio.getPosition(playhead) > 2) sys.sleep(2)
audio.putPcmDataByPtr(playhead, stagingPtr, take * 2, 0)
audio.setSampleUploadLength(playhead, take * 2) audio.setSampleUploadLength(playhead, take * 2)
audio.startSampleUpload(playhead) audio.startSampleUpload(playhead)
remaining -= take remaining -= take
@@ -336,6 +395,6 @@ function sendBuffer(buf, playhead, offsetSec, lengthSec, stagingPtr) {
exports = { exports = {
HW_SAMPLING_RATE, HW_SAMPLING_RATE,
makeBuffer, makeBufferNative, freeBufferNative, clearBuffer, makeBuffer, makeBufferNative, freeBufferNative, clearBuffer,
makeSquare, makeTriangle, makeAliasedTriangle, makeNoise, makeSquare, makeTriangle, makeAliasedTriangle, makeAliasedTriangleNES, makeNoise,
sendBuffer sendBuffer, sendBufferFast
} }

View File

@@ -2014,17 +2014,17 @@ notes are tuned as 4096 Tone-Equal Temperament. Tuning is set per-sample using t
Sound Adapter MMIO Sound Adapter MMIO
0..1 RW: Play head #1 position 0..1 RW: Play head #0 position (how many samples has been queued)
2..3 RW: Play head #1 length param 2..3 RW: Play head #0 length param
4 RW: Play head #1 master volume 4 RW: Play head #0 master volume
5 RW: Play head #1 master pan 5 RW: Play head #0 master pan
6..9 RW: Play head #1 flags 6..9 RW: Play head #0 flags
10..11 RW:Play head #2 position 10..11 RW:Play head #1 position (how many samples has been queued)
12..13 RW:Play head #2 length param 12..13 RW:Play head #1 length param
14 RW: Play head #2 master volume 14 RW: Play head #1 master volume
15 RW: Play head #2 master pan 15 RW: Play head #1 master pan
16..19 RW:Play head #2 flags 16..19 RW:Play head #1 flags
... auto-fill to Play head #4 ... auto-fill to Play head #4
@@ -2034,13 +2034,16 @@ Sound Adapter MMIO
When called with byte 17, initialisation will precede before the decoding When called with byte 17, initialisation will precede before the decoding
41 RO: Media Decoder Status 41 RO: MP2 Decoder Status
Non-zero value indicates the decoder is busy Non-zero value indicates the decoder is busy
42 WO: TAD Decoder Control 42 WO: TAD Decoder Control
Write 1 to decode TAD data Write 1 to decode TAD data
43 RW: TAD Quality 43 RW: TAD Quality
Must be set to appropriate value before decoding Must be set to appropriate value before decoding
44 RW: TAD Decoder Status
45 RW: Select PCM Bin for playhead (writing causes side effects)
64..2367 RW: MP2 Decoded Samples (unsigned 8-bit stereo) 64..2367 RW: MP2 Decoded Samples (unsigned 8-bit stereo)
2368..4095 RW: MP2 Frame to be decoded 2368..4095 RW: MP2 Frame to be decoded

View File

@@ -5,6 +5,23 @@ import net.torvald.tsvm.peripheral.AudioAdapter
import net.torvald.tsvm.peripheral.MP2Env 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. * Created by minjaesong on 2022-12-31.
*/ */
class AudioJSR223Delegate(private val vm: VM) { 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 setTickRate(playhead: Int, rate: Int) { getPlayhead(playhead)?.tickRate = rate and 255 }
fun getTickRate(playhead: Int) = getPlayhead(playhead)?.tickRate 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 { getFirstSnd()?.let {
val vkMult = if (ptr >= 0) 1 else -1 val vkMult = if (ptr >= 0) 1 else -1
for (k in 0L until length) { for (k in 0L until length) {
val vk = k * vkMult 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 setPcmQueueCapacityIndex(playhead: Int, index: Int) { getPlayhead(playhead)?.pcmQueueSizeIndex = index }
fun getPcmQueueCapacityIndex(playhead: Int) { getPlayhead(playhead)?.pcmQueueSizeIndex } fun getPcmQueueCapacityIndex(playhead: Int) { getPlayhead(playhead)?.pcmQueueSizeIndex }

View File

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