mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-06 05:28:31 +09:00
vol/pan ind wip
This commit is contained in:
@@ -952,6 +952,8 @@ function loadTaudSongList(filePath) {
|
||||
|
||||
const [SCRH, SCRW] = con.getmaxyx()
|
||||
const [SCRPW, SCRPH] = graphics.getPixelDimension()
|
||||
const CELL_PW = (SCRPW / SCRW) | 0 // px per character column
|
||||
const CELL_PH = (SCRPH / SCRH) | 0 // px per character row
|
||||
const PTNVIEW_OFFSET_X = 3
|
||||
const PTNVIEW_OFFSET_Y = 5
|
||||
const PTNVIEW_HEIGHT = SCRH - PTNVIEW_OFFSET_Y
|
||||
@@ -996,6 +998,15 @@ const colTabInactive = 45
|
||||
const colWHITE = 239
|
||||
const colBLACK = 240
|
||||
|
||||
// Voice-header playback meters (volume bar grows from centre out; pan bar = centre tick + dot).
|
||||
// Pixels are drawn beneath text — only the glyph foregrounds occlude the bars, so the bars sit
|
||||
// on rows 0 and (cellH - 1) where the 7×14 glyph has the least foreground.
|
||||
const METER_VOL_COL = 41 // colHighlight
|
||||
const METER_PAN_TICK_COL = 6 // colColumnSep
|
||||
const METER_PAN_DOT_COL = 239 // colWHITE
|
||||
const METER_BAR_PAD = 2 // px gap from cell edges (each side)
|
||||
const METER_TRANSPARENT = 255
|
||||
|
||||
let separatorStyle = 0
|
||||
|
||||
const PATEDITOR_LIST_X = 1
|
||||
@@ -1199,6 +1210,94 @@ function drawVoiceHeaders() {
|
||||
}
|
||||
|
||||
drawSeparators(separatorStyle)
|
||||
// Voice headers were just repainted with bg=255 (transparent), so any meter pixels
|
||||
// beneath them survived the redraw — but the cached per-slot state may still match,
|
||||
// which would skip the redraw on the next updatePlayback. Force a redraw by clearing
|
||||
// the cache; the next updatePlayback re-emits any active bars.
|
||||
invalidateVoiceMeters()
|
||||
}
|
||||
|
||||
// Per-slot cache of last-drawn meter state: { voice, vol, pan } or null when slot is clear.
|
||||
// Indexed by slot index 0..VOCSIZE_TIMELINE_FULL-1 (never grows beyond 20 slots in practice).
|
||||
const meterPrevSlot = new Array(20).fill(null)
|
||||
|
||||
function invalidateVoiceMeters() {
|
||||
for (let i = 0; i < meterPrevSlot.length; i++) meterPrevSlot[i] = null
|
||||
}
|
||||
|
||||
// Wipe the pixel strip used by the voice-header meters back to transparent (255).
|
||||
// Called when leaving the Timeline panel or when playback stops.
|
||||
function clearVoiceMeters() {
|
||||
const yTop = (PTNVIEW_OFFSET_Y - 2) * CELL_PH
|
||||
const yBot = (PTNVIEW_OFFSET_Y - 1) * CELL_PH - 1
|
||||
for (let x = 0; x < SCRPW; x++) {
|
||||
graphics.plotPixel(x, yTop, METER_TRANSPARENT)
|
||||
graphics.plotPixel(x, yBot, METER_TRANSPARENT)
|
||||
}
|
||||
invalidateVoiceMeters()
|
||||
}
|
||||
|
||||
/**
|
||||
* Repaint the per-voice volume and pan indicators in the voice-header row.
|
||||
* Volume: horizontal bar growing from the cell centre outward, length ∝ effective tracker
|
||||
* volume (after envelopes, fadeout, vol-column/D/tremolo ramps, per-voice fader). Drawn on
|
||||
* the cell's bottom pixel row.
|
||||
* Pan: centre tick + a single dot offset by (pan-128)/128 × halfWidth. Drawn on the cell's
|
||||
* top pixel row.
|
||||
* Only redraws slots whose (voice, volPix, panPix) tuple has changed since the last call,
|
||||
* so the work per frame stays bounded by actual movement.
|
||||
*/
|
||||
function drawVoiceMeters() {
|
||||
if (playbackMode === PLAYMODE_NONE || currentPanel !== VIEW_TIMELINE) return
|
||||
const yPan = (PTNVIEW_OFFSET_Y - 2) * CELL_PH // top pixel of header row
|
||||
const yVol = (PTNVIEW_OFFSET_Y - 1) * CELL_PH - 1 // bottom pixel of header row
|
||||
const slotPW = COLSIZE_TIMELINE_FULL * CELL_PW
|
||||
const halfW = (slotPW >>> 1) - METER_BAR_PAD
|
||||
|
||||
for (let c = 0; c < VOCSIZE_TIMELINE_FULL; c++) {
|
||||
const voice = voiceOff + c
|
||||
const slotX0 = (PTNVIEW_OFFSET_X + COLSIZE_TIMELINE_FULL * c - 1) * CELL_PW
|
||||
const xCenter = slotX0 + (slotPW >>> 1)
|
||||
const prev = meterPrevSlot[c]
|
||||
|
||||
if (voice >= song.numVoices) {
|
||||
if (prev !== null) {
|
||||
for (let x = slotX0 + METER_BAR_PAD; x < slotX0 + slotPW - METER_BAR_PAD; x++) {
|
||||
graphics.plotPixel(x, yPan, METER_TRANSPARENT)
|
||||
graphics.plotPixel(x, yVol, METER_TRANSPARENT)
|
||||
}
|
||||
meterPrevSlot[c] = null
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
const volRaw = audio.getVoiceEffectiveVolume(PLAYHEAD, voice) || 0
|
||||
const panRaw = audio.getVoiceEffectivePan(PLAYHEAD, voice)
|
||||
const volPix = Math.max(0, Math.min(halfW, Math.round(volRaw * halfW)))
|
||||
// Pan range 0..255, centre 128 → map to ±halfW.
|
||||
let panPix = Math.round((panRaw - 128) / 128 * halfW)
|
||||
if (panPix < -halfW) panPix = -halfW
|
||||
else if (panPix > halfW) panPix = halfW
|
||||
|
||||
if (prev !== null && prev.voice === voice && prev.vol === volPix && prev.pan === panPix) continue
|
||||
|
||||
// Clear both bar strips in this slot before redrawing.
|
||||
for (let x = slotX0 + METER_BAR_PAD; x < slotX0 + slotPW - METER_BAR_PAD; x++) {
|
||||
graphics.plotPixel(x, yPan, METER_TRANSPARENT)
|
||||
graphics.plotPixel(x, yVol, METER_TRANSPARENT)
|
||||
}
|
||||
// Volume bar (grows from centre out). Silent voices show no bar.
|
||||
if (volPix > 0) {
|
||||
for (let dx = -volPix; dx <= volPix; dx++) {
|
||||
graphics.plotPixel(xCenter + dx, yVol, METER_VOL_COL)
|
||||
}
|
||||
}
|
||||
// Pan bar: faint centre tick, bright dot at pan position.
|
||||
graphics.plotPixel(xCenter, yPan, METER_PAN_TICK_COL)
|
||||
graphics.plotPixel(xCenter + panPix, yPan, METER_PAN_DOT_COL)
|
||||
|
||||
meterPrevSlot[c] = { voice: voice, vol: volPix, pan: panPix }
|
||||
}
|
||||
}
|
||||
|
||||
// Sub-field layout for style-0 cells (shared by drawPatternRowAt and drawVoiceColumnAt)
|
||||
@@ -1710,6 +1809,9 @@ function setTimelineRowStyle(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
|
||||
// Slot widths and per-slot voice mapping are about to change; wipe meter pixels so the
|
||||
// narrower/wider layout doesn't leave stale bar fragments from the old slot widths.
|
||||
clearVoiceMeters()
|
||||
clampVoice()
|
||||
drawAll()
|
||||
}
|
||||
@@ -3074,6 +3176,7 @@ function stopPlayback() {
|
||||
audio.stop(PLAYHEAD)
|
||||
playbackMode = PLAYMODE_NONE
|
||||
clampPatternGrid()
|
||||
clearVoiceMeters()
|
||||
}
|
||||
|
||||
function updatePlayback() {
|
||||
@@ -3085,9 +3188,12 @@ function updatePlayback() {
|
||||
drawPatternRowAt(cursorRow - scrollRow)
|
||||
else if (currentPanel === VIEW_PATTERN_DETAILS && song.numPats > 0) { simStateKey = ''; redrawPanel() }
|
||||
drawAlwaysOnElems()
|
||||
clearVoiceMeters()
|
||||
return
|
||||
}
|
||||
|
||||
drawVoiceMeters()
|
||||
|
||||
const nowCue = audio.getCuePosition(PLAYHEAD)
|
||||
const nowRow = audio.getTrackerRow(PLAYHEAD)
|
||||
|
||||
@@ -3791,10 +3897,12 @@ while (!exitFlag) {
|
||||
}
|
||||
|
||||
if (keyJustHit && keysym === "<TAB>") {
|
||||
const wasTimeline = (currentPanel === VIEW_TIMELINE)
|
||||
currentPanel = (currentPanel + (shiftDown ? -1 : 1))
|
||||
if (currentPanel < 0) currentPanel += panels.length
|
||||
currentPanel = currentPanel % panels.length
|
||||
applyMuteTransition(currentPanel)
|
||||
if (wasTimeline && currentPanel !== VIEW_TIMELINE) clearVoiceMeters()
|
||||
if (isExternalPanel(currentPanel)) {
|
||||
// Redraw header now so the tab highlight is visible immediately,
|
||||
// but defer the actual sub-program launch to after withEvent returns.
|
||||
@@ -3825,9 +3933,11 @@ while (!exitFlag) {
|
||||
pendingExternalDraw = false
|
||||
redrawPanel()
|
||||
while (_G.TAUT.UI.NEXTPANEL !== undefined && _G.TAUT.UI.NEXTPANEL !== null) {
|
||||
const wasTimeline = (currentPanel === VIEW_TIMELINE)
|
||||
currentPanel = _G.TAUT.UI.NEXTPANEL
|
||||
_G.TAUT.UI.NEXTPANEL = undefined
|
||||
applyMuteTransition(currentPanel)
|
||||
if (wasTimeline && currentPanel !== VIEW_TIMELINE) clearVoiceMeters()
|
||||
if (isExternalPanel(currentPanel)) {
|
||||
con.clear(); drawAlwaysOnElems(); drawControlHint()
|
||||
redrawPanel()
|
||||
|
||||
@@ -67,13 +67,13 @@ a s d f g h j k
|
||||
let helpCommon = `<c>COMMON CONTROLS</c>
|
||||
<c>\u00B7${'\u00B8'.repeat(15)}\u00B9</c>
|
||||
&bul;<b>!</b> : <O>show this help message</O>
|
||||
&bul;<b>Y</b> : <O>play the entire song from the current cue</O>
|
||||
&bul;<b>U</b> : <O>play the current cue then stop</O>
|
||||
&bul;<b>I</b> : <O>play the current row</O>
|
||||
&bul;<b>O</b> : <O>stop the playback</O>
|
||||
&bul;<b>tab</b> : <O>switch forward a tab</O>
|
||||
&bul;<b>TAB</b> : <O>switch backward a tab</O>
|
||||
&bul;<b>q</b> : <O>close µtone;</O>
|
||||
&bul;<b>Y</b> : <O>plays the entire song from the current cue</O>
|
||||
&bul;<b>U</b> : <O>plays the current cue then stop</O>
|
||||
&bul;<b>I</b> : <O>plays the current row</O>
|
||||
&bul;<b>O</b> : <O>stops the playback</O>
|
||||
&bul;<b>tab</b> : <O>switchs forward a tab</O>
|
||||
&bul;<b>TAB</b> : <O>switchs backward a tab</O>
|
||||
&bul;<b>q</b> : <O>closes µtone;</O>
|
||||
`
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -85,29 +85,29 @@ Timeline has two distinct modes: view and edit mode. Two modes are toggled using
|
||||
<b> VIEW MODE</b>
|
||||
<b>\u00B7${'\u00B8'.repeat(9)}\u00B9</b>
|
||||
&bul;Note jamming : <O>plays the note</O>
|
||||
&bul;<b>&udlr;</b> : <O>move the viewing cursor by voices and rows</O>
|
||||
&bul;<b>pg&updn;</b> : <O>go to previous/next cue</O>
|
||||
&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;<b>n</b> : <O>toggle soloing of the selected voice</O>
|
||||
&bul;<b>m</b> : <O>toggle muting of the selected voice</O>
|
||||
&bul;<b>[</b>&mdot;<b>]</b> : <O>change tick rate of playhead</O>
|
||||
&bul;<b>&udlr;</b> : <O>moves the viewing cursor by voices and rows</O>
|
||||
&bul;<b>pg&updn;</b> : <O>goes to previous/next cue</O>
|
||||
&bul;<b>W</b>&mdot;<b>E</b>&mdot;<b>R</b> : <O>toggles timeline view mode. W-most detailed, R-most abridged</O>
|
||||
&bul;<b>n</b> : <O>toggles soloing of the selected voice</O>
|
||||
&bul;<b>m</b> : <O>toggles muting of the selected voice</O>
|
||||
&bul;<b>[</b>&mdot;<b>]</b> : <O>changes tick rate of playhead</O>
|
||||
|
||||
<b> EDIT MODE</b>
|
||||
<b>\u00B7${'\u00B8'.repeat(9)}\u00B9</b>
|
||||
&bul;Note jamming : <O>(note column) inserts the note</O>
|
||||
&bul;<b>{</b>&mdot;<b>}</b> : <O>(note column) lower/raise a note by one octave (or period)</O>
|
||||
&bul;<b>[</b>&mdot;<b>]</b> : <O>(note column) lower/raise a note by one unit</O>
|
||||
&bul;<b>z</b> : <O>(note column) insert a key-off &keyoffsym;</O>
|
||||
&bul;<b>x</b> : <O>(note column) insert a note-cut ¬ecutsym;</O>
|
||||
&bul;<b>.</b> : <O>clear fields</O>
|
||||
&bul;<b>bksp</b> : <O>delete one character on the selected column</O>
|
||||
&bul;<b>{</b>&mdot;<b>}</b> : <O>(note column) lowers/raises a note by one octave (or period)</O>
|
||||
&bul;<b>[</b>&mdot;<b>]</b> : <O>(note column) lowers/raises a note by one unit</O>
|
||||
&bul;<b>z</b> : <O>(note column) inserts a key-off &keyoffsym;</O>
|
||||
&bul;<b>x</b> : <O>(note column) inserts a note-cut ¬ecutsym;</O>
|
||||
&bul;<b>.</b> : <O>clears fields</O>
|
||||
&bul;<b>bksp</b> : <O>deletes one character on the selected column</O>
|
||||
&bul;<b>0</b>&ddot;<b>9</b> <b>a</b>&ddot;<b>f</b> : <O>inserts a (hexa)decimal number</O>
|
||||
&bul;<b>0</b>&ddot;<b>9</b> <b>a</b>&ddot;<b>z</b> : <O>(fx column) inserts an effect</O>
|
||||
&bul;<b>^</b>&mdot;<b>v</b> : <O>(volume column) slide up/down</O>
|
||||
&bul;<b><</b>&mdot;<b>></b>: <O>(panning column) slide left/right</O>
|
||||
&bul;<b>-</b>&mdot;<b>=</b> : <O>(vol/pan col) fine slide down/up</O>
|
||||
&bul;<b>&udlr;</b> : <O>move the viewing cursor by columns and rows</O>
|
||||
&bul;<b>pg&updn;</b> : <O>go to previous/next cue</O>
|
||||
&bul;<b>&udlr;</b> : <O>moves the viewing cursor by columns and rows</O>
|
||||
&bul;<b>pg&updn;</b> : <O>goes to previous/next cue</O>
|
||||
|
||||
<b> ACCIDENTALS</b>
|
||||
<b>\u00B7${'\u00B8'.repeat(11)}\u00B9</b>
|
||||
@@ -116,8 +116,27 @@ Timeline has two distinct modes: view and edit mode. Two modes are toggled using
|
||||
|
||||
<b> GLOBAL EDIT</b>
|
||||
<b>\u00B7${'\u00B8'.repeat(11)}\u00B9</b>
|
||||
&bul;<b>Q</b> : <O>retune current song into different tuning and strategy</O>
|
||||
<O>In general, nearest-note works best for macrotonals, nearest-harmonic and nearest-delta works best for highly microtonals (31+); 17- and 19-TET takes nearest-harmonic pretty well, while 22-TET seem to only benefit from the nearest-note</O>
|
||||
&bul;<b>Q</b> : <O>retunes current song into different tuning and strategy. In general, nearest-note works best for macrotonals, nearest-harmonic and nearest-delta works best for highly microtonals (31+); 17- and 19-TET takes nearest-harmonic pretty well, while 22-TET seem to only benefit from the nearest-note</O>
|
||||
`
|
||||
|
||||
let helpProjectFlags = `<c>MIXER FLAGS</c>
|
||||
<c>\u00B7${'\u00B8'.repeat(11)}\u00B9</c>
|
||||
Mixer flags define how should the mixer behave.
|
||||
|
||||
<b> TONE MODE</b>
|
||||
<b>\u00B7${'\u00B8'.repeat(9)}\u00B9</b>
|
||||
&bul;Linear pitch : <O>pitch shift effects operate on linear pitch scale. The default and recommended setting for a new project</O>
|
||||
&bul;Amiga pitch : <O>pitch shift effects operate on Amiga period scale. Backwards compatible setting for MOD/S3M/XM/IT formats</O>
|
||||
&bul;Linear freq : <O>pitch shift effects operate on linear frequency scale. Backwards compatible setting for MONOTONE format</O>
|
||||
|
||||
<b> INTERPOLATION</b>
|
||||
<b>\u00B7${'\u00B8'.repeat(13)}\u00B9</b>
|
||||
&bul;Default : <O>three-tap fast sinc interpolation. The default and recommended setting for a new project</O>
|
||||
&bul;None : <O>zeroth-order hold</O>
|
||||
&bul;A500 : <O>emulates what Paula chip of Amiga 500 does. <b>S 0x00</b> effects only work with this and Amiga 1200 mode</O>
|
||||
&bul;A1200 : <O>emulates what Paula chip of Amiga 1200 does</O>
|
||||
&bul;SNES : <O>four-tap gaussian interpolation used by SNES</O>
|
||||
&bul;DPCM : <O>simulates Differential Pulse Code Modulation used by NES</O>
|
||||
`
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -380,13 +399,13 @@ function typeset(text, customWidth) {
|
||||
}
|
||||
|
||||
let helpMessages = [ // index: taut.js PANEL_NAMES
|
||||
[helpJam, helpTimeline, helpCommon, helpNotation].join(HRULE),
|
||||
[helpCommon, helpNotation].join(HRULE), // placeholder
|
||||
[helpCommon, helpNotation].join(HRULE), // placeholder
|
||||
[helpCommon, helpNotation].join(HRULE), // placeholder
|
||||
[helpCommon, helpNotation].join(HRULE), // placeholder
|
||||
[helpCommon, helpNotation].join(HRULE), // placeholder
|
||||
[helpCommon, helpNotation].join(HRULE), // placeholder
|
||||
/* Timeline */[helpJam, helpTimeline, helpCommon, helpNotation].join(HRULE),
|
||||
/* Cues */[helpCommon, helpNotation].join(HRULE), // placeholder
|
||||
/* Patterns */[helpCommon, helpNotation].join(HRULE), // placeholder
|
||||
/* Samples */[helpCommon, helpNotation].join(HRULE), // placeholder
|
||||
/* Instruments */[helpCommon, helpNotation].join(HRULE), // placeholder
|
||||
/* Project */[helpProjectFlags, helpCommon, helpNotation].join(HRULE), // placeholder
|
||||
/* File */[helpCommon, helpNotation].join(HRULE), // placeholder
|
||||
]
|
||||
|
||||
help.MSG_BY_TABS = helpMessages.map(it => typeset(it))
|
||||
|
||||
Reference in New Issue
Block a user