# Concatenate two MuseScore3 .mscx files (scores) into album format. # Both scores must have same instrumentation and number of staves. # The staff layout and instrumentation of the first score is applied # to both scores. Any title-page text box(es) in the second score will # be deleted. # # The first file may itself be an .mscx album file produced by this # program; this allows for concatenation of three or more scores. # # The result is written out as an .mscx file which may be loaded # into MuseScore3 and written out in standard .mscz format. # # Written for python3. # Last mod: 2021 Dec 16 import re # regular expression module # Edit the lines below with the actual file paths/names: inputfile1 = '/home/homeuser/Desktop/OrchFragment3-MS3.mscx' inputfile2 = '/home/homeuser/Desktop/OrchFragment8-MS3.mscx' outputfile = '/home/homeuser/Desktop/album_temp.mscx' # find next line with string s in a score & return its line index, or -1 if not found def find_next(datalines, s, init_index): j = next((i for i in enumerate(datalines[init_index+1:-1]) if s in i[1]),[-1,-1])[0] if j < 0: return -1 return init_index+1+j # find the next '' and return the value and the line index # call with init_index = -1 or the returned index from the prev call # returns id, index as a tuple def find_next_staff_id(datalines, init_index): index = init_index+1+next((i for i in enumerate(datalines[init_index+1:-1]) if "Staff id" in i[1]),[-1,-1])[0] id = int(re.findall('\d+', datalines[index])[0]) return id, index # get the number of staves = largest staff id in a score = datalines # return (maxid, id, index) with maxid = number of staves, id = 1, # and index pointing to the '' line preceding the first measure def get_num_staves(datalines): maxid = -1 index = -1 while True: id, index = find_next_staff_id(datalines, index) if id < maxid: break else: maxid = id return maxid, id, index # splice list2 in between the i-1'th and i'th elements of list1, return result # (if i is less than the length of list1, the result is an insertion; # if i is equal or greater, the result is an append to the end of list1; # if i is negative, it is taken as a count back from the end of list1) ) def splice(list1, list2, i): return list1[0:i] + list2 + list1[i:] with open(inputfile1) as f: file1data = f.readlines() with open(inputfile2) as g: file2data = g.readlines() # check that both files have the same number of staves ns, id1, ix1 = get_num_staves(file1data) n2, id2, ix2 = get_num_staves(file2data) if ns == n2: print("Both files have " + str(ns) + " staves.") else: print("Different numbers of staves in files; exiting.") exit() while id1 <= ns: # loop over staves if id1 == ns: jx1 = find_next(file1data, '', ix1) # end of final staff jx2 = find_next(file2data, '', ix2) else: jd1, jx1 = find_next_staff_id(file1data, ix1) # point to next '' jd2, jx2 = find_next_staff_id(file2data, ix2) # shd have jd1 = jd2 = id1+1 if id1 == 1: # special actions for staff 1 i = jx1 - 1 # insert section break in last measure of staff 1 while '' not in file1data[i]: i = i - 1 # back up to start of final measure i = i + 1 # insert after '' file1data = splice(file1data, [' \n',' section\n',' \n'], i) jx1 = jx1 + 3 # correct the index for length of spliced-in segment # remove any text box(es) from file2data[ix2:jx2] and correct jx2 while True: i = find_next(file2data[0:jx2], '', ix2) if i < 0: break # not found j = find_next(file2data[0:jx2], '', i+1) file2data = file2data[0:i] + file2data[j+1: ] # cut out VBox jx2 = jx2 - (j+1-i) # done with special actions for staff 1 # now splice the current staff segment from file 2 into file 1 file1data = splice(file1data, file2data[ix2+1:jx2-1], jx1-1) ix1 = jx1 + jx2 - ix2 # add length of spliced-in segment ix2 = jx2 id1 = id1 + 1 # write out file1data as the "album" in .mscx format with open(outputfile, 'w') as h: h.writelines(file1data) print('wrote file ' + outputfile)