
#YM FILE OPERATIONS
# by John Talbert,  Aug 2022
 
print()
print("First make sure your YM file is uncompressed from its lha format,")
print("--by Haruyazu Yoshizaki. Use Terminal command - lha e music.ym")
print(" or an app like Incredible Bee's Archiver for the Mac")
print()
print("Please enter your full YM file address below, or just the", )
print("name if it is placed in the same folder as this program.")
fileName = input(" FILE = ")
print("Thanks. ", fileName, "will be used" )
print()
 
try:
    with open(fileName, "rb") as f:
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# UNCOMPRESSED .BIN FILE HEADER PRINTOUT  
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~      
# File ID = YM5! ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        id = bytearray()
        i = 0
        while i <= 3:
            id+=f.read(1)
            i += 1    
        #print(id) 
        print("id =", id.decode('ASCII'))
 
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~        
# File Check Sring = LeOnArD! ~~~~~~~~~~~~~~~~~~~~~~
        check = bytearray()
        i = 0
        while i <= 7:
            check+=f.read(1)
            i += 1   
        #print(check) 
        print("check =", check.decode('ASCII'))
 
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~        
# Number of Register Frames (affects length of song = nbframes*frame_rate_HZ)
        nbframes = bytearray()
        i = 0
        while i <= 3:
            nbframes+=f.read(1)
            i += 1   
        #print([hex(c) for c in nbframes]) 
        number_frames = int.from_bytes(nbframes, "big", signed="False")
        print("nbframes =",number_frames)
 
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~        
# Attributes = 1 (indicating interleaved format) ~~~
        attributes = bytearray()
        i = 0
        while i <= 3:
            attributes+=f.read(1)
            i += 1   
        print("song attributes =", [hex(c) for c in attributes]) 
        #print(int.from_bytes(attributes, "big", signed="False"))
 
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~        
# Number of DIGIDRUM Sample Files = 0 (not implemented) 
        nbdigidrums = bytearray()
        i = 0
        while i <= 1:
            nbdigidrums+=f.read(1)
            i += 1   
        #print([hex(c) for c in nbdigidrums]) 
        print("nb digidrums =", int.from_bytes(nbdigidrums, "big", signed="False"))
 
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~        
# Processor Master Clock = 2MHz (affects voice pitch) 
        clockHZ = bytearray()
        i = 0
        while i <= 3:
            clockHZ+=f.read(1)
            i += 1   
        print("master clock HZ =", int.from_bytes(clockHZ, "big", signed="False"))
 
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~        
# Frame Rate = 50Hz (speed of frame loads to synth, affects tempo) 
        frame_rate_HZ = bytearray()
        i = 0
        while i <= 1:
            frame_rate_HZ+=f.read(1)
            i += 1   
        print("frame_rate HZ =", int.from_bytes(frame_rate_HZ, "big", signed="False"))
 
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~        
# Loop Frame = ?? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        loop_frame = bytearray()
        i = 0
        while i <= 3:
            loop_frame+=f.read(1)
            i += 1   
        print("loop_frame =", [hex(c) for c in loop_frame]) 
        #print(int.from_bytes(loop_frame, "big", signed="False"))
 
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~        
# NData = 0 (future uses?) ~~~~~~~~~~~~~~~~~~~~~~~~~
        nbdata = bytearray()
        i = 0
        while i <= 1:
            nbdata+=f.read(1)
            i += 1   
        print("nbdata =", int.from_bytes(nbdata, "big", signed="False"))
 
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~        
 
