Compare commits

...

7 Commits

Author SHA1 Message Date
minjaesong
8d28bde119 taud base note def changed (A3@440Hz) 2026-04-23 23:11:09 +09:00
minjaesong
755afb7df4 reatable fx names; horz scroll fix 2026-04-23 22:23:15 +09:00
minjaesong
539df453ec taut: fix voleff 'v1' rendering as 'v.1' 2026-04-23 21:51:47 +09:00
minjaesong
5e3ffea6d3 taut: better scrolling behav(2) 2026-04-23 21:18:30 +09:00
minjaesong
4bda55d511 taut: better scrolling behav 2026-04-23 21:12:11 +09:00
minjaesong
852c0e6e80 taut: displaying note symbol 2026-04-23 21:03:20 +09:00
minjaesong
25309cf5b6 format revision and tracker GUI 2026-04-23 20:48:40 +09:00
6 changed files with 177 additions and 63 deletions

View File

@@ -679,9 +679,9 @@ on sample byte read during loop playback:
Each cell carries a 6-bit value field plus a 2-bit selector field for the volume column. The four selectors are: Each cell carries a 6-bit value field plus a 2-bit selector field for the volume column. The four selectors are:
- **`0.$xx` — Set volume** to `$xx` (6-bit, $00..$3F). Equivalent to a note's default volume. - **`0.$xx` — Set volume** to `$xx` (6-bit, $00..$3F). Equivalent to a note's default volume.
- **`1.$xx` — Volume slide up** by `$xx` per non-first tick (6-bit). Volume clamps at $3F. - **`1.$xx` — Volume slide up** by `$xx` per non-first tick (4-bit). Volume clamps at $3F.
- **`2.$xx` — Volume slide down** by `$xx` per non-first tick (6-bit). Volume clamps at $00. - **`2.$xx` — Volume slide down** by `$xx` per non-first tick (4-bit). Volume clamps at $00.
- **`3.$Sx` — Fine volume slide** on tick 0 only. The high bit `$S` of the value selects direction (0 = down, 1 = up); the low 5 bits `$x` ($00..$1F) are the magnitude. Equivalent in scale to `D $xF00` / `D $Fy00` but with a 5-bit cap. Fires once per row regardless of speed. - **`3.$Sx` — Fine volume slide** on tick 0 only. The high bit `$S` of the value selects direction (0 = down, 1 = up); the low 4 bits `$x` ($0..$F) are the magnitude. Equivalent in scale to `D $xF00` / `D $Fy00` but with a 5-bit cap. Fires once per row regardless of speed.
Volume-column effects do not consume the main effect slot; a cell can carry both (for instance, a tone portamento in the effect slot and a volume slide in the volume column). Volume-column effects do not consume the main effect slot; a cell can carry both (for instance, a tone portamento in the effect slot and a volume slide in the volume column).
@@ -696,8 +696,8 @@ NOTE: **`3.00` — is No-op**
The panning column uses the same 6-bit value + 2-bit selector layout: The panning column uses the same 6-bit value + 2-bit selector layout:
- **`0.$xx` — Set pan** (6-bit, $00..$3F mapped onto the channel's 8-bit pan space; $01 = full left, $1F = centre-left, $20 = centre-right, $3F = full right). For 8-bit precision use `S $80xx` instead. - **`0.$xx` — Set pan** (6-bit, $00..$3F mapped onto the channel's 8-bit pan space; $01 = full left, $1F = centre-left, $20 = centre-right, $3F = full right). For 8-bit precision use `S $80xx` instead.
- **`1.$xx` — Pan slide right** by `$xx` per non-first tick. - **`1.$xx` — Pan slide right** by `$xx` per non-first tick (4-bit).
- **`2.$xx` — Pan slide left** by `$xx` per non-first tick. - **`2.$xx` — Pan slide left** by `$xx` per non-first tick (4-bit).
- **`3.$Sx` — Fine pan slide** on tick 0 only, same direction-bit encoding as the volume column's selector 3. - **`3.$Sx` — Fine pan slide** on tick 0 only, same direction-bit encoding as the volume column's selector 3.
NOTE: **`3.00` — is No-op** NOTE: **`3.00` — is No-op**

View File

@@ -10,6 +10,9 @@ const taud = require("taud")
font.setHighRom("A:/tvdos/bin/tautfont_high.chr") font.setHighRom("A:/tvdos/bin/tautfont_high.chr")
const BUILD_DATE = "260423"
const TRACKER_SIGNATURE = "TsvmTaut"+BUILD_DATE // 14-byte string
const MIDDOT = "\u00FA" const MIDDOT = "\u00FA"
const BIGDOT = "\u00F9" const BIGDOT = "\u00F9"
const BULLET = "\u00847u" const BULLET = "\u00847u"
@@ -47,13 +50,13 @@ keyoff:"\u00A0\u00CD\u00CD\u00A1",
notecut:"\u00A4\u00A4\u00A4\u00A4", notecut:"\u00A4\u00A4\u00A4\u00A4",
/* special effects */ /* special effects */
volset:MIDDOT, volset:'',//MIDDOT,
volup:"\u008430u", volup:"\u008430u",
voldn:"\u008431u", voldn:"\u008431u",
volfineup:"+", volfineup:"+",
volfinedn:"-", volfinedn:"-",
panset:MIDDOT, panset:'',//MIDDOT,
panle:"\u008417u", panle:"\u008417u",
panri:"\u008416u", panri:"\u008416u",
panfinele:"\u008427u", panfinele:"\u008427u",
@@ -65,6 +68,55 @@ ticked:"\u009F",
middot:MIDDOT middot:MIDDOT
} }
const fxNames = {
A:"Set tick speed",
B:"Jump to order",
C:"Break pattern to",
D:"Volume slide",
E:"Pitch down",
F:"Pitch up",
G:"Portamento",
H:"Vibrato",
U:"Fine vibrato",
I:"Tremor",
J:"Arpeggio",
K:"Vibrato + vol slide",
L:"Portamento + vol slide",
O:"Sample offset",
Q:"Retrigger",
R:"Tremolo",
T:"Tempo",
V:"Gloval volume",
S:"Special",
S1:"Glissando ctrl",
S2:"Sample finetune",
S3:"Vibrato LFO",
S4:"Tremolo LFO",
S8:"Channel pan",
SB:"Pattern loop",
SC:"Note cut",
SD:"Note delay",
SE:"Pattern delay",
SF:"Funk it"
}
const panFxNames = {
0:"Set",
1:"Pan slide L",
2:"Pan slide R",
3:"Fine pan slide",
30:"Fine pan slide L",
31:"Fine pan slide R"
}
const volFxNames = {
0:"Set",
1:"Vol slide UP",
2:"Vol slide DN",
3:"Fine vol slide",
30:"Fine vol slide DN",
31:"Fine vol slide UP"
}
const pitchTablePresets = { const pitchTablePresets = {
// index: pitch table number to be recorded on .taudproj file // index: pitch table number to be recorded on .taudproj file
0:{index:0,name:"null", table:[], sym:[]}, // when null is specified, hex numbers will be displayed instead 0:{index:0,name:"null", table:[], sym:[]}, // when null is specified, hex numbers will be displayed instead
@@ -121,6 +173,8 @@ const colEffOp = 213
const colEffArg = 231 const colEffArg = 231
const colBackPtn = 255 const colBackPtn = 255
const PITCH_PRESET_IDX = 240 // TODO read from the Project Data section of the .taud
Number.prototype.hex02 = function() { Number.prototype.hex02 = function() {
return this.toString(16).toUpperCase().padStart(2,'0') return this.toString(16).toUpperCase().padStart(2,'0')
} }
@@ -133,6 +187,9 @@ Number.prototype.hex04 = function() {
Number.prototype.hexD2 = function() { Number.prototype.hexD2 = function() {
return this.toString(16).toUpperCase().padStart(2, sym.middot) return this.toString(16).toUpperCase().padStart(2, sym.middot)
} }
Number.prototype.hex1 = function() {
return this.toString(16).toUpperCase()
}
Number.prototype.dec02 = function() { Number.prototype.dec02 = function() {
return this.toString(10).toUpperCase().padStart(2,'0') return this.toString(10).toUpperCase().padStart(2,'0')
} }
@@ -141,6 +198,24 @@ Number.prototype.decD2 = function() {
} }
function noteToStr(note) {
if (note === 0xFFFF) return sym.middot.repeat(4)
if (note === 0xFFFE) return sym.notecut
if (note === 0x0000) return sym.keyoff
const table = pitchTablePresets[PITCH_PRESET_IDX].table
const syms = pitchTablePresets[PITCH_PRESET_IDX].sym
if (table.length === 0) return note.hex04()
const pitchInOct = note & 0xFFF
const octave = (note >> 12) - 1
let best = 0, bestDist = 0x1000
for (let i = 0; i < table.length; i++) {
const d = Math.abs(pitchInOct - table[i])
if (d < bestDist) { bestDist = d; best = i }
}
if ((0x1000 - pitchInOct) < bestDist) return syms[0] + (octave + 1)
return syms[best] + octave
}
/** /**
* Builds the coloured string fragments for a single row of pattern data. * Builds the coloured string fragments for a single row of pattern data.
*/ */
@@ -156,53 +231,56 @@ function buildRowCell(ptnDat, row) {
const effop = ptnDat[off+5] const effop = ptnDat[off+5]
const effarg = ptnDat[off+6] | (ptnDat[off+7] << 8) const effarg = ptnDat[off+6] | (ptnDat[off+7] << 8)
let sNote = note.hex04() const sNote = noteToStr(note)
if (note == 0xFFFF) sNote = sym.middot.repeat(4)
else if (note == 0xFFFE) sNote = sym.notecut
else if (note == 0x0000) sNote = sym.keyoff
let sInst = inst.hexD2() let sInst = inst.hexD2()
if (inst == 0) sInst = sym.middot.repeat(2) if (inst == 0) sInst = sym.middot.repeat(2)
let sVolEff = volEffSym[voleff >>> 6] let sVolEff = volEffSym[voleff >>> 6]
let sVolArg = voleffarg.decD2() let sVolArg = voleffarg.hexD2()
if (voleff === 0) { if (voleff === 0) {
sVolEff = sym.middot sVolEff = ''
sVolArg = sym.middot.repeat(2) sVolArg = sym.middot.repeat(2)
} }
else if (voleff >>> 6 == 1 || voleff >>> 6 == 2) {
sVolArg = (voleffarg & 15).hex1()
}
else if (voleff >>> 6 == 3) { else if (voleff >>> 6 == 3) {
if (voleffarg == 0) { if (voleffarg == 0) {
sVolEff = sym.middot sVolEff = sym.middot
sVolArg = sym.middot.repeat(2) sVolArg = sym.middot.repeat(1)
} }
else if (voleffarg >= 32) { else if (voleffarg >= 32) {
sVolEff = volEffSym[3] sVolEff = volEffSym[3]
sVolArg = (voleffarg & 31).dec02() sVolArg = (voleffarg & 15).hex1()
} }
else { else {
sVolEff = volEffSym[4] sVolEff = volEffSym[4]
sVolArg = (voleffarg & 31).dec02() sVolArg = (voleffarg & 15).hex1()
} }
} }
let sPanEff = panEffSym[paneff >>> 6] let sPanEff = panEffSym[paneff >>> 6]
let sPanArg = paneffarg.decD2() let sPanArg = paneffarg.hexD2()
if (paneff === 0) { if (paneff === 0) {
sPanEff = sym.middot sPanEff = ''
sPanArg = sym.middot.repeat(2) sPanArg = sym.middot.repeat(2)
} }
else if (paneff >>> 6 == 1 || paneff >>> 6 == 2) {
sPanArg = (paneffarg & 15).hex1()
}
else if (paneff >>> 6 == 3) { else if (paneff >>> 6 == 3) {
if (paneffarg == 0) { if (paneffarg == 0) {
sPanEff = sym.middot sPanEff = sym.middot
sPanArg = sym.middot.repeat(2) sPanArg = sym.middot.repeat(1)
} }
else if (paneffarg >= 32) { else if (paneffarg >= 32) {
sPanEff = panEffSym[4] sPanEff = panEffSym[4]
sPanArg = (paneffarg & 31).dec02() sPanArg = (paneffarg & 15).hex1()
} }
else { else {
sPanEff = panEffSym[3] sPanEff = panEffSym[3]
sPanArg = (paneffarg & 31).dec02() sPanArg = (paneffarg & 15).hex1()
} }
} }
@@ -219,9 +297,9 @@ function buildRowCell(ptnDat, row) {
const EMPTY_CELL = { const EMPTY_CELL = {
sNote: sym.middot.repeat(4), sNote: sym.middot.repeat(4),
sInst: sym.middot.repeat(3), sInst: sym.middot.repeat(3),
sVolEff: sym.middot, sVolEff: '',
sVolArg: sym.middot.repeat(2), sVolArg: sym.middot.repeat(2),
sPanEff: sym.middot, sPanEff: '',
sPanArg: sym.middot.repeat(2), sPanArg: sym.middot.repeat(2),
sEffOp: sym.middot, sEffOp: sym.middot,
sEffArg: sym.middot.repeat(4) sEffArg: sym.middot.repeat(4)
@@ -339,11 +417,11 @@ function loadTaud(filePath, songIndex) {
///////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////
const [SCRH, SCRW] = con.getmaxyx() const [SCRH, SCRW] = con.getmaxyx()
const PTNVIEW_OFFSET_X = 5 const PTNVIEW_OFFSET_X = 3
const PTNVIEW_OFFSET_Y = 9 const PTNVIEW_OFFSET_Y = 9
const PTNVIEW_HEIGHT = SCRH - PTNVIEW_OFFSET_Y const PTNVIEW_HEIGHT = SCRH - PTNVIEW_OFFSET_Y
const COLSIZE = 18 const COLSIZE = 15
const VOCSIZE = 4 const VOCSIZE = 5
const VIEW_TIMELINE = 0 const VIEW_TIMELINE = 0
const VIEW_ORDERS = 1 const VIEW_ORDERS = 1
@@ -358,6 +436,8 @@ const colStatus = 253
const colVoiceHdr = 230 const colVoiceHdr = 230
const colSep = 252 const colSep = 252
let separatorStyle = 0
function fillLine(y, c, back) { function fillLine(y, c, back) {
con.color_pair(c, back) con.color_pair(c, back)
for (let x = 1; x <= SCRW; x++) { for (let x = 1; x <= SCRW; x++) {
@@ -369,17 +449,36 @@ function drawStatusBar() {
fillLine(1, colStatus, 255) fillLine(1, colStatus, 255)
const maxCue = song.lastActiveCue < 0 ? 0 : song.lastActiveCue const maxCue = song.lastActiveCue < 0 ? 0 : song.lastActiveCue
const vHi = Math.min(voiceOff + VOCSIZE, song.numVoices) const vHi = Math.min(voiceOff + VOCSIZE, song.numVoices)
const txt = ` ${song.filePath} Cue ${cueIdx.hex03()}/${maxCue.hex03()} Row ${cursorRow.dec02()} V${(voiceOff+1).dec02()}-${vHi.dec02()}/${song.numVoices.dec02()} BPM ${song.bpm} Spd ${song.tickRate} ` const txt = `${song.filePath} Cue ${cueIdx.hex03()}/${maxCue.hex03()} Row ${cursorRow.dec02()} V${(voiceOff+1).dec02()}-${vHi.dec02()}/${song.numVoices.dec02()} BPM ${song.bpm} Spd ${song.tickRate} `
con.move(1, 1) con.move(1, 1)
con.color_pair(colStatus, 255) con.color_pair(colStatus, 255)
print(txt) print(txt)
} }
function drawSeparators(posY, col_size) { /**
con.color_pair(colSep, 255) * @param style 0: condensed timeline, 1: vertical bars between voices
for (let c = 0; c < VOCSIZE - 1; c++) { */
con.move(posY, PTNVIEW_OFFSET_X + col_size * (c+1) - 1) function drawSeparators(style) {
con.prnch(0xB3) if (style == 1) {
con.color_pair(colSep, 255)
for (let c = 0; c < VOCSIZE - 1; c++) {
for (let y = PTNVIEW_OFFSET_Y - 1; y < PTNVIEW_HEIGHT; y++) {
con.move(y, PTNVIEW_OFFSET_X + COLSIZE * (c+1) - 1)
con.prnch(0xB3)
}
}
}
else {
// paint the first column of pattern view with colour
for (let x = PTNVIEW_OFFSET_X; x < SCRW - 3; x += COLSIZE) {
for (let y = 0; y < PTNVIEW_HEIGHT+1; y++) {
let memOffset = (y+PTNVIEW_OFFSET_Y-2) * SCRW + (x-1)
let bgColOffset = GPU_MEM - TEXT_BACK_OFF - memOffset
sys.poke(bgColOffset, colHighlight)
}
}
con.color_pair(colSep, 255)
} }
} }
@@ -400,12 +499,12 @@ function drawVoiceHeaders() {
const ptnIdx = cue.ptns[voice] const ptnIdx = cue.ptns[voice]
const vlabel = `V${(voice+1).dec02()}` const vlabel = `V${(voice+1).dec02()}`
const plabel = (ptnIdx === CUE_EMPTY) ? '---' : ptnIdx.hex03() const plabel = (ptnIdx === CUE_EMPTY) ? '---' : ptnIdx.hex03()
const label = ` ${vlabel} ptn ${plabel} ` const label = ` ${vlabel} ptn ${plabel} `
print((label + ' ').substring(0, COLSIZE - 1)) print((label + ' ').substring(0, COLSIZE - 1))
} }
} }
drawSeparators(PTNVIEW_OFFSET_Y - 1, COLSIZE) drawSeparators(separatorStyle)
} }
function drawPatternRowAt(viewRow) { function drawPatternRowAt(viewRow) {
@@ -440,7 +539,7 @@ function drawPatternRowAt(viewRow) {
drawCellAt(y, x, cell, back) drawCellAt(y, x, cell, back)
} }
drawSeparators(y, COLSIZE) drawSeparators(separatorStyle)
} }
function drawPatternView() { function drawPatternView() {
@@ -449,9 +548,8 @@ function drawPatternView() {
function drawControlHint() { function drawControlHint() {
let hintElem = [ let hintElem = [
[`\u008424u\u008425u`,'Row'], [`\u008427u\u008425u\u008424u\u008426u`,'Ptn'],
[`\u008427u\u008426u`,'Vox'], [`Pg\u008424u\u008425u`,'Cue'],
[`Pg\u008424u\u008425u`,'Ptn'],
['sep'], ['sep'],
['F5','Song'], ['F5','Song'],
['F6','Cue'], ['F6','Cue'],
@@ -459,7 +557,9 @@ function drawControlHint() {
['F8/Sp','Stop'], ['F8/Sp','Stop'],
['sep'], ['sep'],
['m','Mute'], ['m','Mute'],
['s','Solo'] ['s','Solo'],
['sep'],
['q','Quit'],
] ]
// erase current line // erase current line
@@ -529,10 +629,18 @@ function drawVoiceDetail() {
const effarg = ptnDat[6] | (ptnDat[7] << 8) const effarg = ptnDat[6] | (ptnDat[7] << 8)
con.move(6,1) con.move(6,1)
print(`Pattern $${ptnIdx.hex02()}\tRow ${cursorRow.dec02()}\tVoice ${cursorVox}`) print(`Pitch $${note.hex04()}\tInst $${inst.hex02()}\tVolEff ${voleffop}.$${voleffarg.hex02()}\t`+
`PanEff ${paneffop}.$${paneffarg.hex02()}`)
con.move(7,1) con.move(7,1)
print(`Pitch $${note.hex04()}\tInst $${inst.hex02()}\tVolEff ${voleffop}.${voleffarg.dec02()}\t`+ let fx = effop.toString(36).toUpperCase()
`PanEff ${paneffop}.${paneffarg.dec02()}\tFx ${effop.toString(36).toUpperCase()}.${effarg.hex04()}`) if (fx == '0') {
print(`Fx`+' '.repeat(32))
}
else {
if (fx == 'S') fx += (effarg >>> 12).hex1()
let fxName = fxNames[fx]
print(`Fx ${fxName} $${effarg.hex04()} `)
}
} }
function drawAll() { function drawAll() {
@@ -541,6 +649,7 @@ function drawAll() {
drawVoiceHeaders() drawVoiceHeaders()
drawPatternView() drawPatternView()
drawVoiceDetail() drawVoiceDetail()
drawSeparators(separatorStyle)
drawControlHint() drawControlHint()
con.move(1, 1) con.move(1, 1)
} }
@@ -564,12 +673,8 @@ const TEXT_PLANES = [TEXT_CHAR_OFF, TEXT_BACK_OFF, TEXT_FORE_OFF]
// One scratch strip, reused across shifts // One scratch strip, reused across shifts
const SCRATCH_PTR = sys.malloc(SCRW * PTNVIEW_HEIGHT) const SCRATCH_PTR = sys.malloc(SCRW * PTNVIEW_HEIGHT)
// Horizontal salvage: 3 carried voice columns minus the missing trailing separator. // Horizontal salvage
// For shift-left: source x=23..75 (old cols 1,2,3); dest x=5..57 (new cols 0,1,2). const SALVAGE_HORIZ_LEN = (VOCSIZE - 1) * COLSIZE
// For shift-right: source x=5..57 (old cols 0,1,2); dest x=23..75 (new cols 1,2,3).
// The separator at the boundary of the exposed column is already in place after
// the shift (it was never overwritten), so no extra separator fix-up is needed.
const SALVAGE_HORIZ_LEN = (VOCSIZE - 1) * COLSIZE - 1 // 53 chars
/** /**
* Shift the pattern-view rows by `dy` lines (positive = down, negative = up) * Shift the pattern-view rows by `dy` lines (positive = down, negative = up)
@@ -639,7 +744,6 @@ function drawVoiceColumnAt(slot) {
cell = buildRowCell(song.patterns[ptnIdx], actualRow) cell = buildRowCell(song.patterns[ptnIdx], actualRow)
} }
drawCellAt(y, x, cell, back) drawCellAt(y, x, cell, back)
drawSeparators(y, COLSIZE)
} }
} }
@@ -789,6 +893,7 @@ function updatePlayback() {
drawPatternRowAt(cursorRow - scrollRow) drawPatternRowAt(cursorRow - scrollRow)
} }
drawStatusBar() drawStatusBar()
drawSeparators(separatorStyle)
drawVoiceDetail() drawVoiceDetail()
} }
} }
@@ -809,7 +914,9 @@ function clampVoice() {
if (cursorVox < 0) cursorVox = 0 if (cursorVox < 0) cursorVox = 0
if (cursorVox >= song.numVoices) cursorVox = song.numVoices - 1 if (cursorVox >= song.numVoices) cursorVox = song.numVoices - 1
if (cursorVox < voiceOff) voiceOff = cursorVox if (cursorVox < voiceOff) voiceOff = cursorVox
if (cursorVox >= voiceOff + VOCSIZE) voiceOff = cursorVox - VOCSIZE + 1 // keep cursor centred until view reaches an edge (mirrors clampCursor logic)
if (cursorVox < voiceOff + (VOCSIZE>>>1) && voiceOff > 0) voiceOff = cursorVox - (VOCSIZE>>>1)
if (cursorVox >= voiceOff + ((VOCSIZE+1)>>>1)) voiceOff = cursorVox - ((VOCSIZE+1)>>>1) + 1
const maxOff = Math.max(0, song.numVoices - VOCSIZE) const maxOff = Math.max(0, song.numVoices - VOCSIZE)
if (voiceOff < 0) voiceOff = 0 if (voiceOff < 0) voiceOff = 0
if (voiceOff > maxOff) voiceOff = maxOff if (voiceOff > maxOff) voiceOff = maxOff
@@ -836,6 +943,8 @@ while (!exitFlag) {
input.withEvent(event => { input.withEvent(event => {
if (event[0] !== "key_down") return if (event[0] !== "key_down") return
const keysym = event[1] const keysym = event[1]
const shiftDown = (event.indexOf(59) > 0 || event.indexOf(60) > 0)
const moveDelta = shiftDown ? 4 : 1
if (keysym === "<ESC>" || keysym === "q" || keysym === "Q") { if (keysym === "<ESC>" || keysym === "q" || keysym === "Q") {
exitFlag = true exitFlag = true
@@ -846,7 +955,7 @@ while (!exitFlag) {
if (keysym === "<F8>" || keysym === " ") { stopPlayback(); drawAll() } if (keysym === "<F8>" || keysym === " ") { stopPlayback(); drawAll() }
else if (keysym === "<LEFT>" || keysym === "<RIGHT>") { else if (keysym === "<LEFT>" || keysym === "<RIGHT>") {
const oldVoiceOff = voiceOff const oldVoiceOff = voiceOff
cursorVox += (keysym === "<LEFT>") ? -1 : 1 cursorVox += (keysym === "<LEFT>") ? -moveDelta : moveDelta
clampVoice() clampVoice()
const dVoice = voiceOff - oldVoiceOff const dVoice = voiceOff - oldVoiceOff
if (dVoice !== 0) { if (dVoice !== 0) {
@@ -854,6 +963,7 @@ while (!exitFlag) {
drawVoiceColumnAt(dVoice > 0 ? VOCSIZE - 1 : 0) drawVoiceColumnAt(dVoice > 0 ? VOCSIZE - 1 : 0)
} }
drawVoiceHeaders() drawVoiceHeaders()
drawSeparators(separatorStyle)
drawStatusBar() drawStatusBar()
} }
else if (keysym === "m" || keysym === "M") { toggleMute(cursorVox) } else if (keysym === "m" || keysym === "M") { toggleMute(cursorVox) }
@@ -873,7 +983,7 @@ while (!exitFlag) {
if (keysym === "<LEFT>" || keysym === "<RIGHT>") { if (keysym === "<LEFT>" || keysym === "<RIGHT>") {
const oldVoiceOff = voiceOff const oldVoiceOff = voiceOff
cursorVox += (keysym === "<LEFT>") ? -1 : 1 cursorVox += (keysym === "<LEFT>") ? -moveDelta : moveDelta
clampVoice() clampVoice()
const dVoice = voiceOff - oldVoiceOff const dVoice = voiceOff - oldVoiceOff
if (dVoice !== 0) { if (dVoice !== 0) {
@@ -881,6 +991,7 @@ while (!exitFlag) {
drawVoiceColumnAt(dVoice > 0 ? VOCSIZE - 1 : 0) drawVoiceColumnAt(dVoice > 0 ? VOCSIZE - 1 : 0)
} }
drawVoiceHeaders() drawVoiceHeaders()
drawSeparators(separatorStyle)
drawStatusBar() drawStatusBar()
drawVoiceDetail() drawVoiceDetail()
return return
@@ -889,12 +1000,12 @@ while (!exitFlag) {
if (keysym === "m" || keysym === "M") { toggleMute(cursorVox); return } if (keysym === "m" || keysym === "M") { toggleMute(cursorVox); return }
if (keysym === "s" || keysym === "S") { toggleSolo(cursorVox); return } if (keysym === "s" || keysym === "S") { toggleSolo(cursorVox); return }
if (keysym === "<UP>") { cursorRow -= 1; rowMove = true } if (keysym === "<UP>") { cursorRow -= moveDelta; rowMove = true }
else if (keysym === "<DOWN>") { cursorRow += 1; rowMove = true } else if (keysym === "<DOWN>") { cursorRow += moveDelta; rowMove = true }
else if (keysym === "<HOME>") { cursorRow = 0; rowMove = true } else if (keysym === "<HOME>") { cursorRow = 0; rowMove = true }
else if (keysym === "<END>") { cursorRow = ROWS_PER_PAT-1; rowMove = true } else if (keysym === "<END>") { cursorRow = ROWS_PER_PAT-1; rowMove = true }
else if (keysym === "<PAGE_UP>") { cueIdx -= 1; fullRedraw = true } else if (keysym === "<PAGE_UP>") { cueIdx -= moveDelta; fullRedraw = true }
else if (keysym === "<PAGE_DOWN>") { cueIdx += 1; fullRedraw = true } else if (keysym === "<PAGE_DOWN>") { cueIdx += moveDelta; fullRedraw = true }
else return else return
clampCursor(); clampVoice(); clampCue() clampCursor(); clampVoice(); clampCue()
@@ -933,6 +1044,7 @@ while (!exitFlag) {
drawPatternRowAt(cursorRow - scrollRow) drawPatternRowAt(cursorRow - scrollRow)
} }
drawSeparators(separatorStyle)
drawStatusBar() drawStatusBar()
drawVoiceDetail() drawVoiceDetail()
}) })

View File

@@ -40,7 +40,8 @@ const COL_HL_EXT = {
"tap": 190, "tap": 190,
"txt": 223, "txt": 223,
"md": 223, "md": 223,
"log": 223 "log": 223,
"taud":109,
} }
const EXEC_FUNS = { const EXEC_FUNS = {
@@ -62,7 +63,8 @@ const EXEC_FUNS = {
"bas": (f) => _G.shell.execute(`basic "${f}"`), "bas": (f) => _G.shell.execute(`basic "${f}"`),
"txt": (f) => _G.shell.execute(`less "${f}"`), "txt": (f) => _G.shell.execute(`less "${f}"`),
"md": (f) => _G.shell.execute(`less "${f}"`), "md": (f) => _G.shell.execute(`less "${f}"`),
"log": (f) => _G.shell.execute(`less "${f}"`) "log": (f) => _G.shell.execute(`less "${f}"`),
"taud": (f) => _G.shell.execute(`taut "${f}"`),
} }
let windowMode = 0 // 0 == left, 1 == right let windowMode = 0 // 0 == left, 1 == right

View File

@@ -219,8 +219,8 @@ function captureTrackerDataToFile(outFile) {
numPats & 0xFF, (numPats >>> 8) & 0xFF, // numPatterns Uint16 LE numPats & 0xFF, (numPats >>> 8) & 0xFF, // numPatterns Uint16 LE
bpmStored, // BPM with 24 bias bpmStored, // BPM with 24 bias
tickRate, // initial tick-rate tickRate, // initial tick-rate
0x40,0, // basenote 0x00,0x4C, // basenote (0x4C00 -- A3)
0x13,0xd0,0x82,0x43, // basefreq 0x00,0x00,0xDC,0x43, // basefreq (440 Hz)
0, // padding 0, // padding
] ]

View File

@@ -904,7 +904,7 @@ def assemble_taud(h: S3MHeader, instruments: list, patterns: list) -> bytes:
num_taud_pats_hi, num_taud_pats_hi,
bpm_stored, bpm_stored,
speed, speed,
) + b'\x40\x00' + b'\x13\xd0\x82\x43' + b'\x00' ) + b'\x00\x4C' + b'\x00\x00\xDC\x43' + b'\x00'
assert len(song_table) == TAUD_SONG_ENTRY assert len(song_table) == TAUD_SONG_ENTRY
# Cue sheet (using remapped pattern indices) # Cue sheet (using remapped pattern indices)

View File

@@ -2210,8 +2210,8 @@ Rows of 16 bytes:
Uint16 Number of patterns (0 is invalid. pattern bin length = numPats * 8 bytes) Uint16 Number of patterns (0 is invalid. pattern bin length = numPats * 8 bytes)
Uint8 Initial BPM (bias of -24. 0x00=24, 0xFF=279) Uint8 Initial BPM (bias of -24. 0x00=24, 0xFF=279)
Uint8 Initial Tickrate (0 is invalid) Uint8 Initial Tickrate (0 is invalid)
Uint16 Current Tuning base note (1..65533), assuming octave 3. C3 (the default value) is 0x4000. If zero, assume the default value Uint16 Current Tuning base note (1..65533). A3 (the default value) is 0x4C00. If zero, assume the default value
Float32 Frequency at the base note. Default (A440) is 261.6255653. If zero, assume the default value Float32 Frequency at the base note. Default (A440) is 440.0. If zero, assume the default value
Byte[1] Reserved for future versions Byte[1] Reserved for future versions
Taud device can queue up to 2 "playdata" in its buffer, which can be interpreted as a song. Taud device can queue up to 2 "playdata" in its buffer, which can be interpreted as a song.