From f69108c40d2857f96eec015f17f245af301d66ac Mon Sep 17 00:00:00 2001 From: minjaesong Date: Sat, 9 May 2026 20:19:04 +0900 Subject: [PATCH] TsvmEmulator: better snd debug view --- assets/disk0/tvdos/bin/taut.js | 3 +- mod2taud.py | 5 +- mon2taud.py | 3 +- terranmon.txt | 3 +- .../net/torvald/tsvm/AudioJSR223Delegate.kt | 7 +- .../torvald/tsvm/peripheral/AudioAdapter.kt | 15 +- .../src/net/torvald/tsvm/AudioMenu.kt | 255 +++++++++--------- 7 files changed, 152 insertions(+), 139 deletions(-) diff --git a/assets/disk0/tvdos/bin/taut.js b/assets/disk0/tvdos/bin/taut.js index 3138825..fcf9c31 100644 --- a/assets/disk0/tvdos/bin/taut.js +++ b/assets/disk0/tvdos/bin/taut.js @@ -2249,7 +2249,8 @@ function drawProjectContents(wo) { let mixerflag = initialTrackerMixerflags let toneModeStr = ['Linear pitch','Amiga pitch','Linear freq',''][mixerflag & 3] - let flagStrSelected = [toneModeStr] + let intpModeStr = ['Fast Sinc','No intp.','A500 intp.','A1200 intp.'][(mixerflag >>> 2) & 3] + let flagStrSelected = [toneModeStr, intpModeStr] let projMeta = { diff --git a/mod2taud.py b/mod2taud.py index 4ae0ba1..c2fe453 100644 --- a/mod2taud.py +++ b/mod2taud.py @@ -59,6 +59,9 @@ PT_MEM_TOP = frozenset({0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0xA}) # E sub-effects with memory (key is sub-nibble of the E command): PT_MEM_E_SUB = frozenset({0x1, 0x2, 0xA, 0xB}) +GLOBAL_FLAGS_AMIGA_FREQ = 0b01 +GLOBAL_FLAGS_A500_INTP = 0b1000 + # ── Taud constants (mod-specific) ──────────────────────────────────────────── @@ -769,7 +772,7 @@ def assemble_taud(mod: dict, with_project_data: bool = True) -> bytes: # equal-energy engine-wide. PT has no instrument-level fadeout, so every Taud # instrument carries fadeout=0 ("no fade") — notes retire on sample-end or # pattern note-cut instead, which matches PT semantics. - flags_byte = 0x01 + flags_byte = GLOBAL_FLAGS_AMIGA_FREQ | GLOBAL_FLAGS_A500_INTP song_table = encode_song_entry( song_offset=song_offset, num_voices=n_channels, diff --git a/mon2taud.py b/mon2taud.py index d57c8cf..eeba2dc 100644 --- a/mon2taud.py +++ b/mon2taud.py @@ -59,6 +59,7 @@ MON_NOTE_C4 = 40 # `Frequency:=Frequency±parm1` arithmetic (see MTSRC/MT_PLAY.PAS:606-630). # Panning law is fixed to the equal-energy — there is no `p` bit any more. GLOBAL_FLAGS_LINEAR_FREQ = 0b10 +GLOBAL_FLAGS_NO_INTERPOLATION = 0b0100 # ── Taud container ─────────────────────────────────────────────────────────── @@ -361,7 +362,7 @@ def assemble_taud(mon: dict, with_project_data: bool = True) -> bytes: # Pan law is fixed engine-wide to the equal-energy (no flag). Monotone has no # instrument-level fadeout, so every Taud instrument carries fadeout=0 ("no fade") — # notes retire on sample-end or pattern note-cut instead. - flags_byte = GLOBAL_FLAGS_LINEAR_FREQ + flags_byte = GLOBAL_FLAGS_LINEAR_FREQ | GLOBAL_FLAGS_NO_INTERPOLATION song_table = encode_song_entry( song_offset = song_offset, diff --git a/terranmon.txt b/terranmon.txt index d9d23ed..6459e28 100644 --- a/terranmon.txt +++ b/terranmon.txt @@ -2394,7 +2394,8 @@ TODO: when no V column is present. Engine + all four `*2taud` converters updated; legacy `.taud` files (byte 196 == 0) fall back to the previous "row volume default = 63" behaviour. - [ ] Physical Presence order 1F chn 2: note cuts unexpectedly fast? + [ ] Physical Presence order 0x1F chn 2: note cuts unexpectedly fast? + GSLINGER order 0x03 chn 1: L 0100 fades unexpectedly fast? TODO - list of demo songs that MUST ship with Microtone: * 4THSYM (rename to Fourth Symmetriad) — excellent piece for demonstrating NNAs and filter envelopes diff --git a/tsvm_core/src/net/torvald/tsvm/AudioJSR223Delegate.kt b/tsvm_core/src/net/torvald/tsvm/AudioJSR223Delegate.kt index 9583a67..1e4d613 100644 --- a/tsvm_core/src/net/torvald/tsvm/AudioJSR223Delegate.kt +++ b/tsvm_core/src/net/torvald/tsvm/AudioJSR223Delegate.kt @@ -134,12 +134,7 @@ class AudioJSR223Delegate(private val vm: VM) { fun setTrackerMixerFlags(playhead: Int, flags: Int) { getFirstSnd()?.playheads?.get(playhead)?.let { ph -> ph.initialGlobalFlags = flags - ph.trackerState?.let { ts -> - ts.toneMode = flags and 3 - // Bits 2-7 reserved. Bit 2 was the old 'm' fadeout-zero policy; removed. - // Pan law is fixed to the equal-energy engine-wide — no flag bit any more. - // See AudioAdapter.kt and TAUD_NOTE_EFFECTS.md §1. - } + ph.updateTrackerGlobalBehaviour(flags) } } diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/AudioAdapter.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/AudioAdapter.kt index 0fe870f..179829f 100644 --- a/tsvm_core/src/net/torvald/tsvm/peripheral/AudioAdapter.kt +++ b/tsvm_core/src/net/torvald/tsvm/peripheral/AudioAdapter.kt @@ -2225,8 +2225,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) { // bits 2-3 (rr): 0=Fast Sinc, 1=none, 2=Amiga 500, 3=Amiga 1200 // Panning law is fixed to the equal-energy; no runtime selection. val flags = rawArg ushr 8 - ts.toneMode = flags and 3 - ts.interpolationMode = (flags ushr 2) and 3 + playhead.updateTrackerGlobalBehaviour(flags) } EffectOp.OP_8 -> { // 8 $xyzz — Bitcrusher. See TAUD_NOTE_EFFECTS.md §8. @@ -3492,6 +3491,13 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) { var pcmQueueSizeIndex: Int = 0, val audioDevice: OpenALBufferedAudioDevice, ) { + fun updateTrackerGlobalBehaviour(flags: Int) { + trackerState?.let { ts -> + ts.toneMode = flags and 3 + ts.interpolationMode = (flags ushr 2) and 3 + } + } + var trackerState: TrackerState? = TrackerState() // default mode is tracker (isPcmMode=false) // Initial global behaviour flags (song-table byte, written via MMIO register 7 in tracker mode). @@ -3554,10 +3560,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) { } } 7 -> if (isPcmMode) { pcmUpload = true } else { initialGlobalFlags = byte - trackerState?.let { ts -> - ts.toneMode = byte and 3 - ts.interpolationMode = (byte ushr 2) and 3 - } + updateTrackerGlobalBehaviour(initialGlobalFlags) } 8 -> { bpm = byte + 25 } 9 -> { tickRate = byte } diff --git a/tsvm_executable/src/net/torvald/tsvm/AudioMenu.kt b/tsvm_executable/src/net/torvald/tsvm/AudioMenu.kt index 79344b4..a22c2cf 100644 --- a/tsvm_executable/src/net/torvald/tsvm/AudioMenu.kt +++ b/tsvm_executable/src/net/torvald/tsvm/AudioMenu.kt @@ -32,6 +32,20 @@ class AudioMenu(parent: VMEmuExecutable, x: Int, y: Int, w: Int, h: Int) : EmuMe private val scopeScrollHorz = IntArray(4) private val SCOPE_MODE_COUNT = 5 + // Which playhead the big scope is showing. Status-panel clicks change this. + private var selectedPlayhead = 0 + + // Layout — one big scope on top, four status panels along the bottom. + private val bigScopeX = 7 + private val bigScopeY = 5 + private val bigScopeW = 622 + private val bigScopeH = 336 + private val statusW = 102 + private val statusH = 8 * FONT.H + 4 + private val statusY = bigScopeY + bigScopeH + 4 + // Spread the four status panels evenly across the big-scope width. + private fun statusX(i: Int): Int = bigScopeX + i * (bigScopeW - statusW) / 3 + override fun show() { } @@ -41,96 +55,71 @@ class AudioMenu(parent: VMEmuExecutable, x: Int, y: Int, w: Int, h: Int) : EmuMe private var guiClickLatched = arrayOf(false, false, false, false, false, false, false, false) private var guiKeypressLatched = BitSet(256) + private fun panelAtMouse(mx: Int, my: Int): Int { + if (my !in statusY until (statusY + statusH)) return -1 + for (i in 0..3) { + val sx = statusX(i) + if (mx in sx until (sx + statusW)) return i + } + return -1 + } + + private fun mouseInBigScope(mx: Int, my: Int): Boolean = + mx in bigScopeX until (bigScopeX + bigScopeW) && + my in bigScopeY until (bigScopeY + bigScopeH) + override fun update() { - // mouse clicks + val mx = Gdx.input.x - x + val my = Gdx.input.y - y + + // ── LEFT click ───────────────────────────────────────────────────────────── + // On a status panel: select that playhead as the big-scope target. + // On the big scope: cycle scope mode forward for the selected playhead. if (Gdx.input.isButtonPressed(Buttons.LEFT)) { if (!guiClickLatched[Buttons.LEFT]) { - val mx = Gdx.input.x - x - val my = Gdx.input.y - y - - if (mx in 117..629) { - for (i in 0..3) { - val syTop = h - 7 - 115 * i - 8 * FONT.H - val syBot = h - 3 - 115 * i - if (my in syTop..syBot) { - scopeMode[3 - i] = (scopeMode[3 - i] + 1) % SCOPE_MODE_COUNT - break - } - } + val panel = panelAtMouse(mx, my) + if (panel >= 0) { + selectedPlayhead = panel + } else if (mouseInBigScope(mx, my)) { + scopeMode[selectedPlayhead] = + (scopeMode[selectedPlayhead] + 1) % SCOPE_MODE_COUNT } - guiClickLatched[Buttons.LEFT] = true } - } - else { + } else { guiClickLatched[Buttons.LEFT] = false } + + // ── RIGHT click on the big scope: cycle scope mode backward. ──────────────── if (Gdx.input.isButtonPressed(Buttons.RIGHT)) { if (!guiClickLatched[Buttons.RIGHT]) { - val mx = Gdx.input.x - x - val my = Gdx.input.y - y - - if (mx in 117..629) { - for (i in 0..3) { - val syTop = h - 7 - 115 * i - 8 * FONT.H - val syBot = h - 3 - 115 * i - if (my in syTop..syBot) { - scopeMode[3 - i] = (scopeMode[3 - i] + SCOPE_MODE_COUNT - 1) % SCOPE_MODE_COUNT - break - } - } + if (mouseInBigScope(mx, my)) { + scopeMode[selectedPlayhead] = + (scopeMode[selectedPlayhead] + SCOPE_MODE_COUNT - 1) % SCOPE_MODE_COUNT } - guiClickLatched[Buttons.RIGHT] = true } - } - else { + } else { guiClickLatched[Buttons.RIGHT] = false } - // keyboard left/right + // ── Keyboard left/right: scroll the selected playhead's pattern view. ─────── if (Gdx.input.isKeyPressed(Input.Keys.LEFT)) { if (!guiKeypressLatched[Input.Keys.LEFT]) { - val mx = Gdx.input.x - x - val my = Gdx.input.y - y - - if (mx in 117..629) { - for (i in 0..3) { - val syTop = h - 7 - 115 * i - 8 * FONT.H - val syBot = h - 3 - 115 * i - if (my in syTop..syBot) { - scopeScrollHorz[3 - i] = (scopeScrollHorz[3 - i] - 1).coerceIn(0, 14) - break - } - } - } - + scopeScrollHorz[selectedPlayhead] = + (scopeScrollHorz[selectedPlayhead] - 1).coerceIn(0, 14) guiKeypressLatched[Input.Keys.LEFT] = true } - } - else { + } else { guiKeypressLatched[Input.Keys.LEFT] = false } if (Gdx.input.isKeyPressed(Input.Keys.RIGHT)) { if (!guiKeypressLatched[Input.Keys.RIGHT]) { - val mx = Gdx.input.x - x - val my = Gdx.input.y - y - - if (mx in 117..629) { - for (i in 0..3) { - val syTop = h - 7 - 115 * i - 8 * FONT.H - val syBot = h - 3 - 115 * i - if (my in syTop..syBot) { - scopeScrollHorz[3 - i] = (scopeScrollHorz[3 - i] + 1).coerceIn(0, 14) - break - } - } - } - + scopeScrollHorz[selectedPlayhead] = + (scopeScrollHorz[selectedPlayhead] + 1).coerceIn(0, 14) guiKeypressLatched[Input.Keys.RIGHT] = true } - } - else { + } else { guiKeypressLatched[Input.Keys.RIGHT] = false } } @@ -170,27 +159,32 @@ class AudioMenu(parent: VMEmuExecutable, x: Int, y: Int, w: Int, h: Int) : EmuMe val adev = parent.currentlyPersistentVM?.vm?.peripheralTable?.getOrNull(cardIndex ?: -1)?.peripheral as? AudioAdapter if (adev != null) { + val playheads = adev.extortField>("playheads")!! - // draw status LCD + // ── Big scope background (row 1) and status-panel backgrounds (row 2) ───── batch.inUse { - // draw backgrounds - batch.color = COL_WELL - for (i in 0..3) { batch.fillRect(7, 5 + 115*i, 102, 8*FONT.H + 4) } - } - for (i in 0..3) { - val ahead = adev.extortField>("playheads")!![i] - drawStatusLCD(adev, ahead, batch, i, 9f + 7, 7f + 7 + 115 * i) - } - - // draw Soundscope like this so that the overflown queue sparkline would not be overlaid on top of the envelopes - batch.inUse { - // draw backgrounds batch.color = COL_SOUNDSCOPE_BACK - for (i in 0..3) { batch.fillRect(117, 5 + 115*i, 512, 8*FONT.H + 4) } + batch.fillRect(bigScopeX, bigScopeY, bigScopeW, bigScopeH) + + // Highlight border behind the selected status panel. + batch.color = COL_HIGHLIGHT2 + val selX = statusX(selectedPlayhead) + batch.fillRect(selX - 2, statusY - 2, statusW + 4, statusH + 4) + + batch.color = COL_WELL + for (i in 0..3) batch.fillRect(statusX(i), statusY, statusW, statusH) } + + // ── Big scope contents — only the selected playhead ──────────────────────── + drawSoundscope(adev, playheads[selectedPlayhead], batch, selectedPlayhead, + bigScopeX.toFloat(), bigScopeY.toFloat(), bigScopeW, bigScopeH) + + // ── All four status LCDs along the bottom ────────────────────────────────── + // Use the same (9, 9) inset from the panel as the original layout, so the + // existing label-positioning math inside drawStatusLCD still fits cleanly. for (i in 0..3) { - val ahead = adev.extortField>("playheads")!![i] - drawSoundscope(adev, ahead, batch, i, 117f, 5f + 115 * i) + drawStatusLCD(adev, playheads[i], batch, i, + statusX(i).toFloat() + 9f, statusY.toFloat() + 9f) } } else { @@ -206,11 +200,16 @@ class AudioMenu(parent: VMEmuExecutable, x: Int, y: Int, w: Int, h: Int) : EmuMe // NOTE: Samples count for PCM mode is drawn by drawSoundscope() function, not this one! batch.inUse { + // "P{n+1}" tag — bright on the selected playhead so the panel-as-button + // affordance is obvious. + batch.color = if (index == selectedPlayhead) COL_HIGHLIGHT2 else Color.WHITE + FONT.draw(batch, "P${index + 1}", x, y) + batch.color = Color.WHITE - // PLAY icon + // PLAY icon (shifted right to make room for the playhead tag) if (ahead.isPlaying) - FONT.draw(batch, STR_PLAY, x, y) - FONT.draw(batch, if (ahead.isPcmMode) "PCM" else "TRACKER", x + 21, y) + FONT.draw(batch, STR_PLAY, x + 21, y) + FONT.draw(batch, if (ahead.isPcmMode) "PCM" else "TRACKER", x + 42, y) // PCM Mode labels if (ahead.isPcmMode) { @@ -241,7 +240,7 @@ class AudioMenu(parent: VMEmuExecutable, x: Int, y: Int, w: Int, h: Int) : EmuMe FONT.draw(batch, "Tickrate", x, y + 6*FONT.H) batch.color = COL_ACTIVE3 - FONT.drawRalign(batch, "${ahead.trackerState?.cuePos?.toString(16)?.uppercase()?.padStart(2,'0')}:${ahead.trackerState?.rowIndex?.toString()?.uppercase()?.padStart(2,'0')}", x + 84, y + 2*FONT.H) + FONT.drawRalign(batch, "${ahead.trackerState?.cuePos?.toString(16)?.uppercase()?.padStart(3,'0')}:${ahead.trackerState?.rowIndex?.toString()?.uppercase()?.padStart(2,'0')}", 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) @@ -326,54 +325,62 @@ class AudioMenu(parent: VMEmuExecutable, x: Int, y: Int, w: Int, h: Int) : EmuMe private val VOL_SYM = arrayOf('@','^','&',' ') private val PAN_SYM = arrayOf('@','<','>',' ') - private fun drawSoundscope(audio: AudioAdapter, ahead: AudioAdapter.Playhead, batch: SpriteBatch, index: Int, x: Float, y: Float) { + private fun drawSoundscope(audio: AudioAdapter, ahead: AudioAdapter.Playhead, batch: SpriteBatch, index: Int, x: Float, y: Float, w: Int, h: Int) { val gdxadev = ahead.audioDevice val bytes = gdxadev.extortField("bytes") val bytesLen = gdxadev.extortField("bytesLength")!! - val envelopeHalfHeight = 27 + val envelopeHalfHeight = h / 4 + val lCenterY = h / 4 + val rCenterY = 3 * h / 4 batch.inUse { if (ahead.isPcmMode && bytes != null) { val smpCnt = bytesLen / 4 - 1 - for (s in 0..511) { - val i = (smpCnt * (s / 511.0)).roundToInt().and(0xfffffe) + try { + for (s in 0 until w) { + val i = (smpCnt * (s / (w - 1).toDouble())).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 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 - val smpRH = smpR * envelopeHalfHeight + val smpLH = smpL * envelopeHalfHeight + val smpRH = smpR * envelopeHalfHeight - val smpLHi = bipolarFloor(smpLH) - val smpRHi = bipolarFloor(smpRH) - val smpLHi2 = bipolarCeil(smpLH) - val smpRHi2 = bipolarCeil(smpRH) + val smpLHi = bipolarFloor(smpLH) + val smpRHi = bipolarFloor(smpRH) + val smpLHi2 = bipolarCeil(smpLH) + val smpRHi2 = bipolarCeil(smpRH) - val smpLHe = abs(smpLH - smpLHi).toFloat() - val smpRHe = abs(smpRH - smpRHi).toFloat() + val smpLHe = abs(smpLH - smpLHi).toFloat() + val smpRHe = abs(smpRH - smpRHi).toFloat() - // antialias in y-axis - if (smpLHi != smpLHi2) { - batch.color = COL_SOUNDSCOPE_FORE.cpy().mul(smpLHe) - batch.fillRect(x + s, y + 27, 1, smpLHi2) - } - if (smpRHi != smpRHi2) { - batch.color = COL_SOUNDSCOPE_FORE.cpy().mul(smpRHe) - batch.fillRect(x + s, y + 81, 1, smpRHi2) + // antialias in y-axis + if (smpLHi != smpLHi2) { + batch.color = COL_SOUNDSCOPE_FORE.cpy().mul(smpLHe) + batch.fillRect(x + s, y + lCenterY, 1, smpLHi2) + } + if (smpRHi != smpRHi2) { + batch.color = COL_SOUNDSCOPE_FORE.cpy().mul(smpRHe) + batch.fillRect(x + s, y + rCenterY, 1, smpRHi2) + } + + // base texture + batch.color = COL_SOUNDSCOPE_FORE + batch.fillRect(x + s, y + lCenterY, 1, smpLHi) + batch.fillRect(x + s, y + rCenterY, 1, smpRHi) } - // base texture - batch.color = COL_SOUNDSCOPE_FORE - batch.fillRect(x + s, y + 27, 1, smpLHi) - batch.fillRect(x + s, y + 81, 1, smpRHi) + // PCM Samples count — drawn inside the scope (top-left) since the status + // panels no longer sit beside it in the new single-scope layout. + batch.color = Color.WHITE + FONT.draw(batch, "Samples", x + 4, y + 4) + batch.color = COL_ACTIVE3 + FONT.draw(batch, "${smpCnt + 1}", x + 4 + 8 * FONT.W, y + 4) } - - batch.color = Color.WHITE - FONT.draw(batch, "Samples", x - 101, y + 5*FONT.H + 9) - batch.color = COL_ACTIVE3 - FONT.drawRalign(batch, "${smpCnt+1}", x - 17, y + 5*FONT.H + 9) - + catch (_: ArrayIndexOutOfBoundsException) {} } else { // Tracker pattern visualiser. @@ -385,7 +392,9 @@ class AudioMenu(parent: VMEmuExecutable, x: Int, y: Int, w: Int, h: Int) : EmuMe } else { val cuePos = ts.cuePos val rowIdx = ts.rowIndex - val ROWS = 17 + // Rows scale with available height — the original 17-row layout was sized + // for the old 108-pixel scope; the big scope can show many more rows. + val ROWS = ((h - 8) / TINY.H).coerceAtLeast(1) val PTN_MAX_ROWS = 63 when (scopeMode[index]) { @@ -403,7 +412,7 @@ class AudioMenu(parent: VMEmuExecutable, x: Int, y: Int, w: Int, h: Int) : EmuMe if (here) { batch.color = COL_TRACKER_ROW - batch.fillRect(x, ry, 512, TINY.H) + batch.fillRect(x, ry, w, TINY.H) } var cx = x @@ -450,8 +459,8 @@ class AudioMenu(parent: VMEmuExecutable, x: Int, y: Int, w: Int, h: Int) : EmuMe batch.color = COL_SOUNDSCOPE_FORE FONT.draw(batch, "No active voices", x, y + 4) } else { - val scopeH = 8 * FONT.H + 4 - val scopeW = 512 + val scopeH = h + val scopeW = w val n = activeVoiceIndices.size val grid = pickWaveformGrid(n, scopeW, scopeH) val cols = grid[0] @@ -515,8 +524,8 @@ class AudioMenu(parent: VMEmuExecutable, x: Int, y: Int, w: Int, h: Int) : EmuMe // voice index label (top-left of cell), only when there is room if (drawLabel) { batch.color = COL_VOICE_PALETTE[vi % COL_VOICE_PALETTE.size] - TINY.draw(batch, vi.toString(16).padStart(2, '0').uppercase(), - cellX + 1, cellY) + TINY.draw(batch, (vi+1).toString().padStart(2, '0').uppercase(), + cellX + 1, cellY + 1) } } } @@ -564,7 +573,7 @@ class AudioMenu(parent: VMEmuExecutable, x: Int, y: Int, w: Int, h: Int) : EmuMe if (here) { batch.color = COL_TRACKER_ROW - batch.fillRect(patX, ry, 512 - cueW - sepW, TINY.H) + batch.fillRect(patX, ry, w - cueW - sepW, TINY.H) } var cx = patX