diff --git "a/TMIDIX.py" "b/TMIDIX.py" --- "a/TMIDIX.py" +++ "b/TMIDIX.py" @@ -14,14 +14,14 @@ r'''############################################################################ # # Project Los Angeles # -# Tegridy Code 2021 +# Tegridy Code 2025 # # https://github.com/Tegridy-Code/Project-Los-Angeles # # ################################################################################### ################################################################################### -# Copyright 2021 Project Los Angeles / Tegridy Code +# Copyright 2025 Project Los Angeles / Tegridy Code # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -56,6 +56,8 @@ VersionDate = '20201120' _previous_warning = '' # 5.4 _previous_times = 0 # 5.4 +_no_warning = False + #------------------------------- Encoding stuff -------------------------- def opus2midi(opus=[], text_encoding='ISO-8859-1'): @@ -845,10 +847,11 @@ def _unshift_ber_int(ba): r'''Given a bytearray, returns a tuple of (the ber-integer at the start, and the remainder of the bytearray). ''' - if not len(ba): # 6.7 + if not len(ba): # 6.7 _warn('_unshift_ber_int: no integer found') return ((0, b"")) - byte = ba.pop(0) + byte = ba[0] + ba = ba[1:] integer = 0 while True: integer += (byte & 0x7F) @@ -857,13 +860,17 @@ start, and the remainder of the bytearray). if not len(ba): _warn('_unshift_ber_int: no end-of-integer found') return ((0, ba)) - byte = ba.pop(0) + byte = ba[0] + ba = ba[1:] integer <<= 7 + def _clean_up_warnings(): # 5.4 # Call this before returning from any publicly callable function # whenever there's a possibility that a warning might have been printed # by the function, or by any private functions it might have called. + if _no_warning: + return global _previous_times global _previous_warning if _previous_times > 1: @@ -876,27 +883,32 @@ def _clean_up_warnings(): # 5.4 _previous_times = 0 _previous_warning = '' + def _warn(s=''): + if _no_warning: + return global _previous_times global _previous_warning if s == _previous_warning: # 5.4 _previous_times = _previous_times + 1 else: _clean_up_warnings() - sys.stderr.write(str(s)+"\n") + sys.stderr.write(str(s) + "\n") _previous_warning = s + def _some_text_event(which_kind=0x01, text=b'some_text', text_encoding='ISO-8859-1'): - if str(type(text)).find("'str'") >= 0: # 6.4 test for back-compatibility + if str(type(text)).find("'str'") >= 0: # 6.4 test for back-compatibility data = bytes(text, encoding=text_encoding) else: data = bytes(text) - return b'\xFF'+bytes((which_kind,))+_ber_compressed_int(len(data))+data + return b'\xFF' + bytes((which_kind,)) + _ber_compressed_int(len(data)) + data + def _consistentise_ticks(scores): # 3.6 # used by mix_scores, merge_scores, concatenate_scores if len(scores) == 1: - return copy.deepcopy(scores) + return copy.deepcopy(scores) are_consistent = True ticks = scores[0][0] iscore = 1 @@ -917,9 +929,8 @@ def _consistentise_ticks(scores): # 3.6 ########################################################################### - def _decode(trackdata=b'', exclude=None, include=None, - event_callback=None, exclusive_event_callback=None, no_eot_magic=False): + event_callback=None, exclusive_event_callback=None, no_eot_magic=False): r'''Decodes MIDI track data into an opus-style list of events. The options: 'exclude' is a list of event types which will be ignored SHOULD BE A SET @@ -939,24 +950,24 @@ The options: exclude = set(exclude) # Pointer = 0; not used here; we eat through the bytearray instead. - event_code = -1; # used for running status + event_code = -1; # used for running status event_count = 0; events = [] - while(len(trackdata)): + while (len(trackdata)): # loop while there's anything to analyze ... - eot = False # When True, the event registrar aborts this loop + eot = False # When True, the event registrar aborts this loop event_count += 1 E = [] # E for events - we'll feed it to the event registrar at the end. # Slice off the delta time code, and analyze it - [time, remainder] = _unshift_ber_int(trackdata) + [time, trackdata] = _unshift_ber_int(trackdata) # Now let's see what we can make of the command - first_byte = trackdata.pop(0) & 0xFF - + first_byte = trackdata[0] & 0xFF + trackdata = trackdata[1:] if (first_byte < 0xF0): # It's a MIDI event if (first_byte & 0x80): event_code = first_byte @@ -970,17 +981,19 @@ The options: command = event_code & 0xF0 channel = event_code & 0x0F - if (command == 0xF6): # 0-byte argument + if (command == 0xF6): # 0-byte argument pass - elif (command == 0xC0 or command == 0xD0): # 1-byte argument - parameter = trackdata.pop(0) # could be B - else: # 2-byte argument could be BB or 14-bit - parameter = (trackdata.pop(0), trackdata.pop(0)) + elif (command == 0xC0 or command == 0xD0): # 1-byte argument + parameter = trackdata[0] # could be B + trackdata = trackdata[1:] + else: # 2-byte argument could be BB or 14-bit + parameter = (trackdata[0], trackdata[1]) + trackdata = trackdata[2:] ################################################################# # MIDI events - if (command == 0x80): + if (command == 0x80): if 'note_off' in exclude: continue E = ['note_off', time, channel, parameter[0], parameter[1]] @@ -991,11 +1004,11 @@ The options: elif (command == 0xA0): if 'key_after_touch' in exclude: continue - E = ['key_after_touch',time,channel,parameter[0],parameter[1]] + E = ['key_after_touch', time, channel, parameter[0], parameter[1]] elif (command == 0xB0): if 'control_change' in exclude: continue - E = ['control_change',time,channel,parameter[0],parameter[1]] + E = ['control_change', time, channel, parameter[0], parameter[1]] elif (command == 0xC0): if 'patch_change' in exclude: continue @@ -1008,93 +1021,94 @@ The options: if 'pitch_wheel_change' in exclude: continue E = ['pitch_wheel_change', time, channel, - _read_14_bit(parameter)-0x2000] + _read_14_bit(parameter) - 0x2000] else: - _warn("Shouldn't get here; command="+hex(command)) + _warn("Shouldn't get here; command=" + hex(command)) elif (first_byte == 0xFF): # It's a Meta-Event! ################## - #[command, length, remainder] = + # [command, length, remainder] = # unpack("xCwa*", substr(trackdata, $Pointer, 6)); - #Pointer += 6 - len(remainder); + # Pointer += 6 - len(remainder); # # Move past JUST the length-encoded. - command = trackdata.pop(0) & 0xFF + command = trackdata[0] & 0xFF + trackdata = trackdata[1:] [length, trackdata] = _unshift_ber_int(trackdata) - if (command == 0x00): - if (length == 2): - E = ['set_sequence_number',time,_twobytes2int(trackdata)] - else: - _warn('set_sequence_number: length must be 2, not '+str(length)) - E = ['set_sequence_number', time, 0] - - elif command >= 0x01 and command <= 0x0f: # Text events + if (command == 0x00): + if (length == 2): + E = ['set_sequence_number', time, _twobytes2int(trackdata)] + else: + _warn('set_sequence_number: length must be 2, not ' + str(length)) + E = ['set_sequence_number', time, 0] + + elif command >= 0x01 and command <= 0x0f: # Text events # 6.2 take it in bytes; let the user get the right encoding. # text_str = trackdata[0:length].decode('ascii','ignore') # text_str = trackdata[0:length].decode('ISO-8859-1') # 6.4 take it in bytes; let the user get the right encoding. - text_data = bytes(trackdata[0:length]) # 6.4 + text_data = bytes(trackdata[0:length]) # 6.4 # Defined text events if (command == 0x01): - E = ['text_event', time, text_data] + E = ['text_event', time, text_data] elif (command == 0x02): - E = ['copyright_text_event', time, text_data] + E = ['copyright_text_event', time, text_data] elif (command == 0x03): - E = ['track_name', time, text_data] + E = ['track_name', time, text_data] elif (command == 0x04): - E = ['instrument_name', time, text_data] + E = ['instrument_name', time, text_data] elif (command == 0x05): - E = ['lyric', time, text_data] + E = ['lyric', time, text_data] elif (command == 0x06): - E = ['marker', time, text_data] + E = ['marker', time, text_data] elif (command == 0x07): - E = ['cue_point', time, text_data] + E = ['cue_point', time, text_data] # Reserved but apparently unassigned text events elif (command == 0x08): - E = ['text_event_08', time, text_data] + E = ['text_event_08', time, text_data] elif (command == 0x09): - E = ['text_event_09', time, text_data] + E = ['text_event_09', time, text_data] elif (command == 0x0a): - E = ['text_event_0a', time, text_data] + E = ['text_event_0a', time, text_data] elif (command == 0x0b): - E = ['text_event_0b', time, text_data] + E = ['text_event_0b', time, text_data] elif (command == 0x0c): - E = ['text_event_0c', time, text_data] + E = ['text_event_0c', time, text_data] elif (command == 0x0d): - E = ['text_event_0d', time, text_data] + E = ['text_event_0d', time, text_data] elif (command == 0x0e): - E = ['text_event_0e', time, text_data] + E = ['text_event_0e', time, text_data] elif (command == 0x0f): - E = ['text_event_0f', time, text_data] + E = ['text_event_0f', time, text_data] # Now the sticky events ------------------------------------- elif (command == 0x2F): - E = ['end_track', time] - # The code for handling this, oddly, comes LATER, - # in the event registrar. - elif (command == 0x51): # DTime, Microseconds/Crochet - if length != 3: - _warn('set_tempo event, but length='+str(length)) - E = ['set_tempo', time, - struct.unpack(">I", b'\x00'+trackdata[0:3])[0]] + E = ['end_track', time] + # The code for handling this, oddly, comes LATER, + # in the event registrar. + elif (command == 0x51): # DTime, Microseconds/Crochet + if length != 3: + _warn('set_tempo event, but length=' + str(length)) + E = ['set_tempo', time, + struct.unpack(">I", b'\x00' + trackdata[0:3])[0]] elif (command == 0x54): - if length != 5: # DTime, HR, MN, SE, FR, FF - _warn('smpte_offset event, but length='+str(length)) - E = ['smpte_offset',time] + list(struct.unpack(">BBBBB",trackdata[0:5])) + if length != 5: # DTime, HR, MN, SE, FR, FF + _warn('smpte_offset event, but length=' + str(length)) + E = ['smpte_offset', time] + list(struct.unpack(">BBBBB", trackdata[0:5])) elif (command == 0x58): - if length != 4: # DTime, NN, DD, CC, BB - _warn('time_signature event, but length='+str(length)) - E = ['time_signature', time]+list(trackdata[0:4]) + if length != 4: # DTime, NN, DD, CC, BB + _warn('time_signature event, but length=' + str(length)) + E = ['time_signature', time] + list(trackdata[0:4]) elif (command == 0x59): - if length != 2: # DTime, SF(signed), MI - _warn('key_signature event, but length='+str(length)) - E = ['key_signature',time] + list(struct.unpack(">bB",trackdata[0:2])) - elif (command == 0x7F): # 6.4 - E = ['sequencer_specific',time, bytes(trackdata[0:length])] + if length != 2: # DTime, SF(signed), MI + _warn('key_signature event, but length=' + str(length)) + E = ['key_signature', time] + list(struct.unpack(">bB", trackdata[0:2])) + elif (command == 0x7F): # 6.4 + E = ['sequencer_specific', time, bytes(trackdata[0:length])] else: - E = ['raw_meta_event', time, command, - bytes(trackdata[0:length])] # 6.0 - #"[uninterpretable meta-event command of length length]" - # DTime, Command, Binary Data - # It's uninterpretable; record it as raw_data. + E = ['raw_meta_event', time, command, + bytes(trackdata[0:length])] # 6.0 + # "[uninterpretable meta-event command of length length]" + # DTime, Command, Binary Data + # It's uninterpretable; record it as raw_data. # Pointer += length; # Now move Pointer trackdata = trackdata[length:] @@ -1111,7 +1125,7 @@ The options: # is omitted if this is a non-final block in a multiblock sysex; # but the F7 (if there) is counted in the message's declared # length, so we don't have to think about it anyway.) - #command = trackdata.pop(0) + # command = trackdata.pop(0) [length, trackdata] = _unshift_ber_int(trackdata) if first_byte == 0xF0: # 20091008 added ISO-8859-1 to get an 8-bit str @@ -1135,32 +1149,32 @@ The options: # from the MIDI file spec. So, I'm going to assume that # they CAN, in practice, occur. I don't know whether it's # proper for you to actually emit these into a MIDI file. - - elif (first_byte == 0xF2): # DTime, Beats + + elif (first_byte == 0xF2): # DTime, Beats # ::= F2 E = ['song_position', time, _read_14_bit(trackdata[:2])] trackdata = trackdata[2:] - elif (first_byte == 0xF3): # ::= F3 + elif (first_byte == 0xF3): # ::= F3 # E = ['song_select', time, struct.unpack('>B',trackdata.pop(0))[0]] E = ['song_select', time, trackdata[0]] trackdata = trackdata[1:] # DTime, Thing (what?! song number? whatever ...) - elif (first_byte == 0xF6): # DTime + elif (first_byte == 0xF6): # DTime E = ['tune_request', time] # What would a tune request be doing in a MIDI /file/? - ######################################################### - # ADD MORE META-EVENTS HERE. TODO: - # f1 -- MTC Quarter Frame Message. One data byte follows - # the Status; it's the time code value, from 0 to 127. - # f8 -- MIDI clock. no data. - # fa -- MIDI start. no data. - # fb -- MIDI continue. no data. - # fc -- MIDI stop. no data. - # fe -- Active sense. no data. - # f4 f5 f9 fd -- unallocated + ######################################################### + # ADD MORE META-EVENTS HERE. TODO: + # f1 -- MTC Quarter Frame Message. One data byte follows + # the Status; it's the time code value, from 0 to 127. + # f8 -- MIDI clock. no data. + # fa -- MIDI start. no data. + # fb -- MIDI continue. no data. + # fc -- MIDI stop. no data. + # fe -- Active sense. no data. + # f4 f5 f9 fd -- unallocated r''' elif (first_byte > 0xF0) { # Some unknown kinda F-series event #### @@ -1175,31 +1189,30 @@ The options: elif first_byte > 0xF0: # Some unknown F-series event # Here we only produce a one-byte piece of raw data. # E = ['raw_data', time, bytest(trackdata[0])] # 6.4 - E = ['raw_data', time, trackdata[0]] # 6.4 6.7 + E = ['raw_data', time, trackdata[0]] # 6.4 6.7 trackdata = trackdata[1:] else: # Fallthru. - _warn("Aborting track. Command-byte first_byte="+hex(first_byte)) + _warn("Aborting track. Command-byte first_byte=" + hex(first_byte)) break # End of the big if-group - ###################################################################### # THE EVENT REGISTRAR... - if E and (E[0] == 'end_track'): + if E and (E[0] == 'end_track'): # This is the code for exceptional handling of the EOT event. eot = True if not no_eot_magic: if E[1] > 0: # a null text-event to carry the delta-time E = ['text_event', E[1], ''] else: - E = [] # EOT with a delta-time of 0; ignore it. - + E = [] # EOT with a delta-time of 0; ignore it. + if E and not (E[0] in exclude): - #if ( $exclusive_event_callback ): + # if ( $exclusive_event_callback ): # &{ $exclusive_event_callback }( @E ); - #else: + # else: # &{ $event_callback }( @E ) if $event_callback; - events.append(E) + events.append(E) if eot: break @@ -4677,7 +4690,8 @@ def augment_enhanced_score_notes(enhanced_score_notes, pitch_shift=0, ceil_timings=False, round_timings=False, - legacy_timings=True + legacy_timings=True, + sort_drums_last=False ): esn = copy.deepcopy(enhanced_score_notes) @@ -4727,6 +4741,9 @@ def augment_enhanced_score_notes(enhanced_score_notes, esn.sort(key=lambda x: x[6]) esn.sort(key=lambda x: x[4], reverse=True) esn.sort(key=lambda x: x[1]) + + if sort_drums_last: + esn.sort(key=lambda x: (x[1], -x[4], x[6]) if x[6] != 128 else (x[1], x[6], -x[4])) return esn @@ -7084,13 +7101,25 @@ def escore_notes_averages(escore_notes, else: durs = [e[durs_index] for e in escore_notes if e[chans_index] != 9] + if len(times) == 0: + times = [0] + + if len(durs) == 0: + durs = [0] + if return_ptcs_and_vels: if average_drums: ptcs = [e[ptcs_index] for e in escore_notes] vels = [e[vels_index] for e in escore_notes] else: ptcs = [e[ptcs_index] for e in escore_notes if e[chans_index] != 9] - vels = [e[vels_index] for e in escore_notes if e[chans_index] != 9] + vels = [e[vels_index] for e in escore_notes if e[chans_index] != 9] + + if len(ptcs) == 0: + ptcs = [0] + + if len(vels) == 0: + vels = [0] return [sum(times) / len(times), sum(durs) / len(durs), sum(ptcs) / len(ptcs), sum(vels) / len(vels)] @@ -7870,19 +7899,21 @@ def solo_piano_escore_notes(escore_notes, chord = [] for cc in c: - if cc[pitches_index] not in seen: - if cc[channels_index] != 9: + if cc[channels_index] != 9: + if cc[pitches_index] not in seen: + cc[channels_index] = 0 cc[patches_index] = 0 - + chord.append(cc) seen.append(cc[pitches_index]) - - else: - if keep_drums: + + else: + if keep_drums: + if cc[pitches_index]+128 not in seen: chord.append(cc) - seen.append(cc[pitches_index]) + seen.append(cc[pitches_index]+128) sp_escore_notes.append(chord) @@ -9098,7 +9129,1967 @@ def count_bad_chords_in_chordified_score(chordified_score, if tones_chord not in CHORDS: bad_chords_count += 1 - return [bad_chords_count, len(chordified_score)] + return [bad_chords_count, len(chordified_score)] + +################################################################################### + +def needleman_wunsch_aligner(seq1, + seq2, + align_idx, + gap_penalty=-1, + match_score=2, + mismatch_penalty=-1 + ): + + n = len(seq1) + m = len(seq2) + + score_matrix = [[0] * (m + 1) for _ in range(n + 1)] + + for i in range(1, n + 1): + score_matrix[i][0] = gap_penalty * i + for j in range(1, m + 1): + score_matrix[0][j] = gap_penalty * j + + for i in range(1, n + 1): + for j in range(1, m + 1): + match = score_matrix[i-1][j-1] + (match_score if seq1[i-1][align_idx] == seq2[j-1][align_idx] else mismatch_penalty) + delete = score_matrix[i-1][j] + gap_penalty + insert = score_matrix[i][j-1] + gap_penalty + score_matrix[i][j] = max(match, delete, insert) + + align1, align2 = [], [] + i, j = n, m + + while i > 0 and j > 0: + + score = score_matrix[i][j] + score_diag = score_matrix[i-1][j-1] + score_up = score_matrix[i-1][j] + score_left = score_matrix[i][j-1] + + if score == score_diag + (match_score if seq1[i-1][align_idx] == seq2[j-1][align_idx] else mismatch_penalty): + align1.append(seq1[i-1]) + align2.append(seq2[j-1]) + i -= 1 + j -= 1 + elif score == score_up + gap_penalty: + align1.append(seq1[i-1]) + align2.append([None] * 6) + i -= 1 + elif score == score_left + gap_penalty: + align1.append([None] * 6) + align2.append(seq2[j-1]) + j -= 1 + + while i > 0: + align1.append(seq1[i-1]) + align2.append([None] * 6) + i -= 1 + while j > 0: + align1.append([None] * 6) + align2.append(seq2[j-1]) + j -= 1 + + align1.reverse() + align2.reverse() + + return align1, align2 + +################################################################################### + +def align_escore_notes_to_escore_notes(src_escore_notes, + trg_escore_notes, + recalculate_scores_timings=True, + pitches_idx=4 + ): + + if recalculate_scores_timings: + src_escore_notes = recalculate_score_timings(src_escore_notes) + trg_escore_notes = recalculate_score_timings(trg_escore_notes) + + src_align1, trg_align2 = needleman_wunsch_aligner(src_escore_notes, trg_escore_notes, pitches_idx) + + aligned_scores = [[al[0], al[1]] for al in zip(src_align1, trg_align2) if al[0][0] is not None and al[1][0] is not None] + + return aligned_scores + +################################################################################### + +def t_to_n(arr, si, t): + + ct = 0 + ci = si + + while ct + arr[ci][1] < t and ci < len(arr)-1: + ct += arr[ci][1] + ci += 1 + + return ci+1 + +################################################################################### + +def max_sum_chunk_idxs(arr, t=255): + + n = t_to_n(arr, 0, t) + + if n > len(arr): + return [0, n] + + max_sum = 0 + max_sum_start_index = 0 + + max_sum_start_idxs = [0, len(arr), sum([a[0] for a in arr])] + + for i in range(len(arr)): + + n = t_to_n(arr, i, t) + + current_sum = sum([a[0] for a in arr[i:n]]) + current_time = sum([a[1] for a in arr[i:n]]) + + if current_sum > max_sum and current_time <= t: + max_sum = current_sum + max_sum_start_idxs = [i, n, max_sum] + + return max_sum_start_idxs + +################################################################################### + +def find_highest_density_escore_notes_chunk(escore_notes, max_chunk_time=512): + + dscore = delta_score_notes(escore_notes) + + cscore = chordify_score([d[1:] for d in dscore]) + + notes_counts = [[len(c), c[0][0]] for c in cscore] + + msc_idxs = max_sum_chunk_idxs(notes_counts, max_chunk_time) + + chunk_dscore = [['note'] + c for c in flatten(cscore[msc_idxs[0]:msc_idxs[1]])] + + chunk_escore = recalculate_score_timings(delta_score_to_abs_score(chunk_dscore)) + + return chunk_escore + +################################################################################### + +def advanced_add_drums_to_escore_notes(escore_notes, + main_beat_min_dtime=5, + main_beat_dtime_thres=1, + drums_durations_value=2, + drums_pitches_velocities=[(36, 100), + (38, 100), + (41, 125)], + recalculate_score_timings=True, + intro_drums_count=4, + intro_drums_time_k=4, + intro_drums_pitch_velocity=[37, 110] + ): + + #=========================================================== + + new_dscore = delta_score_notes(escore_notes) + + times = [d[1] for d in new_dscore if d[1] != 0] + + time = [c[0] for c in Counter(times).most_common() if c[0] >= main_beat_min_dtime][0] + + #=========================================================== + + if intro_drums_count > 0: + + drums_score = [] + + for i in range(intro_drums_count): + + if i == 0: + dtime = 0 + + else: + dtime = time + + drums_score.append(['note', + dtime * intro_drums_time_k, + drums_durations_value, + 9, + intro_drums_pitch_velocity[0], + intro_drums_pitch_velocity[1], + 128] + ) + + new_dscore[0][1] = time * intro_drums_time_k + + new_dscore = drums_score + new_dscore + + #=========================================================== + + for e in new_dscore: + + if abs(e[1] - time) == main_beat_dtime_thres: + e[1] = time + + if recalculate_score_timings: + + if e[1] % time != 0 and e[1] > time: + if e[1] % time < time // 2: + e[1] -= e[1] % time + + else: + e[1] += time - (e[1] % time) + + #=========================================================== + + drums_score = [] + + dtime = 0 + + idx = 0 + + for i, e in enumerate(new_dscore): + + drums_score.append(e) + + dtime += e[1] + + if e[1] != 0: + idx += 1 + + if i >= intro_drums_count: + + if (e[1] % time == 0 and e[1] != 0) or i == 0: + + if idx % 2 == 0 and e[1] != 0: + drums_score.append(['note', + 0, + drums_durations_value, + 9, + drums_pitches_velocities[0][0], + drums_pitches_velocities[0][1], + 128] + ) + + if idx % 2 != 0 and e[1] != 0: + drums_score.append(['note', + 0, + drums_durations_value, + 9, + drums_pitches_velocities[1][0], + drums_pitches_velocities[1][1], + 128] + ) + + if idx % 4 == 0 and e[1] != 0: + drums_score.append(['note', + 0, + drums_durations_value, + 9, + drums_pitches_velocities[2][0], + drums_pitches_velocities[2][1], + 128] + ) + + #=========================================================== + + return delta_score_to_abs_score(drums_score) + +################################################################################### + +MIDI_TEXT_EVENTS = ['text_event', + 'copyright_text_event', + 'track_name', + 'instrument_name', + 'lyric', + 'marker', + 'cue_point', + 'text_event_08', + 'text_event_09', + 'text_event_0a', + 'text_event_0b', + 'text_event_0c', + 'text_event_0d', + 'text_event_0e', + 'text_event_0f' + ] + +################################################################################### + +import hashlib +import re + +################################################################################### + +def get_md5_hash(data): + return hashlib.md5(data).hexdigest() + +################################################################################### + +def is_valid_md5_hash(string): + return bool(re.match(r'^[a-fA-F0-9]{32}$', string)) + +################################################################################### + +def clean_string(original_string, + regex=r'[^a-zA-Z0-9 ]', + remove_duplicate_spaces=True, + title=False + ): + + cstr1 = re.sub(regex, '', original_string) + + if title: + cstr1 = cstr1.title() + + if remove_duplicate_spaces: + return re.sub(r'[ ]+', ' ', cstr1).strip() + + else: + return cstr1 + +################################################################################### + +def encode_to_ord(text, chars_range=[], sub_char='', chars_shift=0): + + if not chars_range: + chars_range = [32] + list(range(65, 91)) + list(range(97, 123)) + + if sub_char: + chars_range.append(ord(sub_char)) + + chars_range = sorted(set(chars_range)) + + encoded = [] + + for char in text: + if ord(char) in chars_range: + encoded.append(chars_range.index(ord(char)) + chars_shift) + + else: + if sub_char: + encoded.append(chars_range.index(ord(sub_char)) + chars_shift) + + + return [encoded, chars_range] + +################################################################################### + +def decode_from_ord(ord_list, chars_range=[], sub_char='', chars_shift=0): + + if not chars_range: + chars_range = [32] + list(range(65, 91)) + list(range(97, 123)) + + if sub_char: + chars_range.append(ord(sub_char)) + + chars_range = sorted(set(chars_range)) + + return ''.join(chr(chars_range[num-chars_shift]) if 0 <= num-chars_shift < len(chars_range) else sub_char for num in ord_list) + +################################################################################### + +def lists_similarity(list1, list2, by_elements=True, by_sum=True): + + if len(list1) != len(list2): + return -1 + + element_ratios = [] + total_counts1 = sum(list1) + total_counts2 = sum(list2) + + for a, b in zip(list1, list2): + if a == 0 and b == 0: + element_ratios.append(1) + elif a == 0 or b == 0: + element_ratios.append(0) + else: + element_ratios.append(min(a, b) / max(a, b)) + + average_element_ratio = sum(element_ratios) / len(element_ratios) + + total_counts_ratio = min(total_counts1, total_counts2) / max(total_counts1, total_counts2) + + if by_elements and by_sum: + return (average_element_ratio + total_counts_ratio) / 2 + + elif by_elements and not by_sum: + return average_element_ratio + + elif not by_elements and by_sum: + return total_counts_ratio + + else: + return -1 + +################################################################################### + +def find_indexes(lst, value, mode='equal', dual_mode=True): + + indexes = [] + + if mode == 'equal' or dual_mode: + indexes.extend([index for index, elem in enumerate(lst) if elem == value]) + + if mode == 'smaller': + indexes.extend([index for index, elem in enumerate(lst) if elem < value]) + + if mode == 'larger': + indexes.extend([index for index, elem in enumerate(lst) if elem > value]) + + return sorted(set(indexes)) + +################################################################################### + +NUMERALS = ["one", "two", "three", "four", + "five", "six", "seven", "eight", + "nine", "ten", "eleven", "twelve", + "thirteen", "fourteen", "fifteen", "sixteen" + ] + +SEMITONES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"] + +BASIC_SCALES = ['Major', 'Minor'] + +################################################################################### + +def alpha_str(string): + astr = re.sub(r'[^a-zA-Z ()0-9]', '', string).strip() + return re.sub(r'\s+', ' ', astr).strip() + +################################################################################### + +def escore_notes_to_text_description(escore_notes, + song_name='', + artist_name='', + timings_divider=16, + ): + + #============================================================================== + + song_time_min = (escore_notes[-1][1] * timings_divider) / 1000 / 60 + + if song_time_min < 1.5: + song_length = 'short' + + elif 1.5 <= song_time_min < 2.5: + song_length = 'average' + + elif song_time_min >= 2.5: + song_length = 'long' + + #============================================================================== + + escore_times = [e[1] for e in escore_notes if e[3] != 9] + + comp_type = '' + + if len(escore_times) > 0: + if len(escore_times) == len(set(escore_times)): + comp_type = 'monophonic melody' + ctype = 'melody' + + elif len(escore_times) >= len(set(escore_times)) and 1 in Counter(escore_times).values(): + comp_type = 'melody and accompaniment' + ctype = 'song' + + elif len(escore_times) >= len(set(escore_times)) and 1 not in Counter(escore_times).values(): + comp_type = 'accompaniment' + ctype = 'song' + + else: + comp_type = 'drum track' + ctype = 'drum track' + + #============================================================================== + + all_patches = [e[6] for e in escore_notes] + + patches = ordered_set(all_patches) + + instruments = [alpha_str(Number2patch[p]) for p in patches if p < 128] + + if instruments: + + nd_patches_counts = Counter([p for p in all_patches if p < 128]).most_common() + + dominant_instrument = alpha_str(Number2patch[nd_patches_counts[0][0]]) + + if 128 in patches: + drums_present = True + + drums_pitches = [e[4] for e in escore_notes if e[3] == 9] + + most_common_drums = [alpha_str(Notenum2percussion[p[0]]) for p in Counter(drums_pitches).most_common(3) if p[0] in Notenum2percussion] + + else: + drums_present = False + + #============================================================================== + + pitches = [e[4] for e in escore_notes if e[3] != 9] + + key = '' + + if pitches: + key = SEMITONES[statistics.mode(pitches) % 12] + + #============================================================================== + + scale = '' + mood = '' + + if pitches: + + result = escore_notes_scale(escore_notes) + + scale = result[0] + mood = result[1].split(' ')[0].lower() + + #============================================================================== + + if pitches: + + escore_averages = escore_notes_averages(escore_notes, return_ptcs_and_vels=True) + + if escore_averages[0] < (128 / timings_divider): + rythm = 'fast' + + elif (128 / timings_divider) <= escore_averages[0] <= (192 / timings_divider): + rythm = 'average' + + elif escore_averages[0] > (192 / timings_divider): + rythm = 'slow' + + if escore_averages[1] < (256 / timings_divider): + tempo = 'fast' + + elif (256 / timings_divider) <= escore_averages[1] <= (384 / timings_divider): + tempo = 'average' + + elif escore_averages[1] > (384 / timings_divider): + tempo = 'slow' + + if escore_averages[2] < 50: + tone = 'bass' + + elif 50 <= escore_averages[2] <= 70: + tone = 'midrange' + + elif escore_averages[2] > 70: + tone = 'treble' + + if escore_averages[3] < 64: + dynamics = 'quiet' + + elif 64 <= escore_averages[3] <= 96: + dynamics = 'average' + + elif escore_averages[3] > 96: + dynamics = 'loud' + + #============================================================================== + + mono_melodies = escore_notes_monoponic_melodies([e for e in escore_notes if e[6] < 88]) + + lead_melodies = [] + base_melodies = [] + + if mono_melodies: + + for mel in mono_melodies: + + escore_avgs = escore_notes_pitches_range(escore_notes, range_patch = mel[0]) + + if mel[0] in LEAD_INSTRUMENTS and escore_avgs[3] > 60: + lead_melodies.append([Number2patch[mel[0]], mel[1]]) + + elif mel[0] in BASE_INSTRUMENTS and escore_avgs[3] <= 60: + base_melodies.append([Number2patch[mel[0]], mel[1]]) + + if lead_melodies: + lead_melodies.sort(key=lambda x: x[1], reverse=True) + + if base_melodies: + base_melodies.sort(key=lambda x: x[1], reverse=True) + + #============================================================================== + + description = '' + + if song_name != '': + description = 'The song "' + song_name + '"' + + if artist_name != '': + description += ' by ' + artist_name + + if song_name != '' or artist_name != '': + description += '.' + description += '\n' + + description += 'The song is ' + + if song_length != 'average': + description += 'a ' + song_length + + else: + description += 'an ' + song_length + + description += ' duration ' + + description += comp_type + ' composition' + + if comp_type != 'drum track': + + if drums_present: + description += ' with drums' + + else: + description += ' without drums' + + if key and scale: + description += ' in ' + key + ' ' + scale + + description += '.' + + description += '\n' + + if pitches: + + if comp_type not in ['monophonic melody', 'drum track']: + + description += 'This ' + mood + ' song has ' + + elif comp_type == 'monophonic melody': + + description += 'This ' + mood + ' melody has ' + + + else: + description += 'TThis drum track has ' + + description += rythm + ' rythm, ' + description += tempo + ' tempo, ' + description += tone + ' tone and ' + description += dynamics + ' dynamics.' + + description += '\n' + + if instruments: + + if comp_type not in ['monophonic melody', 'drum track']: + description += 'The song ' + + else: + description += 'The melody ' + + if len(instruments) == 1: + description += 'is played on a solo ' + instruments[0] + '.' + + description += '\n' + + else: + description += 'features ' + NUMERALS[max(0, min(15, len(instruments)-1))] + ' instruments: ' + description += ', '.join(instruments[:-1]) + ' and ' + instruments[-1] + '.' + + description += '\n' + + if instruments[0] != dominant_instrument: + description += 'The song opens with ' + instruments[0] + + description += ' and primarily performed on ' + dominant_instrument + '.' + + else: + description += 'The song opens with and performed on ' + instruments[0] + '.' + + description += '\n' + + if lead_melodies or base_melodies: + + tm_count = len(lead_melodies + base_melodies) + + if tm_count == 1: + + if lead_melodies: + description += 'The song has one distinct lead melody played on ' + lead_melodies[0][0] + '.' + + else: + description += 'The song has one distinct base melody played on ' + base_melodies[0][0] + '.' + + description += '\n' + + else: + + if lead_melodies and not base_melodies: + + if len(lead_melodies) == 1: + mword = 'melody' + + else: + mword = 'melodies' + + description += 'The song has ' + NUMERALS[len(lead_melodies)-1] + ' distinct lead ' + mword + ' played on ' + + if len(lead_melodies) > 1: + description += ', '.join(lead_melodies[:-1][0]) + ' and ' + lead_melodies[-1][0] + '.' + + else: + description += lead_melodies[0][0] + '.' + + description += '\n' + + elif base_melodies and not lead_melodies: + + if len(base_melodies) == 1: + mword = 'melody' + + else: + mword = 'melodies' + + description += 'The song has ' + NUMERALS[len(base_melodies)-1] + ' distinct base ' + mword + ' played on ' + + if len(base_melodies) > 1: + description += ', '.join(base_melodies[:-1][0]) + ' and ' + base_melodies[-1][0] + '.' + + else: + description += base_melodies[0][0] + '.' + + description += '\n' + + elif lead_melodies and base_melodies: + + if len(lead_melodies) == 1: + lmword = 'melody' + + else: + lmword = 'melodies' + + description += 'The song has ' + NUMERALS[len(lead_melodies)-1] + ' distinct lead ' + lmword + ' played on ' + + if len(lead_melodies) > 1: + description += ', '.join(lead_melodies[:-1][0]) + ' and ' + lead_melodies[-1][0] + '.' + + else: + description += lead_melodies[0][0] + '.' + + if len(base_melodies) == 1: + bmword = 'melody' + + else: + bmword = 'melodies' + + description += ' And ' + NUMERALS[len(base_melodies)-1] + ' distinct base ' + bmword + ' played on ' + + if len(base_melodies) > 1: + description += ', '.join(base_melodies[:-1][0]) + ' and ' + base_melodies[-1][0] + '.' + + else: + description += base_melodies[0][0] + '.' + + description += '\n' + + if drums_present and most_common_drums: + + if len(most_common_drums) > 1: + description += 'The drum track has predominant ' + description += ', '.join(most_common_drums[:-1]) + ' and ' + most_common_drums[-1] + '.' + + else: + description += 'The drum track is a solo ' + description += most_common_drums[0] + '.' + + description += '\n' + + #============================================================================== + + return description + +################################################################################### + +#================================================================================== +# +# Below constants code is a courtesy of MidiTok +# +# Retrieved on 12/29/2024 +# +# https://github.com/Natooz/MidiTok/blob/main/src/miditok/constants.py +# +#================================================================================== + +MIDI_FILES_EXTENSIONS = [".mid", ".midi", ".kar", ".MID", ".MIDI", ".KAR"] + +# The recommended pitches for piano in the GM2 specs are from 21 to 108 +PIANO_PITCH_RANGE = range(21, 109) + +# Chord params +# "chord_unknown" specifies the range of number of notes that can form "unknown" chords +# (that do not fit in "chord_maps") to add in tokens. +# Known chord maps, with 0 as root note +BASIC_CHORDS_MAP = { + "min": (0, 3, 7), + "maj": (0, 4, 7), + "dim": (0, 3, 6), + "aug": (0, 4, 8), + "sus2": (0, 2, 7), + "sus4": (0, 5, 7), + "7dom": (0, 4, 7, 10), + "7min": (0, 3, 7, 10), + "7maj": (0, 4, 7, 11), + "7halfdim": (0, 3, 6, 10), + "7dim": (0, 3, 6, 9), + "7aug": (0, 4, 8, 11), + "9maj": (0, 4, 7, 10, 14), + "9min": (0, 4, 7, 10, 13), + } + +# Drums +# Recommended range from the GM2 specs +DRUMS_PITCH_RANGE = range(27, 90) + +# Used with chords +PITCH_CLASSES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"] + +# http://newt.phys.unsw.edu.au/jw/notes.html +# https://www.midi.org/specifications + +# index i = program i+1 in the GM2 specs (7. Appendix A) +# index i = program i as retrieved by packages +MIDI_INSTRUMENTS = [ + # Piano + {"name": "Acoustic Grand Piano", "pitch_range": range(21, 109)}, + {"name": "Bright Acoustic Piano", "pitch_range": range(21, 109)}, + {"name": "Electric Grand Piano", "pitch_range": range(21, 109)}, + {"name": "Honky-tonk Piano", "pitch_range": range(21, 109)}, + {"name": "Electric Piano 1", "pitch_range": range(28, 104)}, + {"name": "Electric Piano 2", "pitch_range": range(28, 104)}, + {"name": "Harpsichord", "pitch_range": range(41, 90)}, + {"name": "Clavi", "pitch_range": range(36, 97)}, + # Chromatic Percussion + {"name": "Celesta", "pitch_range": range(60, 109)}, + {"name": "Glockenspiel", "pitch_range": range(72, 109)}, + {"name": "Music Box", "pitch_range": range(60, 85)}, + {"name": "Vibraphone", "pitch_range": range(53, 90)}, + {"name": "Marimba", "pitch_range": range(48, 85)}, + {"name": "Xylophone", "pitch_range": range(65, 97)}, + {"name": "Tubular Bells", "pitch_range": range(60, 78)}, + {"name": "Dulcimer", "pitch_range": range(60, 85)}, + # Organs + {"name": "Drawbar Organ", "pitch_range": range(36, 97)}, + {"name": "Percussive Organ", "pitch_range": range(36, 97)}, + {"name": "Rock Organ", "pitch_range": range(36, 97)}, + {"name": "Church Organ", "pitch_range": range(21, 109)}, + {"name": "Reed Organ", "pitch_range": range(36, 97)}, + {"name": "Accordion", "pitch_range": range(53, 90)}, + {"name": "Harmonica", "pitch_range": range(60, 85)}, + {"name": "Tango Accordion", "pitch_range": range(53, 90)}, + # Guitars + {"name": "Acoustic Guitar (nylon)", "pitch_range": range(40, 85)}, + {"name": "Acoustic Guitar (steel)", "pitch_range": range(40, 85)}, + {"name": "Electric Guitar (jazz)", "pitch_range": range(40, 87)}, + {"name": "Electric Guitar (clean)", "pitch_range": range(40, 87)}, + {"name": "Electric Guitar (muted)", "pitch_range": range(40, 87)}, + {"name": "Overdriven Guitar", "pitch_range": range(40, 87)}, + {"name": "Distortion Guitar", "pitch_range": range(40, 87)}, + {"name": "Guitar Harmonics", "pitch_range": range(40, 87)}, + # Bass + {"name": "Acoustic Bass", "pitch_range": range(28, 56)}, + {"name": "Electric Bass (finger)", "pitch_range": range(28, 56)}, + {"name": "Electric Bass (pick)", "pitch_range": range(28, 56)}, + {"name": "Fretless Bass", "pitch_range": range(28, 56)}, + {"name": "Slap Bass 1", "pitch_range": range(28, 56)}, + {"name": "Slap Bass 2", "pitch_range": range(28, 56)}, + {"name": "Synth Bass 1", "pitch_range": range(28, 56)}, + {"name": "Synth Bass 2", "pitch_range": range(28, 56)}, + # Strings & Orchestral instruments + {"name": "Violin", "pitch_range": range(55, 94)}, + {"name": "Viola", "pitch_range": range(48, 85)}, + {"name": "Cello", "pitch_range": range(36, 73)}, + {"name": "Contrabass", "pitch_range": range(28, 56)}, + {"name": "Tremolo Strings", "pitch_range": range(28, 94)}, + {"name": "Pizzicato Strings", "pitch_range": range(28, 94)}, + {"name": "Orchestral Harp", "pitch_range": range(23, 104)}, + {"name": "Timpani", "pitch_range": range(36, 58)}, + # Ensembles + {"name": "String Ensembles 1", "pitch_range": range(28, 97)}, + {"name": "String Ensembles 2", "pitch_range": range(28, 97)}, + {"name": "SynthStrings 1", "pitch_range": range(36, 97)}, + {"name": "SynthStrings 2", "pitch_range": range(36, 97)}, + {"name": "Choir Aahs", "pitch_range": range(48, 80)}, + {"name": "Voice Oohs", "pitch_range": range(48, 80)}, + {"name": "Synth Voice", "pitch_range": range(48, 85)}, + {"name": "Orchestra Hit", "pitch_range": range(48, 73)}, + # Brass + {"name": "Trumpet", "pitch_range": range(58, 95)}, + {"name": "Trombone", "pitch_range": range(34, 76)}, + {"name": "Tuba", "pitch_range": range(29, 56)}, + {"name": "Muted Trumpet", "pitch_range": range(58, 83)}, + {"name": "French Horn", "pitch_range": range(41, 78)}, + {"name": "Brass Section", "pitch_range": range(36, 97)}, + {"name": "Synth Brass 1", "pitch_range": range(36, 97)}, + {"name": "Synth Brass 2", "pitch_range": range(36, 97)}, + # Reed + {"name": "Soprano Sax", "pitch_range": range(54, 88)}, + {"name": "Alto Sax", "pitch_range": range(49, 81)}, + {"name": "Tenor Sax", "pitch_range": range(42, 76)}, + {"name": "Baritone Sax", "pitch_range": range(37, 69)}, + {"name": "Oboe", "pitch_range": range(58, 92)}, + {"name": "English Horn", "pitch_range": range(52, 82)}, + {"name": "Bassoon", "pitch_range": range(34, 73)}, + {"name": "Clarinet", "pitch_range": range(50, 92)}, + # Pipe + {"name": "Piccolo", "pitch_range": range(74, 109)}, + {"name": "Flute", "pitch_range": range(60, 97)}, + {"name": "Recorder", "pitch_range": range(60, 97)}, + {"name": "Pan Flute", "pitch_range": range(60, 97)}, + {"name": "Blown Bottle", "pitch_range": range(60, 97)}, + {"name": "Shakuhachi", "pitch_range": range(55, 85)}, + {"name": "Whistle", "pitch_range": range(60, 97)}, + {"name": "Ocarina", "pitch_range": range(60, 85)}, + # Synth Lead + {"name": "Lead 1 (square)", "pitch_range": range(21, 109)}, + {"name": "Lead 2 (sawtooth)", "pitch_range": range(21, 109)}, + {"name": "Lead 3 (calliope)", "pitch_range": range(36, 97)}, + {"name": "Lead 4 (chiff)", "pitch_range": range(36, 97)}, + {"name": "Lead 5 (charang)", "pitch_range": range(36, 97)}, + {"name": "Lead 6 (voice)", "pitch_range": range(36, 97)}, + {"name": "Lead 7 (fifths)", "pitch_range": range(36, 97)}, + {"name": "Lead 8 (bass + lead)", "pitch_range": range(21, 109)}, + # Synth Pad + {"name": "Pad 1 (new age)", "pitch_range": range(36, 97)}, + {"name": "Pad 2 (warm)", "pitch_range": range(36, 97)}, + {"name": "Pad 3 (polysynth)", "pitch_range": range(36, 97)}, + {"name": "Pad 4 (choir)", "pitch_range": range(36, 97)}, + {"name": "Pad 5 (bowed)", "pitch_range": range(36, 97)}, + {"name": "Pad 6 (metallic)", "pitch_range": range(36, 97)}, + {"name": "Pad 7 (halo)", "pitch_range": range(36, 97)}, + {"name": "Pad 8 (sweep)", "pitch_range": range(36, 97)}, + # Synth SFX + {"name": "FX 1 (rain)", "pitch_range": range(36, 97)}, + {"name": "FX 2 (soundtrack)", "pitch_range": range(36, 97)}, + {"name": "FX 3 (crystal)", "pitch_range": range(36, 97)}, + {"name": "FX 4 (atmosphere)", "pitch_range": range(36, 97)}, + {"name": "FX 5 (brightness)", "pitch_range": range(36, 97)}, + {"name": "FX 6 (goblins)", "pitch_range": range(36, 97)}, + {"name": "FX 7 (echoes)", "pitch_range": range(36, 97)}, + {"name": "FX 8 (sci-fi)", "pitch_range": range(36, 97)}, + # Ethnic Misc. + {"name": "Sitar", "pitch_range": range(48, 78)}, + {"name": "Banjo", "pitch_range": range(48, 85)}, + {"name": "Shamisen", "pitch_range": range(50, 80)}, + {"name": "Koto", "pitch_range": range(55, 85)}, + {"name": "Kalimba", "pitch_range": range(48, 80)}, + {"name": "Bag pipe", "pitch_range": range(36, 78)}, + {"name": "Fiddle", "pitch_range": range(55, 97)}, + {"name": "Shanai", "pitch_range": range(48, 73)}, + # Percussive + {"name": "Tinkle Bell", "pitch_range": range(72, 85)}, + {"name": "Agogo", "pitch_range": range(60, 73)}, + {"name": "Steel Drums", "pitch_range": range(52, 77)}, + {"name": "Woodblock", "pitch_range": range(128)}, + {"name": "Taiko Drum", "pitch_range": range(128)}, + {"name": "Melodic Tom", "pitch_range": range(128)}, + {"name": "Synth Drum", "pitch_range": range(128)}, + {"name": "Reverse Cymbal", "pitch_range": range(128)}, + # SFX + {"name": "Guitar Fret Noise, Guitar Cutting Noise", "pitch_range": range(128)}, + {"name": "Breath Noise, Flute Key Click", "pitch_range": range(128)}, + { + "name": "Seashore, Rain, Thunder, Wind, Stream, Bubbles", + "pitch_range": range(128), + }, + {"name": "Bird Tweet, Dog, Horse Gallop", "pitch_range": range(128)}, + { + "name": "Telephone Ring, Door Creaking, Door, Scratch, Wind Chime", + "pitch_range": range(128), + }, + {"name": "Helicopter, Car Sounds", "pitch_range": range(128)}, + { + "name": "Applause, Laughing, Screaming, Punch, Heart Beat, Footstep", + "pitch_range": range(128), + }, + {"name": "Gunshot, Machine Gun, Lasergun, Explosion", "pitch_range": range(128)}, +] + +INSTRUMENTS_CLASSES = [ + {"name": "Piano", "program_range": range(8)}, # 0 + {"name": "Chromatic Percussion", "program_range": range(8, 16)}, + {"name": "Organ", "program_range": range(16, 24)}, + {"name": "Guitar", "program_range": range(24, 32)}, + {"name": "Bass", "program_range": range(32, 40)}, + {"name": "Strings", "program_range": range(40, 48)}, # 5 + {"name": "Ensemble", "program_range": range(48, 56)}, + {"name": "Brass", "program_range": range(56, 64)}, + {"name": "Reed", "program_range": range(64, 72)}, + {"name": "Pipe", "program_range": range(72, 80)}, + {"name": "Synth Lead", "program_range": range(80, 88)}, # 10 + {"name": "Synth Pad", "program_range": range(88, 96)}, + {"name": "Synth Effects", "program_range": range(96, 104)}, + {"name": "Ethnic", "program_range": range(104, 112)}, + {"name": "Percussive", "program_range": range(112, 120)}, + {"name": "Sound Effects", "program_range": range(120, 128)}, # 15 + {"name": "Drums", "program_range": range(-1, 0)}, +] + +# To easily get the class index of any instrument program +CLASS_OF_INST = [ + i + for i, inst_class in enumerate(INSTRUMENTS_CLASSES) + for _ in inst_class["program_range"] +] + +# index i = program i+1 in the GM2 specs (8. Appendix B) +# index i = program i retrieved by packages +DRUMS_SETS = { + 0: "Standard", + 8: "Room", + 16: "Power", + 24: "Electronic", + 25: "Analog", + 32: "Jazz", + 40: "Brush", + 48: "Orchestra", + 56: "SFX", +} + +# Control changes list (without specifications): +# https://www.midi.org/specifications-old/item/table-3-control-change-messages-data-bytes-2 +# Undefined and general control changes are not considered here +# All these attributes can take values from 0 to 127, with some of them being on/off +CONTROL_CHANGES = { + # MSB + 0: "Bank Select", + 1: "Modulation Depth", + 2: "Breath Controller", + 4: "Foot Controller", + 5: "Portamento Time", + 6: "Data Entry", + 7: "Channel Volume", + 8: "Balance", + 10: "Pan", + 11: "Expression Controller", + # LSB + 32: "Bank Select", + 33: "Modulation Depth", + 34: "Breath Controller", + 36: "Foot Controller", + 37: "Portamento Time", + 38: "Data Entry", + 39: "Channel Volume", + 40: "Balance", + 42: "Pan", + 43: "Expression Controller", + # On / Off control changes, ≤63 off, ≥64 on + 64: "Damper Pedal", + 65: "Portamento", + 66: "Sostenuto", + 67: "Soft Pedal", + 68: "Legato Footswitch", + 69: "Hold 2", + # Continuous controls + 70: "Sound Variation", + 71: "Timbre/Harmonic Intensity", + 72: "Release Time", + 73: "Attack Time", + 74: "Brightness", + 75: "Decay Time", + 76: "Vibrato Rate", + 77: "Vibrato Depth", + 78: "Vibrato Delay", + 84: "Portamento Control", + 88: "High Resolution Velocity Prefix", + # Effects depths + 91: "Reverb Depth", + 92: "Tremolo Depth", + 93: "Chorus Depth", + 94: "Celeste Depth", + 95: "Phaser Depth", + # Registered parameters numbers + 96: "Data Increment", + 97: "Data Decrement", + # 98: 'Non-Registered Parameter Number (NRPN) - LSB', + # 99: 'Non-Registered Parameter Number (NRPN) - MSB', + 100: "Registered Parameter Number (RPN) - LSB", + 101: "Registered Parameter Number (RPN) - MSB", + # Channel mode controls + 120: "All Sound Off", + 121: "Reset All Controllers", + 122: "Local Control On/Off", + 123: "All Notes Off", + 124: "Omni Mode Off", # + all notes off + 125: "Omni Mode On", # + all notes off + 126: "Mono Mode On", # + poly off, + all notes off + 127: "Poly Mode On", # + mono off, +all notes off +} + +################################################################################### + +def patches_onset_times(escore_notes, times_idx=1, patches_idx=6): + + patches = [e[patches_idx] for e in escore_notes] + + patches_oset = ordered_set(patches) + + patches_onset_times = [] + + for p in patches_oset: + for e in escore_notes: + if e[patches_idx] == p: + patches_onset_times.append([p, e[times_idx]]) + break + + return patches_onset_times + +################################################################################### + +def count_escore_notes_patches(escore_notes, patches_idx=6): + + patches = [e[patches_idx] for e in escore_notes] + + return Counter(patches).most_common() + +################################################################################### + +def escore_notes_monoponic_melodies(escore_notes, + bad_notes_ratio=0.0, + times_idx=1, + patches_idx=6 + ): + + patches = escore_notes_patches(escore_notes, patches_index=patches_idx) + + monophonic_melodies = [] + + for p in patches: + patch_score = [e for e in escore_notes if e[patches_idx] == p] + + ps_times = [e[times_idx] for e in patch_score] + + if len(ps_times) <= len(set(ps_times)) * (1+bad_notes_ratio): + monophonic_melodies.append([p, len(patch_score)]) + + return monophonic_melodies + +################################################################################### + +from itertools import groupby +from operator import itemgetter + +def group_by_threshold(data, threshold, groupby_idx): + + data.sort(key=itemgetter(groupby_idx)) + + grouped_data = [] + cluster = [] + + for i, item in enumerate(data): + if not cluster: + cluster.append(item) + elif abs(item[groupby_idx] - cluster[-1][groupby_idx]) <= threshold: + cluster.append(item) + else: + grouped_data.append(cluster) + cluster = [item] + + if cluster: + grouped_data.append(cluster) + + return grouped_data + +################################################################################### + +def split_escore_notes_by_time(escore_notes, time_threshold=256): + + dscore = delta_score_notes(escore_notes, timings_clip_value=time_threshold-1) + + score_chunks = [] + + ctime = 0 + pchunk_idx = 0 + + for i, e in enumerate(dscore): + + ctime += e[1] + + if ctime >= time_threshold: + score_chunks.append(escore_notes[pchunk_idx:i]) + pchunk_idx = i + ctime = 0 + + return score_chunks + +################################################################################### + +def escore_notes_grouped_patches(escore_notes, time_threshold=256): + + split_score_chunks = split_escore_notes_by_time(escore_notes, + time_threshold=time_threshold + ) + + chunks_patches = [] + + for s in split_score_chunks: + chunks_patches.append(escore_notes_patches(s)) + + return chunks_patches + +################################################################################### + +def computeLPSArray(pattern, M, lps): + length = 0 + i = 1 + + lps[0] = 0 + + while i < M: + if pattern[i] == pattern[length]: + length += 1 + lps[i] = length + i += 1 + else: + if length != 0: + length = lps[length-1] + else: + lps[i] = 0 + i += 1 + +################################################################################### + +def find_pattern_idxs(sub_pattern, pattern): + + lst = pattern + pattern = sub_pattern + + M = len(pattern) + N = len(lst) + + lps = [0] * M + j = 0 # index for pattern[] + + computeLPSArray(pattern, M, lps) + + i = 0 # index for lst[] + indexes = [] + + while i < N: + if pattern[j] == lst[i]: + i += 1 + j += 1 + + if j == M: + end_index = i - 1 + start_index = end_index - M + 1 + indexes.append((start_index, end_index)) + j = lps[j-1] + elif i < N and pattern[j] != lst[i]: + if j != 0: + j = lps[j-1] + else: + i += 1 + + return indexes + +################################################################################### + +def escore_notes_patch_lrno_patterns(escore_notes, + patch=0, + zero_score_timings=False, + pitches_idx=4, + patches_idx=6 + ): + + patch_escore = [e for e in escore_notes if e[patches_idx] == patch] + + if patch_escore: + + patch_cscore = chordify_score([1000, patch_escore]) + + patch_tscore = [] + + for c in patch_cscore: + + tones_chord = sorted(set([p[pitches_idx] % 12 for p in c])) + + if tones_chord not in ALL_CHORDS_SORTED: + tnoes_chord = check_and_fix_tones_chord(tones_chord) + + patch_tscore.append(ALL_CHORDS_SORTED.index(tones_chord)) + + pattern = find_lrno_pattern_fast(patch_tscore) + + patterns_idxs = find_pattern_idxs(pattern, patch_tscore) + + patch_lrno_scores = [] + + for idxs in patterns_idxs: + + score = patch_escore[idxs[0]:idxs[1]] + + if zero_score_timings: + score = recalculate_score_timings(score) + + patch_lrno_scores.append(score) + + return patch_lrno_scores + + else: + return [] + +################################################################################### + +ALL_BASE_CHORDS_SORTED = [[0], [0, 2], [0, 2, 4], [0, 2, 4, 6], [0, 2, 4, 6, 8], [0, 2, 4, 6, 8, 10], + [0, 2, 4, 6, 9], [0, 2, 4, 6, 10], [0, 2, 4, 7], [0, 2, 4, 7, 9], + [0, 2, 4, 7, 10], [0, 2, 4, 8], [0, 2, 4, 8, 10], [0, 2, 4, 9], [0, 2, 4, 10], + [0, 2, 5], [0, 2, 5, 7], [0, 2, 5, 7, 9], [0, 2, 5, 7, 10], [0, 2, 5, 8], + [0, 2, 5, 8, 10], [0, 2, 5, 9], [0, 2, 5, 10], [0, 2, 6], [0, 2, 6, 8], + [0, 2, 6, 8, 10], [0, 2, 6, 9], [0, 2, 6, 10], [0, 2, 7], [0, 2, 7, 9], + [0, 2, 7, 10], [0, 2, 8], [0, 2, 8, 10], [0, 2, 9], [0, 2, 10], [0, 3], + [0, 3, 5], [0, 3, 5, 7], [0, 3, 5, 7, 9], [0, 3, 5, 7, 10], [0, 3, 5, 8], + [0, 3, 5, 8, 10], [0, 3, 5, 9], [0, 3, 5, 10], [0, 3, 6], [0, 3, 6, 8], + [0, 3, 6, 8, 10], [0, 3, 6, 9], [0, 3, 6, 10], [0, 3, 7], [0, 3, 7, 9], + [0, 3, 7, 10], [0, 3, 8], [0, 3, 8, 10], [0, 3, 9], [0, 3, 10], [0, 4], + [0, 4, 6], [0, 4, 6, 8], [0, 4, 6, 8, 10], [0, 4, 6, 9], [0, 4, 6, 10], + [0, 4, 7], [0, 4, 7, 9], [0, 4, 7, 10], [0, 4, 8], [0, 4, 8, 10], [0, 4, 9], + [0, 4, 10], [0, 5], [0, 5, 7], [0, 5, 7, 9], [0, 5, 7, 10], [0, 5, 8], + [0, 5, 8, 10], [0, 5, 9], [0, 5, 10], [0, 6], [0, 6, 8], [0, 6, 8, 10], + [0, 6, 9], [0, 6, 10], [0, 7], [0, 7, 9], [0, 7, 10], [0, 8], [0, 8, 10], + [0, 9], [0, 10]] + +################################################################################### + +MAJOR_SCALE_CHORDS_COUNTS = [[317, 6610], [320, 6468], [267, 6460], [89, 6329], [301, 6228], [178, 6201], + [0, 5822], [314, 5805], [309, 5677], [319, 5545], [288, 5494], [233, 5395], + [112, 2232], [194, 1956], [127, 1935], [216, 1884], [256, 1871], [283, 1815], + [201, 1768], [16, 1756], [105, 1743], [38, 1727], [23, 1718], [249, 1386], + [272, 796], [91, 770], [191, 740], [303, 735], [181, 718], [306, 717], + [235, 703], [183, 690], [94, 686], [13, 686], [269, 677], [280, 675], + [102, 665], [92, 662], [293, 659], [212, 658], [114, 656], [37, 653], + [180, 651], [215, 644], [316, 640], [290, 636], [5, 636], [110, 625], + [270, 625], [3, 624], [238, 615], [123, 609], [34, 591], [254, 584], + [258, 571], [126, 567], [2, 559], [246, 556], [104, 556], [203, 550], + [291, 537], [311, 522], [304, 520], [193, 509], [236, 496], [199, 493], + [15, 468], [25, 452], [312, 444], [282, 443], [248, 433], [21, 408], + [268, 281], [179, 273], [144, 259], [90, 252], [162, 250], [234, 250], + [1, 246], [221, 214], [73, 213], [43, 213], [45, 213], [134, 212], [318, 210], + [119, 210], [159, 209], [120, 209], [302, 207], [310, 201], [289, 195], + [42, 193], [264, 193], [220, 185], [131, 183], [55, 180], [315, 180], + [132, 176], [30, 174], [31, 172], [209, 171], [227, 169], [217, 163], + [223, 159], [70, 158], [39, 157], [36, 153], [214, 142], [196, 141], + [285, 141], [8, 137], [208, 133], [125, 133], [147, 130], [186, 130], + [97, 130], [49, 130], [58, 130], [128, 130], [138, 128], [241, 125], + [228, 124], [263, 120], [251, 120], [275, 119], [296, 118], [259, 116], + [99, 114], [10, 113], [50, 111], [273, 111], [139, 111], [298, 106], [18, 105], + [153, 105], [7, 101], [277, 101], [243, 99], [96, 99], [9, 96], [160, 96], + [188, 95], [115, 94], [24, 93], [107, 92], [204, 90], [150, 90], [148, 84], + [202, 83], [213, 82], [187, 82], [35, 80], [113, 79], [98, 78], [239, 77], + [59, 77], [26, 76], [281, 76], [184, 75], [64, 75], [124, 75], [71, 75], + [257, 75], [95, 74], [294, 73], [192, 70], [247, 70], [61, 67], [307, 66], + [242, 65], [218, 65], [146, 64], [276, 63], [6, 63], [68, 60], [284, 59], + [103, 59], [297, 56], [14, 56], [185, 55], [57, 55], [40, 55], [129, 54], + [274, 52], [308, 52], [46, 51], [224, 49], [240, 47], [135, 46], [17, 45], + [295, 45], [106, 45], [48, 44], [157, 44], [206, 43], [195, 42], [158, 42], + [69, 41], [117, 41], [225, 40], [222, 37], [226, 35], [261, 34], [164, 32], + [75, 32], [28, 32], [11, 32], [250, 31], [44, 30], [137, 28], [47, 26], + [133, 26], [255, 25], [182, 24], [136, 24], [197, 23], [93, 23], [237, 22], + [287, 22], [165, 22], [79, 21], [271, 21], [109, 21], [253, 20], [76, 20], + [168, 19], [155, 19], [149, 19], [108, 19], [4, 18], [51, 18], [292, 18], + [198, 18], [41, 17], [286, 17], [19, 17], [219, 17], [173, 17], [66, 16], + [54, 16], [229, 16], [140, 16], [175, 15], [171, 15], [82, 15], [130, 15], + [20, 15], [230, 15], [244, 14], [145, 14], [84, 14], [305, 14], [278, 14], + [86, 13], [60, 13], [232, 12], [100, 12], [141, 12], [52, 12], [189, 12], + [252, 12], [56, 11], [53, 11], [143, 10], [151, 10], [154, 10], [163, 9], + [116, 9], [27, 9], [65, 9], [313, 9], [205, 9], [170, 8], [62, 8], [299, 7], + [142, 7], [231, 7], [156, 6], [22, 6], [63, 6], [152, 6], [77, 5], [67, 5], + [166, 5], [174, 5], [85, 4], [72, 4], [190, 4], [111, 4], [101, 4], [200, 4], + [12, 4], [245, 3], [300, 3], [279, 3], [81, 2], [210, 2], [32, 2], [265, 2], + [260, 2], [74, 2], [161, 1], [207, 1], [29, 1], [118, 1], [262, 1], [121, 1]] + +################################################################################### + +MINOR_SCALE_CHORDS_COUNTS = [[267, 10606], [89, 10562], [301, 10522], [320, 10192], [178, 10191], + [317, 10153], [233, 10101], [314, 10065], [288, 9914], [0, 9884], [309, 9694], + [319, 9648], [114, 1963], [193, 1778], [25, 1705], [104, 1689], [248, 1671], + [282, 1614], [283, 1610], [127, 1530], [203, 1525], [37, 1508], [215, 1473], + [105, 1465], [38, 1462], [258, 1445], [112, 1419], [94, 1413], [280, 1391], + [194, 1388], [126, 1384], [16, 1374], [272, 1370], [23, 1364], [238, 1351], + [306, 1342], [303, 1340], [5, 1338], [183, 1334], [102, 1333], [290, 1322], + [269, 1312], [191, 1311], [249, 1305], [15, 1291], [246, 1290], [316, 1288], + [13, 1279], [216, 1278], [235, 1275], [256, 1268], [311, 1241], [293, 1228], + [91, 1219], [180, 1173], [34, 1167], [2, 1138], [212, 1131], [123, 1118], + [201, 1103], [270, 1017], [304, 961], [181, 958], [92, 943], [3, 940], + [236, 932], [254, 923], [291, 921], [110, 920], [21, 911], [312, 891], + [199, 832], [268, 431], [179, 395], [234, 395], [302, 385], [144, 368], + [90, 365], [289, 362], [310, 352], [318, 350], [1, 332], [55, 323], [315, 322], + [8, 307], [162, 304], [97, 302], [186, 302], [241, 300], [10, 299], [217, 289], + [275, 275], [128, 267], [73, 266], [243, 265], [125, 262], [296, 259], + [298, 251], [36, 250], [39, 250], [99, 249], [214, 231], [119, 230], + [120, 227], [188, 227], [159, 226], [264, 225], [263, 225], [138, 223], + [31, 222], [227, 219], [134, 216], [277, 214], [70, 210], [209, 207], + [30, 203], [49, 186], [46, 185], [45, 184], [221, 172], [281, 170], [96, 169], + [131, 169], [224, 165], [148, 159], [59, 157], [43, 157], [7, 157], [247, 155], + [208, 153], [132, 152], [274, 150], [223, 149], [135, 148], [273, 148], + [240, 137], [220, 132], [185, 131], [239, 131], [42, 130], [147, 119], + [213, 117], [307, 115], [24, 112], [95, 108], [192, 107], [150, 106], + [294, 105], [106, 104], [58, 102], [103, 102], [17, 100], [129, 100], [61, 99], + [9, 98], [139, 96], [295, 96], [284, 96], [146, 96], [218, 95], [184, 94], + [308, 87], [195, 87], [40, 86], [14, 85], [50, 82], [250, 82], [285, 81], + [57, 79], [259, 79], [6, 79], [276, 78], [228, 78], [35, 76], [187, 75], + [242, 73], [206, 73], [160, 72], [113, 72], [117, 72], [261, 72], [98, 71], + [202, 70], [115, 70], [158, 69], [71, 68], [48, 67], [28, 67], [204, 66], + [157, 64], [124, 63], [257, 59], [196, 59], [69, 59], [68, 57], [251, 55], + [225, 50], [137, 50], [107, 49], [165, 49], [297, 48], [64, 46], [153, 45], + [226, 44], [198, 44], [287, 43], [26, 43], [219, 41], [253, 40], [109, 40], + [66, 39], [47, 39], [41, 39], [76, 38], [11, 38], [136, 38], [130, 36], + [155, 35], [18, 31], [93, 31], [20, 30], [271, 29], [4, 28], [292, 28], + [237, 27], [182, 26], [62, 26], [164, 25], [151, 25], [108, 25], [286, 24], + [145, 24], [305, 24], [75, 24], [56, 23], [149, 23], [252, 23], [197, 23], + [255, 23], [313, 21], [60, 18], [244, 17], [278, 17], [189, 17], [100, 16], + [299, 15], [200, 13], [175, 13], [111, 13], [22, 13], [170, 12], [232, 11], + [86, 11], [141, 11], [52, 11], [65, 10], [173, 10], [133, 10], [222, 10], + [143, 10], [154, 9], [82, 8], [19, 8], [85, 8], [44, 8], [84, 8], [163, 7], + [205, 7], [230, 7], [54, 7], [174, 7], [116, 7], [27, 7], [171, 7], [229, 6], + [81, 5], [79, 4], [142, 4], [231, 4], [210, 3], [168, 3], [53, 3], [51, 3], + [74, 3], [265, 3], [260, 3], [152, 2], [245, 2], [279, 2], [190, 2], [12, 2], + [101, 2], [262, 1], [63, 1], [72, 1], [207, 1], [166, 1], [83, 1], [176, 1], + [118, 1], [67, 1], [172, 1], [29, 1], [121, 1], [77, 1], [266, 1], [156, 1], + [211, 1], [300, 1], [87, 1], [140, 1], [161, 1]] + +################################################################################### + +def get_weighted_score(src_order, trg_order): + + score = 0 + + for i, (item, count) in enumerate(src_order): + if item in trg_order: + score += count * abs(i - trg_order.index(item)) + + else: + score += count * len(trg_order) + + return score + +################################################################################### + +def escore_notes_scale(escore_notes, + score_mult_factor=3, + start_note=0, + num_notes=-1, + return_scale_indexes=False + ): + + trg_chords = [] + + for i in range(-score_mult_factor, score_mult_factor): + + trans_escore_notes = transpose_escore_notes(escore_notes[start_note:start_note+num_notes], i) + + cscore = chordify_score([1000, trans_escore_notes]) + + tones_chords = [] + + for c in cscore: + + seen = [] + pitches = [] + + for e in c: + + if e[4] not in seen: + pitches.append(e[4]) + seen.append(e[4]) + + if pitches: + + tones_chord = sorted(set([p % 12 for p in pitches])) + + if tones_chord not in ALL_CHORDS_SORTED: + tones_chord = check_and_fix_tones_chord(tones_chord) + + tones_chords.append(ALL_CHORDS_SORTED.index(tones_chord)) + + if tones_chords: + trg_chords.extend(tones_chords) + + #======================================================================== + + scales_results = [] + + #======================================================================== + + if trg_chords: + + #======================================================================== + + src_order = Counter(trg_chords).most_common() + + trg1_items = [item for item, count in MAJOR_SCALE_CHORDS_COUNTS] + trg2_items = [item for item, count in MINOR_SCALE_CHORDS_COUNTS] + + + trg1_score = get_weighted_score(src_order, trg1_items) + trg2_score = get_weighted_score(src_order, trg2_items) + + #======================================================================== + + if trg1_score <= trg2_score: + + if return_scale_indexes: + scales_results.append(1) + + else: + scales_results.append('Major') + + else: + if return_scale_indexes: + scales_results.append(0) + + else: + scales_results.append('Minor') + + #======================================================================== + + best_match = None + best_score = float('inf') + + for trg_order in ALL_MOOD_TYPES: + + trg_items = [item for item, count in trg_order] + + trg_score = get_weighted_score(src_order, trg_items) + + if trg_score < best_score: + best_score = trg_score + + if return_scale_indexes: + best_match = ALL_MOOD_TYPES.index(trg_order) + + else: + best_match = ALL_MOOD_TYPES_LABELS[ALL_MOOD_TYPES.index(trg_order)] + + scales_results.append(best_match) + + else: + if return_scale_indexes: + scales_results.extend([-1, -1]) + + else: + scales_results.extend(['Unknown', 'Unknown']) + + return scales_results + +################################################################################### + +HAPPY_MAJOR = [(317, 1916), (89, 1876), (320, 1840), (267, 1817), (301, 1795), (178, 1750), + (314, 1725), (0, 1691), (319, 1658), (288, 1624), (309, 1599), (233, 1559), + (112, 1050), (127, 972), (201, 884), (194, 879), (216, 860), (38, 831), + (256, 828), (23, 822), (105, 820), (283, 756), (16, 734), (249, 622), + (91, 254), (303, 242), (34, 237), (316, 235), (110, 235), (123, 234), + (212, 230), (92, 225), (181, 225), (114, 219), (272, 218), (290, 213), + (235, 208), (180, 207), (269, 206), (2, 201), (3, 199), (203, 198), (37, 195), + (254, 191), (199, 189), (311, 189), (293, 187), (5, 186), (270, 185), + (183, 184), (291, 183), (94, 183), (25, 182), (304, 181), (258, 176), + (215, 173), (191, 172), (193, 168), (104, 167), (282, 164), (238, 162), + (248, 157), (15, 156), (13, 156), (126, 153), (21, 150), (102, 150), + (306, 150), (312, 144), (280, 141), (236, 139), (162, 116), (120, 114), + (246, 113), (134, 109), (43, 108), (221, 105), (264, 103), (73, 100), + (159, 98), (42, 95), (45, 94), (220, 93), (131, 91), (119, 91), (227, 90), + (209, 88), (70, 86), (144, 86), (31, 85), (223, 84), (58, 82), (1, 80), + (132, 79), (30, 76), (90, 75), (268, 75), (259, 74), (234, 72), (179, 72), + (147, 70), (318, 69), (208, 67), (315, 66), (55, 66), (49, 64), (310, 63), + (138, 62), (214, 61), (263, 60), (204, 59), (302, 58), (196, 58), (115, 56), + (107, 53), (18, 53), (153, 52), (289, 52), (9, 50), (10, 50), (217, 49), + (243, 48), (39, 48), (99, 48), (7, 47), (188, 46), (26, 46), (68, 46), + (36, 45), (125, 43), (202, 43), (285, 42), (24, 42), (277, 41), (98, 40), + (251, 39), (113, 39), (8, 38), (128, 38), (187, 37), (35, 36), (213, 36), + (97, 35), (186, 35), (61, 34), (150, 34), (160, 33), (124, 32), (96, 32), + (257, 32), (275, 31), (241, 31), (296, 30), (64, 30), (297, 29), (298, 29), + (117, 29), (46, 28), (273, 28), (206, 28), (157, 27), (242, 26), (224, 26), + (185, 26), (222, 26), (59, 25), (135, 24), (158, 23), (28, 23), (294, 22), + (69, 22), (276, 21), (274, 21), (225, 21), (148, 20), (50, 20), (48, 20), + (281, 19), (139, 19), (307, 19), (228, 19), (75, 18), (164, 18), (44, 18), + (133, 18), (79, 17), (184, 17), (57, 17), (240, 17), (239, 17), (295, 17), + (247, 16), (95, 16), (261, 15), (308, 15), (287, 14), (76, 14), (165, 14), + (175, 14), (82, 14), (284, 14), (71, 14), (253, 12), (155, 12), (86, 12), + (4, 12), (93, 12), (171, 12), (137, 12), (66, 11), (232, 11), (168, 11), + (103, 11), (192, 11), (54, 10), (145, 10), (40, 10), (51, 10), (182, 10), + (226, 10), (14, 10), (129, 9), (218, 9), (146, 9), (237, 9), (19, 9), (108, 9), + (197, 9), (140, 8), (229, 8), (6, 7), (17, 7), (56, 6), (106, 6), (271, 6), + (109, 6), (163, 5), (143, 5), (65, 5), (154, 5), (27, 5), (116, 5), (205, 5), + (195, 5), (250, 5), (198, 5), (41, 5), (136, 5), (47, 4), (52, 4), (141, 4), + (230, 4), (84, 4), (173, 4), (255, 4), (11, 4), (100, 4), (189, 4), (244, 4), + (278, 4), (219, 3), (20, 3), (286, 3), (130, 3), (170, 3), (151, 3), (53, 2), + (77, 2), (166, 2), (67, 2), (156, 2), (63, 2), (60, 2), (292, 2), (62, 2), + (142, 1), (231, 1), (85, 1), (174, 1), (81, 1), (152, 1), (262, 1), (72, 1), + (161, 1), (29, 1), (118, 1), (207, 1), (149, 1), (300, 1), (299, 1), (252, 1)] + +################################################################################### + +MELANCHOLIC_MAJOR = [(317, 451), (301, 430), (89, 426), (320, 419), (267, 416), (178, 415), + (314, 401), (319, 400), (0, 394), (309, 390), (288, 389), (233, 365), + (37, 224), (215, 207), (258, 203), (126, 191), (114, 185), (203, 183), + (283, 141), (127, 131), (38, 127), (216, 115), (194, 113), (112, 112), + (23, 109), (105, 105), (249, 103), (16, 99), (306, 96), (256, 92), (13, 87), + (280, 86), (181, 86), (102, 85), (92, 84), (104, 84), (15, 84), (191, 83), + (246, 83), (270, 81), (94, 74), (3, 73), (238, 72), (272, 72), (236, 72), + (201, 72), (183, 70), (293, 66), (193, 63), (254, 63), (212, 61), (282, 60), + (123, 58), (5, 57), (25, 55), (291, 53), (34, 52), (316, 50), (304, 48), + (91, 47), (2, 47), (110, 46), (248, 45), (303, 38), (311, 38), (45, 36), + (180, 35), (199, 34), (235, 33), (162, 33), (221, 33), (21, 32), (144, 32), + (132, 31), (179, 29), (90, 29), (43, 29), (217, 29), (312, 28), (39, 28), + (128, 28), (302, 27), (268, 27), (36, 27), (125, 27), (269, 26), (134, 26), + (234, 26), (73, 25), (318, 25), (55, 25), (1, 24), (290, 23), (8, 22), + (310, 22), (315, 22), (97, 20), (186, 20), (241, 20), (275, 20), (296, 20), + (289, 20), (119, 18), (298, 18), (31, 17), (6, 17), (95, 17), (184, 17), + (273, 17), (223, 16), (276, 15), (120, 15), (239, 15), (30, 15), (208, 14), + (59, 14), (159, 13), (146, 13), (42, 13), (209, 13), (26, 13), (264, 13), + (147, 13), (187, 13), (242, 13), (115, 12), (220, 12), (70, 12), (226, 12), + (47, 12), (148, 12), (24, 11), (49, 11), (131, 10), (227, 10), (214, 10), + (136, 9), (225, 9), (69, 9), (138, 9), (158, 9), (106, 9), (98, 9), (257, 8), + (263, 8), (297, 8), (50, 8), (204, 8), (259, 8), (7, 8), (294, 8), (281, 8), + (9, 8), (113, 7), (202, 7), (17, 7), (124, 7), (213, 7), (57, 7), (96, 7), + (247, 7), (285, 6), (185, 6), (130, 6), (219, 6), (218, 6), (58, 6), (139, 5), + (35, 5), (240, 5), (195, 5), (250, 5), (20, 5), (284, 5), (150, 5), (261, 5), + (48, 5), (107, 4), (196, 4), (251, 4), (292, 4), (41, 4), (228, 4), (61, 4), + (71, 4), (160, 4), (109, 4), (103, 4), (192, 4), (206, 4), (137, 4), (274, 3), + (18, 3), (305, 3), (295, 3), (93, 3), (308, 3), (182, 3), (237, 3), (271, 3), + (198, 3), (168, 3), (51, 3), (140, 3), (229, 3), (54, 3), (155, 3), (10, 3), + (99, 3), (157, 2), (64, 2), (143, 2), (224, 2), (253, 2), (307, 2), (66, 2), + (40, 2), (129, 2), (188, 2), (11, 2), (243, 2), (28, 1), (117, 1), (4, 1), + (313, 1), (62, 1), (151, 1), (56, 1), (135, 1), (46, 1), (165, 1), (79, 1), + (299, 1), (60, 1), (149, 1), (22, 1), (111, 1), (200, 1)] + +################################################################################### + +MELANCHOLIC_MINOR = [(89, 3681), (267, 3628), (317, 3472), (301, 3408), (320, 3290), (178, 3261), + (314, 3261), (288, 3206), (0, 3140), (233, 3050), (319, 2894), (309, 2841), + (114, 570), (283, 559), (104, 544), (193, 529), (215, 509), (37, 507), + (127, 482), (126, 468), (38, 456), (282, 432), (248, 417), (25, 415), + (194, 414), (216, 412), (112, 411), (258, 407), (23, 403), (105, 399), + (249, 399), (303, 387), (203, 386), (15, 366), (256, 356), (16, 351), + (290, 343), (316, 343), (269, 332), (235, 323), (91, 312), (311, 296), + (272, 286), (34, 273), (94, 271), (180, 269), (212, 265), (123, 260), + (306, 259), (270, 254), (102, 246), (201, 246), (238, 246), (280, 242), + (110, 236), (183, 236), (191, 232), (293, 230), (5, 228), (2, 228), (291, 226), + (304, 225), (13, 219), (312, 207), (21, 207), (181, 203), (92, 195), + (246, 192), (3, 191), (254, 181), (236, 173), (199, 155), (268, 124), + (179, 114), (144, 103), (90, 103), (302, 102), (318, 101), (234, 99), + (289, 86), (1, 84), (310, 83), (31, 79), (120, 79), (55, 78), (315, 72), + (162, 72), (264, 71), (73, 70), (209, 69), (159, 61), (227, 61), (263, 60), + (49, 58), (138, 57), (119, 51), (273, 49), (70, 49), (10, 47), (8, 44), + (97, 44), (186, 44), (241, 44), (275, 44), (99, 44), (146, 43), (239, 42), + (296, 39), (214, 39), (217, 39), (95, 38), (148, 37), (36, 36), (281, 34), + (307, 33), (125, 33), (218, 32), (59, 31), (134, 31), (160, 31), (184, 31), + (129, 29), (208, 29), (223, 29), (71, 29), (30, 29), (96, 27), (147, 27), + (228, 27), (57, 27), (6, 27), (284, 26), (50, 26), (139, 26), (247, 24), + (24, 24), (250, 24), (115, 24), (204, 24), (259, 24), (9, 23), (240, 23), + (274, 23), (220, 23), (58, 23), (103, 22), (40, 22), (131, 22), (243, 22), + (106, 22), (285, 22), (46, 22), (295, 21), (308, 21), (221, 21), (14, 20), + (45, 20), (42, 20), (195, 20), (294, 19), (188, 19), (277, 19), (185, 18), + (192, 18), (17, 18), (135, 18), (224, 18), (7, 17), (61, 17), (150, 16), + (225, 14), (69, 14), (158, 14), (128, 14), (257, 14), (149, 13), (64, 13), + (298, 13), (39, 13), (213, 12), (113, 12), (43, 11), (132, 11), (28, 11), + (35, 10), (124, 10), (47, 10), (136, 10), (41, 10), (130, 10), (157, 10), + (202, 10), (165, 10), (66, 9), (155, 9), (219, 9), (153, 9), (18, 9), (255, 9), + (11, 9), (60, 8), (22, 8), (111, 8), (107, 8), (299, 7), (143, 7), (232, 7), + (86, 7), (175, 7), (276, 6), (313, 6), (56, 6), (62, 6), (278, 6), (151, 6), + (26, 6), (117, 6), (206, 6), (196, 6), (98, 5), (187, 5), (242, 5), (200, 5), + (109, 5), (198, 5), (229, 5), (54, 5), (305, 5), (261, 5), (48, 5), (76, 5), + (226, 5), (145, 4), (20, 4), (251, 4), (68, 4), (292, 4), (253, 4), (287, 4), + (244, 3), (4, 3), (189, 3), (93, 2), (182, 2), (237, 2), (297, 2), (100, 2), + (173, 2), (53, 2), (142, 2), (231, 2), (85, 2), (174, 2), (271, 2), (137, 2), + (82, 2), (171, 2), (164, 1), (44, 1), (133, 1), (222, 1), (163, 1), (65, 1), + (154, 1), (27, 1), (116, 1), (205, 1)] + +################################################################################### + +NEUTRAL_MAJOR = [(320, 574), (89, 542), (0, 535), (317, 488), (319, 458), (314, 439), + (178, 424), (267, 405), (233, 375), (301, 330), (309, 321), (288, 287), + (283, 77), (112, 76), (38, 71), (23, 67), (216, 61), (127, 59), (291, 54), + (316, 52), (269, 51), (290, 51), (34, 50), (303, 50), (110, 49), (280, 47), + (13, 45), (311, 44), (306, 43), (238, 43), (272, 43), (3, 42), (21, 42), + (16, 41), (270, 41), (183, 39), (102, 39), (92, 39), (312, 37), (105, 37), + (194, 37), (199, 35), (191, 35), (246, 35), (5, 35), (181, 34), (304, 34), + (94, 33), (293, 31), (91, 29), (268, 27), (236, 27), (256, 27), (144, 24), + (90, 24), (179, 23), (234, 23), (302, 23), (235, 23), (2, 23), (318, 22), + (1, 22), (254, 22), (123, 22), (315, 22), (212, 22), (249, 22), (8, 21), + (97, 21), (186, 21), (241, 21), (289, 21), (180, 21), (310, 21), (201, 21), + (104, 20), (214, 19), (55, 18), (296, 17), (275, 17), (36, 17), (125, 17), + (193, 16), (58, 16), (147, 16), (10, 15), (37, 14), (215, 14), (15, 14), + (25, 14), (114, 14), (217, 13), (282, 12), (259, 12), (9, 12), (98, 12), + (187, 12), (99, 11), (126, 10), (248, 10), (188, 10), (243, 10), (277, 10), + (264, 10), (96, 10), (73, 10), (162, 10), (43, 10), (128, 10), (203, 8), + (150, 8), (221, 8), (39, 8), (24, 8), (113, 8), (274, 6), (295, 6), (308, 6), + (159, 6), (258, 6), (120, 6), (42, 6), (131, 6), (220, 6), (30, 6), (132, 6), + (7, 6), (298, 6), (119, 6), (228, 4), (185, 4), (71, 4), (240, 4), (160, 4), + (153, 4), (18, 4), (61, 4), (35, 4), (285, 4), (209, 4), (95, 4), (307, 4), + (146, 4), (184, 4), (239, 4), (202, 4), (247, 4), (273, 4), (257, 4), (281, 4), + (64, 2), (156, 2), (50, 2), (63, 2), (45, 2), (139, 2), (152, 2), (134, 2), + (124, 2), (107, 2), (12, 2), (11, 2), (223, 2), (213, 2), (196, 2), (101, 2), + (31, 2), (251, 2), (190, 2), (106, 2), (40, 2), (195, 2), (6, 2), (129, 2), + (250, 2), (218, 2), (284, 2), (294, 2), (57, 2), (59, 2), (148, 2)] + +################################################################################### + +NEUTRAL_MINOR = [(317, 530), (301, 499), (267, 454), (309, 438), (314, 422), (288, 420), + (178, 415), (320, 414), (89, 399), (319, 383), (0, 341), (233, 307), + (215, 133), (37, 127), (212, 123), (193, 121), (123, 121), (34, 119), + (191, 117), (126, 115), (104, 108), (112, 107), (272, 105), (23, 102), + (15, 96), (127, 92), (38, 87), (283, 85), (102, 84), (91, 83), (94, 83), + (306, 82), (216, 80), (2, 80), (280, 79), (293, 78), (5, 78), (13, 77), + (183, 76), (114, 74), (316, 69), (105, 68), (180, 64), (201, 62), (256, 58), + (16, 56), (246, 55), (203, 55), (303, 52), (194, 52), (282, 49), (311, 49), + (248, 47), (238, 43), (258, 41), (249, 39), (7, 32), (10, 29), (96, 29), + (25, 28), (125, 27), (214, 27), (36, 26), (134, 23), (99, 22), (310, 22), + (270, 21), (291, 20), (223, 20), (302, 20), (213, 19), (185, 19), (217, 19), + (3, 19), (221, 19), (45, 18), (268, 16), (289, 16), (235, 15), (179, 14), + (234, 14), (181, 14), (312, 13), (240, 13), (21, 13), (274, 13), (110, 13), + (92, 13), (236, 13), (31, 13), (120, 13), (304, 12), (269, 11), (113, 11), + (150, 10), (43, 10), (132, 10), (68, 9), (157, 9), (202, 9), (55, 9), (144, 9), + (315, 9), (318, 9), (42, 9), (131, 9), (188, 8), (70, 8), (159, 8), (241, 7), + (275, 7), (296, 7), (8, 7), (290, 7), (97, 7), (186, 7), (24, 7), (119, 7), + (227, 7), (254, 6), (219, 6), (35, 6), (273, 6), (124, 6), (294, 6), (247, 6), + (220, 6), (281, 6), (208, 6), (46, 6), (61, 6), (243, 5), (199, 5), (128, 5), + (30, 5), (11, 5), (218, 5), (192, 5), (162, 5), (257, 5), (138, 5), (264, 5), + (148, 4), (41, 4), (130, 4), (39, 4), (307, 4), (40, 4), (129, 4), (17, 4), + (106, 4), (195, 4), (224, 4), (135, 4), (209, 4), (276, 3), (297, 3), (26, 3), + (115, 3), (277, 3), (20, 3), (109, 3), (198, 3), (6, 3), (298, 3), (95, 3), + (184, 3), (1, 3), (165, 3), (66, 3), (155, 3), (73, 3), (69, 3), (158, 3), + (71, 3), (160, 3), (64, 3), (153, 3), (18, 3), (107, 3), (187, 2), (242, 2), + (59, 2), (239, 2), (226, 2), (163, 2), (14, 2), (65, 2), (263, 2), (103, 2), + (154, 2), (49, 2), (27, 2), (253, 2), (116, 2), (287, 2), (205, 2), (4, 1), + (93, 1), (182, 1), (237, 1), (271, 1), (292, 1), (222, 1), (19, 1), (108, 1), + (197, 1), (57, 1), (146, 1), (143, 1), (211, 1), (232, 1), (266, 1), (47, 1), + (86, 1), (87, 1), (136, 1), (175, 1), (176, 1), (225, 1), (82, 1), (83, 1), + (171, 1), (172, 1), (117, 1), (206, 1), (261, 1), (48, 1), (137, 1), (90, 1), + (204, 1), (250, 1), (259, 1), (284, 1)] + +################################################################################### + +SAD_MAJOR = [(267, 46), (301, 45), (178, 43), (89, 37), (288, 35), (233, 35), (215, 34), + (317, 32), (320, 32), (309, 30), (314, 24), (0, 22), (319, 21), (114, 19), + (203, 19), (258, 19), (37, 19), (193, 18), (126, 18), (15, 17), (104, 17), + (248, 16), (282, 16), (112, 13), (134, 13), (105, 10), (221, 10), (194, 10), + (45, 10), (162, 8), (43, 8), (201, 8), (132, 8), (256, 8), (16, 8), (127, 7), + (283, 6), (38, 6), (306, 5), (223, 5), (216, 5), (31, 5), (23, 5), (120, 5), + (272, 4), (123, 4), (293, 4), (119, 3), (181, 3), (125, 3), (94, 3), (236, 3), + (212, 3), (183, 3), (270, 3), (2, 3), (238, 3), (291, 3), (91, 3), (304, 3), + (209, 3), (312, 3), (264, 3), (163, 2), (148, 2), (157, 2), (316, 2), (217, 2), + (13, 2), (65, 2), (208, 2), (7, 2), (214, 2), (34, 2), (36, 2), (102, 2), + (154, 2), (249, 2), (263, 2), (96, 2), (10, 2), (191, 2), (27, 2), (49, 2), + (99, 2), (116, 2), (138, 2), (180, 2), (205, 2), (227, 2), (235, 2), (226, 1), + (298, 1), (307, 1), (213, 1), (159, 1), (292, 1), (144, 1), (147, 1), (290, 1), + (47, 1), (39, 1), (40, 1), (42, 1), (305, 1), (68, 1), (1, 1), (9, 1), + (303, 1), (136, 1), (128, 1), (129, 1), (131, 1), (313, 1), (90, 1), (98, 1), + (311, 1), (225, 1), (218, 1), (185, 1), (220, 1), (62, 1), (179, 1), (187, 1), + (59, 1), (246, 1), (69, 1), (57, 1), (247, 1), (240, 1), (30, 1), (151, 1), + (188, 1), (239, 1), (234, 1), (242, 1), (280, 1), (158, 1), (146, 1), (281, 1), + (274, 1), (56, 1), (243, 1), (273, 1), (268, 1), (276, 1)] + +################################################################################### + +SAD_MINOR = [(178, 1800), (267, 1764), (233, 1727), (309, 1671), (288, 1644), (0, 1610), + (301, 1580), (320, 1532), (89, 1512), (317, 1454), (319, 1417), (314, 1383), + (272, 238), (269, 232), (183, 230), (180, 224), (212, 219), (34, 217), + (238, 217), (311, 214), (2, 212), (5, 210), (303, 208), (293, 206), (91, 202), + (94, 202), (235, 200), (13, 199), (290, 198), (316, 192), (3, 190), (306, 188), + (280, 187), (193, 185), (291, 184), (123, 183), (191, 182), (37, 179), + (199, 172), (102, 169), (181, 164), (110, 163), (92, 163), (246, 161), + (21, 157), (236, 156), (312, 154), (270, 146), (203, 146), (15, 144), + (126, 135), (25, 135), (114, 135), (304, 132), (215, 131), (104, 131), + (254, 130), (38, 124), (112, 124), (282, 123), (216, 114), (23, 111), + (127, 102), (201, 101), (16, 100), (283, 96), (248, 96), (289, 92), (268, 92), + (194, 92), (258, 91), (310, 87), (105, 86), (302, 81), (179, 77), (234, 77), + (249, 76), (256, 76), (318, 60), (315, 57), (1, 53), (8, 49), (186, 47), + (90, 47), (97, 47), (224, 47), (55, 46), (241, 46), (275, 46), (296, 45), + (45, 43), (144, 42), (46, 38), (274, 37), (42, 36), (135, 36), (134, 34), + (217, 31), (214, 30), (59, 30), (61, 30), (240, 28), (148, 28), (70, 28), + (159, 28), (73, 27), (49, 27), (277, 26), (295, 26), (308, 26), (138, 26), + (227, 26), (223, 25), (10, 25), (120, 25), (221, 24), (31, 24), (128, 24), + (185, 23), (39, 23), (99, 23), (36, 23), (150, 21), (243, 21), (162, 21), + (7, 20), (206, 18), (298, 18), (96, 18), (125, 18), (284, 16), (198, 16), + (209, 16), (264, 16), (43, 16), (14, 15), (213, 15), (132, 15), (158, 14), + (28, 14), (188, 13), (117, 13), (35, 13), (253, 12), (103, 12), (192, 12), + (220, 12), (30, 12), (225, 11), (69, 11), (287, 11), (131, 11), (24, 10), + (119, 10), (208, 10), (261, 9), (48, 9), (76, 9), (165, 9), (9, 9), (66, 9), + (4, 9), (195, 8), (250, 8), (58, 8), (147, 8), (247, 8), (281, 8), (47, 7), + (219, 7), (20, 7), (109, 7), (56, 7), (242, 6), (204, 6), (259, 6), (137, 6), + (226, 6), (292, 6), (93, 6), (62, 6), (98, 6), (151, 6), (187, 5), (115, 5), + (273, 5), (294, 5), (17, 5), (130, 5), (106, 5), (145, 5), (313, 5), (182, 5), + (239, 5), (237, 5), (276, 4), (6, 4), (41, 4), (57, 4), (113, 4), (124, 4), + (146, 4), (271, 4), (18, 4), (297, 3), (40, 3), (129, 3), (19, 3), (68, 3), + (95, 3), (108, 3), (157, 3), (184, 3), (197, 3), (232, 3), (86, 3), (175, 3), + (82, 3), (228, 3), (71, 3), (160, 3), (64, 3), (153, 3), (26, 2), (307, 2), + (60, 2), (218, 2), (222, 2), (305, 2), (202, 2), (263, 2), (11, 2), (136, 2), + (171, 2), (79, 2), (244, 1), (278, 1), (299, 1), (149, 1), (22, 1), (257, 1), + (252, 1), (286, 1), (75, 1), (77, 1), (54, 1), (166, 1), (143, 1), (67, 1), + (156, 1), (63, 1), (152, 1), (107, 1), (196, 1), (251, 1), (285, 1), (50, 1)] + +################################################################################### + +UPLIFTING_MAJOR = [(267, 3776), (317, 3723), (301, 3628), (320, 3603), (178, 3569), (89, 3448), + (309, 3337), (314, 3216), (0, 3180), (288, 3159), (233, 3061), (319, 3008), + (112, 981), (194, 917), (256, 916), (16, 874), (216, 843), (283, 835), + (201, 783), (105, 771), (127, 766), (23, 715), (38, 692), (249, 637), + (272, 459), (191, 448), (91, 437), (235, 437), (306, 423), (303, 404), + (280, 400), (13, 396), (183, 394), (269, 394), (94, 393), (102, 389), + (180, 386), (293, 371), (181, 370), (5, 358), (290, 348), (212, 342), + (238, 335), (246, 324), (270, 315), (92, 314), (3, 310), (254, 308), + (316, 301), (110, 295), (123, 291), (2, 285), (104, 268), (236, 255), + (304, 254), (311, 250), (34, 250), (193, 244), (291, 244), (199, 235), + (312, 232), (114, 219), (215, 216), (248, 205), (37, 201), (25, 201), + (15, 197), (126, 195), (282, 191), (21, 184), (258, 167), (268, 151), + (179, 148), (203, 142), (234, 128), (90, 123), (1, 119), (144, 116), + (289, 102), (302, 99), (228, 97), (310, 95), (318, 94), (119, 92), (159, 91), + (285, 89), (139, 85), (162, 83), (50, 81), (73, 78), (42, 78), (196, 77), + (30, 76), (131, 75), (251, 75), (220, 73), (39, 72), (55, 71), (45, 71), + (315, 70), (217, 70), (120, 69), (227, 67), (264, 64), (209, 63), (31, 63), + (134, 62), (36, 62), (273, 61), (70, 60), (43, 58), (221, 58), (8, 56), + (160, 55), (138, 55), (192, 55), (97, 54), (186, 54), (241, 53), (71, 53), + (49, 53), (128, 53), (132, 52), (223, 52), (298, 52), (296, 51), (275, 51), + (208, 50), (263, 50), (99, 50), (214, 50), (277, 50), (153, 49), (96, 48), + (148, 48), (218, 47), (14, 46), (18, 45), (103, 44), (281, 44), (150, 43), + (125, 43), (10, 43), (247, 42), (294, 41), (64, 41), (307, 40), (40, 40), + (129, 40), (239, 40), (7, 38), (284, 38), (243, 38), (146, 37), (6, 37), + (95, 37), (184, 37), (213, 36), (188, 36), (35, 35), (59, 35), (124, 34), + (107, 33), (24, 32), (17, 31), (257, 31), (147, 30), (195, 30), (202, 29), + (308, 28), (106, 28), (57, 28), (276, 26), (115, 26), (58, 26), (61, 25), + (9, 25), (242, 25), (113, 25), (11, 24), (204, 23), (259, 22), (46, 22), + (274, 21), (255, 21), (135, 21), (224, 21), (240, 20), (295, 19), (187, 19), + (250, 19), (48, 19), (297, 19), (185, 18), (26, 17), (149, 17), (98, 16), + (261, 14), (197, 14), (286, 14), (75, 14), (164, 14), (68, 13), (157, 13), + (173, 13), (271, 12), (137, 12), (226, 12), (44, 12), (230, 11), (109, 11), + (117, 11), (206, 11), (292, 11), (182, 11), (222, 11), (252, 11), (244, 10), + (278, 10), (84, 10), (305, 10), (198, 10), (237, 10), (108, 10), (60, 10), + (53, 9), (136, 9), (158, 9), (225, 9), (69, 9), (47, 9), (287, 8), (41, 8), + (100, 8), (189, 8), (52, 8), (141, 8), (28, 8), (219, 8), (19, 8), (93, 8), + (133, 8), (165, 7), (313, 7), (20, 7), (76, 6), (142, 6), (231, 6), (253, 6), + (130, 6), (151, 5), (51, 5), (140, 5), (229, 5), (168, 5), (4, 5), (299, 5), + (22, 5), (170, 5), (155, 4), (62, 4), (145, 4), (174, 4), (66, 3), (56, 3), + (72, 3), (54, 3), (143, 3), (154, 3), (85, 3), (77, 3), (166, 3), (67, 3), + (152, 3), (245, 3), (279, 3), (111, 3), (200, 3), (171, 3), (79, 3), (210, 2), + (265, 2), (74, 2), (163, 2), (65, 2), (27, 2), (116, 2), (205, 2), (260, 2), + (32, 2), (156, 2), (63, 2), (300, 2), (12, 2), (101, 2), (190, 2), (232, 1), + (121, 1), (81, 1), (86, 1), (175, 1), (82, 1)] + +################################################################################### + +UPLIFTING_MINOR = [(301, 5035), (233, 5017), (314, 4999), (89, 4970), (320, 4956), (319, 4954), + (0, 4793), (267, 4760), (309, 4744), (178, 4715), (317, 4697), (288, 4644), + (114, 1184), (25, 1127), (248, 1111), (282, 1010), (193, 943), (203, 938), + (105, 912), (104, 906), (258, 906), (280, 883), (246, 882), (283, 870), + (16, 867), (94, 857), (127, 854), (238, 845), (102, 834), (194, 830), (5, 822), + (306, 813), (38, 795), (183, 792), (249, 791), (13, 784), (191, 780), + (256, 778), (112, 777), (290, 774), (23, 748), (272, 741), (235, 737), + (269, 737), (293, 714), (215, 700), (37, 695), (201, 694), (303, 693), + (15, 685), (316, 684), (311, 682), (216, 672), (126, 666), (91, 622), (2, 618), + (180, 616), (254, 606), (270, 596), (304, 592), (236, 590), (181, 577), + (92, 572), (34, 558), (123, 554), (3, 540), (21, 534), (212, 524), (312, 517), + (110, 508), (199, 500), (291, 491), (128, 224), (243, 217), (298, 217), + (144, 214), (90, 214), (39, 210), (8, 207), (162, 206), (234, 205), (97, 204), + (186, 204), (241, 203), (217, 200), (268, 199), (10, 198), (1, 192), (55, 190), + (179, 190), (188, 187), (125, 184), (315, 184), (302, 182), (318, 180), + (275, 178), (296, 168), (289, 168), (277, 166), (73, 166), (36, 165), + (119, 162), (263, 161), (99, 160), (310, 160), (30, 157), (214, 135), + (138, 135), (264, 133), (159, 129), (134, 128), (131, 127), (227, 125), + (70, 125), (281, 122), (43, 120), (46, 119), (209, 118), (247, 117), + (132, 116), (120, 110), (221, 108), (208, 108), (31, 106), (45, 103), (49, 99), + (224, 96), (96, 95), (59, 94), (220, 91), (148, 90), (135, 90), (7, 88), + (273, 88), (147, 84), (239, 82), (274, 77), (307, 76), (294, 75), (223, 75), + (240, 73), (17, 73), (106, 73), (192, 72), (213, 71), (185, 71), (58, 71), + (24, 71), (139, 70), (103, 66), (9, 66), (276, 65), (42, 65), (129, 64), + (95, 64), (187, 63), (242, 60), (98, 60), (150, 59), (285, 58), (40, 57), + (261, 57), (184, 57), (218, 56), (50, 55), (195, 55), (284, 53), (48, 52), + (196, 52), (117, 52), (251, 50), (295, 49), (202, 49), (250, 49), (146, 48), + (259, 48), (228, 48), (206, 48), (14, 48), (57, 47), (35, 47), (61, 46), + (6, 45), (113, 45), (124, 43), (157, 42), (28, 42), (137, 41), (68, 41), + (297, 40), (308, 40), (257, 39), (115, 38), (158, 38), (107, 37), (204, 35), + (160, 35), (71, 33), (26, 32), (226, 31), (69, 31), (153, 30), (165, 27), + (64, 27), (287, 26), (136, 25), (109, 25), (225, 24), (164, 24), (76, 24), + (286, 23), (75, 23), (155, 23), (11, 22), (252, 22), (253, 22), (93, 22), + (271, 22), (47, 21), (108, 21), (41, 21), (198, 20), (197, 19), (237, 19), + (219, 19), (182, 18), (66, 18), (130, 17), (292, 17), (305, 17), (20, 16), + (145, 15), (4, 15), (18, 15), (255, 14), (100, 14), (189, 14), (62, 14), + (244, 13), (151, 13), (170, 12), (52, 11), (141, 11), (278, 10), (313, 10), + (56, 10), (149, 9), (133, 9), (84, 8), (173, 8), (60, 8), (200, 8), (65, 7), + (299, 7), (230, 7), (44, 7), (154, 6), (85, 6), (222, 6), (174, 5), (81, 5), + (111, 5), (163, 4), (27, 4), (116, 4), (205, 4), (19, 4), (22, 4), (210, 3), + (265, 3), (74, 3), (168, 3), (51, 3), (260, 3), (12, 2), (101, 2), (190, 2), + (245, 2), (279, 2), (142, 2), (231, 2), (175, 2), (82, 2), (171, 2), (79, 2), + (152, 1), (140, 1), (229, 1), (54, 1), (143, 1), (53, 1), (121, 1), (300, 1), + (262, 1), (72, 1), (161, 1), (29, 1), (118, 1), (207, 1)] + +################################################################################### + +ALL_MOOD_TYPES = [HAPPY_MAJOR, + UPLIFTING_MAJOR, + UPLIFTING_MINOR, + NEUTRAL_MAJOR, + NEUTRAL_MINOR, + MELANCHOLIC_MAJOR, + MELANCHOLIC_MINOR, + SAD_MAJOR, + SAD_MINOR + ] + +################################################################################### + +ALL_MOOD_TYPES_LABELS = ['Happy Major', + 'Uplifting Major', + 'Uplifting Minor', + 'Neutral Major', + 'Neutral Minor', + 'Melancholic Major', + 'Melancholic Minor', + 'Sad Major', + 'Sad Minor' + ] + +################################################################################### + +LEAD_INSTRUMENTS = [0, 1, 2, 3, 4, 5, 6, 7, # Piano + 8, 9, 10, 11, 12, 13, 14, 15, # Chromatic Percussion + 16, 17, 18, 19, 20, 21, 22, 23, # Organ + 24, 25, 26, 27, 28, 29, 30, 31, # Guitar + 40, 41, # Strings + 52, 53, 54, # Ensemble + 56, 57, 59, 60, # Brass + 64, 65, 66, 67, 68, 69, 70, 71, # Reed + 72, 73, 74, 75, 76, 77, 78, 79, # Pipe + 80, 81, 87 # Synth Lead + ] + +################################################################################### + +BASE_INSTRUMENTS = [32, 33, 34, 35, 36, 37, 38, 39, # Bass + 42, 43, # Strings + 58, 61, 62, 63, # Brass + 87 # Synth Lead + ] + +################################################################################### + +def escore_notes_pitches_range(escore_notes, + range_patch=-1, + pitches_idx=4, + patches_idx=6 + ): + + pitches = [] + + if -1 < range_patch < 129: + pitches = [e[pitches_idx] for e in escore_notes if e[patches_idx] == range_patch] + + else: + pitches = [e[pitches_idx] for e in escore_notes] + + if pitches: + min_pitch = min(pitches) + avg_pitch = sum(pitches) / len(pitches) + mode_pitch = statistics.mode(pitches) + max_pitch = max(pitches) + + return [max_pitch-min_pitch, min_pitch, max_pitch, avg_pitch, mode_pitch] + + else: + return [ -1] * 6 ################################################################################### #