Compare commits

...

11 Commits

Author SHA1 Message Date
minjaesong
b838b35525 taud: amiga mode pitchbend 2026-04-29 20:07:25 +09:00
minjaesong
1148454fb3 graphics: colour 0 is default to half-transparent black 2026-04-29 19:20:15 +09:00
minjaesong
cfb7b97bf0 taut: popup back col 2026-04-29 15:57:41 +09:00
minjaesong
176aa824fe taut: comp for pat and cue 2026-04-29 15:50:52 +09:00
minjaesong
d33484c3c8 perkele 2026-04-29 15:49:57 +09:00
minjaesong
737b1cebe7 did it work? 2026-04-29 15:41:47 +09:00
minjaesong
176766b793 did it fix it? 2026-04-29 14:29:18 +09:00
minjaesong
191c733913 taut: better top bar(2) 2026-04-29 14:09:37 +09:00
minjaesong
895f1b27ef taut: better top bar 2026-04-29 13:19:22 +09:00
minjaesong
538d718568 taut: top bar 2026-04-29 12:41:20 +09:00
minjaesong
b3c5719e3a it2taud sample signedness fix 2026-04-29 12:27:52 +09:00
16 changed files with 231 additions and 136 deletions

View File

@@ -45,9 +45,9 @@ mix = sample × note_vol × channel_vol × global_vol >> normalisation_shift
with saturation applied before the 8-bit stereo output.
## 4. Rows, ticks, patterns, orders
## 4. Rows, ticks, patterns, cues
A pattern is a rectangular grid of rows and channels; each cell holds one note event. Playback divides each row into `speed` ticks (effect A); tempo (effect T) sets the duration of one tick. At 125 BPM and speed 6, one row takes 120 ms and one tick 20 ms. Songs play patterns in an order sequence; effects B and C navigate this sequence.
A pattern is a rectangular grid of rows and channels; each cell holds one note event. Playback divides each row into `speed` ticks (effect A); tempo (effect T) sets the duration of one tick. At 125 BPM and speed 6, one row takes 120 ms and one tick 20 ms. Songs play patterns in a cue sequence; effects B and C navigate this sequence.
## 5. Default parameters at song start
@@ -58,7 +58,7 @@ A pattern is a rectangular grid of rows and channels; each cell holds one note e
| Global volume | $80 (mid-scale) |
| Channel volume | $3F (full) |
| Pan (all channels) | $80 (centre) |
| Order index | $0000 |
| cue index | $0000 |
## 6. Effect memory groups
@@ -89,25 +89,25 @@ Opcodes are single base-36 digits (0-9, then A-Z); arguments are 16-bit hexadeci
---
## B $xxyy — Jump to order $xxyy
## B $xxyy — Jump to cue $xxyy
**Plain.** Finishes the current row, then continues playback at row 0 of the pattern at order position $xxyy. Use this to create song-level jumps, loops, or branching structures.
**Plain.** Finishes the current row, then continues playback at row 0 of the pattern at cue position $xxyy. Use this to create song-level jumps, loops, or branching structures.
**Compatibility.** ST3 `Bxx` jumps to an 8-bit order and maps to Taud `B $00xx`. The extended 16-bit range means Taud songs may have up to $10000 order entries.
**Compatibility.** ST3 `Bxx` jumps to an 8-bit cue and maps to Taud `B $00xx`. The extended 16-bit range means Taud songs may have up to $10000 cue entries.
**Implementation.** On the last tick of the current row, set the next order index to the argument and the next row to 0. If the argument exceeds the song length, wrap to the song's defined restart position (order $0000 by default). Jumps are detected by a visited `(order, row)` set so that pathological loops do not prevent song-length computation, though they do not interrupt actual playback. There is no memory for B.
**Implementation.** On the last tick of the current row, set the next cue index to the argument and the next row to 0. If the argument exceeds the song length, wrap to the song's defined restart position (cue $0000 by default). Jumps are detected by a visited `(cue, row)` set so that pathological loops do not prevent song-length computation, though they do not interrupt actual playback. There is no memory for B.
**Simultaneous B and C on the same row.** If a B command appears in the same row as a C command (on any channel), both fire: B chooses the order, C chooses the row within that order. If the two commands appear on different channels, channel priority is **ascending channel index** — the lowest-numbered channel carrying either effect wins its parameter. If both appear on the same channel row (only possible if one is a volume-column equivalent), the effect column takes precedence.
**Simultaneous B and C on the same row.** If a B command appears in the same row as a C command (on any channel), both fire: B chooses the cue, C chooses the row within that cue. If the two commands appear on different channels, channel priority is **ascending channel index** — the lowest-numbered channel carrying either effect wins its parameter. If both appear on the same channel row (only possible if one is a volume-column equivalent), the effect column takes precedence.
---
## C $xxyy — Break pattern to row $xxyy
**Plain.** Finishes the current row, then skips ahead to row $xxyy of the **next** pattern in the order sequence.
**Plain.** Finishes the current row, then skips ahead to row $xxyy of the **next** pattern in the cue sequence.
**Compatibility.** ST3 stores `Cxx` as **BCD** (so on-disk `$10` means decimal row 10); Taud stores the argument as plain binary. When converting from ST3, decode with `row = (byte >> 4) × 10 + (byte & $0F)`. Valid ST3 source bytes are those representing decimal 0..63; out-of-range BCD bytes should clamp to row 0 on import. When exporting back to ST3, encode with `byte = ((row / 10) << 4) | (row % 10)`, clamped at row 63.
**Implementation.** On the last tick of the current row, advance the order index by 1 (or honour a co-occurring B), then set the next row to the argument. If the argument exceeds the destination pattern's row count, start the destination pattern at row 0. There is no memory for C.
**Implementation.** On the last tick of the current row, advance the cue index by 1 (or honour a co-occurring B), then set the next row to the argument. If the argument exceeds the destination pattern's row count, start the destination pattern at row 0. There is no memory for C.
---
@@ -149,9 +149,9 @@ D's 16-bit argument encodes four mutually exclusive modes using the top nibble a
---
## E $xxxx — Pitch slide down by $xxxx (linear)
## E $xxxx — Pitch slide down by $xxxx
**Plain.** Lowers the channel's pitch by the argument per tick. Taud's pitch slides are **linear in the 4096-TET grid** — the slide value is subtracted directly from the stored pitch, without any period-table indirection. A coarse slide uses the full value range; a fine slide applies only once per row; an extra-fine slide is not provided (the 16-bit argument already gives microtonal precision below 1/64 semitone).
**Plain.** Lowers the channel's pitch by the argument per tick. By default (linear mode, `f` bit unset in effect `1`) the coarse slide value is subtracted directly from the stored pitch in the 4096-TET grid. When Amiga mode is active (`f` bit set), coarse slides are instead applied in Amiga period space: the stored value is converted back to Amiga period units and subtracted from the equivalent period, producing the characteristic non-linear pitch drift of ProTracker-style slides. Fine slides (`E $Fxxx`) are always applied in linear pitch-unit space regardless of mode. A coarse slide uses the full value range; a fine slide applies only once per row.
Coarse and fine modes are distinguished by the high nibble of the argument:
@@ -165,7 +165,7 @@ Coarse and fine modes are distinguished by the high nibble of the argument:
- 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.
ST3 Amiga-mode coarse slides do not have a clean conversion and should be treated as linear-mode equivalents during import (same `round(× 64/3)` scale). The Amiga-mode flag (`f` bit in effect `1` or the song-table flags byte) is set in the output file to signal the mixer to apply the stored values in period space rather than directly in pitch space. This preserves the characteristic non-linearity of Amiga slides (lower pitches slide more slowly in semitone terms) without requiring a different numeric encoding. Fine and extra-fine slides (`E $Fxxx`) are always applied in linear pitch-unit space regardless of the Amiga-mode flag, as they are ST3-specific extensions absent from ProTracker.
Because E and F share memory in Taud (narrower than ST3's broad shared memory), an ST3 song that used `E00` or `F00` to recall a D, G, or Q argument will break on import; the converter must eagerly resolve ST3 recalls into explicit Taud arguments rather than relying on memory.
@@ -185,18 +185,24 @@ on row start:
on tick > 0:
if mode_this_row == COARSE:
pitch -= slide_amount_this_row
if amiga_mode:
# period = AMIGA_BASE_PERIOD × 2^((pitch C3) / 4096)
# period += slide_amount_this_row × (3/64) # convert Taud units → Amiga period units
# pitch = C3 + 4096 × log2(AMIGA_BASE_PERIOD / period)
pitch = amiga_slide_down(pitch, slide_amount_this_row)
else:
pitch -= slide_amount_this_row
```
Glissando control (S $1x) snaps the output pitch to the nearest semitone after every slide application; see S $1x.
---
## F $xxxx — Pitch slide up by $xxxx (linear)
## F $xxxx — Pitch slide up by $xxxx
**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.
**Plain.** Raises the channel's pitch by the argument per tick, with the same mode-selection scheme as E. Coarse, fine, memory behaviour, and Amiga-mode handling are identical in form but inverted in direction.
**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.
**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. Amiga-mode behaviour is controlled by the same `f` flag as E; coarse F slides are applied in period space when the flag is set, while fine slides remain linear.
**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.
@@ -527,6 +533,21 @@ Peak at maximum settings: $7F × $FF >> 9 = $3F — the full panning range. Retr
---
## 8 $xyzz — Bitcrusher
**Plain.** Applies Bitcrusher to the current voice.
- x: clipping mode. 0: clamp, 1: fold, 2: modulus
- y: bit depth (1..15). 8..15 has no effect on TSVM audio adapter (already operates on 8 bits)
- z: sample skip (0..255). 0: no skip, 1: use every 2nd samples, 2: use every 3rd samples, ..., 255: use every 256th samples
- `8 0000` will disable the bitcrusher
**Compatibility.** Unique to Taud. No compatible equivalent exists.
**Implementation.** TODO
---
# The S subcommand family
S is a multiplexing opcode; the **high nibble of the high byte** selects the sub-effect, and the remainder is the sub-argument.
@@ -755,20 +776,33 @@ NOTE: **`3.00` — is No-op**
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
## 1 $xx00 — Global behaviour flags
**Plain.** Sets how the mixer should treat the panning. Available modes are:
**Plain.** Sets how the mixer should treat the panning. Available flags 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.
0b 0000 00fp
- p unset: Linear panning mode (tracker-accurate). Centre panning gets 3 dB boost. Default setting.
- p set: Equal-power panning mode. L/R amplitude is at 0.707 when centre-panned.
- f unset: Linear tone mode. Pitch shift will behave like MIDI/ImpulseTracker/ScreamTracker linear mode.
- f set: Amiga tone mode. Pitch shift will behave like ProTracker/ScreamTracker default mode.
**Implementation.**
- Mode 0:
- Panning-linear:
- 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:
- Panning-equal-power:
- L_gain = cos(pi*x / 512.0)
- R_gain = sin(pi*x / 512.0)
- Amiga tone (coarse E/F pitch slides only; fine slides are always linear):
- AMIGA_BASE_PERIOD = 214.0 (period at the Taud reference pitch C3 for a standard 8363 Hz instrument, NTSC clock)
- AMIGA_PERIOD_SCALE = 3.0 / 64.0 (converts stored Taud coarse-slide units back to Amiga period units)
- period = AMIGA_BASE_PERIOD × 2^((noteVal C3) / 4096)
- period_new = period slideArg × AMIGA_PERIOD_SCALE (slideArg < 0 for E, > 0 for F)
- noteVal_new = C3 + 4096 × log2(AMIGA_BASE_PERIOD / period_new)
**Initialisation from the song table.** The same flags byte is stored in the song-table entry (see file format §Song Table). A Taud player should write this byte to MMIO playhead register 7 before starting playback; the mixer then applies it as the initial state on every reset, and subsequent in-pattern `1` effects may override it.
---
@@ -833,7 +867,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: 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.
**Linear pitch slides.** ST3's slide arithmetic is period-based (Amiga) or linear-table-indexed; Taud's default 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). ST3 songs in Amiga mode use the **same numeric conversion** for coarse E/F (the exact period-step count is not preserved), but the converter sets bit 1 (`f`) of the song-table flags byte and Taud's mixer re-applies the stored coarse slide values in Amiga period space at playback, recovering the non-linear pitch character. G is always treated as linear regardless of mode. Fine/extra-fine slides are always linear.
**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.

View File

@@ -88,14 +88,14 @@ rightshade:'\u00B2',
const fxNames = {
'0':"-- ",
'1':"Mixer config ", // Taud: 1 01xx: set stereo panning law
'1':"Mixer config ",
'2':"UNIMPLEMENTED",
'3':"UNIMPLEMENTED",
'4':"UNIMPLEMENTED",
'5':"UNIMPLEMENTED",
'6':"UNIMPLEMENTED",
'7':"UNIMPLEMENTED",
'8':"UNIMPLEMENTED",
'8':"Bitcrusher ",
'9':"UNIMPLEMENTED",
A:"Tick speed ",
B:"Jump to order",
@@ -572,7 +572,7 @@ const colTabBarBack = 187
const colTabBarBack2 = 136
const colTabBarOrn = 136
const colBrand = 211
const colPopupBack = 52
const colPopupBack = 245//57
const colTabActive = 239
const colTabInactive = 45
@@ -615,8 +615,8 @@ const transportControlColour = [160,20,20,20]
const transportControlHint = ["O","I","U","Y"]
let transportControlOldPos = 3 // index for transportControlReverse
function drawStatusBar() {
fillLine(1, colStatus, 255)
fillLine(2, colStatus, 255)
fillLine(1, colWHITE, 255)
fillLine(2, colWHITE, 255)
const sCueIdx = cueIdx.hex03()
const sCueMax = (song.lastActiveCue < 0 ? 0 : song.lastActiveCue).hex03()
@@ -636,7 +636,7 @@ function drawStatusBar() {
if (active)
con.color_pair(transportControlColour[j], 255)
else
con.color_pair(colStatus, 255)
con.color_pair(colWHITE, 255)
con.move(1, SCRW - 5*(j+1) + 1 + 2)
print(transportControlSymbol[j])
@@ -644,27 +644,27 @@ function drawStatusBar() {
if (active)
con.color_pair(transportControlColour[j], 255)
else
con.color_pair(colVoiceHdr, 255)
con.color_pair(235, 255)
con.move(2, SCRW - 5*(j+1) + 1 + 2)
print(transportControlHint[j])
if (active) transportControlNewPos = j;
})
// draw button background
if (transportControlOldPos != transportControlNewPos) {
// erase button from old pos
gl.drawTexImage(buttonNullTexture, SCRPW - 35*(transportControlOldPos+1), 0)
// paint button in new pos
gl.drawTexImage(buttonTexture, SCRPW - 35*(transportControlNewPos+1), 0)
// update pos tracking
transportControlOldPos = transportControlNewPos
}
// draw tob bar background
gl.drawTexPattern(buttonTexture, 0, 0, SCRPW, 28)
graphics.plotPixel(0, 0, 255)
graphics.plotPixel(0, 1, 254)
graphics.plotPixel(SCRPW-1, 0, 255)
graphics.plotPixel(SCRPW-1, 1, 254)
// update pos tracking
transportControlOldPos = transportControlNewPos
// current audio device status
// play/stop sym
con.color_pair(colStatus, 255)
con.color_pair(colWHITE, 255)
con.move(1,1)
print(`${sym.playhead}${PLAYHEAD}`)
con.move(2,1)
@@ -672,30 +672,22 @@ function drawStatusBar() {
// 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}`)
con.color_pair(colWHITE, 255); print(`Cue `)
con.color_pair(20, 255); print(`${sCueIdx}`)
// 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}`)
// 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}`)
con.color_pair(colWHITE, 255); print(`BPM `)
con.color_pair(161, 255); print(`${sBPM}`)
con.color_pair(colWHITE, 255); print(` Tick `)
con.color_pair(235, 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')*/
gl.drawTexImage(logoTexture, (SCRPW-logoTexture.width) >>> 1, 6)
gl.drawTexImageOver(logoTexture, (SCRPW-logoTexture.width) >>> 1, 8)
}
@@ -1183,10 +1175,10 @@ const logoBytes = logofile.bread(); logofile.close()
const logoTexture = new gl.Texture(88, 12, logoBytes)
const buttonfile = files.open("A:/tvdos/bin/tautbtn.r8")
const buttonBytes = buttonfile.bread(); buttonfile.close()
const buttonTexture = new gl.Texture(35, 28, buttonBytes)
const buttonNullfile = files.open("A:/tvdos/bin/tautbtn0.r8")
const buttonNullBytes = buttonNullfile.bread(); buttonNullfile.close()
const buttonNullTexture = new gl.Texture(35, 28, buttonNullBytes)
const buttonTexture = new gl.Texture(2, 28, buttonBytes)
//const buttonNullfile = files.open("A:/tvdos/bin/tautbtn0.r8")
//const buttonNullBytes = buttonNullfile.bread(); buttonNullfile.close()
//const buttonNullTexture = new gl.Texture(35, 28, buttonNullBytes)
font.setLowRom("A:/tvdos/bin/tautfont_low.chr")
font.setHighRom("A:/tvdos/bin/tautfont_high.chr")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 B

After

Width:  |  Height:  |  Size: 518 B

View File

@@ -1 +1 @@
﻾﻾﻾﻾﻾﻾﻾﻾﻾﻾﻾﻾﻾﻾﻾﻾ﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﻻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯻﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺﯺ﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫺﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹﫹粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒粒笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠笠。慡慡慡慡慡慡慡慡慡慡慡慡慡慡慡慡。
<EFBFBD><EFBFBD>洄洄洄洄洄泚<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD>_

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 B

After

Width:  |  Height:  |  Size: 730 B

View File

@@ -1 +1 @@
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>嗷嗷<EFBFBD><EFBFBD><EFBFBD><EFBFBD>嗷嗷<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>嗷嗷<EFBFBD><EFBFBD><EFBFBD><EFBFBD>嗷嗷<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>嗷嗷嗷<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>嗷嗷嗷<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>嗷嗷<EFBFBD><EFBFBD><EFBFBD>嗷嗷<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>嗷嗷嗷嗷<EFBFBD><EFBFBD>嗷嗷嗷<EFBFBD><EFBFBD><EFBFBD>嗷嗷嗷嗷<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>嗷嗷<EFBFBD><EFBFBD><EFBFBD>嗷嗷<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>嗷嗷<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>嗷嗷<EFBFBD><EFBFBD><EFBFBD><EFBFBD>嗷嗷<EFBFBD><EFBFBD>嗷嗷<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>嗷嗷嗷嗷嗷<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>嗷嗷<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>嗷嗷<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>嗷嗷<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>嗷嗷<EFBFBD><EFBFBD>嗷嗷<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>嗷嗷嗷嗷<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>嗷嗷嗷嗷<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>嗷嗷嗷<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>嗷嗷嗷<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
Ζ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ΖΖ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ΖΖ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ΖΖ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ΖΖ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ΖΖ<EFBFBD><EFBFBD><EFBFBD>ΖΖ<EFBFBD><EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ΖΖΖ<EFBFBD><EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ΖΖΖ<EFBFBD><EFBFBD><EFBFBD>闖闖闖<EFBFBD><EFBFBD><EFBFBD>闖闖闖<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>闖闖<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>闖闖闖<EFBFBD><EFBFBD>ΖΖ<EFBFBD><EFBFBD><EFBFBD>ΖΖ<EFBFBD><EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ΖΖΖΖ<EFBFBD><EFBFBD>ΖΖΖ<EFBFBD><EFBFBD><EFBFBD>ΖΖΖΖ<EFBFBD><EFBFBD>闖闖闖<EFBFBD><EFBFBD>闖闖闖闖<EFBFBD><EFBFBD>闖闖闖闖<EFBFBD><EFBFBD><EFBFBD><EFBFBD>闖闖闖闖<EFBFBD>ΖΖΖΖΖΖ<EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD>ΖΖ<EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD>ΖΖ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ΖΖ<EFBFBD><EFBFBD>ΖΖ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>闖闖<EFBFBD><EFBFBD>闖闖<EFBFBD>闖闖<EFBFBD><EFBFBD>闖闖<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>闖錵Ζ<EFBFBD>ΖΖ<EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>闖闖錵Ζ<EFBFBD>ΖΖ<EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>闖闖闖闖<EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD>ΖΖ<EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ΖΖ<EFBFBD><EFBFBD>ΖΖ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>闖闖<EFBFBD><EFBFBD>闖闖<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>闖闖<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ΖΖΖΖ<EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ΖΖΖΖ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>闖闖<EFBFBD><EFBFBD>闖闖闖闖<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>闖闖闖闖<EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ΖΖΖ<EFBFBD><EFBFBD><EFBFBD>Ζ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ΖΖΖ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>闖闖闖<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>闖闖闖<EFBFBD><EFBFBD>

