taut: panelised view

This commit is contained in:
minjaesong
2026-04-25 15:29:02 +09:00
parent 92b9984ef8
commit 85b8586a3a

View File

@@ -444,8 +444,11 @@ const [SCRH, SCRW] = con.getmaxyx()
const PTNVIEW_OFFSET_X = 3 const PTNVIEW_OFFSET_X = 3
const PTNVIEW_OFFSET_Y = 9 const PTNVIEW_OFFSET_Y = 9
const PTNVIEW_HEIGHT = SCRH - PTNVIEW_OFFSET_Y const PTNVIEW_HEIGHT = SCRH - PTNVIEW_OFFSET_Y
const COLSIZE = 15
const VOCSIZE = 5 const COLSIZE_TIMELINE_FULL = 15
const VOCSIZE_TIMELINE_FULL = 5
const VOCSIZE_ORDERS = 18
const VIEW_TIMELINE = 0 const VIEW_TIMELINE = 0
const VIEW_ORDERS = 1 const VIEW_ORDERS = 1
@@ -469,11 +472,14 @@ function fillLine(y, c, back) {
} }
} }
const PANEL_NAMES = ['Timeline', 'Orders ']
function drawStatusBar() { function drawStatusBar() {
fillLine(1, colStatus, 255) fillLine(1, colStatus, 255)
const maxCue = song.lastActiveCue < 0 ? 0 : song.lastActiveCue const maxCue = song.lastActiveCue < 0 ? 0 : song.lastActiveCue
const vHi = Math.min(voiceOff + VOCSIZE, song.numVoices) const vHi = Math.min(voiceOff + VOCSIZE_TIMELINE_FULL, song.numVoices)
const txt = `${song.filePath} Cue ${cueIdx.hex03()}/${maxCue.hex03()} Row ${cursorRow.dec02()} V${(voiceOff+1).dec02()}-${vHi.dec02()}/${song.numVoices.dec02()} BPM ${audio.getBPM(PLAYHEAD)} Spd ${audio.getTickRate(PLAYHEAD)} ` const pname = PANEL_NAMES[currentPanel] || '? '
const txt = `${song.filePath} [${pname}] Cue ${cueIdx.hex03()}/${maxCue.hex03()} Row ${cursorRow.dec02()} V${(voiceOff+1).dec02()}-${vHi.dec02()}/${song.numVoices.dec02()} BPM ${audio.getBPM(PLAYHEAD)} Spd ${audio.getTickRate(PLAYHEAD)} `
con.move(1, 1) con.move(1, 1)
con.color_pair(colStatus, 255) con.color_pair(colStatus, 255)
print(txt) print(txt)
@@ -485,16 +491,16 @@ function drawStatusBar() {
function drawSeparators(style) { function drawSeparators(style) {
if (style == 1) { if (style == 1) {
con.color_pair(colSep, 255) con.color_pair(colSep, 255)
for (let c = 0; c < VOCSIZE - 1; c++) { for (let c = 0; c < VOCSIZE_TIMELINE_FULL - 1; c++) {
for (let y = PTNVIEW_OFFSET_Y - 1; y < PTNVIEW_HEIGHT; y++) { for (let y = PTNVIEW_OFFSET_Y - 1; y < PTNVIEW_HEIGHT; y++) {
con.move(y, PTNVIEW_OFFSET_X + COLSIZE * (c+1) - 1) con.move(y, PTNVIEW_OFFSET_X + COLSIZE_TIMELINE_FULL * (c+1) - 1)
con.prnch(0xB3) con.prnch(0xB3)
} }
} }
} }
else { else {
// paint the first column of pattern view with colour // paint the first column of pattern view with colour
for (let x = PTNVIEW_OFFSET_X; x < SCRW - 3; x += COLSIZE) { for (let x = PTNVIEW_OFFSET_X; x < SCRW - 3; x += COLSIZE_TIMELINE_FULL) {
for (let y = 0; y < PTNVIEW_HEIGHT+1; y++) { for (let y = 0; y < PTNVIEW_HEIGHT+1; y++) {
let memOffset = (y+PTNVIEW_OFFSET_Y-2) * SCRW + (x-1) let memOffset = (y+PTNVIEW_OFFSET_Y-2) * SCRW + (x-1)
let bgColOffset = GPU_MEM - TEXT_BACK_OFF - memOffset let bgColOffset = GPU_MEM - TEXT_BACK_OFF - memOffset
@@ -509,13 +515,13 @@ function drawSeparators(style) {
function drawVoiceHeaders() { function drawVoiceHeaders() {
fillLine(PTNVIEW_OFFSET_Y - 1, colVoiceHdr, 255) fillLine(PTNVIEW_OFFSET_Y - 1, colVoiceHdr, 255)
const cue = song.cues[cueIdx] const cue = song.cues[cueIdx]
for (let c = 0; c < VOCSIZE; c++) { for (let c = 0; c < VOCSIZE_TIMELINE_FULL; c++) {
const voice = voiceOff + c const voice = voiceOff + c
const x = PTNVIEW_OFFSET_X + COLSIZE * c const x = PTNVIEW_OFFSET_X + COLSIZE_TIMELINE_FULL * c
con.move(PTNVIEW_OFFSET_Y - 1, x) con.move(PTNVIEW_OFFSET_Y - 1, x)
if (voice >= song.numVoices) { if (voice >= song.numVoices) {
con.color_pair(colVoiceHdr, 255) con.color_pair(colVoiceHdr, 255)
print(` `.substring(0, COLSIZE - 1)) print(` `.substring(0, COLSIZE_TIMELINE_FULL - 1))
} else { } else {
const isCursor = (voice === cursorVox) const isCursor = (voice === cursorVox)
const isMuted = voiceMutes[voice] const isMuted = voiceMutes[voice]
@@ -524,7 +530,7 @@ function drawVoiceHeaders() {
const vlabel = `V${(voice+1).dec02()}` const vlabel = `V${(voice+1).dec02()}`
const plabel = (ptnIdx === CUE_EMPTY) ? '---' : ptnIdx.hex03() const plabel = (ptnIdx === CUE_EMPTY) ? '---' : ptnIdx.hex03()
const label = ` ${vlabel} ptn ${plabel} ` const label = ` ${vlabel} ptn ${plabel} `
print((label + ' ').substring(0, COLSIZE - 1)) print((label + ' ').substring(0, COLSIZE_TIMELINE_FULL - 1))
} }
} }
@@ -550,9 +556,9 @@ function drawPatternRowAt(viewRow) {
} }
// TODO scroll indicator on x=SCRW? // TODO scroll indicator on x=SCRW?
for (let c = 0; c < VOCSIZE; c++) { for (let c = 0; c < VOCSIZE_TIMELINE_FULL; c++) {
const voice = voiceOff + c const voice = voiceOff + c
const x = PTNVIEW_OFFSET_X + COLSIZE * c const x = PTNVIEW_OFFSET_X + COLSIZE_TIMELINE_FULL * c
let cell = EMPTY_CELL let cell = EMPTY_CELL
if (actualRow < ROWS_PER_PAT && voice < song.numVoices) { if (actualRow < ROWS_PER_PAT && voice < song.numVoices) {
const ptnIdx = cue.ptns[voice] const ptnIdx = cue.ptns[voice]
@@ -571,7 +577,7 @@ function drawPatternView() {
} }
function drawControlHint() { function drawControlHint() {
let hintElem = [ let hintElemTimeline = [
[`\u008428u\u008429u`,'Ptn'], [`\u008428u\u008429u`,'Ptn'],
[`Pg\u008418u`,'Cue'], [`Pg\u008418u`,'Cue'],
['sep'], ['sep'],
@@ -582,17 +588,29 @@ function drawControlHint() {
['sep'], ['sep'],
['m','Mute'], ['m','Mute'],
['s','Solo'], ['s','Solo'],
['sep'],
['Tab','Panel'],
//['q','Quit'],
]
let hintElemOrders = [
[`\u008428u\u008429u`,'Order'],
[`Ent`,'Go to cue'],
['sep'],
['Tab','Panel'],
['sep'], ['sep'],
['q','Quit'], ['q','Quit'],
] ]
let hintElems = [hintElemTimeline, hintElemOrders]
// erase current line // erase current line
con.move(SCRH, 1) con.move(SCRH, 1)
print(' '.repeat(SCRW-1)) print(' '.repeat(SCRW-1))
// start writing // start writing
con.move(SCRH, 1) con.move(SCRH, 1)
hintElem.forEach((pair, i, list) => {
hintElems[currentPanel].forEach((pair, i, list) => {
con.color_pair(colStatus,255) con.color_pair(colStatus,255)
if (pair[0] == 'sep') { if (pair[0] == 'sep') {
print(` ${BIGDOT} `) print(` ${BIGDOT} `)
@@ -667,11 +685,8 @@ function drawVoiceDetail() {
function drawAll() { function drawAll() {
con.clear() con.clear()
drawStatusBar() drawStatusBar()
drawVoiceHeaders()
drawPatternView()
drawVoiceDetail()
drawSeparators(separatorStyle)
drawControlHint() drawControlHint()
redrawPanel()
con.move(1, 1) con.move(1, 1)
} }
@@ -695,7 +710,7 @@ const TEXT_PLANES = [TEXT_CHAR_OFF, TEXT_BACK_OFF, TEXT_FORE_OFF]
const SCRATCH_PTR = sys.malloc(SCRW * PTNVIEW_HEIGHT) const SCRATCH_PTR = sys.malloc(SCRW * PTNVIEW_HEIGHT)
// Horizontal salvage // Horizontal salvage
const SALVAGE_HORIZ_LEN = (VOCSIZE - 1) * COLSIZE const SALVAGE_HORIZ_LEN = (VOCSIZE_TIMELINE_FULL - 1) * COLSIZE_TIMELINE_FULL
/** /**
* Shift the pattern-view rows by `dy` lines (positive = down, negative = up) * Shift the pattern-view rows by `dy` lines (positive = down, negative = up)
@@ -726,9 +741,9 @@ function shiftPatternArea(dy) {
* voice headers and status bar must be redrawn by the caller. * voice headers and status bar must be redrawn by the caller.
*/ */
function shiftPatternAreaHorizontal(dVoice) { function shiftPatternAreaHorizontal(dVoice) {
// Column of the first char to copy (1-indexed); dest is COLSIZE chars earlier/later. // Column of the first char to copy (1-indexed); dest is COLSIZE_TIMELINE_FULL chars earlier/later.
const srcX = PTNVIEW_OFFSET_X + (dVoice > 0 ? COLSIZE : 0) const srcX = PTNVIEW_OFFSET_X + (dVoice > 0 ? COLSIZE_TIMELINE_FULL : 0)
const dstX = PTNVIEW_OFFSET_X + (dVoice > 0 ? 0 : COLSIZE) const dstX = PTNVIEW_OFFSET_X + (dVoice > 0 ? 0 : COLSIZE_TIMELINE_FULL)
const srcOff = srcX - 1 // 0-indexed offset from column 1 for address arithmetic const srcOff = srcX - 1 // 0-indexed offset from column 1 for address arithmetic
const dstOff = dstX - 1 const dstOff = dstX - 1
@@ -743,13 +758,13 @@ function shiftPatternAreaHorizontal(dVoice) {
} }
/** /**
* Redraw every row of one voice column (slot 0..VOCSIZE-1) after a horizontal shift. * Redraw every row of one voice column (slot 0..VOCSIZE_TIMELINE_FULL-1) after a horizontal shift.
* Also redraws separators for the whole row so any separator at the exposed boundary * Also redraws separators for the whole row so any separator at the exposed boundary
* (which the VRAM shift left correct) is confirmed visually consistent. * (which the VRAM shift left correct) is confirmed visually consistent.
*/ */
function drawVoiceColumnAt(slot) { function drawVoiceColumnAt(slot) {
const voice = voiceOff + slot const voice = voiceOff + slot
const x = PTNVIEW_OFFSET_X + COLSIZE * slot const x = PTNVIEW_OFFSET_X + COLSIZE_TIMELINE_FULL * slot
const cue = song.cues[cueIdx] const cue = song.cues[cueIdx]
const ptnIdx = (voice < song.numVoices) ? cue.ptns[voice] : CUE_EMPTY const ptnIdx = (voice < song.numVoices) ? cue.ptns[voice] : CUE_EMPTY
@@ -781,6 +796,8 @@ let cursorRow = 0
let scrollRow = 0 let scrollRow = 0
let voiceOff = 0 let voiceOff = 0
let cursorVox = 0 let cursorVox = 0
let ordersCursor = 0
let ordersScroll = 0
if (exec_args[1] === undefined) { if (exec_args[1] === undefined) {
println(`Usage: ${exec_args[0]} path_to.taud`) println(`Usage: ${exec_args[0]} path_to.taud`)
@@ -803,24 +820,188 @@ function resetAudioDevice() {
audio.stop(PLAYHEAD) audio.stop(PLAYHEAD)
} }
function redrawFull() { function redrawFull() { drawAll() }
con.clear()
drawStatusBar()
drawControlHint()
redrawPanel()
con.move(1, 1)
}
function redrawPanel() { function redrawPanel() {
switch (currentPanel) { panels[currentPanel].drawContents()
case VIEW_TIMELINE: }
drawPatternView()
drawVoiceHeaders() /////////////////////////////////////////////////////////////////////////////////////////////////////////////
drawSeparators(separatorStyle) // PANELS
drawVoiceDetail() /////////////////////////////////////////////////////////////////////////////////////////////////////////////
function drawTimelineContents(wo) {
drawVoiceHeaders()
drawPatternView()
drawSeparators(separatorStyle)
drawVoiceDetail()
}
function drawOrdersHeader() {
fillLine(PTNVIEW_OFFSET_Y - 1, colVoiceHdr, 255)
con.move(PTNVIEW_OFFSET_Y - 1, 1)
con.color_pair(colVoiceHdr, 255)
let hdr = ' '
for (let c = 0; c < VOCSIZE_ORDERS; c++) {
const v = voiceOff + c
hdr += v < song.numVoices ? `V${(v+1).dec02()} ` : ' '
}
print(hdr)
}
function drawOrdersContents(wo) {
drawOrdersHeader()
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
const isSel = (ci === ordersCursor)
const isCur = playbackMode !== PLAYMODE_NONE && ci === cueIdx
const back = isSel ? (playbackMode !== PLAYMODE_NONE ? colPlayback : colHighlight)
: (isCur ? colPlayback : colBackPtn)
con.move(y, 1)
if (ci > maxCue) {
con.color_pair(colBackPtn, colBackPtn)
print(' '.repeat(SCRW - 1))
} else {
const cue = song.cues[ci]
const rowstr = ci.hex03()
con.color_pair(ci % 4 === 0 ? colRowNumEmph1 : colRowNum, back)
con.prnch(rowstr.charCodeAt(0)); con.move(y, 2)
con.prnch(rowstr.charCodeAt(1)); con.move(y, 3)
con.prnch(rowstr.charCodeAt(2))
con.move(y, 5)
for (let c = 0; c < VOCSIZE_ORDERS; c++) {
const v = voiceOff + c
const ptn = v < song.numVoices ? cue.ptns[v] : CUE_EMPTY
con.color_pair(ptn === CUE_EMPTY ? colSep : colNote, back)
print(ptn === CUE_EMPTY ? '--- ' : ptn.hex03() + ' ')
}
const endX = 5 + VOCSIZE_ORDERS * 4
if (endX <= SCRW) { con.color_pair(colBackPtn, back); print(' '.repeat(SCRW - endX)) }
}
} }
} }
function timelineInput(wo, event) {
const keysym = event[1]
const keyJustHit = (1 == event[2])
const shiftDown = (event.includes(59) || event.includes(60))
const moveDelta = shiftDown ? 4 : 1
if (playbackMode !== PLAYMODE_NONE) {
if (keyJustHit && shiftDown && event.includes(keys.Y) || keysym === " ") { stopPlayback(); redrawPanel() }
else if (keysym === "<LEFT>" || keysym === "<RIGHT>") {
const oldVoiceOff = voiceOff
cursorVox += (keysym === "<LEFT>") ? -moveDelta : moveDelta
clampVoice()
const dVoice = voiceOff - oldVoiceOff
if (dVoice !== 0) { shiftPatternAreaHorizontal(dVoice); drawVoiceColumnAt(dVoice > 0 ? VOCSIZE_TIMELINE_FULL - 1 : 0) }
drawVoiceHeaders(); drawSeparators(separatorStyle); drawStatusBar(); drawVoiceDetail()
}
else if (keyJustHit && !shiftDown && event.includes(keys.M)) { toggleMute(cursorVox) }
else if (keyJustHit && !shiftDown && event.includes(keys.S)) { toggleSolo(cursorVox) }
return
}
if (keyJustHit && shiftDown && event.includes(keys.Y)) { startPlaySong(); redrawPanel(); return }
if (keyJustHit && shiftDown && event.includes(keys.U)) { startPlayCue(); redrawPanel(); return }
if ( shiftDown && event.includes(keys.I)) { startPlayRow(); drawPatternRowAt(cursorRow - scrollRow); return }
if (keyJustHit && shiftDown && event.includes(keys.O) || keysym === " ") { stopPlayback(); return }
const oldCursor = cursorRow
const oldScroll = scrollRow
let rowMove = false
let fullRedraw = false
if (keysym === "<LEFT>" || keysym === "<RIGHT>") {
const oldVoiceOff = voiceOff
cursorVox += (keysym === "<LEFT>") ? -moveDelta : moveDelta
clampVoice()
const dVoice = voiceOff - oldVoiceOff
if (dVoice !== 0) { shiftPatternAreaHorizontal(dVoice); drawVoiceColumnAt(dVoice > 0 ? VOCSIZE_TIMELINE_FULL - 1 : 0) }
drawVoiceHeaders(); drawSeparators(separatorStyle); drawStatusBar(); drawVoiceDetail()
return
}
if (keyJustHit && !shiftDown && event.includes(keys.M)) { toggleMute(cursorVox); return }
if (keyJustHit && !shiftDown && event.includes(keys.S)) { toggleSolo(cursorVox); return }
if (keysym === "<UP>") { cursorRow -= moveDelta; rowMove = true }
else if (keysym === "<DOWN>") { cursorRow += moveDelta; rowMove = true }
else if (keysym === "<HOME>") { cursorRow = 0; rowMove = true }
else if (keysym === "<END>") { cursorRow = ROWS_PER_PAT-1; rowMove = true }
else if (keysym === "<PAGE_UP>") { cueIdx -= moveDelta; fullRedraw = true }
else if (keysym === "<PAGE_DOWN>") { cueIdx += moveDelta; fullRedraw = true }
else return
clampCursor(); clampVoice(); clampCue()
if (fullRedraw) { drawAll(); return }
if (!rowMove || cursorRow === oldCursor) return
const dScroll = scrollRow - oldScroll
if (dScroll === 0) {
drawPatternRowAt(oldCursor - scrollRow)
drawPatternRowAt(cursorRow - scrollRow)
} else if (Math.abs(dScroll) >= PTNVIEW_HEIGHT) {
drawPatternView()
} else {
shiftPatternArea(-dScroll)
if (dScroll > 0) { for (let i = 0; i < dScroll; i++) drawPatternRowAt(PTNVIEW_HEIGHT - 1 - i) }
else { for (let i = 0; i < -dScroll; i++) drawPatternRowAt(i) }
if (oldCursor >= scrollRow && oldCursor < scrollRow + PTNVIEW_HEIGHT) drawPatternRowAt(oldCursor - scrollRow)
drawPatternRowAt(cursorRow - scrollRow)
}
drawSeparators(separatorStyle); drawStatusBar(); drawVoiceDetail()
}
function ordersInput(wo, event) {
const keysym = event[1]
const keyJustHit = (1 == event[2])
const shiftDown = (event.includes(59) || event.includes(60))
const moveDelta = shiftDown ? 4 : 1
const maxCue = song.lastActiveCue < 0 ? 0 : song.lastActiveCue
if (keysym === '<UP>') {
ordersCursor = Math.max(0, ordersCursor - moveDelta)
if (ordersCursor < ordersScroll) ordersScroll = ordersCursor
drawOrdersContents(wo)
} else if (keysym === '<DOWN>') {
ordersCursor = Math.min(maxCue, ordersCursor + moveDelta)
if (ordersCursor >= ordersScroll + PTNVIEW_HEIGHT) ordersScroll = Math.max(0, ordersCursor - PTNVIEW_HEIGHT + 1)
drawOrdersContents(wo)
} else if (keysym === '<PAGE_UP>') {
ordersCursor = Math.max(0, ordersCursor - PTNVIEW_HEIGHT)
ordersScroll = Math.max(0, ordersScroll - PTNVIEW_HEIGHT)
drawOrdersContents(wo)
} else if (keysym === '<PAGE_DOWN>') {
ordersCursor = Math.min(maxCue, ordersCursor + PTNVIEW_HEIGHT)
if (ordersCursor >= ordersScroll + PTNVIEW_HEIGHT) ordersScroll = Math.max(0, ordersCursor - PTNVIEW_HEIGHT + 1)
drawOrdersContents(wo)
} else if (keysym === '<LEFT>' || keysym === '<RIGHT>') {
const oldVoiceOff = voiceOff
cursorVox += (keysym === '<LEFT>') ? -moveDelta : moveDelta
clampVoice()
if (voiceOff !== oldVoiceOff) drawOrdersContents(wo)
} else if (keyJustHit && keysym === '\n') {
cueIdx = ordersCursor
clampCue()
currentPanel = VIEW_TIMELINE
drawAll()
return
} else {
return
}
drawStatusBar()
}
const panelTimeline = new win.WindowObject(1, PTNVIEW_OFFSET_Y, SCRW, PTNVIEW_HEIGHT, timelineInput, drawTimelineContents, undefined, ()=>{})
const panelOrders = new win.WindowObject(1, PTNVIEW_OFFSET_Y, SCRW, PTNVIEW_HEIGHT, ordersInput, drawOrdersContents, undefined, ()=>{})
const panels = [panelTimeline, panelOrders]
///////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////
// PLAYBACK STATE // PLAYBACK STATE
///////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -883,7 +1064,8 @@ function stopPlayback() {
function updatePlayback() { function updatePlayback() {
if (!audio.isPlaying(PLAYHEAD)) { if (!audio.isPlaying(PLAYHEAD)) {
playbackMode = PLAYMODE_NONE playbackMode = PLAYMODE_NONE
if (cursorRow >= scrollRow && cursorRow < scrollRow + PTNVIEW_HEIGHT) if (currentPanel === VIEW_TIMELINE &&
cursorRow >= scrollRow && cursorRow < scrollRow + PTNVIEW_HEIGHT)
drawPatternRowAt(cursorRow - scrollRow) drawPatternRowAt(cursorRow - scrollRow)
drawStatusBar() drawStatusBar()
return return
@@ -894,12 +1076,13 @@ function updatePlayback() {
if (playbackMode === PLAYMODE_CUE && nowCue !== playStartCue) { if (playbackMode === PLAYMODE_CUE && nowCue !== playStartCue) {
stopPlayback() stopPlayback()
redrawPanel() if (currentPanel === VIEW_TIMELINE) redrawPanel()
return return
} }
if (playbackMode === PLAYMODE_ROW && (nowRow !== playStartRow || nowCue !== playStartCue)) { if (playbackMode === PLAYMODE_ROW && (nowRow !== playStartRow || nowCue !== playStartCue)) {
stopPlayback() stopPlayback()
if (cursorRow >= scrollRow && cursorRow < scrollRow + PTNVIEW_HEIGHT) if (currentPanel === VIEW_TIMELINE &&
cursorRow >= scrollRow && cursorRow < scrollRow + PTNVIEW_HEIGHT)
drawPatternRowAt(cursorRow - scrollRow) drawPatternRowAt(cursorRow - scrollRow)
drawStatusBar() drawStatusBar()
return return
@@ -914,32 +1097,34 @@ function updatePlayback() {
cueIdx = nowCue cueIdx = nowCue
cursorRow = nowRow cursorRow = nowRow
clampCursor() clampCursor()
redrawPanel() if (currentPanel === VIEW_TIMELINE) redrawPanel()
} else { } else {
const oldCursor = cursorRow const oldCursor = cursorRow
const oldScroll = scrollRow const oldScroll = scrollRow
cursorRow = nowRow cursorRow = nowRow
clampCursor() clampCursor()
const dScroll = scrollRow - oldScroll if (currentPanel === VIEW_TIMELINE) {
if (dScroll === 0) { const dScroll = scrollRow - oldScroll
drawPatternRowAt(oldCursor - scrollRow) if (dScroll === 0) {
drawPatternRowAt(cursorRow - scrollRow)
} else if (Math.abs(dScroll) >= PTNVIEW_HEIGHT) {
drawPatternView()
} else {
shiftPatternArea(-dScroll)
if (dScroll > 0) {
for (let i = 0; i < dScroll; i++) drawPatternRowAt(PTNVIEW_HEIGHT - 1 - i)
} else {
for (let i = 0; i < -dScroll; i++) drawPatternRowAt(i)
}
if (oldCursor >= scrollRow && oldCursor < scrollRow + PTNVIEW_HEIGHT)
drawPatternRowAt(oldCursor - scrollRow) drawPatternRowAt(oldCursor - scrollRow)
drawPatternRowAt(cursorRow - scrollRow) drawPatternRowAt(cursorRow - scrollRow)
} else if (Math.abs(dScroll) >= PTNVIEW_HEIGHT) {
drawPatternView()
} else {
shiftPatternArea(-dScroll)
if (dScroll > 0) {
for (let i = 0; i < dScroll; i++) drawPatternRowAt(PTNVIEW_HEIGHT - 1 - i)
} else {
for (let i = 0; i < -dScroll; i++) drawPatternRowAt(i)
}
if (oldCursor >= scrollRow && oldCursor < scrollRow + PTNVIEW_HEIGHT)
drawPatternRowAt(oldCursor - scrollRow)
drawPatternRowAt(cursorRow - scrollRow)
}
drawSeparators(separatorStyle)
drawVoiceDetail()
} }
drawStatusBar() drawStatusBar()
drawSeparators(separatorStyle)
drawVoiceDetail()
} }
} }
@@ -960,9 +1145,9 @@ function clampVoice() {
if (cursorVox >= song.numVoices) cursorVox = song.numVoices - 1 if (cursorVox >= song.numVoices) cursorVox = song.numVoices - 1
if (cursorVox < voiceOff) voiceOff = cursorVox if (cursorVox < voiceOff) voiceOff = cursorVox
// keep cursor centred until view reaches an edge (mirrors clampCursor logic) // keep cursor centred until view reaches an edge (mirrors clampCursor logic)
if (cursorVox < voiceOff + (VOCSIZE>>>1) && voiceOff > 0) voiceOff = cursorVox - (VOCSIZE>>>1) if (cursorVox < voiceOff + (VOCSIZE_TIMELINE_FULL>>>1) && voiceOff > 0) voiceOff = cursorVox - (VOCSIZE_TIMELINE_FULL>>>1)
if (cursorVox >= voiceOff + ((VOCSIZE+1)>>>1)) voiceOff = cursorVox - ((VOCSIZE+1)>>>1) + 1 if (cursorVox >= voiceOff + ((VOCSIZE_TIMELINE_FULL+1)>>>1)) voiceOff = cursorVox - ((VOCSIZE_TIMELINE_FULL+1)>>>1) + 1
const maxOff = Math.max(0, song.numVoices - VOCSIZE) const maxOff = Math.max(0, song.numVoices - VOCSIZE_TIMELINE_FULL)
if (voiceOff < 0) voiceOff = 0 if (voiceOff < 0) voiceOff = 0
if (voiceOff > maxOff) voiceOff = maxOff if (voiceOff > maxOff) voiceOff = maxOff
} }
@@ -985,114 +1170,21 @@ let exitFlag = false
while (!exitFlag) { while (!exitFlag) {
input.withEvent(event => { input.withEvent(event => {
if (event[0] !== "key_down") return if (event[0] !== "key_down") return
const keysym = event[1] const keysym = event[1]
const keyJustHit = (1 == event[2]) const keyJustHit = (1 == event[2])
const shiftDown = (event.includes(59) || event.includes(60))
const moveDelta = shiftDown ? 4 : 1
if (keysym === "<ESC>" || keysym === "q" || keysym === "Q") { if (keysym === "<ESC>" || keysym === "q" || keysym === "Q") {
exitFlag = true exitFlag = true
return return
} }
if (playbackMode !== PLAYMODE_NONE) { if (keysym === "<TAB>") {
if (keyJustHit && shiftDown && event.includes(keys.Y) || keysym === " ") { stopPlayback(); redrawPanel() } currentPanel = (currentPanel + 1) % panels.length
else if (keysym === "<LEFT>" || keysym === "<RIGHT>") {
const oldVoiceOff = voiceOff
cursorVox += (keysym === "<LEFT>") ? -moveDelta : moveDelta
clampVoice()
const dVoice = voiceOff - oldVoiceOff
if (dVoice !== 0) {
shiftPatternAreaHorizontal(dVoice)
drawVoiceColumnAt(dVoice > 0 ? VOCSIZE - 1 : 0)
}
drawVoiceHeaders()
drawSeparators(separatorStyle)
drawStatusBar()
drawVoiceDetail()
}
else if (keyJustHit && !shiftDown && event.includes(keys.M)) { toggleMute(cursorVox) }
else if (keyJustHit && !shiftDown && event.includes(keys.S)) { toggleSolo(cursorVox) }
return
}
if (keyJustHit && shiftDown && event.includes(keys.Y)) { startPlaySong(); redrawPanel(); return }
if (keyJustHit && shiftDown && event.includes(keys.U)) { startPlayCue(); redrawPanel(); return }
if ( shiftDown && event.includes(keys.I)) { startPlayRow(); drawPatternRowAt(cursorRow - scrollRow); return } // allow multiple plays by holding the keys down
if (keyJustHit && shiftDown && event.includes(keys.O) || keysym === " ") { stopPlayback(); return }
const oldCursor = cursorRow
const oldScroll = scrollRow
let rowMove = false // pure row-cursor movement; can be fast-path
let fullRedraw = false // voice/cue change; needs full viewport refresh
if (keysym === "<LEFT>" || keysym === "<RIGHT>") {
const oldVoiceOff = voiceOff
cursorVox += (keysym === "<LEFT>") ? -moveDelta : moveDelta
clampVoice()
const dVoice = voiceOff - oldVoiceOff
if (dVoice !== 0) {
shiftPatternAreaHorizontal(dVoice)
drawVoiceColumnAt(dVoice > 0 ? VOCSIZE - 1 : 0)
}
drawVoiceHeaders()
drawSeparators(separatorStyle)
drawStatusBar()
drawVoiceDetail()
return
}
if (keyJustHit && !shiftDown && event.includes(keys.M)) { toggleMute(cursorVox); return }
if (keyJustHit && !shiftDown && event.includes(keys.S)) { toggleSolo(cursorVox); return }
if (keysym === "<UP>") { cursorRow -= moveDelta; rowMove = true }
else if (keysym === "<DOWN>") { cursorRow += moveDelta; rowMove = true }
else if (keysym === "<HOME>") { cursorRow = 0; rowMove = true }
else if (keysym === "<END>") { cursorRow = ROWS_PER_PAT-1; rowMove = true }
else if (keysym === "<PAGE_UP>") { cueIdx -= moveDelta; fullRedraw = true }
else if (keysym === "<PAGE_DOWN>") { cueIdx += moveDelta; fullRedraw = true }
else return
clampCursor(); clampVoice(); clampCue()
if (fullRedraw) {
drawAll() drawAll()
return return
} }
if (!rowMove || cursorRow === oldCursor) return panels[currentPanel].processInput(event)
const dScroll = scrollRow - oldScroll
if (dScroll === 0) {
// in-viewport cursor move: just flip the two affected rows
drawPatternRowAt(oldCursor - scrollRow)
drawPatternRowAt(cursorRow - scrollRow)
}
else if (Math.abs(dScroll) >= PTNVIEW_HEIGHT) {
// huge jump, nothing salvageable
drawPatternView()
}
else {
// scroll: shift VRAM, then redraw only newly exposed edge rows
shiftPatternArea(-dScroll)
if (dScroll > 0) {
for (let i = 0; i < dScroll; i++)
drawPatternRowAt(PTNVIEW_HEIGHT - 1 - i)
} else {
for (let i = 0; i < -dScroll; i++)
drawPatternRowAt(i)
}
// The old cursor row, if still visible, carried its highlight along with the shift — unhighlight it
if (oldCursor >= scrollRow && oldCursor < scrollRow + PTNVIEW_HEIGHT)
drawPatternRowAt(oldCursor - scrollRow)
// The new cursor row always needs highlight
drawPatternRowAt(cursorRow - scrollRow)
}
drawSeparators(separatorStyle)
drawStatusBar()
drawVoiceDetail()
}) })
if (playbackMode !== PLAYMODE_NONE) updatePlayback() if (playbackMode !== PLAYMODE_NONE) updatePlayback()