taut: modularity refactor

This commit is contained in:
minjaesong
2026-06-21 22:23:45 +09:00
parent eb42281a4e
commit 62601de531
11 changed files with 3100 additions and 3161 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,77 +0,0 @@
/**
* TAUT File Operations
* Sub-program launched by taut.js when the File tab is active.
* Rows 1-3 are owned by the parent; this program draws rows 4+.
*
* exec_args[1] = path to .taud file
* Sets _G.TAUT.UI.NEXTPANEL before returning to request a panel switch.
*
* Created by minjaesong on 2026-04-27
*/
const win = require("wintex")
const PANEL_COUNT = 7
const MY_PANEL = 6 // VIEW_FILE
const [SCRH, SCRW] = con.getmaxyx()
const PANEL_Y = 4
const PANEL_H = SCRH - PANEL_Y
const colStatus = 253
const colContent = 240
const colHdr = 230
function drawFileOpContents(wo) {
for (let y = PANEL_Y; y < SCRH; y++) {
con.move(y, 1)
con.color_pair(colContent, 255)
print(' '.repeat(SCRW))
}
con.move(PANEL_Y + 1, 3)
con.color_pair(colHdr, 255)
print('[ File ]')
con.move(PANEL_Y + 3, 3)
con.color_pair(colStatus, 255)
print('placeholder — not yet implemented')
}
function drawHints() {
con.move(SCRH, 1)
con.color_pair(colStatus, 255)
print(' '.repeat(SCRW - 1))
con.move(SCRH, 1)
con.color_pair(colHdr, 255); print('Tab ')
con.color_pair(colStatus, 255); print('Panel')
}
function fileOpInput(wo, event) {
// placeholder — no interaction yet
}
const panel = new win.WindowObject(1, PANEL_Y, SCRW, PANEL_H, fileOpInput, drawFileOpContents, undefined, ()=>{})
panel.drawContents()
drawHints()
let done = false
while (!done) {
input.withEvent(event => {
if (event[0] !== 'key_down') return
const keysym = event[1]
const keyJustHit = (1 == event[2])
const shiftDown = (event.includes(59) || event.includes(60))
if (!keyJustHit) return
if (keysym === '<TAB>') {
_G.TAUT.UI.NEXTPANEL = (MY_PANEL + (shiftDown ? -1 : 1) + PANEL_COUNT) % PANEL_COUNT
done = true
return
}
panel.processInput(event)
})
}
return 0

View File

@@ -0,0 +1,40 @@
/**
* TAUT File panel (in-process).
*
* Replaces the old taut_fileop.js sub-program. The File tab is now a normal
* in-process panel: init(HUB) returns { drawContents, input }, wired into a
* wintex WindowObject like the other panels. Tab / global keys are handled by
* taut.js's main loop, so input() is a no-op for now.
*
* Still a placeholder UI — file load / save lands here later.
* Converted from taut_fileop.js on 2026-06-21.
*/
function init(HUB) {
const C = HUB.C
const SCRW = C.SCRW, SCRH = C.SCRH
const PANEL_Y = C.PTNVIEW_OFFSET_Y
const colStatus = C.colStatus, colHdr = C.colVoiceHdr
const colContent = 240
function drawContents(wo) {
for (let y = PANEL_Y; y < SCRH; y++) {
con.move(y, 1)
con.color_pair(colContent, 255)
print(' '.repeat(SCRW))
}
con.move(PANEL_Y + 1, 3)
con.color_pair(colHdr, 255)
print('[ File ]')
con.move(PANEL_Y + 3, 3)
con.color_pair(colStatus, 255)
print('(not yet implemented)')
}
// Main loop owns Tab and the global shortcuts; nothing panel-specific yet.
function input(wo, event) {}
return { drawContents, input }
}
exports = { init }

View File

@@ -1,5 +1,18 @@
if (!_G.TAUT) _G.TAUT = {};
let help = {}
/**
* TAUT help-text module.
*
* In-process replacement for the old taut_helpmsg.js sub-program. Exports
* init(HUB) which typesets every panel's help text at HUB.C.HELP_CONTENT_W and
* returns { MSG_BY_TABS, typeset, COL_TEXT, COL_EMPH }. taut.js stores the result
* on HUB.help; openHelpPopup reads it.
*
* The help-text strings themselves are width-independent, so they live at module
* top level; only the rule width and final typesetting depend on HUB.
*
* Converted from taut_helpmsg.js (separate program) on 2026-06-21. The \uXXXX
* escapes are kept verbatim from the original TSVM's string parser is not
* Unicode and treats raw bytes differently from escapes, so do not normalise them.
*/
let ts = require("typesetter")
@@ -145,16 +158,18 @@ Mixer flags define how should the mixer behave.
////////////////////////////////////////////////////////////////////////////////////////////////////
// assemble help text pieces to complete help message
function init(HUB) {
const W = HUB.C.HELP_CONTENT_W
const HRULE = '<s>' + '\u00B3'.repeat(_G.TAUT.HELPMSG_WIDTH) + '</s>\n'
const HRULE = '<s>' + '\u00B3'.repeat(W) + '</s>\n'
// taut.js's popup uses (HELP_COL_TEXT on background) as the default colour pair.
// The shared typesetter module owns the palette and the markup expander.
function typeset(text) {
return ts.typeset(text, _G.TAUT.HELPMSG_WIDTH)
}
// taut.js's popup uses (HELP_COL_TEXT on background) as the default colour pair.
// The shared typesetter module owns the palette and the markup expander.
function typeset(text) {
return ts.typeset(text, W)
}
let helpMessages = [ // index: taut.js PANEL_NAMES
let helpMessages = [ // index: taut.js PANEL_NAMES
/* Timeline */[helpJam, helpTimeline, helpCommon, helpNotation].join(HRULE),
/* Cues */[helpCommon, helpNotation].join(HRULE), // placeholder
/* Patterns */[helpCommon, helpNotation].join(HRULE), // placeholder
@@ -164,9 +179,12 @@ let helpMessages = [ // index: taut.js PANEL_NAMES
/* File */[helpCommon, helpNotation].join(HRULE), // placeholder
]
help.MSG_BY_TABS = helpMessages.map(it => typeset(it))
help.typeset = typeset
help.COL_TEXT = ts.COL_TEXT
help.COL_EMPH = ts.COL_EMPH
return {
MSG_BY_TABS: helpMessages.map(it => typeset(it)),
typeset: typeset,
COL_TEXT: ts.COL_TEXT,
COL_EMPH: ts.COL_EMPH,
}
}
if (!_G.TAUT.HELPMSG) _G.TAUT.HELPMSG=help;
exports = { init }

View File

@@ -1,77 +0,0 @@
/**
* TAUT Instrument Editor
* Sub-program launched by taut.js when the Instrmnt tab is active.
* Rows 1-3 are owned by the parent; this program draws rows 4+.
*
* exec_args[1] = path to .taud file
* Sets _G.TAUT.UI.NEXTPANEL before returning to request a panel switch.
*
* Created by minjaesong on 2026-04-27
*/
const win = require("wintex")
const PANEL_COUNT = 7
const MY_PANEL = 4 // VIEW_INSTRMNT
const [SCRH, SCRW] = con.getmaxyx()
const PANEL_Y = 4
const PANEL_H = SCRH - PANEL_Y
const colStatus = 253
const colContent = 240
const colHdr = 230
function drawInstEditContents(wo) {
for (let y = PANEL_Y; y < SCRH; y++) {
con.move(y, 1)
con.color_pair(colContent, 255)
print(' '.repeat(SCRW))
}
con.move(PANEL_Y + 1, 3)
con.color_pair(colHdr, 255)
print('[ Instrument Editor ]')
con.move(PANEL_Y + 3, 3)
con.color_pair(colStatus, 255)
print('placeholder — not yet implemented')
}
function drawHints() {
con.move(SCRH, 1)
con.color_pair(colStatus, 255)
print(' '.repeat(SCRW - 1))
con.move(SCRH, 1)
con.color_pair(colHdr, 255); print('Tab ')
con.color_pair(colStatus, 255); print('Panel')
}
function instEditInput(wo, event) {
// placeholder — no interaction yet
}
const panel = new win.WindowObject(1, PANEL_Y, SCRW, PANEL_H, instEditInput, drawInstEditContents, undefined, ()=>{})
panel.drawContents()
drawHints()
let done = false
while (!done) {
input.withEvent(event => {
if (event[0] !== 'key_down') return
const keysym = event[1]
const keyJustHit = (1 == event[2])
const shiftDown = (event.includes(59) || event.includes(60))
if (!keyJustHit) return
if (keysym === '<TAB>') {
_G.TAUT.UI.NEXTPANEL = (MY_PANEL + (shiftDown ? -1 : 1) + PANEL_COUNT) % PANEL_COUNT
done = true
return
}
panel.processInput(event)
})
}
return 0

View File

@@ -0,0 +1,333 @@
/**
* TAUT popups module.
*
* In-process modal dialogs (help / go-to / retune / mixer-flags / confirm-quit)
* plus the shared popup chrome (frame painter, colour palette, scrollbar glyphs).
* Extracted from taut.js on 2026-06-21.
*
* These are pure UI: every engine-state mutation is delegated back through HUB
* callbacks (HUB.applyGoto, HUB.retuneAllPatterns, HUB.commitMixerFlags, …), so
* the engine keeps owning currentPanel / cueIdx / patternIdx / mixer flags / the
* unsaved-changes flag. init(HUB) returns the dialog openers and the chrome (so
* other in-process modules can reuse the same look). Read-only constants come in
* via HUB.C; \uXXXX escapes are kept verbatim (TSVM's string parser is not Unicode).
*/
const win = require("wintex")
function init(HUB) {
const C = HUB.C
const sym = C.sym
const PANEL_NAMES = C.PANEL_NAMES
const pitchTablePresets = C.pitchTablePresets
const colWHITE = C.colWHITE, colPopupBack = C.colPopupBack
const colTabBarOrn = C.colTabBarOrn, colTabBarBack = C.colTabBarBack
const colTabInactive = C.colTabInactive
const colPan = C.colPan, colInst = C.colInst, colStatus = C.colStatus
const colHighlight = C.colHighlight, colVoiceHdr = C.colVoiceHdr
const HELP_CONTENT_W = C.HELP_CONTENT_W, HELP_CONTENT_H = C.HELP_CONTENT_H
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SHARED POPUP CHROME
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Custom window-frame painter passed to wintex showDialog as `drawFrame`.
// Paints a title bar at the top row, then fills the rest of the popup with
// `colPopupBack` (including the bottom row, so the spacing row below wintex's
// button strip stays painted).
const popupDrawFrame = (wo) => {
// draw header
con.move(wo.y, wo.x)
con.color_pair(colTabBarOrn, colTabBarBack)
print(`\u00FB`.repeat(wo.width))
// imprint title
let titleWidth = wo.title.length
con.move(wo.y, wo.x + (((wo.width - titleWidth - 2) & 254) >>> 1))
con.color_pair(colTabInactive, colTabBarBack); print(` ${wo.title} `)
// fill content area (title row already painted above)
for (let r = 1; r < wo.height; r++) {
con.move(wo.y + r, wo.x)
con.color_pair(230, colPopupBack)
print(' '.repeat(wo.width))
}
}
// Taut's charset carries dedicated scrollbar glyphs at 0xBA..0xBF (empty
// top/mid/bottom caps 0xBA..0xBC, filled top/mid/bottom thumb 0xBD..0xBF).
// wintex defaults to the CP437-safe 0xBA/0xDB pair, so pass these to every
// list popup to render the scrollbar in taut's style.
const popupScrollbarChars = [0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF]
// Standard colour palette shared by every taut popup so wintex's defaults blend
// with taut's popup chrome.
const popupColours = {
// fg: colStatus,
// bg: colPopupBack,
// fieldBg: 240,
// dimFg: colVoiceHdrMuted,
// hlFg: colWHITE,
// focusBg: colHighlight,
// listBg: colPopupBack,
// listSelBg: colHighlight,
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// HELP POPUP
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
function openHelpPopup() {
const currentPanel = HUB.getPanel()
const helpmsg = HUB.help || {}
const lines = (helpmsg.MSG_BY_TABS && helpmsg.MSG_BY_TABS[currentPanel]) || ['']
const colText = helpmsg.COL_TEXT || colWHITE
win.showDialog({
title: `Help: ${PANEL_NAMES[currentPanel]}`,
drawFrame: popupDrawFrame,
colours: popupColours,
list: {
items: lines.map(l => ({ label: l })),
bg: colPopupBack,
height: HELP_CONTENT_H,
width: HELP_CONTENT_W+4,
scrollbarChars: popupScrollbarChars,
selectable: () => false,
renderItem: (ctx) => {
con.color_pair(colText, ctx.listBg)
con.move(ctx.y, ctx.x)
const line = (ctx.item.label != null ? ctx.item.label : '')
print(line.padEnd(ctx.w, ' ').substring(0, ctx.w))
},
},
buttons: [{ label: 'OK', action: 'ok', default: true }],
onKey: (ks, _shift, ctx) => {
if (ks === '!' || ks === 'q') { ctx.close({ action: 'cancel' }); return true }
return false
},
})
HUB.drawAll()
}
function openConfirmQuit() {
const messageLines = ['Exit Microtone?']
if (HUB.hasUnsavedChanges()) messageLines.push('You have unsaved changes.')
const res = win.showDialog({
title: 'Quit?',
drawFrame: popupDrawFrame,
colours: popupColours,
message: messageLines,
buttons: [
{ label: 'Yes', action: 'yes' },
{ label: 'No', action: 'no', default: true },
],
onKey: (ks, _shift, ctx) => {
if (ks === 'y' || ks === 'Y') { ctx.close({ action: 'yes' }); return true }
if (ks === 'n' || ks === 'N') { ctx.close({ action: 'no' }); return true }
return false
},
})
const result = (res.action === 'yes')
if (!result) HUB.drawAll()
return result
}
function openGotoPopup() {
const currentPanel = HUB.getPanel()
const prompts = ['Cue (hex):', 'Cue (hex):', 'Pattern (hex):']
const promptStr = prompts[currentPanel] || 'Number:'
const res = win.showDialog({
title: 'Go To',
drawFrame: popupDrawFrame,
colours: popupColours,
fields: [{ label: promptStr, width: 4, maxLength: 3 }],
buttons: [
{ label: 'OK', action: 'ok' },
{ label: 'Cancel', action: 'cancel' },
],
})
if (res.action === 'ok' && res.values[0]) {
const n = parseInt(res.values[0], 16)
if (!isNaN(n)) HUB.applyGoto(n)
}
HUB.drawAll()
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// RETUNE POPUP
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
function openRetunePopup() {
const PITCH_PRESET_IDX = HUB.getPitchPresetIdx()
const entries = Object.values(pitchTablePresets).sort((a, b) => a.index - b.index)
const n = entries.length
// Foreground colour by tuning type (preset.t):
// 'd' = 12-tone family, 'M' = Macrotonal, 'm' = microtonal, '' = Raw.
const tuningTypeColour = { d: 230, M: colPan, m: colInst, '': colStatus }
const methodLabels = {
pitch: 'Nearest-note',
delta: 'Nearest-delta',
cadence: 'Nearest-cadence',
harmonic: 'Nearest-harmonic', // this thing is cadence-aware (hopefully)
}
const methodCycle = ['pitch', 'harmonic', 'delta'/*, 'cadence'*/]
let method = 'pitch'
let selIdx = entries.findIndex(p => p.index === PITCH_PRESET_IDX)
if (selIdx < 0) selIdx = 0
const items = entries.map(e => ({ label: e.name, preset: e }))
const listH = Math.min(n, 13)
const messageLines = [
'Select new tuning preset:',
'Method: ' + methodLabels[method],
]
const res = win.showDialog({
title: 'Retune',
drawFrame: popupDrawFrame,
colours: popupColours,
message: messageLines,
list: {
items: items,
height: listH,
width: 36,
cursor: selIdx,
scrollbarChars: popupScrollbarChars,
renderItem: (ctx) => {
const e = ctx.item.preset
const isCur = (e.index === PITCH_PRESET_IDX)
const fore = (e.t in tuningTypeColour) ? tuningTypeColour[e.t] : 230
const useFg = (ctx.isCursor && ctx.focused) ? colWHITE : fore
const useBg = (ctx.isCursor && ctx.focused) ? colHighlight : ctx.listBg
con.color_pair(useFg, useBg)
con.move(ctx.y, ctx.x)
const marker = isCur ? sym.playhead : ' '
let label = `${marker} ${e.name}`
if (label.length > ctx.w) label = label.substring(0, ctx.w)
else label = label.padEnd(ctx.w, ' ')
print(label)
},
},
buttons: [
{ label: 'OK', action: 'ok' },
{ label: 'Cancel', action: 'cancel' },
],
onKey: (ks, _shift, ctx) => {
if (ks === 'm' || ks === 'M') {
method = methodCycle[(methodCycle.indexOf(method) + 1) % methodCycle.length]
messageLines[1] = 'Method: ' + methodLabels[method]
ctx.render()
return true
}
return false
},
})
if (res.action === 'ok' && res.listItem) {
const target = res.listItem.preset
if (target && target.index !== PITCH_PRESET_IDX) {
HUB.retuneAllPatterns(target.index, method)
}
}
HUB.drawAll()
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MIXER FLAGS POPUP
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
function openFlagsPopup() {
const flags0 = HUB.getMixerFlags()
const toneNames = ['Linear pitch', 'Amiga pitch', 'Linear freq']
const intpNames = ['Default', 'None', 'A500', 'A1200', 'SNES', 'DPCM']
let toneMode = flags0 & 3
let intpMode = (flags0 >>> 2) & 7
if (toneMode >= toneNames.length) toneMode = 0
if (intpMode >= intpNames.length) intpMode = 0
// Build list rows: headers + selectable radio options.
const items = []
items.push({ label: 'Tone Mode:', kind: 'header' })
toneNames.forEach((nm, i) => items.push({ label: nm, kind: 'tone', idx: i }))
items.push({ label: '', kind: 'spacer' })
items.push({ label: 'Interpolation:', kind: 'header' })
intpNames.forEach((nm, i) => items.push({ label: nm, kind: 'intp', idx: i }))
const res = win.showDialog({
title: 'Mixer Flags',
drawFrame: popupDrawFrame,
colours: popupColours,
list: {
items: items,
height: items.length,
width: 22,
drawWell: false,
showScrollbar: false,
scrollbarChars: popupScrollbarChars,
selectable: (it) => it.kind === 'tone' || it.kind === 'intp',
renderItem: (ctx) => {
const it = ctx.item
con.move(ctx.y, ctx.x)
if (it.kind === 'header') {
con.color_pair(colStatus, colPopupBack)
print(it.label.padEnd(ctx.w, ' ').substring(0, ctx.w))
return
}
if (it.kind === 'spacer') {
con.color_pair(colStatus, colPopupBack)
print(' '.repeat(ctx.w))
return
}
const isChecked = (it.kind === 'tone')
? (toneMode === it.idx)
: (intpMode === it.idx)
const useBg = (ctx.isCursor && ctx.focused) ? colHighlight : colPopupBack
const useFg = isChecked ? colVoiceHdr : colWHITE
con.color_pair(useFg, useBg)
const line = ' ' + (isChecked ? sym.ticked : sym.unticked) + ' ' + it.label
print(line.padEnd(ctx.w, ' ').substring(0, ctx.w))
},
// Space and left-click toggle the radio; Enter commits via OK.
onActivate: (item, _idx, key) => {
if (key === ' ' || key === 'click') {
if (item.kind === 'tone') toneMode = item.idx
else if (item.kind === 'intp') intpMode = item.idx
return null
}
if (key === '\n') return 'ok'
return null
},
},
buttons: [
{ label: 'OK', action: 'ok' },
{ label: 'Cancel', action: 'cancel' },
],
})
if (res.action === 'ok') {
const newFlags = (flags0 & ~0x1F) |
(toneMode & 3) | ((intpMode & 7) << 2)
if (newFlags !== flags0) {
HUB.commitMixerFlags(newFlags)
}
}
HUB.drawAll()
}
return {
openHelpPopup, openConfirmQuit, openGotoPopup, openRetunePopup, openFlagsPopup,
popupDrawFrame, popupColours, popupScrollbarChars,
}
}
exports = { init }

View File

@@ -1,181 +0,0 @@
/**
* TAUT Sample Editor (stub)
* Sub-program launched from taut.js's Samples viewer. Rows 1-3 are owned by
* the parent; this program draws rows 4+.
*
* exec_args:
* [1] = path to .taud file
* [2] = parent panel index (where to return)
* [3] = sample index to preload (-1 if none)
*
* Sets _G.TAUT.UI.NEXTPANEL on return to request a panel switch back.
*
* Created by minjaesong on 2026-04-27
* Stub editing UI added on 2026-05-26
*/
const win = require("wintex")
const PARENT_PANEL = (exec_args[2] !== undefined) ? (exec_args[2] | 0) : 3 // VIEW_SAMPLES
const SAMPLE_IDX = (exec_args[3] !== undefined) ? (exec_args[3] | 0) : -1
const [SCRH, SCRW] = con.getmaxyx()
const PANEL_Y = 4
const PANEL_H = SCRH - PANEL_Y
const colStatus = 253
const colContent = 240
const colHdr = 230
const colEmph = 211
const colDim = 246
const colBack = 255
const colSel = 41
// Stub editor "fields": pretend toolbar. None of these write anything yet.
const TOOLS = [
{ key: 'L', label: 'Load .raw / .wav from disk' },
{ key: 'S', label: 'Save current sample to disk' },
{ key: 'D', label: 'Draw waveform freehand' },
{ key: 'X', label: 'Crop / trim selection' },
{ key: 'R', label: 'Resample' },
{ key: 'V', label: 'Reverse' },
{ key: 'N', label: 'Normalise to peak' },
{ key: 'F', label: 'Fade in / out' },
]
let toolCursor = 0
function drawSampleEditFrame() {
for (let y = PANEL_Y; y < SCRH; y++) {
con.move(y, 1)
con.color_pair(colContent, colBack)
print(' '.repeat(SCRW))
}
// Title
con.move(PANEL_Y + 1, 3)
con.color_pair(colHdr, colBack); print('[ Sample Editor ] ')
con.color_pair(colEmph, colBack); print('Sample ')
con.color_pair(colStatus, colBack)
if (SAMPLE_IDX >= 0) print('#' + (SAMPLE_IDX + 1).toString(16).toUpperCase().padStart(2, '0'))
else print('(none)')
con.move(PANEL_Y + 2, 3)
con.color_pair(colDim, colBack)
print('stub editor — actions below are placeholders only.')
}
function drawToolList() {
const x = 5
const y0 = PANEL_Y + 4
con.move(y0, x)
con.color_pair(colHdr, colBack); print('Editing actions')
con.move(y0 + 1, x)
con.color_pair(colDim, colBack); print('-'.repeat(16))
for (let i = 0; i < TOOLS.length; i++) {
const y = y0 + 3 + i
const t = TOOLS[i]
const sel = (i === toolCursor)
const back = sel ? colSel : colBack
con.move(y, x)
con.color_pair(colHdr, back); print(' ' + t.key + ' ')
con.color_pair(colStatus, back); print(' ')
con.color_pair(sel ? colEmph : colStatus, back)
const w = SCRW - x - 6
const lbl = t.label.length > w ? t.label.substring(0, w) : t.label.padEnd(w)
print(lbl)
}
// Drawing-area placeholder on the right
const dx = 38
const dy0 = PANEL_Y + 4
const dw = SCRW - dx - 2
const dh = SCRH - dy0 - 2
con.move(dy0, dx)
con.color_pair(colHdr, colBack); print('Waveform editor')
con.move(dy0 + 1, dx)
con.color_pair(colDim, colBack); print('-'.repeat(16))
// Empty drawing rectangle made of dots
for (let r = 0; r < dh; r++) {
con.move(dy0 + 3 + r, dx)
con.color_pair(colDim, colBack)
if (r === (dh >>> 1)) print('-'.repeat(dw)) // zero line
else print(' '.repeat(dw))
}
con.move(dy0 + 3 + (dh >>> 1) + 1, dx)
con.color_pair(colDim, colBack)
print('(drawing surface — not yet implemented)')
}
function drawHints() {
con.move(SCRH, 1)
con.color_pair(colStatus, colBack)
print(' '.repeat(SCRW - 1))
con.move(SCRH, 1)
con.color_pair(colHdr, colBack); print('„28u„29u ')
con.color_pair(colStatus, colBack); print('Tool ')
con.color_pair(colHdr, colBack); print('Enter ')
con.color_pair(colStatus, colBack); print('Apply ')
con.color_pair(colHdr, colBack); print('Esc/Tab ')
con.color_pair(colStatus, colBack); print('Back to viewer')
}
function flashAction(idx) {
const t = TOOLS[idx]
if (!t) return
con.move(SCRH - 2, 5)
con.color_pair(colEmph, colBack)
print(('Action: ' + t.label + ' (stub, no-op)').padEnd(SCRW - 8))
}
function sampleEditInput(wo, event) {
// wintex panel input — wired up but the loop below handles keys directly.
}
function drawAll() {
drawSampleEditFrame()
drawToolList()
drawHints()
}
const panel = new win.WindowObject(1, PANEL_Y, SCRW, PANEL_H, sampleEditInput, drawAll, undefined, ()=>{})
panel.drawContents()
let done = false
while (!done) {
input.withEvent(event => {
if (event[0] !== 'key_down') return
const keysym = event[1]
const keyJustHit = (1 == event[2])
if (!keyJustHit) return
if (keysym === '<ESCAPE>' || keysym === '<TAB>') {
_G.TAUT.UI.NEXTPANEL = PARENT_PANEL
done = true
return
}
if (keysym === '<UP>') { if (toolCursor > 0) toolCursor--; drawToolList(); return }
if (keysym === '<DOWN>') { if (toolCursor < TOOLS.length-1) toolCursor++; drawToolList(); return }
if (keysym === '\n') {
flashAction(toolCursor)
return
}
// Direct key shortcuts
for (let i = 0; i < TOOLS.length; i++) {
if (keysym === TOOLS[i].key.toLowerCase() || keysym === TOOLS[i].key) {
toolCursor = i
drawToolList()
flashAction(i)
return
}
}
})
}
return 0

File diff suppressed because it is too large Load Diff

View File

@@ -2952,6 +2952,9 @@ TODO:
* DONE 2026-06-20. See `con.setFullscreen()`
[x] Taud double the BPM ceiling
* DONE 2026-06-20. BPM range 25..535 via `T $FFxx` + the song-table byte-8 high bit (tickrate now 7-bit).
[ ] Taud instrument: add a 'percussion' flag so retuner/transposer won't touch them
[ ] Zfm and Command: create rc files whenever they are missing
Zfm: connect *rc to edit.js, with its own highlight colour
TODO - list of demo songs that MUST ship with Microtone:
* 4THSYM (rename to Fourth Symmetriad) — excellent piece for demonstrating NNAs and filter envelopes