emulator: audio debugger

This commit is contained in:
minjaesong
2023-01-22 14:07:42 +09:00
parent 38081a2c36
commit 39a781ff71
8 changed files with 191 additions and 11 deletions

View File

@@ -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,

View File

@@ -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)
}

View File

@@ -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) {
}

View File

@@ -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()

View File

@@ -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<AudioAdapter.Playhead>)[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>): 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)
}
}
}

View File

@@ -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()

View File

@@ -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)

View File

@@ -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()