mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-06 05:28:31 +09:00
Compare commits
5 Commits
a1b62f3155
...
e29f9c3032
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e29f9c3032 | ||
|
|
85b8586a3a | ||
|
|
92b9984ef8 | ||
|
|
3f98d25828 | ||
|
|
d4ea9b2d29 |
@@ -159,11 +159,11 @@ Coarse and fine modes are distinguished by the high nibble of the argument:
|
||||
- `E $F000..$FFFF` — fine slide: on tick 0 only, subtracts `arg & $0FFF` from pitch.
|
||||
- `E $0000` — recalls the last E-or-F argument and applies it as a down-slide, preserving the original form (coarse or fine).
|
||||
|
||||
**Compatibility.** This is **the single intentionally ST3-incompatible command in Taud**. ST3 pitch slides operate on Amiga periods or linear slide units; Taud operates directly on 4096-TET pitch units. Conversion from ST3 linear-mode slides uses 1 ST3 slide unit ≈ $0005 Taud units (1/64 semitone):
|
||||
**Compatibility.** This is **the single intentionally ST3-incompatible command in Taud**. ST3 pitch slides operate on Amiga periods or linear slide units; Taud operates directly on 4096-TET pitch units. Coarse and fine forms use different unit sizes:
|
||||
|
||||
- ST3 `Exx` coarse (where `xx < $E0`) → Taud `E $00xx × $0015` (one ST3 coarse unit = 1/16 semitone ≈ $0015 Taud units).
|
||||
- ST3 `EFx` fine → Taud `E $F0xx × $0015` with appropriate range packing.
|
||||
- ST3 `EEx` extra-fine → Taud `E $F0xx × $0005` (one ST3 extra-fine unit = 1/64 semitone ≈ $0005 Taud units).
|
||||
- ST3 `Exx` coarse (where `xx < $E0`) → Taud `E round($00xx × 64/3)` (1 ST3 coarse unit = 1/16 semitone = 64/3 ≈ 21.33 Taud units, rounded).
|
||||
- ST3 `EFx` fine → Taud `E $F0 round(x × 16/3)` (1 ST3 fine unit = 1/64 semitone = 16/3 ≈ 5.33 Taud units, applied once per row).
|
||||
- ST3 `EEx` extra-fine → Taud `E $F0 round(x × 16/3)` (same unit as fine, applied once per row).
|
||||
|
||||
ST3 Amiga-mode slides do not have a clean conversion and should be treated as linear-mode equivalents during import.
|
||||
|
||||
@@ -196,7 +196,7 @@ Glissando control (S $1x) snaps the output pitch to the nearest semitone after e
|
||||
|
||||
**Plain.** Raises the channel's pitch by the argument per tick, with the same mode-selection scheme as E. Coarse, fine, and memory behaviour are identical in form but inverted in direction.
|
||||
|
||||
**Compatibility.** Same as E. ST3 `Fxx` coarse, `FFx` fine, and `FEx` extra-fine convert with the same scaling factors ($0015 and $0005). F and E share one memory slot in Taud.
|
||||
**Compatibility.** Same as E. ST3 `Fxx` coarse converts using `round(x × 64/3)`; `FFx` fine and `FEx` extra-fine convert using `round(x × 16/3)`. F and E share one memory slot in Taud.
|
||||
|
||||
**Implementation.** As for E, but add instead of subtract. No upper pitch cap is defined by the effect itself, but the sample-rate conversion at the mixer will saturate well before arithmetic overflow at reasonable playing ranges.
|
||||
|
||||
@@ -206,7 +206,7 @@ Glissando control (S $1x) snaps the output pitch to the nearest semitone after e
|
||||
|
||||
**Plain.** Slides the channel's current pitch toward the note specified in the same row, at $xxxx Taud units per tick (after tick 0), stopping when the target is reached. A row with G and a note does **not** re-trigger the sample — the note's pitch becomes the portamento target and the already-sounding sample continues at its current pitch.
|
||||
|
||||
**Compatibility.** ST3 `Gxx` uses an 8-bit value in period-table units; convert to Taud using the same $0015-per-unit scale as E/F coarse (1/16 semitone per ST3 slide unit). ST3 linear mode is the expected import source; Amiga-mode G sources should be treated as linear. G has its **own** memory slot in both ST3 and Taud, so conversion is straightforward and does not suffer the shared-memory problem of E/F.
|
||||
**Compatibility.** ST3 `Gxx` uses an 8-bit value in period-table units; convert to Taud using the same `round(× 64/3)` scale as E/F coarse (1/16 semitone per ST3 slide unit). ST3 linear mode is the expected import source; Amiga-mode G sources should be treated as linear. G has its **own** memory slot in both ST3 and Taud, so conversion is straightforward and does not suffer the shared-memory problem of E/F.
|
||||
|
||||
**Implementation.**
|
||||
|
||||
@@ -517,7 +517,7 @@ Peak at maximum settings: $7F × $FF >> 9 = $3F — the full panning range. Retr
|
||||
|
||||
---
|
||||
|
||||
## X $xx00 — Set Panning
|
||||
## X $xx00 — Fine Set Panning
|
||||
|
||||
**Plain.** **Unimplemented**. On IT, sets the panning position of the current channel, $00 being full-left and $FF being full-right.
|
||||
|
||||
@@ -754,12 +754,12 @@ NOTE: **`3.00` — is No-op**
|
||||
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 |
|
||||
|---|---|---|
|
||||
|---|---|-------------------------------------------------------------------------------------------|
|
||||
| `0 $xy` | `J $xxyy` | Arpeggio; nibble-repeat each byte. See the 12-TET → Taud table above for conversion losses |
|
||||
| `1 $xx` | `F $0xxx × $0015` | Portamento up; ST3 slide unit = 1/16 semitone |
|
||||
| `2 $xx` | `E $0xxx × $0015` | Portamento down |
|
||||
| `5 $xy` | `L $xy00` | Combined portamento + volume slide |
|
||||
| `6 $xy` | `K $xy00` | Combined vibrato + volume slide |
|
||||
| `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 |
|
||||
| `5 $xy` | `L $xy00` | Combined portamento + volume slide (see compatibility note) |
|
||||
| `6 $xy` | `K $xy00` | Combined vibrato + volume slide (see compatibility note) |
|
||||
| `7 $xy` | `R $xxyy` | Tremolo; nibble-repeat |
|
||||
| `8 $xx` | `S $80xx` or panning column `0.$xx` | Fine pan |
|
||||
| `9 $xx` | `O $xx00` | Sample offset |
|
||||
@@ -803,7 +803,7 @@ These quirks of ST3 are worth preserving or flagging when importing S3M files in
|
||||
|
||||
**Global volume scale.** ST3's 0..$40 maps to Taud's 0..$FF with a ×4 scale on import, truncated ÷4 on export.
|
||||
|
||||
**Linear pitch slides.** ST3's slide arithmetic is period-based (Amiga) or linear-table-indexed; Taud's is purely linear in 4096-TET units. ST3 songs in linear mode convert cleanly via the $0015-per-unit coarse and $0005-per-unit extra-fine constants; Amiga-mode slides change character slightly because the non-linearity of period math is not replicated.
|
||||
**Linear pitch slides.** ST3's slide arithmetic is period-based (Amiga) or linear-table-indexed; Taud's is purely linear in 4096-TET units. ST3 songs in linear mode convert cleanly: coarse forms (Exx/Fxx/Gxx) use `round(× 64/3)` (1/16 semitone per unit), fine/extra-fine forms (EFx/EEx/FFx/FEx) use `round(× 16/3)` (1/64 semitone per unit). Amiga-mode slides change character slightly because the non-linearity of period math is not replicated.
|
||||
|
||||
**Default tempo byte.** Taud's default $65 equals 125 BPM under the $18 offset; this is not the same as ST3's `$7D` default, which maps to Taud `$65` after subtracting $18. Converters must remap on both import and export.
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
@@ -1,4 +1,6 @@
|
||||
const win = require("wintex")
|
||||
const keys = require("keysym")
|
||||
|
||||
const COL_TEXT = 253
|
||||
const COL_BACK = 255
|
||||
const COL_BACK_SEL = 81
|
||||
@@ -673,7 +675,7 @@ while (!exit) {
|
||||
let keysym = event[1]
|
||||
let keyJustHit = (1 == event[2])
|
||||
|
||||
if (keyJustHit && event[3] != 66) { // release the latch right away if the key is not Return
|
||||
if (keyJustHit && event[3] != keys.ENTER) { // release the latch right away if the key is not Return
|
||||
firstRunLatch = false
|
||||
}
|
||||
|
||||
|
||||
80
assets/disk0/tvdos/include/keysym.mjs
Normal file
80
assets/disk0/tvdos/include/keysym.mjs
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* These are key symbols returned by `input.withEvent`, NOT `con.getch()`
|
||||
*/
|
||||
|
||||
exports = {
|
||||
NUM_0:7,
|
||||
NUM_1:8,
|
||||
NUM_2:9,
|
||||
NUM_3:10,
|
||||
NUM_4:11,
|
||||
NUM_5:12,
|
||||
NUM_6:13,
|
||||
NUM_7:14,
|
||||
NUM_8:15,
|
||||
NUM_9:16,
|
||||
A:29,
|
||||
ALT_LEFT:57,
|
||||
ALT_RIGHT:58,
|
||||
APOSTROPHE:75,
|
||||
AT:77,
|
||||
B:30,
|
||||
BACK:4,
|
||||
BACKSLASH:73,
|
||||
C:31,
|
||||
CAPS_LOCK:115,
|
||||
COMMA:55,
|
||||
D:32,
|
||||
DEL:67,
|
||||
BACKSPACE:67,
|
||||
FORWARD_DEL:112,
|
||||
DOWN:20,
|
||||
LEFT:21,
|
||||
RIGHT:22,
|
||||
UP:19,
|
||||
E:33,
|
||||
ENTER:66,
|
||||
EQUALS:70,
|
||||
F:34,
|
||||
G:35,
|
||||
GRAVE:68,
|
||||
H:36,
|
||||
HOME:3,
|
||||
I:37,
|
||||
J:38,
|
||||
K:39,
|
||||
L:40,
|
||||
LEFT_BRACKET:71,
|
||||
M:41,
|
||||
MINUS:69,
|
||||
N:42,
|
||||
O:43,
|
||||
P:44,
|
||||
PERIOD:56,
|
||||
PLUS:81,
|
||||
Q:45,
|
||||
R:46,
|
||||
RIGHT_BRACKET:72,
|
||||
S:47,
|
||||
SEMICOLON:74,
|
||||
SHIFT_LEFT:59,
|
||||
SHIFT_RIGHT:60,
|
||||
SLASH:76,
|
||||
SPACE:62,
|
||||
SYM:63, // on MacOS, this is Command (⌘)
|
||||
T:48,
|
||||
TAB:61,
|
||||
U:49,
|
||||
V:50,
|
||||
W:51,
|
||||
X:52,
|
||||
Y:53,
|
||||
Z:54,
|
||||
CONTROL_LEFT:129,
|
||||
CONTROL_RIGHT:130,
|
||||
ESCAPE:111,
|
||||
END:123,
|
||||
INSERT:124,
|
||||
PAGE_UP:92,
|
||||
PAGE_DOWN:93,
|
||||
}
|
||||
23
s3m2taud.py
23
s3m2taud.py
@@ -390,18 +390,16 @@ def encode_effect(cmd: int, arg: int, ch: int = 0, row: int = 0) -> tuple:
|
||||
return (TOP_D, (arg & 0xFF) << 8, None, None)
|
||||
|
||||
if cmd in (EFF_E, EFF_F):
|
||||
# ST3 slide unit = 1/16 semitone = $0015 Taud units (per spec PT table).
|
||||
# Coarse: 1/16 semitone = 64/3 Taud units. Fine/extra-fine: 1/64 semitone = 16/3.
|
||||
op = TOP_E if cmd == EFF_E else TOP_F
|
||||
hi = (arg >> 4) & 0xF
|
||||
lo = arg & 0xF
|
||||
if hi == 0xF and lo > 0:
|
||||
return (op, 0xF000 | ((lo * 0x15) & 0xFFF), None, None)
|
||||
if hi == 0xE and lo > 0:
|
||||
return (op, 0xF000 | ((lo * 0x05) & 0xFFF), None, None)
|
||||
return (op, (arg * 0x15) & 0xFFFF, None, None)
|
||||
if hi in (0xE, 0xF) and lo > 0:
|
||||
return (op, 0xF000 | (round(lo * 16 / 3) & 0xFFF), None, None)
|
||||
return (op, round(arg * 64 / 3) & 0xFFFF, None, None)
|
||||
|
||||
if cmd == EFF_G:
|
||||
return (TOP_G, (arg * 0x15) & 0xFFFF, None, None)
|
||||
return (TOP_G, round(arg * 64 / 3) & 0xFFFF, None, None)
|
||||
|
||||
if cmd in (EFF_H, EFF_I, EFF_R, EFF_U):
|
||||
op = {EFF_H: TOP_H, EFF_I: TOP_I, EFF_R: TOP_R, EFF_U: TOP_U}[cmd]
|
||||
@@ -901,16 +899,15 @@ def assemble_taud(h: S3MHeader, instruments: list, patterns: list) -> bytes:
|
||||
|
||||
# Song table row (16 bytes): offset(4)+voices(1)+patsLo(1)+patsHi(1)+bpm(1)+tick(1)+basenote(2)+basefreq(4)+pad(1)
|
||||
# Built after dedup so num_taud_pats reflects the unique count.
|
||||
num_taud_pats_lo = num_taud_pats & 0xFF
|
||||
num_taud_pats_hi = (num_taud_pats >> 8) & 0xFF
|
||||
song_table = struct.pack('<IBBBBB',
|
||||
song_table = struct.pack('<IBHBBHf',
|
||||
song_offset,
|
||||
C,
|
||||
num_taud_pats_lo,
|
||||
num_taud_pats_hi,
|
||||
num_taud_pats,
|
||||
bpm_stored,
|
||||
speed,
|
||||
) + b'\x00\x90' + b'\x00\xAC\xD02\x46' + b'\x00'
|
||||
0x9000, # C8
|
||||
8363.0, # Hz
|
||||
) + b'\x00'
|
||||
assert len(song_table) == TAUD_SONG_ENTRY
|
||||
|
||||
# Cue sheet (using remapped pattern indices)
|
||||
|
||||
@@ -2121,6 +2121,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
|
||||
it.funkSpeed = 0
|
||||
it.funkAccumulator = 0
|
||||
it.funkWritePos = 0
|
||||
it.muted = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Reference in New Issue
Block a user