Skip to content

Instantly share code, notes, and snippets.

@RepellantMold
Last active April 10, 2024 01:52
Show Gist options
  • Save RepellantMold/070a56e4f35a406d16b98d5358d15419 to your computer and use it in GitHub Desktop.
Save RepellantMold/070a56e4f35a406d16b98d5358d15419 to your computer and use it in GitHub Desktop.

Unofficial STM format documentation

All of this information has been accumulated over reading some sources. Made by RepellantMold, 2023/2024.

Main header

Offset (decimal and hex) Size of field (bytes) Explanation of field (accumulated information from various documents)
0 20 Song title (in ASCIIZ)
20/0x14 8 Magic string, common value is "!Scream!" (this is not checked in either Scream Tracker 2 or 3 but other importers tend to be picky about this!)
28/0x1C 1 0x1A (checked in Scream Tracker 3 but not 2)
29/0x1D 1 File type (song = 1, module = 2)
30/0x1E 1 Major version
31/0x1F 1 Minor version
32/0x20 1 Initial tempo
33/0x21 1 Number of patterns
34/0x22 1 Global volume
35/0x23 13 Reserved
48/0x30 Instrument header * 31 Instrument information
1024/0x410 128 Order list (decimal 99 for blank entries)
1168/0x490 4 * 64 * Number of patterns Pattern data
after the pattern data sample size, padded to 16 byte boundaries PCM data

Instrument header

Offset (decimal and hex) Size of field (bytes) Explanation of field (accumulated information from various documents)
0 12 Sample name (in ASCIIZ)
12/0x0C 1 0x00
13/0x0D 1 Insrtument disk
14/0x0E 2 Parapointer to sample data
16/0x10 2 Sample length
18/0x12 2 Loop start
20/0x14 2 Loop end (65535/0xFFFF denotes no loop)
22/0x16 1 Default volume
23/0x17 1 Reserved
24/0x18 2 C2 speed
26/0x1A 6 Reserved

Pattern

The pattern format consists of the following...

byte 1: oooonnnn
byte 2: iiiiivvv
byte 3: VVVVeeee
byte 4: pppppppp

Byte meanings

o = octave (valid range 0 - 4, all other values are invalid/garbage and 10 - 15 show up as garbage characters)
n = note (valid range 0 - 11 = C - B, other values are undefined)
i = instrument (valid range 0 - 31, 0 = blank cell)
Vv = volume (valid range 0 - 127, 65 = blank cell, 66+ = undefined, causes awful audio clipping and shows up as "??" in the editor)
e = effect (valid range 0 (no effect) - 15, they show up as A (1 internally) - O in the editor, however only A - J are defined with all other effects doing nothing and even bugging out Scream Tracker 3 when importing)
p = effect parameter

Effects

The effects have really strange/quirky behavior when compared to other trackers and even Scream Tracker 3.

Effect Function in ST2/STMIK - in ST3/others Notes/Quirks (Effect memory does not exist in Scream Tracker 2, so a parameter of 0 will act like a no-op for any effect that normally has it like volume slide, porta slides, vibrato, etc.)
Axx Set speed - Set ticks per row It has a scaling factor alongside setting ticks per row.
Bxx Set next order - Position jump It does not perform an immediate pattern break unlike most other trackers.
Cxx Pattern break The parameter is ignored which is identical behavior to NoiseTracker.
Dxy Volume slide There's no fine slides, and y will take priority if both x and y are specified (which is backwards from how it's usually handled).
Exx Portamento down This effect can cause the note period to underflow, and there are no fine/extra-fine slides.
Fxx Portamento up This effect can cause the note period to overflow, ditto for fine/extra-fine slides.
Gxx Tone portamento It does not reset the sample volume unlike most other trackers.
Hxy Vibrato The depth is doubled compared to other trackers.
Ixy Tremor A parameter of 0 will be a very fast tremor which is identical to how it behaves in Scream Tracker 3 before v3.20.
Jxy Arpeggio The effect tends to skip to y halfway through a row if x was 0, it's not commonly implemented this way however.
Kxx (No-op) - Vibrato + Volume slide This can be entered into the editor but it does not do anything.
Lxx (No-op) - Tone portamento + Volume slide Ditto.
Mxx (No-op) Ditto.
Nxx (No-op) Ditto.
Oxx (No-op) - Set sample offset Ditto.

Byte 1 can also have special meanings, specificially if it is...

value meaning
251/0xFB the next 3 bytes do not exist, and are 0
252/0xFC the next 3 bytes do not exist, note = -0- (note cut)
253/0xFD the next 3 bytes do not exist, note = ...
254/0xFE note = -0-
255/0xFF note = ...

Do note that these pseudocode examples does not handle the special note cases.

Reading (pseudocode)

switch(byte1) {
    default:
    octave = byte1 & 0xF0;
    pitch = byte1 >> 4;
    note = octave * 12 + pitch;
    break;

    case 251:
	instrument = 0;
	volume = 0;
	effect_type = 0;
	effect_param = 0;
	
    case 252:
    case 253:
    skip bytes 2 - 4
    break;

    case 254:
    case 255:
    note = byte1;
    break;
}

instrument = byte2 >> 3;                    // 0=empty

volume = (byte2 & 0x07) | (byte3 >> 4);     // 65=empty, 66-127=undefined

effect_type = byte3 & 0x0F;

effect_param = byte4;

Writing (pseudocode)

octave = note / 12;
pitch = note % 12;

byte1 = pitch | (octave << 4);
byte2 = (instrument << 3) | (volume & 0x07);
byte3 = ((volume & 0x78) << 1) | effect_type;
byte4 = effect_param;

These pseudocode examples are made by cs127

References

Libxmp's STM loader OpenMPT's STM loader

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment