mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-03-07 19:51:51 +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.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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
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.
|
||||
*/
|
||||
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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user