Data formats
These are the bytes that the tools write and the library reads. Numbers of more than one byte are little-endian, if a table does not tell otherwise.
Packed pixels and the SHR screen
A pixel is a 4-bit palette index. A byte holds two pixels. The left pixel is in bits 7-4. A row of w pixels uses (w + 1) / 2 bytes.
| AUX bank 0 | Contents |
|---|---|
| $2000-$9CFF | 200 rows of 160 bytes. Row y starts at $2000 + 160y. |
| $9D00-$9DC7 | One scan-line control byte for each row. libtini writes 0: 320-pixel mode, palette 0. |
| $9DC8-$9DFF | Padding. libtini writes 0. |
| $9E00-$9FFF | Sixteen palettes of sixteen 0RGB words. libtini uses palette 0 at $9E00. |
A palette word is 0x0RGB, with four bits each for red, green and blue. Thus 0x0F00 is red and 0x0FFF is white.
Palette JSON
["#000000", "#FFFFFF", "#FF0000", 255, 4095]
The list has a maximum of sixteen entries. Each entry is a 0RGB integer 0-4095 or a "#RRGGBB" string. A string keeps the high four bits of each channel. Missing entries are 0. assets.py image --palette-output writes the sixteen words as 32 bytes.
TI4 images
| Offset | Bytes | Contents |
|---|---|---|
| 0 | 4 | TI4 and a zero byte |
| 4 | 2 | Width, 1-320 |
| 6 | 2 | Height, 1-200 |
| 8 | stride * height | Packed rows, stride = (width + 1) / 2 |
8 + stride * height | ((width + 7) / 8) * height | Optional opacity mask: one bit for each pixel, bit 7 at the left, 1 for opaque. Index 0 is transparent. |
A surface view uses offset 8. The mask_offset of a ti_sprite is the offset of the mask.
Indexed image JSON
{"width": 4, "height": 2, "pixels": [0, 1, 1, 0, 1, 2, 2, 1], "masked": true}
TIF fonts
| Offset | Bytes | Contents |
|---|---|---|
| 0 | 4 | TIF1 |
| 4 | 1 | First character code |
| 5 | 1 | Glyph count, 1-255. The first code plus the count is a maximum of 256. |
| 6 | 1 | Glyph width, 1-8 |
| 7 | 1 | Glyph height, 1-16 |
| 8 | count * height | Glyph rows, one byte each, bit 7 at the left |
{"width": 3, "height": 3, "first": 48, "glyphs": [["###", "#.#", "###"]]}
TIM tile maps
| Offset | Bytes | Contents |
|---|---|---|
| 0 | 4 | TIM1 |
| 4 | 2 | Columns, 1-256 |
| 6 | 2 | Rows, 1-256 |
| 8 | columns * rows | Tile numbers, row by row |
{"rows": [[0, 1, 2], [3, 4, 5]], "tile_count": 6}
TPK1 resource packs
| Offset | Bytes | Contents |
|---|---|---|
| 0 | 4 | TPK1 |
| 4 | 2 | Entry count, 1-16 |
| 6 | 2 | Zero |
| 8 | 24 for each entry | The directory |
| 8 + 24n | Entry data, in directory sequence |
| Entry offset | Bytes | Contents |
|---|---|---|
| 0 | 16 | Name, filled with NUL bytes: a capital letter, then capitals, digits, . or _, a maximum of 15 characters |
| 16 | 4 | Absolute offset of the data in the file |
| 20 | 4 | Size, not zero |
- Names are unique and in strictly increasing byte sequence. All padding bytes are zero.
- The data starts after the directory. The entries are in name sequence, and they do not overlap.
- All bytes are in the file. The file fits the 24-bit length of ProDOS.
ti_pack_load checks all of these conditions for each entry before it allocates memory. The pack manifest is {"entries": [{"name": "SHIP", "path": "ship.ti4"}]}. Each entry is 1 byte to 4 MiB.
Pattern songs
tools/music.py writes these bytes for a song document. ti_song_start reads them.
| Offset | Bytes | Contents |
|---|---|---|
| 0 | 2 | Device ticks for each row, 8.8 fixed point: 256-25,600 (1-100 ticks) |
| 2 | 1 | Tracks, 1-12 |
| 3 | 1 | Order positions, 1-255 |
| 4 | 1 | Loop position, less than the number of positions |
| 5 | 1 | Notes in the period table, 1-96 |
| 6 | 1 | Patterns, 1-255 |
| 7 | 3 for each track | Volume, decay ticks and floor, each 0-15 |
| then | 2 for each note | Tone periods for the 1 MHz base clock, 0-2047 |
| then | 2 for each pattern | Offset of each pattern from the first byte of the song |
| then | positions for each track | Pattern numbers. The order of track 0 is first. |
| then | Pattern bytes |
| Pattern byte | Meaning |
|---|---|
| $00-$5F | Play that note of the period table |
| $60 | Rest |
| $61 | Hold the current sound |
| $80 + n | The next notes, rests and holds last n + 1 rows, 1-64 |
| $C0 + v, $D0 + d, $E0 + f | Set the volume, decay or floor of the track |
| $FF | End of the pattern |
The row length, volume, decay and floor continue into the next pattern. Each pattern starts after the tables. It ends in the song with $FF, and it holds at least one note, rest or hold.
At each row, a track whose note has ended reads its next note, rest or hold. A note sets the tone and starts at the volume of the track. Every decay ticks, it decreases by one level, until it reaches the floor. A decay of 0 keeps the volume.
Decay runs before the row, so a note starts at full volume. After a late update, decay catches up a maximum of 16 ticks.
Song JSON
{"credit": "optional text for the header comment",
"song": {"bpm": 152, "rows_per_beat": 8, "loop": 0,
"tracks": [{"name": "lead", "volume": 12, "decay": 4, "floor": 8, "order": ["a", "a", "b"]}],
"patterns": {"a": "D#5:2 F#5:2 -:4 G#5:8", "b": "v10 B5:12 ^:4"}}}
bpm20-400 androws_per_beat1-16 must give rows of 1-100 ticks.- Tokens are
NOTE:ROWS(rows 1-255),-:ROWSfor a rest,^:ROWSfor a hold, andvN,dN,fNwith N 0-15. - A note is a name with an optional
#orband an octave. C4 is MIDI note 60. Each note must have a period of 1-2047. - The default values of a track are volume 12, decay 0 and floor 0. All orders have the same number of positions, a maximum of 255. All tracks have the same number of rows.
- The compiler stores only the notes that the song uses, in pitch sequence. It stores only the patterns that an order uses. A note of more than 64 rows continues as holds.
Event score JSON
{"voices": 6, "events": [{"duration": 0, "period": 956, "voice": 0, "volume": 7},
{"duration": 25, "period": 1517, "voice": 1, "volume": 5}]}
voices is 6 or 12. There are 1-512 events. Each event has exactly four keys: duration 0-65,535 ticks, period 0-2047, voice less than voices, and volume 0-15.
At least one duration must not be zero. A maximum of 31 zero durations can follow each other. The header holds a ti_music_event array, with six bytes for each event.
Speech JSON and phoneme bytes
{"phrases": {"SAY_READY": "Ready.", "SAY_SCORE": "Score 100!"},
"pronunciations": {"UP": ["UH", "P"], "CLEAR": "K L IY R"}}
- Phrase names are C identifiers in capitals: a capital or underscore, then capitals, digits or underscores.
- Text contains letters, digits, spaces and the characters
. , ! ? ; : - '. The compiler ignores these characters. - A sequence of digits, 0-9999, becomes number words.
- A pronunciation is a list, or a string of phoneme names with spaces between them. A
PApause separates the words.
Each generated byte holds an SSI-263 phoneme code in bits 0-5 and a duration value in bits 6-7. The duration value is 0 for vowels, 2 for stops and the pause, and 1 for all other sounds.
| Names | Codes |
|---|---|
PA pause | $00 |
Vowels IY AY IH EH AE AA AO OW OY UW UH ER, and AW with the code of AO | $01, $05, $07, $0A, $0C, $0F, $10, $11, $12, $14, $18, $1C |
Y R L W | $03, $1D, $20, $23 |
Stops B D G P T K | $24-$29 |
CH J TH DH Z S ZH SH V F HH M N NG | $2A, $2B, $2C, $2D, $2F, $30, $31, $32, $33, $34, $35, $37, $38, $39 |