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.)

Here are two direct download links for sample .IT files from ModArchive, a repository of tracker modules:

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.

.IT File Property Dumper
Drag and drop a .IT file here

  

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;
// }