mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-06 13:38:30 +09:00
Taud: 8 MB sample rom/it and xm resampling too-long samples
This commit is contained in:
@@ -185,6 +185,61 @@ class AudioJSR223Delegate(private val vm: VM) {
|
||||
|
||||
fun getBaseAddr(): Int? = getFirstSnd()?.let { return it.vm.findPeriSlotNum(it)?.times(-131072)?.minus(1) }
|
||||
fun getMemAddr(): Int? = getFirstSnd()?.let { return it.vm.findPeriSlotNum(it)?.times(-1048576)?.minus(1) }
|
||||
|
||||
/** Switch the sample-bin window (peripheral memory 0..524287) to bank `bank` (0..15).
|
||||
* The 8 MB sample pool is organised as 16 × 512 K banks; only the selected bank
|
||||
* is visible through the window. (terranmon.txt:1985-1997, MMIO 46.) */
|
||||
fun setSampleBank(bank: Int) { getFirstSnd()?.mmio_write(46L, bank.toByte()) }
|
||||
fun getSampleBank(): Int? = getFirstSnd()?.sampleBank
|
||||
|
||||
/** Decompress a Taud sample+instrument blob (gzip or zstd) directly into the
|
||||
* audio adapter's 8 MB sample pool and 64 K instrument bin, bypassing the user
|
||||
* memory staging buffer. The decompressed payload must be exactly
|
||||
* `SAMPLE_BIN_TOTAL + 65536` bytes (8 MB samples followed by 64 K instruments).
|
||||
*
|
||||
* Needed because user space is capped at 8 MB and cannot hold the full 8256 kB
|
||||
* decompressed image as a contiguous buffer. */
|
||||
fun uploadSampleInstBlob(srcPtr: Int, srcLen: Int): Int {
|
||||
val snd = getFirstSnd() ?: return 0
|
||||
val inbytes = ByteArray(srcLen) { vm.peek(srcPtr.toLong() + it)!! }
|
||||
val bytes = CompressorDelegate.decomp(inbytes)
|
||||
val sampleSize = AudioAdapter.SAMPLE_BIN_TOTAL.toInt()
|
||||
val instSize = 65536
|
||||
if (bytes.size < sampleSize + instSize) return 0
|
||||
UnsafeHelper.memcpyRaw(
|
||||
bytes, UnsafeHelper.getArrayOffset(bytes),
|
||||
null, snd.sampleBin.ptr,
|
||||
sampleSize.toLong()
|
||||
)
|
||||
for (i in 0 until instSize) {
|
||||
snd.instruments[i / 256].setByte(i % 256, bytes[sampleSize + i].toInt() and 0xFF)
|
||||
}
|
||||
return bytes.size
|
||||
}
|
||||
|
||||
/** Compress the audio adapter's full 8 MB sample pool + 64 K instrument bin
|
||||
* (8256 kB total) and write the resulting gzip/zstd blob to user-memory `dstPtr`.
|
||||
* Returns the compressed size. The caller must ensure `dstMaxLen` is large
|
||||
* enough; for incompressible noise the worst case is ~8.3 MB which exceeds
|
||||
* user space — but realistic sample data compresses easily. */
|
||||
fun captureSampleInstBlob(dstPtr: Int, dstMaxLen: Int): Int {
|
||||
val snd = getFirstSnd() ?: return 0
|
||||
val sampleSize = AudioAdapter.SAMPLE_BIN_TOTAL.toInt()
|
||||
val instSize = 65536
|
||||
val raw = ByteArray(sampleSize + instSize)
|
||||
UnsafeHelper.memcpyRaw(
|
||||
null, snd.sampleBin.ptr,
|
||||
raw, UnsafeHelper.getArrayOffset(raw),
|
||||
sampleSize.toLong()
|
||||
)
|
||||
for (i in 0 until instSize) {
|
||||
raw[sampleSize + i] = snd.instruments[i / 256].getByte(i % 256)
|
||||
}
|
||||
val compressed = CompressorDelegate.comp(raw)
|
||||
val n = minOf(compressed.size, dstMaxLen)
|
||||
for (i in 0 until n) vm.poke((dstPtr + i).toLong(), compressed[i])
|
||||
return compressed.size
|
||||
}
|
||||
fun mp2Init() = getFirstSnd()?.mmio_write(40L, 16)
|
||||
fun mp2Decode() = getFirstSnd()?.mmio_write(40L, 1)
|
||||
fun mp2InitThenDecode() = getFirstSnd()?.mmio_write(40L, 17)
|
||||
|
||||
@@ -812,7 +812,9 @@ class VM(
|
||||
if (fromRel + len > 1048576) return null
|
||||
|
||||
return if (dev is AudioAdapter) {
|
||||
if (relPtrInDev(fromRel, len, 0, 114687)) dev.sampleBin.ptr + fromRel - 0
|
||||
// Sample-bin window: 0..524287 maps into the 8 MB pool through MMIO 46.
|
||||
if (relPtrInDev(fromRel, len, 0, 524287))
|
||||
dev.sampleBin.ptr + dev.sampleBank.toLong() * AudioAdapter.SAMPLE_BANK_SIZE + fromRel
|
||||
else null
|
||||
}
|
||||
else if (dev is GraphicsAdapter) {
|
||||
|
||||
@@ -62,7 +62,9 @@ class VMJSR223Delegate(private val vm: VM) {
|
||||
// System.err.println("MEMORY dev=${dev.typestring}, fromIndex=$fromIndex, fromRel=$fromRel")
|
||||
|
||||
return if (dev is AudioAdapter) {
|
||||
if (relPtrInDev(fromRel, len, 0, 114687)) dev.sampleBin.ptr + fromRel - 0
|
||||
// Sample-bin window: 0..524287 maps into the 8 MB pool through MMIO 46.
|
||||
if (relPtrInDev(fromRel, len, 0, 524287))
|
||||
dev.sampleBin.ptr + dev.sampleBank.toLong() * AudioAdapter.SAMPLE_BANK_SIZE + fromRel
|
||||
else null
|
||||
}
|
||||
else if (dev is GraphicsAdapter) {
|
||||
|
||||
@@ -144,13 +144,26 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
// 8 ms at 32 kHz — long enough to bury the click, short enough not to read as fade.
|
||||
// Applied on sample end only (preserves attack transients on note start).
|
||||
const val RAMP_OUT_SAMPLES = 256
|
||||
|
||||
// Sample bin: 8 MB total, banked through a 512 K window at peripheral
|
||||
// memory 0..524287. MMIO 46 holds the currently-exposed bank index.
|
||||
const val SAMPLE_BANK_SIZE: Long = 524288L // 512 K
|
||||
const val SAMPLE_BANK_COUNT: Int = 16 // 16 × 512 K = 8 MB
|
||||
const val SAMPLE_BIN_TOTAL: Long = SAMPLE_BANK_SIZE * SAMPLE_BANK_COUNT
|
||||
const val SAMPLE_BANK_MASK: Int = SAMPLE_BANK_COUNT - 1
|
||||
}
|
||||
|
||||
// Memory map (terranmon.txt:1985-1997, updated 2026-05-06):
|
||||
// 0..720895 sample bin (704K, was 737280)
|
||||
// Memory map (terranmon.txt:1985-1997, updated 2026-05-08):
|
||||
// 0..524287 sample bin window (512K — exposes one bank of 8 MB pool)
|
||||
// 524288..720895 reserved (no-op on access)
|
||||
// 720896..786431 instrument bin (256 inst × 256 bytes = 64K)
|
||||
// 786432.. play data 1 / 2 / TAD blocks (anchors unchanged)
|
||||
internal val sampleBin = UnsafeHelper.allocate(720896L, this)
|
||||
//
|
||||
// Backing sample memory is 8 MB, banked in 16 × 512K pages. MMIO 46 holds
|
||||
// the currently-exposed bank index (0..15); reads/writes through the window
|
||||
// hit `sampleBin[sampleBank * 524288 + offset]`.
|
||||
internal val sampleBin = UnsafeHelper.allocate(SAMPLE_BIN_TOTAL, this)
|
||||
@Volatile var sampleBank: Int = 0 // 0..15, controls the 0..524287 window
|
||||
internal val instruments = Array(256) { TaudInst(it) }
|
||||
internal val playdata = Array(4096) { Array(64) { TaudPlayData(0xFFFF, 0, 0, 0, 32, 0, 0, 0) } }
|
||||
internal val playheads: Array<Playhead>
|
||||
@@ -322,7 +335,8 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
|
||||
override fun peek(addr: Long): Byte {
|
||||
return when (val adi = addr.toInt()) {
|
||||
in 0..720895 -> sampleBin[addr]
|
||||
in 0..524287 -> sampleBin[sampleBank * SAMPLE_BANK_SIZE + addr]
|
||||
in 524288..720895 -> 0 // reserved
|
||||
in 720896..786431 -> (adi - 720896).let { instruments[it / 256].getByte(it % 256) }
|
||||
in 786432..851967 -> { val off = adi - 786432; playdata[playheads[0].patBank1 * 128 + off / 512][(off % 512) / 8].getByte(off % 8) }
|
||||
in 851968..917503 -> { val off = adi - 851968; playdata[playheads[0].patBank2 * 128 + off / 512][(off % 512) / 8].getByte(off % 8) }
|
||||
@@ -336,7 +350,8 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
val adi = addr.toInt()
|
||||
val bi = byte.toUint()
|
||||
when (adi) {
|
||||
in 0..720895 -> { sampleBin[addr] = byte }
|
||||
in 0..524287 -> { sampleBin[sampleBank * SAMPLE_BANK_SIZE + addr] = byte }
|
||||
in 524288..720895 -> { /* reserved */ }
|
||||
in 720896..786431 -> (adi - 720896).let { instruments[it / 256].setByte(it % 256, bi) }
|
||||
in 786432..851967 -> { val off = adi - 786432; playdata[playheads[0].patBank1 * 128 + off / 512][(off % 512) / 8].setByte(off % 8, bi) }
|
||||
in 851968..917503 -> { val off = adi - 851968; playdata[playheads[0].patBank2 * 128 + off / 512][(off % 512) / 8].setByte(off % 8, bi) }
|
||||
@@ -358,6 +373,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
43 -> tadQuality.toByte()
|
||||
44 -> tadBusy.toInt().toByte()
|
||||
45 -> selectedPcmBin.toByte()
|
||||
46 -> sampleBank.toByte()
|
||||
in 64..2367 -> mediaDecodedBin[addr - 64]
|
||||
in 2368..4095 -> mediaFrameBin[addr - 2368]
|
||||
in 4096..4097 -> 0
|
||||
@@ -393,6 +409,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
tadQuality = bi.coerceIn(0, 5)
|
||||
}
|
||||
45 -> selectedPcmBin = bi % 4
|
||||
46 -> sampleBank = bi and SAMPLE_BANK_MASK
|
||||
in 64..2367 -> { mediaDecodedBin[addr - 64] = byte }
|
||||
in 2368..4095 -> { mediaFrameBin[addr - 2368] = byte }
|
||||
in 32768..65535 -> { (adi - 32768).let {
|
||||
@@ -1619,7 +1636,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
val sampleLen = inst.sampleLength.coerceAtLeast(1)
|
||||
val loopStart = inst.sampleLoopStart.toDouble()
|
||||
val loopEnd = inst.sampleLoopEnd.toDouble().coerceAtLeast(1.0)
|
||||
val binMax = 720895 // sampleBin is 720896 bytes (0..720895)
|
||||
val binMax = (SAMPLE_BIN_TOTAL - 1).toInt() // 8 MB pool, addressed via samplePtr directly (not banked)
|
||||
|
||||
val i0 = voice.samplePos.toInt().coerceIn(0, sampleLen - 1)
|
||||
val i1 = (i0 + 1).coerceAtMost(sampleLen - 1)
|
||||
|
||||
Reference in New Issue
Block a user