#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 from collections import namedtuple #named tuple definitions #struct type I I I I I I I I MUS = namedtuple('MUS','version subversion minorversion something soundBankStart soundBankSize sequenceDataStart s3') MUSStructString= "8I" #struct type 4s I I 4s I H H H H I I I I I I I 12s H H H H I SBV2 = namedtuple('SBV2', 'signature version hversion name a b nMap nSMap nSample f instrumentOffset regionOffset i soundBankSize k l extname m n o p q') SBV2StructString = "4s2I4sI4H7I12s4HI" #struct type B B H I Instrument = namedtuple('Instrument','regionCount volume zero regionOffset') InstrumentStructString = "2BHI" ''' 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... } ''' #struct type B B B B h B B H B B B B H I I Region = namedtuple('Region','type volume1 a b pan c volume2 zero d e f g loopFlag sampleOffset sampleId') RegionStructString = "=BBBBhBBHBBBBHII" #struct type I I I I SEQ = namedtuple('SEQ','version subversion minorversion sequenceDataSize') SEQStructString = "4I" #struct type 4s H H I I 4s I I I I I H B B MID = namedtuple('MID','signature a b c d name e midiDataOffset g midiTempo division zero trackIndex trackCount') MIDStructString = "4s2H2I4s5IH2B" #struct type 4s H B B 4s I MMID = namedtuple('MMID','signature type a trackCount name zero') MMIDStructString = "4sH2B4sI" 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 MUS files") parser.add_argument("-i", dest="filepath", required=True, help="Input path to JAK MUS 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: MUSInfo = MUS._make(struct.unpack(MUSStructString,f.read(32))) print(f"SoundBank Start: {MUSInfo.soundBankStart}") print(f"Sequence Start: {MUSInfo.sequenceDataStart}") sbv2Start = f.tell() sbv2Info = SBV2._make(struct.unpack(SBV2StructString,f.read(80))) instrumentStart = sbv2Info.instrumentOffset + sbv2Start regionStart = sbv2Info.regionOffset + sbv2Start print(MUSInfo) print(sbv2Info) f.seek(instrumentStart) instruments = [] while(f.tell() < regionStart): instrument = Instrument._make(struct.unpack(InstrumentStructString,f.read(8))) instruments.append(instrument) print("INSTRUMENTS") for instrument in instruments: print(instrument) 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"{sbv2Info.name.decode('utf-8')}.mid")