Task 631: .S3M File Format
Task 631: .S3M File Format
1. List of all the properties of this file format intrinsic to its file system
Based on the .S3M file format specifications, the intrinsic properties (structural fields and metadata) are as follows. These include the main header, channel settings, order list, instrument metadata (with type-specific fields), and pattern metadata. Sample data and full pattern packed data are not listed as properties here, as they are variable content rather than fixed properties, but pointers and sizes to them are included.
- Song title: 28-byte null-terminated string.
- Signature 1: 1-byte value (always 0x1A).
- Type: 1-byte value (always 0x10 for S3M modules).
- Reserved (header): 2-byte value (always 0x0000).
- Order count: 2-byte little-endian unsigned integer (number of orders, even value).
- Instrument count: 2-byte little-endian unsigned integer (number of instruments).
- Pattern pointer count: 2-byte little-endian unsigned integer (number of patterns).
- Flags: 2-byte little-endian unsigned integer (bit flags for features like optimizations, limits, etc.).
- Tracker version: 2-byte little-endian unsigned integer (e.g., 0x1320 for Scream Tracker 3.20).
- Sample type: 2-byte little-endian unsigned integer (1 for signed samples, 2 for unsigned).
- Signature 2: 4-byte string ("SCRM").
- Global volume: 1-byte unsigned integer (0-64).
- Initial speed: 1-byte unsigned integer (frames per row).
- Initial tempo: 1-byte unsigned integer (frames per second).
- Master volume: 1-byte unsigned integer (bit 7 for stereo/mono, bits 0-6 for volume).
- Ultra click removal: 1-byte unsigned integer (GUS-specific).
- Default pan: 1-byte unsigned integer (252 to use header pan values).
- Reserved (extended): 8-byte array (unused).
- Pointer to special data: 2-byte little-endian unsigned integer (parapointer, if flags bit 7 set).
- Channel settings: 32-byte array (channel types and enable/disable flags).
- Order list: Variable-length array (order count bytes) of pattern indices (0xFE skip, 0xFF end).
- Instrument pointers: Variable-length array (instrument count * 2 bytes) of little-endian parapointers to instruments.
- Pattern pointers: Variable-length array (pattern pointer count * 2 bytes) of little-endian parapointers to patterns.
For each instrument (repeated for instrument count):
- Instrument type: 1-byte unsigned integer (0=empty, 1=PCM, 2-7=Adlib variants).
- Filename: 12-byte string (DOS 8.3 format, no null).
- If PCM (type 1):
- Data pointer high: 1-byte unsigned integer (upper bits of parapointer).
- Data pointer low: 2-byte little-endian unsigned integer (lower bits of parapointer).
- Length: 4-byte little-endian unsigned integer (sample length in bytes).
- Loop start: 4-byte little-endian unsigned integer (loop start offset).
- Loop end: 4-byte little-endian unsigned integer (loop end offset).
- Volume: 1-byte unsigned integer (0-63).
- Reserved (instrument): 1-byte (0).
- Pack: 1-byte unsigned integer (0=unpacked, 1=packed [deprecated]).
- Flags (instrument): 1-byte unsigned integer (bits: 1=loop, 2=stereo, 4=16-bit).
- C2 speed: 4-byte little-endian unsigned integer (sample rate for C-4).
- Internal: 12-byte array (unused, zeros).
- Instrument title: 28-byte null-terminated string.
- Instrument signature: 4-byte string ("SCRS").
- If Adlib (types 2-7):
- Reserved (Adlib): 3-byte array (zeros).
- OPL values: 12-byte array (OPL register values for synthesis).
- Volume: 1-byte unsigned integer (0-63).
- Disk: 1-byte unsigned integer (unknown).
- Reserved (Adlib extended): 2-byte little-endian (0).
- C2 speed: 4-byte little-endian unsigned integer (rate for C-4).
- Unused: 12-byte array (zeros).
- Instrument title: 28-byte null-terminated string.
- Instrument signature: 4-byte string ("SCRI").
For each pattern (repeated for pattern pointer count):
- Packed size: 2-byte little-endian unsigned integer (size of packed pattern data, including header; 0=empty).
- Packed data: Variable-length (packed commands for 64 rows x 32 channels).
2. Two direct download links for files of format .S3M
- https://api.modarchive.org/downloads.php?moduleid=35327#distance.s3m
- https://api.modarchive.org/downloads.php?moduleid=70022#demo.s3m
3. Ghost blog embedded HTML JavaScript for drag and drop .S3M file dump
Drag and drop .S3M file here
4. Python class for .S3M file handling
import struct
import os
class S3MFile:
def __init__(self, filename):
self.filename = filename
self.data = None
self.properties = {}
self.instruments = []
self.patterns = []
self.load()
def load(self):
with open(self.filename, 'rb') as f:
self.data = f.read()
self.decode()
def decode(self):
offset = 0
self.properties['song_title'] = struct.unpack_from('<28s', self.data, offset)[0].decode('ascii', errors='ignore').rstrip('\x00')
offset += 28
self.properties['sig1'] = struct.unpack_from('<B', self.data, offset)[0]
offset += 1
self.properties['type'] = struct.unpack_from('<B', self.data, offset)[0]
offset += 1
self.properties['reserved_header'] = struct.unpack_from('<H', self.data, offset)[0]
offset += 2
order_count = struct.unpack_from('<H', self.data, offset)[0]
self.properties['order_count'] = order_count
offset += 2
instrument_count = struct.unpack_from('<H', self.data, offset)[0]
self.properties['instrument_count'] = instrument_count
offset += 2
pattern_ptr_count = struct.unpack_from('<H', self.data, offset)[0]
self.properties['pattern_ptr_count'] = pattern_ptr_count
offset += 2
self.properties['flags'] = struct.unpack_from('<H', self.data, offset)[0]
offset += 2
self.properties['tracker_version'] = struct.unpack_from('<H', self.data, offset)[0]
offset += 2
self.properties['sample_type'] = struct.unpack_from('<H', self.data, offset)[0]
offset += 2
self.properties['sig2'] = struct.unpack_from('<4s', self.data, offset)[0].decode('ascii')
offset += 4
self.properties['global_volume'] = struct.unpack_from('<B', self.data, offset)[0]
offset += 1
self.properties['initial_speed'] = struct.unpack_from('<B', self.data, offset)[0]
offset += 1
self.properties['initial_tempo'] = struct.unpack_from('<B', self.data, offset)[0]
offset += 1
self.properties['master_volume'] = struct.unpack_from('<B', self.data, offset)[0]
offset += 1
self.properties['ultra_click_removal'] = struct.unpack_from('<B', self.data, offset)[0]
offset += 1
self.properties['default_pan'] = struct.unpack_from('<B', self.data, offset)[0]
offset += 1
offset += 8 # reserved_extended
self.properties['ptr_special'] = struct.unpack_from('<H', self.data, offset)[0]
offset += 2
channel_settings = list(struct.unpack_from('<32B', self.data, offset))
self.properties['channel_settings'] = channel_settings
offset += 32
order_list = list(struct.unpack_from(f'<{order_count}B', self.data, offset))
self.properties['order_list'] = order_list
offset += order_count
inst_ptrs = [struct.unpack_from('<H', self.data, offset + i*2)[0] * 16 for i in range(instrument_count)]
self.properties['instrument_pointers'] = inst_ptrs
offset += instrument_count * 2
pat_ptrs = [struct.unpack_from('<H', self.data, offset + i*2)[0] * 16 for i in range(pattern_ptr_count)]
self.properties['pattern_pointers'] = pat_ptrs
offset += pattern_ptr_count * 2
# Instruments
for ptr in inst_ptrs:
inst = {}
offset = ptr
inst['type'] = struct.unpack_from('<B', self.data, offset)[0]
offset += 1
inst['filename'] = struct.unpack_from('<12s', self.data, offset)[0].decode('ascii', errors='ignore')
offset += 12
if inst['type'] == 1: # PCM
inst['data_ptr_high'] = struct.unpack_from('<B', self.data, offset)[0]
offset += 1
inst['data_ptr_low'] = struct.unpack_from('<H', self.data, offset)[0]
offset += 2
inst['length'] = struct.unpack_from('<I', self.data, offset)[0]
offset += 4
inst['loop_start'] = struct.unpack_from('<I', self.data, offset)[0]
offset += 4
inst['loop_end'] = struct.unpack_from('<I', self.data, offset)[0]
offset += 4
inst['volume'] = struct.unpack_from('<B', self.data, offset)[0]
offset += 1
inst['reserved_inst'] = struct.unpack_from('<B', self.data, offset)[0]
offset += 1
inst['pack'] = struct.unpack_from('<B', self.data, offset)[0]
offset += 1
inst['flags_inst'] = struct.unpack_from('<B', self.data, offset)[0]
offset += 1
inst['c2_speed'] = struct.unpack_from('<I', self.data, offset)[0]
offset += 4
offset += 12 # internal
inst['title'] = struct.unpack_from('<28s', self.data, offset)[0].decode('ascii', errors='ignore').rstrip('\x00')
offset += 28
inst['sig'] = struct.unpack_from('<4s', self.data, offset)[0].decode('ascii')
offset += 4
elif 2 <= inst['type'] <= 7: # Adlib
offset += 3 # reserved_adlib
offset += 12 # opl_values
inst['volume'] = struct.unpack_from('<B', self.data, offset)[0]
offset += 1
inst['disk'] = struct.unpack_from('<B', self.data, offset)[0]
offset += 1
inst['reserved_adlib_ext'] = struct.unpack_from('<H', self.data, offset)[0]
offset += 2
inst['c2_speed'] = struct.unpack_from('<I', self.data, offset)[0]
offset += 4
offset += 12 # unused
inst['title'] = struct.unpack_from('<28s', self.data, offset)[0].decode('ascii', errors='ignore').rstrip('\x00')
offset += 28
inst['sig'] = struct.unpack_from('<4s', self.data, offset)[0].decode('ascii')
offset += 4
self.instruments.append(inst)
# Patterns (packed size only for print)
for ptr in pat_ptrs:
if ptr == 0: continue
offset = ptr
packed_size = struct.unpack_from('<H', self.data, offset)[0]
self.patterns.append({'packed_size': packed_size})
# Skip packed data for now
def print_properties(self):
for key, value in self.properties.items():
print(f"{key}: {value}")
for i, inst in enumerate(self.instruments):
print(f"\nInstrument {i+1}:")
for key, value in inst.items():
print(f" {key}: {value}")
for i, pat in enumerate(self.patterns):
print(f"\nPattern {i}:")
for key, value in pat.items():
print(f" {key}: {value}")
def write(self, output_filename):
# For simplicity, write back the original data (no modifications)
with open(output_filename, 'wb') as f:
f.write(self.data)
# Example usage:
# s3m = S3MFile('example.s3m')
# s3m.print_properties()
# s3m.write('output.s3m')
5. Java class for .S3M file handling
import java.io.*;
import java.nio.*;
import java.nio.channels.FileChannel;
import java.util.*;
public class S3MFile {
private String filename;
private ByteBuffer buffer;
private Map<String, Object> properties = new HashMap<>();
private List<Map<String, Object>> instruments = new ArrayList<>();
private List<Map<String, Object>> patterns = new ArrayList<>();
public S3MFile(String filename) {
this.filename = filename;
load();
}
private void load() {
try (RandomAccessFile raf = new RandomAccessFile(filename, "r")) {
FileChannel channel = raf.getChannel();
buffer = ByteBuffer.allocate((int) channel.size()).order(ByteOrder.LITTLE_ENDIAN);
channel.read(buffer);
buffer.flip();
decode();
} catch (IOException e) {
e.printStackTrace();
}
}
private void decode() {
int offset = 0;
byte[] titleBytes = new byte[28];
buffer.position(offset);
buffer.get(titleBytes);
properties.put("song_title", new String(titleBytes, java.nio.charset.StandardCharsets.US_ASCII).trim());
offset += 28;
properties.put("sig1", buffer.get(offset++) & 0xFF);
properties.put("type", buffer.get(offset++) & 0xFF);
properties.put("reserved_header", (int) buffer.getShort(offset) & 0xFFFF);
offset += 2;
int orderCount = buffer.getShort(offset) & 0xFFFF;
properties.put("order_count", orderCount);
offset += 2;
int instrumentCount = buffer.getShort(offset) & 0xFFFF;
properties.put("instrument_count", instrumentCount);
offset += 2;
int patternPtrCount = buffer.getShort(offset) & 0xFFFF;
properties.put("pattern_ptr_count", patternPtrCount);
offset += 2;
properties.put("flags", (int) buffer.getShort(offset) & 0xFFFF);
offset += 2;
properties.put("tracker_version", (int) buffer.getShort(offset) & 0xFFFF);
offset += 2;
properties.put("sample_type", (int) buffer.getShort(offset) & 0xFFFF);
offset += 2;
byte[] sig2Bytes = new byte[4];
buffer.position(offset);
buffer.get(sig2Bytes);
properties.put("sig2", new String(sig2Bytes));
offset += 4;
properties.put("global_volume", buffer.get(offset++) & 0xFF);
properties.put("initial_speed", buffer.get(offset++) & 0xFF);
properties.put("initial_tempo", buffer.get(offset++) & 0xFF);
properties.put("master_volume", buffer.get(offset++) & 0xFF);
properties.put("ultra_click_removal", buffer.get(offset++) & 0xFF);
properties.put("default_pan", buffer.get(offset++) & 0xFF);
offset += 8; // reserved_extended
properties.put("ptr_special", (int) buffer.getShort(offset) & 0xFFFF);
offset += 2;
int[] channelSettings = new int[32];
for (int i = 0; i < 32; i++) {
channelSettings[i] = buffer.get(offset++) & 0xFF;
}
properties.put("channel_settings", channelSettings);
int[] orderList = new int[orderCount];
for (int i = 0; i < orderCount; i++) {
orderList[i] = buffer.get(offset++) & 0xFF;
}
properties.put("order_list", orderList);
int[] instPtrs = new int[instrumentCount];
for (int i = 0; i < instrumentCount; i++) {
instPtrs[i] = (buffer.getShort(offset) & 0xFFFF) * 16;
offset += 2;
}
properties.put("instrument_pointers", instPtrs);
int[] patPtrs = new int[patternPtrCount];
for (int i = 0; i < patternPtrCount; i++) {
patPtrs[i] = (buffer.getShort(offset) & 0xFFFF) * 16;
offset += 2;
}
properties.put("pattern_pointers", patPtrs);
// Instruments
for (int ptr : instPtrs) {
Map<String, Object> inst = new HashMap<>();
offset = ptr;
inst.put("type", buffer.get(offset++) & 0xFF);
byte[] filenameBytes = new byte[12];
buffer.position(offset);
buffer.get(filenameBytes);
inst.put("filename", new String(filenameBytes).trim());
offset += 12;
int type = (int) inst.get("type");
if (type == 1) {
inst.put("data_ptr_high", buffer.get(offset++) & 0xFF);
inst.put("data_ptr_low", (int) buffer.getShort(offset) & 0xFFFF);
offset += 2;
inst.put("length", buffer.getInt(offset));
offset += 4;
inst.put("loop_start", buffer.getInt(offset));
offset += 4;
inst.put("loop_end", buffer.getInt(offset));
offset += 4;
inst.put("volume", buffer.get(offset++) & 0xFF);
inst.put("reserved_inst", buffer.get(offset++) & 0xFF);
inst.put("pack", buffer.get(offset++) & 0xFF);
inst.put("flags_inst", buffer.get(offset++) & 0xFF);
inst.put("c2_speed", buffer.getInt(offset));
offset += 4;
offset += 12; // internal
byte[] instTitleBytes = new byte[28];
buffer.position(offset);
buffer.get(instTitleBytes);
inst.put("title", new String(instTitleBytes).trim());
offset += 28;
byte[] instSigBytes = new byte[4];
buffer.get(instSigBytes);
inst.put("sig", new String(instSigBytes));
offset += 4;
} else if (type >= 2 && type <= 7) {
offset += 3; // reserved_adlib
offset += 12; // opl_values
inst.put("volume", buffer.get(offset++) & 0xFF);
inst.put("disk", buffer.get(offset++) & 0xFF);
inst.put("reserved_adlib_ext", (int) buffer.getShort(offset) & 0xFFFF);
offset += 2;
inst.put("c2_speed", buffer.getInt(offset));
offset += 4;
offset += 12; // unused
byte[] instTitleBytes = new byte[28];
buffer.position(offset);
buffer.get(instTitleBytes);
inst.put("title", new String(instTitleBytes).trim());
offset += 28;
byte[] instSigBytes = new byte[4];
buffer.get(instSigBytes);
inst.put("sig", new String(instSigBytes));
offset += 4;
}
instruments.add(inst);
}
// Patterns
for (int ptr : patPtrs) {
if (ptr == 0) continue;
Map<String, Object> pat = new HashMap<>();
offset = ptr;
int packedSize = buffer.getShort(offset) & 0xFFFF;
pat.put("packed_size", packedSize);
patterns.add(pat);
// Skip packed data
}
}
public void printProperties() {
properties.forEach((key, value) -> System.out.println(key + ": " + value));
for (int i = 0; i < instruments.size(); i++) {
System.out.println("\nInstrument " + (i + 1) + ":");
instruments.get(i).forEach((key, value) -> System.out.println(" " + key + ": " + value));
}
for (int i = 0; i < patterns.size(); i++) {
System.out.println("\nPattern " + i + ":");
patterns.get(i).forEach((key, value) -> System.out.println(" " + key + ": " + value));
}
}
public void write(String outputFilename) {
try (FileOutputStream fos = new FileOutputStream(outputFilename)) {
buffer.position(0);
fos.getChannel().write(buffer);
} catch (IOException e) {
e.printStackTrace();
}
}
// Example usage:
// public static void main(String[] args) {
// S3MFile s3m = new S3MFile("example.s3m");
// s3m.printProperties();
// s3m.write("output.s3m");
// }
}
6. JavaScript class for .S3M file handling (Node.js)
const fs = require('fs');
class S3MFile {
constructor(filename) {
this.filename = filename;
this.buffer = null;
this.properties = {};
this.instruments = [];
this.patterns = [];
this.load();
}
load() {
this.buffer = fs.readFileSync(this.filename);
this.decode();
}
decode() {
let offset = 0;
const view = new DataView(this.buffer.buffer);
this.properties.song_title = this.readString(view, offset, 28);
offset += 28;
this.properties.sig1 = view.getUint8(offset++);
this.properties.type = view.getUint8(offset++);
this.properties.reserved_header = view.getUint16(offset, true);
offset += 2;
const orderCount = view.getUint16(offset, true);
this.properties.order_count = orderCount;
offset += 2;
const instrumentCount = view.getUint16(offset, true);
this.properties.instrument_count = instrumentCount;
offset += 2;
const patternPtrCount = view.getUint16(offset, true);
this.properties.pattern_ptr_count = patternPtrCount;
offset += 2;
this.properties.flags = view.getUint16(offset, true);
offset += 2;
this.properties.tracker_version = view.getUint16(offset, true);
offset += 2;
this.properties.sample_type = view.getUint16(offset, true);
offset += 2;
this.properties.sig2 = this.readString(view, offset, 4);
offset += 4;
this.properties.global_volume = view.getUint8(offset++);
this.properties.initial_speed = view.getUint8(offset++);
this.properties.initial_tempo = view.getUint8(offset++);
this.properties.master_volume = view.getUint8(offset++);
this.properties.ultra_click_removal = view.getUint8(offset++);
this.properties.default_pan = view.getUint8(offset++);
offset += 8; // reserved_extended
this.properties.ptr_special = view.getUint16(offset, true);
offset += 2;
this.properties.channel_settings = [];
for (let i = 0; i < 32; i++) {
this.properties.channel_settings.push(view.getUint8(offset++));
}
this.properties.order_list = [];
for (let i = 0; i < orderCount; i++) {
this.properties.order_list.push(view.getUint8(offset++));
}
this.properties.instrument_pointers = [];
for (let i = 0; i < instrumentCount; i++) {
this.properties.instrument_pointers.push(view.getUint16(offset, true) * 16);
offset += 2;
}
this.properties.pattern_pointers = [];
for (let i = 0; i < patternPtrCount; i++) {
this.properties.pattern_pointers.push(view.getUint16(offset, true) * 16);
offset += 2;
}
// Instruments
this.properties.instrument_pointers.forEach(ptr => {
let inst = {};
offset = ptr;
inst.type = view.getUint8(offset++);
inst.filename = this.readString(view, offset, 12);
offset += 12;
if (inst.type === 1) {
inst.data_ptr_high = view.getUint8(offset++);
inst.data_ptr_low = view.getUint16(offset, true);
offset += 2;
inst.length = view.getUint32(offset, true);
offset += 4;
inst.loop_start = view.getUint32(offset, true);
offset += 4;
inst.loop_end = view.getUint32(offset, true);
offset += 4;
inst.volume = view.getUint8(offset++);
inst.reserved_inst = view.getUint8(offset++);
inst.pack = view.getUint8(offset++);
inst.flags_inst = view.getUint8(offset++);
inst.c2_speed = view.getUint32(offset, true);
offset += 4;
offset += 12; // internal
inst.title = this.readString(view, offset, 28);
offset += 28;
inst.sig = this.readString(view, offset, 4);
offset += 4;
} else if (inst.type >= 2 && inst.type <= 7) {
offset += 3; // reserved_adlib
offset += 12; // opl_values
inst.volume = view.getUint8(offset++);
inst.disk = view.getUint8(offset++);
inst.reserved_adlib_ext = view.getUint16(offset, true);
offset += 2;
inst.c2_speed = view.getUint32(offset, true);
offset += 4;
offset += 12; // unused
inst.title = this.readString(view, offset, 28);
offset += 28;
inst.sig = this.readString(view, offset, 4);
offset += 4;
}
this.instruments.push(inst);
});
// Patterns
this.properties.pattern_pointers.forEach(ptr => {
if (ptr === 0) return;
let pat = {};
offset = ptr;
pat.packed_size = view.getUint16(offset, true);
this.patterns.push(pat);
// Skip packed data
});
}
readString(view, offset, len) {
let str = '';
for (let i = 0; i < len; i++) {
const char = view.getUint8(offset + i);
if (char === 0) break;
str += String.fromCharCode(char);
}
return str;
}
printProperties() {
for (const [key, value] of Object.entries(this.properties)) {
console.log(`${key}: ${JSON.stringify(value)}`);
}
this.instruments.forEach((inst, i) => {
console.log(`\nInstrument ${i + 1}:`);
for (const [key, value] of Object.entries(inst)) {
console.log(` ${key}: ${value}`);
}
});
this.patterns.forEach((pat, i) => {
console.log(`\nPattern ${i}:`);
for (const [key, value] of Object.entries(pat)) {
console.log(` ${key}: ${value}`);
}
});
}
write(outputFilename) {
fs.writeFileSync(outputFilename, this.buffer);
}
}
// Example usage:
// const s3m = new S3MFile('example.s3m');
// s3m.printProperties();
// s3m.write('output.s3m');
7. C class for .S3M file handling (using C++ for class support)
#include <iostream>
#include <fstream>
#include <vector>
#include <map>
#include <string>
#include <cstdint>
#include <cstring>
class S3MFile {
private:
std::string filename;
std::vector<uint8_t> data;
std::map<std::string, std::string> properties; // Using string for simplicity in printing
std::vector<std::map<std::string, std::string>> instruments;
std::vector<std::map<std::string, std::string>> patterns;
uint16_t readUint16LE(size_t &offset) {
uint16_t val = (data[offset + 1] << 8) | data[offset];
offset += 2;
return val;
}
uint32_t readUint32LE(size_t &offset) {
uint32_t val = (data[offset + 3] << 24) | (data[offset + 2] << 16) | (data[offset + 1] << 8) | data[offset];
offset += 4;
return val;
}
std::string readString(size_t &offset, size_t len) {
std::string str(reinterpret_cast<char*>(&data[offset]), len);
str.erase(std::find(str.begin(), str.end(), '\0'), str.end());
offset += len;
return str;
}
public:
S3MFile(const std::string& fn) : filename(fn) {
load();
}
void load() {
std::ifstream file(filename, std::ios::binary | std::ios::ate);
if (!file) {
std::cerr << "Failed to open file." << std::endl;
return;
}
size_t size = file.tellg();
data.resize(size);
file.seekg(0);
file.read(reinterpret_cast<char*>(data.data()), size);
decode();
}
void decode() {
size_t offset = 0;
properties["song_title"] = readString(offset, 28);
properties["sig1"] = std::to_string(data[offset++]);
properties["type"] = std::to_string(data[offset++]);
properties["reserved_header"] = std::to_string(readUint16LE(offset));
uint16_t orderCount = readUint16LE(offset);
properties["order_count"] = std::to_string(orderCount);
uint16_t instrumentCount = readUint16LE(offset);
properties["instrument_count"] = std::to_string(instrumentCount);
uint16_t patternPtrCount = readUint16LE(offset);
properties["pattern_ptr_count"] = std::to_string(patternPtrCount);
properties["flags"] = std::to_string(readUint16LE(offset));
properties["tracker_version"] = std::to_string(readUint16LE(offset));
properties["sample_type"] = std::to_string(readUint16LE(offset));
properties["sig2"] = readString(offset, 4);
properties["global_volume"] = std::to_string(data[offset++]);
properties["initial_speed"] = std::to_string(data[offset++]);
properties["initial_tempo"] = std::to_string(data[offset++]);
properties["master_volume"] = std::to_string(data[offset++]);
properties["ultra_click_removal"] = std::to_string(data[offset++]);
properties["default_pan"] = std::to_string(data[offset++]);
offset += 8; // reserved_extended
properties["ptr_special"] = std::to_string(readUint16LE(offset));
std::string channelSettings = "[";
for (int i = 0; i < 32; i++) {
channelSettings += std::to_string(data[offset++]) + (i < 31 ? ", " : "");
}
channelSettings += "]";
properties["channel_settings"] = channelSettings;
std::string orderList = "[";
for (int i = 0; i < orderCount; i++) {
orderList += std::to_string(data[offset++]) + (i < orderCount - 1 ? ", " : "");
}
orderList += "]";
properties["order_list"] = orderList;
std::string instPtrsStr = "[";
std::vector<uint32_t> instPtrs;
for (int i = 0; i < instrumentCount; i++) {
uint32_t ptr = readUint16LE(offset) * 16;
instPtrs.push_back(ptr);
instPtrsStr += std::to_string(ptr) + (i < instrumentCount - 1 ? ", " : "");
}
instPtrsStr += "]";
properties["instrument_pointers"] = instPtrsStr;
std::string patPtrsStr = "[";
std::vector<uint32_t> patPtrs;
for (int i = 0; i < patternPtrCount; i++) {
uint32_t ptr = readUint16LE(offset) * 16;
patPtrs.push_back(ptr);
patPtrsStr += std::to_string(ptr) + (i < patternPtrCount - 1 ? ", " : "");
}
patPtrsStr += "]";
properties["pattern_pointers"] = patPtrsStr;
// Instruments
for (auto ptr : instPtrs) {
std::map<std::string, std::string> inst;
offset = ptr;
uint8_t type = data[offset++];
inst["type"] = std::to_string(type);
inst["filename"] = readString(offset, 12);
if (type == 1) {
inst["data_ptr_high"] = std::to_string(data[offset++]);
inst["data_ptr_low"] = std::to_string(readUint16LE(offset));
inst["length"] = std::to_string(readUint32LE(offset));
inst["loop_start"] = std::to_string(readUint32LE(offset));
inst["loop_end"] = std::to_string(readUint32LE(offset));
inst["volume"] = std::to_string(data[offset++]);
inst["reserved_inst"] = std::to_string(data[offset++]);
inst["pack"] = std::to_string(data[offset++]);
inst["flags_inst"] = std::to_string(data[offset++]);
inst["c2_speed"] = std::to_string(readUint32LE(offset));
offset += 12; // internal
inst["title"] = readString(offset, 28);
inst["sig"] = readString(offset, 4);
} else if (type >= 2 && type <= 7) {
offset += 3; // reserved_adlib
offset += 12; // opl_values
inst["volume"] = std::to_string(data[offset++]);
inst["disk"] = std::to_string(data[offset++]);
inst["reserved_adlib_ext"] = std::to_string(readUint16LE(offset));
inst["c2_speed"] = std::to_string(readUint32LE(offset));
offset += 12; // unused
inst["title"] = readString(offset, 28);
inst["sig"] = readString(offset, 4);
}
instruments.push_back(inst);
}
// Patterns
for (auto ptr : patPtrs) {
if (ptr == 0) continue;
std::map<std::string, std::string> pat;
offset = ptr;
pat["packed_size"] = std::to_string(readUint16LE(offset));
patterns.push_back(pat);
// Skip packed data
}
}
void printProperties() {
for (const auto& prop : properties) {
std::cout << prop.first << ": " << prop.second << std::endl;
}
for (size_t i = 0; i < instruments.size(); ++i) {
std::cout << "\nInstrument " << (i + 1) << ":" << std::endl;
for (const auto& instProp : instruments[i]) {
std::cout << " " << instProp.first << ": " << instProp.second << std::endl;
}
}
for (size_t i = 0; i < patterns.size(); ++i) {
std::cout << "\nPattern " << i << ":" << std::endl;
for (const auto& patProp : patterns[i]) {
std::cout << " " << patProp.first << ": " << patProp.second << std::endl;
}
}
}
void write(const std::string& outputFilename) {
std::ofstream outFile(outputFilename, std::ios::binary);
if (!outFile) {
std::cerr << "Failed to write file." << std::endl;
return;
}
outFile.write(reinterpret_cast<const char*>(data.data()), data.size());
}
};
// Example usage:
// int main() {
// S3MFile s3m("example.s3m");
// s3m.printProperties();
// s3m.write("output.s3m");
// return 0;
// }