mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-06 05:28:31 +09:00
taut: help message panel
This commit is contained in:
@@ -146,11 +146,11 @@ Z:"UNIMPLEMENTED", // IT: MIDI macro
|
|||||||
}
|
}
|
||||||
const panFxNames = {
|
const panFxNames = {
|
||||||
0:"Set to",
|
0:"Set to",
|
||||||
1:"Slide L",
|
1:"Slide R",
|
||||||
2:"Slide R",
|
2:"Slide L",
|
||||||
3:"Fine slide",
|
3:"Fine slide",
|
||||||
30:"Fine slide L",
|
30:"Fine slide R",
|
||||||
31:"Fine slide R",
|
31:"Fine slide L",
|
||||||
999:"---",
|
999:"---",
|
||||||
}
|
}
|
||||||
const volFxNames = {
|
const volFxNames = {
|
||||||
@@ -209,7 +209,7 @@ sym:[` \u00E0\u00E1`,` \u00E2\u00E3`,` \u00E4\u00E5`,` \u00E6\u00E7`,` \u00E8\u0
|
|||||||
|
|
||||||
|
|
||||||
const volEffSym = [sym.volset, sym.volup, sym.voldn, sym.volfineup, sym.volfinedn]
|
const volEffSym = [sym.volset, sym.volup, sym.voldn, sym.volfineup, sym.volfinedn]
|
||||||
const panEffSym = [sym.panset, sym.panle, sym.panri, sym.panfinele, sym.panfineri]
|
const panEffSym = [sym.panset, sym.panri, sym.panle, sym.panfineri, sym.panfinele]
|
||||||
|
|
||||||
const colNote = 239
|
const colNote = 239
|
||||||
const colInst = 114
|
const colInst = 114
|
||||||
@@ -521,7 +521,7 @@ function loadTaud(filePath, songIndex) {
|
|||||||
ptns[i*2] = ((hi >> 4) << 8) | ((mi >> 4) << 4) | (lo >> 4)
|
ptns[i*2] = ((hi >> 4) << 8) | ((mi >> 4) << 4) | (lo >> 4)
|
||||||
ptns[i*2+1] = ((hi & 0xF) << 8) | ((mi & 0xF) << 4) | (lo & 0xF)
|
ptns[i*2+1] = ((hi & 0xF) << 8) | ((mi & 0xF) << 4) | (lo & 0xF)
|
||||||
}
|
}
|
||||||
const instr = sys.peek(cueSheetPtr + c * CUE_SIZE + 30) & 0xFF
|
const instr = (sys.peek(cueSheetPtr + c * CUE_SIZE + 30) << 8) | sys.peek(cueSheetPtr + c * CUE_SIZE + 31)
|
||||||
cues[c] = { ptns, instr }
|
cues[c] = { ptns, instr }
|
||||||
|
|
||||||
for (let v = 0; v < NUM_VOICES; v++) {
|
for (let v = 0; v < NUM_VOICES; v++) {
|
||||||
@@ -557,7 +557,7 @@ let VOCSIZE_TIMELINE_FULL = Math.floor((SCRW - 3) / COLSIZE_TIMELINE_FULL)
|
|||||||
|
|
||||||
const ORDERS_CMD_X = 5
|
const ORDERS_CMD_X = 5
|
||||||
const ORDERS_VOICE_X = 9
|
const ORDERS_VOICE_X = 9
|
||||||
const VOCSIZE_ORDERS = Math.floor((SCRW - 8) / 4)
|
const VOCSIZE_ORDERS = Math.floor((SCRW - 10) / 4)
|
||||||
|
|
||||||
const VIEW_TIMELINE = 0
|
const VIEW_TIMELINE = 0
|
||||||
const VIEW_CUES = 1
|
const VIEW_CUES = 1
|
||||||
@@ -863,7 +863,9 @@ function drawControlHint() {
|
|||||||
['n','Solo'],
|
['n','Solo'],
|
||||||
['m','Mute'],
|
['m','Mute'],
|
||||||
['sep'],
|
['sep'],
|
||||||
['tab','Panel']
|
['tab','Panel'],
|
||||||
|
['sep'],
|
||||||
|
['!','Help'],
|
||||||
// ['sep'],
|
// ['sep'],
|
||||||
// ['q','Quit'],
|
// ['q','Quit'],
|
||||||
]
|
]
|
||||||
@@ -874,6 +876,8 @@ function drawControlHint() {
|
|||||||
['sp','Edit'],
|
['sp','Edit'],
|
||||||
['sep'],
|
['sep'],
|
||||||
['tab','Panel'],
|
['tab','Panel'],
|
||||||
|
['sep'],
|
||||||
|
['!','Help'],
|
||||||
// ['sep'],
|
// ['sep'],
|
||||||
// ['q','Quit'],
|
// ['q','Quit'],
|
||||||
]
|
]
|
||||||
@@ -885,6 +889,8 @@ function drawControlHint() {
|
|||||||
['sp','Edit'],
|
['sp','Edit'],
|
||||||
['sep'],
|
['sep'],
|
||||||
['tab','Panel'],
|
['tab','Panel'],
|
||||||
|
['sep'],
|
||||||
|
['!','Help'],
|
||||||
// ['sep'],
|
// ['sep'],
|
||||||
// ['q','Quit'],
|
// ['q','Quit'],
|
||||||
]
|
]
|
||||||
@@ -902,6 +908,8 @@ function drawControlHint() {
|
|||||||
['sep'],
|
['sep'],
|
||||||
['=','KOff'],
|
['=','KOff'],
|
||||||
['^','KCut'],
|
['^','KCut'],
|
||||||
|
['sep'],
|
||||||
|
['!','Help'],
|
||||||
// ['sep'],
|
// ['sep'],
|
||||||
// ['Sp','ExitEdit'],
|
// ['Sp','ExitEdit'],
|
||||||
]
|
]
|
||||||
@@ -911,18 +919,22 @@ function drawControlHint() {
|
|||||||
['sep'],
|
['sep'],
|
||||||
[`0${sym.doubledot}9 A${sym.doubledot}F`,'Instrument'],
|
[`0${sym.doubledot}9 A${sym.doubledot}F`,'Instrument'],
|
||||||
['sep'],
|
['sep'],
|
||||||
['sp','ExitEdit'],
|
['!','Help'],
|
||||||
|
// ['sep'],
|
||||||
|
// ['sp','ExitEdit'],
|
||||||
]
|
]
|
||||||
let hintElemEditVolEff = [
|
let hintElemEditVolEff = [
|
||||||
[`\u008428u\u008429u`,'Nav'],
|
[`\u008428u\u008429u`,'Nav'],
|
||||||
[`pg\u008418u`,'Cue'],
|
[`pg\u008418u`,'Cue'],
|
||||||
['sep'],
|
['sep'],
|
||||||
['h','Set'],
|
['.','Set'],
|
||||||
['j','SlideDn'],
|
['v','SlideUp'],
|
||||||
['k','SlideUp'],
|
['^','SlideDn'],
|
||||||
['u','FineDn'],
|
['-','FineDn'],
|
||||||
['i','FineUp'],
|
['=','FineUp'],
|
||||||
[`0${sym.doubledot}9 A${sym.doubledot}F`,'Val'],
|
[`0${sym.doubledot}9 A${sym.doubledot}F`,'Val'],
|
||||||
|
['sep'],
|
||||||
|
['!','Help'],
|
||||||
// ['sep'],
|
// ['sep'],
|
||||||
// ['Sp','ExitEdit'],
|
// ['Sp','ExitEdit'],
|
||||||
]
|
]
|
||||||
@@ -930,11 +942,11 @@ function drawControlHint() {
|
|||||||
[`\u008428u\u008429u`,'Nav'],
|
[`\u008428u\u008429u`,'Nav'],
|
||||||
[`pg\u008418u`,'Cue'],
|
[`pg\u008418u`,'Cue'],
|
||||||
['sep'],
|
['sep'],
|
||||||
['h','Set'],
|
['.','Set'],
|
||||||
['j','SlideL'],
|
['<','SlideL'],
|
||||||
['k','SlideR'],
|
['>','SlideR'],
|
||||||
['u','FineL'],
|
['-','FineL'],
|
||||||
['i','FineR'],
|
['=','FineR'],
|
||||||
[`0${sym.doubledot}9 A${sym.doubledot}F`,'Val'],
|
[`0${sym.doubledot}9 A${sym.doubledot}F`,'Val'],
|
||||||
// ['sep'],
|
// ['sep'],
|
||||||
// ['Sp','ExitEdit'],
|
// ['Sp','ExitEdit'],
|
||||||
@@ -945,7 +957,9 @@ function drawControlHint() {
|
|||||||
['sep'],
|
['sep'],
|
||||||
[`0${sym.doubledot}9 A${sym.doubledot}F`,`FxSym`],
|
[`0${sym.doubledot}9 A${sym.doubledot}F`,`FxSym`],
|
||||||
['sep'],
|
['sep'],
|
||||||
['sp','ExitEdit'],
|
['!','Help'],
|
||||||
|
// ['sep'],
|
||||||
|
// ['sp','ExitEdit'],
|
||||||
]
|
]
|
||||||
let hintElemEditFxVal = [
|
let hintElemEditFxVal = [
|
||||||
[`\u008428u\u008429u`,'Nav'],
|
[`\u008428u\u008429u`,'Nav'],
|
||||||
@@ -953,10 +967,12 @@ function drawControlHint() {
|
|||||||
['sep'],
|
['sep'],
|
||||||
[`0${sym.doubledot}9 A${sym.doubledot}F`,`FxVal`],
|
[`0${sym.doubledot}9 A${sym.doubledot}F`,`FxVal`],
|
||||||
['sep'],
|
['sep'],
|
||||||
['sp','ExitEdit'],
|
['!','Help'],
|
||||||
|
// ['sep'],
|
||||||
|
// ['sp','ExitEdit'],
|
||||||
]
|
]
|
||||||
|
|
||||||
const hintElemExternal = [['Tab','Panel']]
|
const hintElemExternal = [['Tab','Panel'],['sep'],['!','Help']]
|
||||||
let hintElems = [hintElemTimeline, hintElemOrders, hintElemPatterns, hintElemExternal, hintElemExternal, hintElemExternal, hintElemExternal]
|
let hintElems = [hintElemTimeline, hintElemOrders, hintElemPatterns, hintElemExternal, hintElemExternal, hintElemExternal, hintElemExternal]
|
||||||
let hintElemPat = [hintElemEditNoteValue, hintElemEditInstValue, hintElemEditVolEff, hintElemEditPanEff, hintElemEditFxSym, hintElemEditFxVal]
|
let hintElemPat = [hintElemEditNoteValue, hintElemEditInstValue, hintElemEditVolEff, hintElemEditPanEff, hintElemEditFxSym, hintElemEditFxVal]
|
||||||
|
|
||||||
@@ -1383,7 +1399,7 @@ function drawOrdersHeader() {
|
|||||||
con.color_pair(colVoiceHdr, 255)
|
con.color_pair(colVoiceHdr, 255)
|
||||||
print(' ')
|
print(' ')
|
||||||
con.color_pair(colVoiceHdr, ordersColCursor === 0 ? colHighlight : 255)
|
con.color_pair(colVoiceHdr, ordersColCursor === 0 ? colHighlight : 255)
|
||||||
print('Cmd ')
|
print('Comand ')
|
||||||
for (let c = 0; c < VOCSIZE_ORDERS; c++) {
|
for (let c = 0; c < VOCSIZE_ORDERS; c++) {
|
||||||
const v = ordersVoiceOff + c
|
const v = ordersVoiceOff + c
|
||||||
con.color_pair(colVoiceHdr, ordersColCursor === v + 1 ? colHighlight : 255)
|
con.color_pair(colVoiceHdr, ordersColCursor === v + 1 ? colHighlight : 255)
|
||||||
@@ -1416,9 +1432,9 @@ function drawOrdersContents(wo) {
|
|||||||
// CMD column — crosshair highlight at (ordersCursor, col 0)
|
// CMD column — crosshair highlight at (ordersCursor, col 0)
|
||||||
const cmdBack = (isSel && ordersColCursor === 0) ? colPlayback : back
|
const cmdBack = (isSel && ordersColCursor === 0) ? colPlayback : back
|
||||||
con.color_pair(cue.instr ? colStatus : colSep, cmdBack)
|
con.color_pair(cue.instr ? colStatus : colSep, cmdBack)
|
||||||
print(cue.instr ? cue.instr.hex02() : '--')
|
print(cue.instr ? cueInstToStr(cue.instr) : '------')
|
||||||
con.color_pair(colBackPtn, back)
|
con.color_pair(colBackPtn, back)
|
||||||
print(' ')
|
print(' ')
|
||||||
// Voice columns
|
// Voice columns
|
||||||
for (let c = 0; c < VOCSIZE_ORDERS; c++) {
|
for (let c = 0; c < VOCSIZE_ORDERS; c++) {
|
||||||
const v = ordersVoiceOff + c
|
const v = ordersVoiceOff + c
|
||||||
@@ -1435,6 +1451,35 @@ function drawOrdersContents(wo) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cueInstToStr(inst) {
|
||||||
|
let foreword = (inst >>> 12) & 15
|
||||||
|
let preamble = (inst >>> 8) & 15
|
||||||
|
let arg12 = inst & 0xFFF
|
||||||
|
let arg8 = inst & 0xFF
|
||||||
|
let fallback = `?${inst.hex04()}?`
|
||||||
|
switch (foreword) {
|
||||||
|
case 0b1000:
|
||||||
|
return "BAK" + arg12.hex03()
|
||||||
|
case 0b1001:
|
||||||
|
return "FWD" + arg12.hex03()
|
||||||
|
case 0b1111:
|
||||||
|
return "JMP" + arg12.hex03()
|
||||||
|
case 0b0000:
|
||||||
|
switch (preamble) {
|
||||||
|
case 0b0010:
|
||||||
|
return "LEN " + arg8.dec02()
|
||||||
|
case 0b0001:
|
||||||
|
return arg8 ? ("FADE" + arg8.dec02()) : "HALT "
|
||||||
|
case 0b0000:
|
||||||
|
return "NO-OP "
|
||||||
|
default:
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function timelineInput(wo, event) {
|
function timelineInput(wo, event) {
|
||||||
const keysym = event[1]
|
const keysym = event[1]
|
||||||
const keyJustHit = (1 == event[2])
|
const keyJustHit = (1 == event[2])
|
||||||
@@ -2404,6 +2449,93 @@ function clampOrdersHoriz() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// HELP POPUP
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
const HELP_POPUP_W = SCRW - 8
|
||||||
|
const HELP_POPUP_X = ((SCRW - HELP_POPUP_W) / 2 | 0) + 1
|
||||||
|
const HELP_POPUP_Y = 5
|
||||||
|
const HELP_POPUP_H = SCRH - HELP_POPUP_Y - 1
|
||||||
|
const HELP_CONTENT_X = HELP_POPUP_X + 2
|
||||||
|
const HELP_CONTENT_Y = HELP_POPUP_Y + 2
|
||||||
|
const HELP_CONTENT_W = HELP_POPUP_W - 6
|
||||||
|
const HELP_CONTENT_H = HELP_POPUP_H - 3
|
||||||
|
|
||||||
|
// Pre-typeset every panel's help text. taut_helpmsg.js reads HELPMSG_WIDTH for
|
||||||
|
// the wrap width and stores ready-to-print display strings into MSG_BY_TABS.
|
||||||
|
_G.TAUT.HELPMSG_WIDTH = HELP_CONTENT_W
|
||||||
|
_G.shell.execute("taut_helpmsg")
|
||||||
|
|
||||||
|
function openHelpPopup() {
|
||||||
|
const helpmsg = _G.TAUT.HELPMSG || {}
|
||||||
|
const lines = (helpmsg.MSG_BY_TABS && helpmsg.MSG_BY_TABS[currentPanel]) || ['']
|
||||||
|
const colText = helpmsg.COL_TEXT || colWHITE
|
||||||
|
|
||||||
|
const popup = new win.WindowObject(
|
||||||
|
HELP_POPUP_X, HELP_POPUP_Y, HELP_POPUP_W, HELP_POPUP_H,
|
||||||
|
()=>{}, ()=>{}, `Help: ${PANEL_NAMES[currentPanel]}`, popupDrawFrame
|
||||||
|
)
|
||||||
|
popup.isHighlighted = true
|
||||||
|
popup.titleBack = colPopupBack
|
||||||
|
|
||||||
|
let scroll = 0
|
||||||
|
const maxScroll = Math.max(0, lines.length - HELP_CONTENT_H)
|
||||||
|
|
||||||
|
const repaint = () => {
|
||||||
|
con.color_pair(230, colPopupBack)
|
||||||
|
popup.drawFrame()
|
||||||
|
|
||||||
|
// popupDrawFrame leaves the bottom row unpainted; fill it ourselves.
|
||||||
|
con.color_pair(colText, colPopupBack)
|
||||||
|
con.move(HELP_POPUP_Y + HELP_POPUP_H - 1, HELP_POPUP_X)
|
||||||
|
print(' '.repeat(HELP_POPUP_W))
|
||||||
|
|
||||||
|
for (let r = 0; r < HELP_CONTENT_H; r++) {
|
||||||
|
con.move(HELP_CONTENT_Y + r, HELP_CONTENT_X)
|
||||||
|
con.color_pair(colText, colPopupBack)
|
||||||
|
const line = lines[scroll + r]
|
||||||
|
print((line === undefined) ? ' '.repeat(HELP_CONTENT_W) : line)
|
||||||
|
}
|
||||||
|
|
||||||
|
// scroll indicator on the right inner edge
|
||||||
|
if (lines.length > HELP_CONTENT_H) {
|
||||||
|
const trackH = HELP_CONTENT_H
|
||||||
|
const indPos = (maxScroll === 0) ? 0 : ((scroll * (trackH - 1) / maxScroll) | 0)
|
||||||
|
for (let r = 0; r < trackH; r++) {
|
||||||
|
con.move(HELP_CONTENT_Y + r, HELP_POPUP_X + HELP_POPUP_W - 2)
|
||||||
|
con.color_pair(colPushBtnBack, colPopupBack)
|
||||||
|
print(r === indPos ? '\u00DB' : '\u00B3')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
con.color_pair(colStatus, 255)
|
||||||
|
}
|
||||||
|
|
||||||
|
repaint()
|
||||||
|
|
||||||
|
let done = false
|
||||||
|
let eventJustReceived = true
|
||||||
|
while (!done) {
|
||||||
|
input.withEvent(ev => {
|
||||||
|
if (ev[0] !== 'key_down') return
|
||||||
|
//if (1 !== ev[2]) return // allow continuous scroll by key repeat
|
||||||
|
if (eventJustReceived) { eventJustReceived = false; return }
|
||||||
|
const ks = ev[1]
|
||||||
|
|
||||||
|
if (ks === '<ESC>' || ks === '!' || ks === 'q' || ks === '\n') { done = true }
|
||||||
|
else if (ks === '<UP>') { if (scroll > 0) { scroll -= 1; repaint() } }
|
||||||
|
else if (ks === '<DOWN>') { if (scroll < maxScroll) { scroll += 1; repaint() } }
|
||||||
|
else if (ks === '<PAGE_UP>') { scroll = Math.max(0, scroll - HELP_CONTENT_H); repaint() }
|
||||||
|
else if (ks === '<PAGE_DOWN>') { scroll = Math.min(maxScroll, scroll + HELP_CONTENT_H); repaint() }
|
||||||
|
else if (ks === '<HOME>') { scroll = 0; repaint() }
|
||||||
|
else if (ks === '<END>') { scroll = maxScroll; repaint() }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
drawAll()
|
||||||
|
}
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// GOTO POPUP
|
// GOTO POPUP
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -2613,6 +2745,11 @@ while (!exitFlag) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (keyJustHit && keysym === '!') {
|
||||||
|
openHelpPopup()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
panels[currentPanel].processInput(event)
|
panels[currentPanel].processInput(event)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,11 @@ Tags:
|
|||||||
<c> - centre the line. If the line spans multiple lines, centre each line
|
<c> - centre the line. If the line spans multiple lines, centre each line
|
||||||
<r> - align right
|
<r> - align right
|
||||||
<l> - align left
|
<l> - align left
|
||||||
µtone; - replace with the brand string
|
<o> - create virtual typesetting box. Left anchor: where the text cursor is. Right anchor: end of the line
|
||||||
&bul; - replace with bullet (\u00847u)
|
µtone; - replace with the brand string (<col 211>Micro</col><col 239>tone</col>)
|
||||||
|
&bul; - replace with bullet (\u00F9)
|
||||||
&ddot; - replace with double-dot (\u008419u)
|
&ddot; - replace with double-dot (\u008419u)
|
||||||
&mdot; - replace with BIGDOT (\u00F9)
|
&mdot; - replace with BIGDOT (\u00FA)
|
||||||
&updn; - up-down arrow (\u008418u)
|
&updn; - up-down arrow (\u008418u)
|
||||||
&udlr; - four direction arrow (\u008428u\u008429u)
|
&udlr; - four direction arrow (\u008428u\u008429u)
|
||||||
&keyoffsym; - pattern view key-off symbol (\u00A0\u00CD\u00CD\u00A1)
|
&keyoffsym; - pattern view key-off symbol (\u00A0\u00CD\u00CD\u00A1)
|
||||||
@@ -22,63 +23,319 @@ Tags:
|
|||||||
default alignment: fully justified
|
default alignment: fully justified
|
||||||
*/
|
*/
|
||||||
|
|
||||||
help.notation = `<c>CONTROL NOTATON</c>
|
let helpNotation = `<c>CONTROL NOTATON</c>
|
||||||
|
|
||||||
µtone; shortcuts differentiate normal and shifted shortcuts.
|
µtone; <O>shortcuts differentiate normal and shifted shortcuts.</O>
|
||||||
&bul;a&ddot;z : alphabet without shift-in
|
&bul;<b>a</b>&ddot;<b>z</b> : <O>alphabet without shift-in</O>
|
||||||
&bul;A&ddot;Z : alphabet with shift-in
|
&bul;<b>A</b>&ddot;<b>Z</b> : <O>alphabet with shift-in</O>
|
||||||
&bul;^ : control key`
|
&bul;<b>^q</b> : <O>hit 'q' with control key</O>
|
||||||
|
&bul;<b>^Q</b> : <O>hit 'q' with control and shift key</O>`
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
help.jam = `<c>NOTE JAMMING</c>
|
let helpJam = `<c>NOTE JAMMING</c>
|
||||||
|
|
||||||
Push keys to play or insert notes.
|
Push keys to play or insert notes.
|
||||||
w e t y u i
|
w e t y u
|
||||||
a s d f g h j k`
|
a s d f g h j k`
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
help.common = `<c>COMMON CONTROLS</c>
|
let helpCommon = `<c>COMMON CONTROLS</c>
|
||||||
|
|
||||||
&bul;Y : play the entire song from the current cue
|
&bul;<b>!</b> : <O>show this help message</O>
|
||||||
&bul;U : play the current cue then stop
|
&bul;<b>Y</b> : <O>play the entire song from the current cue</O>
|
||||||
&bul;I : play the current row
|
&bul;<b>U</b> : <O>play the current cue then stop</O>
|
||||||
&bul;O : stop the playback
|
&bul;<b>I</b> : <O>play the current row</O>
|
||||||
&bul;tab : switch forward a tab
|
&bul;<b>O</b> : <O>stop the playback</O>
|
||||||
&bul;TAB : switch backward a tab
|
&bul;<b>tab</b> : <O>switch forward a tab</O>
|
||||||
&bul;q : close µtone;`
|
&bul;<b>TAB</b> : <O>switch backward a tab</O>
|
||||||
|
&bul;<b>q</b> : <O>close µtone;</O>`
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
help.timeline = `<c>TIMELINE VIEW</c>
|
let helpTimeline = `<c>TIMELINE VIEW</c>
|
||||||
|
|
||||||
Timeline has two distinct modes: view and edit mode. Two modes are toggled using the space bar.
|
Timeline has two distinct modes: view and edit mode. Two modes are toggled using the space bar.
|
||||||
|
|
||||||
<b>View mode</b>
|
<b>VIEW MODE</b>
|
||||||
&bul;Note jamming : plays the note
|
&bul;Note jamming : <O>plays the note</O>
|
||||||
&bul;&udlr; : move the viewing cursor by voices and rows
|
&bul;<b>&udlr;</b> : <O>move the viewing cursor by voices and rows</O>
|
||||||
&bul;pg&updn; : go to previous/next cue
|
&bul;<b>pg&updn;</b> : <O>go to previous/next cue</O>
|
||||||
&bul;W&mdot;E&mdot;R : toggle timeline view mode. W-most detailed, R-most abridged
|
&bul;<b>W</b>&mdot;<b>E</b>&mdot;<b>R</b> : <O>toggle timeline view mode. W-most detailed, R-most abridged</O>
|
||||||
&bul;n : toggle soloing of the selected voice
|
&bul;<b>n</b> : <O>toggle soloing of the selected voice</O>
|
||||||
&bul;m : toggle muting of the selected voice
|
&bul;<b>m</b> : <O>toggle muting of the selected voice</O>
|
||||||
|
|
||||||
<b>Edit mode</b>
|
<b>EDIT MODE</b>
|
||||||
&bul;Note jammping : (note column) inserts the note
|
&bul;Note jamming : <O>(note column) inserts the note</O>
|
||||||
&bul;{&mdot;} : (note column) lower/raise a note by one octave (or period)
|
&bul;<b>{</b>&mdot;<b>}</b> : <O>(note column) lower/raise a note by one octave (or period)</O>
|
||||||
&bul;[&mdot;] : (note column) lower/raise a note by one unit
|
&bul;<b>[</b>&mdot;<b>]</b> : <O>(note column) lower/raise a note by one unit</O>
|
||||||
&bul;= : (note column) insert a key-off &keyoffsym;
|
&bul;<b>=</b> : <O>(note column) insert a key-off &keyoffsym;</O>
|
||||||
&bul;^ : (note column) insert a note-cut ¬ecutsym;
|
&bul;<b>^</b> : <O>(note column) insert a note-cut ¬ecutsym;</O>
|
||||||
&bul;. : remove a symbol on the selected column
|
&bul;<b>.</b> : <O>remove a symbol on the selected column</O>
|
||||||
&bul;bksp : delete one character on the selected column
|
&bul;<b>bksp</b> : <O>delete one character on the selected column</O>
|
||||||
&bul;0&ddot;9 a&ddot;f : inserts a (hexa)decimal number
|
&bul;<b>0</b>&ddot;<b>9</b> <b>a</b>&ddot;<b>f</b> : <O>inserts a (hexa)decimal number</O>
|
||||||
&bul;^&mdot;v : (volume column) slide up/down
|
&bul;<b>^</b>&mdot;<b>v</b> : <O>(volume column) slide up/down</O>
|
||||||
&bul;<&mdot;> : (panning column) slide left/right
|
&bul;<b><</b>&mdot;<b>></b>: <O>(panning column) slide left/right</O>
|
||||||
&bul;-&mdot;= : (vol/pan col) fine slide down/up
|
&bul;<b>-</b>&mdot;<b>=</b> : <O>(vol/pan col) fine slide down/up</O>
|
||||||
&bul;&udlr; : move the viewing cursor by columns and rows
|
&bul;<b>&udlr;</b> : <O>move the viewing cursor by columns and rows</O>
|
||||||
&bul;pg&updn; : go to previous/next cue`
|
&bul;<b>pg&updn;</b> : <O>go to previous/next cue</O>`
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
if (!_G.TAUT.HELPMSG) _G.TAUT.HELPMSG=help;
|
// assemble help text pieces to complete help message
|
||||||
|
|
||||||
|
const SCRW = con.getmaxyx()[1]
|
||||||
|
|
||||||
|
// Display-command palette. taut.js's popup uses (HELP_COL_TEXT on background) as the
|
||||||
|
// default colour pair, so embedded `\x1B[38;5;Nm` codes switch foreground only.
|
||||||
|
const HELP_COL_TEXT = 239 // popup body default (== colWHITE)
|
||||||
|
const HELP_COL_EMPH = 230 // <b>...</b> highlight (== colVoiceHdr)
|
||||||
|
const HELP_COL_BRAND = 211 // first half of "Microtone"
|
||||||
|
const HELP_COL_BRAND_DIM = 239 // second half of "Microtone"
|
||||||
|
|
||||||
|
const fgEsc = (n) => `\x1B[38;5;${n}m`
|
||||||
|
const ESC_DEFAULT = fgEsc(HELP_COL_TEXT)
|
||||||
|
const ESC_EMPH = fgEsc(HELP_COL_EMPH)
|
||||||
|
const MICROTONE = `${fgEsc(HELP_COL_BRAND)}Micro${fgEsc(HELP_COL_BRAND_DIM)}tone${ESC_DEFAULT}`
|
||||||
|
|
||||||
|
// Replace &xxx; entities with their final printable representations.
|
||||||
|
function expandEntities(s) {
|
||||||
|
return s
|
||||||
|
.replaceAll('µtone;', MICROTONE)
|
||||||
|
.replaceAll('&bul;', '\u00F9')
|
||||||
|
.replaceAll('&ddot;', '\u008419u')
|
||||||
|
.replaceAll('&mdot;', '\u00FA')
|
||||||
|
.replaceAll('&updn;', '\u008418u')
|
||||||
|
.replaceAll('&udlr;', '\u008428u\u008429u')
|
||||||
|
.replaceAll('&keyoffsym;', '\u00A0\u00CD\u00CD\u00A1')
|
||||||
|
.replaceAll('¬ecutsym;', '\u00A4\u00A4\u00A4\u00A4')
|
||||||
|
.replaceAll(' ', '\u007F')
|
||||||
|
.replaceAll('­', '')
|
||||||
|
.replaceAll('<', '<')
|
||||||
|
.replaceAll('>', '>')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tokenise a (post-entity-expansion) line. Returns an array of:
|
||||||
|
// {type:'word', text:String, w:int} - non-breakable run of visible chars (may carry ANSI escapes)
|
||||||
|
// {type:'sp'} - a single soft space (eligible for break/expansion)
|
||||||
|
// {type:'anchor', open:Boolean} - <o>/</o> markers (zero width)
|
||||||
|
//
|
||||||
|
// Width accounting:
|
||||||
|
// - ANSI escapes (`\x1B[...m`) : 0 visible chars
|
||||||
|
// - TSVM unicode escapes (`..u`) : 1 visible char
|
||||||
|
// - non-breaking space ( ) : 1 visible char (consumed as part of a word)
|
||||||
|
// - soft hyphen () : dropped (not implemented as a break point)
|
||||||
|
// - everything else : 1 visible char
|
||||||
|
function tokenise(line) {
|
||||||
|
const tokens = []
|
||||||
|
let buf = ''
|
||||||
|
let bufW = 0
|
||||||
|
let i = 0
|
||||||
|
|
||||||
|
const flushWord = () => {
|
||||||
|
if (buf.length > 0) {
|
||||||
|
tokens.push({type: 'word', text: buf, w: bufW})
|
||||||
|
buf = ''
|
||||||
|
bufW = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (i < line.length) {
|
||||||
|
// inline tags (case-sensitive for <b>, case-insensitive for <o>)
|
||||||
|
if (line.slice(i, i + 3) === '<b>') { buf += ESC_EMPH; i += 3; continue }
|
||||||
|
if (line.slice(i, i + 4) === '</b>') { buf += ESC_DEFAULT; i += 4; continue }
|
||||||
|
const head3 = line.slice(i, i + 3).toLowerCase()
|
||||||
|
const head4 = line.slice(i, i + 4).toLowerCase()
|
||||||
|
if (head3 === '<o>') { flushWord(); tokens.push({type: 'anchor', open: true}); i += 3; continue }
|
||||||
|
if (head4 === '</o>') { flushWord(); tokens.push({type: 'anchor', open: false}); i += 4; continue }
|
||||||
|
|
||||||
|
const c = line[i]
|
||||||
|
const cc = line.charCodeAt(i)
|
||||||
|
|
||||||
|
if (cc === 0x1B) {
|
||||||
|
// pre-existing ANSI escape - copy verbatim, zero visible width
|
||||||
|
const m = line.indexOf('m', i)
|
||||||
|
const end = (m < 0) ? line.length : m + 1
|
||||||
|
buf += line.slice(i, end)
|
||||||
|
i = end
|
||||||
|
}
|
||||||
|
else if (cc === 0x84) {
|
||||||
|
// TSVM <digits>u escape - copy verbatim, one visible char
|
||||||
|
const u = line.indexOf('u', i)
|
||||||
|
const end = (u < 0) ? line.length : u + 1
|
||||||
|
buf += line.slice(i, end)
|
||||||
|
bufW += 1
|
||||||
|
i = end
|
||||||
|
}
|
||||||
|
else if (c === ' ') {
|
||||||
|
flushWord()
|
||||||
|
tokens.push({type: 'sp'})
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
else if (cc === 0x00AD) {
|
||||||
|
// soft hyphen: drop (no break-point handling for now)
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
buf += c
|
||||||
|
bufW += 1
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flushWord()
|
||||||
|
return tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build wrapped lines from a token stream then format each one according to alignment.
|
||||||
|
// Returns an array of strings, each exactly `width` visible chars wide (padded with
|
||||||
|
// trailing spaces) so the caller can blit them without further math.
|
||||||
|
function wrapAndAlign(tokens, width, alignment) {
|
||||||
|
const lines = [] // each: {tokens, indent, contentW}
|
||||||
|
let curTokens = []
|
||||||
|
let curW = 0
|
||||||
|
let curIndent = 0
|
||||||
|
let nextIndent = 0 // indent the *next* flushed line should use
|
||||||
|
|
||||||
|
const flushLine = () => {
|
||||||
|
// strip trailing soft spaces
|
||||||
|
while (curTokens.length > 0 && curTokens[curTokens.length - 1].type === 'sp') {
|
||||||
|
curTokens.pop()
|
||||||
|
curW -= 1
|
||||||
|
}
|
||||||
|
lines.push({tokens: curTokens, indent: curIndent, contentW: curW})
|
||||||
|
curTokens = []
|
||||||
|
curW = 0
|
||||||
|
curIndent = nextIndent
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const tok of tokens) {
|
||||||
|
if (tok.type === 'anchor') {
|
||||||
|
// anchor opens at the current visible column (accounting for indent)
|
||||||
|
if (tok.open) nextIndent = curIndent + curW
|
||||||
|
else nextIndent = 0
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tok.type === 'sp') {
|
||||||
|
// ignore leading soft spaces on a fresh line
|
||||||
|
if (curW === 0) continue
|
||||||
|
// hard wrap if the line is already at the right edge
|
||||||
|
if (curIndent + curW + 1 > width) { flushLine(); continue }
|
||||||
|
curTokens.push(tok)
|
||||||
|
curW += 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// word
|
||||||
|
const tw = tok.w
|
||||||
|
if (curIndent + curW + tw > width) {
|
||||||
|
flushLine()
|
||||||
|
// word too wide for the wrapped line: emit it on its own row (possibly clipped by terminal)
|
||||||
|
if (curIndent + tw > width) {
|
||||||
|
curTokens.push(tok)
|
||||||
|
curW += tw
|
||||||
|
flushLine()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
curTokens.push(tok)
|
||||||
|
curW += tw
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curTokens.length > 0 || lines.length === 0) flushLine()
|
||||||
|
|
||||||
|
return lines.map((line, i) => formatLine(line, width, alignment, i === lines.length - 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatLine(line, totalWidth, alignment, isLast) {
|
||||||
|
if (line.tokens.length === 0) return ' '.repeat(totalWidth)
|
||||||
|
|
||||||
|
const indent = ' '.repeat(line.indent)
|
||||||
|
const remaining = totalWidth - line.indent - line.contentW
|
||||||
|
const pad = (n) => (n > 0) ? ' '.repeat(n) : ''
|
||||||
|
const flatText = () => line.tokens.map(t => (t.type === 'sp') ? ' ' : t.text).join('')
|
||||||
|
|
||||||
|
if (alignment === 'c') {
|
||||||
|
const left = remaining >> 1
|
||||||
|
return indent + pad(left) + flatText() + pad(remaining - left)
|
||||||
|
}
|
||||||
|
if (alignment === 'r') return indent + pad(remaining) + flatText()
|
||||||
|
if (alignment === 'l') return indent + flatText() + pad(remaining)
|
||||||
|
|
||||||
|
// justified: only expand spaces when there's slack and we're not on the
|
||||||
|
// last (or single) wrapped line
|
||||||
|
if (isLast || remaining <= 0) return indent + flatText() + pad(remaining)
|
||||||
|
|
||||||
|
const spaceCount = line.tokens.reduce((n, t) => n + (t.type === 'sp' ? 1 : 0), 0)
|
||||||
|
if (spaceCount === 0) return indent + flatText() + pad(remaining)
|
||||||
|
|
||||||
|
const baseExtra = (remaining / spaceCount) | 0
|
||||||
|
let leftover = remaining - baseExtra * spaceCount
|
||||||
|
|
||||||
|
let out = indent
|
||||||
|
for (const tok of line.tokens) {
|
||||||
|
if (tok.type === 'sp') {
|
||||||
|
const extra = baseExtra + (leftover > 0 ? 1 : 0)
|
||||||
|
if (leftover > 0) leftover -= 1
|
||||||
|
out += ' '.repeat(1 + extra)
|
||||||
|
} else {
|
||||||
|
out += tok.text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process a single source line: peel a leading <c>/<r>/<l> alignment tag (if present),
|
||||||
|
// strip its matching close tag, then tokenise + wrap.
|
||||||
|
function typesetSourceLine(line, width) {
|
||||||
|
if (line.length === 0) return [' '.repeat(width)]
|
||||||
|
|
||||||
|
let alignment = 'j' // justified default
|
||||||
|
const startMatch = line.match(/^<([crl])>/i)
|
||||||
|
if (startMatch) {
|
||||||
|
alignment = startMatch[1].toLowerCase()
|
||||||
|
line = line.slice(startMatch[0].length)
|
||||||
|
const closeRe = new RegExp(`</${alignment}>$`, 'i')
|
||||||
|
line = line.replace(closeRe, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokens = tokenise(line)
|
||||||
|
return wrapAndAlign(tokens, width, alignment)
|
||||||
|
}
|
||||||
|
|
||||||
|
function typesetText(text, width) {
|
||||||
|
text = expandEntities(text)
|
||||||
|
const out = []
|
||||||
|
for (const srcLine of text.split('\n')) {
|
||||||
|
for (const outLine of typesetSourceLine(srcLine, width)) out.push(outLine)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
function typeset(text, customWidth) {
|
||||||
|
let typesetWidth = customWidth
|
||||||
|
if (typesetWidth === undefined) typesetWidth = _G.TAUT.HELPMSG_WIDTH
|
||||||
|
if (typesetWidth === undefined) {
|
||||||
|
const currentPosX = con.getyx()[1] // 1-indexed
|
||||||
|
typesetWidth = SCRW - currentPosX + 1
|
||||||
|
}
|
||||||
|
return typesetText(text, typesetWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
let helpMessages = [ // index: taut.js PANEL_NAMES
|
||||||
|
[helpJam, helpTimeline, helpCommon, helpNotation].join('\n\n'),
|
||||||
|
[helpCommon, helpNotation].join('\n\n'), // placeholder
|
||||||
|
[helpCommon, helpNotation].join('\n\n'), // placeholder
|
||||||
|
[helpCommon, helpNotation].join('\n\n'), // placeholder
|
||||||
|
[helpCommon, helpNotation].join('\n\n'), // placeholder
|
||||||
|
[helpCommon, helpNotation].join('\n\n'), // placeholder
|
||||||
|
[helpCommon, helpNotation].join('\n\n'), // placeholder
|
||||||
|
]
|
||||||
|
|
||||||
|
help.MSG_BY_TABS = helpMessages.map(it => typeset(it))
|
||||||
|
help.typeset = typeset
|
||||||
|
help.COL_TEXT = HELP_COL_TEXT
|
||||||
|
help.COL_EMPH = HELP_COL_EMPH
|
||||||
|
|
||||||
|
if (!_G.TAUT.HELPMSG) _G.TAUT.HELPMSG=help;
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -2458,8 +2458,8 @@ Play Head Flags
|
|||||||
1001xxxx yyyyyyyy (FWD000) - Skip forward 0bxxxxyyyyyyyy patterns
|
1001xxxx yyyyyyyy (FWD000) - Skip forward 0bxxxxyyyyyyyy patterns
|
||||||
1111xxxx yyyyyyyy (JMP000) - Go to absolute pattern number 0bxxxxyyyyyyyy
|
1111xxxx yyyyyyyy (JMP000) - Go to absolute pattern number 0bxxxxyyyyyyyy
|
||||||
00000010 00xxxxxx (LEN 00) - Pattern length for this cue (0..63), where 0: 1 row, 63: 64 rows (decoded by AudioAdapter as of 2026-05-05; emitted by xm2taud / it2taud for non-multiple-of-64 source patterns)
|
00000010 00xxxxxx (LEN 00) - Pattern length for this cue (0..63), where 0: 1 row, 63: 64 rows (decoded by AudioAdapter as of 2026-05-05; emitted by xm2taud / it2taud for non-multiple-of-64 source patterns)
|
||||||
00000001 00000000 - Halt (HALT )
|
00000001 00000000 - Halt (HALT ) - Play the full length of the pattern then stop the playback
|
||||||
00000001 00111111 - Fadeout (FADOUT) - Gradually decrease global volume such that at row 63 it reaches zero
|
00000001 00xxxxxx - Fadeout (FADOUT) - Gradually decrease global volume such that at row 0bxxxxxx it reaches zero, then stop the playback
|
||||||
00000000 - No operation
|
00000000 - No operation
|
||||||
|
|
||||||
65536..131071 RW: PCM Sample buffer
|
65536..131071 RW: PCM Sample buffer
|
||||||
|
|||||||
Reference in New Issue
Block a user