mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-15 08:54:05 +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:
|
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**
|
||||||
|
|||||||
@@ -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()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
Reference in New Issue
Block a user