mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-22 04:04:04 +09:00
Compare commits
3 Commits
e29f9c3032
...
3ca31e57a1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ca31e57a1 | ||
|
|
1f630aee62 | ||
|
|
3f3644d165 |
@@ -67,7 +67,10 @@ panfineri:"\u008426u",
|
|||||||
/* miscellaneous */
|
/* miscellaneous */
|
||||||
unticked:"\u009E",
|
unticked:"\u009E",
|
||||||
ticked:"\u009F",
|
ticked:"\u009F",
|
||||||
middot:MIDDOT
|
middot:MIDDOT,
|
||||||
|
doubledot:"\u008419u",
|
||||||
|
stop:"\u008420u\u008421u",
|
||||||
|
play:"\u008422u\u008423u",
|
||||||
}
|
}
|
||||||
|
|
||||||
const fxNames = {
|
const fxNames = {
|
||||||
@@ -125,20 +128,22 @@ Y:"Panbrello ",
|
|||||||
Z:"UNIMPLEMENTED",
|
Z:"UNIMPLEMENTED",
|
||||||
}
|
}
|
||||||
const panFxNames = {
|
const panFxNames = {
|
||||||
0:"Panning Set ",
|
0:"Set to",
|
||||||
1:"Pan slide L ",
|
1:"Slide L",
|
||||||
2:"Pan slide R ",
|
2:"Slide R",
|
||||||
3:"Fpan slide ",
|
3:"Fine slide",
|
||||||
30:"Fpan slide L ",
|
30:"Fine slide L",
|
||||||
31:"Fpan slide R "
|
31:"Fine slide R",
|
||||||
|
999:"--",
|
||||||
}
|
}
|
||||||
const volFxNames = {
|
const volFxNames = {
|
||||||
0:"Volume Set ",
|
0:"Set to",
|
||||||
1:"Vol slide UP ",
|
1:"Slide UP",
|
||||||
2:"Vol slide DN ",
|
2:"Slide DN",
|
||||||
3:"fVol slide ",
|
3:"Fine slide",
|
||||||
30:"fVol slide DN",
|
30:"Fine slide DN",
|
||||||
31:"fVol slide UP"
|
31:"Fine slide UP",
|
||||||
|
999:"--",
|
||||||
}
|
}
|
||||||
|
|
||||||
const pitchTablePresets = {
|
const pitchTablePresets = {
|
||||||
@@ -345,8 +350,8 @@ function drawCellAt(y, x, cell, back) {
|
|||||||
|
|
||||||
// Styles: -1 = spaced (dddd ii vv pp effff, 19 chars)
|
// Styles: -1 = spaced (dddd ii vv pp effff, 19 chars)
|
||||||
// 0 = compact/current (15 chars)
|
// 0 = compact/current (15 chars)
|
||||||
// 1 = non-NOP preference note/fx + vol/pan (9 chars: 1+5+1+2)
|
// 1 = non-NOP preference note/fx + vol/pan (7 chars: 5+2, letters start on border)
|
||||||
// 2 = non-NOP preference note/fx only (6 chars: 1+5)
|
// 2 = non-NOP preference note/fx only (5 chars, letters start on border)
|
||||||
function drawCellAtStyled(y, x, cell, back, style) {
|
function drawCellAtStyled(y, x, cell, back, style) {
|
||||||
if (style === 0) { drawCellAt(y, x, cell, back); return }
|
if (style === 0) { drawCellAt(y, x, cell, back); return }
|
||||||
if (style === -1) {
|
if (style === -1) {
|
||||||
@@ -363,16 +368,15 @@ function drawCellAtStyled(y, x, cell, back, style) {
|
|||||||
con.color_pair(colEffArg, back); print(cell.sEffArg)
|
con.color_pair(colEffArg, back); print(cell.sEffArg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Styles 1 and 2: 1sp prefix + note-or-fx field (5 chars) [+ 1sp + vol-or-pan (2 chars)]
|
// Styles 1 and 2: note-or-fx field (5 chars) starts on the border column [+ vol-or-pan (2 chars)]
|
||||||
const noteEmpty = (cell._note === 0xFFFF)
|
const noteEmpty = (cell._note === 0xFFFF)
|
||||||
const fxEmpty = (cell._effop === 0 && cell._effarg === 0)
|
const fxEmpty = (cell._effop === 0 && cell._effarg === 0)
|
||||||
const volEmpty = (cell._voleff === 0)
|
const volEmpty = (cell._voleff === 0)
|
||||||
const panEmpty = (cell._paneff === 0)
|
const panEmpty = (cell._paneff === 0)
|
||||||
con.move(y, x)
|
con.move(y, x)
|
||||||
con.color_pair(colBackPtn, back); print(' ')
|
|
||||||
if (!noteEmpty) {
|
if (!noteEmpty) {
|
||||||
con.color_pair(colNote, back); print(cell.sNote)
|
|
||||||
con.color_pair(colBackPtn, back); print(' ')
|
con.color_pair(colBackPtn, back); print(' ')
|
||||||
|
con.color_pair(colNote, back); print(cell.sNote)
|
||||||
} else if (!fxEmpty) {
|
} else if (!fxEmpty) {
|
||||||
con.color_pair(colEffOp, back); print(cell.sEffOp)
|
con.color_pair(colEffOp, back); print(cell.sEffOp)
|
||||||
con.color_pair(colEffArg, back); print(cell.sEffArg)
|
con.color_pair(colEffArg, back); print(cell.sEffArg)
|
||||||
@@ -380,7 +384,7 @@ function drawCellAtStyled(y, x, cell, back, style) {
|
|||||||
con.color_pair(colNote, back); print(sym.middot.repeat(5))
|
con.color_pair(colNote, back); print(sym.middot.repeat(5))
|
||||||
}
|
}
|
||||||
if (style === 1) {
|
if (style === 1) {
|
||||||
con.color_pair(colBackPtn, back); print(' ')
|
//con.color_pair(colBackPtn, back); print(' ')
|
||||||
if (!volEmpty) {
|
if (!volEmpty) {
|
||||||
con.color_pair(colVol, back); print(cell.sVolEff); print(cell.sVolArg)
|
con.color_pair(colVol, back); print(cell.sVolEff); print(cell.sVolArg)
|
||||||
} else if (!panEmpty) {
|
} else if (!panEmpty) {
|
||||||
@@ -404,6 +408,8 @@ const ROWS_PER_PAT = 64
|
|||||||
const NUM_CUES = 1024
|
const NUM_CUES = 1024
|
||||||
const CUE_SIZE = 32
|
const CUE_SIZE = 32
|
||||||
const NUM_VOICES = 20
|
const NUM_VOICES = 20
|
||||||
|
const NUM_INSTRUMENTS = 256
|
||||||
|
const INSTRUMENT_SIZE = 64
|
||||||
const CUE_EMPTY = 0xFFF
|
const CUE_EMPTY = 0xFFF
|
||||||
|
|
||||||
function _peekU32LE(ptr, off) {
|
function _peekU32LE(ptr, off) {
|
||||||
@@ -476,12 +482,22 @@ function loadTaud(filePath, songIndex) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const instrBase = cueBase + NUM_CUES * CUE_SIZE
|
||||||
|
const instruments = new Array(NUM_INSTRUMENTS)
|
||||||
|
for (let n = 0; n < NUM_INSTRUMENTS; n++) {
|
||||||
|
const instr = new Uint8Array(INSTRUMENT_SIZE)
|
||||||
|
for (let k = 0; k < INSTRUMENT_SIZE; k++) {
|
||||||
|
instr[k] = sys.peek(ptr + instrBase + n * INSTRUMENT_SIZE + k) & 0xFF
|
||||||
|
}
|
||||||
|
instruments[n] = instr
|
||||||
|
}
|
||||||
|
|
||||||
sys.free(ptr)
|
sys.free(ptr)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filePath, version, numSongs, numVoices, numPats,
|
filePath, version, numSongs, numVoices, numPats,
|
||||||
bpm: (bpmStored + 24) & 0xFF, tickRate,
|
bpm: (bpmStored + 24) & 0xFF, tickRate,
|
||||||
patterns, cues, lastActiveCue
|
patterns, cues, lastActiveCue, instruments
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -495,18 +511,23 @@ 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_TIMELINE_FULL = 15
|
const TIMELINE_COLSIZES = [15, 7, 5]
|
||||||
const VOCSIZE_TIMELINE_FULL = 5
|
let timelineRowStyle = 0
|
||||||
|
let COLSIZE_TIMELINE_FULL = TIMELINE_COLSIZES[0]
|
||||||
|
let VOCSIZE_TIMELINE_FULL = Math.floor((SCRW - 3) / COLSIZE_TIMELINE_FULL)
|
||||||
|
|
||||||
const VOCSIZE_ORDERS = 18
|
const ORDERS_CMD_X = 5
|
||||||
|
const ORDERS_VOICE_X = 9
|
||||||
|
const VOCSIZE_ORDERS = Math.floor((SCRW - 8) / 4)
|
||||||
|
|
||||||
const VIEW_TIMELINE = 0
|
const VIEW_TIMELINE = 0
|
||||||
const VIEW_ORDERS = 1
|
const VIEW_ORDERS = 1
|
||||||
const VIEW_INSTRUMENT = 2
|
const VIEW_INSTRUMENT = 2
|
||||||
const VIEW_PATTERN_DETAILS = 3
|
const VIEW_PATTERN_DETAILS = 3
|
||||||
|
|
||||||
const colPlayback = 40
|
const colPlayback = 86
|
||||||
const colHighlight = 41
|
const colHighlight = 41
|
||||||
|
const colColumnSep = 6
|
||||||
const colRowNum = 250
|
const colRowNum = 250
|
||||||
const colRowNumEmph1 = 180
|
const colRowNumEmph1 = 180
|
||||||
const colStatus = 253
|
const colStatus = 253
|
||||||
@@ -529,7 +550,8 @@ function fillLine(y, c, back) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const PANEL_NAMES = [' Timeline ', ' Orders ', ' Patterns ', ' Samples ', 'Instruments']
|
const TAB_GAP = 2
|
||||||
|
const PANEL_NAMES = ['Timeline', 'Orders', 'Patterns', 'Samples', 'Instruments', 'Project', 'File']
|
||||||
|
|
||||||
function drawAlwaysOnElems() {
|
function drawAlwaysOnElems() {
|
||||||
drawStatusBar()
|
drawStatusBar()
|
||||||
@@ -547,14 +569,14 @@ function drawStatusBar() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function drawTabIndicator() {
|
function drawTabIndicator() {
|
||||||
const XOFF = 3
|
const XOFF = 2
|
||||||
const YOFF = PTNVIEW_OFFSET_Y - 4
|
const YOFF = PTNVIEW_OFFSET_Y - 4
|
||||||
const TABSIZE = 16
|
|
||||||
|
|
||||||
// TODO make it fancier
|
// TODO make it fancier
|
||||||
|
|
||||||
|
con.move(YOFF, XOFF)
|
||||||
for (let i = 0; i < PANEL_NAMES.length; i++) {
|
for (let i = 0; i < PANEL_NAMES.length; i++) {
|
||||||
con.move(YOFF, XOFF + TABSIZE*i)
|
if (i > 0) con.curs_right(TAB_GAP);
|
||||||
let panStr = PANEL_NAMES[i]
|
let panStr = PANEL_NAMES[i]
|
||||||
print((currentPanel === i) ? `[${panStr}]` : ` ${panStr} `)
|
print((currentPanel === i) ? `[${panStr}]` : ` ${panStr} `)
|
||||||
}
|
}
|
||||||
@@ -580,7 +602,10 @@ function drawSeparators(style) {
|
|||||||
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
|
||||||
sys.poke(bgColOffset, colHighlight)
|
let oldBgCol = sys.peek(bgColOffset)
|
||||||
|
if (oldBgCol == 255) {
|
||||||
|
sys.poke(bgColOffset, colColumnSep)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -597,7 +622,7 @@ function drawVoiceHeaders() {
|
|||||||
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_TIMELINE_FULL - 1))
|
print(` `.substring(0, COLSIZE_TIMELINE_FULL))
|
||||||
} else {
|
} else {
|
||||||
const isCursor = (voice === cursorVox)
|
const isCursor = (voice === cursorVox)
|
||||||
const isMuted = voiceMutes[voice]
|
const isMuted = voiceMutes[voice]
|
||||||
@@ -605,15 +630,22 @@ function drawVoiceHeaders() {
|
|||||||
const ptnIdx = cue.ptns[voice]
|
const ptnIdx = cue.ptns[voice]
|
||||||
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 =
|
||||||
print((label + ' ').substring(0, COLSIZE_TIMELINE_FULL - 1))
|
(timelineRowStyle == 0) ? ` ${vlabel} ptn ${plabel} ` :
|
||||||
|
(timelineRowStyle == 1) ? ` ${vlabel.substring(1)}:${plabel}` :
|
||||||
|
` ${vlabel}`
|
||||||
|
print((label + ' ').substring(0, COLSIZE_TIMELINE_FULL))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
drawSeparators(separatorStyle)
|
drawSeparators(separatorStyle)
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawPatternRowAt(viewRow, style = 0) {
|
// Sub-field layout for style-0 cells (shared by drawPatternRowAt and drawVoiceColumnAt)
|
||||||
|
const TL_FIELD_OFFSETS = [0, 4, 6, 8, 10, 11]
|
||||||
|
const TL_FIELD_FGS = [colNote, colInst, colVol, colPan, colEffOp, colEffArg]
|
||||||
|
|
||||||
|
function drawPatternRowAt(viewRow, style = timelineRowStyle) {
|
||||||
const actualRow = scrollRow + viewRow
|
const actualRow = scrollRow + viewRow
|
||||||
const y = PTNVIEW_OFFSET_Y + viewRow
|
const y = PTNVIEW_OFFSET_Y + viewRow
|
||||||
const highlight = (actualRow === cursorRow)
|
const highlight = (actualRow === cursorRow)
|
||||||
@@ -625,8 +657,11 @@ function drawPatternRowAt(viewRow, style = 0) {
|
|||||||
if (actualRow % 4 == 0) {con.color_pair(colRowNumEmph1, back)}
|
if (actualRow % 4 == 0) {con.color_pair(colRowNumEmph1, back)}
|
||||||
let rowstr = actualRow.dec02()
|
let rowstr = actualRow.dec02()
|
||||||
con.move(y, 1); con.prnch(rowstr.charCodeAt(0)); con.move(y, 2); con.prnch(rowstr.charCodeAt(1))
|
con.move(y, 1); con.prnch(rowstr.charCodeAt(0)); con.move(y, 2); con.prnch(rowstr.charCodeAt(1))
|
||||||
|
|
||||||
|
if (timelineRowStyle != 1) {
|
||||||
con.move(y, SCRW-2); con.prnch(rowstr.charCodeAt(0)); con.move(y, SCRW-1); con.prnch(rowstr.charCodeAt(1))
|
con.move(y, SCRW-2); con.prnch(rowstr.charCodeAt(0)); con.move(y, SCRW-1); con.prnch(rowstr.charCodeAt(1))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
print(' ')
|
print(' ')
|
||||||
}
|
}
|
||||||
@@ -643,18 +678,25 @@ function drawPatternRowAt(viewRow, style = 0) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
drawCellAtStyled(y, x, cell, back, style)
|
drawCellAtStyled(y, x, cell, back, style)
|
||||||
|
if (style === 0 && highlight && playbackMode === PLAYMODE_NONE && voice === cursorVox) {
|
||||||
|
const fieldStr = [cell.sNote, cell.sInst, cell.sVolEff+cell.sVolArg,
|
||||||
|
cell.sPanEff+cell.sPanArg, cell.sEffOp, cell.sEffArg][timelineColCursor]
|
||||||
|
con.move(y, x + TL_FIELD_OFFSETS[timelineColCursor])
|
||||||
|
con.color_pair(TL_FIELD_FGS[timelineColCursor], colPlayback)
|
||||||
|
print(fieldStr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
drawSeparators(separatorStyle)
|
drawSeparators(separatorStyle)
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawPatternView(style = 0) {
|
function drawPatternView(style = timelineRowStyle) {
|
||||||
for (let vr = 0; vr < PTNVIEW_HEIGHT; vr++) drawPatternRowAt(vr, style)
|
for (let vr = 0; vr < PTNVIEW_HEIGHT; vr++) drawPatternRowAt(vr, style)
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawControlHint() {
|
function drawControlHint() {
|
||||||
let hintElemTimeline = [
|
let hintElemTimeline = [
|
||||||
[`\u008428u\u008429u`,'Ptn'],
|
[`\u008428u\u008429u`,'Nav'],
|
||||||
[`Pg\u008418u`,'Cue'],
|
[`Pg\u008418u`,'Cue'],
|
||||||
['sep'],
|
['sep'],
|
||||||
['Y','Song'],
|
['Y','Song'],
|
||||||
@@ -669,7 +711,7 @@ function drawControlHint() {
|
|||||||
//['q','Quit'],
|
//['q','Quit'],
|
||||||
]
|
]
|
||||||
let hintElemOrders = [
|
let hintElemOrders = [
|
||||||
[`\u008428u\u008429u`,'Order'],
|
[`\u008428u\u008429u`,'Nav'],
|
||||||
[`Ent`,'Go to cue'],
|
[`Ent`,'Go to cue'],
|
||||||
['sep'],
|
['sep'],
|
||||||
['U','Cue'],
|
['U','Cue'],
|
||||||
@@ -785,11 +827,18 @@ function drawVoiceDetail(isVerticalLayout = false, ptn = null, activeRow = -1, c
|
|||||||
const dx = PATEDITOR_DETAIL_X
|
const dx = PATEDITOR_DETAIL_X
|
||||||
const detailW = SCRW - dx + 1
|
const detailW = SCRW - dx + 1
|
||||||
|
|
||||||
|
let voleffop1 = (voleffop == 3) ? 30 + (voleffarg >>> 5) : voleffop
|
||||||
|
let paneffop1 = (paneffop == 3) ? 30 + (paneffarg >>> 5) : paneffop
|
||||||
|
let voleffarg1 = '$'+((voleffop == 3) ? voleffarg & 15 : voleffarg).hex02()
|
||||||
|
let paneffarg1 = '$'+((paneffop == 3) ? paneffarg & 15 : paneffarg).hex02()
|
||||||
|
if (voleff == 0xC0) { voleffop1 = 999; voleffarg1 = '' }
|
||||||
|
if (paneff == 0xC0) { paneffop1 = 999; paneffarg1 = '' }
|
||||||
|
|
||||||
const lines = []
|
const lines = []
|
||||||
lines.push({ label: 'Note ', value: `${noteToStr(note)} ($${note.hex04()})`, fg: colNote })
|
lines.push({ label: 'Note ', value: `${noteToStr(note)} ($${note.hex04()})`, fg: colNote })
|
||||||
lines.push({ label: 'Inst ', value: inst === 0 ? '--' : inst.hex02(), fg: colInst })
|
lines.push({ label: 'Inst ', value: inst === 0 ? '--' : inst.hex02(), fg: colInst })
|
||||||
lines.push({ label: 'VolEff', value: `${voleffop}.$${voleffarg.hex02()}`, fg: colVol })
|
lines.push({ label: 'VolEff', value: `${volFxNames[voleffop1]} ${voleffarg1}`, fg: colVol })
|
||||||
lines.push({ label: 'PanEff', value: `${paneffop}.$${paneffarg.hex02()}`, fg: colPan })
|
lines.push({ label: 'PanEff', value: `${panFxNames[paneffop1]} ${paneffarg1}`, fg: colPan })
|
||||||
lines.push({ label: 'FxOp ', value: fx, fg: colEffOp })
|
lines.push({ label: 'FxOp ', value: fx, fg: colEffOp })
|
||||||
lines.push({ label: 'FxArg ', value: `$${effarg.hex04()}`, fg: colEffArg })
|
lines.push({ label: 'FxArg ', value: `$${effarg.hex04()}`, fg: colEffArg })
|
||||||
lines.push({ label: 'Fx ', value: fxName.trimEnd(), fg: colEffOp })
|
lines.push({ label: 'Fx ', value: fxName.trimEnd(), fg: colEffOp })
|
||||||
@@ -818,7 +867,7 @@ function drawVoiceDetail(isVerticalLayout = false, ptn = null, activeRow = -1, c
|
|||||||
const y = PTNVIEW_OFFSET_Y + i
|
const y = PTNVIEW_OFFSET_Y + i
|
||||||
const line = lines[i]
|
const line = lines[i]
|
||||||
con.move(y, dx)
|
con.move(y, dx)
|
||||||
con.color_pair(colVoiceHdr, 255)
|
con.color_pair(colNote, 255)
|
||||||
print((line.label + ' ').substring(0, 6) + ' ')
|
print((line.label + ' ').substring(0, 6) + ' ')
|
||||||
con.color_pair(line.fg, 255)
|
con.color_pair(line.fg, 255)
|
||||||
print((line.value + ' '.repeat(detailW)).substring(0, detailW - 8))
|
print((line.value + ' '.repeat(detailW)).substring(0, detailW - 8))
|
||||||
@@ -859,7 +908,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_TIMELINE_FULL - 1) * COLSIZE_TIMELINE_FULL
|
let 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)
|
||||||
@@ -928,10 +977,25 @@ function drawVoiceColumnAt(slot) {
|
|||||||
ptnIdx !== CUE_EMPTY && ptnIdx < song.numPats) {
|
ptnIdx !== CUE_EMPTY && ptnIdx < song.numPats) {
|
||||||
cell = buildRowCell(song.patterns[ptnIdx], actualRow)
|
cell = buildRowCell(song.patterns[ptnIdx], actualRow)
|
||||||
}
|
}
|
||||||
drawCellAt(y, x, cell, back)
|
drawCellAtStyled(y, x, cell, back, timelineRowStyle)
|
||||||
|
if (timelineRowStyle === 0 && highlight && playbackMode === PLAYMODE_NONE && voice === cursorVox) {
|
||||||
|
const fieldStr = [cell.sNote, cell.sInst, cell.sVolEff+cell.sVolArg,
|
||||||
|
cell.sPanEff+cell.sPanArg, cell.sEffOp, cell.sEffArg][timelineColCursor]
|
||||||
|
con.move(y, x + TL_FIELD_OFFSETS[timelineColCursor])
|
||||||
|
con.color_pair(TL_FIELD_FGS[timelineColCursor], colPlayback)
|
||||||
|
print(fieldStr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setTimelineRowStyle(style) {
|
||||||
|
timelineRowStyle = style
|
||||||
|
COLSIZE_TIMELINE_FULL = TIMELINE_COLSIZES[style]
|
||||||
|
VOCSIZE_TIMELINE_FULL = Math.floor((SCRW - 3) / COLSIZE_TIMELINE_FULL)
|
||||||
|
SALVAGE_HORIZ_LEN = (VOCSIZE_TIMELINE_FULL - 1) * COLSIZE_TIMELINE_FULL
|
||||||
|
clampVoice()
|
||||||
|
drawAll()
|
||||||
|
}
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// APPLICATION STUB
|
// APPLICATION STUB
|
||||||
@@ -945,8 +1009,11 @@ let cursorRow = 0
|
|||||||
let scrollRow = 0
|
let scrollRow = 0
|
||||||
let voiceOff = 0
|
let voiceOff = 0
|
||||||
let cursorVox = 0
|
let cursorVox = 0
|
||||||
|
let timelineColCursor = 0 // sub-field within cursorVox (0=note,1=inst,2=vol,3=pan,4=fxop,5=fxarg)
|
||||||
let ordersCursor = 0
|
let ordersCursor = 0
|
||||||
let ordersScroll = 0
|
let ordersScroll = 0
|
||||||
|
let ordersColCursor = 0 // 0=Cmd, 1..numVoices=voice columns
|
||||||
|
let ordersVoiceOff = 0 // horizontal scroll for voice columns
|
||||||
let patternIdx = 0
|
let patternIdx = 0
|
||||||
let patternListScroll = 0
|
let patternListScroll = 0
|
||||||
let patternGridRow = 0
|
let patternGridRow = 0
|
||||||
@@ -967,7 +1034,6 @@ if (fullPathObj === undefined) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const song = loadTaud(fullPathObj.full, 0)
|
const song = loadTaud(fullPathObj.full, 0)
|
||||||
const TAUD_PREVIEW_PATH = fullPathObj.full + '.preview'
|
|
||||||
|
|
||||||
const voiceMutes = new Array(NUM_VOICES).fill(false)
|
const voiceMutes = new Array(NUM_VOICES).fill(false)
|
||||||
|
|
||||||
@@ -998,12 +1064,14 @@ function drawOrdersHeader() {
|
|||||||
fillLine(PTNVIEW_OFFSET_Y - 1, colVoiceHdr, 255)
|
fillLine(PTNVIEW_OFFSET_Y - 1, colVoiceHdr, 255)
|
||||||
con.move(PTNVIEW_OFFSET_Y - 1, 1)
|
con.move(PTNVIEW_OFFSET_Y - 1, 1)
|
||||||
con.color_pair(colVoiceHdr, 255)
|
con.color_pair(colVoiceHdr, 255)
|
||||||
let hdr = ' '
|
print(' ')
|
||||||
|
con.color_pair(colVoiceHdr, ordersColCursor === 0 ? colHighlight : 255)
|
||||||
|
print('Cmd ')
|
||||||
for (let c = 0; c < VOCSIZE_ORDERS; c++) {
|
for (let c = 0; c < VOCSIZE_ORDERS; c++) {
|
||||||
const v = voiceOff + c
|
const v = ordersVoiceOff + c
|
||||||
hdr += v < song.numVoices ? `V${(v+1).dec02()} ` : ' '
|
con.color_pair(colVoiceHdr, ordersColCursor === v + 1 ? colHighlight : 255)
|
||||||
|
print(v < song.numVoices ? `V${(v+1).dec02()} ` : ' ')
|
||||||
}
|
}
|
||||||
print(hdr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawOrdersContents(wo) {
|
function drawOrdersContents(wo) {
|
||||||
@@ -1024,19 +1092,27 @@ function drawOrdersContents(wo) {
|
|||||||
print(' '.repeat(SCRW - 1))
|
print(' '.repeat(SCRW - 1))
|
||||||
} else {
|
} else {
|
||||||
const cue = song.cues[ci]
|
const cue = song.cues[ci]
|
||||||
const rowstr = ci.hex03()
|
|
||||||
con.color_pair(ci % 4 === 0 ? colRowNumEmph1 : colRowNum, back)
|
con.color_pair(ci % 4 === 0 ? colRowNumEmph1 : colRowNum, back)
|
||||||
con.prnch(rowstr.charCodeAt(0)); con.move(y, 2)
|
print(ci.hex03())
|
||||||
con.prnch(rowstr.charCodeAt(1)); con.move(y, 3)
|
con.color_pair(colBackPtn, back)
|
||||||
con.prnch(rowstr.charCodeAt(2))
|
print(' ')
|
||||||
con.move(y, 5)
|
// CMD column — crosshair highlight at (ordersCursor, col 0)
|
||||||
|
const cmdBack = (isSel && ordersColCursor === 0) ? colPlayback : back
|
||||||
|
con.color_pair(cue.instr ? colNote : colSep, cmdBack)
|
||||||
|
print(cue.instr ? cue.instr.hex02() : '--')
|
||||||
|
con.color_pair(colBackPtn, back)
|
||||||
|
print(' ')
|
||||||
|
// Voice columns
|
||||||
for (let c = 0; c < VOCSIZE_ORDERS; c++) {
|
for (let c = 0; c < VOCSIZE_ORDERS; c++) {
|
||||||
const v = voiceOff + c
|
const v = ordersVoiceOff + c
|
||||||
const ptn = v < song.numVoices ? cue.ptns[v] : CUE_EMPTY
|
const ptn = v < song.numVoices ? cue.ptns[v] : CUE_EMPTY
|
||||||
con.color_pair(ptn === CUE_EMPTY ? colSep : colNote, back)
|
const vBack = (isSel && ordersColCursor === v + 1) ? colPlayback : back
|
||||||
print(ptn === CUE_EMPTY ? '--- ' : ptn.hex03() + ' ')
|
con.color_pair(ptn === CUE_EMPTY ? colSep : colNote, vBack)
|
||||||
|
print(ptn === CUE_EMPTY ? '---' : ptn.hex03())
|
||||||
|
con.color_pair(colBackPtn, back)
|
||||||
|
print(' ')
|
||||||
}
|
}
|
||||||
const endX = 5 + VOCSIZE_ORDERS * 4
|
const endX = ORDERS_VOICE_X + VOCSIZE_ORDERS * 4
|
||||||
if (endX <= SCRW) { con.color_pair(colBackPtn, back); print(' '.repeat(SCRW - endX)) }
|
if (endX <= SCRW) { con.color_pair(colBackPtn, back); print(' '.repeat(SCRW - endX)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1048,11 +1124,17 @@ function timelineInput(wo, event) {
|
|||||||
const shiftDown = (event.includes(59) || event.includes(60))
|
const shiftDown = (event.includes(59) || event.includes(60))
|
||||||
const moveDelta = shiftDown ? 4 : 1
|
const moveDelta = shiftDown ? 4 : 1
|
||||||
|
|
||||||
|
if (keyJustHit && shiftDown && event.includes(keys.W)) { setTimelineRowStyle(0); return }
|
||||||
|
if (keyJustHit && shiftDown && event.includes(keys.E)) { setTimelineRowStyle(1); return }
|
||||||
|
if (keyJustHit && shiftDown && event.includes(keys.R)) { setTimelineRowStyle(2); return }
|
||||||
|
|
||||||
if (playbackMode !== PLAYMODE_NONE) {
|
if (playbackMode !== PLAYMODE_NONE) {
|
||||||
if (keyJustHit && shiftDown && event.includes(keys.Y) || keysym === " ") { stopPlayback(); redrawPanel() }
|
if (keyJustHit && shiftDown && event.includes(keys.Y) || keysym === " ") { stopPlayback(); redrawPanel() }
|
||||||
else if (keysym === "<LEFT>" || keysym === "<RIGHT>") {
|
else if (keysym === "<LEFT>" || keysym === "<RIGHT>") {
|
||||||
|
const dir = (keysym === "<LEFT>") ? -1 : 1
|
||||||
const oldVoiceOff = voiceOff
|
const oldVoiceOff = voiceOff
|
||||||
cursorVox += (keysym === "<LEFT>") ? -moveDelta : moveDelta
|
cursorVox += dir * moveDelta
|
||||||
|
timelineColCursor = 0
|
||||||
clampVoice()
|
clampVoice()
|
||||||
const dVoice = voiceOff - oldVoiceOff
|
const dVoice = voiceOff - oldVoiceOff
|
||||||
if (dVoice !== 0) { shiftPatternAreaHorizontal(dVoice); drawVoiceColumnAt(dVoice > 0 ? VOCSIZE_TIMELINE_FULL - 1 : 0) }
|
if (dVoice !== 0) { shiftPatternAreaHorizontal(dVoice); drawVoiceColumnAt(dVoice > 0 ? VOCSIZE_TIMELINE_FULL - 1 : 0) }
|
||||||
@@ -1074,12 +1156,24 @@ function timelineInput(wo, event) {
|
|||||||
let fullRedraw = false
|
let fullRedraw = false
|
||||||
|
|
||||||
if (keysym === "<LEFT>" || keysym === "<RIGHT>") {
|
if (keysym === "<LEFT>" || keysym === "<RIGHT>") {
|
||||||
|
const dir = (keysym === "<LEFT>") ? -1 : 1
|
||||||
const oldVoiceOff = voiceOff
|
const oldVoiceOff = voiceOff
|
||||||
cursorVox += (keysym === "<LEFT>") ? -moveDelta : moveDelta
|
const prevVox = cursorVox
|
||||||
|
let triedCross = false
|
||||||
|
if (shiftDown) {
|
||||||
|
cursorVox += dir * moveDelta
|
||||||
|
timelineColCursor = dir > 0 ? 0 : 5
|
||||||
|
} else {
|
||||||
|
timelineColCursor += dir
|
||||||
|
if (timelineColCursor < 0) { timelineColCursor = 5; cursorVox--; triedCross = true }
|
||||||
|
else if (timelineColCursor > 5) { timelineColCursor = 0; cursorVox++; triedCross = true }
|
||||||
|
}
|
||||||
clampVoice()
|
clampVoice()
|
||||||
|
if (triedCross && cursorVox === prevVox) timelineColCursor = dir < 0 ? 0 : 5
|
||||||
const dVoice = voiceOff - oldVoiceOff
|
const dVoice = voiceOff - oldVoiceOff
|
||||||
if (dVoice !== 0) { shiftPatternAreaHorizontal(dVoice); drawVoiceColumnAt(dVoice > 0 ? VOCSIZE_TIMELINE_FULL - 1 : 0) }
|
if (dVoice !== 0) { shiftPatternAreaHorizontal(dVoice); drawVoiceColumnAt(dVoice > 0 ? VOCSIZE_TIMELINE_FULL - 1 : 0) }
|
||||||
drawVoiceHeaders(); drawSeparators(separatorStyle); drawAlwaysOnElems(); drawVoiceDetail()
|
drawVoiceHeaders(); drawSeparators(separatorStyle); drawAlwaysOnElems(); drawVoiceDetail()
|
||||||
|
drawPatternRowAt(cursorRow - scrollRow)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1153,10 +1247,9 @@ function ordersInput(wo, event) {
|
|||||||
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)
|
drawOrdersContents(wo)
|
||||||
} else if (keysym === '<LEFT>' || keysym === '<RIGHT>') {
|
} else if (keysym === '<LEFT>' || keysym === '<RIGHT>') {
|
||||||
const oldVoiceOff = voiceOff
|
ordersColCursor += (keysym === '<LEFT>') ? -1 : 1
|
||||||
cursorVox += (keysym === '<LEFT>') ? -moveDelta : moveDelta
|
clampOrdersHoriz()
|
||||||
clampVoice()
|
drawOrdersContents(wo)
|
||||||
if (voiceOff !== oldVoiceOff) drawOrdersContents(wo)
|
|
||||||
} else if (keyJustHit && keysym === '\n') {
|
} else if (keyJustHit && keysym === '\n') {
|
||||||
cueIdx = ordersCursor
|
cueIdx = ordersCursor
|
||||||
clampCue()
|
clampCue()
|
||||||
@@ -1201,17 +1294,21 @@ function clampPatternIdx() {
|
|||||||
patternListScroll = Math.max(0, song.numPats - PTNVIEW_HEIGHT)
|
patternListScroll = Math.max(0, song.numPats - PTNVIEW_HEIGHT)
|
||||||
}
|
}
|
||||||
|
|
||||||
function clampPatternGrid() {
|
function scrollPatternGridTo(row) {
|
||||||
if (patternGridRow < 0) patternGridRow = 0
|
if (row < patternGridScroll) patternGridScroll = row
|
||||||
if (patternGridRow >= ROWS_PER_PAT) patternGridRow = ROWS_PER_PAT - 1
|
if (row < patternGridScroll + (PTNVIEW_HEIGHT >>> 1) && patternGridScroll > 0)
|
||||||
if (patternGridRow < patternGridScroll) patternGridScroll = patternGridRow
|
patternGridScroll = row - (PTNVIEW_HEIGHT >>> 1)
|
||||||
if (patternGridRow < patternGridScroll + (PTNVIEW_HEIGHT >>> 1) && patternGridScroll > 0)
|
if (row >= patternGridScroll + ((PTNVIEW_HEIGHT + 1) >>> 1))
|
||||||
patternGridScroll = patternGridRow - (PTNVIEW_HEIGHT >>> 1)
|
patternGridScroll = row - ((PTNVIEW_HEIGHT + 1) >>> 1) + 1
|
||||||
if (patternGridRow >= patternGridScroll + ((PTNVIEW_HEIGHT + 1) >>> 1))
|
|
||||||
patternGridScroll = patternGridRow - ((PTNVIEW_HEIGHT + 1) >>> 1) + 1
|
|
||||||
if (patternGridScroll < 0) patternGridScroll = 0
|
if (patternGridScroll < 0) patternGridScroll = 0
|
||||||
if (patternGridScroll + PTNVIEW_HEIGHT > ROWS_PER_PAT)
|
if (patternGridScroll + PTNVIEW_HEIGHT > ROWS_PER_PAT)
|
||||||
patternGridScroll = Math.max(0, ROWS_PER_PAT - PTNVIEW_HEIGHT)
|
patternGridScroll = Math.max(0, ROWS_PER_PAT - PTNVIEW_HEIGHT)
|
||||||
|
}
|
||||||
|
|
||||||
|
function clampPatternGrid() {
|
||||||
|
if (patternGridRow < 0) patternGridRow = 0
|
||||||
|
if (patternGridRow >= ROWS_PER_PAT) patternGridRow = ROWS_PER_PAT - 1
|
||||||
|
scrollPatternGridTo(patternGridRow)
|
||||||
if (patternGridCol < 0) patternGridCol = 0
|
if (patternGridCol < 0) patternGridCol = 0
|
||||||
if (patternGridCol > 5) patternGridCol = 5
|
if (patternGridCol > 5) patternGridCol = 5
|
||||||
}
|
}
|
||||||
@@ -1468,13 +1565,50 @@ const PLAYMODE_SONG = 1
|
|||||||
const PLAYMODE_CUE = 2
|
const PLAYMODE_CUE = 2
|
||||||
const PLAYMODE_ROW = 3
|
const PLAYMODE_ROW = 3
|
||||||
|
|
||||||
|
// Scratch cue slot used for pattern-only preview; beyond any real cue the song uses
|
||||||
|
const PREVIEW_CUE_IDX = NUM_CUES - 1
|
||||||
|
|
||||||
let playbackMode = PLAYMODE_NONE
|
let playbackMode = PLAYMODE_NONE
|
||||||
let playStartCue = 0
|
let playStartCue = 0
|
||||||
let playStartRow = 0
|
let playStartRow = 0
|
||||||
let pbCue = 0
|
let pbCue = 0
|
||||||
let pbRow = 0
|
let pbRow = 0
|
||||||
|
let previewActive = false // true while a pattern-only preview is loaded in PREVIEW_CUE_IDX
|
||||||
|
|
||||||
|
// Encode a cue object (from song.cues[]) back to its 32-byte wire format
|
||||||
|
function encodeCue(cue) {
|
||||||
|
const bin = new Uint8Array(CUE_SIZE)
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
const p0 = cue.ptns[i*2], p1 = cue.ptns[i*2+1]
|
||||||
|
bin[i] = ((p0 & 0xF) << 4) | (p1 & 0xF)
|
||||||
|
bin[10+i] = (((p0 >> 4) & 0xF) << 4) | ((p1 >> 4) & 0xF)
|
||||||
|
bin[20+i] = (((p0 >> 8) & 0xF) << 4) | ((p1 >> 8) & 0xF)
|
||||||
|
}
|
||||||
|
bin[30] = cue.instr || 0
|
||||||
|
return bin
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a preview cue with voice 0 = pidx, all other voices = CUE_EMPTY
|
||||||
|
function buildPreviewCue(pidx) {
|
||||||
|
const bin = new Uint8Array(CUE_SIZE)
|
||||||
|
for (let b = 0; b < 30; b++) bin[b] = 0xFF
|
||||||
|
bin[0] = ((pidx & 0xF) << 4) | 0xF
|
||||||
|
bin[10] = (((pidx >> 4) & 0xF) << 4) | 0xF
|
||||||
|
bin[20] = (((pidx >> 8) & 0xF) << 4) | 0xF
|
||||||
|
return bin
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore the scratch cue slot and original BPM/tickRate before full-song playback
|
||||||
|
function restoreFullSongParams() {
|
||||||
|
if (!previewActive) return
|
||||||
|
audio.uploadCue(PREVIEW_CUE_IDX, encodeCue(song.cues[PREVIEW_CUE_IDX]))
|
||||||
|
audio.setBPM(PLAYHEAD, song.bpm)
|
||||||
|
audio.setTickRate(PLAYHEAD, song.tickRate)
|
||||||
|
previewActive = false
|
||||||
|
}
|
||||||
|
|
||||||
function startPlaySong() {
|
function startPlaySong() {
|
||||||
|
restoreFullSongParams()
|
||||||
audio.stop(PLAYHEAD)
|
audio.stop(PLAYHEAD)
|
||||||
audio.setCuePosition(PLAYHEAD, cueIdx)
|
audio.setCuePosition(PLAYHEAD, cueIdx)
|
||||||
audio.setTrackerRow(PLAYHEAD, 0)
|
audio.setTrackerRow(PLAYHEAD, 0)
|
||||||
@@ -1487,6 +1621,7 @@ function startPlaySong() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function startPlayCue() {
|
function startPlayCue() {
|
||||||
|
restoreFullSongParams()
|
||||||
audio.stop(PLAYHEAD)
|
audio.stop(PLAYHEAD)
|
||||||
audio.setCuePosition(PLAYHEAD, cueIdx)
|
audio.setCuePosition(PLAYHEAD, cueIdx)
|
||||||
audio.setTrackerRow(PLAYHEAD, 0)
|
audio.setTrackerRow(PLAYHEAD, 0)
|
||||||
@@ -1500,6 +1635,7 @@ function startPlayCue() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function startPlayRow(fromRow, fromCue) {
|
function startPlayRow(fromRow, fromCue) {
|
||||||
|
restoreFullSongParams()
|
||||||
if (fromRow === undefined) fromRow = cursorRow
|
if (fromRow === undefined) fromRow = cursorRow
|
||||||
if (fromCue === undefined) fromCue = cueIdx
|
if (fromCue === undefined) fromCue = cueIdx
|
||||||
audio.stop(PLAYHEAD)
|
audio.stop(PLAYHEAD)
|
||||||
@@ -1513,42 +1649,49 @@ function startPlayRow(fromRow, fromCue) {
|
|||||||
audio.play(PLAYHEAD)
|
audio.play(PLAYHEAD)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find first cue containing patternIdx; return it or -1 if not used
|
|
||||||
function _findCueForPattern(pidx) {
|
|
||||||
const maxCue = song.lastActiveCue < 0 ? 0 : song.lastActiveCue
|
|
||||||
for (let c = 0; c <= maxCue; c++) {
|
|
||||||
const cue = song.cues[c]
|
|
||||||
for (let v = 0; v < song.numVoices; v++) {
|
|
||||||
if (cue.ptns[v] === pidx) return c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
function startPlayPattern() {
|
function startPlayPattern() {
|
||||||
if (song.numPats === 0) return
|
if (song.numPats === 0) return
|
||||||
const found = _findCueForPattern(patternIdx)
|
audio.stop(PLAYHEAD)
|
||||||
if (found < 0) return
|
audio.setBPM(PLAYHEAD, song.bpm)
|
||||||
cueIdx = found; clampCue()
|
audio.setTickRate(PLAYHEAD, song.tickRate)
|
||||||
startPlayCue()
|
audio.uploadCue(PREVIEW_CUE_IDX, buildPreviewCue(patternIdx))
|
||||||
|
audio.setCuePosition(PLAYHEAD, PREVIEW_CUE_IDX)
|
||||||
|
audio.setTrackerRow(PLAYHEAD, 0)
|
||||||
|
playStartCue = PREVIEW_CUE_IDX
|
||||||
|
pbCue = PREVIEW_CUE_IDX
|
||||||
|
pbRow = 0
|
||||||
|
playbackMode = PLAYMODE_CUE
|
||||||
|
previewActive = true
|
||||||
|
audio.play(PLAYHEAD)
|
||||||
}
|
}
|
||||||
|
|
||||||
function startPlayPatternRow() {
|
function startPlayPatternRow() {
|
||||||
if (song.numPats === 0) return
|
if (song.numPats === 0) return
|
||||||
const found = _findCueForPattern(patternIdx)
|
audio.stop(PLAYHEAD)
|
||||||
if (found < 0) return
|
audio.setBPM(PLAYHEAD, song.bpm)
|
||||||
cueIdx = found; clampCue()
|
audio.setTickRate(PLAYHEAD, song.tickRate)
|
||||||
startPlayRow(patternGridRow, found)
|
audio.uploadCue(PREVIEW_CUE_IDX, buildPreviewCue(patternIdx))
|
||||||
|
audio.setCuePosition(PLAYHEAD, PREVIEW_CUE_IDX)
|
||||||
|
audio.setTrackerRow(PLAYHEAD, patternGridRow)
|
||||||
|
playStartCue = PREVIEW_CUE_IDX
|
||||||
|
playStartRow = patternGridRow
|
||||||
|
pbCue = PREVIEW_CUE_IDX
|
||||||
|
pbRow = patternGridRow
|
||||||
|
playbackMode = PLAYMODE_ROW
|
||||||
|
previewActive = true
|
||||||
|
audio.play(PLAYHEAD)
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopPlayback() {
|
function stopPlayback() {
|
||||||
audio.stop(PLAYHEAD)
|
audio.stop(PLAYHEAD)
|
||||||
playbackMode = PLAYMODE_NONE
|
playbackMode = PLAYMODE_NONE
|
||||||
|
clampPatternGrid()
|
||||||
}
|
}
|
||||||
|
|
||||||
function updatePlayback() {
|
function updatePlayback() {
|
||||||
if (!audio.isPlaying(PLAYHEAD)) {
|
if (!audio.isPlaying(PLAYHEAD)) {
|
||||||
playbackMode = PLAYMODE_NONE
|
playbackMode = PLAYMODE_NONE
|
||||||
|
clampPatternGrid()
|
||||||
if (currentPanel === VIEW_TIMELINE &&
|
if (currentPanel === VIEW_TIMELINE &&
|
||||||
cursorRow >= scrollRow && cursorRow < scrollRow + PTNVIEW_HEIGHT)
|
cursorRow >= scrollRow && cursorRow < scrollRow + PTNVIEW_HEIGHT)
|
||||||
drawPatternRowAt(cursorRow - scrollRow)
|
drawPatternRowAt(cursorRow - scrollRow)
|
||||||
@@ -1581,13 +1724,13 @@ function updatePlayback() {
|
|||||||
pbCue = nowCue
|
pbCue = nowCue
|
||||||
pbRow = nowRow
|
pbRow = nowRow
|
||||||
|
|
||||||
if (nowCue !== cueIdx) {
|
if (!previewActive && nowCue !== cueIdx) {
|
||||||
cueIdx = nowCue
|
cueIdx = nowCue
|
||||||
cursorRow = nowRow
|
cursorRow = nowRow
|
||||||
clampCursor()
|
clampCursor()
|
||||||
if (currentPanel === VIEW_TIMELINE) redrawPanel()
|
if (currentPanel === VIEW_TIMELINE) redrawPanel()
|
||||||
else if (currentPanel === 2 && song.numPats > 0) { simStateKey = ''; redrawPanel() }
|
else if (currentPanel === 2 && song.numPats > 0) { simStateKey = ''; redrawPanel() }
|
||||||
} else {
|
} else if (previewActive || nowCue === cueIdx) {
|
||||||
const oldCursor = cursorRow
|
const oldCursor = cursorRow
|
||||||
const oldScroll = scrollRow
|
const oldScroll = scrollRow
|
||||||
cursorRow = nowRow
|
cursorRow = nowRow
|
||||||
@@ -1617,6 +1760,7 @@ function updatePlayback() {
|
|||||||
const activeRow = getActiveRowForDetail()
|
const activeRow = getActiveRowForDetail()
|
||||||
simState = simulateRowState(song.patterns[patternIdx], activeRow)
|
simState = simulateRowState(song.patterns[patternIdx], activeRow)
|
||||||
simStateKey = `${patternIdx}:${activeRow}:${playbackMode}`
|
simStateKey = `${patternIdx}:${activeRow}:${playbackMode}`
|
||||||
|
scrollPatternGridTo(pbRow)
|
||||||
drawPatternGrid()
|
drawPatternGrid()
|
||||||
drawVoiceDetail(true, song.patterns[patternIdx], activeRow, simState)
|
drawVoiceDetail(true, song.patterns[patternIdx], activeRow, simState)
|
||||||
}
|
}
|
||||||
@@ -1654,7 +1798,18 @@ function clampCue() {
|
|||||||
if (cueIdx > maxCue) cueIdx = maxCue
|
if (cueIdx > maxCue) cueIdx = maxCue
|
||||||
}
|
}
|
||||||
|
|
||||||
clampCursor(); clampVoice(); clampCue(); clampPatternIdx(); clampPatternGrid()
|
function clampOrdersHoriz() {
|
||||||
|
if (ordersColCursor < 0) ordersColCursor = 0
|
||||||
|
if (ordersColCursor > song.numVoices) ordersColCursor = song.numVoices
|
||||||
|
if (ordersColCursor >= 1) {
|
||||||
|
const v = ordersColCursor - 1
|
||||||
|
if (v < ordersVoiceOff) ordersVoiceOff = v
|
||||||
|
if (v >= ordersVoiceOff + VOCSIZE_ORDERS) ordersVoiceOff = v - VOCSIZE_ORDERS + 1
|
||||||
|
if (ordersVoiceOff < 0) ordersVoiceOff = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clampCursor(); clampVoice(); clampCue(); clampOrdersHoriz(); clampPatternIdx(); clampPatternGrid()
|
||||||
drawAll()
|
drawAll()
|
||||||
|
|
||||||
resetAudioDevice()
|
resetAudioDevice()
|
||||||
@@ -1668,6 +1823,7 @@ while (!exitFlag) {
|
|||||||
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))
|
||||||
|
|
||||||
if (keysym === "<ESC>" || keysym === "q" || keysym === "Q") {
|
if (keysym === "<ESC>" || keysym === "q" || keysym === "Q") {
|
||||||
exitFlag = true
|
exitFlag = true
|
||||||
@@ -1675,7 +1831,10 @@ while (!exitFlag) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (keyJustHit && keysym === "<TAB>") {
|
if (keyJustHit && keysym === "<TAB>") {
|
||||||
currentPanel = (currentPanel + 1) % panels.length
|
currentPanel = (currentPanel + (shiftDown ? -1 : 1))
|
||||||
|
if (currentPanel < 0) currentPanel += panels.length
|
||||||
|
currentPanel = currentPanel % panels.length
|
||||||
|
|
||||||
drawAll()
|
drawAll()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -479,21 +479,21 @@ con.getmaxyx = function() {
|
|||||||
con.getyx = function() {
|
con.getyx = function() {
|
||||||
return graphics.getCursorYX();
|
return graphics.getCursorYX();
|
||||||
};
|
};
|
||||||
con.curs_up = function() {
|
con.curs_up = function(n = 1) {
|
||||||
let [y,x] = con.getyx();
|
let [y,x] = con.getyx();
|
||||||
con.move(y-1,x);
|
con.move(y-n,x);
|
||||||
};
|
};
|
||||||
con.curs_down = function() {
|
con.curs_down = function(n = 1) {
|
||||||
let [y,x] = con.getyx();
|
let [y,x] = con.getyx();
|
||||||
con.move(y+1,x);
|
con.move(y+n,x);
|
||||||
};
|
};
|
||||||
con.curs_left = function() {
|
con.curs_left = function(n = 1) {
|
||||||
let [y,x] = con.getyx();
|
let [y,x] = con.getyx();
|
||||||
con.move(y,x-1);
|
con.move(y,x-n);
|
||||||
};
|
};
|
||||||
con.curs_right = function() {
|
con.curs_right = function(n = 1) {
|
||||||
let [y,x] = con.getyx();
|
let [y,x] = con.getyx();
|
||||||
con.move(y,x+1);
|
con.move(y,x+n);
|
||||||
};
|
};
|
||||||
con.hitterminate = function() { // ^C
|
con.hitterminate = function() { // ^C
|
||||||
sys.poke(-40, 1);
|
sys.poke(-40, 1);
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user