mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-06 05:28:31 +09:00
TsvmEmulator: better snd debug view
This commit is contained in:
@@ -2249,7 +2249,8 @@ function drawProjectContents(wo) {
|
|||||||
|
|
||||||
let mixerflag = initialTrackerMixerflags
|
let mixerflag = initialTrackerMixerflags
|
||||||
let toneModeStr = ['Linear pitch','Amiga pitch','Linear freq',''][mixerflag & 3]
|
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 = {
|
let projMeta = {
|
||||||
|
|||||||
@@ -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):
|
# E sub-effects with memory (key is sub-nibble of the E command):
|
||||||
PT_MEM_E_SUB = frozenset({0x1, 0x2, 0xA, 0xB})
|
PT_MEM_E_SUB = frozenset({0x1, 0x2, 0xA, 0xB})
|
||||||
|
|
||||||
|
GLOBAL_FLAGS_AMIGA_FREQ = 0b01
|
||||||
|
GLOBAL_FLAGS_A500_INTP = 0b1000
|
||||||
|
|
||||||
|
|
||||||
# ── Taud constants (mod-specific) ────────────────────────────────────────────
|
# ── 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
|
# 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
|
# instrument carries fadeout=0 ("no fade") — notes retire on sample-end or
|
||||||
# pattern note-cut instead, which matches PT semantics.
|
# 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_table = encode_song_entry(
|
||||||
song_offset=song_offset,
|
song_offset=song_offset,
|
||||||
num_voices=n_channels,
|
num_voices=n_channels,
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ MON_NOTE_C4 = 40
|
|||||||
# `Frequency:=Frequency±parm1` arithmetic (see MTSRC/MT_PLAY.PAS:606-630).
|
# `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.
|
# Panning law is fixed to the equal-energy — there is no `p` bit any more.
|
||||||
GLOBAL_FLAGS_LINEAR_FREQ = 0b10
|
GLOBAL_FLAGS_LINEAR_FREQ = 0b10
|
||||||
|
GLOBAL_FLAGS_NO_INTERPOLATION = 0b0100
|
||||||
|
|
||||||
|
|
||||||
# ── Taud container ───────────────────────────────────────────────────────────
|
# ── 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
|
# 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") —
|
# instrument-level fadeout, so every Taud instrument carries fadeout=0 ("no fade") —
|
||||||
# notes retire on sample-end or pattern note-cut instead.
|
# 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_table = encode_song_entry(
|
||||||
song_offset = song_offset,
|
song_offset = song_offset,
|
||||||
|
|||||||
@@ -2394,7 +2394,8 @@ TODO:
|
|||||||
when no V column is present. Engine + all four `*2taud` converters
|
when no V column is present. Engine + all four `*2taud` converters
|
||||||
updated; legacy `.taud` files (byte 196 == 0) fall back to the
|
updated; legacy `.taud` files (byte 196 == 0) fall back to the
|
||||||
previous "row volume default = 63" behaviour.
|
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:
|
TODO - list of demo songs that MUST ship with Microtone:
|
||||||
* 4THSYM (rename to Fourth Symmetriad) — excellent piece for demonstrating NNAs and filter envelopes
|
* 4THSYM (rename to Fourth Symmetriad) — excellent piece for demonstrating NNAs and filter envelopes
|
||||||
|
|||||||
@@ -134,12 +134,7 @@ class AudioJSR223Delegate(private val vm: VM) {
|
|||||||
fun setTrackerMixerFlags(playhead: Int, flags: Int) {
|
fun setTrackerMixerFlags(playhead: Int, flags: Int) {
|
||||||
getFirstSnd()?.playheads?.get(playhead)?.let { ph ->
|
getFirstSnd()?.playheads?.get(playhead)?.let { ph ->
|
||||||
ph.initialGlobalFlags = flags
|
ph.initialGlobalFlags = flags
|
||||||
ph.trackerState?.let { ts ->
|
ph.updateTrackerGlobalBehaviour(flags)
|
||||||
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.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
// 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.
|
// Panning law is fixed to the equal-energy; no runtime selection.
|
||||||
val flags = rawArg ushr 8
|
val flags = rawArg ushr 8
|
||||||
ts.toneMode = flags and 3
|
playhead.updateTrackerGlobalBehaviour(flags)
|
||||||
ts.interpolationMode = (flags ushr 2) and 3
|
|
||||||
}
|
}
|
||||||
EffectOp.OP_8 -> {
|
EffectOp.OP_8 -> {
|
||||||
// 8 $xyzz — Bitcrusher. See TAUD_NOTE_EFFECTS.md §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,
|
var pcmQueueSizeIndex: Int = 0,
|
||||||
val audioDevice: OpenALBufferedAudioDevice,
|
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)
|
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).
|
// 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 {
|
7 -> if (isPcmMode) { pcmUpload = true } else {
|
||||||
initialGlobalFlags = byte
|
initialGlobalFlags = byte
|
||||||
trackerState?.let { ts ->
|
updateTrackerGlobalBehaviour(initialGlobalFlags)
|
||||||
ts.toneMode = byte and 3
|
|
||||||
ts.interpolationMode = (byte ushr 2) and 3
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
8 -> { bpm = byte + 25 }
|
8 -> { bpm = byte + 25 }
|
||||||
9 -> { tickRate = byte }
|
9 -> { tickRate = byte }
|
||||||
|
|||||||
@@ -32,6 +32,20 @@ class AudioMenu(parent: VMEmuExecutable, x: Int, y: Int, w: Int, h: Int) : EmuMe
|
|||||||
private val scopeScrollHorz = IntArray(4)
|
private val scopeScrollHorz = IntArray(4)
|
||||||
private val SCOPE_MODE_COUNT = 5
|
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() {
|
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 guiClickLatched = arrayOf(false, false, false, false, false, false, false, false)
|
||||||
private var guiKeypressLatched = BitSet(256)
|
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() {
|
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 (Gdx.input.isButtonPressed(Buttons.LEFT)) {
|
||||||
if (!guiClickLatched[Buttons.LEFT]) {
|
if (!guiClickLatched[Buttons.LEFT]) {
|
||||||
val mx = Gdx.input.x - x
|
val panel = panelAtMouse(mx, my)
|
||||||
val my = Gdx.input.y - y
|
if (panel >= 0) {
|
||||||
|
selectedPlayhead = panel
|
||||||
if (mx in 117..629) {
|
} else if (mouseInBigScope(mx, my)) {
|
||||||
for (i in 0..3) {
|
scopeMode[selectedPlayhead] =
|
||||||
val syTop = h - 7 - 115 * i - 8 * FONT.H
|
(scopeMode[selectedPlayhead] + 1) % SCOPE_MODE_COUNT
|
||||||
val syBot = h - 3 - 115 * i
|
|
||||||
if (my in syTop..syBot) {
|
|
||||||
scopeMode[3 - i] = (scopeMode[3 - i] + 1) % SCOPE_MODE_COUNT
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
guiClickLatched[Buttons.LEFT] = true
|
guiClickLatched[Buttons.LEFT] = true
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
guiClickLatched[Buttons.LEFT] = false
|
guiClickLatched[Buttons.LEFT] = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── RIGHT click on the big scope: cycle scope mode backward. ────────────────
|
||||||
if (Gdx.input.isButtonPressed(Buttons.RIGHT)) {
|
if (Gdx.input.isButtonPressed(Buttons.RIGHT)) {
|
||||||
if (!guiClickLatched[Buttons.RIGHT]) {
|
if (!guiClickLatched[Buttons.RIGHT]) {
|
||||||
val mx = Gdx.input.x - x
|
if (mouseInBigScope(mx, my)) {
|
||||||
val my = Gdx.input.y - y
|
scopeMode[selectedPlayhead] =
|
||||||
|
(scopeMode[selectedPlayhead] + SCOPE_MODE_COUNT - 1) % SCOPE_MODE_COUNT
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
guiClickLatched[Buttons.RIGHT] = true
|
guiClickLatched[Buttons.RIGHT] = true
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
guiClickLatched[Buttons.RIGHT] = false
|
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 (Gdx.input.isKeyPressed(Input.Keys.LEFT)) {
|
||||||
if (!guiKeypressLatched[Input.Keys.LEFT]) {
|
if (!guiKeypressLatched[Input.Keys.LEFT]) {
|
||||||
val mx = Gdx.input.x - x
|
scopeScrollHorz[selectedPlayhead] =
|
||||||
val my = Gdx.input.y - y
|
(scopeScrollHorz[selectedPlayhead] - 1).coerceIn(0, 14)
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
guiKeypressLatched[Input.Keys.LEFT] = true
|
guiKeypressLatched[Input.Keys.LEFT] = true
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
guiKeypressLatched[Input.Keys.LEFT] = false
|
guiKeypressLatched[Input.Keys.LEFT] = false
|
||||||
}
|
}
|
||||||
if (Gdx.input.isKeyPressed(Input.Keys.RIGHT)) {
|
if (Gdx.input.isKeyPressed(Input.Keys.RIGHT)) {
|
||||||
if (!guiKeypressLatched[Input.Keys.RIGHT]) {
|
if (!guiKeypressLatched[Input.Keys.RIGHT]) {
|
||||||
val mx = Gdx.input.x - x
|
scopeScrollHorz[selectedPlayhead] =
|
||||||
val my = Gdx.input.y - y
|
(scopeScrollHorz[selectedPlayhead] + 1).coerceIn(0, 14)
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
guiKeypressLatched[Input.Keys.RIGHT] = true
|
guiKeypressLatched[Input.Keys.RIGHT] = true
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
guiKeypressLatched[Input.Keys.RIGHT] = false
|
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
|
val adev = parent.currentlyPersistentVM?.vm?.peripheralTable?.getOrNull(cardIndex ?: -1)?.peripheral as? AudioAdapter
|
||||||
|
|
||||||
if (adev != null) {
|
if (adev != null) {
|
||||||
|
val playheads = adev.extortField<Array<AudioAdapter.Playhead>>("playheads")!!
|
||||||
|
|
||||||
// draw status LCD
|
// ── Big scope background (row 1) and status-panel backgrounds (row 2) ─────
|
||||||
batch.inUse {
|
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<Array<AudioAdapter.Playhead>>("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
|
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) {
|
for (i in 0..3) {
|
||||||
val ahead = adev.extortField<Array<AudioAdapter.Playhead>>("playheads")!![i]
|
drawStatusLCD(adev, playheads[i], batch, i,
|
||||||
drawSoundscope(adev, ahead, batch, i, 117f, 5f + 115 * i)
|
statusX(i).toFloat() + 9f, statusY.toFloat() + 9f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
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!
|
// NOTE: Samples count for PCM mode is drawn by drawSoundscope() function, not this one!
|
||||||
|
|
||||||
batch.inUse {
|
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
|
batch.color = Color.WHITE
|
||||||
// PLAY icon
|
// PLAY icon (shifted right to make room for the playhead tag)
|
||||||
if (ahead.isPlaying)
|
if (ahead.isPlaying)
|
||||||
FONT.draw(batch, STR_PLAY, x, y)
|
FONT.draw(batch, STR_PLAY, x + 21, y)
|
||||||
FONT.draw(batch, if (ahead.isPcmMode) "PCM" else "TRACKER", x + 21, y)
|
FONT.draw(batch, if (ahead.isPcmMode) "PCM" else "TRACKER", x + 42, y)
|
||||||
|
|
||||||
// PCM Mode labels
|
// PCM Mode labels
|
||||||
if (ahead.isPcmMode) {
|
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)
|
FONT.draw(batch, "Tickrate", x, y + 6*FONT.H)
|
||||||
|
|
||||||
batch.color = COL_ACTIVE3
|
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.masterVolume}", x + 84, y + 3*FONT.H)
|
||||||
FONT.drawRalign(batch, "${ahead.masterPan}", x + 84, y + 4*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.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 VOL_SYM = arrayOf('@','^','&',' ')
|
||||||
private val PAN_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 gdxadev = ahead.audioDevice
|
||||||
val bytes = gdxadev.extortField<ByteArray>("bytes")
|
val bytes = gdxadev.extortField<ByteArray>("bytes")
|
||||||
val bytesLen = gdxadev.extortField<Int>("bytesLength")!!
|
val bytesLen = gdxadev.extortField<Int>("bytesLength")!!
|
||||||
val envelopeHalfHeight = 27
|
val envelopeHalfHeight = h / 4
|
||||||
|
val lCenterY = h / 4
|
||||||
|
val rCenterY = 3 * h / 4
|
||||||
|
|
||||||
batch.inUse {
|
batch.inUse {
|
||||||
if (ahead.isPcmMode && bytes != null) {
|
if (ahead.isPcmMode && bytes != null) {
|
||||||
val smpCnt = bytesLen / 4 - 1
|
val smpCnt = bytesLen / 4 - 1
|
||||||
|
|
||||||
for (s in 0..511) {
|
try {
|
||||||
val i = (smpCnt * (s / 511.0)).roundToInt().and(0xfffffe)
|
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 smpL =
|
||||||
val smpR = (bytes[i*4+2].toUint() or bytes[i*4+3].toUint().shl(8)).u16Tos16().toDouble().div(32767)
|
(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 smpLH = smpL * envelopeHalfHeight
|
||||||
val smpRH = smpR * envelopeHalfHeight
|
val smpRH = smpR * envelopeHalfHeight
|
||||||
|
|
||||||
val smpLHi = bipolarFloor(smpLH)
|
val smpLHi = bipolarFloor(smpLH)
|
||||||
val smpRHi = bipolarFloor(smpRH)
|
val smpRHi = bipolarFloor(smpRH)
|
||||||
val smpLHi2 = bipolarCeil(smpLH)
|
val smpLHi2 = bipolarCeil(smpLH)
|
||||||
val smpRHi2 = bipolarCeil(smpRH)
|
val smpRHi2 = bipolarCeil(smpRH)
|
||||||
|
|
||||||
val smpLHe = abs(smpLH - smpLHi).toFloat()
|
val smpLHe = abs(smpLH - smpLHi).toFloat()
|
||||||
val smpRHe = abs(smpRH - smpRHi).toFloat()
|
val smpRHe = abs(smpRH - smpRHi).toFloat()
|
||||||
|
|
||||||
// antialias in y-axis
|
// antialias in y-axis
|
||||||
if (smpLHi != smpLHi2) {
|
if (smpLHi != smpLHi2) {
|
||||||
batch.color = COL_SOUNDSCOPE_FORE.cpy().mul(smpLHe)
|
batch.color = COL_SOUNDSCOPE_FORE.cpy().mul(smpLHe)
|
||||||
batch.fillRect(x + s, y + 27, 1, smpLHi2)
|
batch.fillRect(x + s, y + lCenterY, 1, smpLHi2)
|
||||||
}
|
}
|
||||||
if (smpRHi != smpRHi2) {
|
if (smpRHi != smpRHi2) {
|
||||||
batch.color = COL_SOUNDSCOPE_FORE.cpy().mul(smpRHe)
|
batch.color = COL_SOUNDSCOPE_FORE.cpy().mul(smpRHe)
|
||||||
batch.fillRect(x + s, y + 81, 1, smpRHi2)
|
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
|
// PCM Samples count — drawn inside the scope (top-left) since the status
|
||||||
batch.color = COL_SOUNDSCOPE_FORE
|
// panels no longer sit beside it in the new single-scope layout.
|
||||||
batch.fillRect(x + s, y + 27, 1, smpLHi)
|
batch.color = Color.WHITE
|
||||||
batch.fillRect(x + s, y + 81, 1, smpRHi)
|
FONT.draw(batch, "Samples", x + 4, y + 4)
|
||||||
|
batch.color = COL_ACTIVE3
|
||||||
|
FONT.draw(batch, "${smpCnt + 1}", x + 4 + 8 * FONT.W, y + 4)
|
||||||
}
|
}
|
||||||
|
catch (_: ArrayIndexOutOfBoundsException) {}
|
||||||
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)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Tracker pattern visualiser.
|
// Tracker pattern visualiser.
|
||||||
@@ -385,7 +392,9 @@ class AudioMenu(parent: VMEmuExecutable, x: Int, y: Int, w: Int, h: Int) : EmuMe
|
|||||||
} else {
|
} else {
|
||||||
val cuePos = ts.cuePos
|
val cuePos = ts.cuePos
|
||||||
val rowIdx = ts.rowIndex
|
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
|
val PTN_MAX_ROWS = 63
|
||||||
|
|
||||||
when (scopeMode[index]) {
|
when (scopeMode[index]) {
|
||||||
@@ -403,7 +412,7 @@ class AudioMenu(parent: VMEmuExecutable, x: Int, y: Int, w: Int, h: Int) : EmuMe
|
|||||||
|
|
||||||
if (here) {
|
if (here) {
|
||||||
batch.color = COL_TRACKER_ROW
|
batch.color = COL_TRACKER_ROW
|
||||||
batch.fillRect(x, ry, 512, TINY.H)
|
batch.fillRect(x, ry, w, TINY.H)
|
||||||
}
|
}
|
||||||
|
|
||||||
var cx = x
|
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
|
batch.color = COL_SOUNDSCOPE_FORE
|
||||||
FONT.draw(batch, "No active voices", x, y + 4)
|
FONT.draw(batch, "No active voices", x, y + 4)
|
||||||
} else {
|
} else {
|
||||||
val scopeH = 8 * FONT.H + 4
|
val scopeH = h
|
||||||
val scopeW = 512
|
val scopeW = w
|
||||||
val n = activeVoiceIndices.size
|
val n = activeVoiceIndices.size
|
||||||
val grid = pickWaveformGrid(n, scopeW, scopeH)
|
val grid = pickWaveformGrid(n, scopeW, scopeH)
|
||||||
val cols = grid[0]
|
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
|
// voice index label (top-left of cell), only when there is room
|
||||||
if (drawLabel) {
|
if (drawLabel) {
|
||||||
batch.color = COL_VOICE_PALETTE[vi % COL_VOICE_PALETTE.size]
|
batch.color = COL_VOICE_PALETTE[vi % COL_VOICE_PALETTE.size]
|
||||||
TINY.draw(batch, vi.toString(16).padStart(2, '0').uppercase(),
|
TINY.draw(batch, (vi+1).toString().padStart(2, '0').uppercase(),
|
||||||
cellX + 1, cellY)
|
cellX + 1, cellY + 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -564,7 +573,7 @@ class AudioMenu(parent: VMEmuExecutable, x: Int, y: Int, w: Int, h: Int) : EmuMe
|
|||||||
|
|
||||||
if (here) {
|
if (here) {
|
||||||
batch.color = COL_TRACKER_ROW
|
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
|
var cx = patX
|
||||||
|
|||||||
Reference in New Issue
Block a user