Extended Module: Nim parsing library

XM (standing for eXtended Module) is a popular module music file format, that was introduced in 1994 in FastTracker2 by Triton demo group. Akin to MOD files, it bundles both digital samples (instruments) and instructions on which note to play at what time (patterns), which provides good audio quality with relatively small file size. Audio is reproducible without relying on the sound of particular hardware samplers or synths.

Application

["FastTracker 2", "Protracker", "MilkyTracker", "libmodplug", "Mikmod"]

File extension

xm

KS implementation details

License: Unlicense

References

This page hosts a formal specification of Extended Module using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Nim source code to parse Extended Module

fasttracker_xm_module.nim

import kaitai_struct_nim_runtime
import options

type
  FasttrackerXmModule* = ref object of KaitaiStruct
    `preheader`*: FasttrackerXmModule_Preheader
    `header`*: FasttrackerXmModule_Header
    `patterns`*: seq[FasttrackerXmModule_Pattern]
    `instruments`*: seq[FasttrackerXmModule_Instrument]
    `parent`*: KaitaiStruct
    `rawHeader`*: seq[byte]
  FasttrackerXmModule_Preheader* = ref object of KaitaiStruct
    `signature0`*: seq[byte]
    `moduleName`*: string
    `signature1`*: seq[byte]
    `trackerName`*: string
    `versionNumber`*: FasttrackerXmModule_Preheader_Version
    `headerSize`*: uint32
    `parent`*: FasttrackerXmModule
  FasttrackerXmModule_Preheader_Version* = ref object of KaitaiStruct
    `minor`*: uint8
    `major`*: uint8
    `parent`*: FasttrackerXmModule_Preheader
    `valueInst`*: int
  FasttrackerXmModule_Pattern* = ref object of KaitaiStruct
    `header`*: FasttrackerXmModule_Pattern_Header
    `packedData`*: seq[byte]
    `parent`*: FasttrackerXmModule
  FasttrackerXmModule_Pattern_Header* = ref object of KaitaiStruct
    `headerLength`*: uint32
    `main`*: FasttrackerXmModule_Pattern_Header_HeaderMain
    `parent`*: FasttrackerXmModule_Pattern
    `rawMain`*: seq[byte]
  FasttrackerXmModule_Pattern_Header_HeaderMain* = ref object of KaitaiStruct
    `packingType`*: uint8
    `numRowsRaw`*: uint16
    `lenPackedPattern`*: uint16
    `parent`*: FasttrackerXmModule_Pattern_Header
    `numRowsInst`*: int
  FasttrackerXmModule_Flags* = ref object of KaitaiStruct
    `reserved`*: uint64
    `freqTableType`*: bool
    `parent`*: FasttrackerXmModule_Header
  FasttrackerXmModule_Header* = ref object of KaitaiStruct
    `songLength`*: uint16
    `restartPosition`*: uint16
    `numChannels`*: uint16
    `numPatterns`*: uint16
    `numInstruments`*: uint16
    `flags`*: FasttrackerXmModule_Flags
    `defaultTempo`*: uint16
    `defaultBpm`*: uint16
    `patternOrderTable`*: seq[uint8]
    `parent`*: FasttrackerXmModule
  FasttrackerXmModule_Instrument* = ref object of KaitaiStruct
    `headerSize`*: uint32
    `header`*: FasttrackerXmModule_Instrument_Header
    `samplesHeaders`*: seq[FasttrackerXmModule_Instrument_SampleHeader]
    `samples`*: seq[FasttrackerXmModule_Instrument_SamplesData]
    `parent`*: FasttrackerXmModule
    `rawHeader`*: seq[byte]
  FasttrackerXmModule_Instrument_Header* = ref object of KaitaiStruct
    `name`*: string
    `type`*: uint8
    `numSamples`*: uint16
    `extraHeader`*: FasttrackerXmModule_Instrument_ExtraHeader
    `parent`*: FasttrackerXmModule_Instrument
  FasttrackerXmModule_Instrument_ExtraHeader* = ref object of KaitaiStruct
    `lenSampleHeader`*: uint32
    `idxSamplePerNote`*: seq[uint8]
    `volumePoints`*: seq[FasttrackerXmModule_Instrument_ExtraHeader_EnvelopePoint]
    `panningPoints`*: seq[FasttrackerXmModule_Instrument_ExtraHeader_EnvelopePoint]
    `numVolumePoints`*: uint8
    `numPanningPoints`*: uint8
    `volumeSustainPoint`*: uint8
    `volumeLoopStartPoint`*: uint8
    `volumeLoopEndPoint`*: uint8
    `panningSustainPoint`*: uint8
    `panningLoopStartPoint`*: uint8
    `panningLoopEndPoint`*: uint8
    `volumeType`*: FasttrackerXmModule_Instrument_ExtraHeader_Type
    `panningType`*: FasttrackerXmModule_Instrument_ExtraHeader_Type
    `vibratoType`*: uint8
    `vibratoSweep`*: uint8
    `vibratoDepth`*: uint8
    `vibratoRate`*: uint8
    `volumeFadeout`*: uint16
    `reserved`*: uint16
    `parent`*: FasttrackerXmModule_Instrument_Header
  FasttrackerXmModule_Instrument_ExtraHeader_Type* = enum
    true = 0
    sustain = 1
    loop = 2
  FasttrackerXmModule_Instrument_ExtraHeader_EnvelopePoint* = ref object of KaitaiStruct
    `x`*: uint16
    `y`*: uint16
    `parent`*: FasttrackerXmModule_Instrument_ExtraHeader
  FasttrackerXmModule_Instrument_SamplesData* = ref object of KaitaiStruct
    `data`*: seq[byte]
    `header`*: FasttrackerXmModule_Instrument_SampleHeader
    `parent`*: FasttrackerXmModule_Instrument
  FasttrackerXmModule_Instrument_SampleHeader* = ref object of KaitaiStruct
    `sampleLength`*: uint32
    `sampleLoopStart`*: uint32
    `sampleLoopLength`*: uint32
    `volume`*: uint8
    `fineTune`*: int8
    `type`*: FasttrackerXmModule_Instrument_SampleHeader_LoopType
    `panning`*: uint8
    `relativeNoteNumber`*: int8
    `reserved`*: uint8
    `name`*: string
    `parent`*: FasttrackerXmModule_Instrument
  FasttrackerXmModule_Instrument_SampleHeader_LoopType* = ref object of KaitaiStruct
    `reserved0`*: uint64
    `isSampleData16Bit`*: bool
    `reserved1`*: uint64
    `loopType`*: FasttrackerXmModule_Instrument_SampleHeader_LoopType_LoopType
    `parent`*: FasttrackerXmModule_Instrument_SampleHeader
  FasttrackerXmModule_Instrument_SampleHeader_LoopType_LoopType* = enum
    none = 0
    forward = 1
    ping_pong = 2

proc read*(_: typedesc[FasttrackerXmModule], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): FasttrackerXmModule
proc read*(_: typedesc[FasttrackerXmModule_Preheader], io: KaitaiStream, root: KaitaiStruct, parent: FasttrackerXmModule): FasttrackerXmModule_Preheader
proc read*(_: typedesc[FasttrackerXmModule_Preheader_Version], io: KaitaiStream, root: KaitaiStruct, parent: FasttrackerXmModule_Preheader): FasttrackerXmModule_Preheader_Version
proc read*(_: typedesc[FasttrackerXmModule_Pattern], io: KaitaiStream, root: KaitaiStruct, parent: FasttrackerXmModule): FasttrackerXmModule_Pattern
proc read*(_: typedesc[FasttrackerXmModule_Pattern_Header], io: KaitaiStream, root: KaitaiStruct, parent: FasttrackerXmModule_Pattern): FasttrackerXmModule_Pattern_Header
proc read*(_: typedesc[FasttrackerXmModule_Pattern_Header_HeaderMain], io: KaitaiStream, root: KaitaiStruct, parent: FasttrackerXmModule_Pattern_Header): FasttrackerXmModule_Pattern_Header_HeaderMain
proc read*(_: typedesc[FasttrackerXmModule_Flags], io: KaitaiStream, root: KaitaiStruct, parent: FasttrackerXmModule_Header): FasttrackerXmModule_Flags
proc read*(_: typedesc[FasttrackerXmModule_Header], io: KaitaiStream, root: KaitaiStruct, parent: FasttrackerXmModule): FasttrackerXmModule_Header
proc read*(_: typedesc[FasttrackerXmModule_Instrument], io: KaitaiStream, root: KaitaiStruct, parent: FasttrackerXmModule): FasttrackerXmModule_Instrument
proc read*(_: typedesc[FasttrackerXmModule_Instrument_Header], io: KaitaiStream, root: KaitaiStruct, parent: FasttrackerXmModule_Instrument): FasttrackerXmModule_Instrument_Header
proc read*(_: typedesc[FasttrackerXmModule_Instrument_ExtraHeader], io: KaitaiStream, root: KaitaiStruct, parent: FasttrackerXmModule_Instrument_Header): FasttrackerXmModule_Instrument_ExtraHeader
proc read*(_: typedesc[FasttrackerXmModule_Instrument_ExtraHeader_EnvelopePoint], io: KaitaiStream, root: KaitaiStruct, parent: FasttrackerXmModule_Instrument_ExtraHeader): FasttrackerXmModule_Instrument_ExtraHeader_EnvelopePoint
proc read*(_: typedesc[FasttrackerXmModule_Instrument_SamplesData], io: KaitaiStream, root: KaitaiStruct, parent: FasttrackerXmModule_Instrument, header: any): FasttrackerXmModule_Instrument_SamplesData
proc read*(_: typedesc[FasttrackerXmModule_Instrument_SampleHeader], io: KaitaiStream, root: KaitaiStruct, parent: FasttrackerXmModule_Instrument): FasttrackerXmModule_Instrument_SampleHeader
proc read*(_: typedesc[FasttrackerXmModule_Instrument_SampleHeader_LoopType], io: KaitaiStream, root: KaitaiStruct, parent: FasttrackerXmModule_Instrument_SampleHeader): FasttrackerXmModule_Instrument_SampleHeader_LoopType

