Skip to content

Instantly share code, notes, and snippets.

@ftsf

ftsf/m8i.nim Secret

Last active February 7, 2024 06:09
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ftsf/223b0fc761339b3c23dda7dd891514d9 to your computer and use it in GitHub Desktop.
Save ftsf/223b0fc761339b3c23dda7dd891514d9 to your computer and use it in GitHub Desktop.
code for loading m8i / m8s files
import strutils
import strformat
import bitops
when not defined(js):
import streams
else:
import jsstream
const instrumentSize* = 357
type InstrumentType* = enum
WAVSYNTH = 0
MACROSYN = 1
SAMPLE = 2
MIDIOUT = 3
FMSYNTH = 4
NONE = 0xFF
type WavShape* = enum
PULSE12 = 0
PULSE25 = 1
PULSE50 = 2
PULSE75 = 3
SAW = 4
TRIANGLE = 5
SINE = 6
NOISE_PITCHED = 7
NOISE = 8
OVERFLOW = 9
type MacroShape* {.pure.} = enum
CSAW
MORPH
SAW_SQUARE
SINE_TRIANGLE
BUZZ
SQUARE_SUB
SAW_SUB
SQUARE_SYNC
SAW_SYNC
TRIPLE_SAW
TRIPLE_SQUARE
TRIPLE_TRIANGLE
TRIPLE_SIN
TRIPLE_RNG
SAW_SWARM
SAW_COMB
TOY
DIGITAL_FILTER_LP
DIGITAL_FILTER_PK
DIGITAL_FILTER_BP
DIGITAL_FILTER_HP
VOSIM
VOWEL
VOWEL_FOF
HARMONICS
FM
FEEDBACK_FM
CHAOTIC_FEEDBACK_FM
PLUCKED
BOWED
BLOWN
STRUCK_BELL
STRUCK_DRUM
KICK
CYMBAL
SNARE
WAVETABLES
WAVE_MAP
WAV_LINE
WAV_PARAPHONIC
FILTERED_NOISE
TWIN_PEAKS_NOISE
CLOCKED_NOISE
GRANULAR_CLOUD
PARTICLE_NOISE
type MacroInstrument* = object
shape*: int
timbre*: int
color*: int
degrade*: int
redux*: int
type WavInstrument* = object
shape*: int
size*: int
mult*: int
warp*: int
mirror*: int
type SampleInstrument* = object
samplePath*: string
fineTune*: int
detune*: int
playMode*: int
slices*: int
start*: int
loopStart*: int
length*: int
degrade*: int
type LimitType* {.pure.} = enum
CLIP
SIN
FOLD
WRAP
POST
POSTAD = "POST: AD"
type Env* = object
dest*: int
amount*: int
attack*: int
hold*: int
decay*: int
retrigger*: int
type LFO* = object
shape*: int
dest*: int
triggerMode*: int
freq*: int
amount*: int
retrigger*: int
type FMWave* {.pure.} = enum
SIN
SW2
SW3
SW4
SW5
SW6
TRI
SAW
SQR
PUL
IMP
NOI
proc FMAlgoStr*(x: SomeInteger): string =
case x:
of 0x00: "A>B>C>D"
of 0x01: "[A+B]>C>D"
of 0x02: "[A>B+C]>D"
of 0x03: "[A>B+A>C]>D"
of 0x04: "[A+B+C]>D"
of 0x05: "[A>B>C]+D"
of 0x06: "[A>B>C]+[A>B>D]"
of 0x07: "[A>B]+[C>D]"
of 0x08: "[A>B]+[A>C]+[A>D]"
of 0x09: "[A>B]+[A>C]+D"
of 0x0A: "[A>B]+C+D"
of 0x0B: "A+B+C+D"
else: ""
type FMInstrument* = object
algo*: int
wave*: array[4, FMWave]
ratio*: array[4, int]
ratioFine*: array[4, int]
level*: array[4, int]
fb*: array[4, int]
modA*: array[4, int]
modB*: array[4, int]
mods*: array[4, int]
type CommonSettings* = object
filter*: int
cutoff*: int
res*: int
amp*: int
lim*: int
pan*: int
dry*: int
cho*: int
del*: int
rev*: int
env*: array[2,Env]
lfo*: array[2,LFO]
type
FileType* {.pure.} = enum
Song = 0x0
Inst = 0x1
Theme = 0x2
Scale = 0x3
M8Version* = object
versionStr*: string
fileType*: FileType
majorVersion*: int
minorVersion*: int
patchVersion*: int
Groove* = object
data: array[16, int]
Phrase* = object
rows*: array[16, array[9, int]]
empty*: bool
Chain* = object
rows*: array[16, array[2, int]]
empty*: bool
Table* = object
rows*: array[16, array[8, int]]
Scale* = object
name*: string
notes*: array[12, bool]
offsets*: array[12, (int,int)]
Song* = ref object
currentChain*: uint8
currentPhrase*: uint8
currentInstrument*: uint8
version*: M8Version
directory*: string
transpose*: int
tempo*: float33
quantize*: bool
projectName*: string
midiSyncInputMode*: int
midiSyncInputTransport*: int
midiSyncOutputMode*: int
midiSyncOutputTransport*: int
midiRecordNoteChannel*: int
midiRecordVelocity*: bool
midiRecordDelayKill*: bool
midiControlChannel*: int
midiSongRowCueChannel*: int
trackMidiInputChannel*: array[8, int]
trackMidiInputInstrument*: array[8, int]
trackMidiInputProgramChange*: bool
mixerMainVolume*: int
mixerTrackVolume*: array[8, int]
mixerChorusVolume*: int
mixerDelayVolume*: int
mixerReverbVolume*: int
mixerAnalogInputVolume*: int
mixerUSBInputVolume*: int
mixerAnalogInputChorus*: int
mixerUSBInputChorus*: int
mixerAnalogInputDelay*: int
mixerUSBInputDelay*: int
mixerAnalogInputReverb*: int
mixerUSBInputReverb*: int
grooves*: array[32, Groove]
songOrder*: array[256, array[8, int]]
phrases*: array[255, Phrase]
chains*: array[255, Chain]
tables*: array[256, Table]
scales*: array[16, Scale]
instruments*: array[128, Instrument]
lastInstrument*: array[8, uint8]
lastSongRow*: int
currentTrack*: int
songCursor*: (int,int)
chainCursor*: (int,int)
phraseCursor*: (int,int)
Instrument* = ref object
case kind*: InstrumentType
of FMSYNTH: fm*: FMInstrument
of WAVSYNTH: wavsyn*: WavInstrument
of MACROSYN: macrosyn*: MacroInstrument
of SAMPLE: sample*: SampleInstrument
else:
discard
version*: M8Version
name*: string
author*: string
transpose*: bool
tableTick*: int
volume*: int
pitch*: int
fineTune*: int
common*: CommonSettings
tableData*: Table
type LfoShape* = enum
TRI
SIN
RAMP_DOWN
RAMP_UP
EXP_DN
EXP_UP
SQR_DN
SQR_UP
RANDOM
DRUNK
TRI_T
RAMPD_T
RAMPU_T
EXPD_T
EXPU_T
SQ_D_T
SQ_U_T
RAND_T
DRNK_T
type LfoTriggerMode* {.pure.} = enum
FREE
RETRIG
HOLD
ONCE
type FMModDest* {.pure.} = enum
LEV
RAT
PIT
FBK
proc FMModStr*(x: int): string =
return case x:
of 0x01: &"1>LEV"
of 0x02: &"2>LEV"
of 0x03: &"3>LEV"
of 0x04: &"4>LEV"
of 0x05: &"1>RAT"
of 0x06: &"2>RAT"
of 0x07: &"3>RAT"
of 0x08: &"4>RAT"
of 0x09: &"1>PIT"
of 0x0A: &"2>PIT"
of 0x0B: &"3>PIT"
of 0x0C: &"4>PIT"
of 0x0D: &"1>FBK"
of 0x0E: &"2>FBK"
of 0x0F: &"3>FBK"
of 0x10: &"4>FBK"
else: "-----"
type FMOp* = object
ratio*: uint8
ratioFine*: uint8
level*: uint8
fb*: uint8
modA*, modB*: int
type EnvDestWav* {.pure.} = enum
OFF
VOLUME
PITCH
SIZE
MULT
WARP
MIRROR
CUTOFF
RES
AMP
PAN
type EnvDestSample* {.pure.} = enum
OFF
VOLUME
PITCH
LOOP_ST
LENGTH
DEGRADE
CUTOFF
RES
AMP
PAN
type EnvDestFM* {.pure.} = enum
OFF
VOLUME
PITCH
MOD1
MOD2
MOD3
MOD4
CUTOFF
RES
AMP
PAN
type EnvDestMacro* {.pure.} = enum
OFF
VOLUME
PITCH
TIMBRE
COLOR
DEGRADE
REDUX
CUTOFF
RES
AMP
PAN
type FilterTypeWAVPreLPHP* {.pure.} = enum
OFF
LOWPASS
HIGHPASS
BANDPASS
BANDSTOP
WAV_LP
WAV_HP
WAV_BP
WAV_BS
type FilterTypeWAV* {.pure.} = enum
OFF
LOWPASS
HIGHPASS
BANDPASS
BANDSTOP
LP2HP = "LP>HP"
WAV_LP
WAV_HP
WAV_BP
WAV_BS
type FilterType* {.pure.} = enum
OFF
LOWPASS
HIGHPASS
BANDPASS
BANDSTOP
LP2HP = "LP>HP"
type CommonCmd* {.pure.} = enum
ARP
CHA
DEL
GRV
HOP
KIL
RAN
RET
REP
NTH
PSL
PVB
PVX
SED
TBL
THO
TIC
TPO
VMV
XCM
XCF
XCW
XCR
XDT
XDF
XDW
XDR
XRS
XRD
XRM
XRF
XRW
XRZ
VCH
VDE
VRE
VT1
VT2
VT3
VT4
VT5
VT6
VT7
VT8
DJF
type CommonCmd25* {.pure.} = enum
ARP
CHA
DEL
GRV
HOP
KIL
RAN
RET
REP
NTH
PSL
PBN
PVB
PVX
SCA
SCG
SED
SNG
TBL
THO
TIC
TPO
TSP
VMV
XCM
XCF
XCW
XCR
XDT
XDF
XDW
XDR
XRS
XRD
XRM
XRF
XRW
XRZ
VCH
VDE
VRE
VT1
VT2
VT3
VT4
VT5
VT6
VT7
VT8
DJF
IVO
ICH
IDE
IRE
IV2
IC2
ID2
IR2
USB
type MacrosynCmd* {.pure.} = enum
VOL = 0x80
PIT
FIN
OSC
TBR
COL
DEG
RED
FLT
CUT
RES
AMP
LIM
PAN
DRY
SCH
SDL
SRV
EA1
AT1
HO1
DE1
ET1
EA2
AT2
HO2
DE2
ET2
LA1
LF1
LT1
LA2
LF2
LT2
TRG
XX1
XX2
XX3
type WavsynCmd* {.pure.} = enum
VOL = 0x80
PIT
FIN
OSC
SIZ
MUL
WRP
MIR
FLT
CUT
RES
AMP
LIM
PAN
DRY
SCH
SDL
SRV
EA1
AT1
HO1
DE1
ET1
EA2
AT2
HO2
DE2
ET2
LA1
LF1
LT1
LA2
LF2
LT2
XX1
XX2
XX3
type FMCmd* {.pure.} = enum
VOL = 0x80
PIT
FIN
ALG
FM1
FM2
FM3
FM4
FLT
CUT
RES
AMP
LIM
PAN
DRY
SCH
SDL
SRV
EA1
AT1
HO1
DE1
ET1
EA2
AT2
HO2
DE2
ET2
LA1
LF1
LT1
LA2
LF2
LT2
FMP
XX1 = "---"
XX2 = "---"
XX3 = "---"
type SampleCmd* {.pure.} = enum
VOL = 0x80
PIT
FIN
PLY
STA
LOP
LEN
DEG
FLT
CUT
RES
AMP
LIM
PAN
DRY
SCH
SDL
SRV
EA1
AT1
HO1
DE1
ET1
EA2
AT2
HO2
DE2
ET2
LA1
LF1
LT1
LA2
LF2
LT2
SLI
XX2 = "---"
XX3 = "---"
type SamplePlayMode* = enum
FWD
REV
FWDLOOP
REVLOOP
FWD_PP
REV_PP
OSC
OSC_REV
OSC_PP
proc M8Signed*(x: uint8): int =
if x >= 0x80:
result = (x.int-256)
else:
result = x.int
proc readStrFF*(fp: auto, length: int): string =
var i = 0
while i < length:
i.inc()
let ch = fp.readChar()
if ch == 0xff.char or ch == '\0':
break
result &= ch
while i < length:
discard fp.readChar()
i.inc()
proc writeStrFF*(fp: auto, str: string, length: int) =
# writes string padded with 0xff
for i in 0..<length:
if i < str.len:
fp.write(str[i])
else:
fp.write(0xff.char)
proc writeStr00*(fp: auto, str: string, length: int) =
# writes string padded with 0x00
for i in 0..<length:
if i < str.len:
fp.write(str[i])
else:
fp.write(0x00.char)
proc writeUint8*(fp: auto, x: SomeOrdinal) =
fp.write(x.uint8)
proc `$`*(self: M8Version): string =
result = &"{self.fileType}:{self.majorVersion.toHex(2)}.{self.minorVersion.toHex(2)}.{self.patchVersion.toHex(2)}"
proc `==`*(a,b: M8Version): bool =
a.majorVersion == b.majorVersion and a.minorVersion == b.minorVersion and a.patchVersion == b.patchVersion
proc `<`*(a,b: M8Version): bool =
if a.majorVersion < b.majorVersion:
return true
elif a.minorVersion < b.minorVersion:
return true
elif a.patchVersion < b.patchVersion:
return true
return false
proc `<=`*(a,b: M8Version): bool =
if a == b:
return true
return a < b
proc version*(major,minor,patch: int, fileType: FileType = Inst): M8Version =
result.versionStr = "M8VERSION"
result.majorVersion = major
result.minorVersion = minor
result.patchVersion = patch
result.fileType = fileType
const vFmWavesAdded = version(0x00,0x01,0x40)
const vTablePaddingAdded = version(0x00,0x01,0x40)
const vTwoLFOs = version(0x00,0x01,0x40)
const v25 = version(0x00,0x02,0x50)
const vLPHPAdded = version(0x00,0x02,0x51)
proc readEnv*(fp: auto): Env =
result.dest = fp.readUint8().int
result.amount = fp.readUint8().int
result.attack = fp.readUint8().int
result.hold = fp.readUint8().int
result.decay = fp.readUint8().int
result.retrigger = fp.readUint8().int
proc writeEnv*(fp: auto, env: Env) =
fp.writeUint8(env.dest)
fp.writeUint8(env.amount)
fp.writeUint8(env.attack)
fp.writeUint8(env.hold)
fp.writeUint8(env.decay)
fp.writeUint8(env.retrigger)
proc readLFO*(fp: auto): LFO =
result.shape = fp.readUint8().int
result.dest = fp.readUint8().int
result.triggerMode = fp.readUint8().int
result.freq = fp.readUint8().int
result.amount = fp.readUint8().int
result.retrigger = fp.readUint8().int
proc writeLFO*(fp: auto, lfo: LFO) =
fp.writeUint8(lfo.shape)
fp.writeUint8(lfo.dest)
fp.writeUint8(lfo.triggerMode)
fp.writeUint8(lfo.freq)
fp.writeUint8(lfo.amount)
fp.writeUint8(lfo.retrigger)
proc readCommon*(fp: auto, version: M8Version): CommonSettings =
result.filter = fp.readUint8().int
result.cutoff = fp.readUint8().int
result.res = fp.readUint8().int
result.amp = fp.readUint8().int
result.lim = fp.readUint8().int
result.pan = fp.readUint8().int
result.dry = fp.readUint8().int
result.cho = fp.readUint8().int
result.del = fp.readUint8().int
result.rev = fp.readUint8().int
for i in 0..<2:
result.env[i] = fp.readEnv()
let lfoCount = if version >= vTwoLFOs: 2 else: 1
for i in 0..<lfoCount:
result.lfo[i] = fp.readLFO()
proc writeCommon*(fp: auto, instr: Instrument) =
fp.writeUint8(instr.common.filter)
fp.writeUint8(instr.common.cutoff)
fp.writeUint8(instr.common.res)
fp.writeUint8(instr.common.amp)
fp.writeUint8(instr.common.lim)
fp.writeUint8(instr.common.pan)
fp.writeUint8(instr.common.dry)
fp.writeUint8(instr.common.cho)
fp.writeUint8(instr.common.del)
fp.writeUint8(instr.common.rev)
for i in 0..<2:
fp.writeEnv(instr.common.env[i])
let lfoCount = if instr.version >= vTwoLFOs: 2 else: 1
for i in 0..<lfoCount:
fp.writeLFO(instr.common.lfo[i])
proc readMacrosyn*(fp: auto, version: M8Version): MacroInstrument =
result.shape = fp.readUint8().int
result.timbre = fp.readUint8().int
result.color = fp.readUint8().int
result.degrade = fp.readUint8().int
result.redux = fp.readUint8().int
proc writeMacrosyn*(fp: auto, m: MacroInstrument) =
fp.writeUint8(m.shape)
fp.writeUint8(m.timbre)
fp.writeUint8(m.color)
fp.writeUint8(m.degrade)
fp.writeUint8(m.redux)
proc readWavsynth*(fp: auto, version: M8Version): WavInstrument =
result.shape = fp.readUint8().int
result.size = fp.readUint8().int
result.mult = fp.readUint8().int
result.warp = fp.readUint8().int
result.mirror = fp.readUint8().int
proc writeWavsynth*(fp: auto, wav: WavInstrument) =
fp.writeUint8(wav.shape)
fp.writeUint8(wav.size)
fp.writeUint8(wav.mult)
fp.writeUint8(wav.warp)
fp.writeUint8(wav.mirror)
proc readSample*(fp: auto, version: M8Version): SampleInstrument =
result.playMode = fp.readUint8().int
result.slices = fp.readUint8().int
result.start = fp.readUint8().int
result.loopStart = fp.readUint8().int
result.length = fp.readUint8().int
result.degrade = fp.readUint8().int
proc writeSample*(fp: auto, sample: SampleInstrument) =
fp.writeUint8(sample.playMode)
fp.writeUint8(sample.slices)
fp.writeUint8(sample.start)
fp.writeUint8(sample.loopStart)
fp.writeUint8(sample.length)
fp.writeUint8(sample.degrade)
proc readFM*(fp: auto, version: M8Version): FMInstrument =
result.algo = fp.readUint8().int
var opData: array[4, FMOp]
if version >= vFmWavesAdded:
for op in 0..<4:
result.wave[op] = fp.readUint8().FMWave
for op in 0..<4:
result.ratio[op] = fp.readUint8().int
result.ratioFine[op] = fp.readUint8().int
for op in 0..<4:
result.level[op] = fp.readUint8().int
result.fb[op] = fp.readUint8().int
for op in 0..<4:
result.modA[op] = fp.readUint8().int
for op in 0..<4:
result.modB[op] = fp.readUint8().int
for i in 0..<4:
result.mods[i] = fp.readUint8().int
proc writeFM*(fp: auto, fm: FMInstrument) =
fp.writeUint8(fm.algo)
for op in 0..<4:
fp.writeUint8(fm.wave[op])
for op in 0..<4:
fp.writeUint8(fm.ratio[op])
fp.writeUint8(fm.ratioFine[op])
for op in 0..<4:
fp.writeUint8(fm.level[op])
fp.writeUint8(fm.fb[op])
for op in 0..<4:
fp.writeUint8(fm.modA[op])
for op in 0..<4:
fp.writeUint8(fm.modB[op])
for op in 0..<4:
fp.writeUint8(fm.mods[op])
proc readM8Version*(fp: auto): M8Version =
var versionStr = fp.readStrFF(9)
discard fp.readUint8()
var versionPatch = fp.readUint8().int
var versionMinor = fp.readUint8().int
var versionMajor = fp.readUint8().int
var fileType = (fp.readUint8() shr 4).int
#echo &"'{versionStr}' ft: {fileType.toHex(2)} {versionMajor.toHex(2)}.{versionMinor.toHex(2)}.{versionPatch.toHex(2)}"
result.versionStr = versionStr
result.fileType = fileType.FileType
result.majorVersion = versionMajor
result.minorVersion = versionMinor
result.patchVersion = versionPatch
proc writeM8Version*(fp: auto, version: M8Version) =
fp.writeStr00(version.versionStr, 9)
fp.writeUint8(0)
fp.writeUint8(version.patchVersion)
fp.writeUint8(version.minorVersion)
fp.writeUint8(version.majorVersion)
fp.writeUint8(version.fileType.int shl 4)
echo "writeM8Version done"
proc readScale*(fp: auto, version: M8Version): Scale =
# each scale is 0x2a (42) bytes
# the enabled notes are stored in the first two bytes as a bit map
result = Scale()
let noteMap = fp.readUint16()
for i in 0..<12:
result.notes[i] = noteMap.testBit(i)
for i in 0..<12:
result.offsets[i] = (fp.readUInt8().M8Signed, fp.readUInt8().M8Signed)
result.name = fp.readStrFF(16)
proc writeScale*(fp: auto, scale: Scale) =
var noteMap: uint16
for i in 0..<12:
noteMap.setBit(i, scale.notes[i])
fp.write(noteMap)
proc readSong*(fp: auto, version: M8Version): Song =
echo "readSong version ", version
result = Song()
result.currentChain = 0xff
result.version = version
result.directory = fp.readStrFF(128)
echo result.directory
result.transpose = fp.readUint8().int
var tempo1 = fp.readUint8()
var tempo2 = fp.readUint8()
var tempo3 = fp.readUint8()
var tempo4 = fp.readUint8()
echo &"tempo {tempo1}, {tempo2}, {tempo3}, {tempo4}"
result.tempo = 0
result.quantize = fp.readUint8().bool
result.projectName = fp.readStrFF(12)
echo result.projectName
result.midiSyncInputMode = fp.readUint8().int
result.midiSyncInputTransport = fp.readUint8().int
result.midiSyncOutputMode = fp.readUint8().int
result.midiSyncOutputTransport = fp.readUint8().int
result.midiRecordNoteChannel = fp.readUint8().int
result.midiRecordVelocity = fp.readUint8().bool
result.midiRecordDelayKill = fp.readUint8().bool
result.midiControlChannel = fp.readUint8().int
result.midiSongRowCueChannel = fp.readUint8().int
for i in 0..<8:
result.trackMidiInputChannel[i] = fp.readUint8().int
result.trackMidiInputInstrument[i] = fp.readUint8().int
result.trackMidiInputProgramChange = fp.readUint8().bool
result.mixerMainVolume = fp.readUint8().int
for i in 0..<8:
result.mixerTrackVolume[i] = fp.readUint8().int
result.mixerChorusVolume = fp.readUint8().int
result.mixerDelayVolume = fp.readUint8().int
result.mixerReverbVolume = fp.readUint8().int
result.mixerAnalogInputVolume = fp.readUint8().int
result.mixerUSBInputVolume = fp.readUint8().int
result.mixerAnalogInputChorus = fp.readUint8().int
result.mixerUSBInputChorus = fp.readUint8().int
result.mixerAnalogInputDelay = fp.readUint8().int
result.mixerUSBInputDelay = fp.readUint8().int
result.mixerAnalogInputReverb = fp.readUint8().int
result.mixerUSBInputReverb = fp.readUint8().int
for i in 0..<32:
discard fp.readUint8()
for i in 0..<32:
echo "GROOVE ", i.toHex(2)
for j in 0..<16:
result.grooves[i].data[j] = fp.readUint8().int
if result.grooves[i].data[j] != 0xff:
echo j.toHex(2) & ": ", result.grooves[i].data[j].toHex(2)
echo " "
echo "SONG DATA ", fp.getPosition().toHex(8)
for i in 0..<256:
var row = i.toHex(2) & ": "
var anyEntries = false
for j in 0..<8:
result.songOrder[i][j] = fp.readUint8().int
if result.songOrder[i][j] == 0xff:
row &= "--" & " "
else:
row &= result.songOrder[i][j].toHex(2) & " "
anyEntries = true
result.lastSongRow = i
if anyEntries:
echo row
echo "PHRASE DATA ", fp.getPosition().toHex(8)
for i in 0..<255:
var anyEntries = false
for j in 0..<16:
for k in 0..<9:
result.phrases[i].rows[j][k] = fp.readUint8().int
if result.phrases[i].rows[j][0] != 0xff:
anyEntries = true
if anyEntries:
echo "PHRASE ", i.toHex(2)
else:
result.phrases[i].empty = true
echo "CHAIN DATA ", fp.getPosition().toHex(8)
for i in 0..<255:
var anyEntries = false
var notEmpty = false
for j in 0..<16:
for k in 0..<2:
result.chains[i].rows[j][k] = fp.readUint8().int
if result.chains[i].rows[j][0] != 0xff:
anyEntries = true
if notEmpty == false and not result.phrases[result.chains[i].rows[j][0]].empty:
notEmpty = true
if anyEntries:
echo "CHAIN ", i.toHex(2)
if not notEmpty:
result.chains[i].empty = true
echo "TABLE DATA ", fp.getPosition().toHex(8)
for i in 0..<256:
result.tables[i] = fp.readTable()
echo "INSTRUMENT DATA ", fp.getPosition().toHex(8)
for i in 0..<128:
# each instrument is 207 bytes * 128 = 26496
var pos = fp.getPosition()
result.instruments[i] = fp.readInstr(version, fromSong = true)
if result.instruments[i].kind != None:
echo pos.toHex(8), ": ", i.toHex(2), " ", result.instruments[i].kind, " '", result.instruments[i].name, "'"
else:
#echo pos.toHex(8), ": ", i.toHex(2), " ", result.instruments[i].kind
discard
result.instruments[i].tableData = result.tables[i]
if version >= v25:
fp.setPosition(0x1aa7e)
echo "SCALES DATA ", fp.getPosition().toHex(8)
for i in 0..<16:
result.scales[i] = fp.readScale(version)
echo result.scales[i].name
echo result.scales[i].notes
echo result.scales[i].offsets
proc readInstr*(fp: auto, version: M8Version, fromSong: bool = false): Instrument =
var startPos = if not fromSong: fp.getPosition() - 14 else: fp.getPosition()
var kind = fp.readUint8().InstrumentType
result = Instrument(kind: kind)
result.version = version
result.name = fp.readStrFF(12)
result.transpose = fp.readUint8().bool
result.tableTick = fp.readUint8().int
result.volume = fp.readUint8().int
result.pitch = fp.readUint8().int
result.fineTune = fp.readUint8().int
case kind:
of FMSYNTH:
result.fm = readFM(fp, version)
of WAVSYNTH:
result.wavsyn = readWavsynth(fp, version)
of MACROSYN:
result.macrosyn = readMacrosyn(fp, version)
of SAMPLE:
result.sample = readSample(fp, version)
else:
discard
result.common = readCommon(fp, version)
let samplePathPos = startPos + (if fromSong: 0x56 else: 0x5D + 8)
fp.setPosition(samplePathPos)
if kind == SAMPLE:
result.sample.samplePath = fp.readStrFF(127)
if fromSong:
fp.setPosition(startPos + 0xD7)
else:
fp.setPosition(startPos + 0xDD)
if version >= vTablePaddingAdded:
# some padding before tables
for i in 0..<8:
discard fp.readUint8()
result.tableData = fp.readTable()
proc readTable*(fp: auto): Table =
for i in 0..<16:
for j in 0..<8:
result.rows[i][j] = fp.readUint8().int
proc writeTable*(fp: auto, table: Table) =
for i in 0..<16:
for j in 0..<8:
fp.writeUint8(table.rows[i][j])
proc writeInstr*(fp: auto, instr: Instrument) =
var startPos = fp.getPosition() - 14
# allow us to save an instrument to file
fp.writeUint8(instr.kind)
fp.writeStr00(instr.name, 12)
fp.writeUint8(instr.transpose)
fp.writeUint8(instr.tableTick)
fp.writeUint8(instr.volume)
fp.writeUint8(instr.pitch)
fp.writeUint8(instr.fineTune)
case instr.kind:
of FMSYNTH:
fp.writeFM(instr.fm)
of WAVSYNTH:
fp.writeWavsynth(instr.wavsyn)
of SAMPLE:
fp.writeSample(instr.sample)
of MACROSYN:
fp.writeMacrosyn(instr.macrosyn)
else:
discard
fp.writeCommon(instr)
fp.setPosition(startPos + 0x5D + 8)
if instr.kind == SAMPLE:
fp.writeStr00(instr.sample.samplePath, 127)
# tables
fp.setPosition(startPos + 0xDD)
if instr.version >= vTablePaddingAdded:
for i in 0..<8:
fp.writeUint8(0'u8)
fp.writeTable(instr.tableData)
proc write*(fp: auto, version: M8Version) =
fp.write("M8VERSION")
fp.write(0.uint8)
fp.write(version.patchVersion.uint8)
fp.write(version.minorVersion.uint8)
fp.write(version.majorVersion.uint8)
fp.write((version.fileType.int shl 4).uint8)
proc writeFM*(fp: auto, m8i: Instrument) =
fp.write(m8i.fm.algo.uint8)
for op in 0..<4:
fp.write(m8i.fm.wave[op].uint8)
for op in 0..<4:
fp.write(m8i.fm.ratio[op].uint8)
fp.write(m8i.fm.ratioFine[op].uint8)
for op in 0..<4:
fp.write(m8i.fm.level[op].uint8)
fp.write(m8i.fm.fb[op].uint8)
for op in 0..<4:
fp.write(m8i.fm.modA[op].uint8)
for op in 0..<4:
fp.write(m8i.fm.modB[op].uint8)
for op in 0..<4:
fp.write(m8i.fm.mods[op].uint8)
proc writeCommon*(fp: auto, m8i: Instrument) =
fp.write(m8i.common.filter.uint8)
fp.write(m8i.common.cutoff.uint8)
fp.write(m8i.common.res.uint8)
fp.write(m8i.common.amp.uint8)
fp.write(m8i.common.lim.uint8)
fp.write(m8i.common.pan.uint8)
fp.write(m8i.common.dry.uint8)
fp.write(m8i.common.cho.uint8)
fp.write(m8i.common.del.uint8)
fp.write(m8i.common.rev.uint8)
for i in 0..<2:
fp.write(m8i.common.env[i].dest.uint8)
fp.write(m8i.common.env[i].amount.uint8)
fp.write(m8i.common.env[i].attack.uint8)
fp.write(m8i.common.env[i].hold.uint8)
fp.write(m8i.common.env[i].decay.uint8)
fp.write(m8i.common.env[i].retrigger.uint8)
for i in 0..<2:
fp.write(m8i.common.lfo[i].shape.uint8)
fp.write(m8i.common.lfo[i].dest.uint8)
fp.write(m8i.common.lfo[i].triggerMode.uint8)
fp.write(m8i.common.lfo[i].freq.uint8)
fp.write(m8i.common.lfo[i].amount.uint8)
fp.write(m8i.common.lfo[i].retrigger.uint8)
proc write*(fp: auto, m8i: Instrument) =
fp.write(m8i.kind.uint8)
for i in 0..<12:
if i < m8i.name.len:
fp.write(m8i.name[i].char)
else:
fp.write(0.char)
fp.write(m8i.transpose.uint8)
fp.write(m8i.tableTick.uint8)
fp.write(m8i.volume.uint8)
fp.write(m8i.pitch.uint8)
fp.write(m8i.fineTune.uint8)
case m8i.kind:
of FMSYNTH:
fp.writeFM(m8i)
else:
discard
fp.writeCommon(m8i)
# sample name
if m8i.kind == SAMPLE:
# jump to 0x5D + 8
fp.setPosition(0x5D + 8)
for i in 0..<127:
if i < m8i.sample.samplePath.len:
fp.write(m8i.sample.samplePath[i].char)
else:
fp.write(0.char)
# jump to 0xDD
fp.setPosition(0xDD)
for j in 0..<8:
fp.write(0.uint8)
# table data
for i in 0..<16:
for j in 0..<8:
fp.write(m8i.tableData.rows[i][j].uint8)
proc noteStr*(note: int): string =
let oct = note div 12
let key = note mod 12
result = case key:
of 0: "C-"
of 1: "C#"
of 2: "D-"
of 3: "D#"
of 4: "E-"
of 5: "F-"
of 6: "F#"
of 7: "G-"
of 8: "G#"
of 9: "A-"
of 10: "A#"
of 11: "B-"
else: "?"
result &= $oct
proc getCmdName*(x: int, instr: Instrument): string =
case x:
of 0xFF:
"---"
of 0x00..0x1E:
if instr.version >= v25:
$x.CommonCmd25
else:
$x.CommonCmd
of 0x80..0xFE:
case instr.kind:
of MACROSYN:
$x.MacrosynCmd
of FMSYNTH:
$x.FMCmd
of WAVSYNTH:
$x.WavsynCmd
of SAMPLE:
$x.SampleCmd
else:
"UIK"
else:
"NFI"
proc toFilterType*(x: int, instr: Instrument): string =
return case instr.kind:
of WAVSYNTH:
if instr.version >= vLPHPAdded:
$x.FilterTypeWAV
else:
$x.FilterTypeWAVPreLPHP
of FMSYNTH, MACROSYN, SAMPLE:
$x.FilterType
else:
"---"
when isMainModule:
import os
import streams
proc row(label: string, values: varargs[string, `$`]) =
write(stdout, label)
write(stdout, ": ")
for v in values:
write(stdout, v)
write(stdout, " ")
write(stdout, "\n")
template hex1(x: untyped): untyped =
x.toHex(1)
template hex2(x: untyped): untyped =
x.toHex(2)
proc renderInstr(instr: Instrument) =
row("kind", instr.kind)
row("name", instr.name)
row("transpose", instr.transpose)
row("table tic", instr.tableTick)
row("volume", instr.volume)
row("pitch", instr.pitch)
row("fineTune", instr.fineTune)
echo ""
echo "COMMON"
row("filter", instr.common.filter.FilterTypeFM)
row("cutoff", instr.common.cutoff)
row("res", instr.common.res)
row("amp", instr.common.amp)
row("lim", instr.common.lim.LimitType)
row("pan", instr.common.pan)
row("dry", instr.common.dry)
row("cho", instr.common.cho)
row("del", instr.common.del)
row("rev", instr.common.rev)
echo ""
case instr.kind:
of FMSYNTH:
echo "FM"
row("algo", instr.fm.algo.FMAlgoStr)
row("wave", instr.fm.wave)
row("ratio", instr.fm.ratio)
row("ratioFine", instr.fm.ratioFine)
row("level", instr.fm.level)
row("fb", instr.fm.fb)
row("mod ", instr.fm.modA[0].FMModStr, instr.fm.modA[1].FMModStr, instr.fm.modA[2].FMModStr, instr.fm.modA[3].FMModStr)
row(" ", instr.fm.modB[0].FMModStr, instr.fm.modB[1].FMModStr, instr.fm.modB[2].FMModStr, instr.fm.modB[3].FMModStr)
row("mods", instr.fm.mods)
echo ""
else:
discard
echo "TABLE"
echo(" N V FX1 FX2 FX3 ")
for i in 0..<16:
var transpose = instr.tableData.rows[i][0]
var volume = instr.tableData.rows[i][1]
var cmd1 = instr.tableData.rows[i][2]
var cmd1Value = instr.tableData.rows[i][3]
var cmd2 = instr.tableData.rows[i][4]
var cmd2Value = instr.tableData.rows[i][5]
var cmd3 = instr.tableData.rows[i][6]
var cmd3Value = instr.tableData.rows[i][7]
let cmd1Str = if cmd1 != 0xff: cmd1.getCmdName(instr) else: "---"
let cmd2Str = if cmd2 != 0xff: cmd2.getCmdName(instr) else: "---"
let cmd3Str = if cmd3 != 0xff: cmd3.getCmdName(instr) else: "---"
echo &"{i.hex1} {transpose.hex2} {volume.hex2} {cmd1Str}{cmd1Value.hex2} {cmd2Str}{cmd2Value.hex2} {cmd3Str}{cmd3Value.hex2}"
proc renderSong(song: Song) =
discard
proc main() =
# parse file and dump data
if paramCount() == 0:
echo "usage: m8i file.m8i"
quit(1)
let fp = newFileStream(paramStr(1), fmRead)
let version = fp.readM8Version()
row("version", version)
if version.fileType == FileType.Inst:
let instr = fp.readInstr(version)
renderInstr(instr)
elif version.fileType == FileType.Scale:
let scale = fp.readScale(version)
renderScale(scale)
elif version.fileType == FileType.Song:
let song = fp.readSong(version)
renderSong(song)
for i, instr in song.instruments:
if instr.kind != None:
echo "INSTRUMENT ", i.toHex(2)
renderInstr(instr)
var outversion = v25
outversion.fileType = Inst
var outfp = newFileStream(instr.name & ".m8i", fmWrite)
outfp.saveM8Version(outversion)
outfp.writeInstr(instr)
outfp.close()
fp.close()
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment