Exemplo n.º 1
0
def sfx2info(romfile, musyx_position, sfxnames):
    #Compatible with MusyX > GM2SONG 1.03 and possibly other versions
    #
    #Arguments:
    #   romfile = rom file path
    #   musyx_position = position in the file of MusyX.
    #       MusyX is placed at the beginning of a rom bank chosen by the original developer
    #       e.g. in Magi Nation, MusyX is placed at rom bank 0x30
    #   sfxnames = An empty array, or an array of sfx names that will be used as savefiles
    with open(romfile, 'rb') as f:
        rom = f.read()

    projectdata_pos = musyx_position + snd_ProjectData - 0x4000
    sfxtable_address = projectdata_pos + littledata_to_word(
        rom, projectdata_pos + sdp_SFXTableAddress)
    sfxtable_len = rom[projectdata_pos + sdp_NumberOfSFXs]

    targetdir_base = "python/out/"
    os.makedirs(targetdir_base, exist_ok=True)

    with open(targetdir_base + "sfxinfo.txt", "w") as f:
        f.write("{:32} - {:3} {:3} {:3} {:3}\n".format("Name", "Macro", "Pri",
                                                       "Key", "Vel"))
        for i in range(sfxtable_len):
            if (i < len(sfxnames)):
                name = "sfx_{:03}_{}".format(transform_id(i, ID_SFX),
                                             sfxnames[i])
            else:
                name = "sfx_{:03}".format(transform_id(i, ID_SFX))
            print(name)

            pos = sfxtable_address + i * 4

            index = transform_id(rom[pos], ID_MACRO)
            priority = rom[pos + 1]
            defkey = rom[pos + 2]
            defvel = rom[pos + 3] * 8

            f.write("{:32} - {:3} {:3} {:3} {:3}\n".format(
                name, index, priority, defkey, defvel))
Exemplo n.º 2
0
def adsr2mxt(romfile, musyx_position, adsrnames):
    #Designed for MUConv.exe v1.04
    #Takes rom data and converts back to the equivalent mxt files
    #The generated mxt files should compile back into exactly the same data
    #That being said, some parameters could be slightly different from the original mxt file as multiple mxt values sometimes round to the same data when parsed
    #Arguments:
    #   romfile = rom file path
    #   musyx_position = position in the file of MusyX.
    #       MusyX is placed at the beginning of a rom bank chosen by the original developer
    #       e.g. in Magi Nation, MusyX is placed at rom bank 0x30
    #   adsrnames = An empty array, or an array of adsr names that will be used as savefiles
    with open(romfile, 'rb') as f:
        rom = f.read()

    projectdata_pos = musyx_position + snd_ProjectData - 0x4000
    adsrtable_address_pos = projectdata_pos + sdp_ADSRTableAddress
    sfx_address_pos = projectdata_pos + sdp_SFXTableAddress
    adsrbase_offset = littledata_to_word(rom, adsrtable_address_pos)
    sfxbase_offset = littledata_to_word(rom, sfx_address_pos)
    if (adsrbase_offset == sfxbase_offset):
        print("There seems to be 0 ADSR entries. Aborting")
        return
    adsrbase_pos = adsrbase_offset + projectdata_pos

    targetdir = "python/out/Tables/"
    os.makedirs(targetdir, exist_ok=True)

    i = 0
    adsrs = []
    while True:
        if (i < len(adsrnames)):
            name = "adsr_{:03}_{}".format(transform_id(i, ID_ADSR),
                                          adsrnames[i])
        else:
            name = "adsr_{:03}".format(transform_id(i, ID_ADSR))
        print(name)
        newaddress = littledata_to_word(rom, adsrbase_pos + i * 2)
        data_pos = newaddress + adsrbase_pos
        if (i == 0):
            adsrtable_endpos = newaddress + adsrbase_pos

        with open(targetdir + name + ".mxt", "wb") as f:
            data = [
                littledata_to_word(rom, data_pos + 0),
                littledata_to_word(rom, data_pos + 2), rom[data_pos + 4],
                littledata_to_word(rom, data_pos + 5)
            ]
            for d in [1, 3]:  #Signed values
                if (data[d] >= 256**2 // 2):
                    data[d] = data[d] - 256**2
            attack = _solve_cycles(math.floor(3840 / (data[0] - 0.5)))
            decay = _solve_cycles((-1) * (0x0F - data[2]) * 0x100 // data[1])
            sustain = math.ceil((data[2] - 0.5) / 0.003662109375)
            release = _solve_cycles((-1) * data[2] * 0x100 // data[3])
            f.write(twobytearray(attack))
            f.write(twobytearray(decay))
            f.write(twobytearray(sustain))
            f.write(twobytearray(release))

        i += 1
        if (adsrbase_pos + i * 2 == adsrtable_endpos) or (
                i > 256):  #Quit when you reach the address of the first adsr
            break
Exemplo n.º 3
0
def song2wav(songfile_name, musyx_position, songnames, debug=False):
    #Compatible with MusyX > GM2SONG 1.03 and possibly other versions
    #
    #Arguments:
    #   songfile_name = rom file path
    #   musyx_position = position in the file of MusyX.
    #       MusyX is placed at the beginning of a rom bank chosen by the original developer
    #       e.g. in Magi Nation, MusyX is placed at rom bank 0x30
    #   songnames = An empty array, or an array of song names that will be used as savefiles
    with open(songfile_name, 'rb') as f:
        songfile = f.read()
    projectdata_pos = musyx_position + snd_ProjectData - 0x4000

    songtable_pos = projectdata_pos + littledata_to_word(
        songfile, projectdata_pos + sdp_SongTableAddress
    )  #data_to_word(songfile[projectdata_pos+sdp_SongTableAddress:projectdata_pos+sdp_SongTableAddress+2])

    number_of_songs = songfile[projectdata_pos + sdp_NumberOfSongs]
    print("{} songs".format(number_of_songs))
    last_songhash = None
    songlist_i = -1

    os.makedirs(targetdir, exist_ok=True)

    for i in range(number_of_songs - 1, -1, -1):
        if (i < len(songnames)):
            name = "song_{:03}_{}".format(transform_id(i, ID_SONG),
                                          songnames[i])
        else:
            name = "song_{:03}".format(transform_id(i, ID_SONG))
        print(name)

        songlookup_pos = songtable_pos + 3 * i
        relativebank = songfile[songlookup_pos]
        address = +littledata_to_word(
            songfile, songlookup_pos +
            1)  #data_to_word(songfile[songlookup_pos+1:songlookup_pos+3])
        song_pos = musyx_position + relativebank * 0x4000 + address - 0x4000 + 0x84
        filesize, sha = song2gm(songfile_name, song_pos, name + ".mid")

        if (debug):
            with open(targetdir + name + "_ori.dat", "wb") as f:
                f.write(songfile[song_pos:song_pos + filesize])
        else:
            os.remove(targetdir + name + "_new.dat")
        if (hashlib.sha256(songfile[song_pos:song_pos + filesize]).digest() !=
                sha):
            print("NOT IDENTICAL")

        song_pos = musyx_position + relativebank * 0x4000 + address - 0x4000  #header info
        defaults = [
            transform_id(songfile[song_pos + j], ID_MACRO) for j in range(4)
        ]
        song_pos += 4
        soundlist = [
            transform_id(songfile[song_pos + j], ID_MACRO) for j in range(0x80)
        ]
        soundhash = hashlib.sha256(bytes(soundlist)).hexdigest()
        if soundhash != last_songhash:
            songlist_i += 1
            last_songhash = soundhash

            nullprg = transform_id(0, ID_MACRO)
            soundlist_2 = soundlist.copy()
            for j in range(len(soundlist_2)):
                if (soundlist_2[j] == nullprg):
                    soundlist_2[j] = "{:3} or undefined".format(nullprg)
            with open(
                    targetdir_base + "soundlist_{:02}.txt".format(songlist_i),
                    "w") as f:
                for j in range(len(soundlist_2)):
                    f.write("Soundlist {} .mxm id: {:3}\n".format(
                        j + 1, soundlist_2[j]))
                f.write("\n\n")
        with open(targetdir_base + "soundlist_{:02}.txt".format(songlist_i),
                  "a") as f:
            f.write(name + "\n")
            for j in range(len(defaults)):
                success = False
                for k in range(len(soundlist)):
                    if (defaults[j] == soundlist[k]):
                        f.write("MidiSetup Channel {} Pgr.: {:3}\n".format(
                            j + 1, k + 1))
                        success = True
                        break
                if (not (success)):
                    f.write("MidiSetup Channel {} Pgr.: UNKNOWN?\n".format(j +
                                                                           1))
            f.write("\n")
Exemplo n.º 4
0
def macro2mxm(romfile, musyx_position, macronames):
    #Designed for MUConv.exe v1.04
    #Takes rom data and converts back to the equivalent mxm files
    #The generated mxm files should compile back into exactly the same data
    #That being said, some parameters could be slightly different from the original mxm file as multiple mxm values sometimes round to the same data when parsed
    #Arguments:
    #   songfile_name = rom file path
    #   musyx_position = position in the file of MusyX.
    #       MusyX is placed at the beginning of a rom bank chosen by the original developer
    #       e.g. in Magi Nation, MusyX is placed at rom bank 0x30
    #   macronames = An empty array, or an array of macro names that will be used as savefiles
    with open(romfile, 'rb') as f:
        rom = f.read()

    projectdata_pos = musyx_position + snd_ProjectData - 0x4000
    macrobase_pos = projectdata_pos + sdp_SoundMacroLookupTable
    macrosamplemap_pos = projectdata_pos + sdp_SampleMapMacro_Address
    macrosamplemap_n_pos = projectdata_pos + sdp_NumberOfSampleMapEntries
    samplemap_pos = littledata_to_word(
        rom, macrosamplemap_pos) - sdp_SoundMacroLookupTable
    samplemap_n = rom[macrosamplemap_n_pos]
    samplemap_found = False

    i = 0
    macros = []
    while True:
        samplemap_entries = 0
        newaddress = littledata_to_word(rom, macrobase_pos + i * 2)
        if (i == 0):
            macrotable_endpos = newaddress + macrobase_pos
        if (newaddress == samplemap_pos):
            samplemap_entries = samplemap_n
            samplemap_found = True
        macros.append(
            SoundMacro(musyx_position, newaddress, samplemap_entries, i))
        i += 1
        if (macrobase_pos + i * 2 == macrotable_endpos) or (
                i > 256):  #Quit when you reach the address of the first macro
            break

    for i in range(len(macros)):
        if (i < len(macronames)):
            name = "macro_{:03}_{}".format(transform_id(i, ID_MACRO),
                                           macronames[i])
        else:
            name = "macro_{:03}".format(transform_id(i, ID_MACRO))
        macros[i].name = name
        macros[i].init_steps(rom)

    for i in range(len(macros)):
        macros[i].mxm_steps(macros)

    targetdir_base = "python/out/"
    targetdir = "python/out/Soundmacros/"
    os.makedirs(targetdir, exist_ok=True)
    for macro in macros:
        print(macro.name)
        with open(targetdir + macro.name + ".mxm", "wb") as f:
            for step in macro.steps:
                f.write(step.mxm)
    with open(targetdir_base + "macrodebug.txt", "w") as f:
        for macro in macros:
            f.write("{:16} {:06X}\n".format(
                macro.name, macro.address + sdp_SoundMacroLookupTable))
            for step in macro.steps:
                f.write(step.debug())
            f.write("\n\n")
Exemplo n.º 5
0
    def create_mxm(self, macros):
        if (self.samplemap):
            self.store(0x24, 0)
            id = self.val(0)
            id = transform_id(id, ID_SAMPLE)
            self.store(id, 1, 2)
        else:
            opcode = self.val(0)

            command_lengths.pop(opcode, None)

            if (opcode == 0x00):  #END
                self.store(0x00, 0)
            elif (opcode == 0x23):  #STOP
                self.store(0x01, 0)
            elif (opcode == 0x15):  #SPLITKEY
                self.store(0x02, 0)
                key = self.val(1)
                self.store(key, 1)
                id, step = self.solve_address(macros, 2)
                id = transform_id(id, ID_MACRO)
                self.store(id, 2, 2)
                self.store(step, 4, 2)
            elif (opcode == 0x17):  #SPLITVEL
                self.store(0x03, 0)
                vel = self.val(1)
                self.store(vel, 1)
                id, step = self.solve_address(macros, 2)
                id = transform_id(id, ID_MACRO)
                self.store(id, 2, 2)
                self.store(step, 4, 2)
            elif (opcode == 0x20):  #RESET_MOD
                self.store(0x04, 0)
            elif (opcode == 0x05):  #LOOP
                self.store(0x05, 0)
                times = self.val(1)
                self.store(times, 6)
                step = self.solve_relative(macros, self.parent_index,
                                           self.step, 2)
                self.store(step, 4, 2)
            elif (opcode == 0x06):  #GOTO
                self.store(0x06, 0)
                id, step = self.solve_address(macros, 1)
                id = transform_id(id, ID_MACRO)
                self.store(id, 2, 2)
                self.store(step, 4, 2)
            elif (opcode == 0x04):  #WAIT
                self.store(0x07, 0)
                bool1 = self.val(1)
                self.store(bool1, 1)
                bool2 = self.val(2)
                self.store(bool2, 2)
                cycles = self.val(3, 2)
                if (cycles == 0x0001 and bool2 == 1):
                    milli = 0xFFFF
                elif (cycles == 0x0000):
                    milli = 0xFFFF
                else:
                    milli = self.solve_cycles(3) - 1
                self.store(milli, 6, 2)
            elif (opcode == 0x26):  #PLAYMACRO
                self.store(0x08, 0)
                voice = self.val(1)
                if (voice != 0xFF):
                    voice = voice & 0b11
                self.store(voice, 1)
                id = self.val(2)
                id = transform_id(id, ID_MACRO)
                self.store(id, 2, 2)
                bool = self.bit(1, 7)
                self.store(bool, 4)
            elif (opcode == 0x22):  #PLAYKEYSAMPLE
                self.store(0x09, 0)
            elif (opcode == 0x1F):  #STOP_MOD
                self.store(0x0A, 0)
            elif (opcode == 0x0E):  #SETVOICE
                self.store(0x0B, 0)
                voice = self.val(1)
                if (voice == 0xFF):
                    self.store(0xFF, 1)
                    self.store(0, 4)
                    self.store(0, 5)
                else:
                    voice = voice & 0b11
                    macros[
                        self.
                        parent_index].predictedvoice = voice  # Set the predicted voice to discriminate between VOICE_ON and WAVE_ON
                    self.store(voice, 1)
                    bool1 = self.bit(1, 7)
                    self.store(bool1, 4)
                    bool2 = self.bit(1, 4)
                    self.store(bool2, 5)
            elif (opcode == 0x0F):  #SETADSR
                self.store(0x0C, 0)
                id = self.val(1)
                id = transform_id(id, ID_ADSR)
                self.store(id, 1, 2)
            elif (opcode == 0x09):  #SETVOLUME
                self.store(0x0D, 0)
                vol = self.solve_volume(1)
                self.store(vol, 1)
            elif (opcode == 0x0A):  #PANNING
                self.store(0x0E, 0)
                pan = self.val(1)
                if (pan == 0):
                    self.store(0, 1)
                elif (pan == 1):
                    self.store(64, 1)
                elif (pan == 2):
                    self.store(127, 1)
                else:
                    raise ValueError
            elif (opcode == 0x11):  #ENVELOPE (descending)
                self.store(0x0F, 0)
                self.store(0, 1)
                x = int(0x0F00 / self.val(1, 2, True)) * (-1)
                milli = self._solve_cycles(x)
                self.store(milli, 6, 2)
            elif (opcode == 0x27):  #ENVELOPE (ascending)
                self.store(0x0F, 0)
                self.store(1, 1)
                x = int(0x0F00 / self.val(1, 2, True)) * (1)
                milli = self._solve_cycles(x)
                self.store(milli, 6, 2)
            elif (opcode == 0x21):  #STARTSAMPLE
                self.store(0x10, 0)
                id = self.val(1)
                id = transform_id(id, ID_SAMPLE)
                self.store(id, 1, 2)
            elif (opcode == 0x0D):  #VOICE_OFF
                self.store(0x11, 0)
            elif (opcode == 0x12):  #KEYOFF
                self.store(0x12, 0)
                voice = self.val(1)
                self.store(voice, 1)
            elif (opcode == 0x16):  #SPLITRND
                self.store(0x13, 0)
                rand = self.val(1)
                self.store(rand, 1)
                id, step = self.solve_address(macros, 2)
                id = transform_id(id, ID_MACRO)
                self.store(id, 2, 2)
                self.store(step, 4, 2)
            elif (opcode == 0x0C):
                if (macros[self.parent_index].predictedvoice == 2):  #WAVE_ON
                    self.store(0x26, 0)
                    id = self.val(1)
                    if (id == 0xFF):
                        self.store(0, 1)
                    else:
                        id = transform_id(id, ID_SAMPLE)
                        self.store(id, 1, 2)
                elif (macros[self.parent_index].predictedvoice
                      in [0, 1, 3]):  #VOICE_ON
                    self.store(0x14, 0)
                    duty = self.val(1)
                    self.store(duty, 1)
                else:
                    print(
                        "Macro {:03} - guessing step {} to be VOICE_ON, but it could also be WAVE_ON"
                        .format(self.parent_index, self.step))
                    self.store(0x14, 0)
                    duty = self.val(1)
                    self.store(duty, 1)
            elif (opcode == 0x10):  #SETNOISE
                self.store(0x15, 0)
                macros[
                    self.
                    parent_index].predictedvoice = 3  # Set the predicted voice
                byte = self.val(1)
                clock = (byte & 0b11110000) >> 4
                self.store(clock, 1)
                step = (byte & 0b00001000) >> 3
                self.store(step, 2)
                freq = byte & 0b00000111
                self.store(freq, 3)
            elif (opcode == 0x1B):  #PORTLAST
                self.store(0x16, 0)
                key, cent = self.solve_keycents(1)
                self.store(key, 1)
                self.store(cent, 2)
                milli = solve_cycles(3)
                self.store(milli, 6)
            elif (opcode == 0x0B):  #RNDNOTE
                self.store(0x17, 0)
                bool1 = self.bit(1, 7)
                self.store(bool1, 4)
                bool2 = self.bit(1, 0)
                self.store(bool2, 5)
                note1 = self.val(2)
                self.store(note1, 1)
                note2 = self.val(3)
                self.store(note2, 3)
                cent = self.solve_cents(4)  #cent is always positive
                self.store(cent, 2)
            elif (opcode == 0x08):  #ADDNOTE
                self.store(0x18, 0)
                bool = 1 - self.val(1)
                self.store(bool, 3)
                add = self.val(2, 1, True)
                self.store(add, 1)
                key, cent = self.solve_keycents(
                    2
                )  #technically key is undefined, but maybe cent derives its sign from add (i.e. maybe MusyX has an interpretation bug here - I'm not sure so I'll guess - maybe this is changed in more recent versions of MuConv)
                self.store(cent, 2)
            elif (opcode == 0x07):  #SETNOTE
                self.store(0x19, 0)
                key = self.val(1)
                self.store(key, 1)
                key, cent = self.solve_keycents(
                    1
                )  #technically key is undefined, but maybe cent derives its sign from key (i.e. maybe MusyX has an interpretation bug here - I'm not sure so I'll guess - maybe this is changed in more recent versions of MuConv)
                self.store(cent, 2)
            elif (opcode == 0x02):  #PORTAMENTO
                self.store(0x1B, 0)
                key, cent = self.solve_keycents(1)
                self.store(key, 1)
                self.store(cent, 2)
                milli = solve_cycles(3)
                self.store(milli, 6, 2)
                bool = self.val(5)
                self.store(bool, 3)
            elif (opcode == 0x01):  #VIBRATO
                self.store(0x1C, 0)
                x = self.val(3)
                milli = self._solve_cycles(x)
                milli *= 2
                self.store(milli, 6, 2)
                y_ddiv_x = self.val(1, 2, True)
                y = y_ddiv_x * x
                key, cent = self._solve_keycents(y)
                self.store(key, 1)
                self.store(cent, 2)
            elif (opcode == 0x03):  #PITCHSWEEP
                self.store(0x1D, 0)
                bool = self.bit(5, 7)
                self.store(bool, 5)
                key, cent = self.solve_keycents(3)
                self.store(key, 1)
                self.store(cent, 2)
                y = self.val(3, 2, True)
                y_ddiv_x = self.val(1, 2, True)
                x = int(y / y_ddiv_x)
                milli = self._solve_cycles(x)
                self.store(milli, 6, 2)
            elif (opcode == 0x13):  #HARDENVELOPE
                self.store(0x1E, 0)
                bool = self.bit(1, 3)
                self.store(bool, 1)
                x = self.val(1) & 0b111
                x = math.ceil((x - 0.5) / 0.0042666667141020298004150390625)
                self.store(x, 6, 2)
            elif (opcode == 0x1D):  #PWN_START
                macros[
                    self.
                    parent_index].predictedvoice = 2  # Set the predicted voice
                self.store(0x1F, 0)
                low = self.val(1)
                self.store(low, 1)
                high = self.val(2)
                self.store(high, 2)
                delta = (high - low) * 256
                x = self.val(3, 2)
                if (x == 0):
                    self.store(0, 3, 2)
                else:
                    cycle = int(delta / x)
                    milli = self._solve_cycles(cycle)
                    self.store(milli, 3, 2)
            elif (opcode == 0x1E):  #PWN_UPDATE
                macros[
                    self.
                    parent_index].predictedvoice = 2  # Set the predicted voice
                self.store(0x20, 0)  #same as PWN_START except for this line
                low = self.val(1)
                self.store(low, 1)
                high = self.val(2)
                self.store(high, 2)
                delta = (high - low) * 256
                x = self.val(3, 2)
                if (x == 0):
                    self.store(0, 3, 2)
                else:
                    cycle = int(delta / x)
                    milli = self._solve_cycles(cycle)
                    self.store(milli, 3, 2)
            elif (opcode == 0x24):  #PWN_FIXED
                macros[
                    self.
                    parent_index].predictedvoice = 2  # Set the predicted voice
                self.store(0x21, 0)
                duty = self.val(1)
                self.store(duty, 1)
            elif (opcode == 0x25):  #PWN_VELOCITY
                macros[
                    self.
                    parent_index].predictedvoice = 2  # Set the predicted voice
                self.store(0x22, 0)
            elif (opcode == 0x1A):  #SENDFLAG
                self.store(0x23, 0)
                flag = self.val(1)
                self.store(flag, 1)
                #SAMPLEMAP - see above
            elif (opcode == 0x28):  #CURRENTVOL
                self.store(0x25, 0)
                vol = self.solve_volume(1)
                self.store(vol, 1)
                #WAVE_ON - see above
            elif (opcode == 0x29):  #ADD_SET_PRIO
                self.store(0x27, 0)
                prio = self.val(2)
                self.store(prio, 1)
                bool = self.val(1)
                self.store(bool, 2)
            elif (opcode == 0x18):  #TRAP_KEYOFF
                self.store(0x28, 0)
                id, step = self.solve_address(macros, 1)
                id = transform_id(id, ID_MACRO)
                self.store(id, 2, 2)
                self.store(step, 4, 2)
            elif (opcode == 0x19):  #UNTRAP_KEYOFF
                self.store(0x19, 0)
            else:
                raise ValueError
Exemplo n.º 6
0
def sample2wav(romfile, musyx_position, samplenames):
    #Designed for MUConv.exe v1.04
    #Takes rom data and converts back to the equivalent .wav files
    #The generated .wav files should compile back into exactly the same data
    #That being said, some parameters could be slightly different from the original .wav file as multiple .wav values sometimes round to the same data when parsed
    #Arguments:
    #   romfile = rom file path
    #   musyx_position = position in the file of MusyX.
    #       MusyX is placed at the beginning of a rom bank chosen by the original developer
    #       e.g. in Magi Nation, MusyX is placed at rom bank 0x30
    #   samplenames = An empty array, or an array of sample names that will be used as savefiles
    with open(romfile, 'rb') as f:
        rom = f.read()

    projectdata_pos = musyx_position + snd_ProjectData - 0x4000

    sampletable_address_pos = projectdata_pos + sdp_SampleTableAddress
    sampletable_offset = littledata_to_word(rom, sampletable_address_pos)
    sampletable_pos = sampletable_offset + projectdata_pos

    sample_n_pos = projectdata_pos + sdp_NumberOfSamples
    sample_n = rom[sample_n_pos]

    targetdir = "python/out/Samples/"
    os.makedirs(targetdir, exist_ok=True)

    for i in range(sample_n):
        if (i < len(samplenames)):
            name = "sample_{:03}_{}".format(transform_id(i, ID_SAMPLE),
                                            samplenames[i])
        else:
            name = "sample_{:03}".format(transform_id(i, ID_SAMPLE))
        print(name)

        sample_address = littledata_to_word(rom, sampletable_pos + i * 6 + 0)
        sample_length = littledata_to_word(rom, sampletable_pos + i * 6 + 2)
        sample_quality = rom[sampletable_pos + i * 6 + 4]
        sample_bank = rom[sampletable_pos + i * 6 + 5]

        sample_pos = musyx_position + sample_address - 0x4000 + sample_bank * 0x4000

        f = wave.open(targetdir + name + ".wav", "wb")
        f.setparams((1, 2, 8192 if sample_quality else 1920,
                     sample_length * 32, 'NONE', 'not compressed'))

        bytearraylist = []
        datalength = 0

        for row in range(sample_length):
            data = rom[sample_pos + row * 0x10:sample_pos + row * 0x10 + 0x10]
            firstdatum = get_sample_datum(data, 0)
            if (firstdatum == 0x70):
                reps = data[1] + 1
                datalength += reps * 0x20
                bytearraylist.append(bytearray([0x00, 0x00] * 0x20 * reps))
            else:
                for j in range(0x20):
                    datum = get_sample_datum(data, j)
                    datum = (datum - 0x80) % 0x100
                    datalength += 1
                    bytearraylist.append(bytearray([0x00, datum]))

        f.writeframes(b''.join(bytearraylist))