View File

@@ -219,9 +219,9 @@ function captureTrackerDataToFile(outFile) {
numPats & 0xFF, (numPats >>> 8) & 0xFF, // numPatterns Uint16 LE
bpmStored, // BPM with 24 bias
tickRate, // initial tick-rate
0x00,0x90, // basenote (0x9000 -- C8)
0x00,0xA0, // basenote (0xA000 -- C9)
0x00,0xAC,0x02,0x46, // basefreq (8363 Hz)
0, // padding
sys.peek(baseAddr - 7), // mixer flags
]
// -- 7. Write header (creates / truncates file) ---------------------------

View File

@@ -662,7 +662,7 @@ TODO
\endlastfoot
\centering
\begin{tabulary}{\textwidth}{rl}
{\ttfamily 0} & {\ttfamily \#000F} \\
{\ttfamily 0} & {\ttfamily \#0008} \\
{\ttfamily 1} & {\ttfamily \#004F} \\
{\ttfamily 2} & {\ttfamily \#008F} \\
{\ttfamily 3} & {\ttfamily \#00BF} \\

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -302,36 +302,36 @@ def _it214_decompress_block(payload: bytes, num_samples: int,
v = read_bits(width)
if width <= 6:
# Short form: top bit == escape trigger
# Short form: top bit == escape trigger; read escape_bits for new width.
# Reference: cubic.org/itsex.c (Jeffrey Lim, IT author) — no skip-self.
if v == (1 << (width - 1)):
new_w = read_bits(escape_bits) + 1
if new_w >= width:
new_w += 1
width = new_w
width = read_bits(escape_bits) + 1
continue
elif width < init_width:
# Mid form: top `range_count` representable values are escape codes
border = (1 << width) - range_count
if v >= border:
new_w = (v - border) + 1 # 1..range_count
if new_w >= width:
new_w += 1
width = new_w
# 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
continue
else:
# Full form: top bit (bit init_width-1) is escape flag
# Full form: top bit (bit init_width-1) is escape flag.
# new width = lower bits + 1, no skip-self.
top_bit = 1 << (init_width - 1)
if v & top_bit:
new_w = (v & (top_bit - 1)) + 1
if new_w >= width:
new_w += 1
width = new_w
width = (v & (top_bit - 1)) + 1
continue
# Real sample: sign-extend delta and accumulate
delta = _sign_extend(v, width)
# 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:
@@ -920,7 +920,8 @@ def encode_effect_it(cmd: int, arg: int, ch: int = 0, row: int = 0) -> tuple:
# ── IT recall resolution ──────────────────────────────────────────────────────
def resolve_it_recalls(patterns_rows: list, order_list: list,
num_channels: int, link_gef: bool) -> None:
num_channels: int, link_gef: bool,
old_effects: bool = False) -> None:
"""Walk in order, resolve zero-arg recalls per-effect-per-channel.
IT effect memory groups:
@@ -928,11 +929,19 @@ def resolve_it_recalls(patterns_rows: list, order_list: list,
- E / F (/ G when link_gef): shared pitch-slide cohort
- G: own slot (or part of EF cohort when link_gef)
- All others: private slots
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.
"""
# 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)]
# 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).
OLD_EFF_STOPS = frozenset({EFF_E, EFF_F, EFF_J})
def cohort_key(cmd):
if cmd in (EFF_D, EFF_K, EFF_L):
return EFF_D # vol-slide cohort
@@ -957,7 +966,12 @@ def resolve_it_recalls(patterns_rows: list, order_list: list,
continue
key = cohort_key(cell.effect)
if cell.effect_arg == 0:
cell.effect_arg = last_mem[ch].get(key, 0)
if old_effects and cell.effect in OLD_EFF_STOPS:
# E00/F00 in old_effects = stop slide — suppress entirely.
# Taud's E $0000 also recalls, so convert to no-op here.
cell.effect = 0
else:
cell.effect_arg = last_mem[ch].get(key, 0)
else:
last_mem[ch][key] = cell.effect_arg
@@ -1141,7 +1155,12 @@ def build_sample_inst_bin_it(samples_or_proxy: list,
c2spd = min(s.c5_speed, 65535)
ls = min(s.loop_beg, 65535)
le = min(s.loop_end, 65535)
loop_mode = 1 if s.has_loop else 0
if s.has_loop and (s.flags & IT_SMP_PINGPONG):
loop_mode = 2 # backandforth
elif s.has_loop:
loop_mode = 1 # forward loop
else:
loop_mode = 0 # no loop
flags_byte = (ptr_hi << 4) | (loop_mode & 0x3)
base = taud_idx * 64
@@ -1371,7 +1390,8 @@ def assemble_taud(h: ITHeader, samples: list, instruments: list,
patterns_rows: list, decompress: bool) -> bytes:
# ── Resolve IT recalls ───────────────────────────────────────────────────
vprint(" resolving IT recalls…")
resolve_it_recalls(patterns_rows, h.order_list, 64, h.link_gef)
resolve_it_recalls(patterns_rows, h.order_list, 64, h.link_gef,
old_effects=h.old_effects)
# ── Check SBx chunk crossing (warn only) ─────────────────────────────────
for pi, (grid, rows) in enumerate(patterns_rows):
@@ -1524,12 +1544,15 @@ def assemble_taud(h: ITHeader, samples: list, instruments: list,
)
assert len(header) == TAUD_HEADER_SIZE
song_table = struct.pack('<IBHBBHf',
# flags byte: bit 1 (f) = Amiga pitch-slide mode (IT linear_slides flag inverted)
flags_byte = 0x00 if h.linear_slides else 0x02
song_table = struct.pack('<IBHBBHfB',
song_offset, C, num_taud_pats,
bpm_stored, speed,
0x9000, # C8
0xA000, # C9
8363.0,
) + b'\x00'
flags_byte,
)
assert len(song_table) == TAUD_SONG_ENTRY
return header + compressed + song_table + bytes(pat_bin) + bytes(sheet)

View File

@@ -79,7 +79,7 @@ EFF_Z = 26 # sync
TAUD_MAGIC = bytes([0x1F,0x54,0x53,0x56,0x4D,0x61,0x75,0x64])
TAUD_VERSION = 1
TAUD_HEADER_SIZE = 32 # magic(8)+ver(1)+numSongs(1)+compSize(4)+rsvd(2)+sig(16)
TAUD_SONG_ENTRY = 16 # offset(4)+voices(1)+pats_lo(1)+pats_hi(1)+bpm(1)+tick(1)+pad(7)
TAUD_SONG_ENTRY = 16 # offset(4)+voices(1)+pats(2)+bpm(1)+tick(1)+basenote(2)+basefreq(4)+flags(1)
SAMPLEBIN_SIZE = 770048
INSTBIN_SIZE = 16384 # 256 instruments × 64 bytes
SAMPLEINST_SIZE = SAMPLEBIN_SIZE + INSTBIN_SIZE # 786432
@@ -898,17 +898,20 @@ def assemble_taud(h: S3MHeader, instruments: list, patterns: list) -> bytes:
pat_bin, pat_remap, num_taud_pats = deduplicate_patterns(bytes(pat_bin), orig_count)
vprint(f" patterns: {orig_count}{num_taud_pats} unique ({orig_count - num_taud_pats} deduplicated)")
# Song table row (16 bytes): offset(4)+voices(1)+patsLo(1)+patsHi(1)+bpm(1)+tick(1)+basenote(2)+basefreq(4)+pad(1)
# Song table row (16 bytes): offset(4)+voices(1)+pats(2)+bpm(1)+tick(1)+basenote(2)+basefreq(4)+flags(1)
# Built after dedup so num_taud_pats reflects the unique count.
song_table = struct.pack('<IBHBBHf',
# flags byte: bit 1 (f) = Amiga pitch-slide mode (mirrors the S3M linear_slides flag inverted)
flags_byte = 0x00 if h.linear_slides else 0x02
song_table = struct.pack('<IBHBBHfB',
song_offset,
C,
num_taud_pats,
bpm_stored,
speed,
0x9000, # C8
0xA000, # C9
8363.0, # Hz
) + b'\x00'
flags_byte,
)
assert len(song_table) == TAUD_SONG_ENTRY
# Cue sheet (using remapped pattern indices)

View File

@@ -2121,6 +2121,13 @@ Play Head Flags
NOTE: changing from PCM mode to Tracker mode or vice versa will also reset the parameters as described above
Byte 2
- PCM Mode: Write non-zero value to start uploading; always 0 when read
- Tracker Mode: Global mixer flags. Maps directly to Taud effect symbol '1'
0b 0000 00fp
p: panning mode (0: linear, 1: equal-power)
f: pitchshift mode (0: tone-linear, 1: Amiga)
Tracker command may change the mixer state, but the changes WILL NOT BE REFLECTED BACK.
Starting a new song will use whatever written to this register. In other words, changes
made by songs will not persist.
Byte 3 (Tracker Mode)
- BPM (24 to 279. Play Data will change this register)
Byte 4 (Tracker Mode)
@@ -2201,14 +2208,15 @@ Endianness: Little
[HEADER]
[SAMPLE+INSTRUMENT BIN IMAGE (GZip or Zstd compressed. Read 4-byte magic to determine)]
[SONG TABLE]
[PATTERN BIN for SONG 0]
[CUE SHEET for SONG 0]
[PATTERN BIN for SONG 1]
[CUE SHEET for SONG 1]
[PATTERN BIN for SONG 2]
[CUE SHEET for SONG 2]
[PATTERN BIN for SONG 0 (GZip or Zstd compressed)]
[CUE SHEET for SONG 0 (GZip or Zstd compressed)]
[PATTERN BIN for SONG 1 (GZip or Zstd compressed)]
[CUE SHEET for SONG 1 (GZip or Zstd compressed)]
[PATTERN BIN for SONG 2 (GZip or Zstd compressed)]
[CUE SHEET for SONG 2 (GZip or Zstd compressed)]
...
[PROJECT DATA] (optional)
[DATA BLOCKS WITH FOURCC HEADER (see Project Data section)]
## Header
Byte[8] Magic
@@ -2225,24 +2233,24 @@ Endianness: Little
Uint16 Number of patterns (0 is invalid. pattern bin length = numPats * 8 bytes)
Uint8 Initial BPM (bias of -24. 0x00=24, 0xFF=279)
Uint8 Initial Tickrate (0 is invalid)
Uint16 Current Tuning base note (1..65533). A3 (western default) is 0x4C00. C8 (tracker default) is 0x9000. If zero, assume the tracker default value
Uint16 Current Tuning base note (1..65533). A4 (western default) is 0x5C00. C9 (tracker default) is 0xA000. If zero, assume the tracker default value
Float32 Frequency at the base note. Tracker default is 8363.0. If zero, assume the tracker default
Byte[1] Reserved for future versions
Uint8 Flags for Global Behaviour (effect symbol '1')
Taud device can queue up to 2 "playdata" in its buffer, which can be interpreted as a song.
* Known standard tunings
A440. ISO standard
A435. Former French standard (year 1859)
A452. Old Philharmonic pitch (19th century Britain)
C256. Power of two
C262. Modern Chinese a-ak tuning convention
C311. Korean hyang-ak tuning standard (ROK National Gugak Center)
* Known standard tunings:
A4 @ 440 Hz. ISO standard
A4 @ 435 Hz. Former French standard (year 1859)
A4 @ 452 Hz. Old Philharmonic pitch (19th century Britain)
C4 @ 256 Hz. Power of two
C4 @ 262 Hz. Modern Chinese a-ak tuning convention
C4 @ 311 Hz. Korean hyang-ak tuning standard (ROK National Gugak Center)
For your reference, tracker default tuning at A3 is 439.526 Hz (8363*2^(3/4) / 32)
For your reference, tracker default tuning at A4 is 439.526 Hz (8363*2^(3/4) / 32)
## Pattern Bin and Cue Sheet
Raw Pattern Bin/Cue Sheet images
Pattern Bin/Cue Sheet images
## Project Data
@@ -2293,19 +2301,16 @@ prefixes:
Byte[*] Song composer, null terminated. Encoding: UTF-8
Byte[*] Song copyright string, null terminated. Encoding: UTF-8
* nota. Custom notation definition
* nota. Custom notation definition (version 'a')
* Repetition of:
Uint8 Notation index (starting from zero) used by songs
Uint32 Size of this notation following this field
Uint8 Flags
0b 0000 000t
t: NOT using interval system (you are responsible for defining every notes expressible)
Uint8 Reserved
Float32 Interval size (octave system = 2.0f). If Flag 't' is set, this must be NaN. 0f and Infinity are considered illegal
Uint16 Notes between interval MINUS ONE (or octave); 12-TET will have value 11. 0 is considered illegal
Uint16 Reserved for flags
Float32 Interval size (octave system = 2.0f). If you are not using an interval system (which means you are responsible for defining every note expressible), this must be NaN. 0f and Infinity are considered illegal
Uint16 Notes between interval MINUS ONE (or octave); 12-TET will have value 11
Byte[8] Reserved
Byte[*] Name, null terminated. Encoding: UTF-8
Byte[*] Notation table. 0xFF-separated and null-terminated. Encoding: raw bytes
Byte[*] Notation table. 0xFF-separated and null-terminated. Encoding: Taud charset
Uint16[*] Frequency table. Size of the table is defined by "Notes between interval MINUS ONE". This is a lookup table of relative pitch offsets (against the base tuning note) in 4096-TET space. Index zero of this table will be 0x0 if you read the spec right
Note: custom notations will use internal index 65535 down to 65520 (index 0 = 65535, index 15 = 65520)
@@ -2317,7 +2322,12 @@ prefixes:
4. Frequency-Offset Table from the previous step will be applied against the "Base Note at C3" to construct the notes within the notation. Value at index zero of the Frequency Table must be 0
5. The progress will continue outside the "root interval" (C3..C4) to build a complete note-to-frequency table
Note: if your sample is pre-tuned for your system, keep the project setting as A4,440Hz. If you are not working with the conventional octave system, you still need to specify the Interval Size
Note: if your sample is pre-tuned for your system, keep the project setting as the defaults. If you are not working with the conventional octave system, you still need to specify the Interval Size
* Suggested notation serialisation format (for notation editor, etc.)
Byte[8] Magic (\x1E T a u d n o t)
Uint8 Version (Ascii 'a')
Bytes Notation definitions (see above)
--------------------------------------------------------------------------------

View File

@@ -13,6 +13,7 @@ import net.torvald.tsvm.VM
import net.torvald.tsvm.toInt
import java.io.ByteArrayInputStream
import kotlin.math.cos
import kotlin.math.log2
import kotlin.math.pow
import kotlin.math.roundToInt
import kotlin.math.sin
@@ -124,6 +125,12 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
const val SAMPLING_RATE = 32000
const val TRACKER_CHUNK = 512
const val TRACKER_C3 = 0x4000
// Amiga period at TRACKER_C3 for a standard 8363 Hz instrument (NTSC clock 3579545 Hz).
// Used to implement Amiga-mode pitch slides (effect '1' f-bit or song-table flag).
const val AMIGA_BASE_PERIOD = 214.0
// Scale factor that converts a Taud coarse-slide unit back to one Amiga period unit.
// Taud coarse unit = round(ST3_unit × 64/3), so the inverse is × 3/64.
const val AMIGA_PERIOD_SCALE = 3.0 / 64.0
}
internal val sampleBin = UnsafeHelper.allocate(770048L, this)
@@ -1165,6 +1172,16 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
private fun computePlaybackRate(inst: TaudInst, noteVal: Int): Double =
inst.samplingRate.toDouble() / SAMPLING_RATE * 2.0.pow((noteVal - TRACKER_C3) / 4096.0)
// Applies one tick of Amiga-mode pitch slide. slideArg uses the same sign convention as
// linear mode: negative = pitch down (E effect), positive = pitch up (F effect).
// The Taud coarse-slide value is converted back to Amiga period units via AMIGA_PERIOD_SCALE.
private fun amigaSlide(noteVal: Int, slideArg: Int): Int {
val period = AMIGA_BASE_PERIOD * 2.0.pow(-(noteVal - TRACKER_C3).toDouble() / 4096.0)
// Negate slideArg: pitch down (slideArg < 0) → period up, pitch up (slideArg > 0) → period down.
val newPeriod = (period - slideArg * AMIGA_PERIOD_SCALE).coerceAtLeast(1.0)
return (TRACKER_C3 + 4096.0 * log2(AMIGA_BASE_PERIOD / newPeriod)).roundToInt()
}
private fun advanceEnvelope(voice: Voice, inst: TaudInst, tickSec: Double) {
// Volume envelope
// sustain byte: bit7=enabled, bits[5:3]=end_idx, bits[2:0]=start_idx
@@ -1406,8 +1423,12 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
when (op) {
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
// 1 $xx00 — Global behaviour flags byte in the high byte (see TAUD_NOTE_EFFECTS.md §1).
// bit 0 (p): 0=linear pan, 1=equal-power pan
// bit 1 (f): 0=linear pitch slides, 1=Amiga-mode pitch slides
val flags = rawArg ushr 8
ts.panLaw = flags and 1
ts.amigaMode = (flags and 2) != 0
}
EffectOp.OP_A -> {
val tr = (rawArg ushr 8) and 0xFF
@@ -1609,7 +1630,10 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
// Pitch slides (E/F coarse on tick > 0).
if (ts.tickInRow > 0 && (voice.slideMode == 1 || voice.slideMode == 2)) {
voice.noteVal = (voice.noteVal + voice.slideArg).coerceIn(0, 0xFFFE)
voice.noteVal = if (ts.amigaMode)
amigaSlide(voice.noteVal, voice.slideArg).coerceIn(0, 0xFFFE)
else
(voice.noteVal + voice.slideArg).coerceIn(0, 0xFFFE)
voice.basePitch = voice.noteVal
}
@@ -2071,7 +2095,8 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
val voices = Array(20) { Voice() }
// Global mixer config (effect 1).
var panLaw = 0 // 0 = linear balance (default), 1 = equal-power
var panLaw = 0 // 0 = linear balance (default), 1 = equal-power
var amigaMode = false // false = linear pitch slides, true = Amiga period-space slides
// 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
@@ -2111,6 +2136,10 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
) {
var trackerState: TrackerState? = TrackerState() // default mode is tracker (isPcmMode=false)
// Initial global behaviour flags (song-table byte, written via MMIO register 7 in tracker mode).
// Applied to TrackerState on every resetParams(); in-pattern effect '1' can override later.
var initialGlobalFlags: Int = 0
// flags
var isPcmMode: Boolean = false
set(value) {
@@ -2140,7 +2169,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
4 -> masterVolume.toByte()
5 -> masterPan.toByte()
6 -> (isPcmMode.toInt(7) or isPlaying.toInt(4) or pcmQueueSizeIndex.and(15)).toByte()
7 -> 0
7 -> initialGlobalFlags.toByte()
8 -> (bpm - 24).toByte()
9 -> tickRate.toByte()
else -> throw InternalError("Bad offset $index")
@@ -2165,7 +2194,10 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
pcmQueueSizeIndex = (it and 0b00001111)
if (it and 0b00100000 != 0) purgeQueue()
} }
7 -> if (isPcmMode) { pcmUpload = true } else {}
7 -> if (isPcmMode) { pcmUpload = true } else {
initialGlobalFlags = byte
trackerState?.let { ts -> ts.panLaw = byte and 1; ts.amigaMode = (byte and 2) != 0 }
}
8 -> { bpm = byte + 24 }
9 -> { tickRate = byte }
else -> throw InternalError("Bad offset $index")
@@ -2194,7 +2226,8 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
ts.pendingOrderJump = -1; ts.pendingRowJump = -1
ts.patternDelayRemaining = 0; ts.patternDelayActive = false
ts.sexWinningChannel = -1
ts.panLaw = 0
ts.panLaw = initialGlobalFlags and 1
ts.amigaMode = (initialGlobalFlags and 2) != 0
ts.voices.forEach {
it.active = false
it.channelVolume = 0x3F

View File

@@ -1961,7 +1961,7 @@ void main() {
val DEFAULT_PALETTE = intArrayOf( // 0b rrrrrrrr gggggggg bbbbbbbb aaaaaaaa
255,
136,
17663,
35071,
48127,