AHX-module format (formerly THX module format).
Format description by Stuart Caie aka Kyzer/CSG (kyzer@4u.net)
Version : 15 march 2000


Authorized publishing by Dexter/Abyss (Dexter.Abyss@iName.com), but with no warranty
of correctness, by request of lclevy@club-internet.fr.

Please contact Dexter/Abyss and Kyzer if any code based on this file is written .
Dexter and Bartman player code source might become public soon.


The name THX, THX Sound System and the THX logo are the property of
Lucasfilm, and were stolen by Abyss for their player. Due to legalities,
the player has been renamed AHX, and all references to the Abyss music
system must be termed "AHX".

That said, all current and previous AHX modules begin with the letters "T",
"H" and "X".

This document describes the AHX0 and AHX1 format, no later version of AHX
is guaranteed to follow this format. This information may be made invalid
or outdated at any time without warning.


All multiple-byte data elements are stored in big-endian (Motorola) format.
For example, a 32-bit long is composed like this:

address + 0 = bits 31-24
address + 1 = bits 23-16
address + 2 = bits 15-8
address + 3 = bits 7-0


Overview of AHX format:

[Header: 14 bytes]
[Subsong list: SS*2 bytes]
[Position list: LEN*8 bytes]
[Tracks: (TRK+1)*TRL*3 bytes]
[Samples: SMP entries, each one individually sized]
[Names: SMP+1 entries, each one individually sized]


[-header format-------------------------------------------------------------]
LONG (bytes 0-3): This is the ID header. It is either "THX"<<8 aka AHX0
                  for songs saved with AHX v1.00 to 1.27, or it is
                  ("THX"<<8)+1 aka AHX1 for songs saved with
                  AHX 2.0 or better. These are all the versions currently
                  known/available. Versions previous to 1.00 save in the
                  same format as 1.00, except the 0.xx versions of the
                  replayer can't do some things. Never use 0.xx replayers,
                  they are obsolete, 1.00 is the minimum standard.

WORD (bytes 4,5): This is the amount of bytes to skip (from offset 0) to
                  reach the songtitle and samplenames. As it is only a
                  word-size, the value is wrong for AHX modules over 65536
                  bytes. Also, it is not needed by the player, so it could
                  be hacked to an invalid value. Ignore this value when
                  reading but remember to calculate it properly when saving.
                  The calculation is (real_songtitle_offset) & $FFFF.

NYBBLE (byte 6):  Take the top 4 bits of byte six (bits 7-4).
                  Bit 7 indicates if track 0 is saved. If it is 1, track 0
                  is included. If it is 0, track 0 was empty, and is
                  therefore not saved with the module, to save space.

                  Now, the remaining 3 bits (bits 6-4) make a number
                  (calculation: (byte 6)>>4 & %111), this number is always
                  0 if it is an AHX0 mod, but in AHX1 this number is 0-3
                  and is the CIA speed of the module. Let us name this value
                  "SPD". If SPD=0, the mod plays at 50Hz. SPD=1, 100Hz.
                  SPD=2, 150Hz. SPD=3, 200Hz (think of it as single/double/
                  triple/quadruple timing).

WORD (bytes 6,7): After ANDing with $FFF to ignore the top nybble, this word
                  value is the variable "LEN", which is the length of the
                  position list. Valid values for LEN range from 1 to 999.

WORD (bytes 8,9): This is the variable "RES", the automatic restart point
                  for the song after it ends. Valid values for RES range
                  from 0 to (LEN-1).

BYTE 10: This is "TRL", the track length (how many entries are in one track)
         Valid values for TRL range from 1 to 64.

BYTE 11: This is "TRK", the number of actual tracks saved in the mod. Valid
         values for TRK range from 0 to 255.

BYTE 12: This is "SMP", the number of actual samples saved in the mod. Valid
         values for SMP range from 0 to 63.

BYTE 13: This is "SS", the number of subsongs. Valid values for SS range
         from 0 to 255.
[---------------------------------------------------------------------------]


[-Subsong list format-------------------------------------------------------]
There are SS entries in the subsong list. (0 entries is also valid)

Each word-sized entry (2 bytes long) should be between 0 and LEN-1.
[---------------------------------------------------------------------------]


[-Position list format------------------------------------------------------]
There are LEN entries in the position list.

Each entry (8 bytes long) is composed of 4 sets, one for each audiochannel.

Each set (2 bytes long), the first byte is they track to play (must be
between 0 and TRK), the second byte is the transpose value (signed). Any
value is valid in the transpose byte (ie from -$80 to $7F).

Note, that WHEN TRACK 0 ISN'T SAVED, you must look at the track you have to
access. If it is track 0, you must access your OWN copy of a blank track
(192 bytes of cleared memory), not in the module. If it is track 1 to
TRK-1, subtract 1 and access that track instead, so track 8 in the playlist
means access as if it were track 7. Access of track TRK is invalid.
[---------------------------------------------------------------------------]


[-Track format--------------------------------------------------------------]
There are TRK tracks, bunched together. (0 tracks is also valid)

Each track has TRL entries.

Each entry is 24 bits (3 bytes) long, and consists of
bits 23-18 (6 bits): The note. This ranges from 0 (no note) to 60 (B-5)
bits 17-12 (6 bits): The sample. This ranges from 0 to 63.
bits 11-8  (4 bits): The command. See list below
bits 7-0   (8 bits): The command's data. See list below

FOR AHX1

Commands $6 and $7 are invalid and do not exist.
Commands $1,$2,$3,$5,$8,$A and $F may have data of any value.

Cmd  Valid Range
$0   $0 to $9 -- see $B below
$4   $1 to $3F, $41-$7F
$9   $0 to $3F
$C   $0-$40, $50-$90, $A0-$E0
$E   $C0-$CF, $D1-$DF
$B   Interpret first nybble as tens digit, second nybble as units digit,
     if $0 command was issued with $1-$9 as data then use that as a
     hundreds digit. Once this command is parsed, reset the hundred's digit.
     Resultant decimal must be from 0 to LEN-1
$D   Interpret first nybble as tens digit, second nybble as units digit.
     Resultant decimal must be from 0 to TRL-1

FOR AHX0

As above, except:
Command $4 is not valid at all.
Valid range for $D command is $0 alone.
[---------------------------------------------------------------------------]


[-Sample format-------------------------------------------------------------]
There are SMP samples, grouped together (0 samples is also valid)

byte  0: Master volume for sample (0 to 64 valid)
byte  1: bits 7-3: bottom 5 bits (4-0) of the filter modulation speed.
                   Must be 0 in AHX0
byte  1: bits 2-0: The wavelength of the sample. ranges from 0 to 5.
                   0=$04, 1=$08, 2=$10, 3=$20, 4=$40, 5=$80
byte  2: attack length. valid range from 1 to 255
byte  3: attack volume. valid range from 0 to 64
byte  4: decay length. valid range from 1 to 255
byte  5: decay volume. valid range from 0 to 64
byte  6: sustain length. valid range from 1 to 255
byte  7: release length. valid range from 1 to 255
byte  8: release volume. valid range from 0 to 64
byte  9: Unused in AHX0 or AHX1. Should only be 0
byte 10: Unused in AHX0 or AHX1. Should only be 0
byte 11: Unused in AHX0 or AHX1. Should only be 0
byte 12: bit  7  : bit 5 of the filter modulation speed.
                   Must be 0 in AHX0
         bits 6-0: filter modulation lower limit. valid range from 1 to 63.
                   Must be 0 in AHX0
byte 13: vibrato delay. valid range from 0 to 255
byte 14 TOP NYBBLE:    hardcut (bits 6-4) from 0 to 7 and if bit 7, top bit
                       is set then release cut is selected.
                       Must be 0 in AHX0
byte 14 BOTTOM NYBBLE: vibrato depth. valid range from 0 to 15
byte 15: vibrato speed. valid range from 0 to 63
byte 16: square modulation lower limit. valid ranges:
         wavelength $04 : from 32 to 63
         wavelength $08 : from 16 to 63
         wavelength $10 : from  8 to 63
         wavelength $20 : from  4 to 63
         wavelength $40 : from  2 to 63
         wavelength $80 : from  1 to 63
byte 17: square modulation upper limit. valid range from 1 to 63, but really
         should be more than the lower limit!
byte 18: square modulation speed. valid range from 0 to 255
byte 19: bit  7:   bit 6 of the filter modulation speed. Must be 0 in AHX0
         bits 6-0: filter modulation upper limit. valid range from 1 to 63
                   and should be equal or higher than the lower limit.
                   Must be 0 in AHX0
byte 20: playlist default speed (1-255)
byte 21: playlist length ("PLEN") which ranges from 0 to 255. If length is
         0 then this sample is 'empty' and should be skipped, it's values do
         not count.

Now follows the playlist: PLEN entries, grouped together (0 is also valid)
Each entry is 4 bytes (32 bits) long.

bits 31-29 (3 bits) = FX2 command (0-7)
bits 28-26 (3 bits) = FX1 command (0-7)
bits 25-23 (3 bits) = the waveform. 0=hold previous, 1=triangle,
                      2=sawtooth, 3=square, 4=noise, 5,6,7=invalid
bit  22    (1 bit)  = fix note? 1=note fixed, 0=note varies
bits 21-16 (6 bits) = the note data from 0 (no note) to 60 (B-5)
bits 15-8  (8 bits) = the data for FX1
bits 7-0   (8 bits) = the data for FX2

The playlist FX commands are:
0 - null or set filter (data $00-$3F valid, only $00 valid in AHX0)
1 - slide up (data $00-$FF valid)
2 - slide down (data $00-$FF valid)
3 - init square (data $00-$3F valid)
4 - toggle mod (data $00, $01, $0F, $10, $11, $1F, $F0, $F1, $FF valid. Only
    $00 valid in AHX0)
5 - position jump (data 0 to PLEN-1 valid)
6 - aka 'C' - set volume (data $0-$40, $50-$90, $A0-$E0 valid)
7 - aka 'F' - set speed (data $00-$FF valid)
[---------------------------------------------------------------------------]


[-Names format--------------------------------------------------------------]
The names section is last, and is a group of SMP+1 null-terminated strings
grouped together. The first string is the songtitle and should also be a
valid filename (consists only of byte values 0,[32-126],[128-255]). The
rest are sample names, and may be simply null strings (ie only one byte long,
the null terminator.)
[---------------------------------------------------------------------------]

Love, Kyzer