def findTonicNum(songChords, keyTable): #songChords is a list, keyTable is a list of lists # edit songChords to change 7ths to just major songChordsNo7 = copy.deepcopy(songChords) for i in range(0, len(songChordsNo7)): songChordsNo7[i] = songChordsNo7[i].replace("7", "") maxKey = 0 #0 thru 11 for C thru B maxScore = 0 #following vars are used as tiebreakers for major/relative minor later totalMajors = 0 totalMinors = 0 max_I_count = 0 max_vi_count = 0 for i in range( 0, 12 ): #go thru each of the 12 major keys--example for key of C: C Dm Em F G G7 Am Bdim curScore = 0 current_I_count = 0 current_vi_count = 0 key = keyTable[i] for chord in songChordsNo7: if (i == 0 and chord.find('m') == -1): totalMajors += 1 elif (i == 0): totalMinors += 1 for j in range(0, len( key)): #go thru each note in the major scale of the key note = key[j] if chord == note: if (j == 1 or j == 2 or j == 7): curScore += 0.9 #tiebreaker: the ii, iii, and vii are weighted less else: if (j == 0): current_I_count += 1 elif (j == 6): current_vi_count += 1 curScore += 1 #if it's a match, add 1 to the "score" of the current key break if curScore > maxScore: maxScore = curScore maxKey = i max_I_count = current_I_count max_vi_count = current_vi_count #now our best-fitting major key is stored in maxKey maxKey = "w" + (str)(maxKey) # we determine between maxKey and its relative minor # naive tiebreaker rules # 1. If total Majors or total I's is 0, it's minor # likewise, if total minors or total vi's is 0, it's major # 2. Otherwise, compare totalMajors+total I's against totalMinors+total vi's if (totalMajors == 0 or max_I_count == 0 or (totalMinors + max_vi_count > totalMajors + max_I_count)): maxKey = (caster.shiftNumChord(maxKey, 9)) + "m" return maxKey #return key with most matches for the chords in the song
('B', 11)] for origChord in origChords: castedFlag = False #ADDITIONAL CODE FOR CHECKER VERSION #example of process: E/C# -> w4/w1 -> w0/w9 (store shift as 4) -> C/A -> Am -> w9m -> w1m -> C#m #convert origChord to numeral version origChord = caster.makeFlatsSharps(origChord) tempChord = caster.letterChordToNumChord(origChord, noteNums) #shift numeral chord to C numeral version if tempChord[1]=='1' and len(tempChord)>2 and \ (tempChord[2]=='0' or tempChord[2]=='1'): rootNum = int(tempChord[1:3]) else: rootNum = int(tempChord[1]) shift = rootNum tempChord = caster.shiftNumChord(tempChord, -shift) #convert C numeral version to C letter version tempChord = caster.numChordToLetterChord(tempChord) #cast C letter version using table for chord in castingTable: if tempChord == chord[0]: tempChord = chord[1] castedFlag = True break #convert casted letter chord to casted numeral chord tempChord = caster.letterChordToNumChord(tempChord, noteNums) #shift casted numeral chord back tempChord = caster.shiftNumChord(tempChord, shift) #convert casted numeral chord to casted letter chord tempChord = caster.numChordToLetterChord(tempChord) #print('Converted '+origChord+' to '+tempChord)
curLine = f.readline() # now we've hit a songmarker, so let's move on to processing! #--STAGE 2: PROCESSING-- #only process if there actually are chords in origChords if (len(origChords) > 0): #find tonic chord tonicNum = findTonicNumNo7.findTonicNumNo7(origChords, keyTable) #convert all chords in song to number chords for origChord in origChords: origChord = caster.makeFlatsSharps(origChord) # shift numeral chord to relative to C (w0) numChords.append( caster.shiftNumChord( caster.letterChordToNumChord(origChord, noteNums), -tonicNum)) #cast to correct interval based on semitones #we convert each chord to its interval notation (e.g. V, vi, I, etc.) for i in range(0, len(numChords)): for chord in castingTable: if numChords[i] == chord[0]: numChords[i] = chord[1] break #break out of inner loop #iterate through list of chords, tracking and counting each cadence (add it to the list if not there, if it is there, add 1) for i in range( 0, len(numChords) - 1 ): # if we're doing every pair, we want to skip last element for loop
('C#', 1), ('D#', 3), ('F#', 6), ('G#', 8), ('A#', 10), ('C', 0), ('D', 2), ('E', 4), ('F', 5), ('G', 7), ('A', 9), ('B', 11) ] # have to check the sharps first so it doesn't switch 'C#' into 'w0#' fullKeyTable = [[[]]] #outer level: major/melodic minor/harmonic minor #middle level: the twelve "root keys": C, C#, D, D#, etc. #inner level: all the chords within that root key fullKeyTable[0][0] = ["C", "Dm", "Em", "F", "G", "G7", "Am", "Bdim"] #major fullKeyTable.append( [["Cm", "Dm", "D#7", "F", "G", "G7", "Adim", "Bdim"]]) #melodic minor... note D#aug converted to D#7 by our casting fullKeyTable.append([["Cm", "Ddim", "D#7", "Fm", "G", "G7", "G#", "Bdim"]]) #harmonic minor for k in range(0, len(fullKeyTable)): for i in range(1, 12): curKey = [] for j in range(0, len(fullKeyTable[k][0])): tempChord = caster.makeFlatsSharps(fullKeyTable[k][0][j]) curKey.append( caster.shiftNumChord( caster.letterChordToNumChord(tempChord, noteNums), i)) fullKeyTable[k].append(curKey) with open("key_table_UTF-8.txt", "w") as f: for level in fullKeyTable: for key in level: for note in key: note = caster.numChordToLetterChord(note) f.write(note + " "), f.write("\n") print("Finished generating key tables")
def convert(input_file, output_file): #Example input and output files #input_file = "chords_uku_english_only_songmarkers_empty_lines_removed.txt" #output_file = "rns_uku_english_only_songmarkers_empty_lines_removed.txt" #global scope variables noteNums = [ ('C#', 1), ('D#', 3), ('F#', 6), ('G#', 8), ('A#', 10), ('C', 0), ('D', 2), ('E', 4), ('F', 5), ('G', 7), ('A', 9), ('B', 11) ] # have to check the sharps first so it doesn't switch 'C#' into 'w0#' # read in cadence casting table with open('cadence_casting_UTF-8.txt', 'r') as f: inputs = [] outputs = [] exploLine = [] for line in f: exploLine = line.split("\t") exploLine[0] = exploLine[0].replace('\xef\xbb\xbf', '') # clean up special chars inputs.append(exploLine[0].replace( '\\ufeff', '').strip()) # clean up the special chars outputs.append(exploLine[1].replace( '\n', '').strip()) # clean up special chars castingTable = list(zip( inputs, outputs)) # convert casting table to a list of tuples print("Opened Cadence Casting Table") print(castingTable) # test to make sure we've read in the table correctly # read in key tables for finding tonic num keyTable = [] with open('key_table_UTF-8.txt', 'r') as f: curLine = f.readline() while (curLine != ""): keyTable.append(curLine.split()) curLine = f.readline() print(keyTable) # test to make sure we've read table correctly #read in chords from file, song by song #split is by " | | S O N G M A R K E R | |", and it starts with lyrics and the songmarker is AFTER each song with open(input_file, 'r') as f: with open(output_file, 'w') as out: curLine = f.readline() while (curLine != ""): #--STAGE 0: RESET-- origChords = [] numChords = [] tonicNum = 0 songmarker = "" #--STAGE 1: READING SONG-- if (curLine != "\n" and curLine.find("SONG OVER") == -1): while (curLine.find("SONG OVER") == -1 ): #while we're not at the Songmarker lineChords = curLine.split() for chord in lineChords: origChords.append(chord) origChords.append( '\n') #keep output of original file intact curLine = f.readline() songmarker = curLine #make sure we print the songmarker line after each song # now we've hit a songmarker, so let's move on to processing! else: out.write( curLine) #keep the formatting of original file intact #--STAGE 2: PROCESSING-- #only process if there actually are chords in origChords if (len(origChords) > 0): #find tonic chord tonicNum = findTonicNumNo7.findTonicNumNo7( origChords, keyTable) #convert all chords in song to number chords for origChord in origChords: origChord = caster.makeFlatsSharps(origChord) # shift numeral chord to relative to C (w0) numChords.append( caster.shiftNumChord( caster.letterChordToNumChord( origChord, noteNums), -tonicNum)) #cast to correct interval based on semitones #we convert each chord to its interval notation (e.g. V, vi, I, etc.) for i in range(0, len(numChords)): for chord in castingTable: if numChords[i] == chord[0]: numChords[i] = chord[1] break #break out of inner loop #print for numChord in numChords: out.write(numChord) if numChord != '\n': out.write(" ") out.write(songmarker) curLine = f.readline()