#UNFINISHED # A LOT of data format info taken from https://forum.xentax.com/viewtopic.php?t=12966 import os import argparse import struct #yes two midi libraries... import music21 import mido def is_valid_file(parser, arg): if not os.path.exists(arg): parser.error("The file %s does not exist!" % arg) else: return arg parser = argparse.ArgumentParser(description="Process JAK SBK files") parser.add_argument("-i", dest="filepath", required=True, help="Input path to JAK SBK file", metavar="FILE", type=lambda x: is_valid_file(parser, x)) args = parser.parse_args() filename = os.path.basename(args.filepath) name_length = 20 sounds_start = 24 sounds_length = 20 with open(args.filepath, "rb") as f: ''' struct MUS { // 2 * 16 uint32_t version; // 1 version? uint32_t subversion; // 3 subversion? uint32_t minorversion; // 32 minor version? uint32_t oSomething; // points to start of soundbank data, -32? or is it SBv2 struct data size? uint32_t oBank; // this points to the start of the soundbank sample data uint32_t sBank; // size of sound bank uint32_t oMID; // pointer to sequenced music data uint32_t s3; } E.g.: Version 1.3.32, oSomething 704, Bank offset 736 B, Bank Size 534256 B, MID offset 534992, ? 22772 ''' MUSinfo = struct.unpack("IIIIIIII",f.read(32)) soundbankStart = MUSinfo[4] print(f"SoundBank Start: {soundbankStart}") sequenceStart = MUSinfo[6] print(f"Sequence Start: {sequenceStart}") sbv2Start = f.tell() ''' struct SBv2 { // 5x16 B char signature[4]; // "SBv2" uint32_t version; uint32_t hversion; char name[4]; //eg "V1RA" or "BOSS" uint32_t a; // 0xa uint16_t b; //1 uint16_t nMap; uint16_t nSMap; uint16_t nSample; // same as nMap? no it can be different... uint32_t f; //0x34 another offset, to end of extname from start of SBv2? uint32_t oInstrument; // from start of SBv2 uint32_t oRegions; // from start of SBv2 uint32_t i; //0x5040 uint32_t sizeofsamples; // same as in file header uint32_t k; //0 uint32_t l; //4 or 5? char extname[12]; // e.g. "V1RAV1RAV1RA" uint16_t m; uint16_t n; uint16_t o; uint16_t p; uint32_t q; } ''' sbv2 = struct.unpack("4sII4sIHHHHIIIIIII12sHHHHI",f.read(80)) instrumentStart = sbv2[10] + sbv2Start regionStart = sbv2[11] + sbv2Start print(MUSinfo) print(sbv2) f.seek(instrumentStart) instruments = [] while(f.tell() < regionStart): ''' struct Instrument { uint8_t nRegion; // usually 1 uint8_t volume; // ? maybe... like 7f is 1.0? uint16_t something; // always 0 uint32_t oRegion; // relative to start of SBv2 block I think, points to the first region for instrument } ''' instrument = struct.unpack("BBHI",f.read(8)) instruments.append(instrument) print("INSTRUMENTS") for instrument in instruments: print(instrument) ''' struct Region { // 0x18 B uint8_t type; //? always 0 uint8_t marker1; // usually 7f, not always, maybe another volume? uint8_t a; // not same for same sample data uint8_t b; // not same for same sample data int16_t c; // signed in range +/- 64? Tuning? intruments with 2 regions seem to have these in a plus minus couple, maybe panorama? uint32_t keymap; //? LSB is only set for programs with multiple entries? uint16_t marker2; // 0x80ff, bt sometimes LSB is dX uint8_t type2; // in range c9 - d1? can be different for same sample data uint8_t marker3; // always 9f, or 0x80 | 0x1f? uint16_t version; //? 1 or 0, maybe looped flag? not same sample data uint32_t oSample; // offset to sample from start of sample bank uint32_t sampleID; // I think it's the index in the sample bank... } ''' regions = [] while(f.tell() 127: event.velocity = 127 messageToAdd = mido.Message('note_on',channel=event.channel-1, note=event.pitch, velocity=event.velocity) if(delayTime > 0): messageToAdd.time = delayTime delayTime = 0 track.append(messageToAdd) elif event.type == music21.midi.ChannelVoiceMessages.CHANNEL_KEY_PRESSURE: messageToAdd = mido.Message('note_off',channel=event.channel-1, note=event.data, velocity=0) if(delayTime > 0): messageToAdd.time = delayTime delayTime = 0 track.append(messageToAdd) elif event.type == music21.midi.ChannelVoiceMessages.PROGRAM_CHANGE: messageToAdd = mido.Message('program_change',channel=event.channel-1, program=event.data) if(delayTime > 0): messageToAdd.time = delayTime delayTime = 0 track.append(messageToAdd) mid.save(f"{sbv2[3].decode('utf-8')}.mid")