proc value*(this: FasttrackerXmModule_Preheader_Version): int
proc numRows*(this: FasttrackerXmModule_Pattern_Header_HeaderMain): int


##[
XM (standing for eXtended Module) is a popular module music file
format, that was introduced in 1994 in FastTracker2 by Triton demo
group. Akin to MOD files, it bundles both digital samples
(instruments) and instructions on which note to play at what time
(patterns), which provides good audio quality with relatively small
file size. Audio is reproducible without relying on the sound of
particular hardware samplers or synths.

@see <a href="http://sid.ethz.ch/debian/milkytracker/milkytracker-0.90.85%2Bdfsg/resources/reference/xm-form.txt
ftp://ftp.modland.com/pub/documents/format_documentation/FastTracker%202%20v2.04%20(.xm).html
">Source</a>
]##
proc read*(_: typedesc[FasttrackerXmModule], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): FasttrackerXmModule =
  template this: untyped = result
  this = new(FasttrackerXmModule)
  let root = if root == nil: cast[FasttrackerXmModule](this) else: cast[FasttrackerXmModule](root)
  this.io = io
  this.root = root
  this.parent = parent

  let preheaderExpr = FasttrackerXmModule_Preheader.read(this.io, this.root, this)
  this.preheader = preheaderExpr
  let rawHeaderExpr = this.io.readBytes(int((this.preheader.headerSize - 4)))
  this.rawHeader = rawHeaderExpr
  let rawHeaderIo = newKaitaiStream(rawHeaderExpr)
  let headerExpr = FasttrackerXmModule_Header.read(rawHeaderIo, this.root, this)
  this.header = headerExpr
  for i in 0 ..< int(this.header.numPatterns):
    let it = FasttrackerXmModule_Pattern.read(this.io, this.root, this)
    this.patterns.add(it)
  for i in 0 ..< int(this.header.numInstruments):
    let it = FasttrackerXmModule_Instrument.read(this.io, this.root, this)
    this.instruments.add(it)

proc fromFile*(_: typedesc[FasttrackerXmModule], filename: string): FasttrackerXmModule =
  FasttrackerXmModule.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[FasttrackerXmModule_Preheader], io: KaitaiStream, root: KaitaiStruct, parent: FasttrackerXmModule): FasttrackerXmModule_Preheader =
  template this: untyped = result
  this = new(FasttrackerXmModule_Preheader)
  let root = if root == nil: cast[FasttrackerXmModule](this) else: cast[FasttrackerXmModule](root)
  this.io = io
  this.root = root
  this.parent = parent

  let signature0Expr = this.io.readBytes(int(17))
  this.signature0 = signature0Expr

  ##[
  Module name, padded with zeroes
  ]##
  let moduleNameExpr = encode(this.io.readBytes(int(20)).bytesTerminate(0, false), "utf-8")
  this.moduleName = moduleNameExpr
  let signature1Expr = this.io.readBytes(int(1))
  this.signature1 = signature1Expr

  ##[
  Tracker name
  ]##
  let trackerNameExpr = encode(this.io.readBytes(int(20)).bytesTerminate(0, false), "utf-8")
  this.trackerName = trackerNameExpr

  ##[
  Format versions below [0x01, 0x04] have a LOT of differences. Check this field!
  ]##
  let versionNumberExpr = FasttrackerXmModule_Preheader_Version.read(this.io, this.root, this)
  this.versionNumber = versionNumberExpr

  ##[
  Header size << Calculated FROM THIS OFFSET, not from the beginning of the file! >>
  ]##
  let headerSizeExpr = this.io.readU4le()
  this.headerSize = headerSizeExpr

proc fromFile*(_: typedesc[FasttrackerXmModule_Preheader], filename: string): FasttrackerXmModule_Preheader =
  FasttrackerXmModule_Preheader.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[FasttrackerXmModule_Preheader_Version], io: KaitaiStream, root: KaitaiStruct, parent: FasttrackerXmModule_Preheader): FasttrackerXmModule_Preheader_Version =
  template this: untyped = result
  this = new(FasttrackerXmModule_Preheader_Version)
  let root = if root == nil: cast[FasttrackerXmModule](this) else: cast[FasttrackerXmModule](root)
  this.io = io
  this.root = root
  this.parent = parent


  ##[
  currently 0x04
  ]##
  let minorExpr = this.io.readU1()
  this.minor = minorExpr

  ##[
  currently 0x01
  ]##
  let majorExpr = this.io.readU1()
  this.major = majorExpr

proc value(this: FasttrackerXmModule_Preheader_Version): int = 
  if this.valueInst != nil:
    return this.valueInst
  let valueInstExpr = int(((this.major shl 8) or this.minor))
  this.valueInst = valueInstExpr
  if this.valueInst != nil:
    return this.valueInst

proc fromFile*(_: typedesc[FasttrackerXmModule_Preheader_Version], filename: string): FasttrackerXmModule_Preheader_Version =
  FasttrackerXmModule_Preheader_Version.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[FasttrackerXmModule_Pattern], io: KaitaiStream, root: KaitaiStruct, parent: FasttrackerXmModule): FasttrackerXmModule_Pattern =
  template this: untyped = result
  this = new(FasttrackerXmModule_Pattern)
  let root = if root == nil: cast[FasttrackerXmModule](this) else: cast[FasttrackerXmModule](root)
  this.io = io
  this.root = root
  this.parent = parent

  let headerExpr = FasttrackerXmModule_Pattern_Header.read(this.io, this.root, this)
  this.header = headerExpr
  let packedDataExpr = this.io.readBytes(int(this.header.main.lenPackedPattern))
  this.packedData = packedDataExpr

proc fromFile*(_: typedesc[FasttrackerXmModule_Pattern], filename: string): FasttrackerXmModule_Pattern =
  FasttrackerXmModule_Pattern.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[FasttrackerXmModule_Pattern_Header], io: KaitaiStream, root: KaitaiStruct, parent: FasttrackerXmModule_Pattern): FasttrackerXmModule_Pattern_Header =
  template this: untyped = result
  this = new(FasttrackerXmModule_Pattern_Header)
  let root = if root == nil: cast[FasttrackerXmModule](this) else: cast[FasttrackerXmModule](root)
  this.io = io
  this.root = root
  this.parent = parent


  ##[
  Pattern header length
  ]##
  let headerLengthExpr = this.io.readU4le()
  this.headerLength = headerLengthExpr
  let rawMainExpr = this.io.readBytes(int((this.headerLength - 4)))
  this.rawMain = rawMainExpr
  let rawMainIo = newKaitaiStream(rawMainExpr)
  let mainExpr = FasttrackerXmModule_Pattern_Header_HeaderMain.read(rawMainIo, this.root, this)
  this.main = mainExpr

proc fromFile*(_: typedesc[FasttrackerXmModule_Pattern_Header], filename: string): FasttrackerXmModule_Pattern_Header =
  FasttrackerXmModule_Pattern_Header.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[FasttrackerXmModule_Pattern_Header_HeaderMain], io: KaitaiStream, root: KaitaiStruct, parent: FasttrackerXmModule_Pattern_Header): FasttrackerXmModule_Pattern_Header_HeaderMain =
  template this: untyped = result
  this = new(FasttrackerXmModule_Pattern_Header_HeaderMain)
  let root = if root == nil: cast[FasttrackerXmModule](this) else: cast[FasttrackerXmModule](root)
  this.io = io
  this.root = root
  this.parent = parent


  ##[
  Packing type (always 0)
  ]##
  let packingTypeExpr = this.io.readU1()
  this.packingType = packingTypeExpr

  ##[
  Number of rows in pattern (1..256)
  ]##
  block:
    let on = FasttrackerXmModule(this.root).preheader.versionNumber.value
    if on == 258:
      let numRowsRawExpr = uint16(this.io.readU1())
      this.numRowsRaw = numRowsRawExpr
    else:
      let numRowsRawExpr = this.io.readU2le()
      this.numRowsRaw = numRowsRawExpr

  ##[
  Packed pattern data size
  ]##
  let lenPackedPatternExpr = this.io.readU2le()
  this.lenPackedPattern = lenPackedPatternExpr

proc numRows(this: FasttrackerXmModule_Pattern_Header_HeaderMain): int = 
  if this.numRowsInst != nil:
    return this.numRowsInst
  let numRowsInstExpr = int((this.numRowsRaw + (if FasttrackerXmModule(this.root).preheader.versionNumber.value == 258: 1 else: 0)))
  this.numRowsInst = numRowsInstExpr
  if this.numRowsInst != nil:
    return this.numRowsInst

proc fromFile*(_: typedesc[FasttrackerXmModule_Pattern_Header_HeaderMain], filename: string): FasttrackerXmModule_Pattern_Header_HeaderMain =
  FasttrackerXmModule_Pattern_Header_HeaderMain.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[FasttrackerXmModule_Flags], io: KaitaiStream, root: KaitaiStruct, parent: FasttrackerXmModule_Header): FasttrackerXmModule_Flags =
  template this: untyped = result
  this = new(FasttrackerXmModule_Flags)
  let root = if root == nil: cast[FasttrackerXmModule](this) else: cast[FasttrackerXmModule](root)
  this.io = io
  this.root = root
  this.parent = parent

  let reservedExpr = this.io.readBitsIntBe(15)
  this.reserved = reservedExpr

  ##[
  0 = Amiga frequency table (see below); 1 = Linear frequency table
  ]##
  let freqTableTypeExpr = this.io.readBitsIntBe(1) != 0
  this.freqTableType = freqTableTypeExpr

proc fromFile*(_: typedesc[FasttrackerXmModule_Flags], filename: string): FasttrackerXmModule_Flags =
  FasttrackerXmModule_Flags.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[FasttrackerXmModule_Header], io: KaitaiStream, root: KaitaiStruct, parent: FasttrackerXmModule): FasttrackerXmModule_Header =
  template this: untyped = result
  this = new(FasttrackerXmModule_Header)
  let root = if root == nil: cast[FasttrackerXmModule](this) else: cast[FasttrackerXmModule](root)
  this.io = io
  this.root = root
  this.parent = parent


  ##[
  Song length (in pattern order table)
  ]##
  let songLengthExpr = this.io.readU2le()
  this.songLength = songLengthExpr
  let restartPositionExpr = this.io.readU2le()
  this.restartPosition = restartPositionExpr

  ##[
  (2,4,6,8,10,...,32)
  ]##
  let numChannelsExpr = this.io.readU2le()
  this.numChannels = numChannelsExpr

  ##[
  (max 256)
  ]##
  let numPatternsExpr = this.io.readU2le()
  this.numPatterns = numPatternsExpr

  ##[
  (max 128)
  ]##
  let numInstrumentsExpr = this.io.readU2le()
  this.numInstruments = numInstrumentsExpr
  let flagsExpr = FasttrackerXmModule_Flags.read(this.io, this.root, this)
  this.flags = flagsExpr
  let defaultTempoExpr = this.io.readU2le()
  this.defaultTempo = defaultTempoExpr
  let defaultBpmExpr = this.io.readU2le()
  this.defaultBpm = defaultBpmExpr

  ##[
  max 256
  ]##
  for i in 0 ..< int(256):
    let it = this.io.readU1()
    this.patternOrderTable.add(it)

proc fromFile*(_: typedesc[FasttrackerXmModule_Header], filename: string): FasttrackerXmModule_Header =
  FasttrackerXmModule_Header.read(newKaitaiFileStream(filename), nil, nil)


##[
XM's notion of "instrument" typically constitutes of a
instrument metadata and one or several samples. Metadata
includes:

* instrument's name
* instruction of which sample to use for which note
* volume and panning envelopes and looping instructions
* vibrato settings

]##
proc read*(_: typedesc[FasttrackerXmModule_Instrument], io: KaitaiStream, root: KaitaiStruct, parent: FasttrackerXmModule): FasttrackerXmModule_Instrument =
  template this: untyped = result
  this = new(FasttrackerXmModule_Instrument)
  let root = if root == nil: cast[FasttrackerXmModule](this) else: cast[FasttrackerXmModule](root)
  this.io = io
  this.root = root
  this.parent = parent


  ##[
  Instrument size << header that is >>
<< "Instrument Size" field tends to be more than the actual size of the structure documented here (it includes also the extended instrument sample header above). So remember to check it and skip the additional bytes before the first sample header >>

  ]##
  let headerSizeExpr = this.io.readU4le()
  this.headerSize = headerSizeExpr
  let rawHeaderExpr = this.io.readBytes(int((this.headerSize - 4)))
  this.rawHeader = rawHeaderExpr
  let rawHeaderIo = newKaitaiStream(rawHeaderExpr)
  let headerExpr = FasttrackerXmModule_Instrument_Header.read(rawHeaderIo, this.root, this)
  this.header = headerExpr
  for i in 0 ..< int(this.header.numSamples):
    let it = FasttrackerXmModule_Instrument_SampleHeader.read(this.io, this.root, this)
    this.samplesHeaders.add(it)
  for i in 0 ..< int(this.header.numSamples):
    let it = FasttrackerXmModule_Instrument_SamplesData.read(this.io, this.root, this, this.samplesHeaders[i])
    this.samples.add(it)

proc fromFile*(_: typedesc[FasttrackerXmModule_Instrument], filename: string): FasttrackerXmModule_Instrument =
  FasttrackerXmModule_Instrument.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[FasttrackerXmModule_Instrument_Header], io: KaitaiStream, root: KaitaiStruct, parent: FasttrackerXmModule_Instrument): FasttrackerXmModule_Instrument_Header =
  template this: untyped = result
  this = new(FasttrackerXmModule_Instrument_Header)
  let root = if root == nil: cast[FasttrackerXmModule](this) else: cast[FasttrackerXmModule](root)
  this.io = io
  this.root = root
  this.parent = parent

  let nameExpr = encode(this.io.readBytes(int(22)).bytesTerminate(0, false), "utf-8")
  this.name = nameExpr

  ##[
  Usually zero, but this seems pretty random, don't assume it's zero
  ]##
  let typeExpr = this.io.readU1()
  this.type = typeExpr
  let numSamplesExpr = this.io.readU2le()
  this.numSamples = numSamplesExpr
  if this.numSamples > 0:
    let extraHeaderExpr = FasttrackerXmModule_Instrument_ExtraHeader.read(this.io, this.root, this)
    this.extraHeader = extraHeaderExpr

proc fromFile*(_: typedesc[FasttrackerXmModule_Instrument_Header], filename: string): FasttrackerXmModule_Instrument_Header =
  FasttrackerXmModule_Instrument_Header.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[FasttrackerXmModule_Instrument_ExtraHeader], io: KaitaiStream, root: KaitaiStruct, parent: FasttrackerXmModule_Instrument_Header): FasttrackerXmModule_Instrument_ExtraHeader =
  template this: untyped = result
  this = new(FasttrackerXmModule_Instrument_ExtraHeader)
  let root = if root == nil: cast[FasttrackerXmModule](this) else: cast[FasttrackerXmModule](root)
  this.io = io
  this.root = root
  this.parent = parent

  let lenSampleHeaderExpr = this.io.readU4le()
  this.lenSampleHeader = lenSampleHeaderExpr

  ##[
  Index of sample that should be used for any particular
note. In the simplest case, where it's only one sample
is available, it's an array of full of zeroes.

  ]##
  for i in 0 ..< int(96):
    let it = this.io.readU1()
    this.idxSamplePerNote.add(it)

  ##[
  Points for volume envelope. Only `num_volume_points` will be actually used.
  ]##
  for i in 0 ..< int(12):
    let it = FasttrackerXmModule_Instrument_ExtraHeader_EnvelopePoint.read(this.io, this.root, this)
    this.volumePoints.add(it)

  ##[
  Points for panning envelope. Only `num_panning_points` will be actually used.
  ]##
  for i in 0 ..< int(12):
    let it = FasttrackerXmModule_Instrument_ExtraHeader_EnvelopePoint.read(this.io, this.root, this)
    this.panningPoints.add(it)
  let numVolumePointsExpr = this.io.readU1()
  this.numVolumePoints = numVolumePointsExpr
  let numPanningPointsExpr = this.io.readU1()
  this.numPanningPoints = numPanningPointsExpr
  let volumeSustainPointExpr = this.io.readU1()
  this.volumeSustainPoint = volumeSustainPointExpr
  let volumeLoopStartPointExpr = this.io.readU1()
  this.volumeLoopStartPoint = volumeLoopStartPointExpr
  let volumeLoopEndPointExpr = this.io.readU1()
  this.volumeLoopEndPoint = volumeLoopEndPointExpr
  let panningSustainPointExpr = this.io.readU1()
  this.panningSustainPoint = panningSustainPointExpr
  let panningLoopStartPointExpr = this.io.readU1()
  this.panningLoopStartPoint = panningLoopStartPointExpr
  let panningLoopEndPointExpr = this.io.readU1()
  this.panningLoopEndPoint = panningLoopEndPointExpr
  let volumeTypeExpr = FasttrackerXmModule_Instrument_ExtraHeader_Type(this.io.readU1())
  this.volumeType = volumeTypeExpr
  let panningTypeExpr = FasttrackerXmModule_Instrument_ExtraHeader_Type(this.io.readU1())
  this.panningType = panningTypeExpr
  let vibratoTypeExpr = this.io.readU1()
  this.vibratoType = vibratoTypeExpr
  let vibratoSweepExpr = this.io.readU1()
  this.vibratoSweep = vibratoSweepExpr
  let vibratoDepthExpr = this.io.readU1()
  this.vibratoDepth = vibratoDepthExpr
  let vibratoRateExpr = this.io.readU1()
  this.vibratoRate = vibratoRateExpr
  let volumeFadeoutExpr = this.io.readU2le()
  this.volumeFadeout = volumeFadeoutExpr
  let reservedExpr = this.io.readU2le()
  this.reserved = reservedExpr

proc fromFile*(_: typedesc[FasttrackerXmModule_Instrument_ExtraHeader], filename: string): FasttrackerXmModule_Instrument_ExtraHeader =
  FasttrackerXmModule_Instrument_ExtraHeader.read(newKaitaiFileStream(filename), nil, nil)


##[
Envelope frame-counters work in range 0..FFFFh (0..65535 dec).
BUT! FT2 only itself supports only range 0..FFh (0..255 dec).
Some other trackers (like SoundTracker for Unix), however, can use the full range 0..FFFF, so it should be supported.
!!TIP: This is also a good way to detect if the module has been made with FT2 or not. (In case the tracker name is brain- deadly left unchanged!)
Of course it does not help if all instruments have the values inside FT2 supported range.
The value-field of the envelope point is ranged between 00..3Fh (0..64 dec).

]##
proc read*(_: typedesc[FasttrackerXmModule_Instrument_ExtraHeader_EnvelopePoint], io: KaitaiStream, root: KaitaiStruct, parent: FasttrackerXmModule_Instrument_ExtraHeader): FasttrackerXmModule_Instrument_ExtraHeader_EnvelopePoint =
  template this: untyped = result
  this = new(FasttrackerXmModule_Instrument_ExtraHeader_EnvelopePoint)
  let root = if root == nil: cast[FasttrackerXmModule](this) else: cast[FasttrackerXmModule](root)
  this.io = io
  this.root = root
  this.parent = parent


  ##[
  Frame number of the point
  ]##
  let xExpr = this.io.readU2le()
  this.x = xExpr

  ##[
  Value of the point
  ]##
  let yExpr = this.io.readU2le()
  this.y = yExpr

proc fromFile*(_: typedesc[FasttrackerXmModule_Instrument_ExtraHeader_EnvelopePoint], filename: string): FasttrackerXmModule_Instrument_ExtraHeader_EnvelopePoint =
  FasttrackerXmModule_Instrument_ExtraHeader_EnvelopePoint.read(newKaitaiFileStream(filename), nil, nil)


##[
The saved data uses simple delta-encoding to achieve better compression ratios (when compressed with pkzip, etc.)
Pseudocode for converting the delta-coded data to normal data,
old = 0;
for i in range(data_len):
  new = sample[i] + old;
  sample[i] = new;
  old = new;

]##
proc read*(_: typedesc[FasttrackerXmModule_Instrument_SamplesData], io: KaitaiStream, root: KaitaiStruct, parent: FasttrackerXmModule_Instrument, header: any): FasttrackerXmModule_Instrument_SamplesData =
  template this: untyped = result
  this = new(FasttrackerXmModule_Instrument_SamplesData)
  let root = if root == nil: cast[FasttrackerXmModule](this) else: cast[FasttrackerXmModule](root)
  this.io = io
  this.root = root
  this.parent = parent
  let headerExpr = FasttrackerXmModule_Instrument_SampleHeader(header)
  this.header = headerExpr

  let dataExpr = this.io.readBytes(int((this.header.sampleLength * (if this.header.type.isSampleData16Bit: 2 else: 1))))
  this.data = dataExpr

proc fromFile*(_: typedesc[FasttrackerXmModule_Instrument_SamplesData], filename: string): FasttrackerXmModule_Instrument_SamplesData =
  FasttrackerXmModule_Instrument_SamplesData.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[FasttrackerXmModule_Instrument_SampleHeader], io: KaitaiStream, root: KaitaiStruct, parent: FasttrackerXmModule_Instrument): FasttrackerXmModule_Instrument_SampleHeader =
  template this: untyped = result
  this = new(FasttrackerXmModule_Instrument_SampleHeader)
  let root = if root == nil: cast[FasttrackerXmModule](this) else: cast[FasttrackerXmModule](root)
  this.io = io
  this.root = root
  this.parent = parent

  let sampleLengthExpr = this.io.readU4le()
  this.sampleLength = sampleLengthExpr
  let sampleLoopStartExpr = this.io.readU4le()
  this.sampleLoopStart = sampleLoopStartExpr
  let sampleLoopLengthExpr = this.io.readU4le()
  this.sampleLoopLength = sampleLoopLengthExpr
  let volumeExpr = this.io.readU1()
  this.volume = volumeExpr

  ##[
  -16..+15
  ]##
  let fineTuneExpr = this.io.readS1()
  this.fineTune = fineTuneExpr
  let typeExpr = FasttrackerXmModule_Instrument_SampleHeader_LoopType.read(this.io, this.root, this)
  this.type = typeExpr

  ##[
  (0-255)
  ]##
  let panningExpr = this.io.readU1()
  this.panning = panningExpr
  let relativeNoteNumberExpr = this.io.readS1()
  this.relativeNoteNumber = relativeNoteNumberExpr
  let reservedExpr = this.io.readU1()
  this.reserved = reservedExpr
  let nameExpr = encode(this.io.readBytes(int(22)).bytesTerminate(0, false), "utf-8")
  this.name = nameExpr

proc fromFile*(_: typedesc[FasttrackerXmModule_Instrument_SampleHeader], filename: string): FasttrackerXmModule_Instrument_SampleHeader =
  FasttrackerXmModule_Instrument_SampleHeader.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[FasttrackerXmModule_Instrument_SampleHeader_LoopType], io: KaitaiStream, root: KaitaiStruct, parent: FasttrackerXmModule_Instrument_SampleHeader): FasttrackerXmModule_Instrument_SampleHeader_LoopType =
  template this: untyped = result
  this = new(FasttrackerXmModule_Instrument_SampleHeader_LoopType)
  let root = if root == nil: cast[FasttrackerXmModule](this) else: cast[FasttrackerXmModule](root)
  this.io = io
  this.root = root
  this.parent = parent

  let reserved0Expr = this.io.readBitsIntBe(3)
  this.reserved0 = reserved0Expr
  let isSampleData16BitExpr = this.io.readBitsIntBe(1) != 0
  this.isSampleData16Bit = isSampleData16BitExpr
  let reserved1Expr = this.io.readBitsIntBe(2)
  this.reserved1 = reserved1Expr
  let loopTypeExpr = FasttrackerXmModule_Instrument_SampleHeader_LoopType_LoopType(this.io.readBitsIntBe(2))
  this.loopType = loopTypeExpr

proc fromFile*(_: typedesc[FasttrackerXmModule_Instrument_SampleHeader_LoopType], filename: string): FasttrackerXmModule_Instrument_SampleHeader_LoopType =
  FasttrackerXmModule_Instrument_SampleHeader_LoopType.read(newKaitaiFileStream(filename), nil, nil)