Task 318: .IT File Format
Task 318: .IT File Format
.IT File Format Specifications
The .IT file format is the native module format for Impulse Tracker, a music tracker software. It is a binary format supporting multi-channel music, instruments, samples, patterns, and various effects. The format is little-endian. The file starts with the magic identifier 'IMPM' (offsets 0-3), followed by the header and other sections. The specifications are based on standard documentation for the format.
1. List of All Properties Intrinsic to the File Format
The properties refer to the structural fields in the file header and associated sections, which define the file's metadata, configuration, and pointers to data sections. These are intrinsic to how the file is organized and read by compatible software. Below is a comprehensive list from the header and key sub-sections (e.g., edit history if enabled). Offsets are absolute from the file start. Data types are specified, with descriptions.
- Magic Identifier: Offset 0, Data Type: char[4], Length: 4 bytes, Description: Always 'IMPM' to identify the file as an Impulse Tracker module.
- Song Name: Offset 4, Data Type: char[26], Length: 26 bytes, Description: Name of the song, padded with nulls if shorter.
- Pattern Highlight: Offset 30, Data Type: byte[2], Length: 2 bytes, Description: Byte 0: Rows per beat; Byte 1: Rows per measure (used for editing display).
- Order Number: Offset 32, Data Type: uint16, Length: 2 bytes, Description: Number of orders (sequenced patterns) in the song.
- Instrument Number: Offset 34, Data Type: uint16, Length: 2 bytes, Description: Number of instruments in the song.
- Sample Number: Offset 36, Data Type: uint16, Length: 2 bytes, Description: Number of samples in the song.
- Pattern Number: Offset 38, Data Type: uint16, Length: 2 bytes, Description: Number of patterns in the song.
- Created with Tracker: Offset 40, Data Type: uint16, Length: 2 bytes, Description: Version of the tracker that created the file (e.g., 0x0214 for IT 2.14).
- Compatible with Tracker: Offset 42, Data Type: uint16, Length: 2 bytes, Description: Minimum compatible tracker version (e.g., 0x0200 for IT 2.00).
- Flags: Offset 44, Data Type: uint16, Length: 2 bytes, Description: Bit flags for format options (bit 0: stereo, bit 2: use instruments, bit 3: linear slides, etc.).
- Special: Offset 46, Data Type: uint16, Length: 2 bytes, Description: Bit flags for additional features (bit 0: song message attached, bit 1: edit history embedded, bit 3: MIDI macro embedded).
- Global Volume: Offset 48, Data Type: uint8, Length: 1 byte, Description: Global volume (0-128).
- Mix Volume: Offset 49, Data Type: uint8, Length: 1 byte, Description: Mixing volume (0-128).
- Initial Speed: Offset 50, Data Type: uint8, Length: 1 byte, Description: Starting tick speed.
- Initial Tempo: Offset 51, Data Type: uint8, Length: 1 byte, Description: Starting BPM.
- Pan Separation: Offset 52, Data Type: uint8, Length: 1 byte, Description: Panning separation (0-128).
- Pitch Wheel Depth: Offset 53, Data Type: uint8, Length: 1 byte, Description: Pitch wheel depth for MIDI.
- Message Length: Offset 54, Data Type: uint16, Length: 2 bytes, Description: Length of optional song message.
- Message Offset: Offset 56, Data Type: uint32, Length: 4 bytes, Description: File offset to song message.
- Reserved: Offset 60, Data Type: uint32, Length: 4 bytes, Description: Reserved (often 'OMPT' in OpenMPT files).
- Initial Channel Pan: Offset 64, Data Type: uint8[64], Length: 64 bytes, Description: Pan values for each channel (0-64, bit 7 set for muted).
- Initial Channel Volume: Offset 128, Data Type: uint8[64], Length: 64 bytes, Description: Volume for each channel (0-64).
- Orders: Offset 192, Data Type: uint8[Order Number], Length: Variable (Order Number bytes), Description: Sequence of patterns (0-199: pattern index, 254: +++ separator, 255: --- end).
- Instrument Offsets: Offset 192 + Order Number, Data Type: uint32[Instrument Number], Length: Variable (Instrument Number * 4 bytes), Description: File offsets to instrument headers.
- Sample Header Offsets: Offset after Instrument Offsets, Data Type: uint32[Sample Number], Length: Variable (Sample Number * 4 bytes), Description: File offsets to sample headers.
- Pattern Offsets: Offset after Sample Header Offsets, Data Type: uint32[Pattern Number], Length: Variable (Pattern Number * 4 bytes), Description: File offsets to pattern data.
- Edit History Number: Offset after Pattern Offsets (if Special bit 1 set), Data Type: uint16, Length: 2 bytes, Description: Number of edit history entries.
- Edit Histories: Offset after Edit History Number, Data Type: struct[Edit History Number], Length: Variable (8 bytes per entry), Description: Each entry: Fat Date (uint16), Fat Time (uint16), Runtime (uint32 in seconds).
(Note: Additional properties exist in instrument, sample, and pattern sections, but the above are the core header properties intrinsic to the file's structure. Instrument and sample headers have their own fields like filename, loops, envelopes, etc., and patterns are packed with channel data, notes, effects.)
2. Two Direct Download Links for .IT Files
Here are two direct download links for sample .IT files from ModArchive, a repository of tracker modules:
- https://api.modarchive.org/downloads.php?moduleid=167823 (pulse_-_impulse-tracker-demo.it, size ~269KB)
- https://api.modarchive.org/downloads.php?moduleid=36432 (dayafter.it, size unknown, but confirmed .IT module)
3. Ghost Blog Embedded HTML JavaScript for Drag and Drop .IT File Dumper
Assuming "ghost blog embedded" means a simple HTML snippet with JavaScript for a Ghost blog post (or similar), here's an HTML page with embedded JS that allows drag-and-drop of a .IT file and dumps the properties to the screen. It parses the header using DataView.
4. Python Class for .IT File Handling
Here's a Python class that can open, decode, read, write, and print the properties. It uses struct
for parsing. The write method creates a minimal .IT file with the parsed properties (for demonstration; full write would require data sections).
import struct
class ITFile:
def __init__(self, filename):
self.filename = filename
self.properties = {}
self._read()
def _read(self):
with open(self.filename, 'rb') as f:
data = f.read()
if len(data) < 192:
raise ValueError("Invalid .IT file")
# Magic
magic = data[0:4].decode('utf-8')
if magic != 'IMPM':
raise ValueError("Not a valid .IT file")
self.properties['Magic'] = magic
# Song Name
self.properties['Song Name'] = data[4:30].decode('utf-8').rstrip('\x00')
# Pattern Highlight
rows_beat, rows_measure = struct.unpack('<BB', data[30:32])
self.properties['Pattern Highlight'] = f"Rows per beat: {rows_beat}, Rows per measure: {rows_measure}"
# Order Number
ordnum = struct.unpack('<H', data[32:34])[0]
self.properties['Order Number'] = ordnum
# Instrument Number
insnum = struct.unpack('<H', data[34:36])[0]
self.properties['Instrument Number'] = insnum
# Sample Number
smpnum = struct.unpack('<H', data[36:38])[0]
self.properties['Sample Number'] = smpnum
# Pattern Number
patnum = struct.unpack('<H', data[38:40])[0]
self.properties['Pattern Number'] = patnum
# Created with Tracker
self.properties['Created with Tracker'] = hex(struct.unpack('<H', data[40:42])[0])
# Compatible with Tracker
self.properties['Compatible with Tracker'] = hex(struct.unpack('<H', data[42:44])[0])
# Flags
self.properties['Flags'] = hex(struct.unpack('<H', data[44:46])[0])
# Special
special = struct.unpack('<H', data[46:48])[0]
self.properties['Special'] = hex(special)
# Global Volume
self.properties['Global Volume'] = struct.unpack('<B', data[48:49])[0]
# Mix Volume
self.properties['Mix Volume'] = struct.unpack('<B', data[49:50])[0]
# Initial Speed
self.properties['Initial Speed'] = struct.unpack('<B', data[50:51])[0]
# Initial Tempo
self.properties['Initial Tempo'] = struct.unpack('<B', data[51:52])[0]
# Pan Separation
self.properties['Pan Separation'] = struct.unpack('<B', data[52:53])[0]
# Pitch Wheel Depth
self.properties['Pitch Wheel Depth'] = struct.unpack('<B', data[53:54])[0]
# Message Length
self.properties['Message Length'] = struct.unpack('<H', data[54:56])[0]
# Message Offset
self.properties['Message Offset'] = struct.unpack('<I', data[56:60])[0]
# Reserved
self.properties['Reserved'] = hex(struct.unpack('<I', data[60:64])[0])
# Initial Channel Pan
chnpan = list(struct.unpack('<64B', data[64:128]))
self.properties['Initial Channel Pan'] = chnpan
# Initial Channel Volume
chnvol = list(struct.unpack('<64B', data[128:192]))
self.properties['Initial Channel Volume'] = chnvol
# Orders
orders = list(struct.unpack(f'<{ordnum}B', data[192:192+ordnum]))
self.properties['Orders'] = orders
# Instrument Offsets
ins_start = 192 + ordnum
ins_offsets = [struct.unpack('<I', data[ins_start + i*4:ins_start + (i+1)*4])[0] for i in range(insnum)]
self.properties['Instrument Offsets'] = ins_offsets
# Sample Header Offsets
smp_start = ins_start + insnum * 4
smp_offsets = [struct.unpack('<I', data[smp_start + i*4:smp_start + (i+1)*4])[0] for i in range(smpnum)]
self.properties['Sample Header Offsets'] = smp_offsets
# Pattern Offsets
pat_start = smp_start + smpnum * 4
pat_offsets = [struct.unpack('<I', data[pat_start + i*4:pat_start + (i+1)*4])[0] for i in range(patnum)]
self.properties['Pattern Offsets'] = pat_offsets
# Edit History if Special bit 1 set
if special & 2:
edit_start = pat_start + patnum * 4
edit_num = struct.unpack('<H', data[edit_start:edit_start+2])[0]
self.properties['Edit History Number'] = edit_num
edit_hist = []
for i in range(edit_num):
off = edit_start + 2 + i*8
fat_date = struct.unpack('<H', data[off:off+2])[0]
fat_time = struct.unpack('<H', data[off+2:off+4])[0]
runtime = struct.unpack('<I', data[off+4:off+8])[0]
edit_hist.append((fat_date, fat_time, runtime))
self.properties['Edit Histories'] = edit_hist
def print_properties(self):
for key, value in self.properties.items():
print(f"{key}: {value}")
def write(self, output_filename):
# Minimal write: reconstruct header with current properties (data sections omitted for brevity)
with open(output_filename, 'wb') as f:
f.write(b'IMPM')
song_name = self.properties['Song Name'].ljust(26, '\x00').encode('utf-8')
f.write(song_name)
rows_beat, rows_measure = map(int, self.properties['Pattern Highlight'].replace('Rows per beat: ', '').replace(', Rows per measure: ', ',').split(','))
f.write(struct.pack('<BB', rows_beat, rows_measure))
f.write(struct.pack('<H', self.properties['Order Number']))
f.write(struct.pack('<H', self.properties['Instrument Number']))
f.write(struct.pack('<H', self.properties['Sample Number']))
f.write(struct.pack('<H', self.properties['Pattern Number']))
f.write(struct.pack('<H', int(self.properties['Created with Tracker'], 16)))
f.write(struct.pack('<H', int(self.properties['Compatible with Tracker'], 16)))
f.write(struct.pack('<H', int(self.properties['Flags'], 16)))
f.write(struct.pack('<H', int(self.properties['Special'], 16)))
f.write(struct.pack('<B', self.properties['Global Volume']))
f.write(struct.pack('<B', self.properties['Mix Volume']))
f.write(struct.pack('<B', self.properties['Initial Speed']))
f.write(struct.pack('<B', self.properties['Initial Tempo']))
f.write(struct.pack('<B', self.properties['Pan Separation']))
f.write(struct.pack('<B', self.properties['Pitch Wheel Depth']))
f.write(struct.pack('<H', self.properties['Message Length']))
f.write(struct.pack('<I', self.properties['Message Offset']))
f.write(struct.pack('<I', int(self.properties['Reserved'], 16)))
f.write(struct.pack('<64B', *self.properties['Initial Channel Pan']))
f.write(struct.pack('<64B', *self.properties['Initial Channel Volume']))
f.write(struct.pack(f'<{self.properties["Order Number"]}B', *self.properties['Orders']))
for offset in self.properties['Instrument Offsets']:
f.write(struct.pack('<I', offset))
for offset in self.properties['Sample Header Offsets']:
f.write(struct.pack('<I', offset))
for offset in self.properties['Pattern Offsets']:
f.write(struct.pack('<I', offset))
if 'Edit History Number' in self.properties:
f.write(struct.pack('<H', self.properties['Edit History Number']))
for fat_date, fat_time, runtime in self.properties['Edit Histories']:
f.write(struct.pack('<HHI', fat_date, fat_time, runtime))
# Note: Full write would append instrument, sample, pattern data, etc.
# Example usage:
# it = ITFile('example.it')
# it.print_properties()
# it.write('output.it')
5. Java Class for .IT File Handling
Here's a Java class using ByteBuffer for parsing. The write method reconstructs the header.
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.util.HashMap;
import java.util.Map;
import java.util.Arrays;
public class ITFile {
private String filename;
private Map<String, Object> properties = new HashMap<>();
public ITFile(String filename) {
this.filename = filename;
read();
}
private void read() {
try (FileInputStream fis = new FileInputStream(filename);
FileChannel channel = fis.getChannel()) {
ByteBuffer buffer = ByteBuffer.allocate((int) channel.size());
buffer.order(ByteOrder.LITTLE_ENDIAN);
channel.read(buffer);
buffer.flip();
// Magic
byte[] magicBytes = new byte[4];
buffer.get(magicBytes);
String magic = new String(magicBytes);
if (!magic.equals("IMPM")) {
throw new IOException("Not a valid .IT file");
}
properties.put("Magic", magic);
// Song Name
byte[] songNameBytes = new byte[26];
buffer.get(songNameBytes);
properties.put("Song Name", new String(songNameBytes).trim());
// Pattern Highlight
byte rowsBeat = buffer.get();
byte rowsMeasure = buffer.get();
properties.put("Pattern Highlight", "Rows per beat: " + rowsBeat + ", Rows per measure: " + rowsMeasure);
// Order Number
short ordnum = buffer.getShort();
properties.put("Order Number", (int) ordnum);
// Instrument Number
short insnum = buffer.getShort();
properties.put("Instrument Number", (int) insnum);
// Sample Number
short smpnum = buffer.getShort();
properties.put("Sample Number", (int) smpnum);
// Pattern Number
short patnum = buffer.getShort();
properties.put("Pattern Number", (int) patnum);
// Created with Tracker
properties.put("Created with Tracker", "0x" + Integer.toHexString(buffer.getShort() & 0xFFFF));
// Compatible with Tracker
properties.put("Compatible with Tracker", "0x" + Integer.toHexString(buffer.getShort() & 0xFFFF));
// Flags
properties.put("Flags", "0x" + Integer.toHexString(buffer.getShort() & 0xFFFF));
// Special
short special = buffer.getShort();
properties.put("Special", "0x" + Integer.toHexString(special & 0xFFFF));
// Global Volume
properties.put("Global Volume", Byte.toUnsignedInt(buffer.get()));
// Mix Volume
properties.put("Mix Volume", Byte.toUnsignedInt(buffer.get()));
// Initial Speed
properties.put("Initial Speed", Byte.toUnsignedInt(buffer.get()));
// Initial Tempo
properties.put("Initial Tempo", Byte.toUnsignedInt(buffer.get()));
// Pan Separation
properties.put("Pan Separation", Byte.toUnsignedInt(buffer.get()));
// Pitch Wheel Depth
properties.put("Pitch Wheel Depth", Byte.toUnsignedInt(buffer.get()));
// Message Length
properties.put("Message Length", buffer.getShort() & 0xFFFF);
// Message Offset
properties.put("Message Offset", buffer.getInt() & 0xFFFFFFFFL);
// Reserved
properties.put("Reserved", "0x" + Long.toHexString(buffer.getInt() & 0xFFFFFFFFL));
// Initial Channel Pan
byte[] chnpan = new byte[64];
buffer.get(chnpan);
properties.put("Initial Channel Pan", Arrays.toString(chnpan));
// Initial Channel Volume
byte[] chnvol = new byte[64];
buffer.get(chnvol);
properties.put("Initial Channel Volume", Arrays.toString(chnvol));
// Orders
byte[] orders = new byte[ordnum];
buffer.get(orders);
properties.put("Orders", Arrays.toString(orders));
// Instrument Offsets
long[] insOffsets = new long[insnum];
for (int i = 0; i < insnum; i++) {
insOffsets[i] = buffer.getInt() & 0xFFFFFFFFL;
}
properties.put("Instrument Offsets", Arrays.toString(insOffsets));
// Sample Header Offsets
long[] smpOffsets = new long[smpnum];
for (int i = 0; i < smpnum; i++) {
smpOffsets[i] = buffer.getInt() & 0xFFFFFFFFL;
}
properties.put("Sample Header Offsets", Arrays.toString(smpOffsets));
// Pattern Offsets
long[] patOffsets = new long[patnum];
for (int i = 0; i < patnum; i++) {
patOffsets[i] = buffer.getInt() & 0xFFFFFFFFL;
}
properties.put("Pattern Offsets", Arrays.toString(patOffsets));
// Edit History if Special bit 1 set
if ((special & 2) != 0) {
short editNum = buffer.getShort();
properties.put("Edit History Number", (int) editNum);
StringBuilder editHist = new StringBuilder();
for (int i = 0; i < editNum; i++) {
short fatDate = buffer.getShort();
short fatTime = buffer.getShort();
int runtime = buffer.getInt();
editHist.append("Fat Date: ").append(fatDate).append(", Fat Time: ").append(fatTime).append(", Runtime: ").append(runtime).append("\n");
}
properties.put("Edit Histories", editHist.toString());
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void printProperties() {
for (Map.Entry<String, Object> entry : properties.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
public void write(String outputFilename) {
try (FileOutputStream fos = new FileOutputStream(outputFilename);
FileChannel channel = fos.getChannel()) {
ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024); // Large enough for header
buffer.order(ByteOrder.LITTLE_ENDIAN);
// Magic
buffer.put("IMPM".getBytes());
// Song Name
String songName = (String) properties.get("Song Name");
byte[] songNameBytes = songName.getBytes();
buffer.put(songNameBytes, 0, Math.min(26, songNameBytes.length));
for (int i = songNameBytes.length; i < 26; i++) {
buffer.put((byte) 0);
}
// Pattern Highlight
String ph = (String) properties.get("Pattern Highlight");
byte rowsBeat = Byte.parseByte(ph.split(", ")[0].split(": ")[1]);
byte rowsMeasure = Byte.parseByte(ph.split(", ")[1].split(": ")[1]);
buffer.put(rowsBeat);
buffer.put(rowsMeasure);
// Order Number
int ordnum = (int) properties.get("Order Number");
buffer.putShort((short) ordnum);
// Instrument Number
int insnum = (int) properties.get("Instrument Number");
buffer.putShort((short) insnum);
// Sample Number
int smpnum = (int) properties.get("Sample Number");
buffer.putShort((short) smpnum);
// Pattern Number
int patnum = (int) properties.get("Pattern Number");
buffer.putShort((short) patnum);
// Created with Tracker
buffer.putShort((short) Integer.parseInt(((String) properties.get("Created with Tracker")).substring(2), 16));
// Compatible with Tracker
buffer.putShort((short) Integer.parseInt(((String) properties.get("Compatible with Tracker")).substring(2), 16));
// Flags
buffer.putShort((short) Integer.parseInt(((String) properties.get("Flags")).substring(2), 16));
// Special
short special = (short) Integer.parseInt(((String) properties.get("Special")).substring(2), 16);
buffer.putShort(special);
// Global Volume
buffer.put((byte) ((int) properties.get("Global Volume")));
// Mix Volume
buffer.put((byte) ((int) properties.get("Mix Volume")));
// Initial Speed
buffer.put((byte) ((int) properties.get("Initial Speed")));
// Initial Tempo
buffer.put((byte) ((int) properties.get("Initial Tempo")));
// Pan Separation
buffer.put((byte) ((int) properties.get("Pan Separation")));
// Pitch Wheel Depth
buffer.put((byte) ((int) properties.get("Pitch Wheel Depth")));
// Message Length
buffer.putShort((short) ((int) properties.get("Message Length")));
// Message Offset
buffer.putInt((int) ((long) properties.get("Message Offset")));
// Reserved
buffer.putInt((int) Long.parseLong(((String) properties.get("Reserved")).substring(2), 16));
// Initial Channel Pan
String chnpanStr = (String) properties.get("Initial Channel Pan");
byte[] chnpan = Arrays.stream(chnpanStr.substring(1, chnpanStr.length()-1).split(", ")).map(Byte::parseByte).toArray(byte[]::new);
buffer.put(chnpan);
// Initial Channel Volume
String chnvolStr = (String) properties.get("Initial Channel Volume");
byte[] chnvol = Arrays.stream(chnvolStr.substring(1, chnvolStr.length()-1).split(", ")).map(Byte::parseByte).toArray(byte[]::new);
buffer.put(chnvol);
// Orders
String ordersStr = (String) properties.get("Orders");
byte[] orders = Arrays.stream(ordersStr.substring(1, ordersStr.length()-1).split(", ")).map(Byte::parseByte).toArray(byte[]::new);
buffer.put(orders);
// Instrument Offsets
String insOffsetsStr = (String) properties.get("Instrument Offsets");
long[] insOffsets = Arrays.stream(insOffsetsStr.substring(1, insOffsetsStr.length()-1).split(", ")).mapToLong(Long::parseLong).toArray();
for (long offset : insOffsets) {
buffer.putInt((int) offset);
}
// Sample Header Offsets
String smpOffsetsStr = (String) properties.get("Sample Header Offsets");
long[] smpOffsets = Arrays.stream(smpOffsetsStr.substring(1, smpOffsetsStr.length()-1).split(", ")).mapToLong(Long::parseLong).toArray();
for (long offset : smpOffsets) {
buffer.putInt((int) offset);
}
// Pattern Offsets
String patOffsetsStr = (String) properties.get("Pattern Offsets");
long[] patOffsets = Arrays.stream(patOffsetsStr.substring(1, patOffsetsStr.length()-1).split(", ")).mapToLong(Long::parseLong).toArray();
for (long offset : patOffsets) {
buffer.putInt((int) offset);
}
// Edit History if Special bit 1 set
if ((special & 2) != 0) {
int editNum = (int) properties.get("Edit History Number");
buffer.putShort((short) editNum);
String editHist = (String) properties.get("Edit Histories");
String[] lines = editHist.split("\n");
for (String line : lines) {
if (line.isEmpty()) continue;
short fatDate = Short.parseShort(line.split(", ")[0].split(": ")[1]);
short fatTime = Short.parseShort(line.split(", ")[1].split(": ")[1]);
int runtime = Integer.parseInt(line.split(", ")[2].split(": ")[1]);
buffer.putShort(fatDate);
buffer.putShort(fatTime);
buffer.putInt(runtime);
}
}
buffer.flip();
channel.write(buffer);
// Note: Full write would append data sections.
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
ITFile it = new ITFile("example.it");
it.printProperties();
it.write("output.it");
}
}
6. JavaScript Class for .IT File Handling
Here's a JavaScript class using ArrayBuffer and DataView. It requires Node.js for file I/O (using fs).
const fs = require('fs');
class ITFile {
constructor(filename) {
this.filename = filename;
this.properties = {};
this.read();
}
read() {
const data = fs.readFileSync(this.filename);
const view = new DataView(data.buffer);
const textDecoder = new TextDecoder('utf-8');
// Magic
const magic = textDecoder.decode(data.subarray(0, 4));
if (magic !== 'IMPM') {
throw new Error('Not a valid .IT file');
}
this.properties.Magic = magic;
// Song Name
this.properties['Song Name'] = textDecoder.decode(data.subarray(4, 30)).trim();
// Pattern Highlight
this.properties['Pattern Highlight'] = `Rows per beat: ${view.getUint8(30)}, Rows per measure: ${view.getUint8(31)}`;
// Order Number
const ordnum = view.getUint16(32, true);
this.properties['Order Number'] = ordnum;
// Instrument Number
const insnum = view.getUint16(34, true);
this.properties['Instrument Number'] = insnum;
// Sample Number
const smpnum = view.getUint16(36, true);
this.properties['Sample Number'] = smpnum;
// Pattern Number
const patnum = view.getUint16(38, true);
this.properties['Pattern Number'] = patnum;
// Created with Tracker
this.properties['Created with Tracker'] = `0x${view.getUint16(40, true).toString(16)}`;
// Compatible with Tracker
this.properties['Compatible with Tracker'] = `0x${view.getUint16(42, true).toString(16)}`;
// Flags
this.properties['Flags'] = `0x${view.getUint16(44, true).toString(16)}`;
// Special
const special = view.getUint16(46, true);
this.properties['Special'] = `0x${special.toString(16)}`;
// Global Volume
this.properties['Global Volume'] = view.getUint8(48);
// Mix Volume
this.properties['Mix Volume'] = view.getUint8(49);
// Initial Speed
this.properties['Initial Speed'] = view.getUint8(50);
// Initial Tempo
this.properties['Initial Tempo'] = view.getUint8(51);
// Pan Separation
this.properties['Pan Separation'] = view.getUint8(52);
// Pitch Wheel Depth
this.properties['Pitch Wheel Depth'] = view.getUint8(53);
// Message Length
this.properties['Message Length'] = view.getUint16(54, true);
// Message Offset
this.properties['Message Offset'] = view.getUint32(56, true);
// Reserved
this.properties['Reserved'] = `0x${view.getUint32(60, true).toString(16)}`;
// Initial Channel Pan
this.properties['Initial Channel Pan'] = Array.from(data.subarray(64, 128));
// Initial Channel Volume
this.properties['Initial Channel Volume'] = Array.from(data.subarray(128, 192));
// Orders
this.properties['Orders'] = Array.from(data.subarray(192, 192 + ordnum));
// Instrument Offsets
const insStart = 192 + ordnum;
this.properties['Instrument Offsets'] = [];
for (let i = 0; i < insnum; i++) {
this.properties['Instrument Offsets'].push(view.getUint32(insStart + i * 4, true));
}
// Sample Header Offsets
const smpStart = insStart + insnum * 4;
this.properties['Sample Header Offsets'] = [];
for (let i = 0; i < smpnum; i++) {
this.properties['Sample Header Offsets'].push(view.getUint32(smpStart + i * 4, true));
}
// Pattern Offsets
const patStart = smpStart + smpnum * 4;
this.properties['Pattern Offsets'] = [];
for (let i = 0; i < patnum; i++) {
this.properties['Pattern Offsets'].push(view.getUint32(patStart + i * 4, true));
}
// Edit History if Special bit 1 set
if (special & 2) {
const editStart = patStart + patnum * 4;
const editNum = view.getUint16(editStart, true);
this.properties['Edit History Number'] = editNum;
this.properties['Edit Histories'] = [];
for (let i = 0; i < editNum; i++) {
const off = editStart + 2 + i * 8;
const fatDate = view.getUint16(off, true);
const fatTime = view.getUint16(off + 2, true);
const runtime = view.getUint32(off + 4, true);
this.properties['Edit Histories'].push({ fatDate, fatTime, runtime });
}
}
}
printProperties() {
for (const [key, value] of Object.entries(this.properties)) {
console.log(`${key}: ${JSON.stringify(value)}`);
}
}
write(outputFilename) {
const buffer = new ArrayBuffer(1024 * 1024); // Large enough
const view = new DataView(buffer);
let pos = 0;
// Magic
const encoder = new TextEncoder();
const magicBytes = encoder.encode('IMPM');
for (let i = 0; i < 4; i++) {
view.setUint8(pos++, magicBytes[i]);
}
// Song Name
const songName = this.properties['Song Name'];
const songNameBytes = encoder.encode(songName.padEnd(26, '\x00'));
for (let i = 0; i < 26; i++) {
view.setUint8(pos++, songNameBytes[i]);
}
// Pattern Highlight
const ph = this.properties['Pattern Highlight'];
const rowsBeat = parseInt(ph.match(/Rows per beat: (\d+)/)[1]);
const rowsMeasure = parseInt(ph.match(/Rows per measure: (\d+)/)[1]);
view.setUint8(pos++, rowsBeat);
view.setUint8(pos++, rowsMeasure);
// Order Number
const ordnum = this.properties['Order Number'];
view.setUint16(pos, ordnum, true);
pos += 2;
// Instrument Number
const insnum = this.properties['Instrument Number'];
view.setUint16(pos, insnum, true);
pos += 2;
// Sample Number
const smpnum = this.properties['Sample Number'];
view.setUint16(pos, smpnum, true);
pos += 2;
// Pattern Number
const patnum = this.properties['Pattern Number'];
view.setUint16(pos, patnum, true);
pos += 2;
// Created with Tracker
view.setUint16(pos, parseInt(this.properties['Created with Tracker'], 16), true);
pos += 2;
// Compatible with Tracker
view.setUint16(pos, parseInt(this.properties['Compatible with Tracker'], 16), true);
pos += 2;
// Flags
view.setUint16(pos, parseInt(this.properties['Flags'], 16), true);
pos += 2;
// Special
const special = parseInt(this.properties['Special'], 16);
view.setUint16(pos, special, true);
pos += 2;
// Global Volume
view.setUint8(pos++, this.properties['Global Volume']);
// Mix Volume
view.setUint8(pos++, this.properties['Mix Volume']);
// Initial Speed
view.setUint8(pos++, this.properties['Initial Speed']);
// Initial Tempo
view.setUint8(pos++, this.properties['Initial Tempo']);
// Pan Separation
view.setUint8(pos++, this.properties['Pan Separation']);
// Pitch Wheel Depth
view.setUint8(pos++, this.properties['Pitch Wheel Depth']);
// Message Length
view.setUint16(pos, this.properties['Message Length'], true);
pos += 2;
// Message Offset
view.setUint32(pos, this.properties['Message Offset'], true);
pos += 4;
// Reserved
view.setUint32(pos, parseInt(this.properties['Reserved'], 16), true);
pos += 4;
// Initial Channel Pan
this.properties['Initial Channel Pan'].forEach(val => view.setUint8(pos++, val));
// Initial Channel Volume
this.properties['Initial Channel Volume'].forEach(val => view.setUint8(pos++, val));
// Orders
this.properties['Orders'].forEach(val => view.setUint8(pos++, val));
// Instrument Offsets
this.properties['Instrument Offsets'].forEach(offset => {
view.setUint32(pos, offset, true);
pos += 4;
});
// Sample Header Offsets
this.properties['Sample Header Offsets'].forEach(offset => {
view.setUint32(pos, offset, true);
pos += 4;
});
// Pattern Offsets
this.properties['Pattern Offsets'].forEach(offset => {
view.setUint32(pos, offset, true);
pos += 4;
});
// Edit History if Special bit 1 set
if (special & 2) {
view.setUint16(pos, this.properties['Edit History Number'], true);
pos += 2;
this.properties['Edit Histories'].forEach(hist => {
view.setUint16(pos, hist.fatDate, true);
pos += 2;
view.setUint16(pos, hist.fatTime, true);
pos += 2;
view.setUint32(pos, hist.runtime, true);
pos += 4;
});
}
fs.writeFileSync(outputFilename, new Uint8Array(buffer, 0, pos));
// Note: Full write would append data sections.
}
}
// Example usage:
// const it = new ITFile('example.it');
// it.printProperties();
// it.write('output.it');
7. C Class for .IT File Handling
Here's a C++ class using fstream for I/O. The write method reconstructs the header.
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <iomanip>
#include <map>
using namespace std;
class ITFile {
private:
string filename;
map<string, string> properties; // Using string for simplicity in printing
public:
ITFile(const string& fn) : filename(fn) {
read();
}
void read() {
ifstream file(filename, ios::binary | ios::ate);
if (!file) {
throw runtime_error("Cannot open file");
}
streamsize size = file.tellg();
file.seekg(0, ios::beg);
vector<char> buffer(size);
if (!file.read(buffer.data(), size)) {
throw runtime_error("Cannot read file");
}
// Magic
string magic(buffer.begin(), buffer.begin() + 4);
if (magic != "IMPM") {
throw runtime_error("Not a valid .IT file");
}
properties["Magic"] = magic;
// Song Name
string songName(buffer.begin() + 4, buffer.begin() + 30);
size_t nullPos = songName.find('\0');
if (nullPos != string::npos) songName = songName.substr(0, nullPos);
properties["Song Name"] = songName;
// Pattern Highlight
unsigned char rowsBeat = static_cast<unsigned char>(buffer[30]);
unsigned char rowsMeasure = static_cast<unsigned char>(buffer[31]);
properties["Pattern Highlight"] = "Rows per beat: " + to_string(rowsBeat) + ", Rows per measure: " + to_string(rowsMeasure);
// Order Number
uint16_t ordnum = *reinterpret_cast<uint16_t*>(&buffer[32]);
properties["Order Number"] = to_string(ordnum);
// Instrument Number
uint16_t insnum = *reinterpret_cast<uint16_t*>(&buffer[34]);
properties["Instrument Number"] = to_string(insnum);
// Sample Number
uint16_t smpnum = *reinterpret_cast<uint16_t*>(&buffer[36]);
properties["Sample Number"] = to_string(smpnum);
// Pattern Number
uint16_t patnum = *reinterpret_cast<uint16_t*>(&buffer[38]);
properties["Pattern Number"] = to_string(patnum);
// Created with Tracker
uint16_t cwtv = *reinterpret_cast<uint16_t*>(&buffer[40]);
stringstream ss;
ss << "0x" << hex << cwtv;
properties["Created with Tracker"] = ss.str();
// Compatible with Tracker
uint16_t cmwt = *reinterpret_cast<uint16_t*>(&buffer[42]);
ss.str("");
ss << "0x" << hex << cmwt;
properties["Compatible with Tracker"] = ss.str();
// Flags
uint16_t flags = *reinterpret_cast<uint16_t*>(&buffer[44]);
ss.str("");
ss << "0x" << hex << flags;
properties["Flags"] = ss.str();
// Special
uint16_t special = *reinterpret_cast<uint16_t*>(&buffer[46]);
ss.str("");
ss << "0x" << hex << special;
properties["Special"] = ss.str();
// Global Volume
properties["Global Volume"] = to_string(static_cast<unsigned char>(buffer[48]));
// Mix Volume
properties["Mix Volume"] = to_string(static_cast<unsigned char>(buffer[49]));
// Initial Speed
properties["Initial Speed"] = to_string(static_cast<unsigned char>(buffer[50]));
// Initial Tempo
properties["Initial Tempo"] = to_string(static_cast<unsigned char>(buffer[51]));
// Pan Separation
properties["Pan Separation"] = to_string(static_cast<unsigned char>(buffer[52]));
// Pitch Wheel Depth
properties["Pitch Wheel Depth"] = to_string(static_cast<unsigned char>(buffer[53]));
// Message Length
uint16_t msgLen = *reinterpret_cast<uint16_t*>(&buffer[54]);
properties["Message Length"] = to_string(msgLen);
// Message Offset
uint32_t msgOff = *reinterpret_cast<uint32_t*>(&buffer[56]);
properties["Message Offset"] = to_string(msgOff);
// Reserved
uint32_t reserved = *reinterpret_cast<uint32_t*>(&buffer[60]);
ss.str("");
ss << "0x" << hex << reserved;
properties["Reserved"] = ss.str();
// Initial Channel Pan
ss.str("");
ss << "[";
for (int i = 64; i < 128; i++) {
ss << static_cast<int>(static_cast<unsigned char>(buffer[i])) << (i < 127 ? ", " : "");
}
ss << "]";
properties["Initial Channel Pan"] = ss.str();
// Initial Channel Volume
ss.str("");
ss << "[";
for (int i = 128; i < 192; i++) {
ss << static_cast<int>(static_cast<unsigned char>(buffer[i])) << (i < 191 ? ", " : "");
}
ss << "]";
properties["Initial Channel Volume"] = ss.str();
// Orders
ss.str("");
ss << "[";
for (int i = 0; i < ordnum; i++) {
ss << static_cast<int>(static_cast<unsigned char>(buffer[192 + i])) << (i < ordnum - 1 ? ", " : "");
}
ss << "]";
properties["Orders"] = ss.str();
// Instrument Offsets
int insStart = 192 + ordnum;
ss.str("");
ss << "[";
for (int i = 0; i < insnum; i++) {
uint32_t offset = *reinterpret_cast<uint32_t*>(&buffer[insStart + i * 4]);
ss << offset << (i < insnum - 1 ? ", " : "");
}
ss << "]";
properties["Instrument Offsets"] = ss.str();
// Sample Header Offsets
int smpStart = insStart + insnum * 4;
ss.str("");
ss << "[";
for (int i = 0; i < smpnum; i++) {
uint32_t offset = *reinterpret_cast<uint32_t*>(&buffer[smpStart + i * 4]);
ss << offset << (i < smpnum - 1 ? ", " : "");
}
ss << "]";
properties["Sample Header Offsets"] = ss.str();
// Pattern Offsets
int patStart = smpStart + smpnum * 4;
ss.str("");
ss << "[";
for (int i = 0; i < patnum; i++) {
uint32_t offset = *reinterpret_cast<uint32_t*>(&buffer[patStart + i * 4]);
ss << offset << (i < patnum - 1 ? ", " : "");
}
ss << "]";
properties["Pattern Offsets"] = ss.str();
// Edit History if Special bit 1 set
if (special & 2) {
int editStart = patStart + patnum * 4;
uint16_t editNum = *reinterpret_cast<uint16_t*>(&buffer[editStart]);
properties["Edit History Number"] = to_string(editNum);
ss.str("");
for (int i = 0; i < editNum; i++) {
int off = editStart + 2 + i * 8;
uint16_t fatDate = *reinterpret_cast<uint16_t*>(&buffer[off]);
uint16_t fatTime = *reinterpret_cast<uint16_t*>(&buffer[off + 2]);
uint32_t runtime = *reinterpret_cast<uint32_t*>(&buffer[off + 4]);
ss << "Fat Date: " << fatDate << ", Fat Time: " << fatTime << ", Runtime: " << runtime << "\n";
}
properties["Edit Histories"] = ss.str();
}
}
void printProperties() {
for (const auto& prop : properties) {
cout << prop.first << ": " << prop.second << endl;
}
}
void write(const string& outputFilename) {
ofstream file(outputFilename, ios::binary);
if (!file) {
throw runtime_error("Cannot open output file");
}
// Magic
file.write("IMPM", 4);
// Song Name
string songName = properties["Song Name"];
songName.resize(26, '\0');
file.write(songName.c_str(), 26);
// Pattern Highlight
string ph = properties["Pattern Highlight"];
unsigned char rowsBeat = stoi(ph.substr(ph.find(": ") + 2, ph.find(",") - ph.find(": ") - 2));
unsigned char rowsMeasure = stoi(ph.substr(ph.rfind(": ") + 2));
file.put(rowsBeat);
file.put(rowsMeasure);
// Order Number
uint16_t ordnum = stoi(properties["Order Number"]);
file.write(reinterpret_cast<const char*>(&ordnum), sizeof(uint16_t));
// Instrument Number
uint16_t insnum = stoi(properties["Instrument Number"]);
file.write(reinterpret_cast<const char*>(&insnum), sizeof(uint16_t));
// Sample Number
uint16_t smpnum = stoi(properties["Sample Number"]);
file.write(reinterpret_cast<const char*>(&smpnum), sizeof(uint16_t));
// Pattern Number
uint16_t patnum = stoi(properties["Pattern Number"]);
file.write(reinterpret_cast<const char*>(&patnum), sizeof(uint16_t));
// Created with Tracker
uint16_t cwtv = static_cast<uint16_t>(strtoul(properties["Created with Tracker"].c_str(), nullptr, 0));
file.write(reinterpret_cast<const char*>(&cwtv), sizeof(uint16_t));
// Compatible with Tracker
uint16_t cmwt = static_cast<uint16_t>(strtoul(properties["Compatible with Tracker"].c_str(), nullptr, 0));
file.write(reinterpret_cast<const char*>(&cmwt), sizeof(uint16_t));
// Flags
uint16_t flags = static_cast<uint16_t>(strtoul(properties["Flags"].c_str(), nullptr, 0));
file.write(reinterpret_cast<const char*>(&flags), sizeof(uint16_t));
// Special
uint16_t special = static_cast<uint16_t>(strtoul(properties["Special"].c_str(), nullptr, 0));
file.write(reinterpret_cast<const char*>(&special), sizeof(uint16_t));
// Global Volume
unsigned char gv = static_cast<unsigned char>(stoi(properties["Global Volume"]));
file.put(gv);
// Mix Volume
unsigned char mv = static_cast<unsigned char>(stoi(properties["Mix Volume"]));
file.put(mv);
// Initial Speed
unsigned char is = static_cast<unsigned char>(stoi(properties["Initial Speed"]));
file.put(is);
// Initial Tempo
unsigned char it = static_cast<unsigned char>(stoi(properties["Initial Tempo"]));
file.put(it);
// Pan Separation
unsigned char sep = static_cast<unsigned char>(stoi(properties["Pan Separation"]));
file.put(sep);
// Pitch Wheel Depth
unsigned char pwd = static_cast<unsigned char>(stoi(properties["Pitch Wheel Depth"]));
file.put(pwd);
// Message Length
uint16_t msgLen = static_cast<uint16_t>(stoi(properties["Message Length"]));
file.write(reinterpret_cast<const char*>(&msgLen), sizeof(uint16_t));
// Message Offset
uint32_t msgOff = static_cast<uint32_t>(stol(properties["Message Offset"]));
file.write(reinterpret_cast<const char*>(&msgOff), sizeof(uint32_t));
// Reserved
uint32_t reserved = static_cast<uint32_t>(strtoul(properties["Reserved"].c_str(), nullptr, 0));
file.write(reinterpret_cast<const char*>(&reserved), sizeof(uint32_t));
// Initial Channel Pan
string chnpanStr = properties["Initial Channel Pan"];
chnpanStr = chnpanStr.substr(1, chnpanStr.length() - 2);
stringstream chnpanSs(chnpanStr);
string token;
for (int i = 0; i < 64; i++) {
getline(chnpanSs, token, ',');
unsigned char val = static_cast<unsigned char>(stoi(token));
file.put(val);
}
// Initial Channel Volume
string chnvolStr = properties["Initial Channel Volume"];
chnvolStr = chnvolStr.substr(1, chnvolStr.length() - 2);
stringstream chnvolSs(chnvolStr);
for (int i = 0; i < 64; i++) {
getline(chnvolSs, token, ',');
unsigned char val = static_cast<unsigned char>(stoi(token));
file.put(val);
}
// Orders
string ordersStr = properties["Orders"];
ordersStr = ordersStr.substr(1, ordersStr.length() - 2);
stringstream ordersSs(ordersStr);
for (int i = 0; i < ordnum; i++) {
getline(ordersSs, token, ',');
unsigned char val = static_cast<unsigned char>(stoi(token));
file.put(val);
}
// Instrument Offsets
string insOffsetsStr = properties["Instrument Offsets"];
insOffsetsStr = insOffsetsStr.substr(1, insOffsetsStr.length() - 2);
stringstream insSs(insOffsetsStr);
for (int i = 0; i < insnum; i++) {
getline(insSs, token, ',');
uint32_t offset = static_cast<uint32_t>(stol(token));
file.write(reinterpret_cast<const char*>(&offset), sizeof(uint32_t));
}
// Sample Header Offsets
string smpOffsetsStr = properties["Sample Header Offsets"];
smpOffsetsStr = smpOffsetsStr.substr(1, smpOffsetsStr.length() - 2);
stringstream smpSs(smpOffsetsStr);
for (int i = 0; i < smpnum; i++) {
getline(smpSs, token, ',');
uint32_t offset = static_cast<uint32_t>(stol(token));
file.write(reinterpret_cast<const char*>(&offset), sizeof(uint32_t));
}
// Pattern Offsets
string patOffsetsStr = properties["Pattern Offsets"];
patOffsetsStr = patOffsetsStr.substr(1, patOffsetsStr.length() - 2);
stringstream patSs(patOffsetsStr);
for (int i = 0; i < patnum; i++) {
getline(patSs, token, ',');
uint32_t offset = static_cast<uint32_t>(stol(token));
file.write(reinterpret_cast<const char*>(&offset), sizeof(uint32_t));
}
// Edit History if Special bit 1 set
if (special & 2) {
uint16_t editNum = static_cast<uint16_t>(stoi(properties["Edit History Number"]));
file.write(reinterpret_cast<const char*>(&editNum), sizeof(uint16_t));
string editHist = properties["Edit Histories"];
stringstream editSs(editHist);
string line;
while (getline(editSs, line)) {
if (line.empty()) continue;
uint16_t fatDate = static_cast<uint16_t>(stoi(line.substr(line.find(": ") + 2, line.find(",") - line.find(": ") - 2)));
size_t pos1 = line.find(", Fat Time: ") + 12;
uint16_t fatTime = static_cast<uint16_t>(stoi(line.substr(pos1, line.rfind(",") - pos1)));
size_t pos2 = line.rfind(": ") + 2;
uint32_t runtime = static_cast<uint32_t>(stol(line.substr(pos2)));
file.write(reinterpret_cast<const char*>(&fatDate), sizeof(uint16_t));
file.write(reinterpret_cast<const char*>(&fatTime), sizeof(uint16_t));
file.write(reinterpret_cast<const char*>(&runtime), sizeof(uint32_t));
}
}
// Note: Full write would append data sections.
}
};
// Example usage:
// int main() {
// try {
// ITFile it("example.it");
// it.printProperties();
// it.write("output.it");
// } catch (const exception& e) {
// cerr << e.what() << endl;
// }
// return 0;
// }