From 39a781ff71c5c8b358dd8e3802f134b547992041 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Sun, 22 Jan 2023 14:07:42 +0900 Subject: [PATCH] emulator: audio debugger --- .../torvald/tsvm/peripheral/AudioAdapter.kt | 12 +- .../peripheral/OpenALBufferedAudioDevice.kt | 4 + .../net/torvald/tsvm/peripheral/RamBank.kt | 2 +- .../torvald/terrarum/imagefont/TinyAlphNum.kt | 4 + .../src/net/torvald/tsvm/AudioMenu.kt | 148 ++++++++++++++++++ .../src/net/torvald/tsvm/EmuMenu.kt | 2 +- .../src/net/torvald/tsvm/MMUMenu.kt | 6 +- .../src/net/torvald/tsvm/VMEmuExecutable.kt | 24 ++- 8 files changed, 191 insertions(+), 11 deletions(-) create mode 100644 tsvm_executable/src/net/torvald/tsvm/AudioMenu.kt diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/AudioAdapter.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/AudioAdapter.kt index 49ac413..3b75680 100644 --- a/tsvm_core/src/net/torvald/tsvm/peripheral/AudioAdapter.kt +++ b/tsvm_core/src/net/torvald/tsvm/peripheral/AudioAdapter.kt @@ -77,7 +77,7 @@ private class WriteQueueingRunnable(val playhead: AudioAdapter.Playhead, val pcm it.pcmQueue.addLast(samples) it.pcmUploadLength = 0 - it.position += 1 + it.position = it.pcmQueue.size Thread.sleep(6) } else if (it.pcmUpload) { @@ -325,14 +325,14 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) { internal class PlayInstSkip(arg: Int) : PlayInstruction(arg) internal object PlayInstNop : PlayInstruction(0) - internal class Playhead( + class Playhead( private val parent: AudioAdapter, val index: Int, var position: Int = 0, var pcmUploadLength: Int = 0, var masterVolume: Int = 0, - var masterPan: Int = 0, + var masterPan: Int = 128, // var samplingRateMult: ThreeFiveMiniUfloat = ThreeFiveMiniUfloat(32), var bpm: Int = 120, // "stored" as 96 var tickRate: Int = 6, @@ -435,7 +435,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) { } } - internal data class TaudPlayData( + data class TaudPlayData( var note: Int, // 0..65535 var instrment: Int, // 0..255 var volume: Int, // 0..63 @@ -471,8 +471,8 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) { } - internal data class TaudInstVolEnv(var volume: Int, var offset: ThreeFiveMiniUfloat) - internal data class TaudInst( + data class TaudInstVolEnv(var volume: Int, var offset: ThreeFiveMiniUfloat) + data class TaudInst( var samplePtr: Int, // 17-bit number var sampleLength: Int, var samplingRate: Int, diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/OpenALBufferedAudioDevice.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/OpenALBufferedAudioDevice.kt index 35335bc..e04a7cb 100644 --- a/tsvm_core/src/net/torvald/tsvm/peripheral/OpenALBufferedAudioDevice.kt +++ b/tsvm_core/src/net/torvald/tsvm/peripheral/OpenALBufferedAudioDevice.kt @@ -49,6 +49,7 @@ class OpenALBufferedAudioDevice( private var renderedSeconds = 0f private val secondsPerBuffer: Float private var bytes: ByteArray? = null + private var bytesLength = 2 private val tempBuffer: ByteBuffer /** @@ -75,6 +76,7 @@ class OpenALBufferedAudioDevice( bytes!![ii++] = sample i++ } + bytesLength = ii writeSamples(bytes!!, 0, numSamples * 2) } @@ -89,6 +91,7 @@ class OpenALBufferedAudioDevice( bytes!![ii++] = (sample.toInt() shr 8 and 0xFF).toByte() i++ } + bytesLength = ii writeSamples(bytes!!, 0, numSamples * 2) } @@ -105,6 +108,7 @@ class OpenALBufferedAudioDevice( bytes!![ii++] = (intSample shr 8 and 0xFF).toByte() i++ } + bytesLength = ii writeSamples(bytes!!, 0, numSamples * 2) } diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/RamBank.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/RamBank.kt index 5eef03a..3f60576 100644 --- a/tsvm_core/src/net/torvald/tsvm/peripheral/RamBank.kt +++ b/tsvm_core/src/net/torvald/tsvm/peripheral/RamBank.kt @@ -67,7 +67,7 @@ open class RomBank(vm: VM, romfile: File, bankCount: Int) : RamBank(vm, bankCoun val bytes = romfile.readBytes() UnsafeHelper.memcpyRaw(bytes, 0, null, mem.ptr, bytes.size.toLong()) } - override val typestring = "ROMB" + override val typestring = "romb" override fun poke(addr: Long, byte: Byte) { } diff --git a/tsvm_executable/src/net/torvald/terrarum/imagefont/TinyAlphNum.kt b/tsvm_executable/src/net/torvald/terrarum/imagefont/TinyAlphNum.kt index 807e03f..3604a1b 100644 --- a/tsvm_executable/src/net/torvald/terrarum/imagefont/TinyAlphNum.kt +++ b/tsvm_executable/src/net/torvald/terrarum/imagefont/TinyAlphNum.kt @@ -75,6 +75,10 @@ object TinyAlphNum : BitmapFont() { return null } + fun drawRalign(batch: Batch, text: CharSequence, x: Float, y: Float): GlyphLayout? { + return draw(batch, text, x - W*text.length, y) + } + override fun getLineHeight() = H.toFloat() override fun getCapHeight() = getLineHeight() override fun getXHeight() = getLineHeight() diff --git a/tsvm_executable/src/net/torvald/tsvm/AudioMenu.kt b/tsvm_executable/src/net/torvald/tsvm/AudioMenu.kt new file mode 100644 index 0000000..41f8e2a --- /dev/null +++ b/tsvm_executable/src/net/torvald/tsvm/AudioMenu.kt @@ -0,0 +1,148 @@ +package net.torvald.tsvm + +import com.badlogic.gdx.Audio +import com.badlogic.gdx.graphics.Color +import com.badlogic.gdx.graphics.g2d.SpriteBatch +import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.toUint +import net.torvald.tsvm.EmulatorGuiToolkit.Theme.COL_ACTIVE3 +import net.torvald.tsvm.EmulatorGuiToolkit.Theme.COL_HIGHLIGHT2 +import net.torvald.tsvm.EmulatorGuiToolkit.Theme.COL_WELL +import net.torvald.tsvm.VMEmuExecutableWrapper.Companion.FONT +import net.torvald.tsvm.peripheral.AudioAdapter +import kotlin.math.roundToInt + +/** + * Created by minjaesong on 2023-01-22. + */ +class AudioMenu(parent: VMEmuExecutable, x: Int, y: Int, w: Int, h: Int) : EmuMenu(parent, x, y, w, h) { + + override fun show() { + } + + override fun hide() { + } + + override fun update() { + } + + private val COL_SOUNDSCOPE_BACK = Color(0x081c08ff.toInt()) + private val COL_SOUNDSCOPE_FORE = Color(0x80f782ff.toInt()) + private val STR_PLAY = "\u00D2\u00D3" + + override fun render(batch: SpriteBatch) { + + val adev = parent.getCurrentlySelectedVM()?.vm?.peripheralTable?.getOrNull(cardIndex ?: -1)?.peripheral as? AudioAdapter + + batch.inUse { + // draw backgrounds + batch.color = COL_WELL + for (i in 0..3) { batch.fillRect(7, 3 + 116*i, 102, 8*FONT.H + 4) } + batch.color = COL_SOUNDSCOPE_BACK + for (i in 0..3) { batch.fillRect(117, 3 + 116*i, 512, 8*FONT.H + 4) } + } + + + if (adev != null) { + for (i in 0..3) { + val ahead = (adev.extortField("playheads") as Array)[i] + drawStatusLCD(adev, ahead, batch, i, 9f + 7, 5f + 7 + 116 * i) + drawSoundscope(adev, ahead, batch, i, 117f, 5f + 116 * i) + } + } + + } + + private fun drawStatusLCD(audio: AudioAdapter, ahead: AudioAdapter.Playhead, batch: SpriteBatch, index: Int, x: Float, y: Float) { + batch.inUse { + batch.color = Color.WHITE + // PLAY icon + if (ahead.isPlaying) + FONT.draw(batch, STR_PLAY, x, y) + FONT.draw(batch, if (ahead.isPcmMode) "PCM" else "TRACKER", x + 21, y) + + // PCM Mode labels + if (ahead.isPcmMode) { + batch.color = Color.WHITE + FONT.draw(batch, "Queue", x, y + 2*FONT.H) + FONT.draw(batch, "Volume", x, y + 3*FONT.H) + FONT.draw(batch, "Pan", x, y + 4*FONT.H) + + // Queue sparkline + batch.color = COL_SOUNDSCOPE_BACK + batch.fillRect(x + 5*FONT.W + 2, y + 2*FONT.H, FONT.W * 7, FONT.H) + val qgrsize = ahead.getPcmQueueCapacity().let { ahead.position.coerceAtMost(it) / it.toDouble() }.times(FONT.W * 7).roundToInt() + batch.color = COL_HIGHLIGHT2 + batch.fillRect(x + 5*FONT.W + 2, y + 2*FONT.H + 1, qgrsize, FONT.H - 2) + + batch.color = COL_ACTIVE3 + val qtxt = "${ahead.position}/${ahead.getPcmQueueCapacity()}" + FONT.drawRalign(batch, qtxt, x + 84, y + 2*FONT.H) + FONT.drawRalign(batch, "${ahead.masterVolume}", x + 84, y + 3*FONT.H) + FONT.drawRalign(batch, "${ahead.masterPan}", x + 84, y + 4*FONT.H) + } + else { + batch.color = Color.WHITE + FONT.draw(batch, "Pos", x, y + 2*FONT.H) + FONT.draw(batch, "Volume", x, y + 3*FONT.H) + FONT.draw(batch, "Pan", x, y + 4*FONT.H) + FONT.draw(batch, "BPM", x, y + 5*FONT.H) + FONT.draw(batch, "Tickrate", x, y + 6*FONT.H) + + batch.color = COL_ACTIVE3 + FONT.drawRalign(batch, "${ahead.position}", x + 84, y + 2*FONT.H) + FONT.drawRalign(batch, "${ahead.masterVolume}", x + 84, y + 3*FONT.H) + FONT.drawRalign(batch, "${ahead.masterPan}", x + 84, y + 4*FONT.H) + FONT.drawRalign(batch, "${ahead.bpm}", x + 84, y + 5*FONT.H) + FONT.drawRalign(batch, "${ahead.tickRate}", x + 84, y + 6*FONT.H) + } + } + } + + fun Int.u16Tos16() = if (this > 32767) this - 65536 else this + + private fun drawSoundscope(audio: AudioAdapter, ahead: AudioAdapter.Playhead, batch: SpriteBatch, index: Int, x: Float, y: Float) { + val gdxadev = ahead.audioDevice + val bytes = gdxadev.extortField("bytes") as ByteArray? + val bytesLen = gdxadev.extortField("bytesLength") as Int + val envelopeHalfHeight = 27 + + batch.inUse { + batch.color = COL_SOUNDSCOPE_FORE + if (ahead.isPcmMode && bytes != null) { + val smpCnt = bytesLen / 4 - 1 + + for (s in 0..511) { + val i = (smpCnt * (s / 511.0)).roundToInt().and(0xfffffe) + + val smpL = (bytes[i*4].toUint() or bytes[i*4+1].toUint().shl(8)).u16Tos16().toDouble().div(32767) + val smpR = (bytes[i*4+2].toUint() or bytes[i*4+3].toUint().shl(8)).u16Tos16().toDouble().div(32767) + val smpLH = (smpL * envelopeHalfHeight).roundToInt() // -50..50 + val smpRH = (smpR * envelopeHalfHeight).roundToInt() // -50..50 + + batch.fillRect(x + s, y + 27, 1, smpLH) + batch.fillRect(x + s, y + 81, 1, smpLH) + } + } + else { + + } + } + } + + override fun dispose() { + } + + private fun Any.extortField(name: String): Any? { // yes I'm deliberately using negative words for the function name + return this.javaClass.getDeclaredField(name).let { + it.isAccessible = true + it.get(this) + } + } + private fun Any.forceInvoke(name: String, params: Array): Any? { // yes I'm deliberately using negative words for the function name + return this.javaClass.getDeclaredMethod(name, *(params.map { it.javaClass }.toTypedArray())).let { + it.isAccessible = true + it.invoke(this, *params) + } + } + +} \ No newline at end of file diff --git a/tsvm_executable/src/net/torvald/tsvm/EmuMenu.kt b/tsvm_executable/src/net/torvald/tsvm/EmuMenu.kt index f554abb..0d99154 100644 --- a/tsvm_executable/src/net/torvald/tsvm/EmuMenu.kt +++ b/tsvm_executable/src/net/torvald/tsvm/EmuMenu.kt @@ -5,7 +5,7 @@ import com.badlogic.gdx.graphics.g2d.SpriteBatch /** * Created by minjaesong on 2022-10-25. */ -abstract class EmuMenu(val parent: VMEmuExecutable, val x: Int, val y: Int, val w: Int, val h: Int) { +abstract class EmuMenu(val parent: VMEmuExecutable, val x: Int, val y: Int, val w: Int, val h: Int, var cardIndex: Int? = null) { abstract fun show() abstract fun hide() diff --git a/tsvm_executable/src/net/torvald/tsvm/MMUMenu.kt b/tsvm_executable/src/net/torvald/tsvm/MMUMenu.kt index 4303497..b902cfe 100644 --- a/tsvm_executable/src/net/torvald/tsvm/MMUMenu.kt +++ b/tsvm_executable/src/net/torvald/tsvm/MMUMenu.kt @@ -24,17 +24,19 @@ class MMUMenu(parent: VMEmuExecutable, x: Int, y: Int, w: Int, h: Int) : EmuMenu override fun update() { } + private var vmInfo: VMEmuExecutable.VMRunnerInfo? = null // to remember previous selection as `parent.getCurrentlySelectedVM()` will return null when MMU menu is clicked + override fun render(batch: SpriteBatch) { batch.color = Color.WHITE - val vmInfo = parent.getCurrentlySelectedVM() + vmInfo = parent.getCurrentlySelectedVM() ?: vmInfo if (vmInfo == null) { batch.inUse { FONT.draw(batch, "Please select a VM", 12f, 11f + 0* FONT.H) } } - else vmInfo.let { (vm, vmName) -> + else vmInfo!!.let { (vm, vmName) -> batch.inUse { FONT.draw(batch, "Allocated size: ${vm.allocatedBlockCount * vm.MALLOC_UNIT}", 12f, 11f + 0* FONT.H) diff --git a/tsvm_executable/src/net/torvald/tsvm/VMEmuExecutable.kt b/tsvm_executable/src/net/torvald/tsvm/VMEmuExecutable.kt index ebeafa8..80854c9 100644 --- a/tsvm_executable/src/net/torvald/tsvm/VMEmuExecutable.kt +++ b/tsvm_executable/src/net/torvald/tsvm/VMEmuExecutable.kt @@ -193,6 +193,8 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX: currentVMselection = index // TODO somehow implement the inputstream that cares about the currentVMselection Gdx.input.inputProcessor = if (currentVMselection != null) vms[currentVMselection!!]?.vm?.getIO() ?: null else vmEmuInputProcessor + + refreshCardTabs() } internal fun initVMenv(vm: VM, profileName: String) { @@ -402,9 +404,15 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX: private val menuTabX = windowWidth * (panelsX-1) + 2 private val menuTabY =windowHeight * (panelsY-1) + FONT.H + 2 + private val dummyMenu = DummyMenu(this, menuTabX, menuTabY, menuTabW, menuTabH) + private val menuRepository = mapOf( + VM.PERITYPE_SOUND to AudioMenu(this, menuTabX, menuTabY, menuTabW, menuTabH), + "DUMMY" to dummyMenu + ) + private val menuTabs = listOf("Profiles", "MMIO", "MMU", "COM", "Card1", "Card2", "Card3", "Card4", "Card5", "Card6", "Card7", "Setup") private val tabPos = (menuTabs + "").mapIndexed { index, _ -> 1 + menuTabs.subList(0, index).sumBy { it.length } + 2 * index } - private val tabs = listOf( + private val tabs = arrayOf( ProfilesMenu(this, menuTabX, menuTabY, menuTabW, menuTabH), // Profiles DummyMenu(this, menuTabX, menuTabY, menuTabW, menuTabH), // MMIO MMUMenu(this, menuTabX, menuTabY, menuTabW, menuTabH), // MMU @@ -422,6 +430,20 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX: private var tabChangeRequested: Int? = 0 // null: not requested + // call this whenever the VM selection has changed + private fun refreshCardTabs() { + val vm = getCurrentlySelectedVM()?.vm + + if (vm != null) { + for (i in 1..7) { + val periType = vm.peripheralTable[i].type ?: "DUMMY" + val menu = menuRepository[periType] ?: dummyMenu + menu.cardIndex = i + tabs[3 + i] = menu + } + } + } + private fun drawMenu(batch: SpriteBatch, x: Float, y: Float) { if (tabChangeRequested != null) { tabs[menuTabSel].hide()