mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-19 02:44:04 +09:00
Compare commits
5 Commits
3ca31e57a1
...
76011d4fa9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76011d4fa9 | ||
|
|
b44d9c6b68 | ||
|
|
e47e9e1259 | ||
|
|
c5789ec28b | ||
|
|
93f7f436a3 |
@@ -618,7 +618,9 @@ ProTracker `E5x` maps to Taud `S $2x00` with the same index meaning.
|
|||||||
|
|
||||||
**Plain.** Sets the channel pan to `$xx`, with $00 being full left and $FF being full right. $80 is centre.
|
**Plain.** Sets the channel pan to `$xx`, with $00 being full left and $FF being full right. $80 is centre.
|
||||||
|
|
||||||
**Compatibility.** ST3 `S8x` uses a 4-bit value; convert by nibble-repeat: ST3 `S83` → Taud `S $8033`. Panning column command `0.$xx` has the same semantics and is the preferred form when a pan column is available in the pattern. ProTracker `8xx` (fine pan) and `E8x` (coarse pan) both map into Taud's 8-bit pan — the ProTracker 8-bit form maps directly; the 4-bit form nibble-repeats.
|
**Compatibility.** ST3 `S8x` uses a 4-bit value.
|
||||||
|
1. convert by nibble-repeat: ST3 `S83` → Taud `S $8033`. Panning column command `0.$xx` has the same semantics and is the preferred form when a pan column is available in the pattern. ProTracker `8xx` (fine pan) and `E8x` (coarse pan) both map into Taud's 8-bit pan — the ProTracker 8-bit form maps directly; the 4-bit form nibble-repeats.
|
||||||
|
2. convert to PanEff: ST3 `S8x` → PanEff `0.yy`, where `yy = round(4.2 * x)`
|
||||||
|
|
||||||
**Implementation.** Write `channel_pan = arg & $FF`. The pan value is applied at the mixer: `left_gain = (($FF − pan) × $100) >> 8`, `right_gain = (pan × $100) >> 8`, with both applied before the global volume stage.
|
**Implementation.** Write `channel_pan = arg & $FF`. The pan value is applied at the mixer: `left_gain = (($FF − pan) × $100) >> 8`, `right_gain = (pan × $100) >> 8`, with both applied before the global volume stage.
|
||||||
|
|
||||||
@@ -749,37 +751,65 @@ NOTE: **`3.00` — is No-op**
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
# Effects That Modifies Global Behaviour
|
||||||
|
|
||||||
|
Effects in this section modifies the behaviour of the mixer. Primary intention of the commands is to provide switches for legacy tracker and modern DAW behaviours.
|
||||||
|
|
||||||
|
## 1 $01xx — Set stereo panning law
|
||||||
|
|
||||||
|
**Plain.** Sets how the mixer should treat the panning. Available modes are:
|
||||||
|
|
||||||
|
- 0: Linear panning mode (tracker-accurate). Centre panning gets 3 dB boost. Default setting.
|
||||||
|
- 1: Equal-power panning mode. L/R amplitude is at 0.707 when centre-panned.
|
||||||
|
|
||||||
|
**Implementation.**
|
||||||
|
- Mode 0:
|
||||||
|
- L_gain = if (pan < 0x80) 1.0 else 1.0 - (pan - 128.0) / 128.0
|
||||||
|
- R_gain = if (pan < 0x80) pan / 128.0 else 1.0
|
||||||
|
- Mode 1:
|
||||||
|
- L_gain = cos(pi*x / 512.0)
|
||||||
|
- R_gain = sin(pi*x / 512.0)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
# ProTracker to Taud conversion table
|
# ProTracker to Taud conversion table
|
||||||
|
|
||||||
This table maps each PT effect to its Taud equivalent. Arguments follow PT's two-nibble form and expand to Taud's 16-bit form as shown.
|
This table maps each PT effect to its Taud equivalent. Arguments follow PT's two-nibble form and expand to Taud's 16-bit form as shown.
|
||||||
|
|
||||||
| PT effect | Taud effect | Notes |
|
| PT effect | Taud effect | Notes |
|
||||||
|---|---|-------------------------------------------------------------------------------------------|
|
|---------|-----------|-------|
|
||||||
| `0 $xy` | `J $xxyy` | Arpeggio; nibble-repeat each byte. See the 12-TET → Taud table above for conversion losses |
|
| `0 $xy` | `J $xxyy` | Arpeggio; nibble-repeat each byte. See the 12-TET → Taud table above for conversion losses |
|
||||||
| `1 $xx` | `F round($0xxx × 64/3)` | Portamento up; ST3 coarse slide unit = 1/16 semitone |
|
| `1 $xx` | `F round($0xxx × 64/3)` | Portamento up; ST3 coarse slide unit = 1/16 semitone |
|
||||||
| `2 $xx` | `E round($0xxx × 64/3)` | Portamento down |
|
| `2 $xx` | `E round($0xxx × 64/3)` | Portamento down |
|
||||||
| `5 $xy` | `L $xy00` | Combined portamento + volume slide (see compatibility note) |
|
| `3 $xx` | `G round($0xxx × 64/3)` | Portamento to note |
|
||||||
| `6 $xy` | `K $xy00` | Combined vibrato + volume slide (see compatibility note) |
|
| `4 $xy` | `H $xxyy` | Vibrato; nibble-repeat each byte. |
|
||||||
| `7 $xy` | `R $xxyy` | Tremolo; nibble-repeat |
|
| `5 $xy` | `L $xy00` | Combined portamento + volume slide (see compatibility note) |
|
||||||
| `8 $xx` | `S $80xx` or panning column `0.$xx` | Fine pan |
|
| `6 $xy` | `K $xy00` | Combined vibrato + volume slide (see compatibility note) |
|
||||||
| `9 $xx` | `O $xx00` | Sample offset |
|
| `7 $xy` | `R $xxyy` | Tremolo; nibble-repeat |
|
||||||
| `A $xy` | Volume column `1.$xy` | Volume slide |
|
| `8 $xx` | `S $80xx` or panning column `0.$xx` | Fine pan |
|
||||||
| `B $xx` | `B $00xx` | Position jump |
|
| `9 $xx` | `O $xx00` | Sample offset |
|
||||||
| `C $xx` | Volume column `0.$xx` | Set volume |
|
| `A $xy` | Volume column `1.$xy` | Volume slide |
|
||||||
| `D $xx` | `C $00xx` (after BCD decode) | Pattern break |
|
| `B $xx` | `B $00xx` | Position jump |
|
||||||
| `E $3x` | `S $1x00` | Glissando control |
|
| `C $xx` | Volume column `0.$xx` | Set volume |
|
||||||
| `E $4x` | `S $3x00` | Vibrato waveform |
|
| `D $xx` | `C $00xx` (after BCD decode) | Pattern break |
|
||||||
| `E $5x` | `S $2x00` | Set fine-tune |
|
| `E $0x` | `S $000x` | (UNIMPLEMENTED) Set filter |
|
||||||
| `E $6x` | `S $Bx00` | Pattern loop |
|
| `E $1x` | `E $F000 + round($0xxx × 16/3)` | Fine pitch slide up |
|
||||||
| `E $7x` | `S $4x00` | Tremolo waveform |
|
| `E $2x` | `E $F000 + round($0xxx × 16/3)` | Fine pitch slide down |
|
||||||
| `E $8x` | `S $80xx` or panning column `0.$xx` | Coarse pan (nibble-repeat) |
|
| `E $3x` | `S $1x00` | Glissando control |
|
||||||
| `E $9x` | `Q $0x00` | Retrigger |
|
| `E $4x` | `S $3x00` | Vibrato waveform |
|
||||||
| `E $Cx` | `S $Cx00` | Note cut |
|
| `E $5x` | `S $2x00` | Set fine-tune |
|
||||||
| `E $Dx` | `S $Dx00` | Note delay |
|
| `E $6x` | `S $Bx00` | Pattern loop |
|
||||||
| `E $Ex` | `S $Ex00` | Pattern delay |
|
| `E $7x` | `S $4x00` | Tremolo waveform |
|
||||||
| `E $Fx` | `S $Fx00` | Funk repeat |
|
| `E $8x` | `S $80xx` or panning column `0.$xx` | Coarse pan (nibble-repeat) |
|
||||||
| `F $xx` (xx < $20) | `A $xx00` | Set speed |
|
| `E $9x` | `Q $0x00` | Retrigger |
|
||||||
| `F $xx` (xx ≥ $20) | `T $(xx−$18)00` | Set tempo |
|
| `E $Ax` | Volume column `3.$1x` | Fine volume slide up |
|
||||||
|
| `E $Bx` | Volume column `3.$0x` | Fine volume slide down |
|
||||||
|
| `E $Cx` | `S $Cx00` | Note cut |
|
||||||
|
| `E $Dx` | `S $Dx00` | Note delay |
|
||||||
|
| `E $Ex` | `S $Ex00` | Pattern delay |
|
||||||
|
| `E $Fx` | `S $Fx00` | Funk repeat |
|
||||||
|
| `F $xx` (xx < $20) | `A $xx00` | Set speed |
|
||||||
|
| `F $xx` (xx ≥ $20) | `T $(xx−$18)00` | Set tempo |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -64,18 +64,30 @@ panri:"\u008416u",
|
|||||||
panfinele:"\u008427u",
|
panfinele:"\u008427u",
|
||||||
panfineri:"\u008426u",
|
panfineri:"\u008426u",
|
||||||
|
|
||||||
|
/* Fx/Vx/Px */
|
||||||
|
fx:'\u00F8',
|
||||||
|
px:'\u00AC',
|
||||||
|
vx:'\u00AD',
|
||||||
|
|
||||||
|
/* transport control */
|
||||||
|
playall:'\u00A8',
|
||||||
|
playcue:'\u00A9',
|
||||||
|
playrow:'\u00AA',
|
||||||
|
stop:'\u00AB',
|
||||||
|
|
||||||
/* miscellaneous */
|
/* miscellaneous */
|
||||||
unticked:"\u009E",
|
unticked:"\u00AE",
|
||||||
ticked:"\u009F",
|
ticked:"\u00AF",
|
||||||
middot:MIDDOT,
|
middot:MIDDOT,
|
||||||
doubledot:"\u008419u",
|
doubledot:"\u008419u",
|
||||||
stop:"\u008420u\u008421u",
|
statusstop:"\u008420u\u008421u",
|
||||||
play:"\u008422u\u008423u",
|
statusplay:"\u008422u\u008423u",
|
||||||
|
playhead:"\u00A7",
|
||||||
}
|
}
|
||||||
|
|
||||||
const fxNames = {
|
const fxNames = {
|
||||||
'0':"No effect ",
|
'0':"-- ",
|
||||||
'1':"UNIMPLEMENTED",
|
'1':"Mixer config ", // Taud: 1 01xx: set stereo panning law
|
||||||
'2':"UNIMPLEMENTED",
|
'2':"UNIMPLEMENTED",
|
||||||
'3':"UNIMPLEMENTED",
|
'3':"UNIMPLEMENTED",
|
||||||
'4':"UNIMPLEMENTED",
|
'4':"UNIMPLEMENTED",
|
||||||
@@ -94,26 +106,26 @@ G:"Portamento ",
|
|||||||
H:"Vibrato ",
|
H:"Vibrato ",
|
||||||
I:"Tremor ",
|
I:"Tremor ",
|
||||||
J:"Arpeggio ",
|
J:"Arpeggio ",
|
||||||
K:"UNIMPLEMENTED",
|
K:"UNIMPLEMENTED", // Volume slide+Vibrato. Use H0000 and VolEff instead
|
||||||
L:"UNIMPLEMENTED",
|
L:"UNIMPLEMENTED", // Volume slide+Portamento. Use G0000 and VolEff instead
|
||||||
M:"UNIMPLEMENTED",
|
M:"UNIMPLEMENTED", // IT: Set channel volume. Use VolEff instead
|
||||||
N:"UNIMPLEMENTED",
|
N:"UNIMPLEMENTED", // IT: Channel volume slide. Use VolEff instead
|
||||||
O:"Sample offset",
|
O:"Sample offset",
|
||||||
P:"UNIMPLEMENTED",
|
P:"UNIMPLEMENTED", // IT: panning slide. Use PanEff instead
|
||||||
Q:"Retrigger ",
|
Q:"Retrigger ",
|
||||||
R:"Tremolo ",
|
R:"Tremolo ",
|
||||||
S:"Special ",
|
S:"Special ",
|
||||||
S0:"UNIMPLEMENTED",
|
S0:"UNIMPLEMENTED", // PT: Set audio filter.
|
||||||
S1:"Gliss. ctrl ",
|
S1:"Gliss. ctrl ",
|
||||||
S2:"Sample tune ",
|
S2:"Sample tune ",
|
||||||
S3:"Vibrato LFO ",
|
S3:"Vibrato LFO ",
|
||||||
S4:"Tremolo LFO ",
|
S4:"Tremolo LFO ",
|
||||||
S5:"Panbrello LFO",
|
S5:"Panbrello LFO",
|
||||||
S6:"UNIMPLEMENTED",
|
S6:"UNIMPLEMENTED", // IT: Fine pattern delay.
|
||||||
S7:"UNIMPLEMENTED",
|
S7:"UNIMPLEMENTED", // IT: misc. functions
|
||||||
S8:"Channel pan ",
|
S8:"Channel pan ", // Taud: 8-bit channel panning.
|
||||||
S9:"UNIMPLEMENTED",
|
S9:"UNIMPLEMENTED", // IT: Sound control.
|
||||||
SA:"UNIMPLEMENTED",
|
SA:"UNIMPLEMENTED", // SC3: Stereo control. IT: Sample offset high twobyte.
|
||||||
SB:"Pattern loop ",
|
SB:"Pattern loop ",
|
||||||
SC:"Note cut ",
|
SC:"Note cut ",
|
||||||
SD:"Note delay ",
|
SD:"Note delay ",
|
||||||
@@ -122,10 +134,10 @@ SF:"Funk it ",
|
|||||||
T:"Tempo ",
|
T:"Tempo ",
|
||||||
U:"Fine vibrato ",
|
U:"Fine vibrato ",
|
||||||
V:"Global volume",
|
V:"Global volume",
|
||||||
W:"UNIMPLEMENTED",
|
W:"UNIMPLEMENTED", // IT: Global volume slide.
|
||||||
X:"UNIMPLEMENTED",
|
X:"UNIMPLEMENTED", // IT: 8-bit channel panning. Use PanEff or S80xx instead
|
||||||
Y:"Panbrello ",
|
Y:"Panbrello ",
|
||||||
Z:"UNIMPLEMENTED",
|
Z:"UNIMPLEMENTED", // IT: MIDI macro.
|
||||||
}
|
}
|
||||||
const panFxNames = {
|
const panFxNames = {
|
||||||
0:"Set to",
|
0:"Set to",
|
||||||
@@ -194,7 +206,7 @@ sym:[` \u00E0\u00E1`,` \u00E2\u00E3`,` \u00E4\u00E5`,` \u00E6\u00E7`,` \u00E8\u0
|
|||||||
const volEffSym = [sym.volset, sym.volup, sym.voldn, sym.volfineup, sym.volfinedn]
|
const volEffSym = [sym.volset, sym.volup, sym.voldn, sym.volfineup, sym.volfinedn]
|
||||||
const panEffSym = [sym.panset, sym.panle, sym.panri, sym.panfinele, sym.panfineri]
|
const panEffSym = [sym.panset, sym.panle, sym.panri, sym.panfinele, sym.panfineri]
|
||||||
|
|
||||||
const colNote = 254
|
const colNote = 239
|
||||||
const colInst = 114
|
const colInst = 114
|
||||||
const colVol = 155
|
const colVol = 155
|
||||||
const colPan = 219
|
const colPan = 219
|
||||||
@@ -202,7 +214,33 @@ const colEffOp = 220
|
|||||||
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
|
let PITCH_PRESET_IDX = 240 // TODO read from the Project Data section of the .taud
|
||||||
|
|
||||||
|
// pitchSymLut[pitchInOct] = [symString, octaveOffset]
|
||||||
|
// octaveOffset is 1 when pitchInOct is closer to the next octave's root (wraps up) than to any table entry.
|
||||||
|
// Call rebuildPitchLut() whenever PITCH_PRESET_IDX changes.
|
||||||
|
const pitchSymLut = new Array(0x1000)
|
||||||
|
|
||||||
|
function rebuildPitchLut() {
|
||||||
|
const preset = pitchTablePresets[PITCH_PRESET_IDX]
|
||||||
|
if (!preset || preset.table.length === 0) return
|
||||||
|
const table = preset.table
|
||||||
|
const syms = preset.sym
|
||||||
|
for (let p = 0; p < 0x1000; p++) {
|
||||||
|
let best = 0, bestDist = 0x1000
|
||||||
|
for (let i = 0; i < table.length; i++) {
|
||||||
|
const d = Math.abs(p - table[i])
|
||||||
|
if (d < bestDist) { bestDist = d; best = i }
|
||||||
|
}
|
||||||
|
// Distance to the next octave's root (0x1000) vs nearest table entry.
|
||||||
|
if ((0x1000 - p) < bestDist) {
|
||||||
|
pitchSymLut[p] = [syms[0], 1]
|
||||||
|
} else {
|
||||||
|
pitchSymLut[p] = [syms[best], 0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rebuildPitchLut()
|
||||||
|
|
||||||
Number.prototype.hex02 = function() {
|
Number.prototype.hex02 = function() {
|
||||||
return this.toString(16).toUpperCase().padStart(2,'0')
|
return this.toString(16).toUpperCase().padStart(2,'0')
|
||||||
@@ -231,18 +269,9 @@ function noteToStr(note) {
|
|||||||
if (note === 0xFFFF) return sym.middot.repeat(4)
|
if (note === 0xFFFF) return sym.middot.repeat(4)
|
||||||
if (note === 0xFFFE) return sym.notecut
|
if (note === 0xFFFE) return sym.notecut
|
||||||
if (note === 0x0000) return sym.keyoff
|
if (note === 0x0000) return sym.keyoff
|
||||||
const table = pitchTablePresets[PITCH_PRESET_IDX].table
|
if (pitchTablePresets[PITCH_PRESET_IDX].table.length === 0) return note.hex04()
|
||||||
const syms = pitchTablePresets[PITCH_PRESET_IDX].sym
|
const [s, o] = pitchSymLut[note & 0xFFF]
|
||||||
if (table.length === 0) return note.hex04()
|
return s + ((note >> 12) - 1 + o)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -267,7 +296,7 @@ function buildRowCell(ptnDat, row) {
|
|||||||
|
|
||||||
let sVolEff = volEffSym[voleff >>> 6]
|
let sVolEff = volEffSym[voleff >>> 6]
|
||||||
let sVolArg = voleffarg.hexD2()
|
let sVolArg = voleffarg.hexD2()
|
||||||
if (voleff === 0) {
|
if (voleff === 0xC0) {
|
||||||
sVolEff = ''
|
sVolEff = ''
|
||||||
sVolArg = sym.middot.repeat(2)
|
sVolArg = sym.middot.repeat(2)
|
||||||
}
|
}
|
||||||
@@ -291,7 +320,7 @@ function buildRowCell(ptnDat, row) {
|
|||||||
|
|
||||||
let sPanEff = panEffSym[paneff >>> 6]
|
let sPanEff = panEffSym[paneff >>> 6]
|
||||||
let sPanArg = paneffarg.hexD2()
|
let sPanArg = paneffarg.hexD2()
|
||||||
if (paneff === 0) {
|
if (paneff === 0xC0) {
|
||||||
sPanEff = ''
|
sPanEff = ''
|
||||||
sPanArg = sym.middot.repeat(2)
|
sPanArg = sym.middot.repeat(2)
|
||||||
}
|
}
|
||||||
@@ -508,7 +537,7 @@ function loadTaud(filePath, songIndex) {
|
|||||||
|
|
||||||
const [SCRH, SCRW] = con.getmaxyx()
|
const [SCRH, SCRW] = con.getmaxyx()
|
||||||
const PTNVIEW_OFFSET_X = 3
|
const PTNVIEW_OFFSET_X = 3
|
||||||
const PTNVIEW_OFFSET_Y = 9
|
const PTNVIEW_OFFSET_Y = 5
|
||||||
const PTNVIEW_HEIGHT = SCRH - PTNVIEW_OFFSET_Y
|
const PTNVIEW_HEIGHT = SCRH - PTNVIEW_OFFSET_Y
|
||||||
|
|
||||||
const TIMELINE_COLSIZES = [15, 7, 5]
|
const TIMELINE_COLSIZES = [15, 7, 5]
|
||||||
@@ -533,6 +562,15 @@ const colRowNumEmph1 = 180
|
|||||||
const colStatus = 253
|
const colStatus = 253
|
||||||
const colVoiceHdr = 230
|
const colVoiceHdr = 230
|
||||||
const colSep = 252
|
const colSep = 252
|
||||||
|
const colPushBtnBack = 143
|
||||||
|
const colTabBarBack = 187
|
||||||
|
const colTabBarOrn = 135
|
||||||
|
const colBrand = 211
|
||||||
|
|
||||||
|
|
||||||
|
// protip: avoid using colour zero
|
||||||
|
const colWHITE = 239
|
||||||
|
const colBLACK = 240
|
||||||
|
|
||||||
let separatorStyle = 0
|
let separatorStyle = 0
|
||||||
|
|
||||||
@@ -543,6 +581,11 @@ const PATEDITOR_CELL_X = 10
|
|||||||
const PATEDITOR_SEP2_X = 30
|
const PATEDITOR_SEP2_X = 30
|
||||||
const PATEDITOR_DETAIL_X = 32
|
const PATEDITOR_DETAIL_X = 32
|
||||||
|
|
||||||
|
const PLAYMODE_NONE = 0
|
||||||
|
const PLAYMODE_SONG = 1
|
||||||
|
const PLAYMODE_CUE = 2
|
||||||
|
const PLAYMODE_ROW = 3
|
||||||
|
|
||||||
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++) {
|
||||||
@@ -550,37 +593,108 @@ function fillLine(y, c, back) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const TAB_GAP = 2
|
const TAB_GAP = 3
|
||||||
const PANEL_NAMES = ['Timeline', 'Orders', 'Patterns', 'Samples', 'Instruments', 'Project', 'File']
|
const PANEL_NAMES = ['Timeline', 'Cues', 'Patterns', 'Samples', 'Instrmnt', 'Project', 'File']
|
||||||
|
|
||||||
function drawAlwaysOnElems() {
|
function drawAlwaysOnElems() {
|
||||||
drawStatusBar()
|
drawStatusBar()
|
||||||
drawTabIndicator()
|
drawTabBar()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const transportControlReverse = [PLAYMODE_NONE, PLAYMODE_ROW, PLAYMODE_CUE, PLAYMODE_SONG]
|
||||||
|
const transportControlSymbol = [sym.stop, sym.playrow, sym.playcue, sym.playall]
|
||||||
|
const transportControlColour = [160,20,20,20]
|
||||||
|
const transportControlHint = ["O","I","U","Y"]
|
||||||
function drawStatusBar() {
|
function drawStatusBar() {
|
||||||
fillLine(1, colStatus, 255)
|
fillLine(1, colStatus, 255)
|
||||||
const maxCue = song.lastActiveCue < 0 ? 0 : song.lastActiveCue
|
fillLine(2, colStatus, 255)
|
||||||
const vHi = Math.min(voiceOff + VOCSIZE_TIMELINE_FULL, song.numVoices)
|
|
||||||
const txt = `${song.filePath} Cue ${cueIdx.hex03()}/${maxCue.hex03()} Row ${cursorRow.dec02()} V${(voiceOff+1).dec02()}-${vHi.dec02()}/${song.numVoices.dec02()} BPM ${audio.getBPM(PLAYHEAD)} Spd ${audio.getTickRate(PLAYHEAD)} `
|
const sCueIdx = cueIdx.hex03()
|
||||||
con.move(1, 1)
|
const sCueMax = (song.lastActiveCue < 0 ? 0 : song.lastActiveCue).hex03()
|
||||||
|
const vMax = song.numVoices.dec02()
|
||||||
|
const vHi = Math.min(voiceOff + VOCSIZE_TIMELINE_FULL, song.numVoices).dec02()
|
||||||
|
const vLow = (voiceOff+1).dec02()
|
||||||
|
const songPath = song.filePath
|
||||||
|
const sRow = cursorRow.dec02()
|
||||||
|
const sBPM = ''+audio.getBPM(PLAYHEAD)
|
||||||
|
const sSpd = ''+audio.getTickRate(PLAYHEAD)
|
||||||
|
|
||||||
|
// transport control and its control hints
|
||||||
|
transportControlReverse.forEach((thisMode, j) => {
|
||||||
|
let active = (playbackMode == thisMode)
|
||||||
|
|
||||||
|
if (active)
|
||||||
|
con.color_pair(transportControlColour[j], colPushBtnBack)
|
||||||
|
else
|
||||||
|
con.color_pair(colStatus, 255)
|
||||||
|
|
||||||
|
con.move(1, SCRW - 5*(j+1) + 1)
|
||||||
|
print(` ${transportControlSymbol[j]} `)
|
||||||
|
|
||||||
|
if (active)
|
||||||
|
con.color_pair(transportControlColour[j], colPushBtnBack)
|
||||||
|
else
|
||||||
|
con.color_pair(colVoiceHdr, 255)
|
||||||
|
|
||||||
|
con.move(2, SCRW - 5*(j+1) + 1)
|
||||||
|
print(` ${transportControlHint[j]} `)
|
||||||
|
})
|
||||||
|
|
||||||
|
// current audio device status
|
||||||
|
// play/stop sym
|
||||||
con.color_pair(colStatus, 255)
|
con.color_pair(colStatus, 255)
|
||||||
print(txt)
|
con.move(1,1)
|
||||||
|
print(`${sym.playhead}${PLAYHEAD}`)
|
||||||
|
con.move(2,1)
|
||||||
|
print((playbackMode == PLAYMODE_NONE) ? sym.statusstop : sym.statusplay)
|
||||||
|
|
||||||
|
// cue row
|
||||||
|
con.move(1,4)
|
||||||
|
con.color_pair(colStatus, 255); print(`Cue `)
|
||||||
|
con.color_pair(colVol, 255); print(`${sCueIdx}`)
|
||||||
|
con.color_pair(colStatus, 255); print(`/`)
|
||||||
|
con.color_pair(colVol, 255); print(`${sCueMax}`)
|
||||||
|
con.color_pair(colStatus, 255); print(` Row `)
|
||||||
|
con.color_pair(colVoiceHdr, 255); print(`${sRow}`)
|
||||||
|
|
||||||
|
// bpm spd
|
||||||
|
con.move(2,4)
|
||||||
|
con.color_pair(colStatus, 255); print(`BPM `)
|
||||||
|
con.color_pair(colPan, 255); print(`${sBPM}`)
|
||||||
|
con.color_pair(colStatus, 255); print(` Tickspeed `)
|
||||||
|
con.color_pair(colEffOp, 255); print(`${sSpd}`)
|
||||||
|
|
||||||
|
// app title
|
||||||
|
let s1 = "Microtone"
|
||||||
|
let s2 = "tracker for tsvm"
|
||||||
|
con.move(1, (SCRW - (s1.length & 254)) >>> 1)
|
||||||
|
con.color_pair(colBrand, 255); print('Micro')
|
||||||
|
con.color_pair(colStatus, 255); print('tone')
|
||||||
|
con.move(2, (SCRW - (s2.length & 254)) >>> 1)
|
||||||
|
con.color_pair(colSep, 255); print('tracker for ')
|
||||||
|
con.color_pair(74, 255); print('tsvm')
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawTabIndicator() {
|
function drawTabBar() {
|
||||||
const XOFF = 2
|
con.color_pair(colTabBarOrn, colTabBarBack)
|
||||||
const YOFF = PTNVIEW_OFFSET_Y - 4
|
con.move(3,1)
|
||||||
|
print(`\u00FB`.repeat(SCRW))
|
||||||
|
|
||||||
// TODO make it fancier
|
const XOFF = 2
|
||||||
|
const YOFF = 3
|
||||||
|
|
||||||
con.move(YOFF, XOFF)
|
con.move(YOFF, XOFF)
|
||||||
for (let i = 0; i < PANEL_NAMES.length; i++) {
|
for (let i = 0; i < PANEL_NAMES.length; i++) {
|
||||||
if (i > 0) con.curs_right(TAB_GAP);
|
if (i > 0) con.curs_right(TAB_GAP);
|
||||||
let panStr = PANEL_NAMES[i]
|
let tabName = PANEL_NAMES[i]
|
||||||
print((currentPanel === i) ? `[${panStr}]` : ` ${panStr} `)
|
|
||||||
|
let col = (currentPanel === i) ? 161 : 240
|
||||||
|
|
||||||
|
con.color_pair(col, colTabBarBack); print(` ${tabName} `)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
con.color_pair(colStatus, 255)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -699,40 +813,31 @@ function drawControlHint() {
|
|||||||
[`\u008428u\u008429u`,'Nav'],
|
[`\u008428u\u008429u`,'Nav'],
|
||||||
[`Pg\u008418u`,'Cue'],
|
[`Pg\u008418u`,'Cue'],
|
||||||
['sep'],
|
['sep'],
|
||||||
['Y','Song'],
|
['WER','ViewMode'],
|
||||||
['U','Cue'],
|
|
||||||
['I','Row'],
|
|
||||||
['O/Sp','Stop'],
|
|
||||||
['sep'],
|
['sep'],
|
||||||
['m','Mute'],
|
['m','Mute'],
|
||||||
['s','Solo'],
|
['s','Solo'],
|
||||||
['sep'],
|
['sep'],
|
||||||
['Tab','Panel'],
|
['Tab','Panel'],
|
||||||
//['q','Quit'],
|
// ['sep'],
|
||||||
|
// ['q','Quit'],
|
||||||
]
|
]
|
||||||
let hintElemOrders = [
|
let hintElemOrders = [
|
||||||
[`\u008428u\u008429u`,'Nav'],
|
[`\u008428u\u008429u`,'Nav'],
|
||||||
[`Ent`,'Go to cue'],
|
[`Ent`,'Go to cue'],
|
||||||
['sep'],
|
|
||||||
['U','Cue'],
|
|
||||||
['O/Sp','Stop'],
|
|
||||||
['sep'],
|
['sep'],
|
||||||
['Tab','Panel'],
|
['Tab','Panel'],
|
||||||
['sep'],
|
// ['sep'],
|
||||||
['q','Quit'],
|
// ['q','Quit'],
|
||||||
]
|
]
|
||||||
|
|
||||||
let hintElemPatterns = [
|
let hintElemPatterns = [
|
||||||
[`\u008428u\u008429u`,'Nav'],
|
[`\u008428u\u008429u`,'Nav'],
|
||||||
[`Pg\u008418u`,'Ptn'],
|
[`Pg\u008418u`,'Ptn'],
|
||||||
['sep'],
|
|
||||||
['U','Ptn'],
|
|
||||||
['I','Row'],
|
|
||||||
['O/Sp','Stop'],
|
|
||||||
['sep'],
|
['sep'],
|
||||||
['Tab','Panel'],
|
['Tab','Panel'],
|
||||||
['sep'],
|
// ['sep'],
|
||||||
['q','Quit'],
|
// ['q','Quit'],
|
||||||
]
|
]
|
||||||
|
|
||||||
let hintElems = [hintElemTimeline, hintElemOrders, hintElemPatterns]
|
let hintElems = [hintElemTimeline, hintElemOrders, hintElemPatterns]
|
||||||
@@ -818,11 +923,10 @@ function drawVoiceDetail(isVerticalLayout = false, ptn = null, activeRow = -1, c
|
|||||||
const fxName = fxNames[fx] || '? '
|
const fxName = fxNames[fx] || '? '
|
||||||
|
|
||||||
if (!isVerticalLayout) {
|
if (!isVerticalLayout) {
|
||||||
con.move(6, 1)
|
return
|
||||||
print(`Pitch $${note.hex04()}\tInst $${inst.hex02()}\tVolEff ${voleffop}.$${voleffarg.hex02()}\t` +
|
con.move(PTNVIEW_OFFSET_Y-2, 1)
|
||||||
`PanEff ${paneffop}.$${paneffarg.hex02()}`)
|
print(`Pitch $${note.hex04()} Inst $${inst.hex02()} ${sym.vx} ${voleffop}.$${voleffarg.hex02()} ` +
|
||||||
con.move(7, 1)
|
`${sym.px} ${paneffop}.$${paneffarg.hex02()} ${sym.fx} ${fxName} $${effarg.hex04()}`)
|
||||||
print(`\u0084248u ${fxName}\t$${effarg.hex04()} `)
|
|
||||||
} else {
|
} else {
|
||||||
const dx = PATEDITOR_DETAIL_X
|
const dx = PATEDITOR_DETAIL_X
|
||||||
const detailW = SCRW - dx + 1
|
const detailW = SCRW - dx + 1
|
||||||
@@ -835,13 +939,13 @@ function drawVoiceDetail(isVerticalLayout = false, ptn = null, activeRow = -1, c
|
|||||||
if (paneff == 0xC0) { paneffop1 = 999; paneffarg1 = '' }
|
if (paneff == 0xC0) { paneffop1 = 999; paneffarg1 = '' }
|
||||||
|
|
||||||
const lines = []
|
const lines = []
|
||||||
lines.push({ label: 'Note ', value: `${noteToStr(note)} ($${note.hex04()})`, fg: colNote })
|
lines.push({ label: 'Note ', value: `${noteToStr(note)} ($${note.hex04()})`, fg: colNote })
|
||||||
lines.push({ label: 'Inst ', value: inst === 0 ? '--' : inst.hex02(), fg: colInst })
|
lines.push({ label: 'Inst ', value: inst === 0 ? '--' : inst.hex02(), fg: colInst })
|
||||||
lines.push({ label: 'VolEff', value: `${volFxNames[voleffop1]} ${voleffarg1}`, fg: colVol })
|
lines.push({ label: 'Vx ', value: `${volFxNames[voleffop1]} ${voleffarg1}`, fg: colVol })
|
||||||
lines.push({ label: 'PanEff', value: `${panFxNames[paneffop1]} ${paneffarg1}`, fg: colPan })
|
lines.push({ label: 'Px ', value: `${panFxNames[paneffop1]} ${paneffarg1}`, fg: colPan })
|
||||||
lines.push({ label: 'FxOp ', value: fx, fg: colEffOp })
|
|
||||||
lines.push({ label: 'FxArg ', value: `$${effarg.hex04()}`, fg: colEffArg })
|
|
||||||
lines.push({ label: 'Fx ', value: fxName.trimEnd(), fg: colEffOp })
|
lines.push({ label: 'Fx ', value: fxName.trimEnd(), fg: colEffOp })
|
||||||
|
lines.push({ label: 'FxOp ', value: fx, fg: colEffOp })
|
||||||
|
lines.push({ label: 'FxArg', value: `$${effarg.hex04()}`, fg: colEffArg })
|
||||||
|
|
||||||
if (cumState !== null) {
|
if (cumState !== null) {
|
||||||
lines.push({ label: '------', value: '', fg: colSep })
|
lines.push({ label: '------', value: '', fg: colSep })
|
||||||
@@ -849,9 +953,15 @@ function drawVoiceDetail(isVerticalLayout = false, ptn = null, activeRow = -1, c
|
|||||||
lines.push({ label: 'L.Inst', value: cumState.lastInst === 0 ? '--' : cumState.lastInst.hex02(), fg: colInst })
|
lines.push({ label: 'L.Inst', value: cumState.lastInst === 0 ? '--' : cumState.lastInst.hex02(), fg: colInst })
|
||||||
lines.push({ label: 'Vol ', value: `$${cumState.volAbs.hex02()}`, fg: colVol })
|
lines.push({ label: 'Vol ', value: `$${cumState.volAbs.hex02()}`, fg: colVol })
|
||||||
lines.push({ label: 'Pan ', value: `$${cumState.panAbs.hex02()}`, fg: colPan })
|
lines.push({ label: 'Pan ', value: `$${cumState.panAbs.hex02()}`, fg: colPan })
|
||||||
lines.push({ label: 'EF ', value: `$${cumState.memEF.hex04()}`, fg: colEffArg })
|
const _apo = Math.abs(cumState.pitchOff)
|
||||||
|
const _psgn = cumState.pitchOff > 0 ? '+' : cumState.pitchOff < 0 ? '-' : '='
|
||||||
|
const _absN = (cumState.lastNote !== 0xFFFF && cumState.pitchOff !== 0)
|
||||||
|
? noteToStr(Math.max(0, Math.min(0xFFFE, cumState.lastNote + cumState.pitchOff))) + ' '
|
||||||
|
: ''
|
||||||
|
lines.push({ label: 'Pitch ', value: `${_absN}(${_psgn}$${_apo.hex04()})`, fg: colNote })
|
||||||
|
lines.push({ label: `E${MIDDOT}F `, value: `$${cumState.memEF.hex04()}`, fg: colEffArg })
|
||||||
lines.push({ label: 'G ', value: `$${cumState.memG.hex04()}`, fg: colEffArg })
|
lines.push({ label: 'G ', value: `$${cumState.memG.hex04()}`, fg: colEffArg })
|
||||||
lines.push({ label: 'HU ', value: `$${cumState.memHU.speed.hex02()}/$${cumState.memHU.depth.hex02()}`, fg: colEffArg })
|
lines.push({ label: `H${MIDDOT}U `, value: `$${cumState.memHU.speed.hex02()}/$${cumState.memHU.depth.hex02()}`, fg: colEffArg })
|
||||||
lines.push({ label: 'R ', value: `$${cumState.memR.speed.hex02()}/$${cumState.memR.depth.hex02()}`, fg: colEffArg })
|
lines.push({ label: 'R ', value: `$${cumState.memR.speed.hex02()}/$${cumState.memR.depth.hex02()}`, fg: colEffArg })
|
||||||
lines.push({ label: 'Y ', value: `$${cumState.memY.speed.hex02()}/$${cumState.memY.depth.hex02()}`, fg: colEffArg })
|
lines.push({ label: 'Y ', value: `$${cumState.memY.speed.hex02()}/$${cumState.memY.depth.hex02()}`, fg: colEffArg })
|
||||||
lines.push({ label: 'D ', value: `$${cumState.memD.hex04()}`, fg: colEffArg })
|
lines.push({ label: 'D ', value: `$${cumState.memD.hex04()}`, fg: colEffArg })
|
||||||
@@ -867,7 +977,7 @@ function drawVoiceDetail(isVerticalLayout = false, ptn = null, activeRow = -1, c
|
|||||||
const y = PTNVIEW_OFFSET_Y + i
|
const y = PTNVIEW_OFFSET_Y + i
|
||||||
const line = lines[i]
|
const line = lines[i]
|
||||||
con.move(y, dx)
|
con.move(y, dx)
|
||||||
con.color_pair(colNote, 255)
|
con.color_pair(colStatus, 255)
|
||||||
print((line.label + ' ').substring(0, 6) + ' ')
|
print((line.label + ' ').substring(0, 6) + ' ')
|
||||||
con.color_pair(line.fg, 255)
|
con.color_pair(line.fg, 255)
|
||||||
print((line.value + ' '.repeat(detailW)).substring(0, detailW - 8))
|
print((line.value + ' '.repeat(detailW)).substring(0, detailW - 8))
|
||||||
@@ -1098,7 +1208,7 @@ function drawOrdersContents(wo) {
|
|||||||
print(' ')
|
print(' ')
|
||||||
// CMD column — crosshair highlight at (ordersCursor, col 0)
|
// CMD column — crosshair highlight at (ordersCursor, col 0)
|
||||||
const cmdBack = (isSel && ordersColCursor === 0) ? colPlayback : back
|
const cmdBack = (isSel && ordersColCursor === 0) ? colPlayback : back
|
||||||
con.color_pair(cue.instr ? colNote : colSep, cmdBack)
|
con.color_pair(cue.instr ? colStatus : colSep, cmdBack)
|
||||||
print(cue.instr ? cue.instr.hex02() : '--')
|
print(cue.instr ? cue.instr.hex02() : '--')
|
||||||
con.color_pair(colBackPtn, back)
|
con.color_pair(colBackPtn, back)
|
||||||
print(' ')
|
print(' ')
|
||||||
@@ -1107,7 +1217,7 @@ function drawOrdersContents(wo) {
|
|||||||
const v = ordersVoiceOff + c
|
const v = ordersVoiceOff + c
|
||||||
const ptn = v < song.numVoices ? cue.ptns[v] : CUE_EMPTY
|
const ptn = v < song.numVoices ? cue.ptns[v] : CUE_EMPTY
|
||||||
const vBack = (isSel && ordersColCursor === v + 1) ? colPlayback : back
|
const vBack = (isSel && ordersColCursor === v + 1) ? colPlayback : back
|
||||||
con.color_pair(ptn === CUE_EMPTY ? colSep : colNote, vBack)
|
con.color_pair(ptn === CUE_EMPTY ? colSep : colStatus, vBack)
|
||||||
print(ptn === CUE_EMPTY ? '---' : ptn.hex03())
|
print(ptn === CUE_EMPTY ? '---' : ptn.hex03())
|
||||||
con.color_pair(colBackPtn, back)
|
con.color_pair(colBackPtn, back)
|
||||||
print(' ')
|
print(' ')
|
||||||
@@ -1129,7 +1239,7 @@ function timelineInput(wo, event) {
|
|||||||
if (keyJustHit && shiftDown && event.includes(keys.R)) { setTimelineRowStyle(2); return }
|
if (keyJustHit && shiftDown && event.includes(keys.R)) { setTimelineRowStyle(2); return }
|
||||||
|
|
||||||
if (playbackMode !== PLAYMODE_NONE) {
|
if (playbackMode !== PLAYMODE_NONE) {
|
||||||
if (keyJustHit && shiftDown && event.includes(keys.Y) || keysym === " ") { stopPlayback(); redrawPanel() }
|
if (keyJustHit && shiftDown && event.includes(keys.Y) || keysym === " ") { stopPlayback(); redrawPanel(); drawAlwaysOnElems() }
|
||||||
else if (keysym === "<LEFT>" || keysym === "<RIGHT>") {
|
else if (keysym === "<LEFT>" || keysym === "<RIGHT>") {
|
||||||
const dir = (keysym === "<LEFT>") ? -1 : 1
|
const dir = (keysym === "<LEFT>") ? -1 : 1
|
||||||
const oldVoiceOff = voiceOff
|
const oldVoiceOff = voiceOff
|
||||||
@@ -1148,7 +1258,7 @@ function timelineInput(wo, event) {
|
|||||||
if (keyJustHit && shiftDown && event.includes(keys.Y)) { startPlaySong(); redrawPanel(); return }
|
if (keyJustHit && shiftDown && event.includes(keys.Y)) { startPlaySong(); redrawPanel(); return }
|
||||||
if (keyJustHit && shiftDown && event.includes(keys.U)) { startPlayCue(); redrawPanel(); return }
|
if (keyJustHit && shiftDown && event.includes(keys.U)) { startPlayCue(); redrawPanel(); return }
|
||||||
if ( shiftDown && event.includes(keys.I)) { startPlayRow(); drawPatternRowAt(cursorRow - scrollRow); return }
|
if ( shiftDown && event.includes(keys.I)) { startPlayRow(); drawPatternRowAt(cursorRow - scrollRow); return }
|
||||||
if (keyJustHit && shiftDown && event.includes(keys.O) || keysym === " ") { stopPlayback(); return }
|
if (keyJustHit && shiftDown && event.includes(keys.O) || keysym === " ") { stopPlayback(); drawAlwaysOnElems(); return }
|
||||||
|
|
||||||
const oldCursor = cursorRow
|
const oldCursor = cursorRow
|
||||||
const oldScroll = scrollRow
|
const oldScroll = scrollRow
|
||||||
@@ -1320,18 +1430,23 @@ function getActiveRowForDetail() {
|
|||||||
|
|
||||||
// Walk pattern rows 0..uptoRow and accumulate effect-memory cohort state
|
// Walk pattern rows 0..uptoRow and accumulate effect-memory cohort state
|
||||||
function simulateRowState(ptnDat, uptoRow) {
|
function simulateRowState(ptnDat, uptoRow) {
|
||||||
|
const OP_A = 10
|
||||||
const OP_D = 13, OP_E = 14, OP_F = 15, OP_G = 16
|
const OP_D = 13, OP_E = 14, OP_F = 15, OP_G = 16
|
||||||
const OP_H = 17, OP_I = 18, OP_J = 19, OP_O = 24
|
const OP_H = 17, OP_I = 18, OP_J = 19, OP_O = 24
|
||||||
const OP_Q = 26, OP_R = 27, OP_T = 29, OP_U = 30, OP_Y = 34
|
const OP_Q = 26, OP_R = 27, OP_T = 29, OP_U = 30, OP_Y = 34
|
||||||
|
|
||||||
let lastNote = 0xFFFF, lastInst = 0
|
let lastNote = 0xFFFF, lastInst = 0
|
||||||
let volAbs = 0x3F, panAbs = 0x20
|
let volAbs = 0x3F, panAbs = 0x20
|
||||||
|
let pitchOff = 0, portaTarget = -1
|
||||||
|
let speed = 6
|
||||||
let memEF = 0, memG = 0
|
let memEF = 0, memG = 0
|
||||||
let memHU = { speed: 0, depth: 0 }
|
let memHU = { speed: 0, depth: 0 }
|
||||||
let memR = { speed: 0, depth: 0 }
|
let memR = { speed: 0, depth: 0 }
|
||||||
let memY = { speed: 0, depth: 0 }
|
let memY = { speed: 0, depth: 0 }
|
||||||
let memD = 0, memI = 0, memJ = 0, memO = 0, memQ = 0, memTSlide = 0
|
let memD = 0, memI = 0, memJ = 0, memO = 0, memQ = 0, memTSlide = 0
|
||||||
|
|
||||||
|
const clampV = v => Math.max(0, Math.min(0x3F, v | 0))
|
||||||
|
|
||||||
const limit = Math.min(uptoRow, ROWS_PER_PAT - 1)
|
const limit = Math.min(uptoRow, ROWS_PER_PAT - 1)
|
||||||
for (let row = 0; row <= limit; row++) {
|
for (let row = 0; row <= limit; row++) {
|
||||||
const off = 8 * row
|
const off = 8 * row
|
||||||
@@ -1342,17 +1457,99 @@ function simulateRowState(ptnDat, uptoRow) {
|
|||||||
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)
|
||||||
|
|
||||||
if (note !== 0xFFFF && note !== 0xFFFE) lastNote = note
|
// Notes on a portamento row (G) become the slide target; they don't retrigger
|
||||||
|
const isGRow = (effop === OP_G)
|
||||||
|
if (note !== 0xFFFF && note !== 0xFFFE) {
|
||||||
|
if (!isGRow) {
|
||||||
|
lastNote = note
|
||||||
|
pitchOff = 0
|
||||||
|
portaTarget = -1
|
||||||
|
} else {
|
||||||
|
portaTarget = note
|
||||||
|
}
|
||||||
|
}
|
||||||
if (inst !== 0) lastInst = inst
|
if (inst !== 0) lastInst = inst
|
||||||
|
|
||||||
const volop = (voleff >>> 6) & 3
|
// Volume column: set OR slide
|
||||||
if (voleff !== 0 && volop === 0) volAbs = voleff & 63
|
const volop = (voleff >>> 6) & 3
|
||||||
const panop = (paneff >>> 6) & 3
|
const volefarg = voleff & 63
|
||||||
if (paneff !== 0 && panop === 0) panAbs = paneff & 63
|
if (voleff !== 0) {
|
||||||
|
if (volop === 0) {
|
||||||
|
volAbs = volefarg
|
||||||
|
} else if (volop === 1) {
|
||||||
|
volAbs = clampV(volAbs + (volefarg & 15) * (speed - 1))
|
||||||
|
} else if (volop === 2) {
|
||||||
|
volAbs = clampV(volAbs - (volefarg & 15) * (speed - 1))
|
||||||
|
} else if (volop === 3 && volefarg !== 0) {
|
||||||
|
if (volefarg >= 32) volAbs = clampV(volAbs + (volefarg & 15)) // fine slide up
|
||||||
|
else volAbs = clampV(volAbs - (volefarg & 15)) // fine slide down
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pan column: set OR slide
|
||||||
|
const panop = (paneff >>> 6) & 3
|
||||||
|
const panefarg = paneff & 63
|
||||||
|
if (paneff !== 0) {
|
||||||
|
if (panop === 0) {
|
||||||
|
panAbs = panefarg
|
||||||
|
} else if (panop === 1) {
|
||||||
|
panAbs = clampV(panAbs + (panefarg & 15) * (speed - 1))
|
||||||
|
} else if (panop === 2) {
|
||||||
|
panAbs = clampV(panAbs - (panefarg & 15) * (speed - 1))
|
||||||
|
} else if (panop === 3 && panefarg !== 0) {
|
||||||
|
if (panefarg >= 32) panAbs = clampV(panAbs + (panefarg & 15))
|
||||||
|
else panAbs = clampV(panAbs - (panefarg & 15))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (effop !== 0 || effarg !== 0) {
|
if (effop !== 0 || effarg !== 0) {
|
||||||
if (effop === OP_E || effop === OP_F) { if (effarg !== 0) memEF = effarg }
|
if (effop === OP_A) {
|
||||||
else if (effop === OP_G) { if (effarg !== 0) memG = effarg }
|
if ((effarg >>> 8) !== 0) speed = (effarg >>> 8)
|
||||||
|
}
|
||||||
|
else if (effop === OP_D) {
|
||||||
|
const raw = (effarg !== 0) ? (memD = effarg) : memD
|
||||||
|
if (raw !== 0) {
|
||||||
|
const hb = (raw >>> 8) & 0xFF
|
||||||
|
const hiNib = (hb >>> 4) & 0xF
|
||||||
|
const loNib = hb & 0xF
|
||||||
|
if (hiNib === 0xF) {
|
||||||
|
// $Fy00 fine slide down, but $F000/$FF00 → fine slide up by $F
|
||||||
|
if (hb === 0xFF || loNib === 0) volAbs = clampV(volAbs + 0xF)
|
||||||
|
else volAbs = clampV(volAbs - loNib)
|
||||||
|
} else if (loNib === 0xF) {
|
||||||
|
volAbs = clampV(volAbs + hiNib) // $xF00 fine slide up
|
||||||
|
} else if (hiNib === 0 && loNib !== 0) {
|
||||||
|
volAbs = clampV(volAbs - loNib * (speed - 1)) // $0y00 coarse down
|
||||||
|
} else if (hiNib !== 0 && loNib === 0) {
|
||||||
|
volAbs = clampV(volAbs + hiNib * (speed - 1)) // $x000 coarse up
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (effop === OP_E || effop === OP_F) {
|
||||||
|
const raw = (effarg !== 0) ? (memEF = effarg) : memEF
|
||||||
|
if (raw !== 0) {
|
||||||
|
const fine = (raw & 0xF000) === 0xF000
|
||||||
|
const amt = fine ? (raw & 0x0FFF) : raw * (speed - 1)
|
||||||
|
if (effop === OP_E) pitchOff -= amt
|
||||||
|
else pitchOff += amt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (effop === OP_G) {
|
||||||
|
if (effarg !== 0) memG = effarg
|
||||||
|
if (portaTarget !== -1 && memG !== 0 && lastNote !== 0xFFFF) {
|
||||||
|
const curPitch = lastNote + pitchOff
|
||||||
|
const diff = portaTarget - curPitch
|
||||||
|
if (diff !== 0) {
|
||||||
|
const absDiff = Math.abs(diff)
|
||||||
|
const maxStep = memG * (speed - 1)
|
||||||
|
pitchOff += Math.sign(diff) * Math.min(absDiff, maxStep)
|
||||||
|
if (absDiff <= maxStep) {
|
||||||
|
pitchOff = portaTarget - lastNote
|
||||||
|
portaTarget = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
else if (effop === OP_H || effop === OP_U) {
|
else if (effop === OP_H || effop === OP_U) {
|
||||||
const spd = (effarg >>> 8) & 0xFF; const dep = effarg & 0xFF
|
const spd = (effarg >>> 8) & 0xFF; const dep = effarg & 0xFF
|
||||||
if (spd !== 0) memHU.speed = spd; if (dep !== 0) memHU.depth = dep
|
if (spd !== 0) memHU.speed = spd; if (dep !== 0) memHU.depth = dep
|
||||||
@@ -1365,7 +1562,6 @@ function simulateRowState(ptnDat, uptoRow) {
|
|||||||
const spd = (effarg >>> 8) & 0xFF; const dep = effarg & 0xFF
|
const spd = (effarg >>> 8) & 0xFF; const dep = effarg & 0xFF
|
||||||
if (spd !== 0) memY.speed = spd; if (dep !== 0) memY.depth = dep
|
if (spd !== 0) memY.speed = spd; if (dep !== 0) memY.depth = dep
|
||||||
}
|
}
|
||||||
else if (effop === OP_D) { if (effarg !== 0) memD = effarg }
|
|
||||||
else if (effop === OP_I) { if (effarg !== 0) memI = effarg }
|
else if (effop === OP_I) { if (effarg !== 0) memI = effarg }
|
||||||
else if (effop === OP_J) { if (effarg !== 0) memJ = effarg }
|
else if (effop === OP_J) { if (effarg !== 0) memJ = effarg }
|
||||||
else if (effop === OP_O) { if (effarg !== 0) memO = effarg }
|
else if (effop === OP_O) { if (effarg !== 0) memO = effarg }
|
||||||
@@ -1374,7 +1570,7 @@ function simulateRowState(ptnDat, uptoRow) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { lastNote, lastInst, volAbs, panAbs,
|
return { lastNote, lastInst, volAbs, panAbs, pitchOff,
|
||||||
memEF, memG, memHU, memR, memY,
|
memEF, memG, memHU, memR, memY,
|
||||||
memD, memI, memJ, memO, memQ, memTSlide }
|
memD, memI, memJ, memO, memQ, memTSlide }
|
||||||
}
|
}
|
||||||
@@ -1389,7 +1585,7 @@ function drawPatternListColumn() {
|
|||||||
con.color_pair(255, colBackPtn)
|
con.color_pair(255, colBackPtn)
|
||||||
print(' ')
|
print(' ')
|
||||||
} else {
|
} else {
|
||||||
con.color_pair(isCur ? colNote : colRowNum, isCur ? colHighlight : 255)
|
con.color_pair(isCur ? colStatus : colRowNum, isCur ? colHighlight : 255)
|
||||||
print(pi.hex03())
|
print(pi.hex03())
|
||||||
con.color_pair(colSep, 255)
|
con.color_pair(colSep, 255)
|
||||||
print(' ')
|
print(' ')
|
||||||
@@ -1498,14 +1694,14 @@ function patternsInput(wo, event) {
|
|||||||
|
|
||||||
if (playbackMode !== PLAYMODE_NONE) {
|
if (playbackMode !== PLAYMODE_NONE) {
|
||||||
if ((keyJustHit && shiftDown && event.includes(keys.Y)) || keysym === " ") {
|
if ((keyJustHit && shiftDown && event.includes(keys.Y)) || keysym === " ") {
|
||||||
stopPlayback(); simStateKey = ''; drawPatternsContents(wo)
|
stopPlayback(); simStateKey = ''; drawPatternsContents(wo); drawAlwaysOnElems()
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keyJustHit && shiftDown && event.includes(keys.U)) { startPlayPattern(); drawPatternsContents(wo); return }
|
if (keyJustHit && shiftDown && event.includes(keys.U)) { startPlayPattern(); drawPatternsContents(wo); return }
|
||||||
if ( shiftDown && event.includes(keys.I)) { startPlayPatternRow(); drawPatternGrid(); return }
|
if ( shiftDown && event.includes(keys.I)) { startPlayPatternRow(); drawPatternGrid(); return }
|
||||||
if ((keyJustHit && shiftDown && event.includes(keys.O)) || keysym === " ") { stopPlayback(); return }
|
if ((keyJustHit && shiftDown && event.includes(keys.O)) || keysym === " ") { stopPlayback(); drawAlwaysOnElems(); return }
|
||||||
|
|
||||||
if (song.numPats === 0) return
|
if (song.numPats === 0) return
|
||||||
|
|
||||||
@@ -1560,11 +1756,6 @@ const panels = [panelTimeline, panelOrders, panelPatterns]
|
|||||||
|
|
||||||
const PLAYHEAD = 0
|
const PLAYHEAD = 0
|
||||||
|
|
||||||
const PLAYMODE_NONE = 0
|
|
||||||
const PLAYMODE_SONG = 1
|
|
||||||
const PLAYMODE_CUE = 2
|
|
||||||
const PLAYMODE_ROW = 3
|
|
||||||
|
|
||||||
// Scratch cue slot used for pattern-only preview; beyond any real cue the song uses
|
// Scratch cue slot used for pattern-only preview; beyond any real cue the song uses
|
||||||
const PREVIEW_CUE_IDX = NUM_CUES - 1
|
const PREVIEW_CUE_IDX = NUM_CUES - 1
|
||||||
|
|
||||||
@@ -1707,6 +1898,7 @@ function updatePlayback() {
|
|||||||
stopPlayback()
|
stopPlayback()
|
||||||
if (currentPanel === VIEW_TIMELINE) redrawPanel()
|
if (currentPanel === VIEW_TIMELINE) redrawPanel()
|
||||||
else if (currentPanel === 2 && song.numPats > 0) { simStateKey = ''; redrawPanel() }
|
else if (currentPanel === 2 && song.numPats > 0) { simStateKey = ''; redrawPanel() }
|
||||||
|
drawAlwaysOnElems()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (playbackMode === PLAYMODE_ROW && (nowRow !== playStartRow || nowCue !== playStartCue)) {
|
if (playbackMode === PLAYMODE_ROW && (nowRow !== playStartRow || nowCue !== playStartCue)) {
|
||||||
@@ -1848,6 +2040,8 @@ while (!exitFlag) {
|
|||||||
audio.stop(PLAYHEAD)
|
audio.stop(PLAYHEAD)
|
||||||
resetAudioDevice()
|
resetAudioDevice()
|
||||||
sys.free(SCRATCH_PTR)
|
sys.free(SCRATCH_PTR)
|
||||||
|
font.resetLowRom()
|
||||||
|
font.resetHighRom()
|
||||||
con.clear()
|
con.clear()
|
||||||
con.move(1, 1)
|
con.move(1, 1)
|
||||||
con.curs_set(1)
|
con.curs_set(1)
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -19,7 +19,8 @@ Effect support:
|
|||||||
Cxx is BCD-decoded. K/L are split into H $0000 / G $0000 + volume-column
|
Cxx is BCD-decoded. K/L are split into H $0000 / G $0000 + volume-column
|
||||||
slide. M/N/X/P fold into volume / pan columns. W (global vol slide) is
|
slide. M/N/X/P fold into volume / pan columns. W (global vol slide) is
|
||||||
dropped with a -v warning. X converts to pan column. Y (panbrello) converts
|
dropped with a -v warning. X converts to pan column. Y (panbrello) converts
|
||||||
to Taud Y. S5 selects the panbrello LFO waveform.
|
to Taud Y. S5 selects the panbrello LFO waveform. S8x converts to a pan
|
||||||
|
column SET of round(x * 4.2), mapping nibble 0-15 directly to pan 0-63.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -446,8 +447,8 @@ def encode_effect(cmd: int, arg: int, ch: int = 0, row: int = 0) -> tuple:
|
|||||||
# Panbrello LFO waveform — maps directly to Taud S$5x00.
|
# Panbrello LFO waveform — maps directly to Taud S$5x00.
|
||||||
return (TOP_S, 0x5000 | (val << 8), None, None)
|
return (TOP_S, 0x5000 | (val << 8), None, None)
|
||||||
if sub == 0x8:
|
if sub == 0x8:
|
||||||
# Coarse pan: nibble-repeat into Taud's S $80xx full-8-bit pan.
|
# S8x → PanEff 0.yy where yy = round(x * 4.2), mapping nibble 0-15 to pan 0-63.
|
||||||
return (TOP_S, 0x8000 | (val * 0x11), None, None)
|
return (TOP_NONE, 0, None, (SEL_SET, round(val * 4.2)))
|
||||||
# S0/S6/S7/S9/SA: filter, NNA, sound-control, stereo — drop silently.
|
# S0/S6/S7/S9/SA: filter, NNA, sound-control, stereo — drop silently.
|
||||||
return (TOP_NONE, 0, None, None)
|
return (TOP_NONE, 0, None, None)
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,11 @@ import net.torvald.tsvm.ThreeFiveMiniUfloat
|
|||||||
import net.torvald.tsvm.VM
|
import net.torvald.tsvm.VM
|
||||||
import net.torvald.tsvm.toInt
|
import net.torvald.tsvm.toInt
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
|
import kotlin.math.cos
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
import kotlin.math.sin
|
||||||
|
import kotlin.math.PI
|
||||||
|
|
||||||
private class RenderRunnable(val playhead: AudioAdapter.Playhead) : Runnable {
|
private class RenderRunnable(val playhead: AudioAdapter.Playhead) : Runnable {
|
||||||
private fun printdbg(msg: Any) {
|
private fun printdbg(msg: Any) {
|
||||||
@@ -1133,6 +1136,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
|||||||
// Letters A..Z map to 0x0A..0x23 (digit value 10..35).
|
// Letters A..Z map to 0x0A..0x23 (digit value 10..35).
|
||||||
private object EffectOp {
|
private object EffectOp {
|
||||||
const val OP_NONE = 0x00
|
const val OP_NONE = 0x00
|
||||||
|
const val OP_1 = 0x01
|
||||||
const val OP_A = 0x0A
|
const val OP_A = 0x0A
|
||||||
const val OP_B = 0x0B
|
const val OP_B = 0x0B
|
||||||
const val OP_C = 0x0C
|
const val OP_C = 0x0C
|
||||||
@@ -1352,6 +1356,10 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
|||||||
private fun applyEffectRow(ts: TrackerState, playhead: Playhead, voice: Voice, vi: Int, op: Int, rawArg: Int) {
|
private fun applyEffectRow(ts: TrackerState, playhead: Playhead, voice: Voice, vi: Int, op: Int, rawArg: Int) {
|
||||||
when (op) {
|
when (op) {
|
||||||
EffectOp.OP_NONE -> {}
|
EffectOp.OP_NONE -> {}
|
||||||
|
EffectOp.OP_1 -> {
|
||||||
|
// 1 $01xx — Set stereo panning law. High byte selects subcommand; only $01 is defined.
|
||||||
|
if ((rawArg ushr 8) == 0x01) ts.panLaw = rawArg and 0xFF
|
||||||
|
}
|
||||||
EffectOp.OP_A -> {
|
EffectOp.OP_A -> {
|
||||||
val tr = (rawArg ushr 8) and 0xFF
|
val tr = (rawArg ushr 8) and 0xFF
|
||||||
if (tr != 0) playhead.tickRate = tr
|
if (tr != 0) playhead.tickRate = tr
|
||||||
@@ -1733,8 +1741,21 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
|||||||
if (!voice.active || voice.muted) continue
|
if (!voice.active || voice.muted) continue
|
||||||
val s = fetchTrackerSample(voice, instruments[voice.instrumentId])
|
val s = fetchTrackerSample(voice, instruments[voice.instrumentId])
|
||||||
val vol = voice.envVolume * voice.rowVolume / 63.0 * gvol * playhead.masterVolume / 255.0
|
val vol = voice.envVolume * voice.rowVolume / 63.0 * gvol * playhead.masterVolume / 255.0
|
||||||
mixL += s * vol * (63 - voice.rowPan) / 63.0
|
val pan = voice.channelPan
|
||||||
mixR += s * vol * voice.rowPan / 63.0
|
val lGain: Double
|
||||||
|
val rGain: Double
|
||||||
|
when (ts.panLaw) {
|
||||||
|
1 -> { // equal-power: constant loudness at centre (0.707 each)
|
||||||
|
lGain = cos(PI * pan / 512.0)
|
||||||
|
rGain = sin(PI * pan / 512.0)
|
||||||
|
}
|
||||||
|
else -> { // linear balance (tracker default): centre gives 0 dB on both channels
|
||||||
|
lGain = if (pan < 0x80) 1.0 else 1.0 - (pan - 128.0) / 128.0
|
||||||
|
rGain = if (pan < 0x80) pan / 128.0 else 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mixL += s * vol * lGain
|
||||||
|
mixR += s * vol * rGain
|
||||||
}
|
}
|
||||||
|
|
||||||
ts.mixLeft[n] = mixL.toFloat().coerceIn(-1.0f, 1.0f)
|
ts.mixLeft[n] = mixL.toFloat().coerceIn(-1.0f, 1.0f)
|
||||||
@@ -1988,6 +2009,9 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
|||||||
var firstRow = true
|
var firstRow = true
|
||||||
val voices = Array(20) { Voice() }
|
val voices = Array(20) { Voice() }
|
||||||
|
|
||||||
|
// Global mixer config (effect 1).
|
||||||
|
var panLaw = 0 // 0 = linear balance (default), 1 = equal-power
|
||||||
|
|
||||||
// Pending row-end events (set during a row by B/C; consumed at row end).
|
// Pending row-end events (set during a row by B/C; consumed at row end).
|
||||||
var pendingOrderJump = -1 // -1 = none; otherwise the order index to jump to
|
var pendingOrderJump = -1 // -1 = none; otherwise the order index to jump to
|
||||||
var pendingRowJump = -1 // -1 = none; otherwise the row index for the next pattern
|
var pendingRowJump = -1 // -1 = none; otherwise the row index for the next pattern
|
||||||
@@ -2109,6 +2133,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
|||||||
ts.pendingOrderJump = -1; ts.pendingRowJump = -1
|
ts.pendingOrderJump = -1; ts.pendingRowJump = -1
|
||||||
ts.patternDelayRemaining = 0; ts.patternDelayActive = false
|
ts.patternDelayRemaining = 0; ts.patternDelayActive = false
|
||||||
ts.sexWinningChannel = -1
|
ts.sexWinningChannel = -1
|
||||||
|
ts.panLaw = 0
|
||||||
ts.voices.forEach {
|
ts.voices.forEach {
|
||||||
it.active = false
|
it.active = false
|
||||||
it.channelVolume = 0x3F
|
it.channelVolume = 0x3F
|
||||||
|
|||||||
Reference in New Issue
Block a user