mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-06 13:38:30 +09:00
taut: panelised view
This commit is contained in:
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user