mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-06 05:28:31 +09:00
Compare commits
7 Commits
44f11120d8
...
8d28bde119
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d28bde119 | ||
|
|
755afb7df4 | ||
|
|
539df453ec | ||
|
|
5e3ffea6d3 | ||
|
|
4bda55d511 | ||
|
|
852c0e6e80 | ||
|
|
25309cf5b6 |
@@ -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:
|
||||
|
||||
- **`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.
|
||||
- **`2.$xx` — Volume slide down** by `$xx` per non-first tick (6-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.
|
||||
- **`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 (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 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).
|
||||
|
||||
@@ -696,8 +696,8 @@ NOTE: **`3.00` — is No-op**
|
||||
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.
|
||||
- **`1.$xx` — Pan slide right** by `$xx` per non-first tick.
|
||||
- **`2.$xx` — Pan slide left** 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 (4-bit).
|
||||
- **`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**
|
||||
|
||||
@@ -10,6 +10,9 @@ const taud = require("taud")
|
||||
|
||||
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 BIGDOT = "\u00F9"
|
||||
const BULLET = "\u00847u"
|
||||
@@ -47,13 +50,13 @@ keyoff:"\u00A0\u00CD\u00CD\u00A1",
|
||||
notecut:"\u00A4\u00A4\u00A4\u00A4",
|
||||
|
||||
/* special effects */
|
||||
volset:MIDDOT,
|
||||
volset:'',//MIDDOT,
|
||||
volup:"\u008430u",
|
||||
voldn:"\u008431u",
|
||||
volfineup:"+",
|
||||
volfinedn:"-",
|
||||
|
||||
panset:MIDDOT,
|
||||
panset:'',//MIDDOT,
|
||||
panle:"\u008417u",
|
||||
panri:"\u008416u",
|
||||
panfinele:"\u008427u",
|
||||
@@ -65,6 +68,55 @@ ticked:"\u009F",
|
||||
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 = {
|
||||
// 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
|
||||
@@ -121,6 +173,8 @@ const colEffOp = 213
|
||||
const colEffArg = 231
|
||||
const colBackPtn = 255
|
||||
|
||||
const PITCH_PRESET_IDX = 240 // TODO read from the Project Data section of the .taud
|
||||
|
||||
Number.prototype.hex02 = function() {
|
||||
return this.toString(16).toUpperCase().padStart(2,'0')
|
||||
}
|
||||
@@ -133,6 +187,9 @@ Number.prototype.hex04 = function() {
|
||||
Number.prototype.hexD2 = function() {
|
||||
return this.toString(16).toUpperCase().padStart(2, sym.middot)
|
||||
}
|
||||
Number.prototype.hex1 = function() {
|
||||
return this.toString(16).toUpperCase()
|
||||
}
|
||||
Number.prototype.dec02 = function() {
|
||||
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.
|
||||
*/
|
||||
@@ -156,53 +231,56 @@ function buildRowCell(ptnDat, row) {
|
||||
const effop = ptnDat[off+5]
|
||||
const effarg = ptnDat[off+6] | (ptnDat[off+7] << 8)
|
||||
|
||||
let sNote = note.hex04()
|
||||
if (note == 0xFFFF) sNote = sym.middot.repeat(4)
|
||||
else if (note == 0xFFFE) sNote = sym.notecut
|
||||
else if (note == 0x0000) sNote = sym.keyoff
|
||||
const sNote = noteToStr(note)
|
||||
|
||||
let sInst = inst.hexD2()
|
||||
if (inst == 0) sInst = sym.middot.repeat(2)
|
||||
|
||||
let sVolEff = volEffSym[voleff >>> 6]
|
||||
let sVolArg = voleffarg.decD2()
|
||||
let sVolArg = voleffarg.hexD2()
|
||||
if (voleff === 0) {
|
||||
sVolEff = sym.middot
|
||||
sVolEff = ''
|
||||
sVolArg = sym.middot.repeat(2)
|
||||
}
|
||||
else if (voleff >>> 6 == 1 || voleff >>> 6 == 2) {
|
||||
sVolArg = (voleffarg & 15).hex1()
|
||||
}
|
||||
else if (voleff >>> 6 == 3) {
|
||||
if (voleffarg == 0) {
|
||||
sVolEff = sym.middot
|
||||
sVolArg = sym.middot.repeat(2)
|
||||
sVolArg = sym.middot.repeat(1)
|
||||
}
|
||||
else if (voleffarg >= 32) {
|
||||
sVolEff = volEffSym[3]
|
||||
sVolArg = (voleffarg & 31).dec02()
|
||||
sVolArg = (voleffarg & 15).hex1()
|
||||
}
|
||||
else {
|
||||
sVolEff = volEffSym[4]
|
||||
sVolArg = (voleffarg & 31).dec02()
|
||||
sVolArg = (voleffarg & 15).hex1()
|
||||
}
|
||||
}
|
||||
|
||||
let sPanEff = panEffSym[paneff >>> 6]
|
||||
let sPanArg = paneffarg.decD2()
|
||||
let sPanArg = paneffarg.hexD2()
|
||||
if (paneff === 0) {
|
||||
sPanEff = sym.middot
|
||||
sPanEff = ''
|
||||
sPanArg = sym.middot.repeat(2)
|
||||
}
|
||||
else if (paneff >>> 6 == 1 || paneff >>> 6 == 2) {
|
||||
sPanArg = (paneffarg & 15).hex1()
|
||||
}
|
||||
else if (paneff >>> 6 == 3) {
|
||||
if (paneffarg == 0) {
|
||||
sPanEff = sym.middot
|
||||
sPanArg = sym.middot.repeat(2)
|
||||
sPanArg = sym.middot.repeat(1)
|
||||
}
|
||||
else if (paneffarg >= 32) {
|
||||
sPanEff = panEffSym[4]
|
||||
sPanArg = (paneffarg & 31).dec02()
|
||||
sPanArg = (paneffarg & 15).hex1()
|
||||
}
|
||||
else {
|
||||
sPanEff = panEffSym[3]
|
||||
sPanArg = (paneffarg & 31).dec02()
|
||||
sPanArg = (paneffarg & 15).hex1()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,9 +297,9 @@ function buildRowCell(ptnDat, row) {
|
||||
const EMPTY_CELL = {
|
||||
sNote: sym.middot.repeat(4),
|
||||
sInst: sym.middot.repeat(3),
|
||||
sVolEff: sym.middot,
|
||||
sVolEff: '',
|
||||
sVolArg: sym.middot.repeat(2),
|
||||
sPanEff: sym.middot,
|
||||
sPanEff: '',
|
||||
sPanArg: sym.middot.repeat(2),
|
||||
sEffOp: sym.middot,
|
||||
sEffArg: sym.middot.repeat(4)
|
||||
@@ -339,11 +417,11 @@ function loadTaud(filePath, songIndex) {
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const [SCRH, SCRW] = con.getmaxyx()
|
||||
const PTNVIEW_OFFSET_X = 5
|
||||
const PTNVIEW_OFFSET_X = 3
|
||||
const PTNVIEW_OFFSET_Y = 9
|
||||
const PTNVIEW_HEIGHT = SCRH - PTNVIEW_OFFSET_Y
|
||||
const COLSIZE = 18
|
||||
const VOCSIZE = 4
|
||||
const COLSIZE = 15
|
||||
const VOCSIZE = 5
|
||||
|
||||
const VIEW_TIMELINE = 0
|
||||
const VIEW_ORDERS = 1
|
||||
@@ -358,6 +436,8 @@ const colStatus = 253
|
||||
const colVoiceHdr = 230
|
||||
const colSep = 252
|
||||
|
||||
let separatorStyle = 0
|
||||
|
||||
function fillLine(y, c, back) {
|
||||
con.color_pair(c, back)
|
||||
for (let x = 1; x <= SCRW; x++) {
|
||||
@@ -375,13 +455,32 @@ function drawStatusBar() {
|
||||
print(txt)
|
||||
}
|
||||
|
||||
function drawSeparators(posY, col_size) {
|
||||
/**
|
||||
* @param style 0: condensed timeline, 1: vertical bars between voices
|
||||
*/
|
||||
function drawSeparators(style) {
|
||||
if (style == 1) {
|
||||
con.color_pair(colSep, 255)
|
||||
for (let c = 0; c < VOCSIZE - 1; c++) {
|
||||
con.move(posY, PTNVIEW_OFFSET_X + col_size * (c+1) - 1)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
function drawVoiceHeaders() {
|
||||
fillLine(PTNVIEW_OFFSET_Y - 1, colVoiceHdr, 255)
|
||||
@@ -405,7 +504,7 @@ function drawVoiceHeaders() {
|
||||
}
|
||||
}
|
||||
|
||||
drawSeparators(PTNVIEW_OFFSET_Y - 1, COLSIZE)
|
||||
drawSeparators(separatorStyle)
|
||||
}
|
||||
|
||||
function drawPatternRowAt(viewRow) {
|
||||
@@ -440,7 +539,7 @@ function drawPatternRowAt(viewRow) {
|
||||
drawCellAt(y, x, cell, back)
|
||||
}
|
||||
|
||||
drawSeparators(y, COLSIZE)
|
||||
drawSeparators(separatorStyle)
|
||||
}
|
||||
|
||||
function drawPatternView() {
|
||||
@@ -449,9 +548,8 @@ function drawPatternView() {
|
||||
|
||||
function drawControlHint() {
|
||||
let hintElem = [
|
||||
[`\u008424u\u008425u`,'Row'],
|
||||
[`\u008427u\u008426u`,'Vox'],
|
||||
[`Pg\u008424u\u008425u`,'Ptn'],
|
||||
[`\u008427u\u008425u\u008424u\u008426u`,'Ptn'],
|
||||
[`Pg\u008424u\u008425u`,'Cue'],
|
||||
['sep'],
|
||||
['F5','Song'],
|
||||
['F6','Cue'],
|
||||
@@ -459,7 +557,9 @@ function drawControlHint() {
|
||||
['F8/Sp','Stop'],
|
||||
['sep'],
|
||||
['m','Mute'],
|
||||
['s','Solo']
|
||||
['s','Solo'],
|
||||
['sep'],
|
||||
['q','Quit'],
|
||||
]
|
||||
|
||||
// erase current line
|
||||
@@ -529,10 +629,18 @@ function drawVoiceDetail() {
|
||||
const effarg = ptnDat[6] | (ptnDat[7] << 8)
|
||||
|
||||
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)
|
||||
print(`Pitch $${note.hex04()}\tInst $${inst.hex02()}\tVolEff ${voleffop}.${voleffarg.dec02()}\t`+
|
||||
`PanEff ${paneffop}.${paneffarg.dec02()}\tFx ${effop.toString(36).toUpperCase()}.${effarg.hex04()}`)
|
||||
let fx = effop.toString(36).toUpperCase()
|
||||
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() {
|
||||
@@ -541,6 +649,7 @@ function drawAll() {
|
||||
drawVoiceHeaders()
|
||||
drawPatternView()
|
||||
drawVoiceDetail()
|
||||
drawSeparators(separatorStyle)
|
||||
drawControlHint()
|
||||
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
|
||||
const SCRATCH_PTR = sys.malloc(SCRW * PTNVIEW_HEIGHT)
|
||||
|
||||
// Horizontal salvage: 3 carried voice columns minus the missing trailing separator.
|
||||
// For shift-left: source x=23..75 (old cols 1,2,3); dest x=5..57 (new cols 0,1,2).
|
||||
// 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
|
||||
// Horizontal salvage
|
||||
const SALVAGE_HORIZ_LEN = (VOCSIZE - 1) * COLSIZE
|
||||
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
drawCellAt(y, x, cell, back)
|
||||
drawSeparators(y, COLSIZE)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -789,6 +893,7 @@ function updatePlayback() {
|
||||
drawPatternRowAt(cursorRow - scrollRow)
|
||||
}
|
||||
drawStatusBar()
|
||||
drawSeparators(separatorStyle)
|
||||
drawVoiceDetail()
|
||||
}
|
||||
}
|
||||
@@ -809,7 +914,9 @@ function clampVoice() {
|
||||
if (cursorVox < 0) cursorVox = 0
|
||||
if (cursorVox >= song.numVoices) cursorVox = song.numVoices - 1
|
||||
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)
|
||||
if (voiceOff < 0) voiceOff = 0
|
||||
if (voiceOff > maxOff) voiceOff = maxOff
|
||||
@@ -836,6 +943,8 @@ while (!exitFlag) {
|
||||
input.withEvent(event => {
|
||||
if (event[0] !== "key_down") return
|
||||
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") {
|
||||
exitFlag = true
|
||||
@@ -846,7 +955,7 @@ while (!exitFlag) {
|
||||
if (keysym === "<F8>" || keysym === " ") { stopPlayback(); drawAll() }
|
||||
else if (keysym === "<LEFT>" || keysym === "<RIGHT>") {
|
||||
const oldVoiceOff = voiceOff
|
||||
cursorVox += (keysym === "<LEFT>") ? -1 : 1
|
||||
cursorVox += (keysym === "<LEFT>") ? -moveDelta : moveDelta
|
||||
clampVoice()
|
||||
const dVoice = voiceOff - oldVoiceOff
|
||||
if (dVoice !== 0) {
|
||||
@@ -854,6 +963,7 @@ while (!exitFlag) {
|
||||
drawVoiceColumnAt(dVoice > 0 ? VOCSIZE - 1 : 0)
|
||||
}
|
||||
drawVoiceHeaders()
|
||||
drawSeparators(separatorStyle)
|
||||
drawStatusBar()
|
||||
}
|
||||
else if (keysym === "m" || keysym === "M") { toggleMute(cursorVox) }
|
||||
@@ -873,7 +983,7 @@ while (!exitFlag) {
|
||||
|
||||
if (keysym === "<LEFT>" || keysym === "<RIGHT>") {
|
||||
const oldVoiceOff = voiceOff
|
||||
cursorVox += (keysym === "<LEFT>") ? -1 : 1
|
||||
cursorVox += (keysym === "<LEFT>") ? -moveDelta : moveDelta
|
||||
clampVoice()
|
||||
const dVoice = voiceOff - oldVoiceOff
|
||||
if (dVoice !== 0) {
|
||||
@@ -881,6 +991,7 @@ while (!exitFlag) {
|
||||
drawVoiceColumnAt(dVoice > 0 ? VOCSIZE - 1 : 0)
|
||||
}
|
||||
drawVoiceHeaders()
|
||||
drawSeparators(separatorStyle)
|
||||
drawStatusBar()
|
||||
drawVoiceDetail()
|
||||
return
|
||||
@@ -889,12 +1000,12 @@ while (!exitFlag) {
|
||||
if (keysym === "m" || keysym === "M") { toggleMute(cursorVox); return }
|
||||
if (keysym === "s" || keysym === "S") { toggleSolo(cursorVox); return }
|
||||
|
||||
if (keysym === "<UP>") { cursorRow -= 1; rowMove = true }
|
||||
else if (keysym === "<DOWN>") { cursorRow += 1; rowMove = true }
|
||||
if (keysym === "<UP>") { cursorRow -= moveDelta; rowMove = true }
|
||||
else if (keysym === "<DOWN>") { cursorRow += moveDelta; rowMove = true }
|
||||
else if (keysym === "<HOME>") { cursorRow = 0; 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_DOWN>") { cueIdx += 1; fullRedraw = true }
|
||||
else if (keysym === "<PAGE_UP>") { cueIdx -= moveDelta; fullRedraw = true }
|
||||
else if (keysym === "<PAGE_DOWN>") { cueIdx += moveDelta; fullRedraw = true }
|
||||
else return
|
||||
|
||||
clampCursor(); clampVoice(); clampCue()
|
||||
@@ -933,6 +1044,7 @@ while (!exitFlag) {
|
||||
drawPatternRowAt(cursorRow - scrollRow)
|
||||
}
|
||||
|
||||
drawSeparators(separatorStyle)
|
||||
drawStatusBar()
|
||||
drawVoiceDetail()
|
||||
})
|
||||
|
||||
@@ -40,7 +40,8 @@ const COL_HL_EXT = {
|
||||
"tap": 190,
|
||||
"txt": 223,
|
||||
"md": 223,
|
||||
"log": 223
|
||||
"log": 223,
|
||||
"taud":109,
|
||||
}
|
||||
|
||||
const EXEC_FUNS = {
|
||||
@@ -62,7 +63,8 @@ const EXEC_FUNS = {
|
||||
"bas": (f) => _G.shell.execute(`basic "${f}"`),
|
||||
"txt": (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
|
||||
|
||||
@@ -219,8 +219,8 @@ function captureTrackerDataToFile(outFile) {
|
||||
numPats & 0xFF, (numPats >>> 8) & 0xFF, // numPatterns Uint16 LE
|
||||
bpmStored, // BPM with −24 bias
|
||||
tickRate, // initial tick-rate
|
||||
0x40,0, // basenote
|
||||
0x13,0xd0,0x82,0x43, // basefreq
|
||||
0x00,0x4C, // basenote (0x4C00 -- A3)
|
||||
0x00,0x00,0xDC,0x43, // basefreq (440 Hz)
|
||||
0, // padding
|
||||
]
|
||||
|
||||
|
||||
@@ -904,7 +904,7 @@ def assemble_taud(h: S3MHeader, instruments: list, patterns: list) -> bytes:
|
||||
num_taud_pats_hi,
|
||||
bpm_stored,
|
||||
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
|
||||
|
||||
# Cue sheet (using remapped pattern indices)
|
||||
|
||||
@@ -2210,8 +2210,8 @@ Rows of 16 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 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
|
||||
Float32 Frequency at the base note. Default (A440) is 261.6255653. 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 440.0. If zero, assume the default value
|
||||
Byte[1] Reserved for future versions
|
||||
|
||||
Taud device can queue up to 2 "playdata" in its buffer, which can be interpreted as a song.
|
||||
|
||||
Reference in New Issue
Block a user