taut: faster cue tab

This commit is contained in:
minjaesong
2026-05-08 00:44:50 +09:00
parent 6ce8d2cc1e
commit a767eebc2e
2 changed files with 144 additions and 52 deletions

View File

@@ -556,8 +556,9 @@ let COLSIZE_TIMELINE_FULL = TIMELINE_COLSIZES[0]
let VOCSIZE_TIMELINE_FULL = Math.floor((SCRW - 3) / COLSIZE_TIMELINE_FULL) let VOCSIZE_TIMELINE_FULL = Math.floor((SCRW - 3) / COLSIZE_TIMELINE_FULL)
const ORDERS_CMD_X = 5 const ORDERS_CMD_X = 5
const ORDERS_VOICE_X = 9 const ORDERS_VOICE_X = 12 // 1-indexed col where voice columns begin
const VOCSIZE_ORDERS = Math.floor((SCRW - 10) / 4) const ORDERS_VOICE_COL_W = 4
const VOCSIZE_ORDERS = Math.floor((SCRW - (ORDERS_VOICE_X - 1)) / ORDERS_VOICE_COL_W)
const VIEW_TIMELINE = 0 const VIEW_TIMELINE = 0
const VIEW_CUES = 1 const VIEW_CUES = 1
@@ -1407,13 +1408,11 @@ function drawOrdersHeader() {
} }
} }
function drawOrdersContents(wo) { function drawOrdersRowAt(ci) {
drawOrdersHeader() const vr = ci - ordersScroll
const maxCue = song.lastActiveCue < 0 ? 0 : song.lastActiveCue if (vr < 0 || vr >= PTNVIEW_HEIGHT) return
for (let vr = 0; vr < PTNVIEW_HEIGHT; vr++) {
const ci = ordersScroll + vr
const y = PTNVIEW_OFFSET_Y + vr const y = PTNVIEW_OFFSET_Y + vr
const maxCue = song.lastActiveCue < 0 ? 0 : song.lastActiveCue
const isSel = (ci === ordersCursor) const isSel = (ci === ordersCursor)
const isCur = playbackMode !== PLAYMODE_NONE && ci === cueIdx const isCur = playbackMode !== PLAYMODE_NONE && ci === cueIdx
const back = isSel ? (playbackMode !== PLAYMODE_NONE ? colPlayback : colHighlight) const back = isSel ? (playbackMode !== PLAYMODE_NONE ? colPlayback : colHighlight)
@@ -1423,7 +1422,9 @@ function drawOrdersContents(wo) {
if (ci > maxCue) { if (ci > maxCue) {
con.color_pair(colBackPtn, colBackPtn) con.color_pair(colBackPtn, colBackPtn)
print(' '.repeat(SCRW - 1)) print(' '.repeat(SCRW - 1))
} else { return
}
const cue = song.cues[ci] const cue = song.cues[ci]
con.color_pair(ci % 4 === 0 ? colRowNumEmph1 : colRowNum, back) con.color_pair(ci % 4 === 0 ? colRowNumEmph1 : colRowNum, back)
print(ci.hex03()) print(ci.hex03())
@@ -1445,8 +1446,67 @@ function drawOrdersContents(wo) {
con.color_pair(colBackPtn, back) con.color_pair(colBackPtn, back)
print(' ') print(' ')
} }
const endX = ORDERS_VOICE_X + VOCSIZE_ORDERS * 4 const endX = ORDERS_VOICE_X + VOCSIZE_ORDERS * ORDERS_VOICE_COL_W
if (endX <= SCRW) { con.color_pair(colBackPtn, back); print(' '.repeat(SCRW - endX)) } if (endX <= SCRW) { con.color_pair(colBackPtn, back); print(' '.repeat(SCRW - endX)) }
}
function drawOrdersContents(wo) {
drawOrdersHeader()
for (let vr = 0; vr < PTNVIEW_HEIGHT; vr++) drawOrdersRowAt(ordersScroll + vr)
}
// Redraw all rows of one voice column slot (0..VOCSIZE_ORDERS-1).
function drawOrdersVoiceColumnAt(slot) {
const v = ordersVoiceOff + slot
const x = ORDERS_VOICE_X + slot * ORDERS_VOICE_COL_W
const maxCue = song.lastActiveCue < 0 ? 0 : song.lastActiveCue
for (let vr = 0; vr < PTNVIEW_HEIGHT; vr++) {
const ci = ordersScroll + vr
const y = PTNVIEW_OFFSET_Y + vr
if (ci > maxCue) {
con.move(y, x)
con.color_pair(colBackPtn, colBackPtn)
print(' ')
continue
}
const isSel = (ci === ordersCursor)
const isCur = playbackMode !== PLAYMODE_NONE && ci === cueIdx
const back = isSel ? (playbackMode !== PLAYMODE_NONE ? colPlayback : colHighlight)
: (isCur ? colPlayback : colBackPtn)
const cue = song.cues[ci]
const ptn = v < song.numVoices ? cue.ptns[v] : CUE_EMPTY
const vBack = (isSel && ordersColCursor === v + 1) ? colPlayback : back
con.move(y, x)
con.color_pair(ptn === CUE_EMPTY ? colSep : colStatus, vBack)
print(ptn === CUE_EMPTY ? '---' : ptn.hex03())
con.color_pair(colBackPtn, back)
print(' ')
}
}
// Memory-shift the voice-column area horizontally by `dVoice` voice columns.
// Positive = scroll left (new column exposed on right); negative = scroll right.
// Touches body rows only; the header and Cmd column are untouched.
function shiftOrdersAreaHorizontal(dVoice) {
if (dVoice === 0) return
const absD = (dVoice < 0) ? -dVoice : dVoice
if (absD >= VOCSIZE_ORDERS) return // nothing to salvage
const stripWidth = (VOCSIZE_ORDERS - absD) * ORDERS_VOICE_COL_W
const srcX = ORDERS_VOICE_X + (dVoice > 0 ? absD * ORDERS_VOICE_COL_W : 0)
const dstX = ORDERS_VOICE_X + (dVoice > 0 ? 0 : absD * ORDERS_VOICE_COL_W)
const srcOff = srcX - 1
const dstOff = dstX - 1
for (let p = 0; p < 3; p++) {
const chanOff = TEXT_PLANES[p]
for (let vr = 0; vr < PTNVIEW_HEIGHT; vr++) {
const rowBase = GPU_MEM - chanOff - (PTNVIEW_OFFSET_Y + vr - 1) * SCRW
sys.memcpy(rowBase - srcOff, SCRATCH_PTR, stripWidth)
sys.memcpy(SCRATCH_PTR, rowBase - dstOff, stripWidth)
} }
} }
} }
@@ -1592,26 +1652,53 @@ function ordersInput(wo, event) {
stopPlayback(); drawAlwaysOnElems(); return stopPlayback(); drawAlwaysOnElems(); return
} }
if (keysym === '<UP>' || keysym === '<DOWN>' || keysym === '<PAGE_UP>' || keysym === '<PAGE_DOWN>') {
const oldCursor = ordersCursor
const oldScroll = ordersScroll
if (keysym === '<UP>') { if (keysym === '<UP>') {
ordersCursor = Math.max(0, ordersCursor - moveDelta) ordersCursor = Math.max(0, ordersCursor - moveDelta)
if (ordersCursor < ordersScroll) ordersScroll = ordersCursor if (ordersCursor < ordersScroll) ordersScroll = ordersCursor
drawOrdersContents(wo)
} else if (keysym === '<DOWN>') { } else if (keysym === '<DOWN>') {
ordersCursor = Math.min(maxCue, ordersCursor + moveDelta) ordersCursor = Math.min(maxCue, ordersCursor + moveDelta)
if (ordersCursor >= ordersScroll + PTNVIEW_HEIGHT) ordersScroll = Math.max(0, ordersCursor - PTNVIEW_HEIGHT + 1) if (ordersCursor >= ordersScroll + PTNVIEW_HEIGHT) ordersScroll = Math.max(0, ordersCursor - PTNVIEW_HEIGHT + 1)
drawOrdersContents(wo)
} else if (keysym === '<PAGE_UP>') { } else if (keysym === '<PAGE_UP>') {
ordersCursor = Math.max(0, ordersCursor - PTNVIEW_HEIGHT) ordersCursor = Math.max(0, ordersCursor - PTNVIEW_HEIGHT)
ordersScroll = Math.max(0, ordersScroll - PTNVIEW_HEIGHT) ordersScroll = Math.max(0, ordersScroll - PTNVIEW_HEIGHT)
drawOrdersContents(wo)
} else if (keysym === '<PAGE_DOWN>') { } else if (keysym === '<PAGE_DOWN>') {
ordersCursor = Math.min(maxCue, ordersCursor + PTNVIEW_HEIGHT) ordersCursor = Math.min(maxCue, ordersCursor + PTNVIEW_HEIGHT)
if (ordersCursor >= ordersScroll + PTNVIEW_HEIGHT) ordersScroll = Math.max(0, ordersCursor - PTNVIEW_HEIGHT + 1) if (ordersCursor >= ordersScroll + PTNVIEW_HEIGHT) ordersScroll = Math.max(0, ordersCursor - PTNVIEW_HEIGHT + 1)
}
if (ordersCursor === oldCursor && ordersScroll === oldScroll) return
const dScroll = ordersScroll - oldScroll
if (dScroll === 0) {
drawOrdersRowAt(oldCursor)
drawOrdersRowAt(ordersCursor)
} else if (Math.abs(dScroll) >= PTNVIEW_HEIGHT) {
drawOrdersContents(wo) drawOrdersContents(wo)
} else {
shiftPatternArea(-dScroll)
if (dScroll > 0) for (let i = 0; i < dScroll; i++) drawOrdersRowAt(ordersScroll + PTNVIEW_HEIGHT - 1 - i)
else for (let i = 0; i < -dScroll; i++) drawOrdersRowAt(ordersScroll + i)
if (oldCursor >= ordersScroll && oldCursor < ordersScroll + PTNVIEW_HEIGHT) drawOrdersRowAt(oldCursor)
drawOrdersRowAt(ordersCursor)
}
} else if (keysym === '<LEFT>' || keysym === '<RIGHT>') { } else if (keysym === '<LEFT>' || keysym === '<RIGHT>') {
const oldVoiceOff = ordersVoiceOff
const oldColCursor = ordersColCursor
ordersColCursor += (keysym === '<LEFT>') ? -1 : 1 ordersColCursor += (keysym === '<LEFT>') ? -1 : 1
clampOrdersHoriz() clampOrdersHoriz()
drawOrdersContents(wo) if (ordersColCursor === oldColCursor) return // hit edge
const dVoice = ordersVoiceOff - oldVoiceOff
if (dVoice !== 0) {
shiftOrdersAreaHorizontal(dVoice)
if (dVoice > 0) for (let i = 0; i < dVoice; i++) drawOrdersVoiceColumnAt(VOCSIZE_ORDERS - 1 - i)
else for (let i = 0; i < -dVoice; i++) drawOrdersVoiceColumnAt(i)
}
drawOrdersHeader()
drawOrdersRowAt(ordersCursor)
} else if (keyJustHit && keysym === '\n') { } else if (keyJustHit && keysym === '\n') {
cueIdx = ordersCursor cueIdx = ordersCursor
clampCue() clampCue()

View File

@@ -2340,7 +2340,12 @@ TODO:
linear-freq flag in the song-table flags byte. Spec details in linear-freq flag in the song-table flags byte. Spec details in
TAUD_NOTE_EFFECTS.md §1, §E, §F, §G. TAUD_NOTE_EFFECTS.md §1, §E, §F, §G.
[ ] milkytracker-style volume ramping (on sample-end only) [ ] milkytracker-style volume ramping (on sample-end only)
[ ] make Cues tab horizontally scrollable [x] make Cues tab move faster
Resolution: Cues panel now uses memory-shift (`shiftOrdersAreaHorizontal`)
for LEFT/RIGHT and `shiftPatternArea` for UP/DOWN, plus per-row
(`drawOrdersRowAt`) and per-column (`drawOrdersVoiceColumnAt`) helpers,
replacing the full-panel redraw on every keystroke.
[ ] volume and panning policy to match note effect policy: when note is "retriggerred" (note command with instrument specified), the volume/pan must take default value; if not (note command with instrument 0) the volume/pan must stay at the old value. Make both audio engine and taut.js simulator changes.
Play Data: play data are series of tracker-like instructions, visualised as: Play Data: play data are series of tracker-like instructions, visualised as: