Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/env python2
- #parts of code inspired by cseq2midi
- import os, sys, struct, io
- blockIds = {0x5000:'DATA', 0x5001:'LABL'}
- class argTypes:
- UINT8 = 0
- INT8 = 1
- UINT16 = 2
- INT16 = 3
- RANDOM = 4
- VARIABLE = 5
- VARLEN = 6
- class BCSEQ:
- def __init__(self, filename):
- self.blocks = {} #Dicts containing a dict with keys 'offset' and 'size'
- self.labels = []
- self.commands = []
- self.commandOffsets = {}
- self.filename = filename
- with open(self.filename, 'rb') as fh:
- self.bcseq = io.BytesIO(fh.read()) #store file in memory as file type object
- self.bcseq.seek(0)
- if self.bcseq.read(4) != b'CSEQ':
- sys.exit('Not a BCSEQ file')
- self.endianness = readBytes(self.bcseq, 2)
- self.headerSize = readBytes(self.bcseq, 2)
- self.version = readBytes(self.bcseq, 4)
- if self.version != 0x01010000: #1.1.0.0
- sys.exit('Encountered unknown CSEQ version: %X' % (self.version))
- self.bcseqSize = readBytes(self.bcseq, 4)
- self.blockCount = readBytes(self.bcseq, 4)
- for x in xrange(self.blockCount):
- bId = readBytes(self.bcseq, 4)
- if bId not in blockIds:
- sys.exit('Encountered unknown block id: %X' % (bId))
- bLabel = blockIds[bId]
- bOffs = readBytes(self.bcseq, 4)
- bSize = readBytes(self.bcseq, 4)
- self.blocks[bLabel] = {'offset':bOffs, 'size':bSize}
- #print "Found '%s' block. Offset: 0x%X, Size: 0x%X" % (bLabel, bOffs, bSize)
- if not self.blocks['DATA'] or not self.blocks['LABL']:
- sys.exit('Missing DATA or LABL block.')
- self.parseLablBlock()
- self.parseLabels()
- def parseLablBlock(self):
- """
- Returns a sorted list of dicts containing labels found in the label block
- Dict keys are 'offset', 'label', and 'checked'
- """
- labels = []
- startPos = self.bcseq.tell()
- self.bcseq.seek(self.blocks['LABL']['offset'])
- bLabel = self.bcseq.read(4)
- bSize = readBytes(self.bcseq, 4)
- labelInfoCount = readBytes(self.bcseq, 4)
- for x in xrange(labelInfoCount):
- labelInfoId = readBytes(self.bcseq, 4)
- if labelInfoId != 0x5100:
- sys.exit("Unknown ID in 'LABL' block: 0x%X" % (labelInfoId))
- labelInfoOffs = readBytes(self.bcseq, 4)
- tmp = self.bcseq.tell()
- self.bcseq.seek(self.blocks['LABL']['offset'] + 8 + labelInfoOffs) #Offset is relative to after the 8 byte block header
- tmpId = readBytes(self.bcseq, 4) #Should be 0x1F00, but not checking for that
- labelDataOffs = readBytes(self.bcseq, 4) #Relative to after 8 byte header of DATA block
- labelStrSize = readBytes(self.bcseq, 4)
- labelStr = self.bcseq.read(labelStrSize) #Assume label string size >0
- self.addLabel(labelDataOffs, labelStr)
- self.bcseq.seek(tmp)
- self.bcseq.seek(startPos)
- def addLabel(self, offset, label):
- """Adds a label to a list of labels if one doesn't already exist for that offset, and sorts them by offset"""
- if not any(x['offset'] == offset for x in self.labels):
- self.labels.append({'offset':offset, 'label':label, 'checked':False})
- self.labels.sort(key=lambda x: int(x['offset']))
- def labelFromOffset(self, offset, str):
- #Is this the best way to do this?
- label = next( (x['label'] for x in self.labels if x['offset'] == offset), None)
- if label == None:
- label = '%s_%06X' % (str, offset)
- self.addLabel(offset, label)
- return label
- def parseLabels(self):
- """Iterates over every label(including ones generated here) and decompiles their commands"""
- while 1:
- label = next( (x for x in self.labels if x['checked'] == False), None)
- if label == None:
- self.sortCommands()
- break
- self.parseLabelCommands(label)
- label['checked'] = True
- def parseLabelCommands(self, label):
- startPos = self.bcseq.tell()
- self.bcseq.seek(self.blocks['DATA']['offset'] + 8 + label['offset'])
- parsing = True
- while parsing:
- tmpcmd = {'offset':0, 'name':'UNDEFINED', 'suffix1':'', 'suffix2':'', 'suffix3':'', 'arg1Type':None, 'arg2Type':None, 'args':[]}
- tmpcmd['offset'] = self.bcseq.tell() - self.blocks['DATA']['offset'] - 8
- cmd = readBytes(self.bcseq, 1)
- #Parsing code checks for '_if' first, then one of ['_t', '_tr', '_tv'], then one of ['_r', '_v']
- if cmd == 0xA2: #_if
- tmpcmd['suffix3'] = '_if'
- cmd = readBytes(self.bcseq, 1)
- if cmd == 0xA3: #_t
- tmpcmd['suffix2'] = '_t'
- tmpcmd['arg2Type'] = argTypes.INT16
- cmd = readBytes(self.bcseq, 1)
- elif cmd == 0xA4: #_tr
- tmpcmd['suffix2'] = '_tr'
- tmpcmd['arg2Type'] = argTypes.RANDOM
- cmd = readBytes(self.bcseq, 1)
- elif cmd == 0xA5: #_tv
- tmpcmd['suffix2'] = '_tv'
- tmpcmd['arg2Type'] = argTypes.VARIABLE
- cmd = readBytes(self.bcseq, 1)
- if cmd == 0xA0: #_r
- tmpcmd['suffix1'] = '_r'
- tmpcmd['arg1Type'] = argTypes.RANDOM
- cmd = readBytes(self.bcseq, 1)
- elif cmd == 0xA1: #_v
- tmpcmd['suffix1'] = '_v'
- tmpcmd['arg1Type'] = argTypes.VARIABLE
- cmd = readBytes(self.bcseq, 1)
- if cmd < 0x80: #note
- tmpcmd['name'] = keyToStr(cmd)
- tmpcmd['args'].append(str(readBytes(self.bcseq, 1)))
- if tmpcmd['arg1Type'] == None: tmpcmd['arg1Type'] = argTypes.VARLEN
- tmpcmd['args'] += readArg(self.bcseq, tmpcmd['arg1Type'])
- elif cmd == 0x80: #wait
- tmpcmd['name'] = 'wait'
- if tmpcmd['arg1Type'] == None: tmpcmd['arg1Type'] = argTypes.VARLEN
- tmpcmd['args'] += readArg(self.bcseq, tmpcmd['arg1Type'])
- elif cmd == 0x81: #prg
- tmpcmd['name'] = 'prg'
- if tmpcmd['arg1Type'] == None: tmpcmd['arg1Type'] = argTypes.VARLEN
- tmpcmd['args'] += readArg(self.bcseq, tmpcmd['arg1Type'])
- elif cmd == 0x88: #opentrack
- tmpcmd['name'] = 'opentrack'
- tmpcmd['args'].append(str(readBytes(self.bcseq, 1)))
- label = self.labelFromOffset(readBytes(self.bcseq, 3, endian='big'), 'track')
- tmpcmd['args'].append(label)
- elif cmd == 0x89: #jump
- tmpcmd['name'] = 'jump'
- label = self.labelFromOffset(readBytes(self.bcseq, 3, endian='big'), 'loc')
- tmpcmd['args'].append(label)
- if tmpcmd['suffix3'] == '':
- parsing = False
- elif cmd == 0x8A: #call
- tmpcmd['name'] = 'call'
- label = self.labelFromOffset(readBytes(self.bcseq, 3, endian='big'), 'sub')
- tmpcmd['args'].append(label)
- elif 0xB0 <= cmd <= 0xDF and cmd not in [0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xDE]:
- if cmd == 0xB1: #env_hold
- tmpcmd['name'] = 'env_hold'
- if tmpcmd['arg1Type'] == None: tmpcmd['arg1Type'] = argTypes.INT8
- tmpcmd['args'] += readArg(self.bcseq, tmpcmd['arg1Type'])
- elif cmd == 0xB2: #monophonic
- suf = 'off' if readBytes(self.bcseq, 1) == 0 else 'on'
- tmpcmd['name'] = 'monophonic_%s' % (suf)
- elif cmd == 0xBF: #frontbypass
- suf = 'off' if readBytes(self.bcseq, 1) == 0 else 'on'
- tmpcmd['name'] = 'frontbypass_%s' % (suf)
- elif cmd == 0xC3: #transpose
- tmpcmd['name'] = 'transpose'
- if tmpcmd['arg1Type'] == None: tmpcmd['arg1Type'] = argTypes.INT8
- tmpcmd['args'] += readArg(self.bcseq, tmpcmd['arg1Type'])
- elif cmd == 0xC4: #pitchbend
- tmpcmd['name'] = 'pitchbend'
- if tmpcmd['arg1Type'] == None: tmpcmd['arg1Type'] = argTypes.INT8
- tmpcmd['args'] += readArg(self.bcseq, tmpcmd['arg1Type'])
- elif cmd == 0xC7: #notewait
- suf = 'off' if readBytes(self.bcseq, 1) == 0 else 'on'
- tmpcmd['name'] = 'notewait_%s' % (suf)
- elif cmd == 0xC8: #tie
- suf = 'off' if readBytes(self.bcseq, 1) == 0 else 'on'
- tmpcmd['name'] = 'tie%s' % (suf)
- elif cmd == 0xC9: #porta
- tmpcmd['name'] = 'porta'
- tmpcmd['args'].append(keyToStr(readBytes(self.bcseq, 1)))
- elif cmd == 0xCC: #mod_type
- tmpcmd['name'] = 'mod_type'
- modType = readBytes(self.bcseq, 1)
- if modType > 2:
- sys.exit("Unrecognized 'mod_type' value: 0x%02X" % (modType))
- tmpcmd['args'].append(['MOD_TYPE_PITCH', 'MOD_TYPE_VOLUME', 'MOD_TYPE_PAN'][modType])
- elif cmd == 0xCE: #porta
- suf = 'off' if readBytes(self.bcseq, 1) == 0 else 'on'
- tmpcmd['name'] = 'porta_%s' % (suf)
- elif 0xD0 <= cmd <= 0xD3: #Attack, decay, sustain, release
- tmpcmd['name'] = ['attack', 'decay', 'sustain', 'release'][cmd - 0xD0]
- if tmpcmd['arg1Type'] == None: tmpcmd['arg1Type'] = argTypes.INT8
- tmpcmd['args'] += readArg(self.bcseq, tmpcmd['arg1Type'])
- elif cmd == 0xD6: #printvar
- tmpcmd['name'] = 'printvar'
- tmpcmd['args'] += readArg(self.bcseq, argTypes.VARIABLE)
- elif cmd == 0xDF: #damper
- suf = 'off' if readBytes(self.bcseq, 1) == 0 else 'on'
- tmpcmd['name'] = 'damper_%s' % (suf)
- else:
- b0Range = {
- 0xB0:'timebase', 0xB3:'velocity_range', 0xB4:'biquad_type', 0xB5:'biquad_value', 0xB6:'bank_select',
- 0xBD:'mod_phase', 0xBE:'mod_curve', 0xC0:'pan', 0xC1:'volume', 0xC2:'main_volume',
- 0xC5:'bendrange', 0xC6:'prio', 0xCA:'mod_depth', 0xCB:'mod_speed', 0xCD:'mod_range',
- 0xCF:'porta_time', 0xD4:'loop_start', 0xD5:'volume2', 0xD7:'span', 0xD8:'lpf_cutoff',
- 0xD9:'fxsend_a', 0xDA:'fxsend_b', 0xDB:'mainsend', 0xDC:'init_pan', 0xDD:'mute'
- }
- tmpcmd['name'] = b0Range[cmd]
- if tmpcmd['arg1Type'] == None: tmpcmd['arg1Type'] = argTypes.UINT8
- tmpcmd['args'] += readArg(self.bcseq, tmpcmd['arg1Type'])
- if tmpcmd['arg2Type'] != None:
- tmpcmd['args'] += readArg(self.bcseq, tmpcmd['arg2Type'])
- elif cmd == 0xE0: #mod_delay
- tmpcmd['name'] = 'mod_delay'
- if tmpcmd['arg1Type'] == None: tmpcmd['arg1Type'] = argTypes.INT16
- tmpcmd['args'] += readArg(self.bcseq, tmpcmd['arg1Type'])
- elif cmd == 0xE1: #tempo
- tmpcmd['name'] = 'tempo'
- if tmpcmd['arg1Type'] == None: tmpcmd['arg1Type'] = argTypes.INT16
- tmpcmd['args'] += readArg(self.bcseq, tmpcmd['arg1Type'])
- elif cmd == 0xE3: #sweep_pitch
- tmpcmd['name'] = 'sweep_pitch'
- if tmpcmd['arg1Type'] == None: tmpcmd['arg1Type'] = argTypes.INT16
- tmpcmd['args'] += readArg(self.bcseq, tmpcmd['arg1Type'])
- elif cmd == 0xE4: #mod_period
- tmpcmd['name'] = 'mod_period'
- if tmpcmd['arg1Type'] == None: tmpcmd['arg1Type'] = argTypes.INT16
- tmpcmd['args'] += readArg(self.bcseq, tmpcmd['arg1Type'])
- elif cmd == 0xF0: #extended
- cmd = readBytes(self.bcseq, 1)
- if 0x80 <= cmd <= 0x8B or 0x90 <= cmd <= 0x95:
- f080Range = {
- 0x80:'setvar', 0x81:'addvar', 0x82:'subvar', 0x83:'mulvar', 0x84:'divvar',
- 0x85:'shiftvar', 0x86:'randvar', 0x87:'andvar', 0x88:'orvar', 0x89:'xorvar',
- 0x8A:'notvar', 0x8B:'modvar', 0x90:'cmp_eq', 0x91:'cmp_ge', 0x92:'cmp_gt',
- 0x93:'cmp_le', 0x94:'cmp_lt', 0x95:'cmp_ne'
- }
- tmpcmd['name'] = f080Range[cmd]
- tmpcmd['args'] += readArg(self.bcseq, argTypes.VARIABLE)
- if tmpcmd['arg1Type'] == None: tmpcmd['arg1Type'] = argTypes.INT16
- tmpcmd['args'] += readArg(self.bcseq, tmpcmd['arg1Type'])
- elif cmd == 0xA4: #mod2_type
- tmpcmd['name'] = 'mod2_type'
- modType = readBytes(self.bcseq, 1)
- if modType > 2:
- sys.exit("Unrecognized 'mod2_type' value: 0x%02X" % (modType))
- tmpcmd['args'].append(['MOD_TYPE_PITCH', 'MOD_TYPE_VOLUME', 'MOD_TYPE_PAN'][modType])
- elif cmd == 0xAA: #mod3_type
- tmpcmd['name'] = 'mod3_type'
- modType = readBytes(self.bcseq, 1)
- if modType > 2:
- sys.exit("Unrecognized 'mod3_type' value: 0x%02X" % (modType))
- tmpcmd['args'].append(['MOD_TYPE_PITCH', 'MOD_TYPE_VOLUME', 'MOD_TYPE_PAN'][modType])
- elif cmd == 0xB0: #mod4_type
- tmpcmd['name'] = 'mod4_type'
- modType = readBytes(self.bcseq, 1)
- if modType > 2:
- sys.exit("Unrecognized 'mod4_type' value: 0x%02X" % (modType))
- tmpcmd['args'].append(['MOD_TYPE_PITCH', 'MOD_TYPE_VOLUME', 'MOD_TYPE_PAN'][modType])
- elif 0xA0 <= cmd <= 0xB1:
- f0a0Range = {
- 0xA0:'mod2_curve', 0xA1:'mod2_phase', 0xA2:'mod2_depth', 0xA3:'mod2_speed', 0xA5:'mod2_range',
- 0xA6:'mod3_curve', 0xA7:'mod3_phase', 0xA8:'mod3_depth', 0xA9:'mod3_speed', 0xAB:'mod3_range',
- 0xAC:'mod4_curve', 0xAD:'mod4_phase', 0xAE:'mod4_depth', 0xAF:'mod4_speed', 0xB1:'mod4_range'
- }
- tmpcmd['name'] = f0a0Range[cmd]
- if tmpcmd['arg1Type'] == None: tmpcmd['arg1Type'] = argTypes.UINT8
- tmpcmd['args'] += readArg(self.bcseq, tmpcmd['arg1Type'])
- elif cmd == 0xE0: #userproc
- tmpcmd['name'] = 'userproc'
- if tmpcmd['arg1Type'] == None: tmpcmd['arg1Type'] = argTypes.UINT16
- tmpcmd['args'] += readArg(self.bcseq, tmpcmd['arg1Type'])
- elif 0xE1 <= cmd <= 0xE6:
- f0e0Range = {
- 0xE1:'mod2_delay', 0xE2:'mod2_period', 0xE3:'mod3_delay',
- 0xE4:'mod3_period', 0xE5:'mod4_delay', 0xE6:'mod4_period'
- }
- tmpcmd['name'] = f0e0Range[cmd]
- if tmpcmd['arg1Type'] == None: tmpcmd['arg1Type'] = argTypes.INT16
- tmpcmd['args'] += readArg(self.bcseq, tmpcmd['arg1Type'])
- else:
- sys.exit('Unrecognized extended command: 0xFF 0x%X, at 0x%X' % (cmd, self.blocks['DATA']['offset'] + 8 + tmpcmd['offset']))
- elif cmd == 0xFB: #env_reset
- tmpcmd['name'] = 'env_reset'
- elif cmd == 0xFC: #loop_end
- tmpcmd['name'] = 'loop_end'
- elif cmd == 0xFD: #ret
- tmpcmd['name'] = 'ret'
- if tmpcmd['suffix3'] == '':
- parsing = False
- elif cmd == 0xFE: #alloctrack
- tmpcmd['name'] = 'alloctrack'
- tmpcmd['args'].append('0x%X' % (readBytes(self.bcseq, 2, endian='big'))) #TODO REPLACE
- elif cmd == 0xFF: #fin
- tmpcmd['name'] = 'fin'
- if tmpcmd['suffix3'] == '':
- parsing = False
- else:
- sys.exit('Unrecognized command: 0x%X, at 0x%X' % (cmd, self.blocks['DATA']['offset'] + 8 + tmpcmd['offset']))
- if self.commandOffsets.get(tmpcmd['offset'], False) == True:
- parsing = False
- else:
- self.commands.append(tmpcmd)
- self.commandOffsets[tmpcmd['offset']] = True
- self.bcseq.seek(startPos)
- def sortCommands(self):
- self.commands.sort(key=lambda x: int(x['offset']))
- def genCseq(self):
- out = ''
- out += ';;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n'
- out += '; Input file: %s\n' % (os.path.basename(self.filename))
- out += ';;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n\n'
- for command in self.commands:
- line = ''
- label = next( (x for x in self.labels if x['offset'] == command['offset']), None)
- if label != None:
- line += label['label'] + ':\n'
- line += ' '
- line += command['name'] + command['suffix1'] + command['suffix2'] + command['suffix3']
- if command['args']:
- line += ' ' + ', '.join(x for x in command['args'])
- line += '\n'
- if command['name'] == 'jump' or command['name'] == 'ret' or command['name'] == 'fin':
- line += '\n'
- out += line
- return out
- #Yuck, just wanted a generic function to handle this...
- def readBytes(file, bytes, endian='little', signed=False):
- val = 0
- for x in xrange(bytes):
- if endian == 'little':
- SHIFT = (x * 8)
- else: #Assume 'big' if not 'little'
- SHIFT = ((bytes - 1 - x) * 8)
- val |= struct.unpack('B', file.read(1))[0] << SHIFT
- if signed == True:
- if val >= (1 << ((bytes*8)-1)):
- val = val - (1 << (bytes*8))
- return val
- def readVarLen(file):
- val = 0
- temp = 0x80
- while temp & 0x80 != 0:
- temp = struct.unpack('B', file.read(1))[0]
- val = (val << 7) | (temp & 0x7F)
- return val
- def readArg(file, argType):
- """Returns a list because the RANDOM argType has two values"""
- val = []
- if argType == argTypes.UINT8:
- val.append(str(readBytes(file, 1)))
- elif argType == argTypes.INT8:
- val.append(str(readBytes(file, 1, signed=True)))
- elif argType == argTypes.UINT16:
- val.append(str(readBytes(file, 2, endian='big')))
- elif argType == argTypes.INT16:
- val.append(str(readBytes(file, 2, endian='big', signed=True)))
- elif argType == argTypes.RANDOM:
- val.append(str(readBytes(file, 2, endian='big', signed=True)))
- val.append(str(readBytes(file, 2, endian='big', signed=True)))
- elif argType == argTypes.VARIABLE:
- val.append(varToStr(readBytes(file, 1)))
- elif argType == argTypes.VARLEN:
- val.append(str(readVarLen(file)))
- return val
- def keyToStr(inp):
- keys = ['cn', 'cs', 'dn', 'ds', 'en', 'fn', 'fs', 'gn', 'gs', 'an', 'as', 'bn']
- key = keys[inp % 12]
- octave = (inp // 12) - 1 #key 0x00 = C(-1), cnm1 in text CSEQ format
- suffix = 'm' if octave < 0 else ''
- return key + suffix + str(abs(octave))
- def varToStr(num):
- prefixes = ['', 'G', 'T']
- return '%sVAR_%d' % (prefixes[num // 16], (num % 16))
- def main(argc, argv):
- if argc < 2 or not os.path.isfile(argv[1]):
- sys.exit('usage: %s file.bcseq' % (os.path.basename(__file__)))
- cseq = BCSEQ(argv[1])
- #for label in cseq.labels:
- # print label
- print cseq.genCseq()
- if __name__ == '__main__':
- main(len(sys.argv), sys.argv)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement