mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-22 12:14:05 +09:00
taut: modularity refactor
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
40
assets/disk0/tvdos/bin/taut_fileop.mjs
Normal file
40
assets/disk0/tvdos/bin/taut_fileop.mjs
Normal 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 }
|
||||
@@ -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 }
|
||||
@@ -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
|
||||
333
assets/disk0/tvdos/bin/taut_popups.mjs
Normal file
333
assets/disk0/tvdos/bin/taut_popups.mjs
Normal 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 }
|
||||
@@ -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('28u29u ')
|
||||
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
|
||||
2577
assets/disk0/tvdos/bin/taut_views.mjs
Normal file
2577
assets/disk0/tvdos/bin/taut_views.mjs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user