# Here the file would provide Digidrum samples if nbdigidrum is not zero.  
# If used, nbdigidrum = number of digidrum samples,
# Each with a SampleSize (32 bit integer, Big Endian format)
# followed by that many 8-bit data samples.
# Not implemented here (nested while loop with nbdigidrum and Samplesize)
 
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~        
# Extra Info String 1 (Song Name, Author, Year, etc.) 
        file_info1 = bytearray()
        file_info1 = f.read(1)
        while file_info1[-1] != 0:  # stop at string null at end of line
            file_info1+=f.read(1)
        # print([hex(c) for c in file_info1]) 
        file_info1 = file_info1[:-1]  #remove end of line 0
        print()
        print("file_info1 =", file_info1.decode('ASCII')) 
 
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~        
# Extra Info String 2 (Song Name, Author, Year, etc.) 
        file_info2 = bytearray()
        file_info2 = f.read(1)
        while file_info2[-1] != 0:  # stop at string null at end of line
            file_info2+=f.read(1)
        # print([hex(c) for c in file_info2]) 
        file_info2 = file_info2[:-1]  #remove end of line 0
        print("file_info2 =", file_info2.decode('ASCII')) 
 
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~        
# Extra Info String 3 (Song Name, Author, Year, etc.) 
        file_info3 = bytearray()
        file_info3 = f.read(1)
        while file_info3[-1] != 0:  # stop at string null at end of line
                file_info3+=f.read(1)
        # print([hex(c) for c in file_info3])
        file_info3 = file_info3[:-1]  #remove end of line 0
        print("file_info3 =", file_info3.decode('ASCII')) 
        print()
 
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  
# 
# The AY synthesizer has 14 out of 16 registers that set its sound output parameters.
# Each set of 14 8-bit registers is a data "frame" to be loaded into the AY synth
# every 50msec (frame_rate).  Here, in the file, is a sequence of data frames.
# If the lowest bit of "attributes" is 1, these frames are interleaved 
# to allow for better file compression (with lha) -- "number_frames" of r0 registers
# are followed by "number_frames" of r1 registers, and so on up to r15.  
# What follows are instructions to de-interleave the data into r0, r1, ... r15 data frames
# and to build a new .reg file of "number_frames" consecutive 14-register frames.
 
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# CREATE AND FILL 16 REGISTER ARRAYS FROM YM.BIN FILE
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
        def fill_reg_array(rx):
                i = 1
                while i <= number_frames:
                        rx+=f.read(1)
                        i += 1
 
        r0 = bytearray() #Period Voice A
        fill_reg_array(r0)
        r1 = bytearray() #Fine Period Voice A
        fill_reg_array(r1)
        r2 = bytearray() #Period Voice B
        fill_reg_array(r2)
        r3 = bytearray() #Fine Period Voice B
        fill_reg_array(r3)
        r4 = bytearray() #Period Voice C
        fill_reg_array(r4)
        r5 = bytearray() #Fine Period Voice C
        fill_reg_array(r5)
        r6 = bytearray() #Noise Period
        fill_reg_array(r6)
        r7 = bytearray() #Mixer Control
        fill_reg_array(r7)
        r8 = bytearray() #Volume Control Voice A
        fill_reg_array(r8)
        r9 = bytearray() #Volume Control Voice B
        fill_reg_array(r9)
        r10 = bytearray() #Volume Control Voice C
        fill_reg_array(r10)
        r11 = bytearray() #Envelope High Period (not used)
        fill_reg_array(r11)
        r12 = bytearray() #Envelope Low Period (not used)
        fill_reg_array(r12)
        r13 = bytearray() #Envelope Shape (not used)
        fill_reg_array(r13)
        r14 = bytearray() #Extended Data (not used)
        fill_reg_array(r14)
        r15 = bytearray() #Extended Data (not used)
        fill_reg_array(r15)
 
        print("r0 through r15 read done")
        print()
 
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# WRITE NEW DE-INTERLEAVED REGISTER FILE
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
        x = fileName.find(".")
        new_fileName = fileName[:x] + ".reg"
        print("New Register file is", new_fileName)
 
        f1 = open(new_fileName, 'wb')
 
        i = 0
        while i <= (number_frames -1):
                f1.write(r0[i:(i+1)])
                f1.write(r1[i:(i+1)])
                f1.write(r2[i:(i+1)])
                f1.write(r3[i:(i+1)])
                f1.write(r4[i:(i+1)])
                f1.write(r5[i:(i+1)])
                f1.write(r6[i:(i+1)])
                f1.write(r7[i:(i+1)])
                f1.write(r8[i:(i+1)])
                f1.write(r9[i:(i+1)])
                f1.write(r10[i:(i+1)])
                f1.write(r11[i:(i+1)])
                f1.write(r12[i:(i+1)])
                f1.write(r13[i:(i+1)])
                              
                i += 1
 
        print(new_fileName, "write finished")
        print()
        f1.close()
        
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# WRITE FORTH SOURCE (.fs) FILE FOR ESP32FORTH
# Sample output for one frame, 10 bytes, of AY data
# $00 C, $0A C, $00 C, $08 C, $00 C, $06 C, $1F C, $38 C, $0F C, $0F C, 
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 
        fs_fileName = fileName[:x] + ".fs"
        print("New Forth source file is", fs_fileName)
 
        f2 = open(fs_fileName, 'w')
        f2.write("CREATE " + fileName[:x].replace(" ", "_").replace("-", "_") + "-song\n")
 
        i = 0
        while i <= (number_frames - 1):
            line = ""
            for r in (r0, r1, r2, r3, r4, r5, r7, r8, r9, r10):
                line += "$%02X C, " % r[i]
            f2.write(line + "\n")  # end of line
            i += 1
 
        # Define a forth word that returns the frame count as a constant
        f2.write(": " + fileName[:x].replace(" ", "_").replace("-", "_") + "-frames "
                  + str(number_frames) + " ;\n")
        f2.close()
 
        print(fs_fileName, "write finished")
        print()
 
        # File End = End! ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        end = bytearray()
        i = 0
        while i <= 3:
            end+=f.read(1)
            i += 1    
        #print(end) 
        print("end =", end.decode('ASCII'))
 
except IOError:
    print('Error While Opening the file!')
 
 