|
| 1 | +meta: |
| 2 | + id: xm |
| 3 | + title: Extended Module |
| 4 | + application: |
| 5 | + - FastTracker 2 |
| 6 | + - Protracker |
| 7 | + - MilkyTracker |
| 8 | + - libmodplug |
| 9 | + - Mikmod |
| 10 | + file-extension: xm |
| 11 | + license: Unlicense |
| 12 | + endian: le |
| 13 | + encoding: utf-8 |
| 14 | +doc-ref: | |
| 15 | + http://sid.ethz.ch/debian/milkytracker/milkytracker-0.90.85%2Bdfsg/resources/reference/xm-form.txt |
| 16 | + ftp://ftp.modland.com/pub/documents/format_documentation/FastTracker%202%20v2.04%20(.xm).html |
| 17 | +seq: |
| 18 | + - id: preheader |
| 19 | + type: preheader |
| 20 | + - id: header |
| 21 | + size: preheader.header_size - 4 |
| 22 | + type: header |
| 23 | + - id: patterns |
| 24 | + type: pattern |
| 25 | + repeat: expr |
| 26 | + repeat-expr: header.number_of_patterns |
| 27 | + - id: instruments |
| 28 | + type: instrument |
| 29 | + repeat: expr |
| 30 | + repeat-expr: header.number_of_instruments |
| 31 | +types: |
| 32 | + preheader: |
| 33 | + seq: |
| 34 | + - id: signature0 |
| 35 | + contents: 'Extended Module: ' |
| 36 | + - id: module_name |
| 37 | + size: 20 |
| 38 | + type: strz |
| 39 | + doc: Module name, padded with zeroes |
| 40 | + - id: signature1 |
| 41 | + contents: [0x1a] |
| 42 | + - id: tracker_name |
| 43 | + size: 20 |
| 44 | + type: strz |
| 45 | + doc: Tracker name |
| 46 | + - id: version_number |
| 47 | + type: version |
| 48 | + doc: "Format versions below [0x01, 0x04] have a LOT of differences. Check this field!" |
| 49 | + - id: header_size |
| 50 | + type: u4 |
| 51 | + doc: Header size << Calculated FROM THIS OFFSET, not from the beginning of the file! >> |
| 52 | + types: |
| 53 | + version: |
| 54 | + seq: |
| 55 | + - id: minor |
| 56 | + type: u1 |
| 57 | + doc: currently 0x04 |
| 58 | + - id: major |
| 59 | + type: u1 |
| 60 | + doc: currently 0x01 |
| 61 | + instances: |
| 62 | + value: |
| 63 | + value: (major<<8) | minor |
| 64 | + header: |
| 65 | + seq: |
| 66 | + - id: song_length |
| 67 | + type: u2 |
| 68 | + doc: Song length (in pattern order table) |
| 69 | + - id: restart_position |
| 70 | + type: u2 |
| 71 | + - id: number_of_channels |
| 72 | + type: u2 |
| 73 | + doc: "(2,4,6,8,10,...,32)" |
| 74 | + - id: number_of_patterns |
| 75 | + type: u2 |
| 76 | + doc: "(max 256)" |
| 77 | + - id: number_of_instruments |
| 78 | + type: u2 |
| 79 | + doc: "(max 128)" |
| 80 | + - id: flags |
| 81 | + type: flags |
| 82 | + - id: default_tempo |
| 83 | + type: u2 |
| 84 | + - id: default_bpm |
| 85 | + type: u2 |
| 86 | + - id: pattern_order_table |
| 87 | + type: u1 |
| 88 | + doc: "max 256" |
| 89 | + repeat: expr |
| 90 | + #repeat-expr: song_length |
| 91 | + repeat-expr: 256 |
| 92 | + flags: |
| 93 | + seq: |
| 94 | + - id: reserved |
| 95 | + type: b15 |
| 96 | + - id: freq_table_type |
| 97 | + type: b1 |
| 98 | + doc: "0 = Amiga frequency table (see below); 1 = Linear frequency table" |
| 99 | + pattern: |
| 100 | + seq: |
| 101 | + - id: header |
| 102 | + type: header |
| 103 | + - id: packed_data |
| 104 | + size: header.main.packed_pattern_data_size |
| 105 | + types: |
| 106 | + header: |
| 107 | + seq: |
| 108 | + - id: header_length |
| 109 | + type: u4 |
| 110 | + doc: Pattern header length |
| 111 | + - id: main |
| 112 | + type: header_main |
| 113 | + size: header_length - 4 |
| 114 | + types: |
| 115 | + header_main: |
| 116 | + seq: |
| 117 | + - id: packing_type |
| 118 | + type: u1 |
| 119 | + doc: Packing type (always 0) |
| 120 | + - id: number_of_rows_raw |
| 121 | + type: |
| 122 | + switch-on: _root.preheader.version_number.value |
| 123 | + cases: |
| 124 | + 0x0102: u1 |
| 125 | + _: u2 |
| 126 | + doc: Number of rows in pattern (1..256) |
| 127 | + - id: packed_pattern_data_size |
| 128 | + type: u2 |
| 129 | + doc: Packed pattern data size |
| 130 | + instances: |
| 131 | + number_of_rows: |
| 132 | + value: number_of_rows_raw + (_root.preheader.version_number.value==0x0102?1:0) |
| 133 | + instrument: |
| 134 | + seq: |
| 135 | + - id: header_size |
| 136 | + type: u4 |
| 137 | + doc: | |
| 138 | + Instrument size << header that is >> |
| 139 | + << "Instrument Size" field tends to be more than the actual size of the structure documented here (it includes also the extended instrument sample header above). So remember to check it and skip the additional bytes before the first sample header >> |
| 140 | + - id: header |
| 141 | + size: header_size - 4 |
| 142 | + type: header |
| 143 | + - id: samples_headers |
| 144 | + type: sample_header |
| 145 | + repeat: expr |
| 146 | + repeat-expr: header.number_of_samples |
| 147 | + - id: samples |
| 148 | + type: samples_data(_index) |
| 149 | + repeat: expr |
| 150 | + repeat-expr: header.number_of_samples |
| 151 | + types: |
| 152 | + header: |
| 153 | + seq: |
| 154 | + - id: name |
| 155 | + size: 22 |
| 156 | + type: strz |
| 157 | + - id: type |
| 158 | + type: u1 |
| 159 | + doc: Usually zero, but this seems pretty random, don't assume it's zero |
| 160 | + - id: number_of_samples |
| 161 | + type: u2 |
| 162 | + - id: extra_header |
| 163 | + type: extra_header |
| 164 | + if: number_of_samples > 0 |
| 165 | + extra_header: |
| 166 | + seq: |
| 167 | + - id: sample_header_size |
| 168 | + type: u4 |
| 169 | + - id: sample_number_for_all_notes |
| 170 | + type: u1 |
| 171 | + repeat: expr |
| 172 | + repeat-expr: 96 |
| 173 | + - id: points_for_volume_envelope |
| 174 | + type: envelope_point |
| 175 | + repeat: expr |
| 176 | + repeat-expr: 12 |
| 177 | + - id: points_for_panning_envelope |
| 178 | + type: envelope_point |
| 179 | + repeat: expr |
| 180 | + repeat-expr: 12 |
| 181 | + - id: number_of_volume_points |
| 182 | + type: u1 |
| 183 | + - id: number_of_panning_points |
| 184 | + type: u1 |
| 185 | + |
| 186 | + - id: volume_sustain_point |
| 187 | + type: u1 |
| 188 | + - id: volume_loop_start_point |
| 189 | + type: u1 |
| 190 | + - id: volume_loop_end_point |
| 191 | + type: u1 |
| 192 | + |
| 193 | + - id: panning_sustain_point |
| 194 | + type: u1 |
| 195 | + - id: panning_loop_start_point |
| 196 | + type: u1 |
| 197 | + - id: panning_loop_end_point |
| 198 | + type: u1 |
| 199 | + |
| 200 | + - id: volume_type |
| 201 | + type: u1 |
| 202 | + enum: type |
| 203 | + - id: panning_type |
| 204 | + type: u1 |
| 205 | + enum: type |
| 206 | + |
| 207 | + - id: vibrato_type |
| 208 | + type: u1 |
| 209 | + - id: vibrato_sweep |
| 210 | + type: u1 |
| 211 | + - id: vibrato_depth |
| 212 | + type: u1 |
| 213 | + - id: vibrato_rate |
| 214 | + type: u1 |
| 215 | + - id: volume_fadeout |
| 216 | + type: u2 |
| 217 | + - id: reserved |
| 218 | + type: u2 |
| 219 | + types: |
| 220 | + envelope_point: |
| 221 | + doc: | |
| 222 | + Envelope frame-counters work in range 0..FFFFh (0..65535 dec). |
| 223 | + BUT! FT2 only itself supports only range 0..FFh (0..255 dec). |
| 224 | + Some other trackers (like SoundTracker for Unix), however, can use the full range 0..FFFF, so it should be supported. |
| 225 | + !!TIP: This is also a good way to detect if the module has been made with FT2 or not. (In case the tracker name is brain- deadly left unchanged!) |
| 226 | + Of course it does not help if all instruments have the values inside FT2 supported range. |
| 227 | + The value-field of the envelope point is ranged between 00..3Fh (0..64 dec). |
| 228 | + seq: |
| 229 | + - id: x |
| 230 | + type: u2 |
| 231 | + doc: Frame number of the point |
| 232 | + - id: y |
| 233 | + type: u2 |
| 234 | + doc: Value of the point |
| 235 | + enums: |
| 236 | + type: |
| 237 | + 0: on |
| 238 | + 1: sustain |
| 239 | + 2: loop |
| 240 | + samples_data: |
| 241 | + doc: | |
| 242 | + The saved data uses simple delta-encoding to achieve better compression ratios (when compressed with pkzip, etc.) |
| 243 | + Pseudocode for converting the delta-coded data to normal data, |
| 244 | + old = 0; |
| 245 | + for i in range(data_len): |
| 246 | + new = sample[i] + old; |
| 247 | + sample[i] = new; |
| 248 | + old = new; |
| 249 | + params: |
| 250 | + - id: index |
| 251 | + type: u2 |
| 252 | + seq: |
| 253 | + - id: samples_data |
| 254 | + type: |
| 255 | + switch-on: _parent.samples_headers[index].type.is_sample_data_16_bit |
| 256 | + cases: |
| 257 | + true: u2 |
| 258 | + false: u1 |
| 259 | + repeat: expr |
| 260 | + repeat-expr: _parent.samples_headers[index].sample_length |
| 261 | + sample_header: |
| 262 | + seq: |
| 263 | + - id: sample_length |
| 264 | + type: u4 |
| 265 | + - id: sample_loop_start |
| 266 | + type: u4 |
| 267 | + - id: sample_loop_length |
| 268 | + type: u4 |
| 269 | + |
| 270 | + - id: volume |
| 271 | + type: u1 |
| 272 | + - id: fine_tune |
| 273 | + type: s1 |
| 274 | + doc: -16..+15 |
| 275 | + - id: type |
| 276 | + type: loop_type |
| 277 | + - id: panning |
| 278 | + type: u1 |
| 279 | + doc: (0-255) |
| 280 | + - id: relative_note_number |
| 281 | + type: s1 |
| 282 | + - id: reserved |
| 283 | + type: u1 |
| 284 | + - id: name |
| 285 | + size: 22 |
| 286 | + type: strz |
| 287 | + types: |
| 288 | + loop_type: |
| 289 | + seq: |
| 290 | + - id: reserved0 |
| 291 | + type: b3 |
| 292 | + - id: is_sample_data_16_bit |
| 293 | + type: b1 |
| 294 | + - id: reserved1 |
| 295 | + type: b2 |
| 296 | + - id: loop_type |
| 297 | + type: b2 |
| 298 | + enum: loop_type |
| 299 | + enums: |
| 300 | + loop_type: |
| 301 | + 0: none |
| 302 | + 1: forward |
| 303 | + 2: ping_pong |
0 commit comments