Last active
November 5, 2022 09:12
-
-
Save bbbradsmith/4bc17ae16b10a9be03e80addfbea5009 to your computer and use it in GitHub Desktop.
Utility to convert NSFe music files into the backward compatible NSF2 with metadata format.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import sys | |
assert sys.version_info[0] >= 3, "Python 3 required." | |
# | |
# nsfe_to_nsf2.py | |
# Brad Smith, 2018-08-24 | |
# | |
# This converts an NSFe file to a preliminary "NSF2 with metadata" format. | |
# | |
import struct | |
in_nsfe = "in.nsfe" | |
out_nsf = "out.nsf" | |
show_chunks = True | |
def nsfe_to_nsf2(nsfe): | |
# NSF structure to fill in | |
nsf_header = bytearray([0]*0x80) | |
nsf_data = bytearray() | |
nsf_suffix = bytearray() | |
info = False | |
data = False | |
# build default NSF header info | |
nsf_header[0:5] = b"NESM\x1A" | |
nsf_header[5] = 1 # version | |
nsf_header[0x6E:0x70] = struct.pack("<H",16639) # NTSC speed | |
nsf_header[0x78:0x7A] = struct.pack("<H",19997) # PAL speed | |
# parse NSFe header | |
if len(nsfe) < 4: | |
raise Exception("Does not contain header.") | |
if nsfe[0:4] != b"NSFE": | |
raise Exception("Does not begin with 'NSFE' fourCC.") | |
nsfe = nsfe[4:] | |
# parse NSFe chunks | |
while len(nsfe) > 0: | |
if len(nsfe) < 8: | |
raise Exception("Malformed chunk, does not have 8 bytes for size/fourCC.") | |
size = struct.unpack("<L",nsfe[0:4])[0] | |
fourcc = nsfe[4:8] | |
if show_chunks: | |
print ("'%c%c%c%c' (%d bytes)" % (fourcc[0],fourcc[1],fourcc[2],fourcc[3],size)) | |
if (size+8) > len(nsfe): | |
raise Exception("EOF reached in the middle of a chunk. Incomplete file?") | |
# parse chunk | |
raw_chunk = nsfe[0:8+size] # chunk with header | |
chunk = raw_chunk[8:] # chunk without header | |
if fourcc == b"NEND": | |
nsf_suffix += raw_chunk # append this chunk | |
break # stop parsing here (NEND signals end) | |
elif fourcc == b"INFO": | |
# parse NSFe INFO | |
if size < 9: | |
raise Exception("INFO chunk must be at least 9 bytes.") | |
nsfe_load = chunk[0:2] | |
nsfe_init = chunk[2:4] | |
nsfe_play = chunk[4:6] | |
nsfe_reg = chunk[6] | |
nsfe_exp = chunk[7] | |
nsfe_songs = chunk[8] | |
nsfe_start = 0 | |
if size >= 10: | |
nsfe_start = chunk[9] | |
info = True | |
# fill NSF header | |
nsf_header[0x08:0x0A] = nsfe_load | |
nsf_header[0x0A:0x0C] = nsfe_init | |
nsf_header[0x0C:0x0E] = nsfe_play | |
nsf_header[0x7A] = nsfe_reg | |
nsf_header[0x7B] = nsfe_exp | |
nsf_header[0x06] = nsfe_songs | |
nsf_header[0x07] = (nsfe_start+1) & 255 | |
elif fourcc == b"DATA": | |
nsf_data = bytearray(chunk) | |
data = True | |
elif fourcc == b"BANK": | |
for i in range(0,min(8,size)): | |
nsf_header[0x70+i] = chunk[i] | |
elif fourcc == b"RATE": | |
if size >= 2: | |
nsf_header[0x6E:0x70] = chunk[0:2] # NTSC rate | |
if size >= 4: | |
nsf_header[0x78:0x7A] = chunk[2:4] # PAL rate | |
if size >= 6: | |
nsf_suffix += raw_chunk # append to pass Dendy rate | |
elif fourcc == b"NSF2": | |
nsf_header[5] = 2 # version | |
if size > 0: | |
nsf_header[0x7E] = chunk[0] # pass NSF2 bitfield | |
elif fourcc == b"auth": | |
append = False | |
ci = 0 | |
oi = 0 | |
for ci in range(ci,size): | |
c = chunk[ci] | |
if c != 0: | |
if (oi < 31): | |
nsf_header[0x0E+oi] = c | |
oi += 1 | |
else: | |
append = True | |
else: | |
break | |
ci += 1 | |
oi = 0 | |
for ci in range(ci,size): | |
c = chunk[ci] | |
if c != 0: | |
if (oi < 31): | |
nsf_header[0x2E+oi] = c | |
oi += 1 | |
else: | |
append = True | |
else: | |
break | |
ci += 1 | |
oi = 0 | |
for ci in range(ci,size): | |
c = chunk[ci] | |
if c != 0: | |
if (oi < 31): | |
nsf_header[0x4E+oi] = c | |
oi += 1 | |
else: | |
append = True | |
else: | |
break | |
ci += 1 | |
if size > ci: | |
append = True # ripper name as well | |
if append: # these strings won't fit | |
nsf_suffix += raw_chunk | |
else: # other/unknown chunk | |
nsf_suffix += raw_chunk # append the chunk as-is | |
nsfe = nsfe[8+size:] # next chunk | |
if info == False: | |
raise Exception("No INFO chunk found?") | |
if data == False: | |
raise Exception("No DATA chunk found?") | |
# data length, offset to NSFe suffix | |
nsf_header[0x7D:0x80] = struct.pack("<L",len(nsf_data))[0:3] | |
return nsf_header + nsf_data + nsf_suffix | |
print("Reading " + in_nsfe + "...") | |
nsfe = open(in_nsfe,"rb").read() | |
nsf = nsfe_to_nsf2(nsfe) | |
print("Saving " + out_nsf + "...") | |
open(out_nsf,"wb").write(nsf) | |
print ("Done.") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment