mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-12 23:54:04 +09:00
emulator: audio debugger
This commit is contained in:
@@ -77,7 +77,7 @@ private class WriteQueueingRunnable(val playhead: AudioAdapter.Playhead, val pcm
|
|||||||
it.pcmQueue.addLast(samples)
|
it.pcmQueue.addLast(samples)
|
||||||
|
|
||||||
it.pcmUploadLength = 0
|
it.pcmUploadLength = 0
|
||||||
it.position += 1
|
it.position = it.pcmQueue.size
|
||||||
Thread.sleep(6)
|
Thread.sleep(6)
|
||||||
}
|
}
|
||||||
else if (it.pcmUpload) {
|
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 class PlayInstSkip(arg: Int) : PlayInstruction(arg)
|
||||||
internal object PlayInstNop : PlayInstruction(0)
|
internal object PlayInstNop : PlayInstruction(0)
|
||||||
|
|
||||||
internal class Playhead(
|
class Playhead(
|
||||||
private val parent: AudioAdapter,
|
private val parent: AudioAdapter,
|
||||||
val index: Int,
|
val index: Int,
|
||||||
|
|
||||||
var position: Int = 0,
|
var position: Int = 0,
|
||||||
var pcmUploadLength: Int = 0,
|
var pcmUploadLength: Int = 0,
|
||||||
var masterVolume: Int = 0,
|
var masterVolume: Int = 0,
|
||||||
var masterPan: Int = 0,
|
var masterPan: Int = 128,
|
||||||
// var samplingRateMult: ThreeFiveMiniUfloat = ThreeFiveMiniUfloat(32),
|
// var samplingRateMult: ThreeFiveMiniUfloat = ThreeFiveMiniUfloat(32),
|
||||||
var bpm: Int = 120, // "stored" as 96
|
var bpm: Int = 120, // "stored" as 96
|
||||||
var tickRate: Int = 6,
|
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 note: Int, // 0..65535
|
||||||
var instrment: Int, // 0..255
|
var instrment: Int, // 0..255
|
||||||
var volume: Int, // 0..63
|
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)
|
data class TaudInstVolEnv(var volume: Int, var offset: ThreeFiveMiniUfloat)
|
||||||
internal data class TaudInst(
|
data class TaudInst(
|
||||||
var samplePtr: Int, // 17-bit number
|
var samplePtr: Int, // 17-bit number
|
||||||
var sampleLength: Int,
|
var sampleLength: Int,
|
||||||
var samplingRate: Int,
|
var samplingRate: Int,
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ class OpenALBufferedAudioDevice(
|
|||||||
private var renderedSeconds = 0f
|
private var renderedSeconds = 0f
|
||||||
private val secondsPerBuffer: Float
|
private val secondsPerBuffer: Float
|
||||||
private var bytes: ByteArray? = null
|
private var bytes: ByteArray? = null
|
||||||
|
private var bytesLength = 2
|
||||||
private val tempBuffer: ByteBuffer
|
private val tempBuffer: ByteBuffer
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -75,6 +76,7 @@ class OpenALBufferedAudioDevice(
|
|||||||
bytes!![ii++] = sample
|
bytes!![ii++] = sample
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
bytesLength = ii
|
||||||
writeSamples(bytes!!, 0, numSamples * 2)
|
writeSamples(bytes!!, 0, numSamples * 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,6 +91,7 @@ class OpenALBufferedAudioDevice(
|
|||||||
bytes!![ii++] = (sample.toInt() shr 8 and 0xFF).toByte()
|
bytes!![ii++] = (sample.toInt() shr 8 and 0xFF).toByte()
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
bytesLength = ii
|
||||||
writeSamples(bytes!!, 0, numSamples * 2)
|
writeSamples(bytes!!, 0, numSamples * 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,6 +108,7 @@ class OpenALBufferedAudioDevice(
|
|||||||
bytes!![ii++] = (intSample shr 8 and 0xFF).toByte()
|
bytes!![ii++] = (intSample shr 8 and 0xFF).toByte()
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
bytesLength = ii
|
||||||
writeSamples(bytes!!, 0, numSamples * 2)
|
writeSamples(bytes!!, 0, numSamples * 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ open class RomBank(vm: VM, romfile: File, bankCount: Int) : RamBank(vm, bankCoun
|
|||||||
val bytes = romfile.readBytes()
|
val bytes = romfile.readBytes()
|
||||||
UnsafeHelper.memcpyRaw(bytes, 0, null, mem.ptr, bytes.size.toLong())
|
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) {
|
override fun poke(addr: Long, byte: Byte) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,10 @@ object TinyAlphNum : BitmapFont() {
|
|||||||
return null
|
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 getLineHeight() = H.toFloat()
|
||||||
override fun getCapHeight() = getLineHeight()
|
override fun getCapHeight() = getLineHeight()
|
||||||
override fun getXHeight() = getLineHeight()
|
override fun getXHeight() = getLineHeight()
|
||||||
|
|||||||
148
tsvm_executable/src/net/torvald/tsvm/AudioMenu.kt
Normal file
148
tsvm_executable/src/net/torvald/tsvm/AudioMenu.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
|||||||
/**
|
/**
|
||||||
* Created by minjaesong on 2022-10-25.
|
* 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 show()
|
||||||
abstract fun hide()
|
abstract fun hide()
|
||||||
|
|||||||
@@ -24,17 +24,19 @@ class MMUMenu(parent: VMEmuExecutable, x: Int, y: Int, w: Int, h: Int) : EmuMenu
|
|||||||
override fun update() {
|
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) {
|
override fun render(batch: SpriteBatch) {
|
||||||
batch.color = Color.WHITE
|
batch.color = Color.WHITE
|
||||||
|
|
||||||
val vmInfo = parent.getCurrentlySelectedVM()
|
vmInfo = parent.getCurrentlySelectedVM() ?: vmInfo
|
||||||
|
|
||||||
if (vmInfo == null) {
|
if (vmInfo == null) {
|
||||||
batch.inUse {
|
batch.inUse {
|
||||||
FONT.draw(batch, "Please select a VM", 12f, 11f + 0* FONT.H)
|
FONT.draw(batch, "Please select a VM", 12f, 11f + 0* FONT.H)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else vmInfo.let { (vm, vmName) ->
|
else vmInfo!!.let { (vm, vmName) ->
|
||||||
batch.inUse {
|
batch.inUse {
|
||||||
FONT.draw(batch, "Allocated size: ${vm.allocatedBlockCount * vm.MALLOC_UNIT}", 12f, 11f + 0* FONT.H)
|
FONT.draw(batch, "Allocated size: ${vm.allocatedBlockCount * vm.MALLOC_UNIT}", 12f, 11f + 0* FONT.H)
|
||||||
|
|
||||||
|
|||||||
@@ -193,6 +193,8 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX:
|
|||||||
currentVMselection = index
|
currentVMselection = index
|
||||||
// TODO somehow implement the inputstream that cares about the currentVMselection
|
// TODO somehow implement the inputstream that cares about the currentVMselection
|
||||||
Gdx.input.inputProcessor = if (currentVMselection != null) vms[currentVMselection!!]?.vm?.getIO() ?: null else vmEmuInputProcessor
|
Gdx.input.inputProcessor = if (currentVMselection != null) vms[currentVMselection!!]?.vm?.getIO() ?: null else vmEmuInputProcessor
|
||||||
|
|
||||||
|
refreshCardTabs()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun initVMenv(vm: VM, profileName: String) {
|
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 menuTabX = windowWidth * (panelsX-1) + 2
|
||||||
private val menuTabY =windowHeight * (panelsY-1) + FONT.H + 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 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 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
|
ProfilesMenu(this, menuTabX, menuTabY, menuTabW, menuTabH), // Profiles
|
||||||
DummyMenu(this, menuTabX, menuTabY, menuTabW, menuTabH), // MMIO
|
DummyMenu(this, menuTabX, menuTabY, menuTabW, menuTabH), // MMIO
|
||||||
MMUMenu(this, menuTabX, menuTabY, menuTabW, menuTabH), // MMU
|
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
|
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) {
|
private fun drawMenu(batch: SpriteBatch, x: Float, y: Float) {
|
||||||
if (tabChangeRequested != null) {
|
if (tabChangeRequested != null) {
|
||||||
tabs[menuTabSel].hide()
|
tabs[menuTabSel].hide()
|
||||||
|
|||||||
Reference in New Issue
Block a user