mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-06 05:28:31 +09:00
Compare commits
3 Commits
0a247897e4
...
606fa736af
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
606fa736af | ||
|
|
89effb5b24 | ||
|
|
376c3c4766 |
@@ -493,7 +493,7 @@ A tempo slide's memory slot is separate from the set-tempo path and is private t
|
||||
|
||||
**Plain.** Sets the global mix bus volume (0..$FF). $00 is silence; $FF is full. The default is $80.
|
||||
|
||||
**Compatibility.** ST3's global volume is 0..$40; convert with `taud_v = st3_v × 4`, clamped at $FF. On export, `st3_v = taud_v >> 2`, clamped at $40. IT's global volume is 0..$80; convert with `taud_v = it_v × 2`, clamped at $FF.
|
||||
**Compatibility.** ST3's global volume is 0..$40; convert with `taud_v = st3_v × 4`, clamped at $FF. On export, `st3_v = taud_v >> 2`, clamped at $40. IT's global volume is 0..$80; convert with `taud_v = it_v × 2`, clamped at $FF. On IT, the very first `V 00` command must be resolved as the song's initial global volume.
|
||||
|
||||
**Implementation.** Write the high byte to `global_volume` on the row the command appears. The low byte is reserved. ST3's `kST3NoMutedChannels` rule applies: V on a muted channel is ignored by ST3; for strict-compatible playback Taud follows suit, but new Taud compositions should avoid muting channels that carry global effects.
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
con.reset_graphics();con.curs_set(0);con.clear();
|
||||
graphics.resetPalette();graphics.setBackground(0,0,0);
|
||||
graphics.resetPalette();graphics.setPalette(0, 0, 0, 0, 0);graphics.setBackground(0,0,0);
|
||||
|
||||
let logo = gzip.decomp(base64.atob("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"));
|
||||
|
||||
@@ -77,7 +77,7 @@ tmr = sys.nanoTime();
|
||||
while (sys.nanoTime() - tmr < 2147483648) sys.spin();
|
||||
// clear screen
|
||||
graphics.clearPixels(255);con.color_pair(239,255);
|
||||
con.clear();con.move(1,1);
|
||||
con.clear();con.move(1,1);graphics.resetPalette();
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
@@ -158,9 +158,6 @@ audio.purgeQueue(AUDIO_DEVICE)
|
||||
audio.setPcmMode(AUDIO_DEVICE)
|
||||
audio.setMasterVolume(AUDIO_DEVICE, 255)
|
||||
|
||||
// set colour zero as half-opaque black
|
||||
graphics.setPalette(0, 0, 0, 0, 7)
|
||||
|
||||
// Parse SSF-TC subtitle packet and add to event buffer (0x31)
|
||||
function parseSubtitlePacketTC(packetSize) {
|
||||
// Read subtitle index (24-bit, little-endian)
|
||||
@@ -2463,6 +2460,6 @@ finally {
|
||||
audio.purgeQueue(AUDIO_DEVICE)
|
||||
}
|
||||
|
||||
graphics.setPalette(0, 0, 0, 0, 0)
|
||||
graphics.resetPalette()
|
||||
con.move(cy, cx) // restore cursor
|
||||
return errorlevel
|
||||
@@ -216,6 +216,8 @@ const colEffArg = 231
|
||||
const colBackPtn = 255
|
||||
|
||||
let PITCH_PRESET_IDX = 240 // TODO read from the Project Data section of the .taud
|
||||
let beatDivPrimary = 4 // TODO read from the Project Data section of the .taud
|
||||
let beatDivSecondary = 16
|
||||
|
||||
// pitchSymLut[pitchInOct] = [symString, octaveOffset]
|
||||
// octaveOffset is 1 when pitchInOct is closer to the next octave's root (wraps up) than to any table entry.
|
||||
@@ -563,7 +565,8 @@ const colPlayback = 86
|
||||
const colHighlight = 41
|
||||
const colColumnSep = 6
|
||||
const colRowNum = 250
|
||||
const colRowNumEmph1 = 180
|
||||
const colRowNumEmph1 = 225
|
||||
const colRowNumEmph2 = 155
|
||||
const colStatus = 253
|
||||
const colVoiceHdr = 230
|
||||
const colSep = 252
|
||||
@@ -670,6 +673,13 @@ function drawStatusBar() {
|
||||
con.move(2,1)
|
||||
print((playbackMode == PLAYMODE_NONE) ? sym.statusstop : sym.statusplay)
|
||||
|
||||
// beat indicator
|
||||
let beatCursorRow = cursorRow
|
||||
while (beatCursorRow > beatDivSecondary) { beatCursorRow -= beatDivSecondary } // test this behaviour with primary=4, secondary=22 or something
|
||||
let beatInd = (playbackMode != PLAYMODE_NONE && beatCursorRow % beatDivPrimary < (beatDivPrimary >>> 1)) ?
|
||||
((beatCursorRow % beatDivSecondary < (beatDivPrimary >>> 1)) ? '\u00846u' : '\u00847u') :
|
||||
''
|
||||
|
||||
// cue row
|
||||
con.move(1,4)
|
||||
con.color_pair(colWHITE, 255); print(`Cue `)
|
||||
@@ -677,7 +687,7 @@ function drawStatusBar() {
|
||||
// con.color_pair(colWHITE, 255); print(`/`)
|
||||
// con.color_pair(20, 255); print(`${sCueMax}`)
|
||||
con.color_pair(colWHITE, 255); print(` Row `)
|
||||
con.color_pair(130, 255); print(`${sRow}`)
|
||||
con.color_pair(130, 255); print(`${sRow}${beatInd}`)
|
||||
|
||||
// bpm spd
|
||||
con.move(2,4)
|
||||
@@ -687,7 +697,7 @@ function drawStatusBar() {
|
||||
con.color_pair(235, 255); print(`${sSpd}`)
|
||||
|
||||
// app title
|
||||
gl.drawTexImageOver(logoTexture, (SCRPW-logoTexture.width) >>> 1, 8)
|
||||
gl.drawTexImageOver(logoTexture, (SCRPW-logoTexture.width) >>> 1, 7)
|
||||
|
||||
}
|
||||
|
||||
@@ -791,7 +801,11 @@ function drawPatternRowAt(viewRow, style = timelineRowStyle) {
|
||||
|
||||
con.color_pair(colRowNum, back)
|
||||
if (actualRow < ROWS_PER_PAT) {
|
||||
if (actualRow % 4 == 0) {con.color_pair(colRowNumEmph1, back)}
|
||||
let actualRowForBeatCalc = actualRow
|
||||
while (actualRowForBeatCalc >= beatDivSecondary) { actualRowForBeatCalc -= beatDivSecondary }
|
||||
|
||||
if (actualRowForBeatCalc % beatDivPrimary == 0) {con.color_pair(colRowNumEmph1, back)}
|
||||
if (actualRowForBeatCalc % beatDivSecondary == 0) {con.color_pair(colRowNumEmph2, back)}
|
||||
let rowstr = actualRow.dec02()
|
||||
con.move(y, 1); con.prnch(rowstr.charCodeAt(0)); con.move(y, 2); con.prnch(rowstr.charCodeAt(1))
|
||||
|
||||
@@ -1172,7 +1186,7 @@ if (fullPathObj === undefined) {
|
||||
|
||||
const logofile = files.open("A:/tvdos/bin/tauthdr.r8")
|
||||
const logoBytes = logofile.bread(); logofile.close()
|
||||
const logoTexture = new gl.Texture(88, 12, logoBytes)
|
||||
const logoTexture = new gl.Texture(90, 14, logoBytes)
|
||||
const buttonfile = files.open("A:/tvdos/bin/tautbtn.r8")
|
||||
const buttonBytes = buttonfile.bread(); buttonfile.close()
|
||||
const buttonTexture = new gl.Texture(2, 28, buttonBytes)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 730 B After Width: | Height: | Size: 385 B |
Binary file not shown.
@@ -662,7 +662,7 @@ TODO
|
||||
\endlastfoot
|
||||
\centering
|
||||
\begin{tabulary}{\textwidth}{rl}
|
||||
{\ttfamily 0} & {\ttfamily \#0008} \\
|
||||
{\ttfamily 0} & {\ttfamily \#0007} \\
|
||||
{\ttfamily 1} & {\ttfamily \#004F} \\
|
||||
{\ttfamily 2} & {\ttfamily \#008F} \\
|
||||
{\ttfamily 3} & {\ttfamily \#00BF} \\
|
||||
|
||||
102
it2taud.py
102
it2taud.py
@@ -90,11 +90,13 @@ EFF_P = 16; EFF_Q = 17; EFF_R = 18; EFF_S = 19; EFF_T = 20
|
||||
EFF_U = 21; EFF_V = 22; EFF_W = 23; EFF_X = 24; EFF_Y = 25
|
||||
EFF_Z = 26
|
||||
|
||||
# IT effects that recall last non-zero arg (per-effect-private, with cohort exceptions)
|
||||
# IT effects that recall last non-zero arg (per-effect-private, with cohort exceptions).
|
||||
# V (Set Global Volume) recalls in IT compat mode — the first V $00 resolves to the
|
||||
# header's global_vol, not literal 0. Without this, songs starting with V $00 silence.
|
||||
IT_MEM_EFFECTS = frozenset({
|
||||
EFF_D, EFF_E, EFF_F, EFF_G, EFF_H, EFF_I, EFF_J,
|
||||
EFF_K, EFF_L, EFF_N, EFF_O, EFF_P, EFF_Q, EFF_R,
|
||||
EFF_S, EFF_T, EFF_U, EFF_W, EFF_X, EFF_Y,
|
||||
EFF_S, EFF_T, EFF_U, EFF_V, EFF_W, EFF_X, EFF_Y,
|
||||
})
|
||||
|
||||
|
||||
@@ -286,11 +288,13 @@ def _it214_decompress_block(payload: bytes, num_samples: int,
|
||||
|
||||
if is_16bit:
|
||||
init_width = 17
|
||||
range_count = 16 # escape range size for mid/full forms
|
||||
range_count = 16 # escape range size in mid form
|
||||
border_sub = 8 # = range_count / 2; centres escape range on signed midpoint
|
||||
escape_bits = 4 # bits to read in short-form escape
|
||||
else:
|
||||
init_width = 9
|
||||
range_count = 8
|
||||
border_sub = 4
|
||||
escape_bits = 3
|
||||
|
||||
width = init_width
|
||||
@@ -298,55 +302,62 @@ def _it214_decompress_block(payload: bytes, num_samples: int,
|
||||
out = []
|
||||
n = 0
|
||||
|
||||
mask = (1 << (init_width - 1)) - 1 # 0xFF (8-bit) or 0xFFFF (16-bit)
|
||||
|
||||
while n < num_samples:
|
||||
v = read_bits(width)
|
||||
|
||||
if width <= 6:
|
||||
# Short form: top bit == escape trigger; read escape_bits for new width.
|
||||
# Reference: cubic.org/itsex.c (Jeffrey Lim, IT author) — no skip-self.
|
||||
is_data = False
|
||||
if width < 7:
|
||||
# Mode A (short): single escape code at v == 1<<(width-1).
|
||||
if v == (1 << (width - 1)):
|
||||
width = read_bits(escape_bits) + 1
|
||||
new_w = read_bits(escape_bits) + 1
|
||||
width = new_w if new_w < width else new_w + 1 # skip-self
|
||||
continue
|
||||
|
||||
# Else: data, sign-extend from `width` bits.
|
||||
delta = _sign_extend(v, width)
|
||||
is_data = True
|
||||
elif width < init_width:
|
||||
# Mid form. border = (all-ones mask) >> (init_width - width).
|
||||
# For 8-bit: 0xFF>>(9-w) → 63 (w=7), 127 (w=8).
|
||||
# Escape when v > border; new width = v - border directly, no skip-self.
|
||||
# Reference: cubic.org/itsex.c, OpenMPT ITTools.cpp.
|
||||
mask = (1 << (init_width - 1)) - 1 # 0xFF (8-bit) or 0xFFFF (16-bit)
|
||||
border = mask >> (init_width - width)
|
||||
if v > border:
|
||||
width = v - border
|
||||
# Mode B (mid): `range_count` escape codes centred on signed midpoint.
|
||||
# border = (mask >> (init_width-width)) - border_sub, where border_sub
|
||||
# = range_count / 2. Reference: libxmp it_compress.c, OpenMPT ITTools.cpp.
|
||||
# 8-bit: width=7 → border=63-4=59, width=8 → border=127-4=123
|
||||
# 16-bit: width=7..16 with border_sub=8.
|
||||
border = (mask >> (init_width - width)) - border_sub
|
||||
if border < v <= border + range_count:
|
||||
new_w = v - border
|
||||
width = new_w if new_w < width else new_w + 1 # skip-self
|
||||
continue
|
||||
|
||||
if v > border + range_count:
|
||||
v -= range_count # collapse escape range out
|
||||
delta = _sign_extend(v, width)
|
||||
is_data = True
|
||||
else:
|
||||
# Full form: top bit (bit init_width-1) is escape flag.
|
||||
# new width = lower bits + 1, no skip-self.
|
||||
# Mode C (full): top bit (bit init_width-1) signals width change.
|
||||
top_bit = 1 << (init_width - 1)
|
||||
if v & top_bit:
|
||||
width = (v & (top_bit - 1)) + 1
|
||||
continue
|
||||
# Else: data is (init_width-1) bits wide, sign-extend from there.
|
||||
delta = _sign_extend(v, init_width - 1)
|
||||
is_data = True
|
||||
|
||||
# Delta is always cast to the native sample type regardless of current bit-width.
|
||||
# IT SDK: d1 += (signed char)value — i.e. always 8-bit (or 16-bit) signed cast.
|
||||
# Upper-half short-form values (v > escape midpoint) are larger *positive* deltas,
|
||||
# not negatives; _sign_extend(v, width) would wrongly negate them.
|
||||
delta = _sign_extend(v, init_width - 1)
|
||||
if is_16bit:
|
||||
d1 = _wrap16(d1 + delta)
|
||||
if is_it215:
|
||||
d2 = _wrap16(d2 + d1)
|
||||
out.append(d2)
|
||||
if is_data:
|
||||
if is_16bit:
|
||||
d1 = _wrap16(d1 + delta)
|
||||
if is_it215:
|
||||
d2 = _wrap16(d2 + d1)
|
||||
out.append(d2)
|
||||
else:
|
||||
out.append(d1)
|
||||
else:
|
||||
out.append(d1)
|
||||
else:
|
||||
d1 = _wrap8(d1 + delta)
|
||||
if is_it215:
|
||||
d2 = _wrap8(d2 + d1)
|
||||
out.append(d2)
|
||||
else:
|
||||
out.append(d1)
|
||||
n += 1
|
||||
d1 = _wrap8(d1 + delta)
|
||||
if is_it215:
|
||||
d2 = _wrap8(d2 + d1)
|
||||
out.append(d2)
|
||||
else:
|
||||
out.append(d1)
|
||||
n += 1
|
||||
|
||||
return out
|
||||
|
||||
@@ -424,7 +435,8 @@ class ITSample:
|
||||
|
||||
def parse_samples(data: bytes, h: ITHeader, decompress: bool) -> list:
|
||||
samples = []
|
||||
is_it215 = (h.cmwt >= 0x0215)
|
||||
# IT2.15 compression is signaled PER-SAMPLE via cvt bit 2 (0x04), not globally
|
||||
# via the file's cwt. Reference: OpenMPT ITTools.cpp, libxmp it_load.c.
|
||||
for i, ptr in enumerate(h.smp_ptrs):
|
||||
if ptr == 0 or ptr + 0x50 > len(data):
|
||||
vprint(f" warning: sample {i+1} pointer {ptr:#x} out of range, skipping")
|
||||
@@ -465,6 +477,7 @@ def parse_samples(data: bytes, h: ITHeader, decompress: bool) -> list:
|
||||
vprint(f" warning: '{s.name}' is IT2.14 compressed, --no-decompress → silent")
|
||||
else:
|
||||
try:
|
||||
is_it215 = bool(s.cvt & 0x04)
|
||||
raw = it214_decompress(data, s.smp_point, s.length,
|
||||
s.is_16bit, is_it215)
|
||||
s.sample_data = _normalise_sample(raw, True,
|
||||
@@ -921,7 +934,8 @@ def encode_effect_it(cmd: int, arg: int, ch: int = 0, row: int = 0) -> tuple:
|
||||
|
||||
def resolve_it_recalls(patterns_rows: list, order_list: list,
|
||||
num_channels: int, link_gef: bool,
|
||||
old_effects: bool = False) -> None:
|
||||
old_effects: bool = False,
|
||||
initial_global_vol: int = 128) -> None:
|
||||
"""Walk in order, resolve zero-arg recalls per-effect-per-channel.
|
||||
|
||||
IT effect memory groups:
|
||||
@@ -933,10 +947,15 @@ def resolve_it_recalls(patterns_rows: list, order_list: list,
|
||||
old_effects=True (IT_FLAG_OLD_EFFECTS): E00/F00 are ST3-style stops —
|
||||
they do NOT recall and are suppressed to TOP_NONE. All other effects
|
||||
still recall normally even in old_effects mode.
|
||||
|
||||
V memory is primed with initial_global_vol so a song-leading V $0000
|
||||
resolves to the header's global volume, not literal zero.
|
||||
"""
|
||||
# last_mem[ch][eff_key] = last_non_zero_arg
|
||||
# eff_key: integer 1-26 for most effects; we merge cohorts by normalising.
|
||||
last_mem = [{} for _ in range(num_channels)]
|
||||
for ch in range(num_channels):
|
||||
last_mem[ch][EFF_V] = initial_global_vol
|
||||
|
||||
# Effects that stop rather than recall when arg=0 in old_effects mode (ST3 compat).
|
||||
# E/F: pitch slide stop. J: arpeggio stop (J00 = return to normal pitch in ST3).
|
||||
@@ -1391,7 +1410,8 @@ def assemble_taud(h: ITHeader, samples: list, instruments: list,
|
||||
# ── Resolve IT recalls ───────────────────────────────────────────────────
|
||||
vprint(" resolving IT recalls…")
|
||||
resolve_it_recalls(patterns_rows, h.order_list, 64, h.link_gef,
|
||||
old_effects=h.old_effects)
|
||||
old_effects=h.old_effects,
|
||||
initial_global_vol=h.global_vol)
|
||||
|
||||
# ── Check SBx chunk crossing (warn only) ─────────────────────────────────
|
||||
for pi, (grid, rows) in enumerate(patterns_rows):
|
||||
|
||||
@@ -2296,6 +2296,8 @@ prefixes:
|
||||
10121: Pythagorean Diminished Fifth
|
||||
10122: Pythagorean Augmented Fourth
|
||||
10123: Shi'er lü (East Asian traditional tuning)
|
||||
Uint8 Primary beat division (default: 4 rows)
|
||||
Uint8 Secondary beat division (default: 16 rows)
|
||||
|
||||
Byte[*] Song name, null terminated. Encoding: UTF-8
|
||||
Byte[*] Song composer, null terminated. Encoding: UTF-8
|
||||
|
||||
@@ -1439,7 +1439,7 @@ in vec2 v_texCoords;
|
||||
uniform sampler2D u_texture;
|
||||
uniform vec4 pal[256];
|
||||
|
||||
float intensitySteps = 4.0;
|
||||
float intensitySteps = 32.0;
|
||||
uniform vec4 lcdBaseCol;
|
||||
|
||||
void main(void) {
|
||||
@@ -1961,7 +1961,7 @@ void main() {
|
||||
|
||||
|
||||
val DEFAULT_PALETTE = intArrayOf( // 0b rrrrrrrr gggggggg bbbbbbbb aaaaaaaa
|
||||
136,
|
||||
119,
|
||||
17663,
|
||||
35071,
|
||||
48127,
|
||||
|
||||
@@ -20,9 +20,9 @@ uniform sampler2D u_texture; // Input texture
|
||||
uniform vec2 flip = vec2(0.0, 0.0); // UV flip control (0,1 = flip Y)
|
||||
uniform float noiseMagnitude = 0.0;
|
||||
|
||||
// Signal mode: 0 = S-Video, 1 = Composite, 2 = CGA Composite
|
||||
// Signal mode: -1 = disable, 0 = S-Video, 1 = Composite, 2 = CGA Composite
|
||||
// Can be changed at runtime without recompilation
|
||||
uniform int signalMode = 0; // Default should be 1 for composite
|
||||
uniform int signalMode = 0;
|
||||
|
||||
// CGA-specific settings
|
||||
uniform float cgaHue; // Hue adjustment for CGA (default: 0.0, range: -PI to PI)
|
||||
@@ -268,6 +268,11 @@ vec3 decodeCGAComposite(vec2 uv, vec2 texelSize, float pixelX, float pixelY) {
|
||||
return rgb;
|
||||
}
|
||||
|
||||
vec3 decodePassthru(vec2 uv, vec2 texelSize) {
|
||||
vec3 srcRGB = sampleTexture(uv);
|
||||
return srcRGB;
|
||||
}
|
||||
|
||||
// === TRINITRON PHOSPHOR MASK ===
|
||||
vec3 trinitronMask(vec2 screenPos) {
|
||||
float strength = getPhosphorStrength();
|
||||
@@ -324,6 +329,8 @@ void main() {
|
||||
rgb = decodeCGAComposite(uv, texelSize, pixelX, pixelY);
|
||||
} else if (signalMode == 1) {
|
||||
rgb = decodeComposite(uv, texelSize, basePhase);
|
||||
} else if (signalMode == -1) {
|
||||
rgb = decodePassthru(uv, texelSize);
|
||||
} else {
|
||||
rgb = decodeSVideo(uv, texelSize, basePhase);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user