Skip to content

Instantly share code, notes, and snippets.

@depp
Created November 10, 2020 06:21
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save depp/20d20ba2cc568ce8dd1de4ad5e90369d to your computer and use it in GitHub Desktop.
Save depp/20d20ba2cc568ce8dd1de4ad5e90369d to your computer and use it in GitHub Desktop.
Nintendo 64 CTL Dumper
"""Dump a Nintendo 64 .ctl file to text.
Usage: python3 ctl.py <file.ctl>
"""
import argparse
import struct
import sys
SPACE = ' ' * 80
def pr(indent, *msg):
space = SPACE[:indent]
for arg in msg:
sys.stdout.write(space)
sys.stdout.write(str(arg))
space = ' '
sys.stdout.write('\n')
ENV_FIELDS = ["attackTime", "decayTime", "releaseTime", "attackVolume",
"decayVolume"]
def read_envelope(data, ptr, n):
fields = struct.unpack('>iiiBB', data[ptr:ptr+14])
assert len(fields) == len(ENV_FIELDS)
for name, value in zip(ENV_FIELDS, fields):
pr(n, '{} = {}'.format(name, value))
KMAP_FIELDS = ["velocityMin", "velocityMax", "keyMin", "keyMax", "keyBase",
"detune"]
def read_kmap(data, ptr, n):
fields = struct.unpack('>BBBBBb', data[ptr:ptr+6])
assert len(fields) == len(KMAP_FIELDS)
for name, value in zip(KMAP_FIELDS, fields):
pr(n, '{} = {}'.format(name, value))
ADPCM_LOOP_FIELDS = ["start", "end", "count"]
def read_adpcm_loop(data, ptr, n):
fields = struct.unpack('>III', data[ptr:ptr+12])
for (name, value) in zip(ADPCM_LOOP_FIELDS, fields):
pr(n, name, '=', value)
state = struct.unpack('>' + 'h' * 16, data[ptr+12:ptr+12+32])
pr(n, 'state =', state)
def read_adpcm_book(data, ptr, n):
pr(n, '<unimplemented>')
def read_adpcm_info(data, ptr, n):
loop, book = struct.unpack('>II', data[ptr:ptr+8])
if loop:
pr(n, 'loop = {')
read_adpcm_loop(data, loop, n + 2)
pr(n, '}')
else:
pr(n, 'loop = NULL')
pr(n, 'book = {')
read_adpcm_book(data, book, n + 2)
pr(n, '}')
RAW_LOOP_FIELDS = ["start", "end", "count"]
def read_raw_loop(data, ptr, n):
fields = struct.unpack('>III', data[ptr:ptr+12])
for (name, value) in zip(RAW_LOOP_FIELDS, fields):
pr(n, name, '=', value)
def read_raw_info(data, ptr, n):
loop, = struct.unpack('>I', data[ptr:ptr+4])
if loop:
pr(n, 'loop = {')
read_raw_loop(data, loop, n + 2)
pr(n, '}')
else:
pr(n, 'loop = NULL')
def read_wavetable(data, ptr, n):
base, wlen, wtype, flags = struct.unpack('>IIBB', data[ptr:ptr+10])
pr(n, 'base =', base)
pr(n, 'len =', wlen)
pr(n, 'wtype =', wtype)
pr(n, 'flags =', flags)
if wtype == 0:
pr(n, 'waveInfo.adpcmWave = {')
read_adpcm_info(data, ptr + 12, n + 2)
pr(n, '}')
elif wtype == 1:
pr(n, 'waveInfo.rawWave = {')
read_raw_info(data, ptr + 12, n + 2)
pr(n, '}')
def read_sound(data, ptr, n):
env, kmap, wtable, pan, volume, flags = struct.unpack('>IIIBBB', data[ptr:ptr+15])
m = n + 2
pr(n, 'envelope = {')
read_envelope(data, env, m)
pr(n, '}')
pr(n, 'kmap = {')
read_kmap(data, kmap, m)
pr(n, '}')
pr(n, 'wtable = {')
read_wavetable(data, wtable, m)
pr(n, '}')
pr(n, 'pan =', pan)
pr(n, 'volume =', volume)
pr(n, 'flags =', flags)
INSTRUMENT_FNAMES = ["volume", "pan", "priority", "flags", "tremType",
"tremRate", "tremDepth", "tremDelay", "vibType", "vibRate", "vibDepth",
"vibDelay", "bendRange", "soundCount"]
def read_instrument(data, ptr, n):
fields = struct.unpack('>BBBBBBBBBBBBhh', data[ptr:ptr+16])
assert len(fields) == len(INSTRUMENT_FNAMES)
for name, value in zip(INSTRUMENT_FNAMES, fields):
pr(n, '{} = {}'.format(name, value))
count = fields[-1]
m = n + 2
for i in range(count):
pr(n, 'soundArray[{}] = {{'.format(i))
sptr, = struct.unpack('>I', data[ptr+16+4*i:ptr+20+4*i])
read_sound(data, sptr, m)
pr(n, '}')
def read_bank(data, ptr, n):
count, flags, pad, rate, perc = struct.unpack('>hBBiI', data[ptr:ptr+12])
pr(n, 'instCount =', count)
pr(n, 'flags = 0x{:02x}'.format(flags))
pr(n, 'pad =', pad)
pr(n, 'sampleRate =', rate)
m = n + 2
if not perc:
pr(n, 'percussion = NULL')
else:
pr(n, 'percussion = {')
read_instrument(data, perc, m)
pr(n, '}')
for i in range(count):
iptr, = struct.unpack('>I', data[ptr+12+4*i:ptr+16+4*i])
pr(n, 'instArray[{}] = {{'.format(i))
read_instrument(data, iptr, m)
pr(n, '}')
def read_bankfile(data, ptr, n):
rev, count = struct.unpack('>hh', data[ptr:ptr+4])
pr(n, 'revision =', rev)
pr(n, 'bankCount =', count)
m = n + 2
for i in range(count):
pr(n, 'bankArray[{}] = {{'.format(i))
bptr, = struct.unpack('>I', data[ptr+4+4*i:ptr+8+4*i])
read_bank(data, bptr, m)
pr(n, '}')
def main(argv):
p = argparse.ArgumentParser('ctl.py')
p.add_argument('ctl')
args = p.parse_args(argv)
with open(args.ctl, 'rb') as fp:
data = fp.read()
read_bankfile(data, 0, 0)
if __name__ == '__main__':
main(sys.argv[1:])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment