avans06's picture
This commit delivers three key improvements focused on user experience and bug fixing.
5cb75be
#! /usr/bin/python3
import os
import re
import json
import math
import tqdm
import copy
import psutil
import shutil
import random
import hashlib
import secrets
import statistics
import multiprocessing
from src import MIDI
from array import array
from pathlib import Path
from fnmatch import fnmatch
from collections import Counter
from collections import defaultdict
from collections import OrderedDict
from difflib import SequenceMatcher as SM
from operator import itemgetter
from itertools import product, combinations, groupby
###################################################################################
# TMIDI X Code is below
###################################################################################
def Optimus_MIDI_TXT_Processor(MIDI_file,
line_by_line_output=True,
chordify_TXT=False,
dataset_MIDI_events_time_denominator=1,
output_velocity=True,
output_MIDI_channels = False,
MIDI_channel=0,
MIDI_patch=[0, 1],
char_offset = 30000,
transpose_by = 0,
flip=False,
melody_conditioned_encoding=False,
melody_pitch_baseline = 0,
number_of_notes_to_sample = -1,
sampling_offset_from_start = 0,
karaoke=False,
karaoke_language_encoding='utf-8',
song_name='Song',
perfect_timings=False,
musenet_encoding=False,
transform=0,
zero_token=False,
reset_timings=False):
'''Project Los Angeles
Tegridy Code 2021'''
###########
debug = False
ev = 0
chords_list_final = []
chords_list = []
events_matrix = []
melody = []
melody1 = []
itrack = 1
min_note = 0
max_note = 0
ev = 0
patch = 0
score = []
rec_event = []
txt = ''
txtc = ''
chords = []
melody_chords = []
karaoke_events_matrix = []
karaokez = []
sample = 0
start_sample = 0
bass_melody = []
INTS = []
bints = 0
###########
def list_average(num):
sum_num = 0
for t in num:
sum_num = sum_num + t
avg = sum_num / len(num)
return avg
###########
#print('Loading MIDI file...')
midi_file = open(MIDI_file, 'rb')
if debug: print('Processing File:', MIDI_file)
try:
opus = MIDI.midi2opus(midi_file.read())
except:
print('Problematic MIDI. Skipping...')
print('File name:', MIDI_file)
midi_file.close()
return txt, melody, chords
midi_file.close()
score1 = MIDI.to_millisecs(opus)
score2 = MIDI.opus2score(score1)
# score2 = MIDI.opus2score(opus) # TODO Improve score timings when it will be possible.
if MIDI_channel == 16: # Process all MIDI channels
score = score2
if MIDI_channel >= 0 and MIDI_channel <= 15: # Process only a selected single MIDI channel
score = MIDI.grep(score2, [MIDI_channel])
if MIDI_channel == -1: # Process all channels except drums (except channel 9)
score = MIDI.grep(score2, [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15])
#print('Reading all MIDI events from the MIDI file...')
while itrack < len(score):
for event in score[itrack]:
if perfect_timings:
if event[0] == 'note':
event[1] = round(event[1], -1)
event[2] = round(event[2], -1)
if event[0] == 'text_event' or event[0] == 'lyric' or event[0] == 'note':
if perfect_timings:
event[1] = round(event[1], -1)
karaokez.append(event)
if event[0] == 'text_event' or event[0] == 'lyric':
if perfect_timings:
event[1] = round(event[1], -1)
try:
event[2] = str(event[2].decode(karaoke_language_encoding, 'replace')).replace('/', '').replace(' ', '').replace('\\', '')
except:
event[2] = str(event[2]).replace('/', '').replace(' ', '').replace('\\', '')
continue
karaoke_events_matrix.append(event)
if event[0] == 'patch_change':
patch = event[3]
if event[0] == 'note' and patch in MIDI_patch:
if len(event) == 6: # Checking for bad notes...
eve = copy.deepcopy(event)
eve[1] = int(event[1] / dataset_MIDI_events_time_denominator)
eve[2] = int(event[2] / dataset_MIDI_events_time_denominator)
eve[4] = int(event[4] + transpose_by)
if flip == True:
eve[4] = int(127 - (event[4] + transpose_by))
if number_of_notes_to_sample > -1:
if sample <= number_of_notes_to_sample:
if start_sample >= sampling_offset_from_start:
events_matrix.append(eve)
sample += 1
ev += 1
else:
start_sample += 1
else:
events_matrix.append(eve)
ev += 1
start_sample += 1
itrack +=1 # Going to next track...
#print('Doing some heavy pythonic sorting...Please stand by...')
fn = os.path.basename(MIDI_file)
song_name = song_name.replace(' ', '_').replace('=', '_').replace('\'', '-')
if song_name == 'Song':
sng_name = fn.split('.')[0].replace(' ', '_').replace('=', '_').replace('\'', '-')
song_name = sng_name
# Zero token
if zero_token:
txt += chr(char_offset) + chr(char_offset)
if output_MIDI_channels:
txt += chr(char_offset)
if output_velocity:
txt += chr(char_offset) + chr(char_offset)
else:
txt += chr(char_offset)
txtc += chr(char_offset) + chr(char_offset)
if output_MIDI_channels:
txtc += chr(char_offset)
if output_velocity:
txtc += chr(char_offset) + chr(char_offset)
else:
txtc += chr(char_offset)
txt += '=' + song_name + '_with_' + str(len(events_matrix)-1) + '_notes'
txtc += '=' + song_name + '_with_' + str(len(events_matrix)-1) + '_notes'
else:
# Song stamp
txt += 'SONG=' + song_name + '_with_' + str(len(events_matrix)-1) + '_notes'
txtc += 'SONG=' + song_name + '_with_' + str(len(events_matrix)-1) + '_notes'
if line_by_line_output:
txt += chr(10)
txtc += chr(10)
else:
txt += chr(32)
txtc += chr(32)
#print('Sorting input by start time...')
events_matrix.sort(key=lambda x: x[1]) # Sorting input by start time
#print('Timings converter')
if reset_timings:
ev_matrix = Tegridy_Timings_Converter(events_matrix)[0]
else:
ev_matrix = events_matrix
chords.extend(ev_matrix)
#print(chords)
#print('Extracting melody...')
melody_list = []
#print('Grouping by start time. This will take a while...')
values = set(map(lambda x:x[1], ev_matrix)) # Non-multithreaded function version just in case
groups = [[y for y in ev_matrix if y[1]==x and len(y) == 6] for x in values] # Grouping notes into chords while discarting bad notes...
#print('Sorting events...')
for items in groups:
items.sort(reverse=True, key=lambda x: x[4]) # Sorting events by pitch
if melody_conditioned_encoding: items[0][3] = 0 # Melody should always bear MIDI Channel 0 for code to work
melody_list.append(items[0]) # Creating final melody list
melody_chords.append(items) # Creating final chords list
bass_melody.append(items[-1]) # Creating final bass melody list
# [WIP] Melody-conditioned chords list
if melody_conditioned_encoding == True:
if not karaoke:
previous_event = copy.deepcopy(melody_chords[0][0])
for ev in melody_chords:
hp = True
ev.sort(reverse=False, key=lambda x: x[4]) # Sorting chord events by pitch
for event in ev:
# Computing events details
start_time = int(abs(event[1] - previous_event[1]))
duration = int(previous_event[2])
if hp == True:
if int(previous_event[4]) >= melody_pitch_baseline:
channel = int(0)
hp = False
else:
channel = int(previous_event[3]+1)
hp = False
else:
channel = int(previous_event[3]+1)
hp = False
pitch = int(previous_event[4])
velocity = int(previous_event[5])
# Writing INTergerS...
try:
INTS.append([(start_time)+char_offset, (duration)+char_offset, channel+char_offset, pitch+char_offset, velocity+char_offset])
except:
bints += 1
# Converting to TXT if possible...
try:
txtc += str(chr(start_time + char_offset))
txtc += str(chr(duration + char_offset))
txtc += str(chr(pitch + char_offset))
if output_velocity:
txtc += str(chr(velocity + char_offset))
if output_MIDI_channels:
txtc += str(chr(channel + char_offset))
if line_by_line_output:
txtc += chr(10)
else:
txtc += chr(32)
previous_event = copy.deepcopy(event)
except:
# print('Problematic MIDI event! Skipping...')
continue
if not line_by_line_output:
txtc += chr(10)
txt = txtc
chords = melody_chords
# Default stuff (not melody-conditioned/not-karaoke)
else:
if not karaoke:
melody_chords.sort(reverse=False, key=lambda x: x[0][1])
mel_chords = []
for mc in melody_chords:
mel_chords.extend(mc)
if transform != 0:
chords = Tegridy_Transform(mel_chords, transform)
else:
chords = mel_chords
# TXT Stuff
previous_event = copy.deepcopy(chords[0])
for event in chords:
# Computing events details
start_time = int(abs(event[1] - previous_event[1]))
duration = int(previous_event[2])
channel = int(previous_event[3])
pitch = int(previous_event[4] + transpose_by)
if flip == True:
pitch = 127 - int(previous_event[4] + transpose_by)
velocity = int(previous_event[5])
# Writing INTergerS...
try:
INTS.append([(start_time)+char_offset, (duration)+char_offset, channel+char_offset, pitch+char_offset, velocity+char_offset])
except:
bints += 1
# Converting to TXT if possible...
try:
txt += str(chr(start_time + char_offset))
txt += str(chr(duration + char_offset))
txt += str(chr(pitch + char_offset))
if output_velocity:
txt += str(chr(velocity + char_offset))
if output_MIDI_channels:
txt += str(chr(channel + char_offset))
if chordify_TXT == True and int(event[1] - previous_event[1]) == 0:
txt += ''
else:
if line_by_line_output:
txt += chr(10)
else:
txt += chr(32)
previous_event = copy.deepcopy(event)
except:
# print('Problematic MIDI event. Skipping...')
continue
if not line_by_line_output:
txt += chr(10)
# Karaoke stuff
if karaoke:
melody_chords.sort(reverse=False, key=lambda x: x[0][1])
mel_chords = []
for mc in melody_chords:
mel_chords.extend(mc)
if transform != 0:
chords = Tegridy_Transform(mel_chords, transform)
else:
chords = mel_chords
previous_event = copy.deepcopy(chords[0])
for event in chords:
# Computing events details
start_time = int(abs(event[1] - previous_event[1]))
duration = int(previous_event[2])
channel = int(previous_event[3])
pitch = int(previous_event[4] + transpose_by)
velocity = int(previous_event[5])
# Converting to TXT
txt += str(chr(start_time + char_offset))
txt += str(chr(duration + char_offset))
txt += str(chr(pitch + char_offset))
txt += str(chr(velocity + char_offset))
txt += str(chr(channel + char_offset))
if start_time > 0:
for k in karaoke_events_matrix:
if event[1] == k[1]:
txt += str('=')
txt += str(k[2])
break
if line_by_line_output:
txt += chr(10)
else:
txt += chr(32)
previous_event = copy.deepcopy(event)
if not line_by_line_output:
txt += chr(10)
# Final processing code...
# =======================================================================
# Helper aux/backup function for Karaoke
karaokez.sort(reverse=False, key=lambda x: x[1])
# MuseNet sorting
if musenet_encoding and not melody_conditioned_encoding and not karaoke:
chords.sort(key=lambda x: (x[1], x[3]))
# Final melody sort
melody_list.sort()
# auxs for future use
aux1 = [None]
aux2 = [None]
return txt, melody_list, chords, bass_melody, karaokez, INTS, aux1, aux2 # aux1 and aux2 are not used atm
###################################################################################
def Optimus_TXT_to_Notes_Converter(Optimus_TXT_String,
line_by_line_dataset = True,
has_velocities = True,
has_MIDI_channels = True,
dataset_MIDI_events_time_denominator = 1,
char_encoding_offset = 30000,
save_only_first_composition = True,
simulate_velocity=True,
karaoke=False,
zero_token=False):
'''Project Los Angeles
Tegridy Code 2020'''
print('Tegridy Optimus TXT to Notes Converter')
print('Converting TXT to Notes list...Please wait...')
song_name = ''
if line_by_line_dataset:
input_string = Optimus_TXT_String.split('\n')
else:
input_string = Optimus_TXT_String.split(' ')
if line_by_line_dataset:
name_string = Optimus_TXT_String.split('\n')[0].split('=')
else:
name_string = Optimus_TXT_String.split(' ')[0].split('=')
# Zero token
zt = ''
zt += chr(char_encoding_offset) + chr(char_encoding_offset)
if has_MIDI_channels:
zt += chr(char_encoding_offset)
if has_velocities:
zt += chr(char_encoding_offset) + chr(char_encoding_offset)
else:
zt += chr(char_encoding_offset)
if zero_token:
if name_string[0] == zt:
song_name = name_string[1]
else:
if name_string[0] == 'SONG':
song_name = name_string[1]
output_list = []
st = 0
for i in range(2, len(input_string)-1):
if save_only_first_composition:
if zero_token:
if input_string[i].split('=')[0] == zt:
song_name = name_string[1]
break
else:
if input_string[i].split('=')[0] == 'SONG':
song_name = name_string[1]
break
try:
istring = input_string[i]
if has_MIDI_channels == False:
step = 4
if has_MIDI_channels == True:
step = 5
if has_velocities == False:
step -= 1
st += int(ord(istring[0]) - char_encoding_offset) * dataset_MIDI_events_time_denominator
if not karaoke:
for s in range(0, len(istring), step):
if has_MIDI_channels==True:
if step > 3 and len(istring) > 2:
out = []
out.append('note')
out.append(st) # Start time
out.append(int(ord(istring[s+1]) - char_encoding_offset) * dataset_MIDI_events_time_denominator) # Duration
if has_velocities:
out.append(int(ord(istring[s+4]) - char_encoding_offset)) # Channel
else:
out.append(int(ord(istring[s+3]) - char_encoding_offset)) # Channel
out.append(int(ord(istring[s+2]) - char_encoding_offset)) # Pitch
if simulate_velocity:
if s == 0:
sim_vel = int(ord(istring[s+2]) - char_encoding_offset)
out.append(sim_vel) # Simulated Velocity (= highest note's pitch)
else:
out.append(int(ord(istring[s+3]) - char_encoding_offset)) # Velocity
if has_MIDI_channels==False:
if step > 3 and len(istring) > 2:
out = []
out.append('note')
out.append(st) # Start time
out.append(int(ord(istring[s+1]) - char_encoding_offset) * dataset_MIDI_events_time_denominator) # Duration
out.append(0) # Channel
out.append(int(ord(istring[s+2]) - char_encoding_offset)) # Pitch
if simulate_velocity:
if s == 0:
sim_vel = int(ord(istring[s+2]) - char_encoding_offset)
out.append(sim_vel) # Simulated Velocity (= highest note's pitch)
else:
out.append(int(ord(istring[s+3]) - char_encoding_offset)) # Velocity
if step == 3 and len(istring) > 2:
out = []
out.append('note')
out.append(st) # Start time
out.append(int(ord(istring[s+1]) - char_encoding_offset) * dataset_MIDI_events_time_denominator) # Duration
out.append(0) # Channel
out.append(int(ord(istring[s+2]) - char_encoding_offset)) # Pitch
out.append(int(ord(istring[s+2]) - char_encoding_offset)) # Velocity = Pitch
output_list.append(out)
if karaoke:
try:
out = []
out.append('note')
out.append(st) # Start time
out.append(int(ord(istring[1]) - char_encoding_offset) * dataset_MIDI_events_time_denominator) # Duration
out.append(int(ord(istring[4]) - char_encoding_offset)) # Channel
out.append(int(ord(istring[2]) - char_encoding_offset)) # Pitch
if simulate_velocity:
if s == 0:
sim_vel = int(ord(istring[2]) - char_encoding_offset)
out.append(sim_vel) # Simulated Velocity (= highest note's pitch)
else:
out.append(int(ord(istring[3]) - char_encoding_offset)) # Velocity
output_list.append(out)
out = []
if istring.split('=')[1] != '':
out.append('lyric')
out.append(st)
out.append(istring.split('=')[1])
output_list.append(out)
except:
continue
except:
print('Bad note string:', istring)
continue
# Simple error control just in case
S = []
for x in output_list:
if len(x) == 6 or len(x) == 3:
S.append(x)
output_list.clear()
output_list = copy.deepcopy(S)
print('Task complete! Enjoy! :)')
return output_list, song_name
###################################################################################
def Optimus_Data2TXT_Converter(data,
dataset_time_denominator=1,
transpose_by = 0,
char_offset = 33,
line_by_line_output = True,
output_velocity = False,
output_MIDI_channels = False):
'''Input: data as a flat chords list of flat chords lists
Output: TXT string
INTs
Project Los Angeles
Tegridy Code 2021'''
txt = ''
TXT = ''
quit = False
counter = 0
INTs = []
INTs_f = []
for d in tqdm.tqdm(sorted(data)):
if quit == True:
break
txt = 'SONG=' + str(counter)
counter += 1
if line_by_line_output:
txt += chr(10)
else:
txt += chr(32)
INTs = []
# TXT Stuff
previous_event = copy.deepcopy(d[0])
for event in sorted(d):
# Computing events details
start_time = int(abs(event[1] - previous_event[1]) / dataset_time_denominator)
duration = int(previous_event[2] / dataset_time_denominator)
channel = int(previous_event[3])
pitch = int(previous_event[4] + transpose_by)
velocity = int(previous_event[5])
INTs.append([start_time, duration, pitch])
# Converting to TXT if possible...
try:
txt += str(chr(start_time + char_offset))
txt += str(chr(duration + char_offset))
txt += str(chr(pitch + char_offset))
if output_velocity:
txt += str(chr(velocity + char_offset))
if output_MIDI_channels:
txt += str(chr(channel + char_offset))
if line_by_line_output:
txt += chr(10)
else:
txt += chr(32)
previous_event = copy.deepcopy(event)
except KeyboardInterrupt:
quit = True
break
except:
print('Problematic MIDI data. Skipping...')
continue
if not line_by_line_output:
txt += chr(10)
TXT += txt
INTs_f.extend(INTs)
return TXT, INTs_f
###################################################################################
def Optimus_Squash(chords_list, simulate_velocity=True, mono_compression=False):
'''Input: Flat chords list
Simulate velocity or not
Mono-compression enabled or disabled
Default is almost lossless 25% compression, otherwise, lossy 50% compression (mono-compression)
Output: Squashed chords list
Resulting compression level
Please note that if drums are passed through as is
Project Los Angeles
Tegridy Code 2021'''
output = []
ptime = 0
vel = 0
boost = 15
stptc = []
ocount = 0
rcount = 0
for c in chords_list:
cc = copy.deepcopy(c)
ocount += 1
if [cc[1], cc[3], (cc[4] % 12) + 60] not in stptc:
stptc.append([cc[1], cc[3], (cc[4] % 12) + 60])
if cc[3] != 9:
cc[4] = (c[4] % 12) + 60
if simulate_velocity and c[1] != ptime:
vel = c[4] + boost
if cc[3] != 9:
cc[5] = vel
if mono_compression:
if c[1] != ptime:
output.append(cc)
rcount += 1
else:
output.append(cc)
rcount += 1
ptime = c[1]
output.sort(key=lambda x: (x[1], x[4]))
comp_level = 100 - int((rcount * 100) / ocount)
return output, comp_level
###################################################################################
def Optimus_Signature(chords_list, calculate_full_signature=False):
'''Optimus Signature
---In the name of the search for a perfect score slice signature---
Input: Flat chords list to evaluate
Output: Full Optimus Signature as a list
Best/recommended Optimus Signature as a list
Project Los Angeles
Tegridy Code 2021'''
# Pitches
## StDev
if calculate_full_signature:
psd = statistics.stdev([int(y[4]) for y in chords_list])
else:
psd = 0
## Median
pmh = statistics.median_high([int(y[4]) for y in chords_list])
pm = statistics.median([int(y[4]) for y in chords_list])
pml = statistics.median_low([int(y[4]) for y in chords_list])
## Mean
if calculate_full_signature:
phm = statistics.harmonic_mean([int(y[4]) for y in chords_list])
else:
phm = 0
# Durations
dur = statistics.median([int(y[2]) for y in chords_list])
# Velocities
vel = statistics.median([int(y[5]) for y in chords_list])
# Beats
mtds = statistics.median([int(abs(chords_list[i-1][1]-chords_list[i][1])) for i in range(1, len(chords_list))])
if calculate_full_signature:
hmtds = statistics.harmonic_mean([int(abs(chords_list[i-1][1]-chords_list[i][1])) for i in range(1, len(chords_list))])
else:
hmtds = 0
# Final Optimus signatures
full_Optimus_signature = [round(psd), round(pmh), round(pm), round(pml), round(phm), round(dur), round(vel), round(mtds), round(hmtds)]
######################## PStDev PMedianH PMedian PMedianL PHarmoMe Duration Velocity Beat HarmoBeat
best_Optimus_signature = [round(pmh), round(pm), round(pml), round(dur, -1), round(vel, -1), round(mtds, -1)]
######################## PMedianH PMedian PMedianL Duration Velocity Beat
# Return...
return full_Optimus_signature, best_Optimus_signature
###################################################################################
#
# TMIDI 2.0 Helper functions
#
###################################################################################
def Tegridy_FastSearch(needle, haystack, randomize = False):
'''
Input: Needle iterable
Haystack iterable
Randomize search range (this prevents determinism)
Output: Start index of the needle iterable in a haystack iterable
If nothing found, -1 is returned
Project Los Angeles
Tegridy Code 2021'''
need = copy.deepcopy(needle)
try:
if randomize:
idx = haystack.index(need, secrets.randbelow(len(haystack)-len(need)))
else:
idx = haystack.index(need)
except KeyboardInterrupt:
return -1
except:
return -1
return idx
###################################################################################
def Tegridy_Chord_Match(chord1, chord2, match_type=2):
'''Tegridy Chord Match
Input: Two chords to evaluate
Match type: 2 = duration, channel, pitch, velocity
3 = channel, pitch, velocity
4 = pitch, velocity
5 = velocity
Output: Match rating (0-100)
NOTE: Match rating == -1 means identical source chords
NOTE: Match rating == 100 means mutual shortest chord
Project Los Angeles
Tegridy Code 2021'''
match_rating = 0
if chord1 == []:
return 0
if chord2 == []:
return 0
if chord1 == chord2:
return -1
else:
zipped_pairs = list(zip(chord1, chord2))
zipped_diff = abs(len(chord1) - len(chord2))
short_match = [False]
for pair in zipped_pairs:
cho1 = ' '.join([str(y) for y in pair[0][match_type:]])
cho2 = ' '.join([str(y) for y in pair[1][match_type:]])
if cho1 == cho2:
short_match.append(True)
else:
short_match.append(False)
if True in short_match:
return 100
pairs_ratings = []
for pair in zipped_pairs:
cho1 = ' '.join([str(y) for y in pair[0][match_type:]])
cho2 = ' '.join([str(y) for y in pair[1][match_type:]])
pairs_ratings.append(SM(None, cho1, cho2).ratio())
match_rating = sum(pairs_ratings) / len(pairs_ratings) * 100
return match_rating
###################################################################################
def Tegridy_Last_Chord_Finder(chords_list):
'''Tegridy Last Chord Finder
Input: Flat chords list
Output: Last detected chord of the chords list
Last chord start index in the original chords list
First chord end index in the original chords list
Project Los Angeles
Tegridy Code 2021'''
chords = []
cho = []
ptime = 0
i = 0
pc_idx = 0
fc_idx = 0
chords_list.sort(reverse=False, key=lambda x: x[1])
for cc in chords_list:
if cc[1] == ptime:
cho.append(cc)
ptime = cc[1]
else:
if pc_idx == 0:
fc_idx = chords_list.index(cc)
pc_idx = chords_list.index(cc)
chords.append(cho)
cho = []
cho.append(cc)
ptime = cc[1]
i += 1
if cho != []:
chords.append(cho)
i += 1
return chords_list[pc_idx:], pc_idx, fc_idx
###################################################################################
def Tegridy_Chords_Generator(chords_list, shuffle_pairs = True, remove_single_notes=False):
'''Tegridy Score Chords Pairs Generator
Input: Flat chords list
Shuffle pairs (recommended)
Output: List of chords
Average time(ms) per chord
Average time(ms) per pitch
Average chords delta time
Average duration
Average channel
Average pitch
Average velocity
Project Los Angeles
Tegridy Code 2021'''
chords = []
cho = []
i = 0
# Sort by start time
chords_list.sort(reverse=False, key=lambda x: x[1])
# Main loop
pcho = chords_list[0]
for cc in chords_list:
if cc[1] == pcho[1]:
cho.append(cc)
pcho = copy.deepcopy(cc)
else:
if not remove_single_notes:
chords.append(cho)
cho = []
cho.append(cc)
pcho = copy.deepcopy(cc)
i += 1
else:
if len(cho) > 1:
chords.append(cho)
cho = []
cho.append(cc)
pcho = copy.deepcopy(cc)
i += 1
# Averages
t0 = chords[0][0][1]
t1 = chords[-1][-1][1]
tdel = abs(t1 - t0)
avg_ms_per_chord = int(tdel / i)
avg_ms_per_pitch = int(tdel / len(chords_list))
# Delta time
tds = [int(abs(chords_list[i-1][1]-chords_list[i][1]) / 1) for i in range(1, len(chords_list))]
if len(tds) != 0: avg_delta_time = int(sum(tds) / len(tds))
# Chords list attributes
p = int(sum([int(y[4]) for y in chords_list]) / len(chords_list))
d = int(sum([int(y[2]) for y in chords_list]) / len(chords_list))
c = int(sum([int(y[3]) for y in chords_list]) / len(chords_list))
v = int(sum([int(y[5]) for y in chords_list]) / len(chords_list))
# Final shuffle
if shuffle_pairs:
random.shuffle(chords)
return chords, [avg_ms_per_chord, avg_ms_per_pitch, avg_delta_time], [d, c, p, v]
###################################################################################
def Tegridy_Chords_List_Music_Features(chords_list, st_dur_div = 1, pitch_div = 1, vel_div = 1):
'''Tegridy Chords List Music Features
Input: Flat chords list
Output: A list of the extracted chords list's music features
Project Los Angeles
Tegridy Code 2021'''
chords_list1 = [x for x in chords_list if x]
chords_list1.sort(reverse=False, key=lambda x: x[1])
# Features extraction code
melody_list = []
bass_melody = []
melody_chords = []
mel_avg_tds = []
mel_chrd_avg_tds = []
bass_melody_avg_tds = []
#print('Grouping by start time. This will take a while...')
values = set(map(lambda x:x[1], chords_list1)) # Non-multithreaded function version just in case
groups = [[y for y in chords_list1 if y[1]==x and len(y) == 6] for x in values] # Grouping notes into chords while discarting bad notes...
#print('Sorting events...')
for items in groups:
items.sort(reverse=True, key=lambda x: x[4]) # Sorting events by pitch
melody_list.append(items[0]) # Creating final melody list
melody_chords.append(items) # Creating final chords list
bass_melody.append(items[-1]) # Creating final bass melody list
#print('Final sorting by start time...')
melody_list.sort(reverse=False, key=lambda x: x[1]) # Sorting events by start time
melody_chords.sort(reverse=False, key=lambda x: x[0][1]) # Sorting events by start time
bass_melody.sort(reverse=False, key=lambda x: x[1]) # Sorting events by start time
# Extracting music features from the chords list
# Melody features
mel_avg_pitch = int(sum([y[4] for y in melody_list]) / len(melody_list) / pitch_div)
mel_avg_dur = int(sum([int(y[2] / st_dur_div) for y in melody_list]) / len(melody_list))
mel_avg_vel = int(sum([int(y[5] / vel_div) for y in melody_list]) / len(melody_list))
mel_avg_chan = int(sum([int(y[3]) for y in melody_list]) / len(melody_list))
mel_tds = [int(abs(melody_list[i-1][1]-melody_list[i][1])) for i in range(1, len(melody_list))]
if len(mel_tds) != 0: mel_avg_tds = int(sum(mel_tds) / len(mel_tds) / st_dur_div)
melody_features = [mel_avg_tds, mel_avg_dur, mel_avg_chan, mel_avg_pitch, mel_avg_vel]
# Chords list features
mel_chrd_avg_pitch = int(sum([y[4] for y in chords_list1]) / len(chords_list1) / pitch_div)
mel_chrd_avg_dur = int(sum([int(y[2] / st_dur_div) for y in chords_list1]) / len(chords_list1))
mel_chrd_avg_vel = int(sum([int(y[5] / vel_div) for y in chords_list1]) / len(chords_list1))
mel_chrd_avg_chan = int(sum([int(y[3]) for y in chords_list1]) / len(chords_list1))
mel_chrd_tds = [int(abs(chords_list1[i-1][1]-chords_list1[i][1])) for i in range(1, len(chords_list1))]
if len(mel_tds) != 0: mel_chrd_avg_tds = int(sum(mel_chrd_tds) / len(mel_chrd_tds) / st_dur_div)
chords_list_features = [mel_chrd_avg_tds, mel_chrd_avg_dur, mel_chrd_avg_chan, mel_chrd_avg_pitch, mel_chrd_avg_vel]
# Bass melody features
bass_melody_avg_pitch = int(sum([y[4] for y in bass_melody]) / len(bass_melody) / pitch_div)
bass_melody_avg_dur = int(sum([int(y[2] / st_dur_div) for y in bass_melody]) / len(bass_melody))
bass_melody_avg_vel = int(sum([int(y[5] / vel_div) for y in bass_melody]) / len(bass_melody))
bass_melody_avg_chan = int(sum([int(y[3]) for y in bass_melody]) / len(bass_melody))
bass_melody_tds = [int(abs(bass_melody[i-1][1]-bass_melody[i][1])) for i in range(1, len(bass_melody))]
if len(bass_melody_tds) != 0: bass_melody_avg_tds = int(sum(bass_melody_tds) / len(bass_melody_tds) / st_dur_div)
bass_melody_features = [bass_melody_avg_tds, bass_melody_avg_dur, bass_melody_avg_chan, bass_melody_avg_pitch, bass_melody_avg_vel]
# A list to return all features
music_features = []
music_features.extend([len(chords_list1)]) # Count of the original chords list notes
music_features.extend(melody_features) # Extracted melody features
music_features.extend(chords_list_features) # Extracted chords list features
music_features.extend(bass_melody_features) # Extracted bass melody features
music_features.extend([sum([y[4] for y in chords_list1])]) # Sum of all pitches in the original chords list
return music_features
###################################################################################
def Tegridy_Transform(chords_list, to_pitch=60, to_velocity=-1):
'''Tegridy Transform
Input: Flat chords list
Desired average pitch (-1 == no change)
Desired average velocity (-1 == no change)
Output: Transformed flat chords list
Project Los Angeles
Tegridy Code 2021'''
transformed_chords_list = []
chords_list.sort(reverse=False, key=lambda x: x[1])
chords_list_features = Optimus_Signature(chords_list)[1]
pitch_diff = int((chords_list_features[0] + chords_list_features[1] + chords_list_features[2]) / 3) - to_pitch
velocity_diff = chords_list_features[4] - to_velocity
for c in chords_list:
cc = copy.deepcopy(c)
if c[3] != 9: # Except the drums
if to_pitch != -1:
cc[4] = c[4] - pitch_diff
if to_velocity != -1:
cc[5] = c[5] - velocity_diff
transformed_chords_list.append(cc)
return transformed_chords_list
###################################################################################
def Tegridy_MIDI_Zip_Notes_Summarizer(chords_list, match_type = 4):
'''Tegridy MIDI Zip Notes Summarizer
Input: Flat chords list / SONG
Match type according to 'note' event of MIDI.py
Output: Summarized chords list
Number of summarized notes
Number of dicarted notes
Project Los Angeles
Tegridy Code 2021'''
i = 0
j = 0
out1 = []
pout = []
for o in chords_list:
# MIDI Zip
if o[match_type:] not in pout:
pout.append(o[match_type:])
out1.append(o)
j += 1
else:
i += 1
return out1, i
###################################################################################
def Tegridy_Score_Chords_Pairs_Generator(chords_list, shuffle_pairs = True, remove_single_notes=False):
'''Tegridy Score Chords Pairs Generator
Input: Flat chords list
Shuffle pairs (recommended)
Output: Score chords pairs list
Number of created pairs
Number of detected chords
Project Los Angeles
Tegridy Code 2021'''
chords = []
cho = []
i = 0
j = 0
chords_list.sort(reverse=False, key=lambda x: x[1])
pcho = chords_list[0]
for cc in chords_list:
if cc[1] == pcho[1]:
cho.append(cc)
pcho = copy.deepcopy(cc)
else:
if not remove_single_notes:
chords.append(cho)
cho = []
cho.append(cc)
pcho = copy.deepcopy(cc)
i += 1
else:
if len(cho) > 1:
chords.append(cho)
cho = []
cho.append(cc)
pcho = copy.deepcopy(cc)
i += 1
chords_pairs = []
for i in range(len(chords)-1):
chords_pairs.append([chords[i], chords[i+1]])
j += 1
if shuffle_pairs: random.shuffle(chords_pairs)
return chords_pairs, j, i
###################################################################################
def Tegridy_Sliced_Score_Pairs_Generator(chords_list, number_of_miliseconds_per_slice=2000, shuffle_pairs = False):
'''Tegridy Sliced Score Pairs Generator
Input: Flat chords list
Number of miliseconds per slice
Output: Sliced score pairs list
Number of created slices
Project Los Angeles
Tegridy Code 2021'''
chords = []
cho = []
time = number_of_miliseconds_per_slice
i = 0
chords_list1 = [x for x in chords_list if x]
chords_list1.sort(reverse=False, key=lambda x: x[1])
pcho = chords_list1[0]
for cc in chords_list1[1:]:
if cc[1] <= time:
cho.append(cc)
else:
if cho != [] and pcho != []: chords.append([pcho, cho])
pcho = copy.deepcopy(cho)
cho = []
cho.append(cc)
time += number_of_miliseconds_per_slice
i += 1
if cho != [] and pcho != []:
chords.append([pcho, cho])
pcho = copy.deepcopy(cho)
i += 1
if shuffle_pairs: random.shuffle(chords)
return chords, i
###################################################################################
def Tegridy_Timings_Converter(chords_list,
max_delta_time = 1000,
fixed_start_time = 250,
start_time = 0,
start_time_multiplier = 1,
durations_multiplier = 1):
'''Tegridy Timings Converter
Input: Flat chords list
Max delta time allowed between notes
Fixed start note time for excessive gaps
Output: Converted flat chords list
Project Los Angeles
Tegridy Code 2021'''
song = chords_list
song1 = []
p = song[0]
p[1] = start_time
time = start_time
delta = [0]
for i in range(len(song)):
if song[i][0] == 'note':
ss = copy.deepcopy(song[i])
if song[i][1] != p[1]:
if abs(song[i][1] - p[1]) > max_delta_time:
time += fixed_start_time
else:
time += abs(song[i][1] - p[1])
delta.append(abs(song[i][1] - p[1]))
ss[1] = int(round(time * start_time_multiplier, -1))
ss[2] = int(round(song[i][2] * durations_multiplier, -1))
song1.append(ss)
p = copy.deepcopy(song[i])
else:
ss[1] = int(round(time * start_time_multiplier, -1))
ss[2] = int(round(song[i][2] * durations_multiplier, -1))
song1.append(ss)
p = copy.deepcopy(song[i])
else:
ss = copy.deepcopy(song[i])
ss[1] = time
song1.append(ss)
average_delta_st = int(sum(delta) / len(delta))
average_duration = int(sum([y[2] for y in song1 if y[0] == 'note']) / len([y[2] for y in song1 if y[0] == 'note']))
song1.sort(reverse=False, key=lambda x: x[1])
return song1, time, average_delta_st, average_duration
###################################################################################
def Tegridy_Score_Slicer(chords_list, number_of_miliseconds_per_slice=2000, overlap_notes = 0, overlap_chords=False):
'''Tegridy Score Slicer
Input: Flat chords list
Number of miliseconds per slice
Output: Sliced chords list
Number of created slices
Project Los Angeles
Tegridy Code 2021'''
chords = []
cho = []
time = number_of_miliseconds_per_slice
ptime = 0
i = 0
pc_idx = 0
chords_list.sort(reverse=False, key=lambda x: x[1])
for cc in chords_list:
if cc[1] <= time:
cho.append(cc)
if ptime != cc[1]:
pc_idx = cho.index(cc)
ptime = cc[1]
else:
if overlap_chords:
chords.append(cho)
cho.extend(chords[-1][pc_idx:])
else:
chords.append(cho[:pc_idx])
cho = []
cho.append(cc)
time += number_of_miliseconds_per_slice
ptime = cc[1]
i += 1
if cho != []:
chords.append(cho)
i += 1
return [x for x in chords if x], i
###################################################################################
def Tegridy_TXT_Tokenizer(input_TXT_string, line_by_line_TXT_string=True):
'''Tegridy TXT Tokenizer
Input: TXT String
Output: Tokenized TXT string + forward and reverse dics
Project Los Angeles
Tegridy Code 2021'''
print('Tegridy TXT Tokenizer')
if line_by_line_TXT_string:
T = input_TXT_string.split()
else:
T = input_TXT_string.split(' ')
DIC = dict(zip(T, range(len(T))))
RDIC = dict(zip(range(len(T)), T))
TXTT = ''
for t in T:
try:
TXTT += chr(DIC[t])
except:
print('Error. Could not finish.')
return TXTT, DIC, RDIC
print('Done!')
return TXTT, DIC, RDIC
###################################################################################
def Tegridy_TXT_DeTokenizer(input_Tokenized_TXT_string, RDIC):
'''Tegridy TXT Tokenizer
Input: Tokenized TXT String
Output: DeTokenized TXT string
Project Los Angeles
Tegridy Code 2021'''
print('Tegridy TXT DeTokenizer')
Q = list(input_Tokenized_TXT_string)
c = 0
RTXT = ''
for q in Q:
try:
RTXT += RDIC[ord(q)] + chr(10)
except:
c+=1
print('Number of errors:', c)
print('Done!')
return RTXT
###################################################################################
def Tegridy_List_Slicer(input_list, slices_length_in_notes=20):
'''Input: List to slice
Desired slices length in notes
Output: Sliced list of lists
Project Los Angeles
Tegridy Code 2021'''
for i in range(0, len(input_list), slices_length_in_notes):
yield input_list[i:i + slices_length_in_notes]
###################################################################################
def Tegridy_Split_List(list_to_split, split_value=0):
# src courtesy of www.geeksforgeeks.org
# using list comprehension + zip() + slicing + enumerate()
# Split list into lists by particular value
size = len(list_to_split)
idx_list = [idx + 1 for idx, val in
enumerate(list_to_split) if val == split_value]
res = [list_to_split[i: j] for i, j in
zip([0] + idx_list, idx_list +
([size] if idx_list[-1] != size else []))]
# print result
# print("The list after splitting by a value : " + str(res))
return res
###################################################################################
# Binary chords functions
def tones_chord_to_bits(chord, reverse=True):
bits = [0] * 12
for num in chord:
bits[num] = 1
if reverse:
bits.reverse()
return bits
else:
return bits
def bits_to_tones_chord(bits):
return [i for i, bit in enumerate(bits) if bit == 1]
def shift_bits(bits, n):
return bits[-n:] + bits[:-n]
def bits_to_int(bits, shift_bits_value=0):
bits = shift_bits(bits, shift_bits_value)
result = 0
for bit in bits:
result = (result << 1) | bit
return result
def int_to_bits(n):
bits = [0] * 12
for i in range(12):
bits[11 - i] = n % 2
n //= 2
return bits
def bad_chord(chord):
bad = any(b - a == 1 for a, b in zip(chord, chord[1:]))
if (0 in chord) and (11 in chord):
bad = True
return bad
def pitches_chord_to_int(pitches_chord, tones_transpose_value=0):
pitches_chord = [x for x in pitches_chord if 0 < x < 128]
if not (-12 < tones_transpose_value < 12):
tones_transpose_value = 0
tones_chord = sorted(list(set([c % 12 for c in sorted(list(set(pitches_chord)))])))
bits = tones_chord_to_bits(tones_chord)
integer = bits_to_int(bits, shift_bits_value=tones_transpose_value)
return integer
def int_to_pitches_chord(integer, chord_base_pitch=60):
if 0 < integer < 4096:
bits = int_to_bits(integer)
tones_chord = bits_to_tones_chord(bits)
if not bad_chord(tones_chord):
pitches_chord = [t+chord_base_pitch for t in tones_chord]
return [pitches_chord, tones_chord]
else:
return 0 # Bad chord code
else:
return -1 # Bad integer code
###################################################################################
def validate_pitches_chord(pitches_chord, return_sorted = True):
pitches_chord = sorted(list(set([x for x in pitches_chord if 0 < x < 128])))
tones_chord = sorted(list(set([c % 12 for c in sorted(list(set(pitches_chord)))])))
if not bad_chord(tones_chord):
if return_sorted:
pitches_chord.sort(reverse=True)
return pitches_chord
else:
if 0 in tones_chord and 11 in tones_chord:
tones_chord.remove(0)
fixed_tones = [[a, b] for a, b in zip(tones_chord, tones_chord[1:]) if b-a != 1]
fixed_tones_chord = []
for f in fixed_tones:
fixed_tones_chord.extend(f)
fixed_tones_chord = list(set(fixed_tones_chord))
fixed_pitches_chord = []
for p in pitches_chord:
if (p % 12) in fixed_tones_chord:
fixed_pitches_chord.append(p)
if return_sorted:
fixed_pitches_chord.sort(reverse=True)
return fixed_pitches_chord
def validate_pitches(chord, channel_to_check = 0, return_sorted = True):
pitches_chord = sorted(list(set([x[4] for x in chord if 0 < x[4] < 128 and x[3] == channel_to_check])))
if pitches_chord:
tones_chord = sorted(list(set([c % 12 for c in sorted(list(set(pitches_chord)))])))
if not bad_chord(tones_chord):
if return_sorted:
chord.sort(key = lambda x: x[4], reverse=True)
return chord
else:
if 0 in tones_chord and 11 in tones_chord:
tones_chord.remove(0)
fixed_tones = [[a, b] for a, b in zip(tones_chord, tones_chord[1:]) if b-a != 1]
fixed_tones_chord = []
for f in fixed_tones:
fixed_tones_chord.extend(f)
fixed_tones_chord = list(set(fixed_tones_chord))
fixed_chord = []
for c in chord:
if c[3] == channel_to_check:
if (c[4] % 12) in fixed_tones_chord:
fixed_chord.append(c)
else:
fixed_chord.append(c)
if return_sorted:
fixed_chord.sort(key = lambda x: x[4], reverse=True)
return fixed_chord
else:
chord.sort(key = lambda x: x[4], reverse=True)
return chord
def adjust_score_velocities(score,
max_velocity,
adj_per_channel=False,
adj_in_place=True
):
if adj_in_place:
buf = score
else:
buf = copy.deepcopy(score)
notes = [evt for evt in buf if evt[0] == 'note']
if not notes:
return buf
if adj_per_channel:
channel_max = {}
for _, _, _, ch, _, vel, _ in notes:
channel_max[ch] = max(channel_max.get(ch, 0), vel)
channel_factor = {
ch: (max_velocity / vmax if vmax > 0 else 1.0)
for ch, vmax in channel_max.items()
}
for evt in buf:
if evt[0] == 'note':
ch = evt[3]
factor = channel_factor.get(ch, 1.0)
new_vel = int(evt[5] * factor)
evt[5] = max(1, min(127, new_vel))
else:
global_max = max(vel for _, _, _, _, _, vel, _ in notes)
factor = max_velocity / global_max if global_max > 0 else 1.0
for evt in buf:
if evt[0] == 'note':
new_vel = int(evt[5] * factor)
evt[5] = max(1, min(127, new_vel))
if not adj_in_place:
return buf
def chordify_score(score,
return_choridfied_score=True,
return_detected_score_information=False
):
if score:
num_tracks = 1
single_track_score = []
score_num_ticks = 0
if type(score[0]) == int and len(score) > 1:
score_type = 'MIDI_PY'
score_num_ticks = score[0]
while num_tracks < len(score):
for event in score[num_tracks]:
single_track_score.append(event)
num_tracks += 1
else:
score_type = 'CUSTOM'
single_track_score = score
if single_track_score and single_track_score[0]:
try:
if type(single_track_score[0][0]) == str or single_track_score[0][0] == 'note':
single_track_score.sort(key = lambda x: x[1])
score_timings = [s[1] for s in single_track_score]
else:
score_timings = [s[0] for s in single_track_score]
is_score_time_absolute = lambda sct: all(x <= y for x, y in zip(sct, sct[1:]))
score_timings_type = ''
if is_score_time_absolute(score_timings):
score_timings_type = 'ABS'
chords = []
cho = []
if score_type == 'MIDI_PY':
pe = single_track_score[0]
else:
pe = single_track_score[0]
for e in single_track_score:
if score_type == 'MIDI_PY':
time = e[1]
ptime = pe[1]
else:
time = e[0]
ptime = pe[0]
if time == ptime:
cho.append(e)
else:
if len(cho) > 0:
chords.append(cho)
cho = []
cho.append(e)
pe = e
if len(cho) > 0:
chords.append(cho)
else:
score_timings_type = 'REL'
chords = []
cho = []
for e in single_track_score:
if score_type == 'MIDI_PY':
time = e[1]
else:
time = e[0]
if time == 0:
cho.append(e)
else:
if len(cho) > 0:
chords.append(cho)
cho = []
cho.append(e)
if len(cho) > 0:
chords.append(cho)
requested_data = []
if return_detected_score_information:
detected_score_information = []
detected_score_information.append(['Score type', score_type])
detected_score_information.append(['Score timings type', score_timings_type])
detected_score_information.append(['Score tpq', score_num_ticks])
detected_score_information.append(['Score number of tracks', num_tracks])
requested_data.append(detected_score_information)
if return_choridfied_score and return_detected_score_information:
requested_data.append(chords)
if return_choridfied_score and not return_detected_score_information:
requested_data.extend(chords)
return requested_data
except Exception as e:
print('Error!')
print('Check score for consistency and compatibility!')
print('Exception detected:', e)
else:
return None
else:
return None
def fix_monophonic_score_durations(monophonic_score,
min_notes_gap=1,
min_notes_dur=1
):
fixed_score = []
if monophonic_score[0][0] == 'note':
for i in range(len(monophonic_score)-1):
note = monophonic_score[i]
nmt = monophonic_score[i+1][1]
if note[1]+note[2] >= nmt:
note_dur = max(1, nmt-note[1]-min_notes_gap)
else:
note_dur = note[2]
new_note = [note[0], note[1], note_dur] + note[3:]
if new_note[2] >= min_notes_dur:
fixed_score.append(new_note)
if monophonic_score[-1][2] >= min_notes_dur:
fixed_score.append(monophonic_score[-1])
elif type(monophonic_score[0][0]) == int:
for i in range(len(monophonic_score)-1):
note = monophonic_score[i]
nmt = monophonic_score[i+1][0]
if note[0]+note[1] >= nmt:
note_dur = max(1, nmt-note[0]-min_notes_gap)
else:
note_dur = note[1]
new_note = [note[0], note_dur] + note[2:]
if new_note[1] >= min_notes_dur:
fixed_score.append(new_note)
if monophonic_score[-1][1] >= min_notes_dur:
fixed_score.append(monophonic_score[-1])
return fixed_score
###################################################################################
ALL_CHORDS = [[0], [7], [5], [9], [2], [4], [11], [10], [8], [6], [3], [1], [0, 9], [2, 5],
[4, 7], [7, 10], [2, 11], [0, 3], [6, 9], [1, 4], [8, 11], [5, 8], [1, 10],
[3, 6], [0, 4], [5, 9], [7, 11], [0, 7], [0, 5], [2, 10], [2, 7], [2, 9],
[2, 6], [4, 11], [4, 9], [3, 7], [5, 10], [1, 9], [0, 8], [6, 11], [3, 11],
[4, 8], [3, 10], [3, 8], [1, 5], [1, 8], [1, 6], [6, 10], [3, 9], [4, 10],
[1, 7], [0, 6], [2, 8], [5, 11], [5, 7], [0, 10], [0, 2], [9, 11], [7, 9],
[2, 4], [4, 6], [3, 5], [8, 10], [6, 8], [1, 3], [1, 11], [2, 7, 11],
[0, 4, 7], [0, 5, 9], [2, 6, 9], [2, 5, 10], [1, 4, 9], [4, 8, 11], [3, 7, 10],
[0, 3, 8], [3, 6, 11], [1, 5, 8], [1, 6, 10], [0, 4, 9], [2, 5, 9], [4, 7, 11],
[2, 7, 10], [2, 6, 11], [0, 3, 7], [0, 5, 8], [1, 4, 8], [1, 6, 9], [3, 8, 11],
[1, 5, 10], [3, 6, 10], [2, 5, 11], [4, 7, 10], [3, 6, 9], [0, 6, 9],
[0, 3, 9], [2, 8, 11], [2, 5, 8], [1, 7, 10], [1, 4, 7], [0, 3, 6], [1, 4, 10],
[5, 8, 11], [2, 5, 7], [0, 7, 10], [0, 2, 9], [0, 3, 5], [6, 9, 11], [4, 7, 9],
[2, 4, 11], [5, 8, 10], [1, 3, 10], [1, 4, 6], [3, 6, 8], [1, 8, 11],
[5, 7, 11], [0, 4, 10], [3, 5, 9], [0, 2, 6], [1, 7, 9], [0, 7, 9], [5, 7, 10],
[2, 8, 10], [3, 9, 11], [0, 2, 5], [2, 4, 8], [2, 4, 7], [0, 2, 7], [2, 7, 9],
[4, 9, 11], [4, 6, 9], [1, 3, 7], [2, 4, 9], [0, 5, 7], [0, 3, 10], [2, 9, 11],
[0, 5, 10], [0, 6, 8], [4, 6, 10], [4, 6, 11], [1, 4, 11], [6, 8, 11],
[1, 5, 11], [1, 6, 11], [1, 8, 10], [1, 6, 8], [3, 5, 8], [3, 8, 10],
[1, 3, 8], [3, 5, 10], [1, 3, 6], [2, 5, 7, 10], [0, 3, 7, 10], [1, 4, 8, 11],
[2, 4, 7, 11], [0, 4, 7, 9], [0, 2, 5, 9], [2, 6, 9, 11], [1, 5, 8, 10],
[0, 3, 5, 8], [3, 6, 8, 11], [1, 3, 6, 10], [1, 4, 6, 9], [1, 5, 9], [0, 4, 8],
[2, 6, 10], [3, 7, 11], [0, 3, 6, 9], [2, 5, 8, 11], [1, 4, 7, 10],
[2, 5, 7, 11], [0, 2, 6, 9], [0, 4, 7, 10], [2, 4, 8, 11], [0, 3, 5, 9],
[1, 4, 7, 9], [3, 6, 9, 11], [2, 5, 8, 10], [1, 4, 6, 10], [0, 3, 6, 8],
[1, 3, 7, 10], [1, 5, 8, 11], [2, 4, 10], [5, 9, 11], [1, 5, 7], [0, 2, 8],
[0, 4, 6], [1, 7, 11], [3, 7, 9], [1, 3, 9], [7, 9, 11], [5, 7, 9], [0, 6, 10],
[0, 2, 10], [2, 6, 8], [0, 2, 4], [4, 8, 10], [1, 9, 11], [2, 4, 6],
[3, 5, 11], [3, 5, 7], [0, 8, 10], [4, 6, 8], [1, 3, 11], [6, 8, 10],
[1, 3, 5], [0, 2, 5, 10], [0, 5, 7, 9], [0, 3, 8, 10], [0, 2, 4, 7],
[4, 6, 8, 11], [3, 5, 7, 10], [2, 7, 9, 11], [2, 4, 6, 9], [1, 6, 8, 10],
[1, 4, 9, 11], [1, 3, 5, 8], [1, 3, 6, 11], [2, 5, 9, 11], [2, 4, 7, 10],
[0, 2, 5, 8], [1, 5, 7, 10], [0, 4, 6, 9], [1, 3, 6, 9], [0, 3, 6, 10],
[2, 6, 8, 11], [0, 2, 7, 9], [1, 4, 8, 10], [0, 3, 7, 9], [3, 5, 8, 11],
[0, 5, 7, 10], [0, 2, 5, 7], [1, 4, 7, 11], [2, 4, 7, 9], [0, 3, 5, 10],
[4, 6, 9, 11], [1, 4, 6, 11], [2, 4, 9, 11], [1, 6, 8, 11], [1, 3, 6, 8],
[1, 3, 8, 10], [3, 5, 8, 10], [4, 7, 9, 11], [0, 2, 7, 10], [2, 5, 7, 9],
[0, 2, 4, 9], [1, 6, 9, 11], [2, 4, 6, 11], [0, 3, 5, 7], [0, 5, 8, 10],
[1, 4, 6, 8], [1, 3, 5, 10], [1, 3, 8, 11], [3, 6, 8, 10], [0, 2, 5, 7, 10],
[0, 2, 4, 7, 9], [0, 2, 5, 7, 9], [1, 3, 7, 9], [1, 4, 6, 9, 11],
[1, 3, 6, 8, 11], [3, 5, 9, 11], [1, 3, 6, 8, 10], [1, 4, 6, 8, 11],
[1, 3, 5, 8, 10], [2, 4, 6, 9, 11], [2, 4, 8, 10], [2, 4, 7, 9, 11],
[0, 3, 5, 7, 10], [1, 5, 7, 11], [0, 2, 6, 8], [0, 3, 5, 8, 10], [0, 4, 6, 10],
[1, 3, 5, 9], [1, 5, 7, 9], [2, 6, 8, 10], [3, 7, 9, 11], [0, 2, 4, 8],
[0, 4, 6, 8], [0, 4, 8, 10], [2, 4, 6, 10], [1, 3, 7, 11], [0, 2, 6, 10],
[1, 5, 9, 11], [3, 5, 7, 11], [1, 7, 9, 11], [0, 2, 4, 6], [1, 3, 9, 11],
[0, 2, 4, 10], [5, 7, 9, 11], [2, 4, 6, 8], [0, 2, 8, 10], [3, 5, 7, 9],
[1, 3, 5, 7], [4, 6, 8, 10], [0, 6, 8, 10], [1, 3, 5, 11], [0, 3, 6, 8, 10],
[0, 2, 4, 6, 9], [1, 4, 7, 9, 11], [2, 4, 6, 8, 11], [1, 3, 6, 9, 11],
[1, 3, 5, 8, 11], [0, 2, 5, 8, 10], [1, 4, 6, 8, 10], [0, 3, 5, 7, 9],
[2, 5, 7, 9, 11], [1, 3, 5, 7, 10], [0, 2, 4, 7, 10], [1, 3, 5, 7, 9],
[1, 3, 5, 9, 11], [1, 5, 7, 9, 11], [1, 3, 7, 9, 11], [3, 5, 7, 9, 11],
[2, 4, 6, 8, 10], [0, 4, 6, 8, 10], [0, 2, 6, 8, 10], [1, 3, 5, 7, 11],
[0, 2, 4, 8, 10], [0, 2, 4, 6, 8], [0, 2, 4, 6, 10], [0, 2, 4, 6, 8, 10],
[1, 3, 5, 7, 9, 11]]
def find_exact_match_variable_length(list_of_lists, target_list, uncertain_indices):
# Infer possible values for each uncertain index
possible_values = {idx: set() for idx in uncertain_indices}
for sublist in list_of_lists:
for idx in uncertain_indices:
if idx < len(sublist):
possible_values[idx].add(sublist[idx])
# Generate all possible combinations for the uncertain elements
uncertain_combinations = product(*(possible_values[idx] for idx in uncertain_indices))
for combination in uncertain_combinations:
# Create a copy of the target list and update the uncertain elements
test_list = target_list[:]
for idx, value in zip(uncertain_indices, combination):
test_list[idx] = value
# Check if the modified target list is an exact match in the list of lists
# Only consider sublists that are at least as long as the target list
for sublist in list_of_lists:
if len(sublist) >= len(test_list) and sublist[:len(test_list)] == test_list:
return sublist # Return the matching sublist
return None # No exact match found
def advanced_validate_chord_pitches(chord, channel_to_check = 0, return_sorted = True):
pitches_chord = sorted(list(set([x[4] for x in chord if 0 < x[4] < 128 and x[3] == channel_to_check])))
if pitches_chord:
tones_chord = sorted(list(set([c % 12 for c in sorted(list(set(pitches_chord)))])))
if not bad_chord(tones_chord):
if return_sorted:
chord.sort(key = lambda x: x[4], reverse=True)
return chord
else:
bad_chord_indices = list(set([i for s in [[tones_chord.index(a), tones_chord.index(b)] for a, b in zip(tones_chord, tones_chord[1:]) if b-a == 1] for i in s]))
good_tones_chord = find_exact_match_variable_length(ALL_CHORDS, tones_chord, bad_chord_indices)
if good_tones_chord is not None:
fixed_chord = []
for c in chord:
if c[3] == channel_to_check:
if (c[4] % 12) in good_tones_chord:
fixed_chord.append(c)
else:
fixed_chord.append(c)
if return_sorted:
fixed_chord.sort(key = lambda x: x[4], reverse=True)
else:
if 0 in tones_chord and 11 in tones_chord:
tones_chord.remove(0)
fixed_tones = [[a, b] for a, b in zip(tones_chord, tones_chord[1:]) if b-a != 1]
fixed_tones_chord = []
for f in fixed_tones:
fixed_tones_chord.extend(f)
fixed_tones_chord = list(set(fixed_tones_chord))
fixed_chord = []
for c in chord:
if c[3] == channel_to_check:
if (c[4] % 12) in fixed_tones_chord:
fixed_chord.append(c)
else:
fixed_chord.append(c)
if return_sorted:
fixed_chord.sort(key = lambda x: x[4], reverse=True)
return fixed_chord
else:
chord.sort(key = lambda x: x[4], reverse=True)
return chord
###################################################################################
def analyze_score_pitches(score, channels_to_analyze=[0]):
analysis = {}
score_notes = [s for s in score if s[3] in channels_to_analyze]
cscore = chordify_score(score_notes)
chords_tones = []
all_tones = []
all_chords_good = True
bad_chords = []
for c in cscore:
tones = sorted(list(set([t[4] % 12 for t in c])))
chords_tones.append(tones)
all_tones.extend(tones)
if tones not in ALL_CHORDS:
all_chords_good = False
bad_chords.append(tones)
analysis['Number of notes'] = len(score_notes)
analysis['Number of chords'] = len(cscore)
analysis['Score tones'] = sorted(list(set(all_tones)))
analysis['Shortest chord'] = sorted(min(chords_tones, key=len))
analysis['Longest chord'] = sorted(max(chords_tones, key=len))
analysis['All chords good'] = all_chords_good
analysis['Bad chords'] = bad_chords
return analysis
###################################################################################
ALL_CHORDS_GROUPED = [[[0], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11]],
[[0, 2], [0, 3], [0, 4], [0, 5], [0, 6], [0, 7], [0, 8], [0, 9], [0, 10],
[1, 3], [1, 4], [1, 5], [1, 6], [1, 7], [1, 8], [1, 9], [1, 10], [1, 11],
[2, 4], [2, 5], [2, 6], [2, 7], [2, 8], [2, 9], [2, 10], [2, 11], [3, 5],
[3, 6], [3, 7], [3, 8], [3, 9], [3, 10], [3, 11], [4, 6], [4, 7], [4, 8],
[4, 9], [4, 10], [4, 11], [5, 7], [5, 8], [5, 9], [5, 10], [5, 11], [6, 8],
[6, 9], [6, 10], [6, 11], [7, 9], [7, 10], [7, 11], [8, 10], [8, 11],
[9, 11]],
[[0, 2, 4], [0, 2, 5], [0, 3, 5], [0, 2, 6], [0, 3, 6], [0, 4, 6], [0, 2, 7],
[0, 3, 7], [0, 4, 7], [0, 5, 7], [0, 2, 8], [0, 3, 8], [0, 4, 8], [0, 5, 8],
[0, 6, 8], [0, 2, 9], [0, 3, 9], [0, 4, 9], [0, 5, 9], [0, 6, 9], [0, 7, 9],
[0, 2, 10], [0, 3, 10], [0, 4, 10], [0, 5, 10], [0, 6, 10], [0, 7, 10],
[0, 8, 10], [1, 3, 5], [1, 3, 6], [1, 4, 6], [1, 3, 7], [1, 4, 7], [1, 5, 7],
[1, 3, 8], [1, 4, 8], [1, 5, 8], [1, 6, 8], [1, 3, 9], [1, 4, 9], [1, 5, 9],
[1, 6, 9], [1, 7, 9], [1, 3, 10], [1, 4, 10], [1, 5, 10], [1, 6, 10],
[1, 7, 10], [1, 8, 10], [1, 3, 11], [1, 4, 11], [1, 5, 11], [1, 6, 11],
[1, 7, 11], [1, 8, 11], [1, 9, 11], [2, 4, 6], [2, 4, 7], [2, 5, 7],
[2, 4, 8], [2, 5, 8], [2, 6, 8], [2, 4, 9], [2, 5, 9], [2, 6, 9], [2, 7, 9],
[2, 4, 10], [2, 5, 10], [2, 6, 10], [2, 7, 10], [2, 8, 10], [2, 4, 11],
[2, 5, 11], [2, 6, 11], [2, 7, 11], [2, 8, 11], [2, 9, 11], [3, 5, 7],
[3, 5, 8], [3, 6, 8], [3, 5, 9], [3, 6, 9], [3, 7, 9], [3, 5, 10], [3, 6, 10],
[3, 7, 10], [3, 8, 10], [3, 5, 11], [3, 6, 11], [3, 7, 11], [3, 8, 11],
[3, 9, 11], [4, 6, 8], [4, 6, 9], [4, 7, 9], [4, 6, 10], [4, 7, 10],
[4, 8, 10], [4, 6, 11], [4, 7, 11], [4, 8, 11], [4, 9, 11], [5, 7, 9],
[5, 7, 10], [5, 8, 10], [5, 7, 11], [5, 8, 11], [5, 9, 11], [6, 8, 10],
[6, 8, 11], [6, 9, 11], [7, 9, 11]],
[[0, 2, 4, 6], [0, 2, 4, 7], [0, 2, 5, 7], [0, 3, 5, 7], [0, 2, 4, 8],
[0, 2, 5, 8], [0, 2, 6, 8], [0, 3, 5, 8], [0, 3, 6, 8], [0, 4, 6, 8],
[0, 2, 4, 9], [0, 2, 5, 9], [0, 2, 6, 9], [0, 2, 7, 9], [0, 3, 5, 9],
[0, 3, 6, 9], [0, 3, 7, 9], [0, 4, 6, 9], [0, 4, 7, 9], [0, 5, 7, 9],
[0, 2, 4, 10], [0, 2, 5, 10], [0, 2, 6, 10], [0, 2, 7, 10], [0, 2, 8, 10],
[0, 3, 5, 10], [0, 3, 6, 10], [0, 3, 7, 10], [0, 3, 8, 10], [0, 4, 6, 10],
[0, 4, 7, 10], [0, 4, 8, 10], [0, 5, 7, 10], [0, 5, 8, 10], [0, 6, 8, 10],
[1, 3, 5, 7], [1, 3, 5, 8], [1, 3, 6, 8], [1, 4, 6, 8], [1, 3, 5, 9],
[1, 3, 6, 9], [1, 3, 7, 9], [1, 4, 6, 9], [1, 4, 7, 9], [1, 5, 7, 9],
[1, 3, 5, 10], [1, 3, 6, 10], [1, 3, 7, 10], [1, 3, 8, 10], [1, 4, 6, 10],
[1, 4, 7, 10], [1, 4, 8, 10], [1, 5, 7, 10], [1, 5, 8, 10], [1, 6, 8, 10],
[1, 3, 5, 11], [1, 3, 6, 11], [1, 3, 7, 11], [1, 3, 8, 11], [1, 3, 9, 11],
[1, 4, 6, 11], [1, 4, 7, 11], [1, 4, 8, 11], [1, 4, 9, 11], [1, 5, 7, 11],
[1, 5, 8, 11], [1, 5, 9, 11], [1, 6, 8, 11], [1, 6, 9, 11], [1, 7, 9, 11],
[2, 4, 6, 8], [2, 4, 6, 9], [2, 4, 7, 9], [2, 5, 7, 9], [2, 4, 6, 10],
[2, 4, 7, 10], [2, 4, 8, 10], [2, 5, 7, 10], [2, 5, 8, 10], [2, 6, 8, 10],
[2, 4, 6, 11], [2, 4, 7, 11], [2, 4, 8, 11], [2, 4, 9, 11], [2, 5, 7, 11],
[2, 5, 8, 11], [2, 5, 9, 11], [2, 6, 8, 11], [2, 6, 9, 11], [2, 7, 9, 11],
[3, 5, 7, 9], [3, 5, 7, 10], [3, 5, 8, 10], [3, 6, 8, 10], [3, 5, 7, 11],
[3, 5, 8, 11], [3, 5, 9, 11], [3, 6, 8, 11], [3, 6, 9, 11], [3, 7, 9, 11],
[4, 6, 8, 10], [4, 6, 8, 11], [4, 6, 9, 11], [4, 7, 9, 11], [5, 7, 9, 11]],
[[0, 2, 4, 6, 8], [0, 2, 4, 6, 9], [0, 2, 4, 7, 9], [0, 2, 5, 7, 9],
[0, 3, 5, 7, 9], [0, 2, 4, 6, 10], [0, 2, 4, 7, 10], [0, 2, 4, 8, 10],
[0, 2, 5, 7, 10], [0, 2, 5, 8, 10], [0, 2, 6, 8, 10], [0, 3, 5, 7, 10],
[0, 3, 5, 8, 10], [0, 3, 6, 8, 10], [0, 4, 6, 8, 10], [1, 3, 5, 7, 9],
[1, 3, 5, 7, 10], [1, 3, 5, 8, 10], [1, 3, 6, 8, 10], [1, 4, 6, 8, 10],
[1, 3, 5, 7, 11], [1, 3, 5, 8, 11], [1, 3, 5, 9, 11], [1, 3, 6, 8, 11],
[1, 3, 6, 9, 11], [1, 3, 7, 9, 11], [1, 4, 6, 8, 11], [1, 4, 6, 9, 11],
[1, 4, 7, 9, 11], [1, 5, 7, 9, 11], [2, 4, 6, 8, 10], [2, 4, 6, 8, 11],
[2, 4, 6, 9, 11], [2, 4, 7, 9, 11], [2, 5, 7, 9, 11], [3, 5, 7, 9, 11]],
[[0, 2, 4, 6, 8, 10], [1, 3, 5, 7, 9, 11]]]
def group_sublists_by_length(lst):
unique_lengths = sorted(list(set(map(len, lst))), reverse=True)
return [[x for x in lst if len(x) == i] for i in unique_lengths]
def pitches_to_tones_chord(pitches):
return sorted(set([p % 12 for p in pitches]))
def tones_chord_to_pitches(tones_chord, base_pitch=60):
return [t+base_pitch for t in tones_chord if 0 <= t < 12]
###################################################################################
def advanced_score_processor(raw_score,
patches_to_analyze=list(range(129)),
return_score_analysis=False,
return_enhanced_score=False,
return_enhanced_score_notes=False,
return_enhanced_monophonic_melody=False,
return_chordified_enhanced_score=False,
return_chordified_enhanced_score_with_lyrics=False,
return_score_tones_chords=False,
return_text_and_lyric_events=False,
apply_sustain=False
):
'''TMIDIX Advanced Score Processor'''
# Score data types detection
if raw_score and type(raw_score) == list:
num_ticks = 0
num_tracks = 1
basic_single_track_score = []
if type(raw_score[0]) != int:
if len(raw_score[0]) < 5 and type(raw_score[0][0]) != str:
return ['Check score for errors and compatibility!']
else:
basic_single_track_score = copy.deepcopy(raw_score)
else:
num_ticks = raw_score[0]
while num_tracks < len(raw_score):
for event in raw_score[num_tracks]:
ev = copy.deepcopy(event)
basic_single_track_score.append(ev)
num_tracks += 1
for e in basic_single_track_score:
if e[0] == 'note':
e[3] = e[3] % 16
e[4] = e[4] % 128
e[5] = e[5] % 128
if e[0] == 'patch_change':
e[2] = e[2] % 16
e[3] = e[3] % 128
if apply_sustain:
apply_sustain_to_ms_score([1000, basic_single_track_score])
basic_single_track_score.sort(key=lambda x: x[4] if x[0] == 'note' else 128, reverse=True)
basic_single_track_score.sort(key=lambda x: x[1])
enhanced_single_track_score = []
patches = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
all_score_patches = []
num_patch_changes = 0
for event in basic_single_track_score:
if event[0] == 'patch_change':
patches[event[2]] = event[3]
enhanced_single_track_score.append(event)
num_patch_changes += 1
if event[0] == 'note':
if event[3] != 9:
event.extend([patches[event[3]]])
all_score_patches.extend([patches[event[3]]])
else:
event.extend([128])
all_score_patches.extend([128])
if enhanced_single_track_score:
if (event[1] == enhanced_single_track_score[-1][1]):
if ([event[3], event[4]] != enhanced_single_track_score[-1][3:5]):
enhanced_single_track_score.append(event)
else:
enhanced_single_track_score.append(event)
else:
enhanced_single_track_score.append(event)
if event[0] not in ['note', 'patch_change']:
enhanced_single_track_score.append(event)
enhanced_single_track_score.sort(key=lambda x: x[6] if x[0] == 'note' else -1)
enhanced_single_track_score.sort(key=lambda x: x[4] if x[0] == 'note' else 128, reverse=True)
enhanced_single_track_score.sort(key=lambda x: x[1])
# Analysis and chordification
cscore = []
cescore = []
chords_tones = []
tones_chords = []
all_tones = []
all_chords_good = True
bad_chords = []
bad_chords_count = 0
score_notes = []
score_pitches = []
score_patches = []
num_text_events = 0
num_lyric_events = 0
num_other_events = 0
text_and_lyric_events = []
text_and_lyric_events_latin = None
analysis = {}
score_notes = [s for s in enhanced_single_track_score if s[0] == 'note' and s[6] in patches_to_analyze]
score_patches = [sn[6] for sn in score_notes]
if return_text_and_lyric_events:
text_and_lyric_events = [e for e in enhanced_single_track_score if e[0] in ['text_event', 'lyric']]
if text_and_lyric_events:
text_and_lyric_events_latin = True
for e in text_and_lyric_events:
try:
tle = str(e[2].decode())
except:
tle = str(e[2])
for c in tle:
if not 0 <= ord(c) < 128:
text_and_lyric_events_latin = False
if (return_chordified_enhanced_score or return_score_analysis) and any(elem in patches_to_analyze for elem in score_patches):
cescore = chordify_score([num_ticks, enhanced_single_track_score])
if return_score_analysis:
cscore = chordify_score(score_notes)
score_pitches = [sn[4] for sn in score_notes]
text_events = [e for e in enhanced_single_track_score if e[0] == 'text_event']
num_text_events = len(text_events)
lyric_events = [e for e in enhanced_single_track_score if e[0] == 'lyric']
num_lyric_events = len(lyric_events)
other_events = [e for e in enhanced_single_track_score if e[0] not in ['note', 'patch_change', 'text_event', 'lyric']]
num_other_events = len(other_events)
for c in cscore:
tones = sorted(set([t[4] % 12 for t in c if t[3] != 9]))
if tones:
chords_tones.append(tones)
all_tones.extend(tones)
if tones not in ALL_CHORDS:
all_chords_good = False
bad_chords.append(tones)
bad_chords_count += 1
analysis['Number of ticks per quarter note'] = num_ticks
analysis['Number of tracks'] = num_tracks
analysis['Number of all events'] = len(enhanced_single_track_score)
analysis['Number of patch change events'] = num_patch_changes
analysis['Number of text events'] = num_text_events
analysis['Number of lyric events'] = num_lyric_events
analysis['All text and lyric events Latin'] = text_and_lyric_events_latin
analysis['Number of other events'] = num_other_events
analysis['Number of score notes'] = len(score_notes)
analysis['Number of score chords'] = len(cscore)
analysis['Score patches'] = sorted(set(score_patches))
analysis['Score pitches'] = sorted(set(score_pitches))
analysis['Score tones'] = sorted(set(all_tones))
if chords_tones:
analysis['Shortest chord'] = sorted(min(chords_tones, key=len))
analysis['Longest chord'] = sorted(max(chords_tones, key=len))
analysis['All chords good'] = all_chords_good
analysis['Number of bad chords'] = bad_chords_count
analysis['Bad chords'] = sorted([list(c) for c in set(tuple(bc) for bc in bad_chords)])
else:
analysis['Error'] = 'Provided score does not have specified patches to analyse'
analysis['Provided patches to analyse'] = sorted(patches_to_analyze)
analysis['Patches present in the score'] = sorted(set(all_score_patches))
if return_enhanced_monophonic_melody:
score_notes_copy = copy.deepcopy(score_notes)
chordified_score_notes = chordify_score(score_notes_copy)
melody = [c[0] for c in chordified_score_notes]
fixed_melody = []
for i in range(len(melody)-1):
note = melody[i]
nmt = melody[i+1][1]
if note[1]+note[2] >= nmt:
note_dur = nmt-note[1]-1
else:
note_dur = note[2]
melody[i][2] = note_dur
fixed_melody.append(melody[i])
fixed_melody.append(melody[-1])
if return_score_tones_chords:
cscore = chordify_score(score_notes)
for c in cscore:
tones_chord = sorted(set([t[4] % 12 for t in c if t[3] != 9]))
if tones_chord:
tones_chords.append(tones_chord)
if return_chordified_enhanced_score_with_lyrics:
score_with_lyrics = [e for e in enhanced_single_track_score if e[0] in ['note', 'text_event', 'lyric']]
chordified_enhanced_score_with_lyrics = chordify_score(score_with_lyrics)
# Returned data
requested_data = []
if return_score_analysis and analysis:
requested_data.append([[k, v] for k, v in analysis.items()])
if return_enhanced_score and enhanced_single_track_score:
requested_data.append([num_ticks, enhanced_single_track_score])
if return_enhanced_score_notes and score_notes:
requested_data.append(score_notes)
if return_enhanced_monophonic_melody and fixed_melody:
requested_data.append(fixed_melody)
if return_chordified_enhanced_score and cescore:
requested_data.append(cescore)
if return_chordified_enhanced_score_with_lyrics and chordified_enhanced_score_with_lyrics:
requested_data.append(chordified_enhanced_score_with_lyrics)
if return_score_tones_chords and tones_chords:
requested_data.append(tones_chords)
if return_text_and_lyric_events and text_and_lyric_events:
requested_data.append(text_and_lyric_events)
return requested_data
else:
return ['Check score for errors and compatibility!']
###################################################################################
def replace_bad_tones_chord(bad_tones_chord):
bad_chord_p = [0] * 12
for b in bad_tones_chord:
bad_chord_p[b] = 1
match_ratios = []
good_chords = []
for c in ALL_CHORDS:
good_chord_p = [0] * 12
for cc in c:
good_chord_p[cc] = 1
good_chords.append(good_chord_p)
match_ratios.append(sum(i == j for i, j in zip(good_chord_p, bad_chord_p)) / len(good_chord_p))
best_good_chord = good_chords[match_ratios.index(max(match_ratios))]
replaced_chord = []
for i in range(len(best_good_chord)):
if best_good_chord[i] == 1:
replaced_chord.append(i)
return [replaced_chord, max(match_ratios)]
###################################################################################
def check_and_fix_chord(chord,
channel_index=3,
pitch_index=4
):
tones_chord = sorted(set([t[pitch_index] % 12 for t in chord if t[channel_index] != 9]))
notes_events = [t for t in chord if t[channel_index] != 9]
notes_events.sort(key=lambda x: x[pitch_index], reverse=True)
drums_events = [t for t in chord if t[channel_index] == 9]
checked_and_fixed_chord = []
if tones_chord:
new_tones_chord = advanced_check_and_fix_tones_chord(tones_chord, high_pitch=notes_events[0][pitch_index])
if new_tones_chord != tones_chord:
if len(notes_events) > 1:
checked_and_fixed_chord.extend([notes_events[0]])
for cc in notes_events[1:]:
if cc[channel_index] != 9:
if (cc[pitch_index] % 12) in new_tones_chord:
checked_and_fixed_chord.extend([cc])
checked_and_fixed_chord.extend(drums_events)
else:
checked_and_fixed_chord.extend([notes_events[0]])
else:
checked_and_fixed_chord.extend(chord)
else:
checked_and_fixed_chord.extend(chord)
checked_and_fixed_chord.sort(key=lambda x: x[pitch_index], reverse=True)
return checked_and_fixed_chord
###################################################################################
def find_similar_tones_chord(tones_chord,
max_match_threshold=1,
randomize_chords_matches=False,
custom_chords_list=[]):
chord_p = [0] * 12
for b in tones_chord:
chord_p[b] = 1
match_ratios = []
good_chords = []
if custom_chords_list:
CHORDS = copy.deepcopy([list(x) for x in set(tuple(t) for t in custom_chords_list)])
else:
CHORDS = copy.deepcopy(ALL_CHORDS)
if randomize_chords_matches:
random.shuffle(CHORDS)
for c in CHORDS:
good_chord_p = [0] * 12
for cc in c:
good_chord_p[cc] = 1
good_chords.append(good_chord_p)
match_ratio = sum(i == j for i, j in zip(good_chord_p, chord_p)) / len(good_chord_p)
if match_ratio < max_match_threshold:
match_ratios.append(match_ratio)
else:
match_ratios.append(0)
best_good_chord = good_chords[match_ratios.index(max(match_ratios))]
similar_chord = []
for i in range(len(best_good_chord)):
if best_good_chord[i] == 1:
similar_chord.append(i)
return [similar_chord, max(match_ratios)]
###################################################################################
def generate_tones_chords_progression(number_of_chords_to_generate=100,
start_tones_chord=[],
custom_chords_list=[]):
if start_tones_chord:
start_chord = start_tones_chord
else:
start_chord = random.choice(ALL_CHORDS)
chord = []
chords_progression = [start_chord]
for i in range(number_of_chords_to_generate):
if not chord:
chord = start_chord
if custom_chords_list:
chord = find_similar_tones_chord(chord, randomize_chords_matches=True, custom_chords_list=custom_chords_list)[0]
else:
chord = find_similar_tones_chord(chord, randomize_chords_matches=True)[0]
chords_progression.append(chord)
return chords_progression
###################################################################################
def ascii_texts_search(texts = ['text1', 'text2', 'text3'],
search_query = 'Once upon a time...',
deterministic_matching = False
):
texts_copy = texts
if not deterministic_matching:
texts_copy = copy.deepcopy(texts)
random.shuffle(texts_copy)
clean_texts = []
for t in texts_copy:
text_words_list = [at.split(chr(32)) for at in t.split(chr(10))]
clean_text_words_list = []
for twl in text_words_list:
for w in twl:
clean_text_words_list.append(''.join(filter(str.isalpha, w.lower())))
clean_texts.append(clean_text_words_list)
text_search_query = [at.split(chr(32)) for at in search_query.split(chr(10))]
clean_text_search_query = []
for w in text_search_query:
for ww in w:
clean_text_search_query.append(''.join(filter(str.isalpha, ww.lower())))
if clean_texts[0] and clean_text_search_query:
texts_match_ratios = []
words_match_indexes = []
for t in clean_texts:
word_match_count = 0
wmis = []
for c in clean_text_search_query:
if c in t:
word_match_count += 1
wmis.append(t.index(c))
else:
wmis.append(-1)
words_match_indexes.append(wmis)
words_match_indexes_consequtive = all(abs(b) - abs(a) == 1 for a, b in zip(wmis, wmis[1:]))
words_match_indexes_consequtive_ratio = sum([abs(b) - abs(a) == 1 for a, b in zip(wmis, wmis[1:])]) / len(wmis)
if words_match_indexes_consequtive:
texts_match_ratios.append(word_match_count / len(clean_text_search_query))
else:
texts_match_ratios.append(((word_match_count / len(clean_text_search_query)) + words_match_indexes_consequtive_ratio) / 2)
if texts_match_ratios:
max_text_match_ratio = max(texts_match_ratios)
max_match_ratio_text = texts_copy[texts_match_ratios.index(max_text_match_ratio)]
max_text_words_match_indexes = words_match_indexes[texts_match_ratios.index(max_text_match_ratio)]
return [max_match_ratio_text, max_text_match_ratio, max_text_words_match_indexes]
else:
return None
###################################################################################
def ascii_text_words_counter(ascii_text):
text_words_list = [at.split(chr(32)) for at in ascii_text.split(chr(10))]
clean_text_words_list = []
for twl in text_words_list:
for w in twl:
wo = ''
for ww in w.lower():
if 96 < ord(ww) < 123:
wo += ww
if wo != '':
clean_text_words_list.append(wo)
words = {}
for i in clean_text_words_list:
words[i] = words.get(i, 0) + 1
words_sorted = dict(sorted(words.items(), key=lambda item: item[1], reverse=True))
return len(clean_text_words_list), words_sorted, clean_text_words_list
###################################################################################
def check_and_fix_tones_chord(tones_chord, use_full_chords=True):
tones_chord_combs = [list(comb) for i in range(len(tones_chord), 0, -1) for comb in combinations(tones_chord, i)]
if use_full_chords:
CHORDS = ALL_CHORDS_FULL
else:
CHORDS = ALL_CHORDS_SORTED
for c in tones_chord_combs:
if c in CHORDS:
checked_tones_chord = c
break
return sorted(checked_tones_chord)
###################################################################################
def find_closest_tone(tones, tone):
return min(tones, key=lambda x:abs(x-tone))
###################################################################################
def advanced_check_and_fix_tones_chord(tones_chord, high_pitch=0, use_full_chords=True):
tones_chord_combs = [list(comb) for i in range(len(tones_chord), 0, -1) for comb in combinations(tones_chord, i)]
if use_full_chords:
CHORDS = ALL_CHORDS_FULL
else:
CHORDS = ALL_CHORDS_SORTED
for c in tones_chord_combs:
if c in CHORDS:
tchord = c
if 0 < high_pitch < 128 and len(tchord) == 1:
tchord = [high_pitch % 12]
return tchord
###################################################################################
def create_similarity_matrix(list_of_values, matrix_length=0):
counts = Counter(list_of_values).items()
if matrix_length > 0:
sim_matrix = [0] * max(matrix_length, len(list_of_values))
else:
sim_matrix = [0] * len(counts)
for c in counts:
sim_matrix[c[0]] = c[1]
similarity_matrix = [[0] * len(sim_matrix) for _ in range(len(sim_matrix))]
for i in range(len(sim_matrix)):
for j in range(len(sim_matrix)):
if max(sim_matrix[i], sim_matrix[j]) != 0:
similarity_matrix[i][j] = min(sim_matrix[i], sim_matrix[j]) / max(sim_matrix[i], sim_matrix[j])
return similarity_matrix, sim_matrix
###################################################################################
def ceil_with_precision(value, decimal_places):
factor = 10 ** decimal_places
return math.ceil(value * factor) / factor
###################################################################################
def augment_enhanced_score_notes(enhanced_score_notes,
timings_divider=16,
full_sorting=True,
timings_shift=0,
pitch_shift=0,
ceil_timings=False,
round_timings=False,
legacy_timings=True,
sort_drums_last=False,
even_timings=False
):
esn = copy.deepcopy(enhanced_score_notes)
pe = enhanced_score_notes[0]
abs_time = max(0, int(enhanced_score_notes[0][1] / timings_divider))
for i, e in enumerate(esn):
dtime = (e[1] / timings_divider) - (pe[1] / timings_divider)
if round_timings:
dtime = round(dtime)
else:
if ceil_timings:
dtime = math.ceil(dtime)
else:
dtime = int(dtime)
if legacy_timings:
abs_time = int(e[1] / timings_divider) + timings_shift
else:
abs_time += dtime
e[1] = max(0, abs_time + timings_shift)
if round_timings:
e[2] = max(1, round(e[2] / timings_divider)) + timings_shift
else:
if ceil_timings:
e[2] = max(1, math.ceil(e[2] / timings_divider)) + timings_shift
else:
e[2] = max(1, int(e[2] / timings_divider)) + timings_shift
e[4] = max(1, min(127, e[4] + pitch_shift))
pe = enhanced_score_notes[i]
if even_timings:
for e in esn:
if e[1] % 2 != 0:
e[1] += 1
if e[2] % 2 != 0:
e[2] += 1
if full_sorting:
# Sorting by patch, reverse pitch and start-time
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
###################################################################################
def stack_list(lst, base=12):
return sum(j * base**i for i, j in enumerate(lst[::-1]))
def destack_list(num, base=12):
lst = []
while num:
lst.append(num % base)
num //= base
return lst[::-1]
###################################################################################
def extract_melody(chordified_enhanced_score,
melody_range=[48, 84],
melody_channel=0,
melody_patch=0,
melody_velocity=0,
stacked_melody=False,
stacked_melody_base_pitch=60
):
if stacked_melody:
all_pitches_chords = []
for e in chordified_enhanced_score:
all_pitches_chords.append(sorted(set([p[4] for p in e]), reverse=True))
melody_score = []
for i, chord in enumerate(chordified_enhanced_score):
if melody_velocity > 0:
vel = melody_velocity
else:
vel = chord[0][5]
melody_score.append(['note', chord[0][1], chord[0][2], melody_channel, stacked_melody_base_pitch+(stack_list([p % 12 for p in all_pitches_chords[i]]) % 12), vel, melody_patch])
else:
melody_score = copy.deepcopy([c[0] for c in chordified_enhanced_score if c[0][3] != 9])
for e in melody_score:
e[3] = melody_channel
if melody_velocity > 0:
e[5] = melody_velocity
e[6] = melody_patch
if e[4] < melody_range[0]:
e[4] = (e[4] % 12) + melody_range[0]
if e[4] >= melody_range[1]:
e[4] = (e[4] % 12) + (melody_range[1]-12)
return fix_monophonic_score_durations(melody_score)
###################################################################################
def flip_enhanced_score_notes(enhanced_score_notes):
min_pitch = min([e[4] for e in enhanced_score_notes if e[3] != 9])
fliped_score_pitches = [127 - e[4]for e in enhanced_score_notes if e[3] != 9]
delta_min_pitch = min_pitch - min([p for p in fliped_score_pitches])
output_score = copy.deepcopy(enhanced_score_notes)
for e in output_score:
if e[3] != 9:
e[4] = (127 - e[4]) + delta_min_pitch
return output_score
###################################################################################
ALL_CHORDS_SORTED = [[0], [0, 2], [0, 3], [0, 4], [0, 2, 4], [0, 5], [0, 2, 5], [0, 3, 5], [0, 6],
[0, 2, 6], [0, 3, 6], [0, 4, 6], [0, 2, 4, 6], [0, 7], [0, 2, 7], [0, 3, 7],
[0, 4, 7], [0, 5, 7], [0, 2, 4, 7], [0, 2, 5, 7], [0, 3, 5, 7], [0, 8],
[0, 2, 8], [0, 3, 8], [0, 4, 8], [0, 5, 8], [0, 6, 8], [0, 2, 4, 8],
[0, 2, 5, 8], [0, 2, 6, 8], [0, 3, 5, 8], [0, 3, 6, 8], [0, 4, 6, 8],
[0, 2, 4, 6, 8], [0, 9], [0, 2, 9], [0, 3, 9], [0, 4, 9], [0, 5, 9], [0, 6, 9],
[0, 7, 9], [0, 2, 4, 9], [0, 2, 5, 9], [0, 2, 6, 9], [0, 2, 7, 9],
[0, 3, 5, 9], [0, 3, 6, 9], [0, 3, 7, 9], [0, 4, 6, 9], [0, 4, 7, 9],
[0, 5, 7, 9], [0, 2, 4, 6, 9], [0, 2, 4, 7, 9], [0, 2, 5, 7, 9],
[0, 3, 5, 7, 9], [0, 10], [0, 2, 10], [0, 3, 10], [0, 4, 10], [0, 5, 10],
[0, 6, 10], [0, 7, 10], [0, 8, 10], [0, 2, 4, 10], [0, 2, 5, 10],
[0, 2, 6, 10], [0, 2, 7, 10], [0, 2, 8, 10], [0, 3, 5, 10], [0, 3, 6, 10],
[0, 3, 7, 10], [0, 3, 8, 10], [0, 4, 6, 10], [0, 4, 7, 10], [0, 4, 8, 10],
[0, 5, 7, 10], [0, 5, 8, 10], [0, 6, 8, 10], [0, 2, 4, 6, 10],
[0, 2, 4, 7, 10], [0, 2, 4, 8, 10], [0, 2, 5, 7, 10], [0, 2, 5, 8, 10],
[0, 2, 6, 8, 10], [0, 3, 5, 7, 10], [0, 3, 5, 8, 10], [0, 3, 6, 8, 10],
[0, 4, 6, 8, 10], [0, 2, 4, 6, 8, 10], [1], [1, 3], [1, 4], [1, 5], [1, 3, 5],
[1, 6], [1, 3, 6], [1, 4, 6], [1, 7], [1, 3, 7], [1, 4, 7], [1, 5, 7],
[1, 3, 5, 7], [1, 8], [1, 3, 8], [1, 4, 8], [1, 5, 8], [1, 6, 8], [1, 3, 5, 8],
[1, 3, 6, 8], [1, 4, 6, 8], [1, 9], [1, 3, 9], [1, 4, 9], [1, 5, 9], [1, 6, 9],
[1, 7, 9], [1, 3, 5, 9], [1, 3, 6, 9], [1, 3, 7, 9], [1, 4, 6, 9],
[1, 4, 7, 9], [1, 5, 7, 9], [1, 3, 5, 7, 9], [1, 10], [1, 3, 10], [1, 4, 10],
[1, 5, 10], [1, 6, 10], [1, 7, 10], [1, 8, 10], [1, 3, 5, 10], [1, 3, 6, 10],
[1, 3, 7, 10], [1, 3, 8, 10], [1, 4, 6, 10], [1, 4, 7, 10], [1, 4, 8, 10],
[1, 5, 7, 10], [1, 5, 8, 10], [1, 6, 8, 10], [1, 3, 5, 7, 10],
[1, 3, 5, 8, 10], [1, 3, 6, 8, 10], [1, 4, 6, 8, 10], [1, 11], [1, 3, 11],
[1, 4, 11], [1, 5, 11], [1, 6, 11], [1, 7, 11], [1, 8, 11], [1, 9, 11],
[1, 3, 5, 11], [1, 3, 6, 11], [1, 3, 7, 11], [1, 3, 8, 11], [1, 3, 9, 11],
[1, 4, 6, 11], [1, 4, 7, 11], [1, 4, 8, 11], [1, 4, 9, 11], [1, 5, 7, 11],
[1, 5, 8, 11], [1, 5, 9, 11], [1, 6, 8, 11], [1, 6, 9, 11], [1, 7, 9, 11],
[1, 3, 5, 7, 11], [1, 3, 5, 8, 11], [1, 3, 5, 9, 11], [1, 3, 6, 8, 11],
[1, 3, 6, 9, 11], [1, 3, 7, 9, 11], [1, 4, 6, 8, 11], [1, 4, 6, 9, 11],
[1, 4, 7, 9, 11], [1, 5, 7, 9, 11], [1, 3, 5, 7, 9, 11], [2], [2, 4], [2, 5],
[2, 6], [2, 4, 6], [2, 7], [2, 4, 7], [2, 5, 7], [2, 8], [2, 4, 8], [2, 5, 8],
[2, 6, 8], [2, 4, 6, 8], [2, 9], [2, 4, 9], [2, 5, 9], [2, 6, 9], [2, 7, 9],
[2, 4, 6, 9], [2, 4, 7, 9], [2, 5, 7, 9], [2, 10], [2, 4, 10], [2, 5, 10],
[2, 6, 10], [2, 7, 10], [2, 8, 10], [2, 4, 6, 10], [2, 4, 7, 10],
[2, 4, 8, 10], [2, 5, 7, 10], [2, 5, 8, 10], [2, 6, 8, 10], [2, 4, 6, 8, 10],
[2, 11], [2, 4, 11], [2, 5, 11], [2, 6, 11], [2, 7, 11], [2, 8, 11],
[2, 9, 11], [2, 4, 6, 11], [2, 4, 7, 11], [2, 4, 8, 11], [2, 4, 9, 11],
[2, 5, 7, 11], [2, 5, 8, 11], [2, 5, 9, 11], [2, 6, 8, 11], [2, 6, 9, 11],
[2, 7, 9, 11], [2, 4, 6, 8, 11], [2, 4, 6, 9, 11], [2, 4, 7, 9, 11],
[2, 5, 7, 9, 11], [3], [3, 5], [3, 6], [3, 7], [3, 5, 7], [3, 8], [3, 5, 8],
[3, 6, 8], [3, 9], [3, 5, 9], [3, 6, 9], [3, 7, 9], [3, 5, 7, 9], [3, 10],
[3, 5, 10], [3, 6, 10], [3, 7, 10], [3, 8, 10], [3, 5, 7, 10], [3, 5, 8, 10],
[3, 6, 8, 10], [3, 11], [3, 5, 11], [3, 6, 11], [3, 7, 11], [3, 8, 11],
[3, 9, 11], [3, 5, 7, 11], [3, 5, 8, 11], [3, 5, 9, 11], [3, 6, 8, 11],
[3, 6, 9, 11], [3, 7, 9, 11], [3, 5, 7, 9, 11], [4], [4, 6], [4, 7], [4, 8],
[4, 6, 8], [4, 9], [4, 6, 9], [4, 7, 9], [4, 10], [4, 6, 10], [4, 7, 10],
[4, 8, 10], [4, 6, 8, 10], [4, 11], [4, 6, 11], [4, 7, 11], [4, 8, 11],
[4, 9, 11], [4, 6, 8, 11], [4, 6, 9, 11], [4, 7, 9, 11], [5], [5, 7], [5, 8],
[5, 9], [5, 7, 9], [5, 10], [5, 7, 10], [5, 8, 10], [5, 11], [5, 7, 11],
[5, 8, 11], [5, 9, 11], [5, 7, 9, 11], [6], [6, 8], [6, 9], [6, 10],
[6, 8, 10], [6, 11], [6, 8, 11], [6, 9, 11], [7], [7, 9], [7, 10], [7, 11],
[7, 9, 11], [8], [8, 10], [8, 11], [9], [9, 11], [10], [11]]
###################################################################################
MIDI_Instruments_Families = {
0: 'Piano Family',
1: 'Chromatic Percussion Family',
2: 'Organ Family',
3: 'Guitar Family',
4: 'Bass Family',
5: 'Strings Family',
6: 'Ensemble Family',
7: 'Brass Family',
8: 'Reed Family',
9: 'Pipe Family',
10: 'Synth Lead Family',
11: 'Synth Pad Family',
12: 'Synth Effects Family',
13: 'Ethnic Family',
14: 'Percussive Family',
15: 'Sound Effects Family',
16: 'Drums Family',
-1: 'Unknown Family',
}
###################################################################################
def patch_to_instrument_family(MIDI_patch, drums_patch=128):
if 0 <= MIDI_patch < 128:
return MIDI_patch // 8, MIDI_Instruments_Families[MIDI_patch // 8]
elif MIDI_patch == drums_patch:
return MIDI_patch // 8, MIDI_Instruments_Families[16]
else:
return -1, MIDI_Instruments_Families[-1]
###################################################################################
def patch_list_from_enhanced_score_notes(enhanced_score_notes,
default_patch=0,
drums_patch=9,
verbose=False
):
patches = [-1] * 16
for idx, e in enumerate(enhanced_score_notes):
if e[0] == 'note':
if e[3] != 9:
if patches[e[3]] == -1:
patches[e[3]] = e[6]
else:
if patches[e[3]] != e[6]:
if e[6] in patches:
e[3] = patches.index(e[6])
else:
if -1 in patches:
patches[patches.index(-1)] = e[6]
else:
patches[-1] = e[6]
if verbose:
print('=' * 70)
print('WARNING! Composition has more than 15 patches!')
print('Conflict note number:', idx)
print('Conflict channel number:', e[3])
print('Conflict patch number:', e[6])
patches = [p if p != -1 else default_patch for p in patches]
patches[9] = drums_patch
if verbose:
print('=' * 70)
print('Composition patches')
print('=' * 70)
for c, p in enumerate(patches):
print('Cha', str(c).zfill(2), '---', str(p).zfill(3), MIDI.Number2patch[p])
print('=' * 70)
return patches
###################################################################################
def patch_enhanced_score_notes(escore_notes,
default_patch=0,
reserved_patch=-1,
reserved_patch_channel=-1,
drums_patch=9,
verbose=False
):
#===========================================================================
enhanced_score_notes = copy.deepcopy(escore_notes)
#===========================================================================
enhanced_score_notes_with_patch_changes = []
patches = [-1] * 16
if -1 < reserved_patch < 128 and -1 < reserved_patch_channel < 128:
patches[reserved_patch_channel] = reserved_patch
overflow_idx = -1
for idx, e in enumerate(enhanced_score_notes):
if e[0] == 'note':
if e[3] != 9:
if -1 < reserved_patch < 128 and -1 < reserved_patch_channel < 128:
if e[6] == reserved_patch:
e[3] = reserved_patch_channel
if patches[e[3]] == -1:
patches[e[3]] = e[6]
else:
if patches[e[3]] != e[6]:
if e[6] in patches:
e[3] = patches.index(e[6])
else:
if -1 in patches:
patches[patches.index(-1)] = e[6]
else:
overflow_idx = idx
break
enhanced_score_notes_with_patch_changes.append(e)
#===========================================================================
overflow_patches = []
overflow_channels = [-1] * 16
overflow_channels[9] = drums_patch
if -1 < reserved_patch < 128 and -1 < reserved_patch_channel < 128:
overflow_channels[reserved_patch_channel] = reserved_patch
if overflow_idx != -1:
for idx, e in enumerate(enhanced_score_notes[overflow_idx:]):
if e[0] == 'note':
if e[3] != 9:
if e[6] not in overflow_channels:
if -1 in overflow_channels:
free_chan = overflow_channels.index(-1)
overflow_channels[free_chan] = e[6]
e[3] = free_chan
enhanced_score_notes_with_patch_changes.append(['patch_change', e[1], e[3], e[6]])
overflow_patches.append(e[6])
else:
overflow_channels = [-1] * 16
overflow_channels[9] = drums_patch
if -1 < reserved_patch < 128 and -1 < reserved_patch_channel < 128:
overflow_channels[reserved_patch_channel] = reserved_patch
e[3] = reserved_patch_channel
if e[6] != reserved_patch:
free_chan = overflow_channels.index(-1)
e[3] = free_chan
overflow_channels[e[3]] = e[6]
enhanced_score_notes_with_patch_changes.append(['patch_change', e[1], e[3], e[6]])
overflow_patches.append(e[6])
else:
e[3] = overflow_channels.index(e[6])
enhanced_score_notes_with_patch_changes.append(e)
#===========================================================================
patches = [p if p != -1 else default_patch for p in patches]
patches[9] = drums_patch
#===========================================================================
overflow_patches = ordered_set(overflow_patches)
#===========================================================================
if verbose:
print('=' * 70)
print('Main composition patches')
print('=' * 70)
for c, p in enumerate(patches):
print('Cha', str(c).zfill(2), '---', str(p).zfill(3), MIDI.Number2patch[p])
print('=' * 70)
if overflow_patches:
print('Extra composition patches')
print('=' * 70)
for c, p in enumerate(overflow_patches):
print(str(p).zfill(3), MIDI.Number2patch[p])
print('=' * 70)
#===========================================================================
return enhanced_score_notes_with_patch_changes, patches, overflow_patches
###################################################################################
def create_enhanced_monophonic_melody(monophonic_melody):
enhanced_monophonic_melody = []
for i, note in enumerate(monophonic_melody[:-1]):
enhanced_monophonic_melody.append(note)
if note[1]+note[2] < monophonic_melody[i+1][1]:
delta_time = monophonic_melody[i+1][1] - (note[1]+note[2])
enhanced_monophonic_melody.append(['silence', note[1]+note[2], delta_time, note[3], 0, 0, note[6]])
enhanced_monophonic_melody.append(monophonic_melody[-1])
return enhanced_monophonic_melody
###################################################################################
def frame_monophonic_melody(monophonic_melody, min_frame_time_threshold=10):
mzip = list(zip(monophonic_melody[:-1], monophonic_melody[1:]))
times_counts = Counter([(b[1]-a[1]) for a, b in mzip]).most_common()
mc_time = next((item for item, count in times_counts if item >= min_frame_time_threshold), min_frame_time_threshold)
times = [(b[1]-a[1]) // mc_time for a, b in mzip] + [monophonic_melody[-1][2] // mc_time]
framed_melody = []
for i, note in enumerate(monophonic_melody):
stime = note[1]
count = times[i]
if count != 0:
for j in range(count):
new_note = copy.deepcopy(note)
new_note[1] = stime + (j * mc_time)
new_note[2] = mc_time
framed_melody.append(new_note)
else:
framed_melody.append(note)
return [framed_melody, mc_time]
###################################################################################
def delta_score_notes(score_notes,
timings_clip_value=255,
even_timings=False,
compress_timings=False
):
delta_score = []
pe = score_notes[0]
for n in score_notes:
note = copy.deepcopy(n)
time = n[1] - pe[1]
dur = n[2]
if even_timings:
if time != 0 and time % 2 != 0:
time += 1
if dur % 2 != 0:
dur += 1
time = max(0, min(timings_clip_value, time))
dur = max(0, min(timings_clip_value, dur))
if compress_timings:
time /= 2
dur /= 2
note[1] = int(time)
note[2] = int(dur)
delta_score.append(note)
pe = n
return delta_score
###################################################################################
def check_and_fix_chords_in_chordified_score(chordified_score,
channels_index=3,
pitches_index=4
):
fixed_chordified_score = []
bad_chords_counter = 0
for c in chordified_score:
tones_chord = sorted(set([t[pitches_index] % 12 for t in c if t[channels_index] != 9]))
if tones_chord:
if tones_chord not in ALL_CHORDS_SORTED:
bad_chords_counter += 1
while tones_chord not in ALL_CHORDS_SORTED:
tones_chord.pop(0)
new_chord = []
c.sort(key = lambda x: x[pitches_index], reverse=True)
for e in c:
if e[channels_index] != 9:
if e[pitches_index] % 12 in tones_chord:
new_chord.append(e)
else:
new_chord.append(e)
fixed_chordified_score.append(new_chord)
return fixed_chordified_score, bad_chords_counter
###################################################################################
def advanced_check_and_fix_chords_in_chordified_score(chordified_score,
channels_index=3,
pitches_index=4,
patches_index=6,
use_filtered_chords=False,
use_full_chords=False,
remove_duplicate_pitches=True,
fix_bad_tones_chords=False,
fix_bad_pitches=False,
skip_drums=False
):
fixed_chordified_score = []
bad_chords_counter = 0
duplicate_pitches_counter = 0
if use_filtered_chords:
CHORDS = ALL_CHORDS_FILTERED
else:
CHORDS = ALL_CHORDS_SORTED
if use_full_chords:
CHORDS = ALL_CHORDS_FULL
for c in chordified_score:
chord = copy.deepcopy(c)
if remove_duplicate_pitches:
chord.sort(key = lambda x: x[pitches_index], reverse=True)
seen = set()
ddchord = []
for cc in chord:
if cc[channels_index] != 9:
if tuple([cc[pitches_index], cc[patches_index]]) not in seen:
ddchord.append(cc)
seen.add(tuple([cc[pitches_index], cc[patches_index]]))
else:
duplicate_pitches_counter += 1
else:
ddchord.append(cc)
chord = copy.deepcopy(ddchord)
tones_chord = sorted(set([t[pitches_index] % 12 for t in chord if t[channels_index] != 9]))
if tones_chord:
if tones_chord not in CHORDS:
pitches_chord = sorted(set([p[pitches_index] for p in c if p[channels_index] != 9]), reverse=True)
if len(tones_chord) == 2:
tones_counts = Counter([p % 12 for p in pitches_chord]).most_common()
if tones_counts[0][1] > 1:
good_tone = tones_counts[0][0]
bad_tone = tones_counts[1][0]
elif tones_counts[1][1] > 1:
good_tone = tones_counts[1][0]
bad_tone = tones_counts[0][0]
else:
good_tone = pitches_chord[0] % 12
bad_tone = [t for t in tones_chord if t != good_tone][0]
tones_chord = [good_tone]
if fix_bad_tones_chords:
if good_tone > bad_tone:
if sorted([good_tone, (12+(bad_tone+1)) % 12]) in CHORDS:
tones_chord = sorted([good_tone, (12+(bad_tone-1)) % 12])
elif sorted([good_tone, (12+(bad_tone-1)) % 12]) in CHORDS:
tones_chord = sorted([good_tone, (12+(bad_tone+1)) % 12])
else:
if sorted([good_tone, (12+(bad_tone-1)) % 12]) in CHORDS:
tones_chord = sorted([good_tone, (12+(bad_tone-1)) % 12])
elif sorted([good_tone, (12+(bad_tone+1)) % 12]) in CHORDS:
tones_chord = sorted([good_tone, (12+(bad_tone+1)) % 12])
if len(tones_chord) > 2:
tones_chord_combs = [list(comb) for i in range(len(tones_chord)-1, 0, -1) for comb in combinations(tones_chord, i)]
for co in tones_chord_combs:
if co in CHORDS:
break
if fix_bad_tones_chords:
dt_chord = list(set(co) ^ set(tones_chord))
for t in dt_chord:
tones_chord.append((12+(t+1)) % 12)
tones_chord.append((12+(t-1)) % 12)
ex_tones_chord = sorted(set(tones_chord))
tones_chord_combs = [list(comb) for i in range(4, 0, -2) for comb in combinations(ex_tones_chord, i) if all(t in list(comb) for t in co)]
for eco in tones_chord_combs:
if eco in CHORDS:
tones_chord = eco
break
else:
tones_chord = co
if len(tones_chord) == 1:
tones_chord = [pitches_chord[0] % 12]
bad_chords_counter += 1
chord.sort(key = lambda x: x[pitches_index], reverse=True)
new_chord = set()
pipa = []
for e in chord:
if e[channels_index] != 9:
if e[pitches_index] % 12 in tones_chord:
new_chord.add(tuple(e))
pipa.append([e[pitches_index], e[patches_index]])
elif (e[pitches_index]+1) % 12 in tones_chord:
e[pitches_index] += 1
new_chord.add(tuple(e))
pipa.append([e[pitches_index], e[patches_index]])
elif (e[pitches_index]-1) % 12 in tones_chord:
e[pitches_index] -= 1
new_chord.add(tuple(e))
pipa.append([e[pitches_index], e[patches_index]])
if fix_bad_pitches:
bad_chord = set()
for e in chord:
if e[channels_index] != 9:
if e[pitches_index] % 12 not in tones_chord:
bad_chord.add(tuple(e))
elif (e[pitches_index]+1) % 12 not in tones_chord:
bad_chord.add(tuple(e))
elif (e[pitches_index]-1) % 12 not in tones_chord:
bad_chord.add(tuple(e))
for bc in bad_chord:
bc = list(bc)
tone = find_closest_tone(tones_chord, bc[pitches_index] % 12)
new_pitch = ((bc[pitches_index] // 12) * 12) + tone
if [new_pitch, bc[patches_index]] not in pipa:
bc[pitches_index] = new_pitch
new_chord.add(tuple(bc))
pipa.append([[new_pitch], bc[patches_index]])
if not skip_drums:
for e in c:
if e[channels_index] == 9:
new_chord.add(tuple(e))
new_chord = [list(e) for e in new_chord]
new_chord.sort(key = lambda x: (-x[pitches_index], x[patches_index]))
fixed_chordified_score.append(new_chord)
return fixed_chordified_score, bad_chords_counter, duplicate_pitches_counter
###################################################################################
def score_chord_to_tones_chord(chord,
transpose_value=0,
channels_index=3,
pitches_index=4):
return sorted(set([(p[4]+transpose_value) % 12 for p in chord if p[channels_index] != 9]))
###################################################################################
def grouped_set(seq):
return [k for k, v in groupby(seq)]
###################################################################################
def ordered_set(seq):
dic = {}
return [k for k, v in dic.fromkeys(seq).items()]
###################################################################################
def add_melody_to_enhanced_score_notes(enhanced_score_notes,
melody_start_time=0,
melody_start_chord=0,
melody_notes_min_duration=-1,
melody_notes_max_duration=255,
melody_duration_overlap_tolerance=4,
melody_avg_duration_divider=2,
melody_base_octave=5,
melody_channel=3,
melody_patch=40,
melody_max_velocity=110,
acc_max_velocity=90,
pass_drums=True,
return_melody=False
):
if pass_drums:
score = copy.deepcopy(enhanced_score_notes)
else:
score = [e for e in copy.deepcopy(enhanced_score_notes) if e[3] !=9]
if melody_notes_min_duration > 0:
min_duration = melody_notes_min_duration
else:
durs = [d[2] for d in score]
min_duration = Counter(durs).most_common()[0][0]
adjust_score_velocities(score, acc_max_velocity)
cscore = chordify_score([1000, score])
melody_score = []
acc_score = []
pt = melody_start_time
for c in cscore[:melody_start_chord]:
acc_score.extend(c)
for c in cscore[melody_start_chord:]:
durs = [d[2] if d[3] != 9 else -1 for d in c]
if not all(d == -1 for d in durs):
ndurs = [d for d in durs if d != -1]
avg_dur = (sum(ndurs) / len(ndurs)) / melody_avg_duration_divider
best_dur = min(durs, key=lambda x:abs(x-avg_dur))
pidx = durs.index(best_dur)
cc = copy.deepcopy(c[pidx])
if c[0][1] >= pt - melody_duration_overlap_tolerance and best_dur >= min_duration:
cc[3] = melody_channel
cc[4] = (c[pidx][4] % 24)
cc[5] = 100 + ((c[pidx][4] % 12) * 2)
cc[6] = melody_patch
melody_score.append(cc)
acc_score.extend(c)
pt = c[0][1]+c[pidx][2]
else:
acc_score.extend(c)
else:
acc_score.extend(c)
values = [e[4] % 24 for e in melody_score]
smoothed = [values[0]]
for i in range(1, len(values)):
if abs(smoothed[-1] - values[i]) >= 12:
if smoothed[-1] < values[i]:
smoothed.append(values[i] - 12)
else:
smoothed.append(values[i] + 12)
else:
smoothed.append(values[i])
smoothed_melody = copy.deepcopy(melody_score)
for i, e in enumerate(smoothed_melody):
e[4] = (melody_base_octave * 12) + smoothed[i]
for i, m in enumerate(smoothed_melody[1:]):
if m[1] - smoothed_melody[i][1] < melody_notes_max_duration:
smoothed_melody[i][2] = m[1] - smoothed_melody[i][1]
adjust_score_velocities(smoothed_melody, melody_max_velocity)
if return_melody:
final_score = sorted(smoothed_melody, key=lambda x: (x[1], -x[4]))
else:
final_score = sorted(smoothed_melody + acc_score, key=lambda x: (x[1], -x[4]))
return final_score
###################################################################################
def find_paths(list_of_lists, path=[]):
if not list_of_lists:
return [path]
return [p for sublist in list_of_lists[0] for p in find_paths(list_of_lists[1:], path+[sublist])]
###################################################################################
def recalculate_score_timings(score,
start_time=0,
timings_index=1
):
rscore = copy.deepcopy(score)
pe = rscore[0]
abs_time = start_time
for e in rscore:
dtime = e[timings_index] - pe[timings_index]
pe = copy.deepcopy(e)
abs_time += dtime
e[timings_index] = abs_time
return rscore
###################################################################################
WHITE_NOTES = [0, 2, 4, 5, 7, 9, 11]
BLACK_NOTES = [1, 3, 6, 8, 10]
###################################################################################
ALL_CHORDS_FILTERED = [[0], [0, 3], [0, 3, 5], [0, 3, 5, 8], [0, 3, 5, 9], [0, 3, 5, 10], [0, 3, 7],
[0, 3, 7, 10], [0, 3, 8], [0, 3, 9], [0, 3, 10], [0, 4], [0, 4, 6],
[0, 4, 6, 9], [0, 4, 6, 10], [0, 4, 7], [0, 4, 7, 10], [0, 4, 8], [0, 4, 9],
[0, 4, 10], [0, 5], [0, 5, 8], [0, 5, 9], [0, 5, 10], [0, 6], [0, 6, 9],
[0, 6, 10], [0, 7], [0, 7, 10], [0, 8], [0, 9], [0, 10], [1], [1, 4],
[1, 4, 6], [1, 4, 6, 9], [1, 4, 6, 10], [1, 4, 6, 11], [1, 4, 7],
[1, 4, 7, 10], [1, 4, 7, 11], [1, 4, 8], [1, 4, 8, 11], [1, 4, 9], [1, 4, 10],
[1, 4, 11], [1, 5], [1, 5, 8], [1, 5, 8, 11], [1, 5, 9], [1, 5, 10],
[1, 5, 11], [1, 6], [1, 6, 9], [1, 6, 10], [1, 6, 11], [1, 7], [1, 7, 10],
[1, 7, 11], [1, 8], [1, 8, 11], [1, 9], [1, 10], [1, 11], [2], [2, 5],
[2, 5, 8], [2, 5, 8, 11], [2, 5, 9], [2, 5, 10], [2, 5, 11], [2, 6], [2, 6, 9],
[2, 6, 10], [2, 6, 11], [2, 7], [2, 7, 10], [2, 7, 11], [2, 8], [2, 8, 11],
[2, 9], [2, 10], [2, 11], [3], [3, 5], [3, 5, 8], [3, 5, 8, 11], [3, 5, 9],
[3, 5, 10], [3, 5, 11], [3, 7], [3, 7, 10], [3, 7, 11], [3, 8], [3, 8, 11],
[3, 9], [3, 10], [3, 11], [4], [4, 6], [4, 6, 9], [4, 6, 10], [4, 6, 11],
[4, 7], [4, 7, 10], [4, 7, 11], [4, 8], [4, 8, 11], [4, 9], [4, 10], [4, 11],
[5], [5, 8], [5, 8, 11], [5, 9], [5, 10], [5, 11], [6], [6, 9], [6, 10],
[6, 11], [7], [7, 10], [7, 11], [8], [8, 11], [9], [10], [11]]
###################################################################################
def harmonize_enhanced_melody_score_notes(enhanced_melody_score_notes):
mel_tones = [e[4] % 12 for e in enhanced_melody_score_notes]
cur_chord = []
song = []
for i, m in enumerate(mel_tones):
cur_chord.append(m)
cc = sorted(set(cur_chord))
if cc in ALL_CHORDS_FULL:
song.append(cc)
else:
while sorted(set(cur_chord)) not in ALL_CHORDS_FULL:
cur_chord.pop(0)
cc = sorted(set(cur_chord))
song.append(cc)
return song
###################################################################################
def split_melody(enhanced_melody_score_notes,
split_time=-1,
max_score_time=255
):
mel_chunks = []
if split_time == -1:
durs = [max(0, min(max_score_time, e[2])) for e in enhanced_melody_score_notes]
stime = max(durs)
else:
stime = split_time
pe = enhanced_melody_score_notes[0]
chu = []
for e in enhanced_melody_score_notes:
dtime = max(0, min(max_score_time, e[1]-pe[1]))
if dtime > stime:
if chu:
mel_chunks.append(chu)
chu = []
chu.append(e)
else:
chu.append(e)
pe = e
if chu:
mel_chunks.append(chu)
return mel_chunks, [[m[0][1], m[-1][1]] for m in mel_chunks], len(mel_chunks)
###################################################################################
def flatten(list_of_lists):
return [x for y in list_of_lists for x in y]
###################################################################################
def enhanced_delta_score_notes(enhanced_score_notes,
start_time=0,
max_score_time=255
):
delta_score = []
pe = ['note', max(0, enhanced_score_notes[0][1]-start_time)]
for e in enhanced_score_notes:
dtime = max(0, min(max_score_time, e[1]-pe[1]))
dur = max(1, min(max_score_time, e[2]))
cha = max(0, min(15, e[3]))
ptc = max(1, min(127, e[4]))
vel = max(1, min(127, e[5]))
pat = max(0, min(128, e[6]))
delta_score.append([dtime, dur, cha, ptc, vel, pat])
pe = e
return delta_score
###################################################################################
def basic_enhanced_delta_score_notes_tokenizer(enhanced_delta_score_notes,
tokenize_start_times=True,
tokenize_durations=True,
tokenize_channels=True,
tokenize_pitches=True,
tokenize_velocities=True,
tokenize_patches=True,
score_timings_range=256,
max_seq_len=-1,
seq_pad_value=-1
):
score_tokens_ints_seq = []
tokens_shifts = [-1] * 7
for d in enhanced_delta_score_notes:
seq = []
shift = 0
if tokenize_start_times:
seq.append(d[0])
tokens_shifts[0] = shift
shift += score_timings_range
if tokenize_durations:
seq.append(d[1]+shift)
tokens_shifts[1] = shift
shift += score_timings_range
if tokenize_channels:
tokens_shifts[2] = shift
seq.append(d[2]+shift)
shift += 16
if tokenize_pitches:
tokens_shifts[3] = shift
seq.append(d[3]+shift)
shift += 128
if tokenize_velocities:
tokens_shifts[4] = shift
seq.append(d[4]+shift)
shift += 128
if tokenize_patches:
tokens_shifts[5] = shift
seq.append(d[5]+shift)
shift += 129
tokens_shifts[6] = shift
score_tokens_ints_seq.append(seq)
final_score_tokens_ints_seq = flatten(score_tokens_ints_seq)
if max_seq_len > -1:
final_score_tokens_ints_seq = final_score_tokens_ints_seq[:max_seq_len]
if seq_pad_value > -1:
final_score_tokens_ints_seq += [seq_pad_value] * (max_seq_len - len(final_score_tokens_ints_seq))
return [score_tokens_ints_seq,
final_score_tokens_ints_seq,
tokens_shifts,
seq_pad_value,
max_seq_len,
len(score_tokens_ints_seq),
len(final_score_tokens_ints_seq)
]
###################################################################################
def basic_enhanced_delta_score_notes_detokenizer(tokenized_seq,
tokens_shifts,
timings_multiplier=16
):
song_f = []
time = 0
dur = 16
channel = 0
pitch = 60
vel = 90
pat = 0
note_seq_len = len([t for t in tokens_shifts if t > -1])-1
tok_shifts_idxs = [i for i in range(len(tokens_shifts[:-1])) if tokens_shifts[i] > - 1]
song = []
for i in range(0, len(tokenized_seq), note_seq_len):
note = tokenized_seq[i:i+note_seq_len]
song.append(note)
for note in song:
for i, idx in enumerate(tok_shifts_idxs):
if idx == 0:
time += (note[i]-tokens_shifts[0]) * timings_multiplier
elif idx == 1:
dur = (note[i]-tokens_shifts[1]) * timings_multiplier
elif idx == 2:
channel = (note[i]-tokens_shifts[2])
elif idx == 3:
pitch = (note[i]-tokens_shifts[3])
elif idx == 4:
vel = (note[i]-tokens_shifts[4])
elif idx == 5:
pat = (note[i]-tokens_shifts[5])
song_f.append(['note', time, dur, channel, pitch, vel, pat ])
return song_f
###################################################################################
def enhanced_chord_to_chord_token(enhanced_chord,
channels_index=3,
pitches_index=4,
use_filtered_chords=False,
use_full_chords=True
):
bad_chords_counter = 0
duplicate_pitches_counter = 0
if use_filtered_chords:
CHORDS = ALL_CHORDS_FILTERED
else:
CHORDS = ALL_CHORDS_SORTED
if use_full_chords:
CHORDS = ALL_CHORDS_FULL
tones_chord = sorted(set([t[pitches_index] % 12 for t in enhanced_chord if t[channels_index] != 9]))
original_tones_chord = copy.deepcopy(tones_chord)
if tones_chord:
if tones_chord not in CHORDS:
pitches_chord = sorted(set([p[pitches_index] for p in enhanced_chord if p[channels_index] != 9]), reverse=True)
if len(tones_chord) == 2:
tones_counts = Counter([p % 12 for p in pitches_chord]).most_common()
if tones_counts[0][1] > 1:
tones_chord = [tones_counts[0][0]]
elif tones_counts[1][1] > 1:
tones_chord = [tones_counts[1][0]]
else:
tones_chord = [pitches_chord[0] % 12]
else:
tones_chord_combs = [list(comb) for i in range(len(tones_chord)-2, 0, -1) for comb in combinations(tones_chord, i+1)]
for co in tones_chord_combs:
if co in CHORDS:
tones_chord = co
break
if use_filtered_chords:
chord_token = ALL_CHORDS_FILTERED.index(tones_chord)
else:
chord_token = ALL_CHORDS_SORTED.index(tones_chord)
return [chord_token, tones_chord, original_tones_chord, sorted(set(original_tones_chord) ^ set(tones_chord))]
###################################################################################
def enhanced_chord_to_tones_chord(enhanced_chord):
return sorted(set([t[4] % 12 for t in enhanced_chord if t[3] != 9]))
###################################################################################
def md5_hash(file_path_or_data=None, original_md5_hash=None):
if type(file_path_or_data) == str:
with open(file_path_or_data, 'rb') as file_to_check:
data = file_to_check.read()
if data:
md5 = hashlib.md5(data).hexdigest()
else:
if file_path_or_data:
md5 = hashlib.md5(file_path_or_data).hexdigest()
if md5:
if original_md5_hash:
if md5 == original_md5_hash:
check = True
else:
check = False
else:
check = None
return [md5, check]
else:
md5 = None
check = None
return [md5, check]
###################################################################################
ALL_PITCHES_CHORDS_FILTERED = [[67], [64], [62], [69], [60], [65], [59], [70], [66], [63], [68], [61],
[64, 60], [67, 64], [65, 62], [62, 59], [69, 65], [60, 57], [66, 62], [59, 55],
[62, 57], [67, 62], [64, 59], [64, 60, 55], [60, 55], [65, 60], [64, 61],
[69, 64], [66, 62, 57], [69, 66], [62, 59, 55], [64, 60, 57], [62, 58],
[65, 60, 57], [70, 67], [67, 63], [64, 61, 57], [61, 57], [63, 60], [68, 64],
[65, 62, 58], [65, 62, 57], [59, 56], [63, 58], [68, 65], [59, 54, 47, 35],
[70, 65], [66, 61], [64, 59, 56], [65, 61], [64, 59, 55], [63, 59], [61, 58],
[68, 63], [60, 56], [67, 63, 60], [67, 63, 58], [66, 62, 59], [61, 56],
[70, 66], [67, 62, 58], [63, 60, 56], [65, 61, 56], [66, 61, 58], [66, 61, 57],
[65, 60, 56], [65, 61, 58], [65, 59], [68, 64, 61], [66, 60], [64, 58],
[62, 56], [63, 57], [61, 55], [66, 64], [60, 58], [65, 63], [63, 59, 56],
[65, 62, 59], [61, 59], [66, 60, 57], [64, 61, 55], [64, 58, 55], [62, 59, 56],
[64, 60, 58], [63, 60, 57], [64, 60, 58, 55], [65, 62, 56], [64, 61, 58],
[66, 64, 59], [60, 58, 55], [65, 63, 60], [63, 57, 53], [65, 63, 60, 57],
[65, 59, 56], [63, 60, 58, 55], [67, 61, 58], [64, 61, 57, 54], [64, 61, 59],
[70, 65, 60], [68, 65, 63, 60], [63, 60, 58], [65, 63, 58], [69, 66, 64],
[64, 60, 54], [64, 60, 57, 54], [66, 64, 61], [66, 61, 59], [67, 63, 59],
[65, 61, 57], [68, 65, 63], [64, 61, 59, 56], [65, 61, 59], [66, 64, 61, 58],
[64, 61, 58, 55], [64, 60, 56], [65, 61, 59, 56], [66, 62, 58], [61, 59, 56],
[64, 58, 54], [63, 59, 53], [65, 62, 59, 56], [61, 59, 55], [64, 61, 59, 55],
[68, 65, 63, 59], [70, 66, 60], [65, 63, 60, 58], [64, 61, 59, 54],
[70, 64, 60, 54]]
###################################################################################
ALL_PITCHES_CHORDS_SORTED = [[60], [62, 60], [63, 60], [64, 60], [64, 62, 60], [65, 60], [65, 62, 60],
[65, 63, 60], [66, 60], [66, 62, 60], [66, 63, 60], [64, 60, 54],
[64, 60, 54, 50], [60, 55], [67, 62, 60], [67, 63, 60], [64, 60, 55],
[65, 60, 55], [64, 62, 60, 55], [67, 65, 62, 60], [67, 65, 63, 60], [60, 56],
[62, 60, 56], [63, 60, 56], [64, 60, 56], [65, 60, 56], [66, 60, 56],
[72, 68, 64, 62], [65, 62, 60, 56], [66, 62, 60, 56], [68, 65, 63, 60],
[68, 66, 63, 60], [60, 44, 42, 40], [88, 80, 74, 66, 60, 56], [60, 57],
[62, 60, 57], [63, 60, 57], [64, 60, 57], [65, 60, 57], [66, 60, 57],
[67, 60, 57], [64, 62, 60, 57], [65, 62, 60, 57], [69, 66, 62, 60],
[67, 62, 60, 57], [65, 63, 60, 57], [66, 63, 60, 57], [67, 63, 60, 57],
[64, 60, 57, 54], [67, 64, 60, 57], [67, 65, 60, 57], [69, 64, 60, 54, 38],
[67, 64, 62, 60, 57], [67, 65, 62, 60, 57], [67, 65, 63, 60, 57], [60, 58],
[62, 60, 58], [63, 60, 58], [64, 60, 58], [70, 65, 60], [70, 66, 60],
[60, 58, 55], [70, 60, 56], [74, 64, 60, 58], [65, 62, 60, 58],
[70, 66, 62, 60], [62, 60, 58, 55], [72, 68, 62, 58], [65, 63, 60, 58],
[70, 66, 63, 60], [63, 60, 58, 55], [70, 63, 60, 56], [70, 64, 60, 54],
[64, 60, 58, 55], [68, 64, 60, 58], [65, 60, 58, 55], [70, 65, 60, 56],
[70, 66, 60, 56], [78, 76, 74, 72, 70, 66], [67, 64, 62, 58, 36],
[74, 68, 64, 58, 48], [65, 62, 58, 55, 36], [65, 62, 60, 56, 46],
[72, 66, 62, 56, 46], [79, 65, 63, 58, 53, 36], [65, 60, 56, 51, 46, 41],
[70, 66, 63, 60, 44], [68, 66, 64, 58, 56, 48],
[94, 92, 90, 88, 86, 84, 82, 80, 78, 76, 74, 72, 70, 68, 66, 64, 62, 60, 58,
56, 54, 52, 50, 48, 46, 44, 42, 40, 38, 36, 34, 32, 30, 28, 26, 24],
[61], [63, 61], [64, 61], [65, 61], [65, 63, 61], [66, 61], [66, 63, 61],
[66, 64, 61], [61, 55], [67, 63, 61], [64, 61, 55], [65, 61, 55],
[65, 61, 55, 39], [61, 56], [63, 61, 56], [68, 64, 61], [65, 61, 56],
[66, 61, 56], [68, 65, 63, 61], [54, 49, 44, 39], [68, 64, 61, 42], [61, 57],
[63, 61, 57], [64, 61, 57], [65, 61, 57], [66, 61, 57], [67, 61, 57],
[69, 65, 63, 61], [66, 63, 61, 57], [67, 63, 61, 57], [64, 61, 57, 54],
[67, 64, 61, 57], [65, 61, 55, 45], [67, 65, 63, 61, 57], [61, 58],
[63, 61, 58], [64, 61, 58], [65, 61, 58], [66, 61, 58], [67, 61, 58],
[61, 58, 56], [65, 63, 61, 58], [66, 63, 61, 58], [67, 63, 61, 58],
[63, 61, 58, 56], [66, 64, 61, 58], [64, 61, 58, 55], [68, 64, 61, 58],
[65, 61, 58, 55], [65, 61, 58, 56], [58, 54, 49, 44], [70, 65, 61, 55, 39],
[80, 68, 65, 63, 61, 58], [63, 58, 54, 49, 44, 39], [73, 68, 64, 58, 54],
[61, 59], [63, 61, 59], [64, 61, 59], [65, 61, 59], [66, 61, 59], [61, 59, 55],
[61, 59, 56], [61, 59, 57], [63, 59, 53, 49], [66, 63, 61, 59],
[71, 67, 63, 61], [63, 61, 59, 56], [61, 57, 51, 47], [64, 61, 59, 54],
[64, 61, 59, 55], [64, 61, 59, 56], [64, 61, 59, 57], [65, 61, 59, 55],
[65, 61, 59, 56], [69, 65, 61, 59], [66, 61, 59, 56], [71, 66, 61, 57],
[71, 67, 61, 57], [67, 63, 59, 53, 49], [68, 65, 63, 59, 37],
[65, 63, 61, 59, 57], [66, 63, 61, 59, 56], [73, 69, 66, 63, 59],
[79, 75, 73, 61, 59, 33], [61, 56, 52, 47, 42, 35], [76, 73, 69, 66, 35],
[71, 67, 64, 61, 57], [73, 71, 69, 67, 65],
[95, 93, 91, 89, 87, 85, 83, 81, 79, 77, 75, 73, 71, 69, 67, 65, 63, 61, 59,
57, 55, 53, 51, 49, 47, 45, 43, 41, 39, 37, 35, 33, 31, 29, 27, 25],
[62], [64, 62], [65, 62], [66, 62], [66, 64, 62], [67, 62], [67, 64, 62],
[67, 65, 62], [62, 56], [68, 64, 62], [65, 62, 56], [66, 62, 56],
[66, 62, 56, 52], [62, 57], [50, 45, 40], [65, 62, 57], [66, 62, 57],
[55, 50, 45], [66, 64, 62, 57], [55, 50, 45, 40], [69, 67, 65, 62], [62, 58],
[64, 62, 58], [65, 62, 58], [66, 62, 58], [67, 62, 58], [62, 58, 56],
[66, 64, 62, 58], [67, 64, 62, 58], [64, 62, 58, 56], [65, 62, 58, 55],
[65, 62, 58, 56], [66, 62, 58, 56], [66, 64, 58, 44, 38], [62, 59],
[64, 62, 59], [65, 62, 59], [66, 62, 59], [62, 59, 55], [62, 59, 56],
[62, 59, 57], [66, 64, 62, 59], [67, 64, 62, 59], [64, 62, 59, 56],
[64, 62, 59, 57], [67, 65, 62, 59], [65, 62, 59, 56], [69, 65, 62, 59],
[66, 62, 59, 56], [69, 66, 62, 59], [59, 55, 50, 45], [64, 62, 59, 56, 54],
[69, 66, 62, 59, 40], [64, 59, 55, 50, 45, 40], [69, 65, 62, 59, 55], [63],
[65, 63], [66, 63], [67, 63], [67, 65, 63], [68, 63], [68, 65, 63],
[68, 66, 63], [63, 57], [63, 57, 53], [66, 63, 57], [67, 63, 57],
[67, 63, 57, 53], [63, 58], [65, 63, 58], [66, 63, 58], [67, 63, 58],
[68, 63, 58], [67, 65, 63, 58], [63, 58, 56, 53], [70, 68, 66, 63], [63, 59],
[63, 59, 53], [66, 63, 59], [67, 63, 59], [63, 59, 56], [63, 59, 57],
[63, 59, 55, 53], [68, 65, 63, 59], [69, 65, 63, 59], [66, 63, 59, 56],
[66, 63, 59, 57], [67, 63, 59, 57], [67, 63, 59, 57, 41], [64], [66, 64],
[67, 64], [68, 64], [68, 66, 64], [69, 64], [69, 66, 64], [69, 67, 64],
[64, 58], [64, 58, 54], [64, 58, 55], [68, 64, 58], [68, 64, 58, 42], [64, 59],
[66, 64, 59], [64, 59, 55], [64, 59, 56], [64, 59, 57], [64, 59, 56, 54],
[64, 59, 57, 54], [69, 64, 59, 55], [65], [67, 65], [68, 65], [69, 65],
[69, 67, 65], [70, 65], [65, 58, 55], [70, 68, 65], [65, 59], [65, 59, 55],
[65, 59, 56], [59, 57, 53], [69, 65, 59, 55], [66], [68, 66], [69, 66],
[70, 66], [80, 70, 54], [59, 54, 47, 35], [66, 59, 56], [71, 69, 66], [67],
[69, 67], [70, 67], [59, 55], [71, 69, 67], [68], [70, 68], [59, 56], [69],
[71, 69], [70], [59]]
###################################################################################
def sort_list_by_other(list1, list2):
return sorted(list1, key=lambda x: list2.index(x) if x in list2 else len(list2))
###################################################################################
ALL_CHORDS_PAIRS_SORTED = [[[0], [0, 4, 7]], [[0, 2], [0, 4, 7]], [[0, 3], [0, 3, 7]],
[[0, 4], [0, 4, 7]], [[0, 2, 4], [0, 2, 4, 7]], [[0, 5], [0, 5, 9]],
[[0, 2, 5], [0, 2, 5, 9]], [[0, 3, 5], [0, 3, 5, 9]], [[0, 6], [0, 2, 6, 9]],
[[0, 2, 6], [0, 2, 6, 9]], [[0, 3, 6], [0, 3, 6, 8]],
[[0, 4, 6], [0, 4, 6, 9]], [[0, 2, 4, 6], [0, 2, 4, 6, 9]],
[[0, 7], [0, 4, 7]], [[0, 2, 7], [0, 2, 4, 7]], [[0, 3, 7], [0, 3, 7, 10]],
[[0, 4, 7], [0, 4, 7, 9]], [[0, 5, 7], [0, 5, 7, 9]],
[[0, 2, 4, 7], [0, 2, 4, 7, 9]], [[0, 2, 5, 7], [0, 2, 5, 7, 9]],
[[0, 3, 5, 7], [0, 3, 5, 7, 10]], [[0, 8], [0, 3, 8]],
[[0, 2, 8], [0, 2, 5, 8]], [[0, 3, 8], [0, 3, 5, 8]],
[[0, 4, 8], [2, 4, 8, 11]], [[0, 5, 8], [0, 3, 5, 8]],
[[0, 6, 8], [0, 3, 6, 8]], [[0, 2, 4, 8], [0, 2, 4, 6, 8]],
[[0, 2, 5, 8], [0, 2, 5, 8, 10]], [[0, 2, 6, 8], [0, 2, 6, 8, 10]],
[[0, 3, 5, 8], [0, 3, 5, 8, 10]], [[0, 3, 6, 8], [0, 3, 6, 8, 10]],
[[0, 4, 6, 8], [2, 4, 6, 8, 11]], [[0, 2, 4, 6, 8], [2, 4, 6, 8, 11]],
[[0, 9], [0, 4, 9]], [[0, 2, 9], [0, 2, 6, 9]], [[0, 3, 9], [0, 3, 5, 9]],
[[0, 4, 9], [0, 4, 7, 9]], [[0, 5, 9], [0, 2, 5, 9]],
[[0, 6, 9], [0, 2, 6, 9]], [[0, 7, 9], [0, 4, 7, 9]],
[[0, 2, 4, 9], [0, 2, 4, 7, 9]], [[0, 2, 5, 9], [0, 2, 5, 7, 9]],
[[0, 2, 6, 9], [0, 2, 4, 6, 9]], [[0, 2, 7, 9], [0, 2, 4, 7, 9]],
[[0, 3, 5, 9], [0, 3, 5, 7, 9]], [[0, 3, 6, 9], [0, 2, 4, 6, 9]],
[[0, 3, 7, 9], [0, 3, 5, 7, 9]], [[0, 4, 6, 9], [0, 2, 4, 6, 9]],
[[0, 4, 7, 9], [0, 2, 4, 7, 9]], [[0, 5, 7, 9], [0, 2, 5, 7, 9]],
[[0, 2, 4, 6, 9], [2, 4, 6, 9, 11]], [[0, 2, 4, 7, 9], [2, 4, 7, 9, 11]],
[[0, 2, 5, 7, 9], [2, 5, 7, 9, 11]], [[0, 3, 5, 7, 9], [2, 4, 6, 8, 11]],
[[0, 10], [2, 5, 10]], [[0, 2, 10], [0, 2, 5, 10]],
[[0, 3, 10], [0, 3, 7, 10]], [[0, 4, 10], [0, 4, 7, 10]],
[[0, 5, 10], [0, 2, 5, 10]], [[0, 6, 10], [0, 3, 6, 10]],
[[0, 7, 10], [0, 4, 7, 10]], [[0, 8, 10], [0, 3, 8, 10]],
[[0, 2, 4, 10], [0, 2, 4, 7, 10]], [[0, 2, 5, 10], [0, 2, 5, 7, 10]],
[[0, 2, 6, 10], [0, 2, 6, 8, 10]], [[0, 2, 7, 10], [0, 2, 5, 7, 10]],
[[0, 2, 8, 10], [0, 2, 5, 8, 10]], [[0, 3, 5, 10], [0, 3, 5, 7, 10]],
[[0, 3, 6, 10], [0, 3, 6, 8, 10]], [[0, 3, 7, 10], [0, 3, 5, 7, 10]],
[[0, 3, 8, 10], [0, 3, 5, 8, 10]], [[0, 4, 6, 10], [0, 2, 4, 6, 10]],
[[0, 4, 7, 10], [0, 2, 4, 7, 10]], [[0, 4, 8, 10], [0, 2, 4, 8, 10]],
[[0, 5, 7, 10], [0, 3, 5, 7, 10]], [[0, 5, 8, 10], [0, 3, 5, 8, 10]],
[[0, 6, 8, 10], [0, 3, 6, 8, 10]], [[0, 2, 4, 6, 10], [0, 2, 4, 8, 10]],
[[0, 2, 4, 7, 10], [1, 3, 6, 9, 11]], [[0, 2, 4, 8, 10], [1, 3, 7, 9, 11]],
[[0, 2, 5, 7, 10], [0, 3, 5, 7, 10]], [[0, 2, 5, 8, 10], [1, 4, 7, 9, 11]],
[[0, 2, 6, 8, 10], [2, 4, 6, 8, 10]], [[0, 3, 5, 7, 10], [0, 2, 5, 7, 10]],
[[0, 3, 5, 8, 10], [1, 3, 5, 8, 10]], [[0, 3, 6, 8, 10], [1, 3, 6, 8, 10]],
[[0, 4, 6, 8, 10], [0, 2, 4, 6, 9]],
[[0, 2, 4, 6, 8, 10], [1, 3, 5, 7, 9, 11]], [[1], [1, 8]], [[1, 3], [1, 5, 8]],
[[1, 4], [1, 4, 9]], [[1, 5], [1, 5, 8]], [[1, 3, 5], [1, 3, 5, 10]],
[[1, 6], [1, 6, 10]], [[1, 3, 6], [1, 3, 6, 10]], [[1, 4, 6], [1, 4, 6, 9]],
[[1, 7], [1, 4, 7]], [[1, 3, 7], [1, 3, 7, 10]], [[1, 4, 7], [1, 4, 7, 9]],
[[1, 5, 7], [1, 5, 7, 10]], [[1, 3, 5, 7], [1, 3, 5, 7, 10]],
[[1, 8], [1, 5, 8]], [[1, 3, 8], [1, 3, 5, 8]], [[1, 4, 8], [1, 4, 8, 11]],
[[1, 5, 8], [1, 5, 8, 10]], [[1, 6, 8], [1, 3, 6, 8]],
[[1, 3, 5, 8], [1, 3, 5, 8, 10]], [[1, 3, 6, 8], [1, 3, 6, 8, 10]],
[[1, 4, 6, 8], [1, 4, 6, 8, 11]], [[1, 9], [1, 4, 9]],
[[1, 3, 9], [1, 3, 6, 9]], [[1, 4, 9], [1, 4, 6, 9]],
[[1, 5, 9], [0, 3, 5, 9]], [[1, 6, 9], [1, 4, 6, 9]],
[[1, 7, 9], [1, 4, 7, 9]], [[1, 3, 5, 9], [0, 3, 5, 7, 9]],
[[1, 3, 6, 9], [1, 3, 6, 9, 11]], [[1, 3, 7, 9], [1, 3, 5, 7, 9]],
[[1, 4, 6, 9], [1, 4, 6, 9, 11]], [[1, 4, 7, 9], [1, 4, 7, 9, 11]],
[[1, 5, 7, 9], [1, 3, 7, 9, 11]], [[1, 3, 5, 7, 9], [2, 4, 6, 8, 11]],
[[1, 10], [1, 5, 10]], [[1, 3, 10], [1, 3, 7, 10]],
[[1, 4, 10], [1, 4, 6, 10]], [[1, 5, 10], [1, 5, 8, 10]],
[[1, 6, 10], [1, 4, 6, 10]], [[1, 7, 10], [1, 3, 7, 10]],
[[1, 8, 10], [1, 5, 8, 10]], [[1, 3, 5, 10], [1, 3, 5, 8, 10]],
[[1, 3, 6, 10], [1, 3, 6, 8, 10]], [[1, 3, 7, 10], [1, 3, 5, 7, 10]],
[[1, 3, 8, 10], [1, 3, 5, 8, 10]], [[1, 4, 6, 10], [1, 4, 6, 8, 10]],
[[1, 4, 7, 10], [0, 2, 4, 7, 10]], [[1, 4, 8, 10], [1, 4, 6, 8, 10]],
[[1, 5, 7, 10], [1, 3, 5, 7, 10]], [[1, 5, 8, 10], [1, 3, 5, 8, 10]],
[[1, 6, 8, 10], [1, 3, 6, 8, 10]], [[1, 3, 5, 7, 10], [2, 4, 6, 8, 11]],
[[1, 3, 5, 8, 10], [0, 3, 5, 8, 10]], [[1, 3, 6, 8, 10], [0, 3, 6, 8, 10]],
[[1, 4, 6, 8, 10], [0, 3, 5, 7, 9]], [[1, 11], [2, 6, 11]],
[[1, 3, 11], [1, 3, 6, 11]], [[1, 4, 11], [1, 4, 8, 11]],
[[1, 5, 11], [1, 5, 8, 11]], [[1, 6, 11], [1, 4, 6, 11]],
[[1, 7, 11], [1, 4, 7, 11]], [[1, 8, 11], [1, 4, 8, 11]],
[[1, 9, 11], [1, 4, 9, 11]], [[1, 3, 5, 11], [1, 3, 5, 8, 11]],
[[1, 3, 6, 11], [1, 3, 6, 8, 11]], [[1, 3, 7, 11], [1, 3, 7, 9, 11]],
[[1, 3, 8, 11], [1, 3, 6, 8, 11]], [[1, 3, 9, 11], [1, 3, 6, 9, 11]],
[[1, 4, 6, 11], [1, 4, 6, 9, 11]], [[1, 4, 7, 11], [1, 4, 7, 9, 11]],
[[1, 4, 8, 11], [1, 4, 6, 8, 11]], [[1, 4, 9, 11], [1, 4, 6, 9, 11]],
[[1, 5, 7, 11], [0, 4, 6, 8, 10]], [[1, 5, 8, 11], [1, 3, 5, 8, 11]],
[[1, 5, 9, 11], [1, 5, 7, 9, 11]], [[1, 6, 8, 11], [1, 3, 6, 8, 11]],
[[1, 6, 9, 11], [1, 4, 6, 9, 11]], [[1, 7, 9, 11], [1, 4, 7, 9, 11]],
[[1, 3, 5, 7, 11], [0, 2, 4, 6, 8]], [[1, 3, 5, 8, 11], [0, 2, 4, 7, 10]],
[[1, 3, 5, 9, 11], [1, 3, 7, 9, 11]], [[1, 3, 6, 8, 11], [1, 4, 6, 8, 11]],
[[1, 3, 6, 9, 11], [0, 2, 5, 8, 10]], [[1, 3, 7, 9, 11], [1, 3, 6, 9, 11]],
[[1, 4, 6, 8, 11], [1, 4, 6, 9, 11]], [[1, 4, 6, 9, 11], [2, 4, 6, 9, 11]],
[[1, 4, 7, 9, 11], [2, 4, 7, 9, 11]], [[1, 5, 7, 9, 11], [2, 4, 7, 9, 11]],
[[1, 3, 5, 7, 9, 11], [0, 2, 4, 6, 8, 10]], [[2], [2, 9]], [[2, 4], [2, 6, 9]],
[[2, 5], [2, 5, 9]], [[2, 6], [2, 6, 9]], [[2, 4, 6], [2, 4, 6, 9]],
[[2, 7], [2, 7, 11]], [[2, 4, 7], [2, 4, 7, 11]], [[2, 5, 7], [2, 5, 7, 11]],
[[2, 8], [4, 8, 11]], [[2, 4, 8], [2, 4, 8, 11]], [[2, 5, 8], [2, 5, 8, 10]],
[[2, 6, 8], [2, 6, 8, 11]], [[2, 4, 6, 8], [2, 4, 6, 8, 11]],
[[2, 9], [2, 6, 9]], [[2, 4, 9], [2, 4, 6, 9]], [[2, 5, 9], [0, 2, 5, 9]],
[[2, 6, 9], [2, 6, 9, 11]], [[2, 7, 9], [2, 7, 9, 11]],
[[2, 4, 6, 9], [2, 4, 6, 9, 11]], [[2, 4, 7, 9], [2, 4, 7, 9, 11]],
[[2, 5, 7, 9], [0, 2, 5, 7, 9]], [[2, 10], [2, 5, 10]],
[[2, 4, 10], [2, 4, 7, 10]], [[2, 5, 10], [2, 5, 7, 10]],
[[2, 6, 10], [1, 4, 6, 10]], [[2, 7, 10], [2, 5, 7, 10]],
[[2, 8, 10], [2, 5, 8, 10]], [[2, 4, 6, 10], [0, 2, 4, 6, 10]],
[[2, 4, 7, 10], [0, 2, 4, 7, 10]], [[2, 4, 8, 10], [2, 4, 7, 9, 11]],
[[2, 5, 7, 10], [0, 2, 5, 7, 10]], [[2, 5, 8, 10], [0, 2, 5, 8, 10]],
[[2, 6, 8, 10], [1, 3, 5, 7, 10]], [[2, 4, 6, 8, 10], [0, 2, 6, 8, 10]],
[[2, 11], [2, 7, 11]], [[2, 4, 11], [2, 4, 8, 11]],
[[2, 5, 11], [2, 5, 7, 11]], [[2, 6, 11], [2, 6, 9, 11]],
[[2, 7, 11], [2, 4, 7, 11]], [[2, 8, 11], [2, 4, 8, 11]],
[[2, 9, 11], [2, 6, 9, 11]], [[2, 4, 6, 11], [2, 4, 6, 9, 11]],
[[2, 4, 7, 11], [2, 4, 7, 9, 11]], [[2, 4, 8, 11], [2, 4, 6, 8, 11]],
[[2, 4, 9, 11], [2, 4, 7, 9, 11]], [[2, 5, 7, 11], [2, 5, 7, 9, 11]],
[[2, 5, 8, 11], [1, 3, 5, 8, 11]], [[2, 5, 9, 11], [2, 5, 7, 9, 11]],
[[2, 6, 8, 11], [2, 4, 6, 8, 11]], [[2, 6, 9, 11], [2, 4, 6, 9, 11]],
[[2, 7, 9, 11], [2, 4, 7, 9, 11]], [[2, 4, 6, 8, 11], [2, 4, 6, 9, 11]],
[[2, 4, 6, 9, 11], [2, 4, 7, 9, 11]], [[2, 4, 7, 9, 11], [0, 2, 4, 7, 9]],
[[2, 5, 7, 9, 11], [2, 4, 7, 9, 11]], [[3], [3, 10]], [[3, 5], [3, 7, 10]],
[[3, 6], [3, 6, 11]], [[3, 7], [3, 7, 10]], [[3, 5, 7], [3, 5, 7, 10]],
[[3, 8], [0, 3, 8]], [[3, 5, 8], [0, 3, 5, 8]], [[3, 6, 8], [0, 3, 6, 8]],
[[3, 9], [0, 3, 9]], [[3, 5, 9], [0, 3, 5, 9]], [[3, 6, 9], [3, 6, 9, 11]],
[[3, 7, 9], [0, 3, 7, 9]], [[3, 5, 7, 9], [0, 3, 5, 7, 9]],
[[3, 10], [3, 7, 10]], [[3, 5, 10], [3, 5, 7, 10]],
[[3, 6, 10], [1, 3, 6, 10]], [[3, 7, 10], [0, 3, 7, 10]],
[[3, 8, 10], [0, 3, 8, 10]], [[3, 5, 7, 10], [0, 3, 5, 7, 10]],
[[3, 5, 8, 10], [0, 3, 5, 8, 10]], [[3, 6, 8, 10], [1, 3, 6, 8, 10]],
[[3, 11], [3, 6, 11]], [[3, 5, 11], [3, 5, 8, 11]],
[[3, 6, 11], [3, 6, 9, 11]], [[3, 7, 11], [2, 5, 7, 11]],
[[3, 8, 11], [3, 6, 8, 11]], [[3, 9, 11], [3, 6, 9, 11]],
[[3, 5, 7, 11], [3, 5, 7, 9, 11]], [[3, 5, 8, 11], [1, 3, 5, 8, 11]],
[[3, 5, 9, 11], [3, 5, 7, 9, 11]], [[3, 6, 8, 11], [1, 3, 6, 8, 11]],
[[3, 6, 9, 11], [1, 3, 6, 9, 11]], [[3, 7, 9, 11], [2, 4, 7, 9, 11]],
[[3, 5, 7, 9, 11], [2, 5, 7, 9, 11]], [[4], [4, 11]], [[4, 6], [4, 7, 11]],
[[4, 7], [0, 4, 7]], [[4, 8], [4, 8, 11]], [[4, 6, 8], [4, 6, 8, 11]],
[[4, 9], [1, 4, 9]], [[4, 6, 9], [1, 4, 6, 9]], [[4, 7, 9], [1, 4, 7, 9]],
[[4, 10], [4, 7, 10]], [[4, 6, 10], [1, 4, 6, 10]],
[[4, 7, 10], [0, 4, 7, 10]], [[4, 8, 10], [1, 4, 8, 10]],
[[4, 6, 8, 10], [1, 4, 6, 8, 10]], [[4, 11], [4, 8, 11]],
[[4, 6, 11], [4, 6, 8, 11]], [[4, 7, 11], [2, 4, 7, 11]],
[[4, 8, 11], [2, 4, 8, 11]], [[4, 9, 11], [2, 4, 9, 11]],
[[4, 6, 8, 11], [1, 4, 6, 8, 11]], [[4, 6, 9, 11], [2, 4, 6, 9, 11]],
[[4, 7, 9, 11], [2, 4, 7, 9, 11]], [[5], [0, 5, 9]], [[5, 7], [0, 4, 7]],
[[5, 8], [0, 5, 8]], [[5, 9], [0, 5, 9]], [[5, 7, 9], [0, 4, 7, 9]],
[[5, 10], [2, 5, 10]], [[5, 7, 10], [2, 5, 7, 10]],
[[5, 8, 10], [2, 5, 8, 10]], [[5, 11], [0, 5, 9]], [[5, 7, 11], [2, 5, 7, 11]],
[[5, 8, 11], [1, 5, 8, 11]], [[5, 9, 11], [2, 5, 9, 11]],
[[5, 7, 9, 11], [2, 5, 7, 9, 11]], [[6], [1, 6]], [[6, 8], [1, 5, 8]],
[[6, 9], [2, 6, 9]], [[6, 10], [1, 6, 10]], [[6, 8, 10], [1, 5, 8, 10]],
[[6, 11], [3, 6, 11]], [[6, 8, 11], [3, 6, 8, 11]],
[[6, 9, 11], [3, 6, 9, 11]], [[7], [2, 7, 11]], [[7, 9], [2, 6, 9]],
[[7, 10], [2, 7, 10]], [[7, 11], [2, 7, 11]], [[7, 9, 11], [2, 7, 9, 11]],
[[8], [3, 8]], [[8, 10], [3, 7, 10]], [[8, 11], [4, 8, 11]], [[9], [4, 9]],
[[9, 11], [4, 8, 11]], [[10], [2, 5, 10]], [[11], [6, 11]]]
###################################################################################
ALL_CHORDS_PAIRS_FILTERED = [[[0], [0, 4, 7]], [[0, 3], [0, 3, 7]], [[0, 3, 5], [0, 3, 5, 9]],
[[0, 3, 5, 8], [0, 3, 7, 10]], [[0, 3, 5, 9], [0, 3, 7, 10]],
[[0, 3, 5, 10], [0, 3, 5, 9]], [[0, 3, 7], [0, 3, 7, 10]],
[[0, 3, 7, 10], [0, 3, 5, 9]], [[0, 3, 8], [0, 3, 5, 8]],
[[0, 3, 9], [0, 3, 5, 9]], [[0, 3, 10], [0, 3, 7, 10]], [[0, 4], [0, 4, 7]],
[[0, 4, 6], [0, 4, 6, 9]], [[0, 4, 6, 9], [1, 4, 6, 9]],
[[0, 4, 6, 10], [0, 4, 7, 10]], [[0, 4, 7], [0, 4, 7, 10]],
[[0, 4, 7, 10], [1, 4, 7, 10]], [[0, 4, 8], [0, 4, 7, 10]],
[[0, 4, 9], [0, 4, 6, 9]], [[0, 4, 10], [0, 4, 7, 10]], [[0, 5], [0, 5, 9]],
[[0, 5, 8], [0, 3, 5, 8]], [[0, 5, 9], [0, 3, 5, 9]],
[[0, 5, 10], [0, 3, 5, 10]], [[0, 6], [0, 6, 9]], [[0, 6, 9], [0, 4, 6, 9]],
[[0, 6, 10], [0, 4, 7, 10]], [[0, 7], [0, 4, 7]], [[0, 7, 10], [0, 4, 7, 10]],
[[0, 8], [0, 3, 8]], [[0, 9], [0, 4, 9]], [[0, 10], [2, 5, 10]], [[1], [1, 8]],
[[1, 4], [1, 4, 9]], [[1, 4, 6], [1, 4, 6, 9]], [[1, 4, 6, 9], [1, 4, 8, 11]],
[[1, 4, 6, 10], [0, 3, 5, 9]], [[1, 4, 6, 11], [1, 4, 6, 9]],
[[1, 4, 7], [1, 4, 7, 10]], [[1, 4, 7, 10], [0, 4, 7, 10]],
[[1, 4, 7, 11], [1, 4, 6, 10]], [[1, 4, 8], [1, 4, 8, 11]],
[[1, 4, 8, 11], [1, 4, 6, 9]], [[1, 4, 9], [1, 4, 6, 9]],
[[1, 4, 10], [1, 4, 6, 10]], [[1, 4, 11], [1, 4, 8, 11]], [[1, 5], [1, 5, 8]],
[[1, 5, 8], [1, 5, 8, 11]], [[1, 5, 8, 11], [2, 5, 8, 11]],
[[1, 5, 9], [0, 3, 5, 9]], [[1, 5, 10], [0, 4, 7, 10]],
[[1, 5, 11], [1, 5, 8, 11]], [[1, 6], [1, 6, 10]], [[1, 6, 9], [1, 4, 6, 9]],
[[1, 6, 10], [1, 4, 6, 10]], [[1, 6, 11], [1, 4, 6, 11]], [[1, 7], [1, 4, 7]],
[[1, 7, 10], [1, 4, 7, 10]], [[1, 7, 11], [1, 4, 7, 11]], [[1, 8], [1, 5, 8]],
[[1, 8, 11], [1, 4, 8, 11]], [[1, 9], [1, 4, 9]], [[1, 10], [1, 5, 10]],
[[1, 11], [2, 6, 11]], [[2], [2, 9]], [[2, 5], [2, 5, 9]],
[[2, 5, 8], [2, 5, 8, 11]], [[2, 5, 8, 11], [1, 4, 7, 10]],
[[2, 5, 9], [0, 3, 5, 9]], [[2, 5, 10], [0, 3, 5, 9]],
[[2, 5, 11], [2, 5, 8, 11]], [[2, 6], [2, 6, 9]], [[2, 6, 9], [1, 4, 6, 9]],
[[2, 6, 10], [1, 4, 6, 10]], [[2, 6, 11], [1, 4, 6, 10]], [[2, 7], [2, 7, 11]],
[[2, 7, 10], [0, 4, 7, 10]], [[2, 7, 11], [1, 4, 6, 9]], [[2, 8], [4, 8, 11]],
[[2, 8, 11], [2, 5, 8, 11]], [[2, 9], [2, 6, 9]], [[2, 10], [2, 5, 10]],
[[2, 11], [2, 7, 11]], [[3], [3, 10]], [[3, 5], [3, 7, 10]],
[[3, 5, 8], [0, 3, 5, 8]], [[3, 5, 8, 11], [2, 5, 8, 11]],
[[3, 5, 9], [0, 3, 5, 9]], [[3, 5, 10], [0, 3, 5, 10]],
[[3, 5, 11], [3, 5, 8, 11]], [[3, 7], [3, 7, 10]], [[3, 7, 10], [0, 3, 7, 10]],
[[3, 7, 11], [0, 3, 7, 10]], [[3, 8], [0, 3, 8]], [[3, 8, 11], [3, 5, 8, 11]],
[[3, 9], [0, 3, 9]], [[3, 10], [3, 7, 10]], [[3, 11], [3, 8, 11]],
[[4], [4, 11]], [[4, 6], [4, 7, 11]], [[4, 6, 9], [1, 4, 6, 9]],
[[4, 6, 10], [1, 4, 6, 10]], [[4, 6, 11], [1, 4, 6, 11]], [[4, 7], [0, 4, 7]],
[[4, 7, 10], [0, 4, 7, 10]], [[4, 7, 11], [1, 4, 7, 11]], [[4, 8], [4, 8, 11]],
[[4, 8, 11], [1, 4, 8, 11]], [[4, 9], [1, 4, 9]], [[4, 10], [4, 7, 10]],
[[4, 11], [4, 8, 11]], [[5], [0, 5, 9]], [[5, 8], [0, 5, 8]],
[[5, 8, 11], [1, 5, 8, 11]], [[5, 9], [0, 5, 9]], [[5, 10], [2, 5, 10]],
[[5, 11], [0, 5, 9]], [[6], [1, 6]], [[6, 9], [2, 6, 9]],
[[6, 10], [1, 6, 10]], [[6, 11], [2, 6, 11]], [[7], [2, 7, 11]],
[[7, 10], [2, 7, 10]], [[7, 11], [2, 7, 11]], [[8], [3, 8]],
[[8, 11], [4, 8, 11]], [[9], [4, 9]], [[10], [2, 5, 10]], [[11], [6, 11]]]
###################################################################################
ALL_CHORDS_TRIPLETS_SORTED = [[[0], [0, 4, 7], [0]], [[0, 2], [0, 4, 7], [0]], [[0, 3], [0, 3, 7], [0]],
[[0, 4], [0, 4, 7], [0, 4]], [[0, 2, 4], [0, 2, 4, 7], [0]],
[[0, 5], [0, 5, 9], [0, 5]], [[0, 2, 5], [0, 2, 5, 9], [0, 2, 5]],
[[0, 3, 5], [0, 3, 5, 9], [0, 3, 5]], [[0, 6], [0, 2, 6, 9], [2]],
[[0, 2, 6], [0, 2, 6, 9], [0, 2, 6]], [[0, 3, 6], [0, 3, 6, 8], [0, 3, 6]],
[[0, 4, 6], [0, 4, 6, 9], [0, 4, 6]],
[[0, 2, 4, 6], [0, 2, 4, 6, 9], [0, 2, 4, 6]], [[0, 7], [0, 4, 7], [0, 7]],
[[0, 2, 7], [0, 2, 4, 7], [0, 2, 7]], [[0, 3, 7], [0, 3, 7, 10], [0, 3, 7]],
[[0, 4, 7], [0, 4, 7, 9], [0, 4, 7]], [[0, 5, 7], [0, 5, 7, 9], [0, 5, 7]],
[[0, 2, 4, 7], [0, 2, 4, 7, 9], [0, 2, 4, 7]],
[[0, 2, 5, 7], [0, 2, 5, 7, 9], [0, 2, 5, 7]],
[[0, 3, 5, 7], [0, 3, 5, 7, 10], [0, 3, 5, 7]], [[0, 8], [0, 3, 8], [8]],
[[0, 2, 8], [0, 2, 5, 8], [0, 2, 8]], [[0, 3, 8], [0, 3, 5, 8], [0, 3, 8]],
[[0, 4, 8], [2, 4, 8, 11], [0, 4, 9]], [[0, 5, 8], [0, 3, 5, 8], [0, 5, 8]],
[[0, 6, 8], [0, 3, 6, 8], [0, 6, 8]],
[[0, 2, 4, 8], [0, 2, 4, 6, 8], [0, 2, 4, 8]],
[[0, 2, 5, 8], [0, 2, 5, 8, 10], [0, 2, 5, 8]],
[[0, 2, 6, 8], [0, 2, 6, 8, 10], [0, 2, 6, 8]],
[[0, 3, 5, 8], [0, 3, 5, 8, 10], [0, 3, 5, 8]],
[[0, 3, 6, 8], [0, 3, 6, 8, 10], [0, 3, 6, 8]],
[[0, 4, 6, 8], [2, 4, 6, 8, 11], [2, 6, 8, 11]],
[[0, 2, 4, 6, 8], [2, 4, 6, 8, 11], [2, 6, 8, 11]], [[0, 9], [0, 4, 9], [9]],
[[0, 2, 9], [0, 2, 6, 9], [0, 2, 9]], [[0, 3, 9], [0, 3, 5, 9], [0, 3, 9]],
[[0, 4, 9], [0, 4, 7, 9], [0, 4, 9]], [[0, 5, 9], [0, 2, 5, 9], [0, 5, 9]],
[[0, 6, 9], [0, 2, 6, 9], [0, 6, 9]], [[0, 7, 9], [0, 4, 7, 9], [0, 7, 9]],
[[0, 2, 4, 9], [0, 2, 4, 7, 9], [0, 2, 4, 9]],
[[0, 2, 5, 9], [0, 2, 5, 7, 9], [0, 2, 5, 9]],
[[0, 2, 6, 9], [0, 2, 4, 6, 9], [0, 2, 6, 9]],
[[0, 2, 7, 9], [0, 2, 4, 7, 9], [0, 2, 7, 9]],
[[0, 3, 5, 9], [0, 3, 5, 7, 9], [0, 3, 5, 9]],
[[0, 3, 6, 9], [0, 2, 4, 6, 9], [4, 6, 9]],
[[0, 3, 7, 9], [0, 3, 5, 7, 9], [0, 3, 7, 9]],
[[0, 4, 6, 9], [0, 2, 4, 6, 9], [0, 4, 6, 9]],
[[0, 4, 7, 9], [0, 2, 4, 7, 9], [0, 4, 7, 9]],
[[0, 5, 7, 9], [0, 2, 5, 7, 9], [0, 5, 7, 9]],
[[0, 2, 4, 6, 9], [2, 4, 6, 9, 11], [0, 2, 4, 6, 9]],
[[0, 2, 4, 7, 9], [2, 4, 7, 9, 11], [0, 2, 4, 7, 9]],
[[0, 2, 5, 7, 9], [2, 5, 7, 9, 11], [7]],
[[0, 3, 5, 7, 9], [2, 4, 6, 8, 11], [1, 4, 6, 8, 10]],
[[0, 10], [2, 5, 10], [10]], [[0, 2, 10], [0, 2, 5, 10], [10]],
[[0, 3, 10], [0, 3, 7, 10], [0, 3, 10]],
[[0, 4, 10], [0, 4, 7, 10], [0, 4, 10]],
[[0, 5, 10], [0, 2, 5, 10], [0, 5, 10]],
[[0, 6, 10], [0, 3, 6, 10], [0, 6, 10]],
[[0, 7, 10], [0, 4, 7, 10], [0, 7, 10]], [[0, 8, 10], [0, 3, 8, 10], [8]],
[[0, 2, 4, 10], [0, 2, 4, 7, 10], [0, 4, 10]],
[[0, 2, 5, 10], [0, 2, 5, 7, 10], [0, 2, 5, 10]],
[[0, 2, 6, 10], [0, 2, 6, 8, 10], [8]],
[[0, 2, 7, 10], [0, 2, 5, 7, 10], [2, 7, 10]],
[[0, 2, 8, 10], [0, 2, 5, 8, 10], [8, 10]],
[[0, 3, 5, 10], [0, 3, 5, 7, 10], [0, 3, 5, 10]],
[[0, 3, 6, 10], [0, 3, 6, 8, 10], [0, 3, 6, 10]],
[[0, 3, 7, 10], [0, 3, 5, 7, 10], [0, 3, 7, 10]],
[[0, 3, 8, 10], [0, 3, 5, 8, 10], [0, 3, 8, 10]],
[[0, 4, 6, 10], [0, 2, 4, 6, 10], [2]],
[[0, 4, 7, 10], [0, 2, 4, 7, 10], [0, 4, 7, 10]],
[[0, 4, 8, 10], [0, 2, 4, 8, 10], [0, 4, 8, 10]],
[[0, 5, 7, 10], [0, 3, 5, 7, 10], [0, 5, 7, 10]],
[[0, 5, 8, 10], [0, 3, 5, 8, 10], [10]],
[[0, 6, 8, 10], [0, 3, 6, 8, 10], [6]],
[[0, 2, 4, 6, 10], [0, 2, 4, 8, 10], [0, 2, 6, 8, 10]],
[[0, 2, 4, 7, 10], [1, 3, 6, 9, 11], [0, 2, 5, 8, 10]],
[[0, 2, 4, 8, 10], [1, 3, 7, 9, 11], [0, 2, 6, 8, 10]],
[[0, 2, 5, 7, 10], [0, 3, 5, 7, 10], [5, 10]],
[[0, 2, 5, 8, 10], [1, 4, 7, 9, 11], [8]],
[[0, 2, 6, 8, 10], [2, 4, 6, 8, 10], [0, 2, 6, 8, 10]],
[[0, 3, 5, 7, 10], [0, 2, 5, 7, 10], [9]],
[[0, 3, 5, 8, 10], [1, 3, 5, 8, 10], [0, 3, 5, 8, 10]],
[[0, 3, 6, 8, 10], [1, 3, 6, 8, 10], [0, 3, 6, 8, 10]],
[[0, 4, 6, 8, 10], [0, 2, 4, 6, 9], [1, 3, 5, 8, 10]],
[[0, 2, 4, 6, 8, 10], [1, 3, 5, 7, 9, 11], [0, 2, 4, 6, 8, 10]],
[[1], [1, 8], [1]], [[1, 3], [1, 5, 8], [1]], [[1, 4], [1, 4, 9], [9]],
[[1, 5], [1, 5, 8], [1, 5]], [[1, 3, 5], [1, 3, 5, 10], [1, 3, 5]],
[[1, 6], [1, 6, 10], [1, 6]], [[1, 3, 6], [1, 3, 6, 10], [1, 3, 6]],
[[1, 4, 6], [1, 4, 6, 9], [1, 4, 6]], [[1, 7], [1, 4, 7], [1, 7]],
[[1, 3, 7], [1, 3, 7, 10], [1, 3, 7]], [[1, 4, 7], [1, 4, 7, 9], [1, 4, 7]],
[[1, 5, 7], [1, 5, 7, 10], [1, 5, 7]], [[1, 3, 5, 7], [1, 3, 5, 7, 10], [7]],
[[1, 8], [1, 5, 8], [1, 8]], [[1, 3, 8], [1, 3, 5, 8], [1, 3, 8]],
[[1, 4, 8], [1, 4, 8, 11], [1, 4, 8]], [[1, 5, 8], [1, 5, 8, 10], [1, 5, 8]],
[[1, 6, 8], [1, 3, 6, 8], [1, 6, 8]],
[[1, 3, 5, 8], [1, 3, 5, 8, 10], [1, 3, 5, 8]],
[[1, 3, 6, 8], [1, 3, 6, 8, 10], [1, 3, 6, 8]],
[[1, 4, 6, 8], [1, 4, 6, 8, 11], [1, 4, 6, 8]], [[1, 9], [1, 4, 9], [9]],
[[1, 3, 9], [1, 3, 6, 9], [1, 3, 9]], [[1, 4, 9], [1, 4, 6, 9], [1, 4, 9]],
[[1, 5, 9], [0, 3, 5, 9], [0, 5, 9]], [[1, 6, 9], [1, 4, 6, 9], [1, 6, 9]],
[[1, 7, 9], [1, 4, 7, 9], [1, 7, 9]],
[[1, 3, 5, 9], [0, 3, 5, 7, 9], [1, 5, 9]],
[[1, 3, 6, 9], [1, 3, 6, 9, 11], [1, 3, 6, 9]],
[[1, 3, 7, 9], [1, 3, 5, 7, 9], [1, 7]],
[[1, 4, 6, 9], [1, 4, 6, 9, 11], [1, 4, 6, 9]],
[[1, 4, 7, 9], [1, 4, 7, 9, 11], [1, 4, 7, 9]],
[[1, 5, 7, 9], [1, 3, 7, 9, 11], [1, 5, 7, 9]],
[[1, 3, 5, 7, 9], [2, 4, 6, 8, 11], [9]], [[1, 10], [1, 5, 10], [10]],
[[1, 3, 10], [1, 3, 7, 10], [1, 3, 10]],
[[1, 4, 10], [1, 4, 6, 10], [1, 4, 10]],
[[1, 5, 10], [1, 5, 8, 10], [1, 5, 10]],
[[1, 6, 10], [1, 4, 6, 10], [1, 6, 10]],
[[1, 7, 10], [1, 3, 7, 10], [1, 7, 10]], [[1, 8, 10], [1, 5, 8, 10], [10]],
[[1, 3, 5, 10], [1, 3, 5, 8, 10], [1, 3, 5, 10]],
[[1, 3, 6, 10], [1, 3, 6, 8, 10], [1, 3, 6, 10]],
[[1, 3, 7, 10], [1, 3, 5, 7, 10], [1, 3, 7, 10]],
[[1, 3, 8, 10], [1, 3, 5, 8, 10], [1, 3, 8, 10]],
[[1, 4, 6, 10], [1, 4, 6, 8, 10], [1, 4, 6, 10]],
[[1, 4, 7, 10], [0, 2, 4, 7, 10], [0, 4, 7, 10]],
[[1, 4, 8, 10], [1, 4, 6, 8, 10], [1, 4, 8, 10]],
[[1, 5, 7, 10], [1, 3, 5, 7, 10], [1, 5, 7, 10]],
[[1, 5, 8, 10], [1, 3, 5, 8, 10], [1, 5, 8, 10]],
[[1, 6, 8, 10], [1, 3, 6, 8, 10], [1, 6, 8, 10]],
[[1, 3, 5, 7, 10], [2, 4, 6, 8, 11], [0, 3, 5, 7, 9]],
[[1, 3, 5, 8, 10], [0, 3, 5, 8, 10], [6, 8, 10]],
[[1, 3, 6, 8, 10], [0, 3, 6, 8, 10], [8]],
[[1, 4, 6, 8, 10], [0, 3, 5, 7, 9], [2, 4, 6, 8, 11]],
[[1, 11], [2, 6, 11], [11]], [[1, 3, 11], [1, 3, 6, 11], [11]],
[[1, 4, 11], [1, 4, 8, 11], [1]], [[1, 5, 11], [1, 5, 8, 11], [1, 5, 11]],
[[1, 6, 11], [1, 4, 6, 11], [1, 6, 11]],
[[1, 7, 11], [1, 4, 7, 11], [1, 7, 11]],
[[1, 8, 11], [1, 4, 8, 11], [1, 8, 11]], [[1, 9, 11], [1, 4, 9, 11], [9]],
[[1, 3, 5, 11], [1, 3, 5, 8, 11], [1, 3, 5, 11]],
[[1, 3, 6, 11], [1, 3, 6, 8, 11], [1, 3, 6, 11]],
[[1, 3, 7, 11], [1, 3, 7, 9, 11], [0]],
[[1, 3, 8, 11], [1, 3, 6, 8, 11], [1, 3, 8, 11]],
[[1, 3, 9, 11], [1, 3, 6, 9, 11], [1, 3, 9, 11]],
[[1, 4, 6, 11], [1, 4, 6, 9, 11], [1, 4, 6, 11]],
[[1, 4, 7, 11], [1, 4, 7, 9, 11], [1, 4, 7, 11]],
[[1, 4, 8, 11], [1, 4, 6, 8, 11], [1, 4, 8, 11]],
[[1, 4, 9, 11], [1, 4, 6, 9, 11], [1, 4, 9, 11]],
[[1, 5, 7, 11], [0, 4, 6, 8, 10], [5, 7, 9, 11]],
[[1, 5, 8, 11], [1, 3, 5, 8, 11], [1, 5, 8, 11]],
[[1, 5, 9, 11], [1, 5, 7, 9, 11], [9]],
[[1, 6, 8, 11], [1, 3, 6, 8, 11], [1, 6, 8, 11]],
[[1, 6, 9, 11], [1, 4, 6, 9, 11], [1, 6, 9, 11]],
[[1, 7, 9, 11], [1, 4, 7, 9, 11], [1, 7, 9, 11]],
[[1, 3, 5, 7, 11], [0, 2, 4, 6, 8], [7, 9]],
[[1, 3, 5, 8, 11], [0, 2, 4, 7, 10], [1, 3, 6, 9, 11]],
[[1, 3, 5, 9, 11], [1, 3, 7, 9, 11], [0, 2, 6, 8, 10]],
[[1, 3, 6, 8, 11], [1, 4, 6, 8, 11], [6, 8, 11]],
[[1, 3, 6, 9, 11], [0, 2, 5, 8, 10], [1, 4, 7, 9, 11]],
[[1, 3, 7, 9, 11], [1, 3, 6, 9, 11], [11]],
[[1, 4, 6, 8, 11], [1, 4, 6, 9, 11], [9, 11]],
[[1, 4, 6, 9, 11], [2, 4, 6, 9, 11], [1, 4, 6, 9, 11]],
[[1, 4, 7, 9, 11], [2, 4, 7, 9, 11], [7, 9, 11]],
[[1, 5, 7, 9, 11], [2, 4, 7, 9, 11], [5, 7, 9]],
[[1, 3, 5, 7, 9, 11], [0, 2, 4, 6, 8, 10], [1, 3, 5, 7, 9, 11]],
[[2], [2, 9], [2]], [[2, 4], [2, 6, 9], [2]], [[2, 5], [2, 5, 9], [2]],
[[2, 6], [2, 6, 9], [2]], [[2, 4, 6], [2, 4, 6, 9], [2, 4, 6]],
[[2, 7], [2, 7, 11], [2, 7]], [[2, 4, 7], [2, 4, 7, 11], [2, 4, 7]],
[[2, 5, 7], [2, 5, 7, 11], [2, 5, 7]], [[2, 8], [4, 8, 11], [4]],
[[2, 4, 8], [2, 4, 8, 11], [2, 4, 8]], [[2, 5, 8], [2, 5, 8, 10], [2, 5, 8]],
[[2, 6, 8], [2, 6, 8, 11], [2, 6, 8]],
[[2, 4, 6, 8], [2, 4, 6, 8, 11], [2, 4, 6, 8]], [[2, 9], [2, 6, 9], [2, 9]],
[[2, 4, 9], [2, 4, 6, 9], [2, 4, 9]], [[2, 5, 9], [0, 2, 5, 9], [2, 5, 9]],
[[2, 6, 9], [2, 6, 9, 11], [2, 6, 9]], [[2, 7, 9], [2, 7, 9, 11], [2, 7, 9]],
[[2, 4, 6, 9], [2, 4, 6, 9, 11], [2, 4, 6, 9]],
[[2, 4, 7, 9], [2, 4, 7, 9, 11], [2, 4, 7, 9]],
[[2, 5, 7, 9], [0, 2, 5, 7, 9], [2, 5, 7, 9]], [[2, 10], [2, 5, 10], [10]],
[[2, 4, 10], [2, 4, 7, 10], [2, 4, 10]],
[[2, 5, 10], [2, 5, 7, 10], [2, 5, 10]],
[[2, 6, 10], [1, 4, 6, 10], [1, 6, 10]],
[[2, 7, 10], [2, 5, 7, 10], [2, 7, 10]],
[[2, 8, 10], [2, 5, 8, 10], [2, 8, 10]],
[[2, 4, 6, 10], [0, 2, 4, 6, 10], [2, 4, 6, 10]],
[[2, 4, 7, 10], [0, 2, 4, 7, 10], [2, 4, 7, 10]],
[[2, 4, 8, 10], [2, 4, 7, 9, 11], [2, 4, 7, 11]],
[[2, 5, 7, 10], [0, 2, 5, 7, 10], [2, 5, 7, 10]],
[[2, 5, 8, 10], [0, 2, 5, 8, 10], [2, 5, 8, 10]],
[[2, 6, 8, 10], [1, 3, 5, 7, 10], [1, 7]],
[[2, 4, 6, 8, 10], [0, 2, 6, 8, 10], [2, 4, 6, 8, 10]],
[[2, 11], [2, 7, 11], [7]], [[2, 4, 11], [2, 4, 8, 11], [2, 4, 11]],
[[2, 5, 11], [2, 5, 7, 11], [2, 5, 11]],
[[2, 6, 11], [2, 6, 9, 11], [2, 6, 11]],
[[2, 7, 11], [2, 4, 7, 11], [2, 7, 11]],
[[2, 8, 11], [2, 4, 8, 11], [2, 8, 11]],
[[2, 9, 11], [2, 6, 9, 11], [2, 9, 11]],
[[2, 4, 6, 11], [2, 4, 6, 9, 11], [2, 4, 6, 11]],
[[2, 4, 7, 11], [2, 4, 7, 9, 11], [2, 4, 7, 11]],
[[2, 4, 8, 11], [2, 4, 6, 8, 11], [2, 4, 8, 11]],
[[2, 4, 9, 11], [2, 4, 7, 9, 11], [2, 4, 9, 11]],
[[2, 5, 7, 11], [2, 5, 7, 9, 11], [2, 5, 7, 11]],
[[2, 5, 8, 11], [1, 3, 5, 8, 11], [1, 5, 8, 11]],
[[2, 5, 9, 11], [2, 5, 7, 9, 11], [2, 5, 9, 11]],
[[2, 6, 8, 11], [2, 4, 6, 8, 11], [2, 6, 8, 11]],
[[2, 6, 9, 11], [2, 4, 6, 9, 11], [2, 6, 9, 11]],
[[2, 7, 9, 11], [2, 4, 7, 9, 11], [2, 7, 9, 11]],
[[2, 4, 6, 8, 11], [2, 4, 6, 9, 11], [2, 4, 6, 8, 11]],
[[2, 4, 6, 9, 11], [2, 4, 7, 9, 11], [2, 7, 9]],
[[2, 4, 7, 9, 11], [0, 2, 4, 7, 9], [11]],
[[2, 5, 7, 9, 11], [2, 4, 7, 9, 11], [2, 7, 9, 11]], [[3], [3, 10], [3]],
[[3, 5], [3, 7, 10], [3]], [[3, 6], [3, 6, 11], [11]],
[[3, 7], [3, 7, 10], [3]], [[3, 5, 7], [3, 5, 7, 10], [3, 5, 7]],
[[3, 8], [0, 3, 8], [3, 8]], [[3, 5, 8], [0, 3, 5, 8], [8]],
[[3, 6, 8], [0, 3, 6, 8], [3, 6, 8]], [[3, 9], [0, 3, 9], [3, 9]],
[[3, 5, 9], [0, 3, 5, 9], [3, 5, 9]], [[3, 6, 9], [3, 6, 9, 11], [3, 6, 9]],
[[3, 7, 9], [0, 3, 7, 9], [3, 7, 9]],
[[3, 5, 7, 9], [0, 3, 5, 7, 9], [0, 3, 5, 9]], [[3, 10], [3, 7, 10], [3, 10]],
[[3, 5, 10], [3, 5, 7, 10], [3, 5, 10]],
[[3, 6, 10], [1, 3, 6, 10], [3, 6, 10]],
[[3, 7, 10], [0, 3, 7, 10], [3, 7, 10]],
[[3, 8, 10], [0, 3, 8, 10], [3, 8, 10]],
[[3, 5, 7, 10], [0, 3, 5, 7, 10], [3, 5, 7, 10]],
[[3, 5, 8, 10], [0, 3, 5, 8, 10], [3, 5, 8, 10]],
[[3, 6, 8, 10], [1, 3, 6, 8, 10], [3, 6, 8, 10]], [[3, 11], [3, 6, 11], [11]],
[[3, 5, 11], [3, 5, 8, 11], [3, 5, 11]],
[[3, 6, 11], [3, 6, 9, 11], [3, 6, 11]],
[[3, 7, 11], [2, 5, 7, 11], [2, 7, 11]],
[[3, 8, 11], [3, 6, 8, 11], [3, 8, 11]],
[[3, 9, 11], [3, 6, 9, 11], [3, 9, 11]],
[[3, 5, 7, 11], [3, 5, 7, 9, 11], [3, 5, 7, 11]],
[[3, 5, 8, 11], [1, 3, 5, 8, 11], [3, 5, 8, 11]],
[[3, 5, 9, 11], [3, 5, 7, 9, 11], [5, 7, 9, 11]],
[[3, 6, 8, 11], [1, 3, 6, 8, 11], [3, 6, 8, 11]],
[[3, 6, 9, 11], [1, 3, 6, 9, 11], [3, 6, 9, 11]],
[[3, 7, 9, 11], [2, 4, 7, 9, 11], [7, 9, 11]],
[[3, 5, 7, 9, 11], [2, 5, 7, 9, 11], [2, 5, 7, 11]], [[4], [4, 11], [4]],
[[4, 6], [4, 7, 11], [4]], [[4, 7], [0, 4, 7], [0]], [[4, 8], [4, 8, 11], [4]],
[[4, 6, 8], [4, 6, 8, 11], [4]], [[4, 9], [1, 4, 9], [4, 9]],
[[4, 6, 9], [1, 4, 6, 9], [4, 6, 9]], [[4, 7, 9], [1, 4, 7, 9], [4, 7, 9]],
[[4, 10], [4, 7, 10], [4, 10]], [[4, 6, 10], [1, 4, 6, 10], [4, 6, 10]],
[[4, 7, 10], [0, 4, 7, 10], [4, 7, 10]], [[4, 8, 10], [1, 4, 8, 10], [1]],
[[4, 6, 8, 10], [1, 4, 6, 8, 10], [6]], [[4, 11], [4, 8, 11], [4, 11]],
[[4, 6, 11], [4, 6, 8, 11], [4, 6, 11]],
[[4, 7, 11], [2, 4, 7, 11], [4, 7, 11]],
[[4, 8, 11], [2, 4, 8, 11], [4, 8, 11]],
[[4, 9, 11], [2, 4, 9, 11], [4, 9, 11]],
[[4, 6, 8, 11], [1, 4, 6, 8, 11], [4, 6, 8, 11]],
[[4, 6, 9, 11], [2, 4, 6, 9, 11], [4, 6, 9, 11]],
[[4, 7, 9, 11], [2, 4, 7, 9, 11], [4, 7, 9, 11]], [[5], [0, 5, 9], [5]],
[[5, 7], [0, 4, 7], [0]], [[5, 8], [0, 5, 8], [5]], [[5, 9], [0, 5, 9], [5]],
[[5, 7, 9], [0, 4, 7, 9], [5]], [[5, 10], [2, 5, 10], [5, 10]],
[[5, 7, 10], [2, 5, 7, 10], [7]], [[5, 8, 10], [2, 5, 8, 10], [5, 8, 10]],
[[5, 11], [0, 5, 9], [5]], [[5, 7, 11], [2, 5, 7, 11], [5, 7, 11]],
[[5, 8, 11], [1, 5, 8, 11], [5, 8, 11]],
[[5, 9, 11], [2, 5, 9, 11], [5, 9, 11]],
[[5, 7, 9, 11], [2, 5, 7, 9, 11], [5, 7, 9]], [[6], [1, 6], [6]],
[[6, 8], [1, 5, 8], [8]], [[6, 9], [2, 6, 9], [2]], [[6, 10], [1, 6, 10], [6]],
[[6, 8, 10], [1, 5, 8, 10], [6, 8, 10]], [[6, 11], [3, 6, 11], [6, 11]],
[[6, 8, 11], [3, 6, 8, 11], [6, 8, 11]],
[[6, 9, 11], [3, 6, 9, 11], [6, 9, 11]], [[7], [2, 7, 11], [7]],
[[7, 9], [2, 6, 9], [2]], [[7, 10], [2, 7, 10], [7]],
[[7, 11], [2, 7, 11], [7]], [[7, 9, 11], [2, 7, 9, 11], [7, 9, 11]],
[[8], [3, 8], [8]], [[8, 10], [3, 7, 10], [3]], [[8, 11], [4, 8, 11], [4]],
[[9], [4, 9], [9]], [[9, 11], [4, 8, 11], [4]], [[10], [2, 5, 10], [10]],
[[11], [6, 11], [11]]]
###################################################################################
ALL_CHORDS_TRIPLETS_FILTERED = [[[0], [0, 4, 7], [7]], [[0, 3], [0, 3, 7], [0]],
[[0, 3, 5], [0, 3, 5, 9], [5]], [[0, 3, 5, 8], [0, 3, 7, 10], [0]],
[[0, 3, 5, 9], [0, 3, 7, 10], [10]], [[0, 3, 5, 10], [0, 3, 5, 9], [5]],
[[0, 3, 7], [0, 3, 7, 10], [0]], [[0, 3, 7, 10], [0, 3, 5, 9], [2, 5, 10]],
[[0, 3, 8], [0, 3, 5, 8], [8]], [[0, 3, 9], [0, 3, 5, 9], [5]],
[[0, 3, 10], [0, 3, 7, 10], [0]], [[0, 4], [0, 4, 7], [0]],
[[0, 4, 6], [0, 4, 6, 9], [4]], [[0, 4, 6, 9], [1, 4, 6, 9], [9]],
[[0, 4, 6, 10], [0, 4, 7, 10], [0, 4, 10]], [[0, 4, 7], [0, 4, 7, 10], [0]],
[[0, 4, 7, 10], [1, 4, 7, 10], [0]], [[0, 4, 8], [0, 4, 7, 10], [0, 5, 8]],
[[0, 4, 9], [0, 4, 6, 9], [9]], [[0, 4, 10], [0, 4, 7, 10], [0]],
[[0, 5], [0, 5, 9], [5]], [[0, 5, 8], [0, 3, 5, 8], [5]],
[[0, 5, 9], [0, 3, 5, 9], [5]], [[0, 5, 10], [0, 3, 5, 10], [10]],
[[0, 6], [0, 6, 9], [9]], [[0, 6, 9], [0, 4, 6, 9], [6]],
[[0, 6, 10], [0, 4, 7, 10], [10]], [[0, 7], [0, 4, 7], [0]],
[[0, 7, 10], [0, 4, 7, 10], [0]], [[0, 8], [0, 3, 8], [8]],
[[0, 9], [0, 4, 9], [9]], [[0, 10], [2, 5, 10], [10]], [[1], [1, 8], [8]],
[[1, 4], [1, 4, 9], [9]], [[1, 4, 6], [1, 4, 6, 9], [6]],
[[1, 4, 6, 9], [1, 4, 8, 11], [4]], [[1, 4, 6, 10], [0, 3, 5, 9], [5]],
[[1, 4, 6, 11], [1, 4, 6, 9], [6]], [[1, 4, 7], [1, 4, 7, 10], [10]],
[[1, 4, 7, 10], [0, 4, 7, 10], [0]],
[[1, 4, 7, 11], [1, 4, 6, 10], [1, 6, 10]], [[1, 4, 8], [1, 4, 8, 11], [1]],
[[1, 4, 8, 11], [1, 4, 6, 9], [1, 4, 9]], [[1, 4, 9], [1, 4, 6, 9], [9]],
[[1, 4, 10], [1, 4, 6, 10], [6]], [[1, 4, 11], [1, 4, 8, 11], [1]],
[[1, 5], [1, 5, 8], [1]], [[1, 5, 8], [1, 5, 8, 11], [1]],
[[1, 5, 8, 11], [2, 5, 8, 11], [1]], [[1, 5, 9], [0, 3, 5, 9], [0, 5, 9]],
[[1, 5, 10], [0, 4, 7, 10], [0]], [[1, 5, 11], [1, 5, 8, 11], [11]],
[[1, 6], [1, 6, 10], [6]], [[1, 6, 9], [1, 4, 6, 9], [6]],
[[1, 6, 10], [1, 4, 6, 10], [6]], [[1, 6, 11], [1, 4, 6, 11], [11]],
[[1, 7], [1, 4, 7], [4]], [[1, 7, 10], [1, 4, 7, 10], [4]],
[[1, 7, 11], [1, 4, 7, 11], [7]], [[1, 8], [1, 5, 8], [1]],
[[1, 8, 11], [1, 4, 8, 11], [1]], [[1, 9], [1, 4, 9], [9]],
[[1, 10], [1, 5, 10], [10]], [[1, 11], [2, 6, 11], [11]], [[2], [2, 9], [9]],
[[2, 5], [2, 5, 9], [2]], [[2, 5, 8], [2, 5, 8, 11], [2]],
[[2, 5, 8, 11], [1, 4, 7, 10], [0, 3, 8]],
[[2, 5, 9], [0, 3, 5, 9], [2, 5, 10]], [[2, 5, 10], [0, 3, 5, 9], [2, 10]],
[[2, 5, 11], [2, 5, 8, 11], [8]], [[2, 6], [2, 6, 9], [2]],
[[2, 6, 9], [1, 4, 6, 9], [1, 4, 9]], [[2, 6, 10], [1, 4, 6, 10], [1, 6, 10]],
[[2, 6, 11], [1, 4, 6, 10], [1, 6, 10]], [[2, 7], [2, 7, 11], [7]],
[[2, 7, 10], [0, 4, 7, 10], [0]], [[2, 7, 11], [1, 4, 6, 9], [1, 4, 9]],
[[2, 8], [4, 8, 11], [4]], [[2, 8, 11], [2, 5, 8, 11], [4]],
[[2, 9], [2, 6, 9], [2]], [[2, 10], [2, 5, 10], [10]],
[[2, 11], [2, 7, 11], [7]], [[3], [3, 10], [10]], [[3, 5], [3, 7, 10], [3]],
[[3, 5, 8], [0, 3, 5, 8], [8]], [[3, 5, 8, 11], [2, 5, 8, 11], [2]],
[[3, 5, 9], [0, 3, 5, 9], [5]], [[3, 5, 10], [0, 3, 5, 10], [5, 10]],
[[3, 5, 11], [3, 5, 8, 11], [5]], [[3, 7], [3, 7, 10], [3]],
[[3, 7, 10], [0, 3, 7, 10], [10]], [[3, 7, 11], [0, 3, 7, 10], [3, 7, 10]],
[[3, 8], [0, 3, 8], [8]], [[3, 8, 11], [3, 5, 8, 11], [11]],
[[3, 9], [0, 3, 9], [9]], [[3, 10], [3, 7, 10], [3]],
[[3, 11], [3, 8, 11], [8]], [[4], [4, 11], [11]], [[4, 6], [4, 7, 11], [4]],
[[4, 6, 9], [1, 4, 6, 9], [9]], [[4, 6, 10], [1, 4, 6, 10], [6]],
[[4, 6, 11], [1, 4, 6, 11], [11]], [[4, 7], [0, 4, 7], [0]],
[[4, 7, 10], [0, 4, 7, 10], [0]], [[4, 7, 11], [1, 4, 7, 11], [11]],
[[4, 8], [4, 8, 11], [4]], [[4, 8, 11], [1, 4, 8, 11], [4]],
[[4, 9], [1, 4, 9], [9]], [[4, 10], [4, 7, 10], [7]],
[[4, 11], [4, 8, 11], [4]], [[5], [0, 5, 9], [0]], [[5, 8], [0, 5, 8], [5]],
[[5, 8, 11], [1, 5, 8, 11], [1]], [[5, 9], [0, 5, 9], [5]],
[[5, 10], [2, 5, 10], [10]], [[5, 11], [0, 5, 9], [5]], [[6], [1, 6], [1]],
[[6, 9], [2, 6, 9], [2]], [[6, 10], [1, 6, 10], [6]],
[[6, 11], [2, 6, 11], [11]], [[7], [2, 7, 11], [2]],
[[7, 10], [2, 7, 10], [7]], [[7, 11], [2, 7, 11], [7]], [[8], [3, 8], [3]],
[[8, 11], [4, 8, 11], [4]], [[9], [4, 9], [4]], [[10], [2, 5, 10], [5]],
[[11], [6, 11], [6]]]
###################################################################################
def pitches_to_tones(pitches):
return [p % 12 for p in pitches]
###################################################################################
def tones_to_pitches(tones, base_octave=5):
return [(base_octave * 12) + t for t in tones]
###################################################################################
def find_closest_value(lst, val):
closest_value = min(lst, key=lambda x: abs(val - x))
closest_value_indexes = [i for i in range(len(lst)) if lst[i] == closest_value]
return [closest_value, abs(val - closest_value), closest_value_indexes]
###################################################################################
def transpose_tones_chord(tones_chord, transpose_value=0):
return sorted([((60+t)+transpose_value) % 12 for t in sorted(set(tones_chord))])
###################################################################################
def transpose_tones(tones, transpose_value=0):
return [((60+t)+transpose_value) % 12 for t in tones]
###################################################################################
def transpose_pitches_chord(pitches_chord, transpose_value=0):
return [max(1, min(127, p+transpose_value)) for p in sorted(set(pitches_chord), reverse=True)]
###################################################################################
def transpose_pitches(pitches, transpose_value=0):
return [max(1, min(127, p+transpose_value)) for p in pitches]
###################################################################################
def reverse_enhanced_score_notes(escore_notes):
score = recalculate_score_timings(escore_notes)
ematrix = escore_notes_to_escore_matrix(score, reverse_matrix=True)
e_score = escore_matrix_to_original_escore_notes(ematrix)
reversed_score = recalculate_score_timings(e_score)
return reversed_score
###################################################################################
def count_patterns(lst, sublist):
count = 0
idx = 0
for i in range(len(lst) - len(sublist) + 1):
if lst[idx:idx + len(sublist)] == sublist:
count += 1
idx += len(sublist)
else:
idx += 1
return count
###################################################################################
def find_lrno_patterns(seq):
all_seqs = Counter()
max_pat_len = math.ceil(len(seq) / 2)
num_iter = 0
for i in range(len(seq)):
for j in range(i+1, len(seq)+1):
if j-i <= max_pat_len:
all_seqs[tuple(seq[i:j])] += 1
num_iter += 1
max_count = 0
max_len = 0
for val, count in all_seqs.items():
if max_len < len(val):
max_count = max(2, count)
if count > 1:
max_len = max(max_len, len(val))
pval = val
max_pats = []
for val, count in all_seqs.items():
if count == max_count and len(val) == max_len:
max_pats.append(val)
found_patterns = []
for pat in max_pats:
count = count_patterns(seq, list(pat))
if count > 1:
found_patterns.append([count, len(pat), pat])
return found_patterns
###################################################################################
def delta_pitches(escore_notes, pitches_index=4):
pitches = [p[pitches_index] for p in escore_notes]
return [a-b for a, b in zip(pitches[:-1], pitches[1:])]
###################################################################################
def split_list(lst, val):
return [lst[i:j] for i, j in zip([0] + [k + 1 for k, x in enumerate(lst) if x == val], [k for k, x in enumerate(lst) if x == val] + [len(lst)]) if j > i]
###################################################################################
def even_timings(escore_notes,
times_idx=1,
durs_idx=2
):
esn = copy.deepcopy(escore_notes)
for e in esn:
if e[times_idx] != 0:
if e[times_idx] % 2 != 0:
e[times_idx] += 1
if e[durs_idx] % 2 != 0:
e[durs_idx] += 1
return esn
###################################################################################
def delta_score_to_abs_score(delta_score_notes,
times_idx=1
):
abs_score = copy.deepcopy(delta_score_notes)
abs_time = 0
for i, e in enumerate(delta_score_notes):
dtime = e[times_idx]
abs_time += dtime
abs_score[i][times_idx] = abs_time
return abs_score
###################################################################################
def adjust_numbers_to_sum(numbers, target_sum):
current_sum = sum(numbers)
difference = target_sum - current_sum
non_zero_elements = [(i, num) for i, num in enumerate(numbers) if num != 0]
total_non_zero = sum(num for _, num in non_zero_elements)
increments = []
for i, num in non_zero_elements:
proportion = num / total_non_zero
increment = proportion * difference
increments.append(increment)
for idx, (i, num) in enumerate(non_zero_elements):
numbers[i] += int(round(increments[idx]))
current_sum = sum(numbers)
difference = target_sum - current_sum
non_zero_indices = [i for i, num in enumerate(numbers) if num != 0]
for i in range(abs(difference)):
numbers[non_zero_indices[i % len(non_zero_indices)]] += 1 if difference > 0 else -1
return numbers
###################################################################################
def find_next_bar(escore_notes, bar_time, start_note_idx, cur_bar):
for e in escore_notes[start_note_idx:]:
if e[1] // bar_time > cur_bar:
return e, escore_notes.index(e)
###################################################################################
def align_escore_notes_to_bars(escore_notes,
bar_time=4000,
trim_durations=False,
split_durations=False,
even_timings=False
):
#=============================================================================
escore = copy.deepcopy(escore_notes)
if even_timings:
for e in escore:
if e[1] % 2 != 0:
e[1] += 1
if e[2] % 2 != 0:
e[2] += 1
aligned_escore_notes = copy.deepcopy(escore)
abs_time = 0
nidx = 0
delta = 0
bcount = 0
next_bar = [0]
#=============================================================================
while next_bar:
next_bar = find_next_bar(escore, bar_time, nidx, bcount)
if next_bar:
gescore_notes = escore[nidx:next_bar[1]]
else:
gescore_notes = escore[nidx:]
original_timings = [delta] + [(b[1]-a[1]) for a, b in zip(gescore_notes[:-1], gescore_notes[1:])]
adj_timings = adjust_numbers_to_sum(original_timings, bar_time)
for t in adj_timings:
abs_time += t
aligned_escore_notes[nidx][1] = abs_time
aligned_escore_notes[nidx][2] -= int(bar_time // 200)
nidx += 1
if next_bar:
delta = escore[next_bar[1]][1]-escore[next_bar[1]-1][1]
bcount += 1
#=============================================================================
aligned_adjusted_escore_notes = []
bcount = 0
for a in aligned_escore_notes:
bcount = a[1] // bar_time
nbtime = bar_time * (bcount+1)
if a[1]+a[2] > nbtime and a[3] != 9:
if trim_durations or split_durations:
ddiff = ((a[1]+a[2])-nbtime)
aa = copy.deepcopy(a)
aa[2] = a[2] - ddiff
aligned_adjusted_escore_notes.append(aa)
if split_durations:
aaa = copy.deepcopy(a)
aaa[1] = a[1]+aa[2]
aaa[2] = ddiff
aligned_adjusted_escore_notes.append(aaa)
else:
aligned_adjusted_escore_notes.append(a)
else:
aligned_adjusted_escore_notes.append(a)
#=============================================================================
return aligned_adjusted_escore_notes
###################################################################################
def normalize_chord_durations(chord,
dur_idx=2,
norm_factor=100
):
nchord = copy.deepcopy(chord)
for c in nchord:
c[dur_idx] = int(round(max(1 / norm_factor, c[dur_idx] // norm_factor) * norm_factor))
return nchord
###################################################################################
def normalize_chordified_score_durations(chordified_score,
dur_idx=2,
norm_factor=100
):
ncscore = copy.deepcopy(chordified_score)
for cc in ncscore:
for c in cc:
c[dur_idx] = int(round(max(1 / norm_factor, c[dur_idx] // norm_factor) * norm_factor))
return ncscore
###################################################################################
def horizontal_ordered_list_search(list_of_lists,
query_list,
start_idx=0,
end_idx=-1
):
lol = list_of_lists
results = []
if start_idx > 0:
lol = list_of_lists[start_idx:]
if start_idx == -1:
idx = -1
for i, l in enumerate(list_of_lists):
try:
idx = l.index(query_list[0])
lol = list_of_lists[i:]
break
except:
continue
if idx == -1:
results.append(-1)
return results
else:
results.append(i)
if end_idx != -1:
lol = list_of_lists[start_idx:start_idx+max(end_idx, len(query_list))]
for i, q in enumerate(query_list):
try:
idx = lol[i].index(q)
results.append(idx)
except:
results.append(-1)
return results
return results
###################################################################################
def escore_notes_to_escore_matrix(escore_notes,
alt_velocities=False,
flip_matrix=False,
reverse_matrix=False
):
last_time = escore_notes[-1][1]
last_notes = [e for e in escore_notes if e[1] == last_time]
max_last_dur = max([e[2] for e in last_notes])
time_range = last_time+max_last_dur
channels_list = sorted(set([e[3] for e in escore_notes]))
escore_matrixes = []
for cha in channels_list:
escore_matrix = [[[-1, -1]] * 128 for _ in range(time_range)]
pe = escore_notes[0]
for i, note in enumerate(escore_notes):
etype, time, duration, channel, pitch, velocity, patch = note
time = max(0, time)
duration = max(1, duration)
channel = max(0, min(15, channel))
pitch = max(0, min(127, pitch))
velocity = max(0, min(127, velocity))
patch = max(0, min(128, patch))
if alt_velocities:
velocity -= (i % 2)
if channel == cha:
for t in range(time, min(time + duration, time_range)):
escore_matrix[t][pitch] = [velocity, patch]
pe = note
if flip_matrix:
temp_matrix = []
for m in escore_matrix:
temp_matrix.append(m[::-1])
escore_matrix = temp_matrix
if reverse_matrix:
escore_matrix = escore_matrix[::-1]
escore_matrixes.append(escore_matrix)
return [channels_list, escore_matrixes]
###################################################################################
def escore_matrix_to_merged_escore_notes(full_escore_matrix,
max_note_duration=4000
):
merged_escore_notes = []
mat_channels_list = full_escore_matrix[0]
for m, cha in enumerate(mat_channels_list):
escore_matrix = full_escore_matrix[1][m]
result = []
for j in range(len(escore_matrix[0])):
count = 1
for i in range(1, len(escore_matrix)):
if escore_matrix[i][j] != [-1, -1] and escore_matrix[i][j][1] == escore_matrix[i-1][j][1] and count < max_note_duration:
count += 1
else:
if count > 1:
result.append([i-count, count, j, escore_matrix[i-1][j]])
count = 1
if count > 1:
result.append([len(escore_matrix)-count, count, j, escore_matrix[-1][j]])
result.sort(key=lambda x: (x[0], -x[2]))
for r in result:
merged_escore_notes.append(['note', r[0], r[1], cha, r[2], r[3][0], r[3][1]])
return sorted(merged_escore_notes, key=lambda x: (x[1], -x[4], x[6]))
###################################################################################
def escore_matrix_to_original_escore_notes(full_escore_matrix):
merged_escore_notes = []
mat_channels_list = full_escore_matrix[0]
for m, cha in enumerate(mat_channels_list):
escore_matrix = full_escore_matrix[1][m]
result = []
for j in range(len(escore_matrix[0])):
count = 1
for i in range(1, len(escore_matrix)):
if escore_matrix[i][j] != [-1, -1] and escore_matrix[i][j] == escore_matrix[i-1][j]:
count += 1
else:
if count > 1:
result.append([i-count, count, j, escore_matrix[i-1][j]])
count = 1
if count > 1:
result.append([len(escore_matrix)-count, count, j, escore_matrix[-1][j]])
result.sort(key=lambda x: (x[0], -x[2]))
for r in result:
merged_escore_notes.append(['note', r[0], r[1], cha, r[2], r[3][0], r[3][1]])
return sorted(merged_escore_notes, key=lambda x: (x[1], -x[4], x[6]))
###################################################################################
def escore_notes_to_binary_matrix(escore_notes,
channel=0,
patch=0,
flip_matrix=False,
reverse_matrix=False
):
escore = [e for e in escore_notes if e[3] == channel and e[6] == patch]
if escore:
last_time = escore[-1][1]
last_notes = [e for e in escore if e[1] == last_time]
max_last_dur = max([e[2] for e in last_notes])
time_range = last_time+max_last_dur
escore_matrix = []
escore_matrix = [[0] * 128 for _ in range(time_range)]
for note in escore:
etype, time, duration, chan, pitch, velocity, pat = note
time = max(0, time)
duration = max(1, duration)
chan = max(0, min(15, chan))
pitch = max(0, min(127, pitch))
velocity = max(0, min(127, velocity))
pat = max(0, min(128, pat))
if channel == chan and patch == pat:
for t in range(time, min(time + duration, time_range)):
escore_matrix[t][pitch] = 1
if flip_matrix:
temp_matrix = []
for m in escore_matrix:
temp_matrix.append(m[::-1])
escore_matrix = temp_matrix
if reverse_matrix:
escore_matrix = escore_matrix[::-1]
return escore_matrix
else:
return None
###################################################################################
def binary_matrix_to_original_escore_notes(binary_matrix,
channel=0,
patch=0,
velocity=-1
):
result = []
for j in range(len(binary_matrix[0])):
count = 1
for i in range(1, len(binary_matrix)):
if binary_matrix[i][j] != 0 and binary_matrix[i][j] == binary_matrix[i-1][j]:
count += 1
else:
if count > 1:
result.append([i-count, count, j, binary_matrix[i-1][j]])
else:
if binary_matrix[i-1][j] != 0:
result.append([i-count, count, j, binary_matrix[i-1][j]])
count = 1
if count > 1:
result.append([len(binary_matrix)-count, count, j, binary_matrix[-1][j]])
else:
if binary_matrix[i-1][j] != 0:
result.append([i-count, count, j, binary_matrix[i-1][j]])
result.sort(key=lambda x: (x[0], -x[2]))
original_escore_notes = []
vel = velocity
for r in result:
if velocity == -1:
vel = max(40, r[2])
original_escore_notes.append(['note', r[0], r[1], channel, r[2], vel, patch])
return sorted(original_escore_notes, key=lambda x: (x[1], -x[4], x[6]))
###################################################################################
def escore_notes_averages(escore_notes,
times_index=1,
durs_index=2,
chans_index=3,
ptcs_index=4,
vels_index=5,
average_drums=False,
score_is_delta=False,
return_ptcs_and_vels=False
):
if score_is_delta:
if average_drums:
times = [e[times_index] for e in escore_notes if e[times_index] != 0]
else:
times = [e[times_index] for e in escore_notes if e[times_index] != 0 and e[chans_index] != 9]
else:
descore_notes = delta_score_notes(escore_notes)
if average_drums:
times = [e[times_index] for e in descore_notes if e[times_index] != 0]
else:
times = [e[times_index] for e in descore_notes if e[times_index] != 0 and e[chans_index] != 9]
if average_drums:
durs = [e[durs_index] for e in 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]
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)]
else:
return [sum(times) / len(times), sum(durs) / len(durs)]
###################################################################################
def adjust_escore_notes_timings(escore_notes,
adj_k=1,
times_index=1,
durs_index=2,
score_is_delta=False,
return_delta_scpre=False
):
if score_is_delta:
adj_escore_notes = copy.deepcopy(escore_notes)
else:
adj_escore_notes = delta_score_notes(escore_notes)
for e in adj_escore_notes:
if e[times_index] != 0:
e[times_index] = max(1, round(e[times_index] * adj_k))
e[durs_index] = max(1, round(e[durs_index] * adj_k))
if return_delta_scpre:
return adj_escore_notes
else:
return delta_score_to_abs_score(adj_escore_notes)
###################################################################################
def escore_notes_delta_times(escore_notes,
times_index=1
):
descore_notes = delta_score_notes(escore_notes)
return [e[times_index] for e in descore_notes]
###################################################################################
def escore_notes_durations(escore_notes,
durs_index=1
):
descore_notes = delta_score_notes(escore_notes)
return [e[durs_index] for e in descore_notes]
###################################################################################
def ordered_lists_match_ratio(src_list, trg_list):
zlist = list(zip(src_list, trg_list))
return sum([a == b for a, b in zlist]) / len(list(zlist))
###################################################################################
def lists_intersections(src_list, trg_list):
return list(set(src_list) & set(trg_list))
###################################################################################
def transpose_escore_notes(escore_notes,
transpose_value=0,
channel_index=3,
pitches_index=4
):
tr_escore_notes = copy.deepcopy(escore_notes)
for e in tr_escore_notes:
if e[channel_index] != 9:
e[pitches_index] = max(1, min(127, e[pitches_index] + transpose_value))
return tr_escore_notes
###################################################################################
def transpose_escore_notes_to_pitch(escore_notes,
target_pitch_value=60,
channel_index=3,
pitches_index=4
):
tr_escore_notes = copy.deepcopy(escore_notes)
transpose_delta = int(round(target_pitch_value)) - int(round(escore_notes_averages(escore_notes, return_ptcs_and_vels=True)[2]))
for e in tr_escore_notes:
if e[channel_index] != 9:
e[pitches_index] = max(1, min(127, e[pitches_index] + transpose_delta))
return tr_escore_notes
###################################################################################
CHORDS_TYPES = ['WHITE', 'BLACK', 'UNKNOWN', 'MIXED WHITE', 'MIXED BLACK', 'MIXED GRAY']
###################################################################################
def tones_chord_type(tones_chord,
return_chord_type_index=True,
use_filtered_chords=False,
use_full_chords=True
):
WN = WHITE_NOTES
BN = BLACK_NOTES
MX = WHITE_NOTES + BLACK_NOTES
if use_filtered_chords:
CHORDS = ALL_CHORDS_FILTERED
else:
CHORDS = ALL_CHORDS_SORTED
if use_full_chords:
CHORDS = ALL_CHORDS_FULL
tones_chord = sorted(tones_chord)
ctype = 'UNKNOWN'
if tones_chord in CHORDS:
if sorted(set(tones_chord) & set(WN)) == tones_chord:
ctype = 'WHITE'
elif sorted(set(tones_chord) & set(BN)) == tones_chord:
ctype = 'BLACK'
if len(tones_chord) > 1 and sorted(set(tones_chord) & set(MX)) == tones_chord:
if len(sorted(set(tones_chord) & set(WN))) == len(sorted(set(tones_chord) & set(BN))):
ctype = 'MIXED GRAY'
elif len(sorted(set(tones_chord) & set(WN))) > len(sorted(set(tones_chord) & set(BN))):
ctype = 'MIXED WHITE'
elif len(sorted(set(tones_chord) & set(WN))) < len(sorted(set(tones_chord) & set(BN))):
ctype = 'MIXED BLACK'
if return_chord_type_index:
return CHORDS_TYPES.index(ctype)
else:
return ctype
###################################################################################
def tone_type(tone,
return_tone_type_index=True
):
tone = tone % 12
if tone in BLACK_NOTES:
if return_tone_type_index:
return CHORDS_TYPES.index('BLACK')
else:
return "BLACK"
else:
if return_tone_type_index:
return CHORDS_TYPES.index('WHITE')
else:
return "WHITE"
###################################################################################
def lists_sym_differences(src_list, trg_list):
return list(set(src_list) ^ set(trg_list))
###################################################################################
def lists_differences(long_list, short_list):
return list(set(long_list) - set(short_list))
###################################################################################
def find_best_tones_chord(src_tones_chords,
trg_tones_chords,
find_longest=True
):
not_seen_trg_chords = []
max_len = 0
for tc in trg_tones_chords:
if sorted(tc) in src_tones_chords:
not_seen_trg_chords.append(sorted(tc))
max_len = max(max_len, len(tc))
if not not_seen_trg_chords:
max_len = len(max(trg_tones_chords, key=len))
not_seen_trg_chords = trg_tones_chords
if find_longest:
return random.choice([c for c in not_seen_trg_chords if len(c) == max_len])
else:
return random.choice(not_seen_trg_chords)
###################################################################################
def find_matching_tones_chords(tones_chord,
matching_chord_length=-1,
match_chord_type=True,
use_filtered_chords=True,
use_full_chords=True
):
if use_filtered_chords:
CHORDS = ALL_CHORDS_FILTERED
else:
CHORDS = ALL_CHORDS_SORTED
if use_full_chords:
CHORDS = ALL_CHORDS_FULL
tones_chord = sorted(tones_chord)
tclen = len(tones_chord)
tctype = tones_chord_type(tones_chord, use_filtered_chords=use_filtered_chords)
matches = []
for tc in CHORDS:
if matching_chord_length == -1:
if len(tc) > tclen:
if sorted(lists_intersections(tc, tones_chord)) == tones_chord:
if match_chord_type:
if tones_chord_type(tc, use_filtered_chords=use_filtered_chords) == tctype:
tcdiffs = lists_differences(tc, tones_chord)
if all(tone_type(d) == tctype % 3 for d in tcdiffs):
matches.append(tc)
else:
matches.append(tc)
else:
if len(tc) == max(tclen, matching_chord_length):
if sorted(lists_intersections(tc, tones_chord)) == tones_chord:
if match_chord_type:
if tones_chord_type(tc, use_filtered_chords=use_filtered_chords) == tctype:
tcdiffs = lists_differences(tc, tones_chord)
if all(tone_type(d) == tctype % 3 for d in tcdiffs):
matches.append(tc)
else:
matches.append(tc)
return sorted(matches, key=len)
###################################################################################
def adjust_list_of_values_to_target_average(list_of_values,
trg_avg,
min_value,
max_value
):
filtered_values = [value for value in list_of_values if min_value <= value <= max_value]
if not filtered_values:
return list_of_values
current_avg = sum(filtered_values) / len(filtered_values)
scale_factor = trg_avg / current_avg
adjusted_values = [value * scale_factor for value in filtered_values]
total_difference = trg_avg * len(filtered_values) - sum(adjusted_values)
adjustment_per_value = total_difference / len(filtered_values)
final_values = [value + adjustment_per_value for value in adjusted_values]
while abs(sum(final_values) / len(final_values) - trg_avg) > 1e-6:
total_difference = trg_avg * len(final_values) - sum(final_values)
adjustment_per_value = total_difference / len(final_values)
final_values = [value + adjustment_per_value for value in final_values]
final_values = [round(value) for value in final_values]
adjusted_values = copy.deepcopy(list_of_values)
j = 0
for i in range(len(adjusted_values)):
if min_value <= adjusted_values[i] <= max_value:
adjusted_values[i] = final_values[j]
j += 1
return adjusted_values
###################################################################################
def adjust_escore_notes_to_average(escore_notes,
trg_avg,
min_value=1,
max_value=4000,
times_index=1,
durs_index=2,
score_is_delta=False,
return_delta_scpre=False
):
if score_is_delta:
delta_escore_notes = copy.deepcopy(escore_notes)
else:
delta_escore_notes = delta_score_notes(escore_notes)
times = [[e[times_index], e[durs_index]] for e in delta_escore_notes]
filtered_values = [value for value in times if min_value <= value[0] <= max_value]
if not filtered_values:
return escore_notes
current_avg = sum([v[0] for v in filtered_values]) / len([v[0] for v in filtered_values])
scale_factor = trg_avg / current_avg
adjusted_values = [[value[0] * scale_factor, value[1] * scale_factor] for value in filtered_values]
total_difference = trg_avg * len([v[0] for v in filtered_values]) - sum([v[0] for v in adjusted_values])
adjustment_per_value = total_difference / len(filtered_values)
final_values = [[value[0] + adjustment_per_value, value[1] + adjustment_per_value] for value in adjusted_values]
while abs(sum([v[0] for v in final_values]) / len(final_values) - trg_avg) > 1e-6:
total_difference = trg_avg * len(final_values) - sum([v[0] for v in final_values])
adjustment_per_value = total_difference / len(final_values)
final_values = [[value[0] + adjustment_per_value, value[1] + adjustment_per_value] for value in final_values]
final_values = [[round(value[0]), round(value[1])] for value in final_values]
adjusted_delta_score = copy.deepcopy(delta_escore_notes)
j = 0
for i in range(len(adjusted_delta_score)):
if min_value <= adjusted_delta_score[i][1] <= max_value:
adjusted_delta_score[i][times_index] = final_values[j][0]
adjusted_delta_score[i][durs_index] = final_values[j][1]
j += 1
adjusted_escore_notes = delta_score_to_abs_score(adjusted_delta_score)
if return_delta_scpre:
return adjusted_delta_score
else:
return adjusted_escore_notes
###################################################################################
def harmonize_enhanced_melody_score_notes_to_ms_SONG(escore_notes,
melody_velocity=-1,
melody_channel=3,
melody_patch=40,
melody_base_octave=4,
harmonized_tones_chords_velocity=-1,
harmonized_tones_chords_channel=0,
harmonized_tones_chords_patch=0
):
harmonized_tones_chords = harmonize_enhanced_melody_score_notes(escore_notes)
harm_escore_notes = []
time = 0
for i, note in enumerate(escore_notes):
time = note[1]
dur = note[2]
ptc = note[4]
if melody_velocity == -1:
vel = int(110 + ((ptc % 12) * 1.5))
else:
vel = melody_velocity
harm_escore_notes.append(['note', time, dur, melody_channel, ptc, vel, melody_patch])
for t in harmonized_tones_chords[i]:
ptc = (melody_base_octave * 12) + t
if harmonized_tones_chords_velocity == -1:
vel = int(80 + ((ptc % 12) * 1.5))
else:
vel = harmonized_tones_chords_velocity
harm_escore_notes.append(['note', time, dur, harmonized_tones_chords_channel, ptc, vel, harmonized_tones_chords_patch])
return sorted(harm_escore_notes, key=lambda x: (x[1], -x[4], x[6]))
###################################################################################
def check_and_fix_pitches_chord(pitches_chord,
remove_duplicate_pitches=True,
use_filtered_chords=False,
use_full_chords=True,
fix_bad_pitches=False,
):
if remove_duplicate_pitches:
pitches_chord = sorted(set(pitches_chord), reverse=True)
else:
pitches_chord = sorted(pitches_chord, reverse=True)
if use_filtered_chords:
CHORDS = ALL_CHORDS_FILTERED
else:
CHORDS = ALL_CHORDS_SORTED
if use_full_chords:
CHORDS = ALL_CHORDS_FULL
chord = copy.deepcopy(pitches_chord)
tones_chord = sorted(set([t % 12 for t in chord]))
if tones_chord:
if tones_chord not in CHORDS:
if len(tones_chord) == 2:
tones_counts = Counter([p % 12 for p in pitches_chord]).most_common()
if tones_counts[0][1] > 1:
tones_chord = [tones_counts[0][0]]
elif tones_counts[1][1] > 1:
tones_chord = [tones_counts[1][0]]
else:
tones_chord = [pitches_chord[0] % 12]
else:
tones_chord_combs = [list(comb) for i in range(len(tones_chord)-1, 0, -1) for comb in combinations(tones_chord, i)]
for co in tones_chord_combs:
if co in CHORDS:
tones_chord = co
break
if len(tones_chord) == 1:
tones_chord = [pitches_chord[0] % 12]
chord.sort(reverse=True)
new_chord = set()
pipa = []
for e in chord:
if e % 12 in tones_chord:
new_chord.add(tuple([e]))
pipa.append(e)
elif (e+1) % 12 in tones_chord:
e += 1
new_chord.add(tuple([e]))
pipa.append(e)
elif (e-1) % 12 in tones_chord:
e -= 1
new_chord.add(tuple([e]))
pipa.append(e)
if fix_bad_pitches:
bad_chord = set()
for e in chord:
if e % 12 not in tones_chord:
bad_chord.add(tuple([e]))
elif (e+1) % 12 not in tones_chord:
bad_chord.add(tuple([e]))
elif (e-1) % 12 not in tones_chord:
bad_chord.add(tuple([e]))
for bc in bad_chord:
bc = list(bc)
tone = find_closest_tone(tones_chord, bc[0] % 12)
new_pitch = ((bc[0] // 12) * 12) + tone
if new_pitch not in pipa:
new_chord.add(tuple([new_pitch]))
pipa.append(new_pitch)
new_pitches_chord = [e[0] for e in new_chord]
return sorted(new_pitches_chord, reverse=True)
###################################################################################
ALL_CHORDS_TRANS = [[0], [0, 4], [0, 4, 7], [0, 4, 8], [0, 5], [0, 6], [0, 7], [0, 8], [1], [1, 5],
[1, 5, 9], [1, 6], [1, 7], [1, 8], [1, 9], [2], [2, 6], [2, 6, 10], [2, 7],
[2, 8], [2, 9], [2, 10], [3], [3, 7], [3, 7, 11], [3, 8], [3, 9], [3, 10],
[3, 11], [4], [4, 7], [4, 7, 11], [4, 8], [4, 9], [4, 10], [4, 11], [5],
[5, 9], [5, 10], [5, 11], [6], [6, 10], [6, 11], [7], [7, 11], [8], [9], [10],
[11]]
###################################################################################
def minkowski_distance(x, y, p=3, pad_value=float('inf')):
if len(x) != len(y):
return -1
distance = 0
for i in range(len(x)):
if x[i] == pad_value or y[i] == pad_value:
continue
distance += abs(x[i] - y[i]) ** p
return distance ** (1 / p)
###################################################################################
def dot_product(x, y, pad_value=None):
return sum(xi * yi for xi, yi in zip(x, y) if xi != pad_value and yi != pad_value)
def norm(vector, pad_value=None):
return sum(xi ** 2 for xi in vector if xi != pad_value) ** 0.5
def cosine_similarity(x, y, pad_value=None):
if len(x) != len(y):
return -1
dot_prod = dot_product(x, y, pad_value)
norm_x = norm(x, pad_value)
norm_y = norm(y, pad_value)
if norm_x == 0 or norm_y == 0:
return 0.0
return dot_prod / (norm_x * norm_y)
###################################################################################
def hamming_distance(arr1, arr2, pad_value):
return sum(el1 != el2 for el1, el2 in zip(arr1, arr2) if el1 != pad_value and el2 != pad_value)
###################################################################################
def jaccard_similarity(arr1, arr2, pad_value):
intersection = sum(el1 and el2 for el1, el2 in zip(arr1, arr2) if el1 != pad_value and el2 != pad_value)
union = sum((el1 or el2) for el1, el2 in zip(arr1, arr2) if el1 != pad_value or el2 != pad_value)
return intersection / union if union != 0 else 0
###################################################################################
def pearson_correlation(arr1, arr2, pad_value):
filtered_pairs = [(el1, el2) for el1, el2 in zip(arr1, arr2) if el1 != pad_value and el2 != pad_value]
if not filtered_pairs:
return 0
n = len(filtered_pairs)
sum1 = sum(el1 for el1, el2 in filtered_pairs)
sum2 = sum(el2 for el1, el2 in filtered_pairs)
sum1_sq = sum(el1 ** 2 for el1, el2 in filtered_pairs)
sum2_sq = sum(el2 ** 2 for el1, el2 in filtered_pairs)
p_sum = sum(el1 * el2 for el1, el2 in filtered_pairs)
num = p_sum - (sum1 * sum2 / n)
den = ((sum1_sq - sum1 ** 2 / n) * (sum2_sq - sum2 ** 2 / n)) ** 0.5
if den == 0:
return 0
return num / den
###################################################################################
def calculate_combined_distances(array_of_arrays,
combine_hamming_distance=True,
combine_jaccard_similarity=True,
combine_pearson_correlation=True,
pad_value=None
):
binary_arrays = array_of_arrays
binary_array_len = len(binary_arrays)
hamming_distances = [[0] * binary_array_len for _ in range(binary_array_len)]
jaccard_similarities = [[0] * binary_array_len for _ in range(binary_array_len)]
pearson_correlations = [[0] * binary_array_len for _ in range(binary_array_len)]
for i in range(binary_array_len):
for j in range(i + 1, binary_array_len):
hamming_distances[i][j] = hamming_distance(binary_arrays[i], binary_arrays[j], pad_value)
hamming_distances[j][i] = hamming_distances[i][j]
jaccard_similarities[i][j] = jaccard_similarity(binary_arrays[i], binary_arrays[j], pad_value)
jaccard_similarities[j][i] = jaccard_similarities[i][j]
pearson_correlations[i][j] = pearson_correlation(binary_arrays[i], binary_arrays[j], pad_value)
pearson_correlations[j][i] = pearson_correlations[i][j]
max_hamming = max(max(row) for row in hamming_distances)
min_hamming = min(min(row) for row in hamming_distances)
normalized_hamming = [[(val - min_hamming) / (max_hamming - min_hamming) for val in row] for row in hamming_distances]
max_jaccard = max(max(row) for row in jaccard_similarities)
min_jaccard = min(min(row) for row in jaccard_similarities)
normalized_jaccard = [[(val - min_jaccard) / (max_jaccard - min_jaccard) for val in row] for row in jaccard_similarities]
max_pearson = max(max(row) for row in pearson_correlations)
min_pearson = min(min(row) for row in pearson_correlations)
normalized_pearson = [[(val - min_pearson) / (max_pearson - min_pearson) for val in row] for row in pearson_correlations]
selected_metrics = 0
if combine_hamming_distance:
selected_metrics += normalized_hamming[i][j]
if combine_jaccard_similarity:
selected_metrics += (1 - normalized_jaccard[i][j])
if combine_pearson_correlation:
selected_metrics += (1 - normalized_pearson[i][j])
combined_metric = [[selected_metrics for i in range(binary_array_len)] for j in range(binary_array_len)]
return combined_metric
###################################################################################
def tones_chords_to_bits(tones_chords):
bits_tones_chords = []
for c in tones_chords:
c.sort()
bits = tones_chord_to_bits(c)
bits_tones_chords.append(bits)
return bits_tones_chords
###################################################################################
def tones_chords_to_ints(tones_chords):
ints_tones_chords = []
for c in tones_chords:
c.sort()
bits = tones_chord_to_bits(c)
number = bits_to_int(bits)
ints_tones_chords.append(number)
return ints_tones_chords
###################################################################################
def tones_chords_to_types(tones_chords,
return_chord_type_index=False
):
types_tones_chords = []
for c in tones_chords:
c.sort()
ctype = tones_chord_type(c, return_chord_type_index=return_chord_type_index)
types_tones_chords.append(ctype)
return types_tones_chords
###################################################################################
def morph_tones_chord(tones_chord,
trg_tone,
use_filtered_chords=True,
use_full_chords=True
):
src_tones_chord = sorted(sorted(set(tones_chord)) + [trg_tone])
combs = [list(comb) for i in range(len(src_tones_chord), 0, -1) for comb in combinations(src_tones_chord, i) if trg_tone in list(comb)]
matches = []
if use_filtered_chords:
CHORDS = ALL_CHORDS_FILTERED
else:
CHORDS = ALL_CHORDS_SORTED
if use_full_chords:
CHORDS = ALL_CHORDS_FULL
for c in combs:
if sorted(set(c)) in CHORDS:
matches.append(sorted(set(c)))
max_len = len(max(matches, key=len))
return random.choice([m for m in matches if len(m) == max_len])
###################################################################################
def compress_binary_matrix(binary_matrix,
only_compress_zeros=False,
return_compression_ratio=False
):
compressed_bmatrix = []
zm = [0] * len(binary_matrix[0])
pm = [0] * len(binary_matrix[0])
mcount = 0
for m in binary_matrix:
if only_compress_zeros:
if m != zm:
compressed_bmatrix.append(m)
mcount += 1
else:
if m != pm:
compressed_bmatrix.append(m)
mcount += 1
pm = m
if return_compression_ratio:
return [compressed_bmatrix, mcount / len(binary_matrix)]
else:
return compressed_bmatrix
###################################################################################
def solo_piano_escore_notes(escore_notes,
channels_index=3,
pitches_index=4,
patches_index=6,
keep_drums=False,
):
"""
A modified version of TMIDIX.solo_piano_escore_notes that preserves the
original MIDI channel of each note. This allows stereo panning information,
which is often channel-dependent, to be maintained during the conversion
to a solo piano performance.
"""
cscore = chordify_score([1000, escore_notes])
sp_escore_notes = []
for c in cscore:
# --- Use a set to store (pitch, channel) tuples for uniqueness ---
seen_notes = set()
chord = []
for cc in c:
if cc[channels_index] != 9: # If not a drum channel
# Create a unique identifier for each note using both pitch and channel
note_id = (cc[pitches_index], cc[channels_index])
# Check if this specific pitch-channel combination has been seen
if note_id not in seen_notes:
# The original function forced the channel to 0, destroying stereo separation.
# We comment out that line and ONLY change the instrument patch.
# cc[channels_index] = 0 <-- THIS LINE IS REMOVED
# Force the instrument patch to 0 (Acoustic Grand Piano)
cc[patches_index] = 0 # Set patch to Grand Piano
chord.append(cc)
seen_notes.add(note_id) # Add the unique ID to the set
else: # If it is a drum channel
if keep_drums:
# Apply the same logic for drums to be safe
drum_id = (cc[pitches_index] + 128, cc[channels_index])
if drum_id not in seen_notes:
chord.append(cc)
seen_notes.add(drum_id)
sp_escore_notes.append(chord)
return flatten(sp_escore_notes)
###################################################################################
def strip_drums_from_escore_notes(escore_notes,
channels_index=3
):
return [e for e in escore_notes if e[channels_index] != 9]
###################################################################################
def fixed_escore_notes_timings(escore_notes,
fixed_durations=False,
fixed_timings_multiplier=1,
custom_fixed_time=-1,
custom_fixed_dur=-1
):
fixed_timings_escore_notes = delta_score_notes(escore_notes, even_timings=True)
mode_time = round(Counter([e[1] for e in fixed_timings_escore_notes if e[1] != 0]).most_common()[0][0] * fixed_timings_multiplier)
if mode_time % 2 != 0:
mode_time += 1
mode_dur = round(Counter([e[2] for e in fixed_timings_escore_notes if e[2] != 0]).most_common()[0][0] * fixed_timings_multiplier)
if mode_dur % 2 != 0:
mode_dur += 1
for e in fixed_timings_escore_notes:
if e[1] != 0:
if custom_fixed_time > 0:
e[1] = custom_fixed_time
else:
e[1] = mode_time
if fixed_durations:
if custom_fixed_dur > 0:
e[2] = custom_fixed_dur
else:
e[2] = mode_dur
return delta_score_to_abs_score(fixed_timings_escore_notes)
###################################################################################
def cubic_kernel(x):
abs_x = abs(x)
if abs_x <= 1:
return 1.5 * abs_x**3 - 2.5 * abs_x**2 + 1
elif abs_x <= 2:
return -0.5 * abs_x**3 + 2.5 * abs_x**2 - 4 * abs_x + 2
else:
return 0
###################################################################################
def resize_matrix(matrix, new_height, new_width):
old_height = len(matrix)
old_width = len(matrix[0])
resized_matrix = [[0] * new_width for _ in range(new_height)]
for i in range(new_height):
for j in range(new_width):
old_i = i * old_height / new_height
old_j = j * old_width / new_width
value = 0
total_weight = 0
for m in range(-1, 3):
for n in range(-1, 3):
i_m = min(max(int(old_i) + m, 0), old_height - 1)
j_n = min(max(int(old_j) + n, 0), old_width - 1)
if matrix[i_m][j_n] == 0:
continue
weight = cubic_kernel(old_i - i_m) * cubic_kernel(old_j - j_n)
value += matrix[i_m][j_n] * weight
total_weight += weight
if total_weight > 0:
value /= total_weight
resized_matrix[i][j] = int(value > 0.5)
return resized_matrix
###################################################################################
def square_binary_matrix(binary_matrix,
matrix_size=128,
use_fast_squaring=False,
return_plot_points=False
):
if use_fast_squaring:
step = round(len(binary_matrix) / matrix_size)
samples = []
for i in range(0, len(binary_matrix), step):
samples.append(tuple([tuple(d) for d in binary_matrix[i:i+step]]))
resized_matrix = []
zmatrix = [[0] * matrix_size]
for s in samples:
samples_counts = Counter(s).most_common()
best_sample = tuple([0] * matrix_size)
pm = tuple(zmatrix[0])
for sc in samples_counts:
if sc[0] != tuple(zmatrix[0]) and sc[0] != pm:
best_sample = sc[0]
pm = sc[0]
break
pm = sc[0]
resized_matrix.append(list(best_sample))
resized_matrix = resized_matrix[:matrix_size]
resized_matrix += zmatrix * (matrix_size - len(resized_matrix))
else:
resized_matrix = resize_matrix(binary_matrix, matrix_size, matrix_size)
points = [(i, j) for i in range(matrix_size) for j in range(matrix_size) if resized_matrix[i][j] == 1]
if return_plot_points:
return [resized_matrix, points]
else:
return resized_matrix
###################################################################################
def mean(matrix):
return sum(sum(row) for row in matrix) / (len(matrix) * len(matrix[0]))
###################################################################################
def variance(matrix, mean_value):
return sum(sum((element - mean_value) ** 2 for element in row) for row in matrix) / (len(matrix) * len(matrix[0]))
###################################################################################
def covariance(matrix1, matrix2, mean1, mean2):
return sum(sum((matrix1[i][j] - mean1) * (matrix2[i][j] - mean2) for j in range(len(matrix1[0]))) for i in range(len(matrix1))) / (len(matrix1) * len(matrix1[0]))
###################################################################################
def ssim_index(matrix1, matrix2, bit_depth=1):
if len(matrix1) != len(matrix2) and len(matrix1[0]) != len(matrix2[0]):
return -1
K1, K2 = 0.01, 0.03
L = bit_depth
C1 = (K1 * L) ** 2
C2 = (K2 * L) ** 2
mu1 = mean(matrix1)
mu2 = mean(matrix2)
sigma1_sq = variance(matrix1, mu1)
sigma2_sq = variance(matrix2, mu2)
sigma12 = covariance(matrix1, matrix2, mu1, mu2)
ssim = ((2 * mu1 * mu2 + C1) * (2 * sigma12 + C2)) / ((mu1 ** 2 + mu2 ** 2 + C1) * (sigma1_sq + sigma2_sq + C2))
return ssim
###################################################################################
def find_most_similar_matrix(array_of_matrices,
trg_matrix,
matrices_bit_depth=1,
return_most_similar_index=False
):
max_ssim = -float('inf')
most_similar_index = -1
for i, matrix in enumerate(array_of_matrices):
ssim = ssim_index(matrix, trg_matrix, bit_depth=matrices_bit_depth)
if ssim > max_ssim:
max_ssim = ssim
most_similar_index = i
if return_most_similar_index:
return most_similar_index
else:
return array_of_matrices[most_similar_index]
###################################################################################
def chord_to_pchord(chord):
pchord = []
for cc in chord:
if cc[3] != 9:
pchord.append(cc[4])
return pchord
###################################################################################
def summarize_escore_notes(escore_notes,
summary_length_in_chords=128,
preserve_timings=True,
preserve_durations=False,
time_threshold=12,
min_sum_chord_len=2,
use_tones_chords=True
):
cscore = chordify_score([d[1:] for d in delta_score_notes(escore_notes)])
summary_length_in_chords = min(len(cscore), summary_length_in_chords)
ltthresh = time_threshold // 2
uttresh = time_threshold * 2
mc_time = Counter([c[0][0] for c in cscore if c[0][2] != 9 and ltthresh < c[0][0] < uttresh]).most_common()[0][0]
pchords = []
for c in cscore:
if use_tones_chords:
pchords.append([c[0][0]] + pitches_to_tones_chord(chord_to_pchord(c)))
else:
pchords.append([c[0][0]] + chord_to_pchord(c))
step = round(len(pchords) / summary_length_in_chords)
samples = []
for i in range(0, len(pchords), step):
samples.append(tuple([tuple(d) for d in pchords[i:i+step]]))
summarized_escore_notes = []
for i, s in enumerate(samples):
best_chord = list([v[0] for v in Counter(s).most_common() if v[0][0] == mc_time and len(v[0]) > min_sum_chord_len])
if not best_chord:
best_chord = list([v[0] for v in Counter(s).most_common() if len(v[0]) > min_sum_chord_len])
if not best_chord:
best_chord = list([Counter(s).most_common()[0][0]])
chord = copy.deepcopy(cscore[[ss for ss in s].index(best_chord[0])+(i*step)])
if preserve_timings:
if not preserve_durations:
if i > 0:
pchord = summarized_escore_notes[-1]
for pc in pchord:
pc[1] = min(pc[1], chord[0][0])
else:
chord[0][0] = 1
for c in chord:
c[1] = 1
summarized_escore_notes.append(chord)
summarized_escore_notes = summarized_escore_notes[:summary_length_in_chords]
return [['note'] + d for d in delta_score_to_abs_score(flatten(summarized_escore_notes), times_idx=0)]
###################################################################################
def compress_patches_in_escore_notes(escore_notes,
num_patches=4,
group_patches=False
):
if num_patches > 4:
n_patches = 4
elif num_patches < 1:
n_patches = 1
else:
n_patches = num_patches
if group_patches:
patches_set = sorted(set([e[6] for e in escore_notes]))
trg_patch_list = []
seen = []
for p in patches_set:
if p // 8 not in seen:
trg_patch_list.append(p)
seen.append(p // 8)
trg_patch_list = sorted(trg_patch_list)
else:
trg_patch_list = sorted(set([e[6] for e in escore_notes]))
if 128 in trg_patch_list and n_patches > 1:
trg_patch_list = trg_patch_list[:n_patches-1] + [128]
else:
trg_patch_list = trg_patch_list[:n_patches]
new_escore_notes = []
for e in escore_notes:
if e[6] in trg_patch_list:
new_escore_notes.append(e)
return new_escore_notes
###################################################################################
def compress_patches_in_escore_notes_chords(escore_notes,
max_num_patches_per_chord=4,
group_patches=True,
root_grouped_patches=False
):
if max_num_patches_per_chord > 4:
n_patches = 4
elif max_num_patches_per_chord < 1:
n_patches = 1
else:
n_patches = max_num_patches_per_chord
cscore = chordify_score([1000, sorted(escore_notes, key=lambda x: (x[1], x[6]))])
new_escore_notes = []
for c in cscore:
if group_patches:
patches_set = sorted(set([e[6] for e in c]))
trg_patch_list = []
seen = []
for p in patches_set:
if p // 8 not in seen:
trg_patch_list.append(p)
seen.append(p // 8)
trg_patch_list = sorted(trg_patch_list)
else:
trg_patch_list = sorted(set([e[6] for e in c]))
if 128 in trg_patch_list and n_patches > 1:
trg_patch_list = trg_patch_list[:n_patches-1] + [128]
else:
trg_patch_list = trg_patch_list[:n_patches]
for ccc in c:
cc = copy.deepcopy(ccc)
if group_patches:
if cc[6] // 8 in [t // 8 for t in trg_patch_list]:
if root_grouped_patches:
cc[6] = (cc[6] // 8) * 8
new_escore_notes.append(cc)
else:
if cc[6] in trg_patch_list:
new_escore_notes.append(cc)
return new_escore_notes
###################################################################################
def escore_notes_to_image_matrix(escore_notes,
num_img_channels=3,
filter_out_zero_rows=False,
filter_out_duplicate_rows=False,
flip_matrix=False,
reverse_matrix=False
):
escore_notes = sorted(escore_notes, key=lambda x: (x[1], x[6]))
if num_img_channels > 1:
n_mat_channels = 3
else:
n_mat_channels = 1
if escore_notes:
last_time = escore_notes[-1][1]
last_notes = [e for e in escore_notes if e[1] == last_time]
max_last_dur = max([e[2] for e in last_notes])
time_range = last_time+max_last_dur
escore_matrix = []
escore_matrix = [[0] * 128 for _ in range(time_range)]
for note in escore_notes:
etype, time, duration, chan, pitch, velocity, pat = note
time = max(0, time)
duration = max(2, duration)
chan = max(0, min(15, chan))
pitch = max(0, min(127, pitch))
velocity = max(0, min(127, velocity))
patch = max(0, min(128, pat))
if chan != 9:
pat = patch + 128
else:
pat = 127
seen_pats = []
for t in range(time, min(time + duration, time_range)):
mat_value = escore_matrix[t][pitch]
mat_value_0 = (mat_value // (256 * 256)) % 256
mat_value_1 = (mat_value // 256) % 256
cur_num_chans = 0
if 0 < mat_value < 256 and pat not in seen_pats:
cur_num_chans = 1
elif 256 < mat_value < (256 * 256) and pat not in seen_pats:
cur_num_chans = 2
if cur_num_chans < n_mat_channels:
if n_mat_channels == 1:
escore_matrix[t][pitch] = pat
seen_pats.append(pat)
elif n_mat_channels == 3:
if cur_num_chans == 0:
escore_matrix[t][pitch] = pat
seen_pats.append(pat)
elif cur_num_chans == 1:
escore_matrix[t][pitch] = (256 * 256 * mat_value_0) + (256 * pat)
seen_pats.append(pat)
elif cur_num_chans == 2:
escore_matrix[t][pitch] = (256 * 256 * mat_value_0) + (256 * mat_value_1) + pat
seen_pats.append(pat)
if filter_out_zero_rows:
escore_matrix = [e for e in escore_matrix if sum(e) != 0]
if filter_out_duplicate_rows:
dd_escore_matrix = []
pr = [-1] * 128
for e in escore_matrix:
if e != pr:
dd_escore_matrix.append(e)
pr = e
escore_matrix = dd_escore_matrix
if flip_matrix:
temp_matrix = []
for m in escore_matrix:
temp_matrix.append(m[::-1])
escore_matrix = temp_matrix
if reverse_matrix:
escore_matrix = escore_matrix[::-1]
return escore_matrix
else:
return None
###################################################################################
def find_value_power(value, number):
return math.floor(math.log(value, number))
###################################################################################
def image_matrix_to_original_escore_notes(image_matrix,
velocity=-1
):
result = []
for j in range(len(image_matrix[0])):
count = 1
for i in range(1, len(image_matrix)):
if image_matrix[i][j] != 0 and image_matrix[i][j] == image_matrix[i-1][j]:
count += 1
else:
if count > 1:
result.append([i-count, count, j, image_matrix[i-1][j]])
else:
if image_matrix[i-1][j] != 0:
result.append([i-count, count, j, image_matrix[i-1][j]])
count = 1
if count > 1:
result.append([len(image_matrix)-count, count, j, image_matrix[-1][j]])
else:
if image_matrix[i-1][j] != 0:
result.append([i-count, count, j, image_matrix[i-1][j]])
result.sort(key=lambda x: (x[0], -x[2]))
original_escore_notes = []
vel = velocity
for r in result:
if velocity == -1:
vel = max(40, r[2])
ptc0 = 0
ptc1 = 0
ptc2 = 0
if find_value_power(r[3], 256) == 0:
ptc0 = r[3] % 256
elif find_value_power(r[3], 256) == 1:
ptc0 = r[3] // 256
ptc1 = (r[3] // 256) % 256
elif find_value_power(r[3], 256) == 2:
ptc0 = (r[3] // 256) // 256
ptc1 = (r[3] // 256) % 256
ptc2 = r[3] % 256
ptcs = [ptc0, ptc1, ptc2]
patches = [p for p in ptcs if p != 0]
for i, p in enumerate(patches):
if p < 128:
patch = 128
channel = 9
else:
patch = p % 128
chan = p // 8
if chan == 9:
chan += 1
channel = min(15, chan)
original_escore_notes.append(['note', r[0], r[1], channel, r[2], vel, patch])
output_score = sorted(original_escore_notes, key=lambda x: (x[1], -x[4], x[6]))
adjust_score_velocities(output_score, 127)
return output_score
###################################################################################
def escore_notes_delta_times(escore_notes,
timings_index=1,
channels_index=3,
omit_zeros=False,
omit_drums=False
):
if omit_drums:
score = [e for e in escore_notes if e[channels_index] != 9]
dtimes = [score[0][timings_index]] + [b[timings_index]-a[timings_index] for a, b in zip(score[:-1], score[1:])]
else:
dtimes = [escore_notes[0][timings_index]] + [b[timings_index]-a[timings_index] for a, b in zip(escore_notes[:-1], escore_notes[1:])]
if omit_zeros:
dtimes = [d for d in dtimes if d != 0]
return dtimes
###################################################################################
def monophonic_check(escore_notes, times_index=1):
return len(escore_notes) == len(set([e[times_index] for e in escore_notes]))
###################################################################################
def count_escore_notes_patches(escore_notes, patches_index=6):
return [list(c) for c in Counter([e[patches_index] for e in escore_notes]).most_common()]
###################################################################################
def escore_notes_medley(list_of_escore_notes,
list_of_labels=None,
pause_time_value=255
):
if list_of_labels is not None:
labels = [str(l) for l in list_of_labels] + ['No label'] * (len(list_of_escore_notes)-len(list_of_labels))
medley = []
time = 0
for i, m in enumerate(list_of_escore_notes):
if list_of_labels is not None:
medley.append(['text_event', time, labels[i]])
pe = m[0]
for mm in m:
time += mm[1] - pe[1]
mmm = copy.deepcopy(mm)
mmm[1] = time
medley.append(mmm)
pe = mm
time += pause_time_value
return medley
###################################################################################
def proportions_counter(list_of_values):
counts = Counter(list_of_values).most_common()
clen = sum([c[1] for c in counts])
return [[c[0], c[1], c[1] / clen] for c in counts]
###################################################################################
def smooth_escore_notes(escore_notes):
values = [e[4] % 24 for e in escore_notes]
smoothed = [values[0]]
for i in range(1, len(values)):
if abs(smoothed[-1] - values[i]) >= 12:
if smoothed[-1] < values[i]:
smoothed.append(values[i] - 12)
else:
smoothed.append(values[i] + 12)
else:
smoothed.append(values[i])
smoothed_score = copy.deepcopy(escore_notes)
for i, e in enumerate(smoothed_score):
esn_octave = escore_notes[i][4] // 12
e[4] = (esn_octave * 12) + smoothed[i]
return smoothed_score
###################################################################################
def add_base_to_escore_notes(escore_notes,
base_octave=2,
base_channel=2,
base_patch=35,
base_max_velocity=120,
return_base=False
):
score = copy.deepcopy(escore_notes)
cscore = chordify_score([1000, score])
base_score = []
for c in cscore:
chord = sorted([e for e in c if e[3] != 9], key=lambda x: x[4], reverse=True)
base_score.append(chord[-1])
base_score = smooth_escore_notes(base_score)
for e in base_score:
e[3] = base_channel
e[4] = (base_octave * 12) + (e[4] % 12)
e[5] = e[4]
e[6] = base_patch
adjust_score_velocities(base_score, base_max_velocity)
if return_base:
final_score = sorted(base_score, key=lambda x: (x[1], -x[4], x[6]))
else:
final_score = sorted(escore_notes + base_score, key=lambda x: (x[1], -x[4], x[6]))
return final_score
###################################################################################
def add_drums_to_escore_notes(escore_notes,
heavy_drums_pitches=[36, 38, 47],
heavy_drums_velocity=110,
light_drums_pitches=[51, 54],
light_drums_velocity=127,
drums_max_velocity=127,
drums_ratio_time_divider=4,
return_drums=False
):
score = copy.deepcopy([e for e in escore_notes if e[3] != 9])
cscore = chordify_score([1000, score])
drums_score = []
for c in cscore:
min_dur = max(1, min([e[2] for e in c]))
if not (c[0][1] % drums_ratio_time_divider):
drum_note = ['note', c[0][1], min_dur, 9, heavy_drums_pitches[c[0][4] % len(heavy_drums_pitches)], heavy_drums_velocity, 128]
else:
drum_note = ['note', c[0][1], min_dur, 9, light_drums_pitches[c[0][4] % len(light_drums_pitches)], light_drums_velocity, 128]
drums_score.append(drum_note)
adjust_score_velocities(drums_score, drums_max_velocity)
if return_drums:
final_score = sorted(drums_score, key=lambda x: (x[1], -x[4], x[6]))
else:
final_score = sorted(score + drums_score, key=lambda x: (x[1], -x[4], x[6]))
return final_score
###################################################################################
def find_pattern_start_indexes(values, pattern):
start_indexes = []
count = 0
for i in range(len(values)- len(pattern)):
chunk = values[i:i+len(pattern)]
if chunk == pattern:
start_indexes.append(i)
return start_indexes
###################################################################################
def escore_notes_lrno_pattern(escore_notes, mode='chords'):
cscore = chordify_score([1000, escore_notes])
checked_cscore = advanced_check_and_fix_chords_in_chordified_score(cscore)
chords_toks = []
chords_idxs = []
for i, c in enumerate(checked_cscore[0]):
pitches = sorted([p[4] for p in c if p[3] != 9], reverse=True)
tchord = pitches_to_tones_chord(pitches)
if tchord:
if mode == 'chords':
token = ALL_CHORDS_FULL.index(tchord)
elif mode == 'high pitches':
token = pitches[0]
elif mode == 'high pitches tones':
token = pitches[0] % 12
else:
token = ALL_CHORDS_FULL.index(tchord)
chords_toks.append(token)
chords_idxs.append(i)
lrno_pats = find_lrno_patterns(chords_toks)
if lrno_pats:
lrno_pattern = list(lrno_pats[0][2])
start_idx = chords_idxs[find_pattern_start_indexes(chords_toks, lrno_pattern)[0]]
end_idx = chords_idxs[start_idx + len(lrno_pattern)]
return recalculate_score_timings(flatten(cscore[start_idx:end_idx]))
else:
return None
###################################################################################
def chordified_score_pitches(chordified_score,
mode='dominant',
return_tones=False,
omit_drums=True,
score_patch=-1,
channels_index=3,
pitches_index=4,
patches_index=6
):
results = []
for c in chordified_score:
if -1 < score_patch < 128:
ptcs = sorted([e[pitches_index] for e in c if e[channels_index] != 9 and e[patches_index] == score_patch], reverse=True)
else:
ptcs = sorted([e[pitches_index] for e in c if e[channels_index] != 9], reverse=True)
if ptcs:
if mode == 'dominant':
mtone = statistics.mode([p % 12 for p in ptcs])
if return_tones:
results.append(mtone)
else:
results.append(sorted(set([p for p in ptcs if p % 12 == mtone]), reverse=True))
elif mode == 'high':
if return_tones:
results.append(ptcs[0] % 12)
else:
results.append([ptcs[0]])
elif mode == 'base':
if return_tones:
results.append(ptcs[-1] % 12)
else:
results.append([ptcs[-1]])
elif mode == 'average':
if return_tones:
results.append(statistics.mean(ptcs) % 12)
else:
results.append([statistics.mean(ptcs)])
else:
mtone = statistics.mode([p % 12 for p in ptcs])
if return_tones:
results.append(mtone)
else:
results.append(sorted(set([p for p in ptcs if p % 12 == mtone]), reverse=True))
else:
if not omit_drums:
if return_tones:
results.append(-1)
else:
results.append([-1])
return results
###################################################################################
def escore_notes_times_tones(escore_notes,
tones_mode='dominant',
return_abs_times=True,
omit_drums=False
):
cscore = chordify_score([1000, escore_notes])
tones = chordified_score_pitches(cscore, return_tones=True, mode=tones_mode, omit_drums=omit_drums)
if return_abs_times:
times = sorted([c[0][1] for c in cscore])
else:
times = escore_notes_delta_times(escore_notes, omit_zeros=True, omit_drums=omit_drums)
if len(times) != len(tones):
times = [0] + times
return [[t, to] for t, to in zip(times, tones)]
###################################################################################
def escore_notes_middle(escore_notes,
length=10,
use_chords=True
):
if use_chords:
score = chordify_score([1000, escore_notes])
else:
score = escore_notes
middle_idx = len(score) // 2
slen = min(len(score) // 2, length // 2)
start_idx = middle_idx - slen
end_idx = middle_idx + slen
if use_chords:
return flatten(score[start_idx:end_idx])
else:
return score[start_idx:end_idx]
###################################################################################
ALL_CHORDS_FULL = [[0], [0, 3], [0, 3, 5], [0, 3, 5, 8], [0, 3, 5, 9], [0, 3, 5, 10], [0, 3, 6],
[0, 3, 6, 9], [0, 3, 6, 10], [0, 3, 7], [0, 3, 7, 10], [0, 3, 8], [0, 3, 9],
[0, 3, 10], [0, 4], [0, 4, 6], [0, 4, 6, 9], [0, 4, 6, 10], [0, 4, 7],
[0, 4, 7, 10], [0, 4, 8], [0, 4, 9], [0, 4, 10], [0, 5], [0, 5, 8], [0, 5, 9],
[0, 5, 10], [0, 6], [0, 6, 9], [0, 6, 10], [0, 7], [0, 7, 10], [0, 8], [0, 9],
[0, 10], [1], [1, 4], [1, 4, 6], [1, 4, 6, 9], [1, 4, 6, 10], [1, 4, 6, 11],
[1, 4, 7], [1, 4, 7, 10], [1, 4, 7, 11], [1, 4, 8], [1, 4, 8, 11], [1, 4, 9],
[1, 4, 10], [1, 4, 11], [1, 5], [1, 5, 8], [1, 5, 8, 11], [1, 5, 9],
[1, 5, 10], [1, 5, 11], [1, 6], [1, 6, 9], [1, 6, 10], [1, 6, 11], [1, 7],
[1, 7, 10], [1, 7, 11], [1, 8], [1, 8, 11], [1, 9], [1, 10], [1, 11], [2],
[2, 5], [2, 5, 8], [2, 5, 8, 11], [2, 5, 9], [2, 5, 10], [2, 5, 11], [2, 6],
[2, 6, 9], [2, 6, 10], [2, 6, 11], [2, 7], [2, 7, 10], [2, 7, 11], [2, 8],
[2, 8, 11], [2, 9], [2, 10], [2, 11], [3], [3, 5], [3, 5, 8], [3, 5, 8, 11],
[3, 5, 9], [3, 5, 10], [3, 5, 11], [3, 6], [3, 6, 9], [3, 6, 10], [3, 6, 11],
[3, 7], [3, 7, 10], [3, 7, 11], [3, 8], [3, 8, 11], [3, 9], [3, 10], [3, 11],
[4], [4, 6], [4, 6, 9], [4, 6, 10], [4, 6, 11], [4, 7], [4, 7, 10], [4, 7, 11],
[4, 8], [4, 8, 11], [4, 9], [4, 10], [4, 11], [5], [5, 8], [5, 8, 11], [5, 9],
[5, 10], [5, 11], [6], [6, 9], [6, 10], [6, 11], [7], [7, 10], [7, 11], [8],
[8, 11], [9], [10], [11]]
###################################################################################
def escore_notes_to_parsons_code(escore_notes,
times_index=1,
pitches_index=4,
return_as_list=False
):
parsons = "*"
parsons_list = []
prev = ['note', -1, -1, -1, -1, -1, -1]
for e in escore_notes:
if e[times_index] != prev[times_index]:
if e[pitches_index] > prev[pitches_index]:
parsons += "U"
parsons_list.append(1)
elif e[pitches_index] < prev[pitches_index]:
parsons += "D"
parsons_list.append(-1)
elif e[pitches_index] == prev[pitches_index]:
parsons += "R"
parsons_list.append(0)
prev = e
if return_as_list:
return parsons_list
else:
return parsons
###################################################################################
def all_consequtive(list_of_values):
return all(b > a for a, b in zip(list_of_values[:-1], list_of_values[1:]))
###################################################################################
def escore_notes_patches(escore_notes, patches_index=6):
return sorted(set([e[patches_index] for e in escore_notes]))
###################################################################################
def build_suffix_array(lst):
n = len(lst)
suffixes = [(lst[i:], i) for i in range(n)]
suffixes.sort()
suffix_array = [suffix[1] for suffix in suffixes]
return suffix_array
###################################################################################
def build_lcp_array(lst, suffix_array):
n = len(lst)
rank = [0] * n
lcp = [0] * n
for i, suffix in enumerate(suffix_array):
rank[suffix] = i
h = 0
for i in range(n):
if rank[i] > 0:
j = suffix_array[rank[i] - 1]
while i + h < n and j + h < n and lst[i + h] == lst[j + h]:
h += 1
lcp[rank[i]] = h
if h > 0:
h -= 1
return lcp
###################################################################################
def find_lrno_pattern_fast(lst):
n = len(lst)
if n == 0:
return []
suffix_array = build_suffix_array(lst)
lcp_array = build_lcp_array(lst, suffix_array)
max_len = 0
start_index = 0
for i in range(1, n):
if lcp_array[i] > max_len:
if suffix_array[i] + lcp_array[i] <= suffix_array[i - 1] or suffix_array[i - 1] + lcp_array[i - 1] <= suffix_array[i]:
max_len = lcp_array[i]
start_index = suffix_array[i]
return lst[start_index:start_index + max_len]
###################################################################################
def find_chunk_indexes(original_list, chunk, ignore_index=-1):
chunk_length = len(chunk)
for i in range(len(original_list) - chunk_length + 1):
chunk_index = 0
start_index = ignore_index
for j in range(i, len(original_list)):
if original_list[j] == chunk[chunk_index]:
if start_index == ignore_index:
start_index = j
chunk_index += 1
if chunk_index == chunk_length:
return [start_index, j]
elif original_list[j] != ignore_index:
break
return None
###################################################################################
def escore_notes_lrno_pattern_fast(escore_notes,
channels_index=3,
pitches_index=4,
zero_start_time=True
):
cscore = chordify_score([1000, escore_notes])
score_chords = []
for c in cscore:
tchord = sorted(set([e[pitches_index] % 12 for e in c if e[channels_index] != 9]))
chord_tok = -1
if tchord:
if tchord not in ALL_CHORDS_FULL:
tchord = check_and_fix_tones_chord(tchord)
chord_tok = ALL_CHORDS_FULL.index(tchord)
score_chords.append(chord_tok)
schords = [c for c in score_chords if c != -1]
lrno = find_lrno_pattern_fast(schords)
if lrno:
sidx, eidx = find_chunk_indexes(score_chords, lrno)
escore_notes_lrno_pattern = flatten(cscore[sidx:eidx+1])
if escore_notes_lrno_pattern is not None:
if zero_start_time:
return recalculate_score_timings(escore_notes_lrno_pattern)
else:
return escore_notes_lrno_pattern
else:
return None
else:
return None
###################################################################################
def escore_notes_durations_counter(escore_notes,
min_duration=0,
durations_index=2,
channels_index=3
):
escore = [e for e in escore_notes if e[channels_index] != 9]
durs = [e[durations_index] for e in escore if e[durations_index] >= min_duration]
zero_durs = sum([1 for e in escore if e[durations_index] == 0])
return [len(durs), len(escore), zero_durs, Counter(durs).most_common()]
###################################################################################
def count_bad_chords_in_chordified_score(chordified_score,
pitches_index=4,
patches_index=6,
max_patch=127,
use_full_chords=False
):
if use_full_chords:
CHORDS = ALL_CHORDS_FULL
else:
CHORDS = ALL_CHORDS_SORTED
bad_chords_count = 0
for c in chordified_score:
cpitches = [e[pitches_index] for e in c if e[patches_index] <= max_patch]
tones_chord = sorted(set([p % 12 for p in cpitches]))
if tones_chord:
if tones_chord not in CHORDS:
bad_chords_count += 1
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'
]
###################################################################################
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)[:16]
instruments = [alpha_str(MIDI.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(MIDI.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(MIDI.Notenum2percussion[p[0]]) for p in Counter(drums_pitches).most_common(3) if p[0] in MIDI.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([MIDI.Number2patch[mel[0]], mel[1]])
elif mel[0] in BASE_INSTRUMENTS and escore_avgs[3] <= 60:
base_melodies.append([MIDI.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 '
if len(instruments) > 1:
description += 'features ' + NUMERALS[max(0, min(15, len(instruments)-1))] + ' instruments: '
description += ', '.join(instruments[:-1]) + ' and ' + instruments[-1] + '.'
else:
description += 'features one instrument: ' + instruments[0] + '.'
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([l[0] for l in lead_melodies[:-1]]) + ' 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([b[0] for b in base_melodies[:-1]]) + ' 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([l[0] for l in lead_melodies[:-1]]) + ' 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([b[0] for b in base_melodies[:-1]]) + ' 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
###################################################################################
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, 46, # 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
###################################################################################
def escore_notes_core(escore_notes, core_len=128):
cscore = chordify_score([1000, escore_notes])
chords = []
chords_idxs = []
for i, c in enumerate(cscore):
pitches = [e[4] for e in c if e[3] != 9]
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)
chords.append(ALL_CHORDS_SORTED.index(tones_chord))
chords_idxs.append(i)
mid = len(chords_idxs) // 2
clen = core_len // 2
sidx = chords_idxs[mid-clen]
eidx = chords_idxs[mid+clen]
core_chords = chords[mid-clen:mid+clen]
core_score = flatten(cscore[sidx:eidx])
return core_score, core_chords
###################################################################################
def multiprocessing_wrapper(function, data_list, verbose=True):
with multiprocessing.Pool() as pool:
results = []
for result in tqdm.tqdm(pool.imap_unordered(function, data_list),
total=len(data_list),
disable=not verbose
):
results.append(result)
return results
###################################################################################
def rle_encode_ones(matrix, div_mod=-1):
flat_list = [val for row in matrix for val in row]
encoding = []
i = 0
while i < len(flat_list):
if flat_list[i] == 1:
start_index = i
count = 1
i += 1
while i < len(flat_list) and flat_list[i] == 1:
count += 1
i += 1
if div_mod > 0:
encoding.append((start_index // div_mod, start_index % div_mod))
else:
encoding.append(start_index)
else:
i += 1
return encoding
###################################################################################
def rle_decode_ones(encoding, size=(128, 128)):
flat_list = [0] * (size[0] * size[1])
for start_index in encoding:
flat_list[start_index] = 1
matrix = [flat_list[i * size[1]:(i + 1) * size[1]] for i in range(size[0])]
return matrix
###################################################################################
def vertical_list_search(list_of_lists, trg_list):
src_list = list_of_lists
if not src_list or not trg_list:
return []
num_rows = len(src_list)
k = len(trg_list)
row_sets = [set(row) for row in src_list]
results = []
for start in range(num_rows - k + 1):
valid = True
for offset, target in enumerate(trg_list):
if target not in row_sets[start + offset]:
valid = False
break
if valid:
results.append(list(range(start, start + k)))
return results
###################################################################################
def smooth_values(values, window_size=3):
smoothed = []
for i in range(len(values)):
start = max(0, i - window_size // 2)
end = min(len(values), i + window_size // 2 + 1)
window = values[start:end]
smoothed.append(int(sum(window) / len(window)))
return smoothed
###################################################################################
def is_mostly_wide_peaks_and_valleys(values,
min_range=32,
threshold=0.7,
smoothing_window=5
):
if not values:
return False
smoothed_values = smooth_values(values, smoothing_window)
value_range = max(smoothed_values) - min(smoothed_values)
if value_range < min_range:
return False
if all(v == smoothed_values[0] for v in smoothed_values):
return False
trend_types = []
for i in range(1, len(smoothed_values)):
if smoothed_values[i] > smoothed_values[i - 1]:
trend_types.append(1)
elif smoothed_values[i] < smoothed_values[i - 1]:
trend_types.append(-1)
else:
trend_types.append(0)
trend_count = trend_types.count(1) + trend_types.count(-1)
proportion = trend_count / len(trend_types)
return proportion >= threshold
###################################################################################
def system_memory_utilization(return_dict=False):
if return_dict:
return dict(psutil.virtual_memory()._asdict())
else:
print('RAM memory % used:', psutil.virtual_memory()[2])
print('RAM Used (GB):', psutil.virtual_memory()[3]/(1024**3))
###################################################################################
def system_cpus_utilization(return_dict=False):
if return_dict:
return {'num_cpus': psutil.cpu_count(),
'cpus_util': psutil.cpu_percent()
}
else:
print('Number of CPUs:', psutil.cpu_count())
print('CPUs utilization:', psutil.cpu_percent())
###################################################################################
def create_files_list(datasets_paths=['./'],
files_exts=['.mid', '.midi', '.kar', '.MID', '.MIDI', '.KAR'],
max_num_files_per_dir=-1,
randomize_dir_files=False,
max_total_files=-1,
randomize_files_list=True,
check_for_dupes=False,
use_md5_hashes=False,
return_dupes=False,
verbose=True
):
if verbose:
print('=' * 70)
print('Searching for files...')
print('This may take a while on a large dataset in particular...')
print('=' * 70)
files_exts = tuple(files_exts)
filez_set = defaultdict(None)
dupes_list = []
for dataset_addr in datasets_paths:
print('=' * 70)
print('Processing', dataset_addr)
print('=' * 70)
for dirpath, dirnames, filenames in tqdm.tqdm(os.walk(dataset_addr), disable=not verbose):
if randomize_dir_files:
random.shuffle(filenames)
if max_num_files_per_dir > 0:
max_num_files = max_num_files_per_dir
else:
max_num_files = len(filenames)
for file in filenames[:max_num_files]:
if file.endswith(files_exts):
if check_for_dupes:
if use_md5_hashes:
md5_hash = hashlib.md5(open(os.path.join(dirpath, file), 'rb').read()).hexdigest()
if md5_hash not in filez_set:
filez_set[md5_hash] = os.path.join(dirpath, file)
else:
dupes_list.append(os.path.join(dirpath, file))
else:
if file not in filez_set:
filez_set[file] = os.path.join(dirpath, file)
else:
dupes_list.append(os.path.join(dirpath, file))
else:
fpath = os.path.join(dirpath, file)
filez_set[fpath] = fpath
filez = list(filez_set.values())
if verbose:
print('Done!')
print('=' * 70)
if filez:
if randomize_files_list:
if verbose:
print('Randomizing file list...')
random.shuffle(filez)
if verbose:
print('Done!')
print('=' * 70)
if verbose:
print('Found', len(filez), 'files.')
print('Skipped', len(dupes_list), 'duplicate files.')
print('=' * 70)
else:
if verbose:
print('Could not find any files...')
print('Please check dataset dirs and files extensions...')
print('=' * 70)
if max_total_files > 0:
if return_dupes:
return filez[:max_total_files], dupes_list
else:
return filez[:max_total_files]
else:
if return_dupes:
return filez, dupes_list
else:
return filez
###################################################################################
def has_consecutive_trend(nums, count):
if len(nums) < count:
return False
increasing_streak = 1
decreasing_streak = 1
for i in range(1, len(nums)):
if nums[i] > nums[i - 1]:
increasing_streak += 1
decreasing_streak = 1
elif nums[i] < nums[i - 1]:
decreasing_streak += 1
increasing_streak = 1
else:
increasing_streak = decreasing_streak = 1
if increasing_streak == count or decreasing_streak == count:
return True
return False
###################################################################################
def escore_notes_primary_features(escore_notes):
#=================================================================
def mean(values):
return sum(values) / len(values) if values else None
def std(values):
if not values:
return None
m = mean(values)
return math.sqrt(sum((x - m) ** 2 for x in values) / len(values)) if m is not None else None
def skew(values):
if not values:
return None
m = mean(values)
s = std(values)
if s is None or s == 0:
return None
return sum(((x - m) / s) ** 3 for x in values) / len(values)
def kurtosis(values):
if not values:
return None
m = mean(values)
s = std(values)
if s is None or s == 0:
return None
return sum(((x - m) / s) ** 4 for x in values) / len(values) - 3
def median(values):
if not values:
return None
srt = sorted(values)
n = len(srt)
mid = n // 2
if n % 2 == 0:
return (srt[mid - 1] + srt[mid]) / 2.0
return srt[mid]
def percentile(values, p):
if not values:
return None
srt = sorted(values)
n = len(srt)
k = (n - 1) * p / 100.0
f = int(k)
c = k - f
if f + 1 < n:
return srt[f] * (1 - c) + srt[f + 1] * c
return srt[f]
def diff(values):
if not values or len(values) < 2:
return []
return [values[i + 1] - values[i] for i in range(len(values) - 1)]
def mad(values):
if not values:
return None
m = median(values)
return median([abs(x - m) for x in values])
def entropy(values):
if not values:
return None
freq = {}
for v in values:
freq[v] = freq.get(v, 0) + 1
total = len(values)
ent = 0.0
for count in freq.values():
p_val = count / total
ent -= p_val * math.log2(p_val)
return ent
def mode(values):
if not values:
return None
freq = {}
for v in values:
freq[v] = freq.get(v, 0) + 1
max_count = max(freq.values())
modes = [k for k, count in freq.items() if count == max_count]
return min(modes)
#=================================================================
sp_score = solo_piano_escore_notes(escore_notes)
dscore = delta_score_notes(sp_score)
seq = []
for d in dscore:
seq.extend([d[1], d[2], d[4]])
#=================================================================
n = len(seq)
if n % 3 != 0:
seq = seq[: n - (n % 3)]
arr = [seq[i:i + 3] for i in range(0, len(seq), 3)]
#=================================================================
features = {}
delta_times = [row[0] for row in arr]
if delta_times:
features['delta_times_mean'] = mean(delta_times)
features['delta_times_std'] = std(delta_times)
features['delta_times_min'] = min(delta_times)
features['delta_times_max'] = max(delta_times)
features['delta_times_skew'] = skew(delta_times)
features['delta_times_kurtosis'] = kurtosis(delta_times)
delta_zero_count = sum(1 for x in delta_times if x == 0)
features['delta_times_zero_ratio'] = delta_zero_count / len(delta_times)
nonzero_dt = [x for x in delta_times if x != 0]
if nonzero_dt:
features['delta_times_nonzero_mean'] = mean(nonzero_dt)
features['delta_times_nonzero_std'] = std(nonzero_dt)
else:
features['delta_times_nonzero_mean'] = None
features['delta_times_nonzero_std'] = None
features['delta_times_mad'] = mad(delta_times)
features['delta_times_cv'] = (features['delta_times_std'] / features['delta_times_mean']
if features['delta_times_mean'] and features['delta_times_mean'] != 0 else None)
features['delta_times_entropy'] = entropy(delta_times)
features['delta_times_range'] = max(delta_times) - min(delta_times)
features['delta_times_median'] = median(delta_times)
features['delta_times_quantile_25'] = percentile(delta_times, 25)
features['delta_times_quantile_75'] = percentile(delta_times, 75)
if (features['delta_times_quantile_25'] is not None and features['delta_times_quantile_75'] is not None):
features['delta_times_iqr'] = features['delta_times_quantile_75'] - features['delta_times_quantile_25']
else:
features['delta_times_iqr'] = None
else:
for key in ['delta_times_mean', 'delta_times_std', 'delta_times_min', 'delta_times_max',
'delta_times_skew', 'delta_times_kurtosis', 'delta_times_zero_ratio',
'delta_times_nonzero_mean', 'delta_times_nonzero_std', 'delta_times_mad',
'delta_times_cv', 'delta_times_entropy', 'delta_times_range', 'delta_times_median',
'delta_times_quantile_25', 'delta_times_quantile_75', 'delta_times_iqr']:
features[key] = None
#=================================================================
durations = [row[1] for row in arr]
if durations:
features['durations_mean'] = mean(durations)
features['durations_std'] = std(durations)
features['durations_min'] = min(durations)
features['durations_max'] = max(durations)
features['durations_skew'] = skew(durations)
features['durations_kurtosis'] = kurtosis(durations)
features['durations_mad'] = mad(durations)
features['durations_cv'] = (features['durations_std'] / features['durations_mean']
if features['durations_mean'] and features['durations_mean'] != 0 else None)
features['durations_entropy'] = entropy(durations)
features['durations_range'] = max(durations) - min(durations)
features['durations_median'] = median(durations)
features['durations_quantile_25'] = percentile(durations, 25)
features['durations_quantile_75'] = percentile(durations, 75)
if features['durations_quantile_25'] is not None and features['durations_quantile_75'] is not None:
features['durations_iqr'] = features['durations_quantile_75'] - features['durations_quantile_25']
else:
features['durations_iqr'] = None
else:
for key in ['durations_mean', 'durations_std', 'durations_min', 'durations_max',
'durations_skew', 'durations_kurtosis', 'durations_mad', 'durations_cv',
'durations_entropy', 'durations_range', 'durations_median', 'durations_quantile_25',
'durations_quantile_75', 'durations_iqr']:
features[key] = None
#=================================================================
pitches = [row[2] for row in arr]
if pitches:
features['pitches_mean'] = mean(pitches)
features['pitches_std'] = std(pitches)
features['pitches_min'] = min(pitches)
features['pitches_max'] = max(pitches)
features['pitches_skew'] = skew(pitches)
features['pitches_kurtosis'] = kurtosis(pitches)
features['pitches_range'] = max(pitches) - min(pitches)
features['pitches_median'] = median(pitches)
features['pitches_quantile_25'] = percentile(pitches, 25)
features['pitches_quantile_75'] = percentile(pitches, 75)
if len(pitches) > 1:
dps = diff(pitches)
features['pitches_diff_mean'] = mean(dps)
features['pitches_diff_std'] = std(dps)
else:
features['pitches_diff_mean'] = None
features['pitches_diff_std'] = None
features['pitches_mad'] = mad(pitches)
if len(pitches) > 2:
peaks = sum(1 for i in range(1, len(pitches)-1)
if pitches[i] > pitches[i-1] and pitches[i] > pitches[i+1])
valleys = sum(1 for i in range(1, len(pitches)-1)
if pitches[i] < pitches[i-1] and pitches[i] < pitches[i+1])
else:
peaks, valleys = None, None
features['pitches_peak_count'] = peaks
features['pitches_valley_count'] = valleys
if len(pitches) > 1:
x = list(range(len(pitches)))
denominator = (len(x) * sum(xi ** 2 for xi in x) - sum(x) ** 2)
if denominator != 0:
slope = (len(x) * sum(x[i] * pitches[i] for i in range(len(x))) -
sum(x) * sum(pitches)) / denominator
else:
slope = None
features['pitches_trend_slope'] = slope
else:
features['pitches_trend_slope'] = None
features['pitches_unique_count'] = len(set(pitches))
pitch_class_hist = {i: 0 for i in range(12)}
for p in pitches:
pitch_class_hist[p % 12] += 1
total_pitch = len(pitches)
for i in range(12):
features[f'pitches_pc_{i}'] = (pitch_class_hist[i] / total_pitch) if total_pitch > 0 else None
max_asc = 0
cur_asc = 0
max_desc = 0
cur_desc = 0
for i in range(1, len(pitches)):
if pitches[i] > pitches[i-1]:
cur_asc += 1
max_asc = max(max_asc, cur_asc)
cur_desc = 0
elif pitches[i] < pitches[i-1]:
cur_desc += 1
max_desc = max(max_desc, cur_desc)
cur_asc = 0
else:
cur_asc = 0
cur_desc = 0
features['pitches_max_consecutive_ascending'] = max_asc if pitches else None
features['pitches_max_consecutive_descending'] = max_desc if pitches else None
p_intervals = diff(pitches)
features['pitches_median_diff'] = median(p_intervals) if p_intervals else None
if p_intervals:
dc = sum(1 for i in range(1, len(p_intervals))
if (p_intervals[i] > 0 and p_intervals[i-1] < 0) or (p_intervals[i] < 0 and p_intervals[i-1] > 0))
features['pitches_direction_changes'] = dc
else:
features['pitches_direction_changes'] = None
else:
for key in (['pitches_mean', 'pitches_std', 'pitches_min', 'pitches_max', 'pitches_skew',
'pitches_kurtosis', 'pitches_range', 'pitches_median', 'pitches_quantile_25',
'pitches_quantile_75', 'pitches_diff_mean', 'pitches_diff_std', 'pitches_mad',
'pitches_peak_count', 'pitches_valley_count', 'pitches_trend_slope',
'pitches_unique_count', 'pitches_max_consecutive_ascending', 'pitches_max_consecutive_descending',
'pitches_median_diff', 'pitches_direction_changes'] +
[f'pitches_pc_{i}' for i in range(12)]):
features[key] = None
#=================================================================
overall = [x for row in arr for x in row]
if overall:
features['overall_mean'] = mean(overall)
features['overall_std'] = std(overall)
features['overall_min'] = min(overall)
features['overall_max'] = max(overall)
features['overall_cv'] = (features['overall_std'] / features['overall_mean']
if features['overall_mean'] and features['overall_mean'] != 0 else None)
else:
for key in ['overall_mean', 'overall_std', 'overall_min', 'overall_max', 'overall_cv']:
features[key] = None
#=================================================================
onsets = []
cumulative = 0
for dt in delta_times:
onsets.append(cumulative)
cumulative += dt
if onsets and durations:
overall_piece_duration = onsets[-1] + durations[-1]
else:
overall_piece_duration = None
features['overall_piece_duration'] = overall_piece_duration
features['overall_notes_density'] = (len(arr) / overall_piece_duration
if overall_piece_duration and overall_piece_duration > 0 else None)
features['rhythm_ratio'] = (features['durations_mean'] / features['delta_times_mean']
if features['delta_times_mean'] and features['delta_times_mean'] != 0 else None)
features['overall_sum_delta_times'] = (sum(delta_times) if delta_times else None)
features['overall_sum_durations'] = (sum(durations) if durations else None)
features['overall_voicing_ratio'] = (sum(durations) / overall_piece_duration
if overall_piece_duration and durations else None)
features['overall_onset_std'] = std(onsets) if onsets else None
#=================================================================
chords_raw = []
chords_pc = []
current_group = []
for i, note in enumerate(arr):
dt = note[0]
if i == 0:
current_group = [i]
else:
if dt == 0:
current_group.append(i)
else:
if len(current_group) >= 2:
chord_notes = [arr[j][2] for j in current_group]
chords_raw.append(tuple(sorted(chord_notes)))
chords_pc.append(tuple(sorted(set(p % 12 for p in chord_notes))))
current_group = [i]
if current_group and len(current_group) >= 2:
chord_notes = [arr[j][2] for j in current_group]
chords_raw.append(tuple(sorted(chord_notes)))
chords_pc.append(tuple(sorted(set(p % 12 for p in chord_notes))))
if chords_raw:
chord_count = len(chords_raw)
features['chords_count'] = chord_count
features['chords_density'] = (chord_count / overall_piece_duration
if overall_piece_duration and chord_count is not None else None)
chord_sizes = [len(ch) for ch in chords_raw]
features['chords_size_mean'] = mean(chord_sizes)
features['chords_size_std'] = std(chord_sizes)
features['chords_size_min'] = min(chord_sizes) if chord_sizes else None
features['chords_size_max'] = max(chord_sizes) if chord_sizes else None
features['chords_unique_raw_count'] = len(set(chords_raw))
features['chords_unique_pc_count'] = len(set(chords_pc))
features['chords_entropy_raw'] = entropy(chords_raw)
features['chords_entropy_pc'] = entropy(chords_pc)
if len(chords_raw) > 1:
rep_raw = sum(1 for i in range(1, len(chords_raw)) if chords_raw[i] == chords_raw[i - 1])
features['chords_repeat_ratio_raw'] = rep_raw / (len(chords_raw) - 1)
else:
features['chords_repeat_ratio_raw'] = None
if len(chords_pc) > 1:
rep_pc = sum(1 for i in range(1, len(chords_pc)) if chords_pc[i] == chords_pc[i - 1])
features['chords_repeat_ratio_pc'] = rep_pc / (len(chords_pc) - 1)
else:
features['chords_repeat_ratio_pc'] = None
if len(chords_raw) > 1:
bigrams_raw = [(chords_raw[i], chords_raw[i + 1]) for i in range(len(chords_raw) - 1)]
features['chords_bigram_entropy_raw'] = entropy(bigrams_raw)
else:
features['chords_bigram_entropy_raw'] = None
if len(chords_pc) > 1:
bigrams_pc = [(chords_pc[i], chords_pc[i + 1]) for i in range(len(chords_pc) - 1)]
features['chords_bigram_entropy_pc'] = entropy(bigrams_pc)
else:
features['chords_bigram_entropy_pc'] = None
features['chords_mode_raw'] = mode(chords_raw)
features['chords_mode_pc'] = mode(chords_pc)
if chords_pc:
pc_sizes = [len(ch) for ch in chords_pc]
features['chords_pc_size_mean'] = mean(pc_sizes)
else:
features['chords_pc_size_mean'] = None
else:
for key in ['chords_count', 'chords_density', 'chords_size_mean', 'chords_size_std',
'chords_size_min', 'chords_size_max', 'chords_unique_raw_count', 'chords_unique_pc_count',
'chords_entropy_raw', 'chords_entropy_pc', 'chords_repeat_ratio_raw', 'chords_repeat_ratio_pc',
'chords_bigram_entropy_raw', 'chords_bigram_entropy_pc', 'chords_mode_raw', 'chords_mode_pc',
'chords_pc_size_mean']:
features[key] = None
#=================================================================
if delta_times:
med_dt = features['delta_times_median']
iqr_dt = features['delta_times_iqr']
threshold_a = med_dt + 1.5 * iqr_dt if med_dt is not None and iqr_dt is not None else None
threshold_b = percentile(delta_times, 90)
if threshold_a is not None and threshold_b is not None:
phrase_threshold = max(threshold_a, threshold_b)
elif threshold_a is not None:
phrase_threshold = threshold_a
elif threshold_b is not None:
phrase_threshold = threshold_b
else:
phrase_threshold = None
else:
phrase_threshold = None
phrases = []
current_phrase = []
if onsets:
current_phrase.append(0)
for i in range(len(onsets) - 1):
gap = onsets[i + 1] - onsets[i]
if phrase_threshold is not None and gap > phrase_threshold:
phrases.append(current_phrase)
current_phrase = []
current_phrase.append(i + 1)
if current_phrase:
phrases.append(current_phrase)
if phrases:
phrase_note_counts = []
phrase_durations = []
phrase_densities = []
phrase_mean_pitches = []
phrase_pitch_ranges = []
phrase_start_times = []
phrase_end_times = []
for phrase in phrases:
note_count = len(phrase)
phrase_note_counts.append(note_count)
ph_start = onsets[phrase[0]]
ph_end = onsets[phrase[-1]] + durations[phrase[-1]]
phrase_start_times.append(ph_start)
phrase_end_times.append(ph_end)
ph_duration = ph_end - ph_start
phrase_durations.append(ph_duration)
density = note_count / ph_duration if ph_duration > 0 else None
phrase_densities.append(density)
ph_pitches = [pitches[i] for i in phrase if i < len(pitches)]
phrase_mean_pitches.append(mean(ph_pitches) if ph_pitches else None)
phrase_pitch_ranges.append((max(ph_pitches) - min(ph_pitches)) if ph_pitches else None)
if len(phrases) > 1:
phrase_gaps = []
for i in range(len(phrases) - 1):
gap = phrase_start_times[i + 1] - phrase_end_times[i]
phrase_gaps.append(gap if gap > 0 else 0)
else:
phrase_gaps = []
features['phrases_count'] = len(phrases)
features['phrases_avg_note_count'] = mean(phrase_note_counts) if phrase_note_counts else None
features['phrases_std_note_count'] = std(phrase_note_counts) if phrase_note_counts else None
features['phrases_min_note_count'] = min(phrase_note_counts) if phrase_note_counts else None
features['phrases_max_note_count'] = max(phrase_note_counts) if phrase_note_counts else None
features['phrases_avg_duration'] = mean(phrase_durations) if phrase_durations else None
features['phrases_std_duration'] = std(phrase_durations) if phrase_durations else None
features['phrases_min_duration'] = min(phrase_durations) if phrase_durations else None
features['phrases_max_duration'] = max(phrase_durations) if phrase_durations else None
features['phrases_avg_density'] = mean(phrase_densities) if phrase_densities else None
features['phrases_std_density'] = std(phrase_densities) if phrase_densities else None
features['phrases_avg_mean_pitch'] = mean(phrase_mean_pitches) if phrase_mean_pitches else None
features['phrases_avg_pitch_range'] = mean(phrase_pitch_ranges) if phrase_pitch_ranges else None
if phrase_gaps:
features['phrases_avg_gap'] = mean(phrase_gaps)
features['phrases_std_gap'] = std(phrase_gaps)
features['phrases_min_gap'] = min(phrase_gaps)
features['phrases_max_gap'] = max(phrase_gaps)
else:
features['phrases_avg_gap'] = None
features['phrases_std_gap'] = None
features['phrases_min_gap'] = None
features['phrases_max_gap'] = None
features['phrases_threshold'] = phrase_threshold
else:
for key in ['phrases_count', 'phrases_avg_note_count', 'phrases_std_note_count',
'phrases_min_note_count', 'phrases_max_note_count', 'phrases_avg_duration',
'phrases_std_duration', 'phrases_min_duration', 'phrases_max_duration',
'phrases_avg_density', 'phrases_std_density', 'phrases_avg_mean_pitch',
'phrases_avg_pitch_range', 'phrases_avg_gap', 'phrases_std_gap',
'phrases_min_gap', 'phrases_max_gap', 'phrases_threshold']:
features[key] = None
#=================================================================
return features
###################################################################################
def winsorized_normalize(data, new_range=(0, 255), clip=1.5):
#=================================================================
new_min, new_max = new_range
#=================================================================
def percentile(values, p):
srt = sorted(values)
n = len(srt)
if n == 1:
return srt[0]
k = (n - 1) * p / 100.0
f = int(k)
c = k - f
if f + 1 < n:
return srt[f] * (1 - c) + srt[f + 1] * c
return srt[f]
#=================================================================
q1 = percentile(data, 25)
q3 = percentile(data, 75)
iqr = q3 - q1
lower_bound_w = q1 - clip * iqr
upper_bound_w = q3 + clip * iqr
data_min = min(data)
data_max = max(data)
effective_low = max(lower_bound_w, data_min)
effective_high = min(upper_bound_w, data_max)
#=================================================================
if effective_high == effective_low:
if data_max == data_min:
return [int(new_min)] * len(data)
normalized = [(x - data_min) / (data_max - data_min) for x in data]
return [int(round(new_min + norm * (new_max - new_min))) for norm in normalized]
#=================================================================
clipped = [x if x >= effective_low else effective_low for x in data]
clipped = [x if x <= effective_high else effective_high for x in clipped]
normalized = [(x - effective_low) / (effective_high - effective_low) for x in clipped]
#=================================================================
return [int(round(new_min + norm * (new_max - new_min))) for norm in normalized]
###################################################################################
def tokenize_features_to_ints_winsorized(features, new_range=(0, 255), clip=1.5, none_token=-1):
values = []
tokens = []
#=================================================================
def process_value(val):
if isinstance(val, (int, float)):
return int(round(abs(val)))
elif isinstance(val, (list, tuple)):
return int(round(abs(sum(val) / len(val))))
else:
return int(abs(hash(val)) % (10 ** 8))
#=================================================================
for key in sorted(features.keys()):
value = features[key]
if value is None:
tokens.append(none_token)
values.append(none_token)
else:
tokens.append(process_value(value))
if isinstance(value, (list, tuple)):
values.append(sum(value) / len(value))
else:
values.append(value)
#=================================================================
norm_tokens = winsorized_normalize(tokens, new_range, clip)
#=================================================================
return values, tokens, norm_tokens
###################################################################################
def write_jsonl(records_dicts_list,
file_name='data',
file_ext='.jsonl',
file_mode='w',
line_sep='\n',
verbose=True
):
if verbose:
print('=' * 70)
print('Writing', len(records_dicts_list), 'records to jsonl file...')
print('=' * 70)
if not os.path.splitext(file_name)[1]:
file_name += file_ext
l_count = 0
with open(file_name, mode=file_mode) as f:
for record in tqdm.tqdm(records_dicts_list, disable=not verbose):
f.write(json.dumps(record) + line_sep)
l_count += 1
f.close()
if verbose:
print('=' * 70)
print('Written total of', l_count, 'jsonl records.')
print('=' * 70)
print('Done!')
print('=' * 70)
###################################################################################
def read_jsonl(file_name='data',
file_ext='.jsonl',
verbose=True
):
if verbose:
print('=' * 70)
print('Reading jsonl file...')
print('=' * 70)
if not os.path.splitext(file_name)[1]:
file_name += file_ext
with open(file_name, 'r') as f:
records = []
gl_count = 0
for i, line in tqdm.tqdm(enumerate(f), disable=not verbose):
try:
record = json.loads(line)
records.append(record)
gl_count += 1
except KeyboardInterrupt:
if verbose:
print('=' * 70)
print('Stoping...')
print('=' * 70)
f.close()
return records
except json.JSONDecodeError:
if verbose:
print('=' * 70)
print('[ERROR] Line', i, 'is corrupted! Skipping it...')
print('=' * 70)
continue
f.close()
if verbose:
print('=' * 70)
print('Loaded total of', gl_count, 'jsonl records.')
print('=' * 70)
print('Done!')
print('=' * 70)
return records
###################################################################################
def read_jsonl_lines(lines_indexes_list,
file_name='data',
file_ext='.jsonl',
verbose=True
):
if verbose:
print('=' * 70)
print('Reading jsonl file...')
print('=' * 70)
if not os.path.splitext(file_name)[1]:
file_name += file_ext
records = []
l_count = 0
lines_indexes_list.sort(reverse=True)
with open(file_name, 'r') as f:
for current_line_number, line in tqdm.tqdm(enumerate(f)):
try:
if current_line_number in lines_indexes_list:
record = json.loads(line)
records.append(record)
lines_indexes_list = lines_indexes_list[:-1]
l_count += 1
if not lines_indexes_list:
break
except KeyboardInterrupt:
if verbose:
print('=' * 70)
print('Stoping...')
print('=' * 70)
f.close()
return records
except json.JSONDecodeError:
if verbose:
print('=' * 70)
print('[ERROR] Line', current_line_number, 'is corrupted! Skipping it...')
print('=' * 70)
continue
f.close()
if verbose:
print('=' * 70)
print('Loaded total of', l_count, 'jsonl records.')
print('=' * 70)
print('Done!')
print('=' * 70)
return records
###################################################################################
def compute_base(x: int, n: int) -> int:
if x < 0:
raise ValueError("x must be non-negative.")
if x == 0:
return 2
b = max(2, int(x ** (1 / n)))
if b ** n <= x:
b += 1
return b
###################################################################################
def encode_int_auto(x: int, n: int) -> tuple[int, list[int]]:
base = compute_base(x, n)
digits = [0] * n
for i in range(n - 1, -1, -1):
digits[i] = x % base
x //= base
return base, digits
###################################################################################
def decode_int_auto(base: int, digits: list[int]) -> int:
x = 0
for digit in digits:
if digit < 0 or digit >= base:
raise ValueError(f"Each digit must be in the range 0 to {base - 1}. Invalid digit: {digit}")
x = x * base + digit
return x
###################################################################################
def encode_int_manual(x, base, n):
digits = [0] * n
for i in range(n - 1, -1, -1):
digits[i] = x % base
x //= base
return digits
###################################################################################
def escore_notes_pitches_chords_signature(escore_notes,
max_patch=128,
sort_by_counts=False,
use_full_chords=False
):
if use_full_chords:
CHORDS = ALL_CHORDS_FULL
else:
CHORDS = ALL_CHORDS_SORTED
max_patch = max(0, min(128, max_patch))
escore_notes = [e for e in escore_notes if e[6] <= max_patch]
if escore_notes:
cscore = chordify_score([1000, escore_notes])
sig = []
dsig = []
drums_offset = len(CHORDS) + 128
bad_chords_counter = 0
for c in cscore:
all_pitches = [e[4] if e[3] != 9 else e[4]+128 for e in c]
chord = sorted(set(all_pitches))
pitches = sorted([p for p in chord if p < 128], reverse=True)
drums = [(d+drums_offset)-128 for d in chord if d > 127]
if pitches:
if len(pitches) > 1:
tones_chord = sorted(set([p % 12 for p in pitches]))
try:
sig_token = CHORDS.index(tones_chord) + 128
except:
checked_tones_chord = check_and_fix_tones_chord(tones_chord, use_full_chords=use_full_chords)
sig_token = CHORDS.index(checked_tones_chord) + 128
bad_chords_counter += 1
elif len(pitches) == 1:
sig_token = pitches[0]
sig.append(sig_token)
if drums:
dsig.extend(drums)
sig_p = {}
for item in sig+dsig:
if item in sig_p:
sig_p[item] += 1
else:
sig_p[item] = 1
sig_p[-1] = bad_chords_counter
fsig = [list(v) for v in sig_p.items()]
if sort_by_counts:
fsig.sort(key=lambda x: x[1], reverse=True)
return fsig
else:
return []
###################################################################################
def compute_sustain_intervals(events):
intervals = []
pedal_on = False
current_start = None
for t, cc in events:
if not pedal_on and cc >= 64:
pedal_on = True
current_start = t
elif pedal_on and cc < 64:
pedal_on = False
intervals.append((current_start, t))
current_start = None
if pedal_on:
intervals.append((current_start, float('inf')))
merged = []
for interval in intervals:
if merged and interval[0] <= merged[-1][1]:
merged[-1] = (merged[-1][0], max(merged[-1][1], interval[1]))
else:
merged.append(interval)
return merged
###################################################################################
def apply_sustain_to_ms_score(score):
sustain_by_channel = {}
for track in score[1:]:
for event in track:
if event[0] == 'control_change' and event[3] == 64:
channel = event[2]
sustain_by_channel.setdefault(channel, []).append((event[1], event[4]))
sustain_intervals_by_channel = {}
for channel, events in sustain_by_channel.items():
events.sort(key=lambda x: x[0])
sustain_intervals_by_channel[channel] = compute_sustain_intervals(events)
global_max_off = 0
for track in score[1:]:
for event in track:
if event[0] == 'note':
global_max_off = max(global_max_off, event[1] + event[2])
for channel, intervals in sustain_intervals_by_channel.items():
updated_intervals = []
for start, end in intervals:
if end == float('inf'):
end = global_max_off
updated_intervals.append((start, end))
sustain_intervals_by_channel[channel] = updated_intervals
if sustain_intervals_by_channel:
for track in score[1:]:
for event in track:
if event[0] == 'note':
start = event[1]
nominal_dur = event[2]
nominal_off = start + nominal_dur
channel = event[3]
intervals = sustain_intervals_by_channel.get(channel, [])
effective_off = nominal_off
for intv_start, intv_end in intervals:
if intv_start < nominal_off < intv_end:
effective_off = intv_end
break
effective_dur = effective_off - start
event[2] = effective_dur
return score
###################################################################################
def copy_file(src_file: str, trg_dir: str, add_subdir: bool = False, verbose: bool = False):
src_path = Path(src_file)
target_directory = Path(trg_dir)
if not src_path.is_file():
if verbose:
print("Source file does not exist or is not a file.")
return None
target_directory.mkdir(parents=True, exist_ok=True)
if add_subdir:
first_letter = src_path.name[0]
target_directory = target_directory / first_letter
target_directory.mkdir(parents=True, exist_ok=True)
destination = target_directory / src_path.name
try:
shutil.copy2(src_path, destination)
except:
if verbose:
print('File could not be copied!')
return None
if verbose:
print('File copied!')
return None
###################################################################################
def escore_notes_even_timings(escore_notes, in_place=True):
if in_place:
for e in escore_notes:
if e[1] % 2 != 0:
e[1] += 1
if e[2] % 2 != 0:
e[2] += 1
return []
else:
escore = copy.deepcopy(escore_notes)
for e in escore:
if e[1] % 2 != 0:
e[1] += 1
if e[2] % 2 != 0:
e[2] += 1
return escore
###################################################################################
def both_chords(chord1, chord2, merge_threshold=2):
if len(chord1) > 1 and len(chord2) > 0 and chord2[0][1]-chord1[0][1] <= merge_threshold:
return True
elif len(chord1) > 0 and len(chord2) > 1 and chord2[0][1]-chord1[0][1] <= merge_threshold:
return True
else:
return False
def merge_chords(chord1, chord2, sort_drums_last=False):
mchord = chord1
seen = []
for e in chord2:
if tuple([e[4], e[6]]) not in seen:
mchord.append(e)
seen.append(tuple([e[4], e[6]]))
for e in mchord[1:]:
e[1] = mchord[0][1]
if sort_drums_last:
mchord.sort(key=lambda x: (-x[4], x[6]) if x[6] != 128 else (x[6], -x[4]))
else:
mchord.sort(key=lambda x: (-x[4], x[6]))
return mchord
def merge_escore_notes(escore_notes, merge_threshold=2, sort_drums_last=False):
cscore = chordify_score([1000, escore_notes])
merged_chords = []
merged_chord = cscore[0]
for i in range(1, len(cscore)):
cchord = cscore[i]
if both_chords(merged_chord, cchord, merge_threshold=merge_threshold):
merged_chord = merge_chords(merged_chord, cchord, sort_drums_last=sort_drums_last)
else:
merged_chords.append(merged_chord)
merged_chord = cchord
return flatten(merged_chords)
###################################################################################
def solo_piano_escore_notes_tokenized(escore_notes,
compress_start_times=True,
encode_velocities=False,
verbose=False
):
if verbose:
print('=' * 70)
print('Encoding MIDI...')
sp_escore_notes = solo_piano_escore_notes(escore_notes)
zscore = recalculate_score_timings(sp_escore_notes)
dscore = delta_score_notes(zscore, timings_clip_value=127)
score = []
notes_counter = 0
chords_counter = 1
for i, e in enumerate(dscore):
dtime = e[1]
dur = e[2]
ptc = e[4]
vel = e[5]
if compress_start_times:
if i == 0:
score.extend([0, dur+128, ptc+256])
if encode_velocities:
score.append(vel+384)
else:
if dtime == 0:
score.extend([dur+128, ptc+256])
else:
score.extend([dtime, dur+128, ptc+256])
if encode_velocities:
score.append(vel+384)
if dtime != 0:
chords_counter += 1
else:
score.extend([dtime, dur+128, ptc+256])
if encode_velocities:
score.append(vel+384)
if dtime != 0:
chords_counter += 1
notes_counter += 1
if verbose:
print('Done!')
print('=' * 70)
print('Source MIDI composition has', len(zscore), 'notes')
print('Source MIDI composition has', len([d[1] for d in dscore if d[1] !=0 ])+1, 'chords')
print('-' * 70)
print('Encoded sequence has', notes_counter, 'pitches')
print('Encoded sequence has', chords_counter, 'chords')
print('-' * 70)
print('Final encoded sequence has', len(score), 'tokens')
print('=' * 70)
return score
###################################################################################
def equalize_closest_elements_dynamic(seq,
min_val=128,
max_val=256,
splitting_factor=1.5,
tightness_threshold=0.15
):
candidates = [(i, x) for i, x in enumerate(seq) if min_val <= x <= max_val]
if len(candidates) < 2:
return seq.copy()
sorted_candidates = sorted(candidates, key=lambda pair: pair[1])
candidate_values = [val for _, val in sorted_candidates]
differences = [candidate_values[i+1] - candidate_values[i] for i in range(len(candidate_values)-1)]
def median(lst):
n = len(lst)
sorted_lst = sorted(lst)
mid = n // 2
if n % 2 == 0:
return (sorted_lst[mid - 1] + sorted_lst[mid]) / 2.0
else:
return sorted_lst[mid]
med_diff = median(differences)
split_indices = [i for i, diff in enumerate(differences) if diff > splitting_factor * med_diff]
clusters = []
if split_indices:
start = 0
for split_index in split_indices:
clusters.append(sorted_candidates[start:split_index+1])
start = split_index + 1
clusters.append(sorted_candidates[start:])
else:
clusters = [sorted_candidates]
valid_clusters = [cluster for cluster in clusters if len(cluster) >= 2]
if not valid_clusters:
return seq.copy()
def cluster_spread(cluster):
values = [val for (_, val) in cluster]
return max(values) - min(values)
valid_clusters.sort(key=lambda cluster: (len(cluster), -cluster_spread(cluster)), reverse=True)
selected_cluster = valid_clusters[0]
allowed_range_width = max_val - min_val
spread = cluster_spread(selected_cluster)
ratio = spread / allowed_range_width
if ratio > tightness_threshold:
return seq.copy()
cluster_values = [val for (_, val) in selected_cluster]
equal_value = sum(cluster_values) // len(cluster_values)
result = list(seq)
for idx, _ in selected_cluster:
result[idx] = equal_value
return result
###################################################################################
def chunk_list(lst, chunk_size):
return [lst[i:i + chunk_size] for i in range(0, len(lst), chunk_size)]
###################################################################################
def compress_tokens_sequence(seq,
min_val=128,
max_val=256,
group_size=2,
splitting_factor=1.5,
tightness_threshold=0.15
):
comp_seq = equalize_closest_elements_dynamic(seq,
min_val,
max_val,
splitting_factor=splitting_factor,
tightness_threshold=tightness_threshold
)
seq_split = sorted(chunk_list(comp_seq, group_size), key=lambda x: (-x[0], -x[1]))
seq_grouped = [[[k]] + [vv[1:] for vv in v] for k, v in groupby(seq_split, key=lambda x: x[0])]
return flatten(flatten(sorted(seq_grouped, key=lambda x: -x[1][0])))
###################################################################################
def merge_adjacent_pairs(values_counts):
merged = []
i = 0
while i < len(values_counts):
if i < len(values_counts) - 1:
value1, count1 = values_counts[i]
value2, count2 = values_counts[i + 1]
if value2 - value1 == 1:
if count2 > count1:
merged_value = value2
else:
merged_value = value1
merged_count = count1 + count2
merged.append((merged_value, merged_count))
i += 2
continue
merged.append(values_counts[i])
i += 1
return merged
###################################################################################
def merge_escore_notes_start_times(escore_notes, num_merges=1):
new_dscore = delta_score_notes(escore_notes)
times = [e[1] for e in new_dscore if e[1] != 0]
times_counts = sorted(Counter(times).most_common())
prev_counts = []
new_times_counts = times_counts
mcount = 0
while prev_counts != new_times_counts:
prev_counts = new_times_counts
new_times_counts = merge_adjacent_pairs(new_times_counts)
mcount += 1
if mcount == num_merges:
break
gtimes = [r[0] for r in new_times_counts]
for e in new_dscore:
if e[1] > 0:
e[1] = find_closest_value(gtimes, e[1])[0]
e[2] -= num_merges
return delta_score_to_abs_score(new_dscore)
###################################################################################
def multi_instrumental_escore_notes_tokenized(escore_notes, compress_seq=False):
melody_chords = []
pe = escore_notes[0]
for i, e in enumerate(escore_notes):
dtime = max(0, min(255, e[1]-pe[1]))
dur = max(0, min(255, e[2]))
cha = max(0, min(15, e[3]))
if cha == 9:
pat = 128
else:
pat = max(0, min(127, e[6]))
ptc = max(0, min(127, e[4]))
vel = max(8, min(127, e[5]))
velocity = round(vel / 15)-1
dur_vel = (8 * dur) + velocity
pat_ptc = (129 * pat) + ptc
if compress_seq:
if dtime != 0 or i == 0:
melody_chords.extend([dtime, dur_vel+256, pat_ptc+2304])
else:
melody_chords.extend([dur_vel+256, pat_ptc+2304])
else:
melody_chords.extend([dtime, dur_vel+256, pat_ptc+2304])
pe = e
return melody_chords
###################################################################################
def merge_counts(data, return_lists=True):
merged = defaultdict(int)
for value, count in data:
merged[value] += count
if return_lists:
return [[k, v] for k, v in merged.items()]
else:
return list(merged.items())
###################################################################################
def convert_escore_notes_pitches_chords_signature(signature, convert_to_full_chords=True):
if convert_to_full_chords:
SRC_CHORDS = ALL_CHORDS_SORTED
TRG_CHORDS = ALL_CHORDS_FULL
else:
SRC_CHORDS = ALL_CHORDS_FULL
TRG_CHORDS = ALL_CHORDS_SORTED
cdiff = len(TRG_CHORDS) - len(SRC_CHORDS)
pitches_counts = [c for c in signature if -1 < c[0] < 128]
chords_counts = [c for c in signature if 127 < c[0] < len(SRC_CHORDS)+128]
drums_counts = [[c[0]+cdiff, c[1]] for c in signature if len(SRC_CHORDS)+127 < c[0] < len(SRC_CHORDS)+256]
bad_chords_count = [c for c in signature if c[0] == -1]
new_chords_counts = []
for c in chords_counts:
tones_chord = SRC_CHORDS[c[0]-128]
if tones_chord not in TRG_CHORDS:
tones_chord = check_and_fix_tones_chord(tones_chord, use_full_chords=convert_to_full_chords)
bad_chords_count[0][1] += 1
new_chords_counts.append([TRG_CHORDS.index(tones_chord)+128, c[1]])
return pitches_counts + merge_counts(new_chords_counts) + drums_counts + bad_chords_count
###################################################################################
def convert_bytes_in_nested_list(lst,
encoding='utf-8',
errors='ignore',
return_changed_events_count=False
):
new_list = []
ce_count = 0
for item in lst:
if isinstance(item, list):
new_list.append(convert_bytes_in_nested_list(item))
elif isinstance(item, bytes):
new_list.append(item.decode(encoding, errors=errors))
ce_count += 1
else:
new_list.append(item)
if return_changed_events_count:
return new_list, ce_count
else:
return new_list
###################################################################################
def mult_pitches(pitches, min_oct=4, max_oct=6):
tones_chord = sorted(set([p % 12 for p in pitches]))
mult_ptcs = []
for t in tones_chord:
for i in range(min_oct, max_oct):
mult_ptcs.append((i*12)+t)
return mult_ptcs
###################################################################################
def find_next(pitches, cur_ptc):
i = 0
for i, p in enumerate(pitches):
if p != cur_ptc:
break
return i
###################################################################################
def ordered_groups_unsorted(data, key_index):
def keyfunc(sublist):
return sublist[key_index]
groups = []
for key, group in groupby(data, key=keyfunc):
groups.append((key, list(group)))
return groups
###################################################################################
def ordered_groups(data, ptc_idx, pat_idx):
groups = OrderedDict()
for sublist in data:
key = tuple([sublist[ptc_idx], sublist[pat_idx]])
if key not in groups:
groups[key] = []
groups[key].append(sublist)
return list(groups.items())
###################################################################################
def merge_melody_notes(escore_notes, pitches_idx=4, max_dur=255, last_dur=128):
groups = ordered_groups_unsorted(escore_notes, pitches_idx)
merged_melody_notes = []
for i, (k, g) in enumerate(groups[:-1]):
if len(g) == 1:
merged_melody_notes.extend(g)
else:
dur = min(max_dur, groups[i+1][1][0][1] - g[0][1])
merged_melody_notes.append(['note',
g[0][1],
dur,
g[0][3],
g[0][4],
g[0][5],
g[0][6]
])
merged_melody_notes.append(['note',
groups[-1][1][0][1],
last_dur,
groups[-1][1][0][3],
groups[-1][1][0][4],
groups[-1][1][0][5],
groups[-1][1][0][6]
])
return merged_melody_notes
###################################################################################
def add_expressive_melody_to_enhanced_score_notes(escore_notes,
melody_start_chord=0,
melody_prime_pitch=60,
melody_step=1,
melody_channel=3,
melody_patch=40,
melody_notes_max_duration=255,
melody_last_note_dur=128,
melody_clip_max_min_durs=[],
melody_max_velocity=120,
acc_max_velocity=90,
return_melody=False
):
score = copy.deepcopy(escore_notes)
adjust_score_velocities(score, acc_max_velocity)
cscore = chordify_score([1000, score])
melody_pitches = [melody_prime_pitch]
for i, c in enumerate(cscore[melody_start_chord:]):
if i % melody_step == 0:
pitches = [e[4] for e in c if e[3] != 9]
if pitches:
cptc = find_closest_value(mult_pitches(pitches), melody_pitches[-1])[0]
melody_pitches.append(cptc)
song_f = []
mel_f = []
idx = 1
for i, c in enumerate(cscore[:-melody_step]):
pitches = [e[4] for e in c if e[3] != 9]
if pitches and i >= melody_start_chord and i % melody_step == 0:
dur = min(cscore[i+melody_step][0][1] - c[0][1], melody_notes_max_duration)
mel_f.append(['note',
c[0][1],
dur,
melody_channel,
60+(melody_pitches[idx] % 24),
100 + ((melody_pitches[idx] % 12) * 2),
melody_patch
])
idx += 1
song_f.extend(c)
song_f.extend(flatten(cscore[-melody_step:]))
if len(melody_clip_max_min_durs) == 2:
for e in mel_f:
if e[2] >= melody_clip_max_min_durs[0]:
e[2] = melody_clip_max_min_durs[1]
adjust_score_velocities(mel_f, melody_max_velocity)
merged_melody_notes = merge_melody_notes(mel_f,
max_dur=melody_notes_max_duration,
last_dur=melody_last_note_dur
)
song_f = sorted(merged_melody_notes + song_f,
key=lambda x: x[1]
)
if return_melody:
return mel_f
else:
return song_f
###################################################################################
def list_md5_hash(ints_list):
arr = array('H', ints_list)
binary_data = arr.tobytes()
return hashlib.md5(binary_data).hexdigest()
###################################################################################
def fix_escore_notes_durations(escore_notes,
min_notes_gap=1,
min_notes_dur=1,
times_idx=1,
durs_idx=2,
channels_idx = 3,
pitches_idx=4,
patches_idx=6
):
notes = [e for e in escore_notes if e[channels_idx] != 9]
drums = [e for e in escore_notes if e[channels_idx] == 9]
escore_groups = ordered_groups(notes, pitches_idx, patches_idx)
merged_score = []
for k, g in escore_groups:
if len(g) > 2:
fg = fix_monophonic_score_durations(g,
min_notes_gap=min_notes_gap,
min_notes_dur=min_notes_dur
)
merged_score.extend(fg)
elif len(g) == 2:
if g[0][times_idx]+g[0][durs_idx] >= g[1][times_idx]:
g[0][durs_idx] = max(1, g[1][times_idx] - g[0][times_idx] - min_notes_gap)
merged_score.extend(g)
else:
merged_score.extend(g)
return sorted(merged_score + drums, key=lambda x: x[times_idx])
###################################################################################
def create_nested_chords_tree(chords_list):
tree = {}
for chord in chords_list:
node = tree
for semitone in chord:
if semitone not in node:
node[semitone] = {}
node = node[semitone]
node.setdefault(-1, []).append(chord)
return tree
###################################################################################
def get_chords_with_prefix(nested_chords_tree, prefix):
node = nested_chords_tree
for semitone in prefix:
if semitone in node:
node = node[semitone]
else:
return []
collected_chords = []
def recursive_collect(subnode):
if -1 in subnode:
collected_chords.extend(subnode[-1])
for key, child in subnode.items():
if key != -1:
recursive_collect(child)
recursive_collect(node)
return collected_chords
###################################################################################
def get_chords_by_semitones(chords_list, chord_semitones):
query_set = set(chord_semitones)
results = []
for chord in chords_list:
chord_set = set(chord)
if query_set.issubset(chord_set):
results.append(sorted(set(chord)))
return results
###################################################################################
def remove_duplicate_pitches_from_escore_notes(escore_notes,
pitches_idx=4,
patches_idx=6,
return_dupes_count=False
):
cscore = chordify_score([1000, escore_notes])
new_escore = []
bp_count = 0
for c in cscore:
cho = []
seen = []
for cc in c:
if [cc[pitches_idx], cc[patches_idx]] not in seen:
cho.append(cc)
seen.append([cc[pitches_idx], cc[patches_idx]])
else:
bp_count += 1
new_escore.extend(cho)
if return_dupes_count:
return bp_count
else:
return new_escore
###################################################################################
def chunks_shuffle(lst,
min_len=1,
max_len=3,
seed=None
):
rnd = random.Random(seed)
chunks = []
i, n = 0, len(lst)
while i < n:
size = rnd.randint(min_len, max_len)
size = min(size, n - i)
chunks.append(lst[i : i + size])
i += size
rnd.shuffle(chunks)
flattened = []
for chunk in chunks:
flattened.extend(chunk)
return flattened
###################################################################################
def find_deepest_midi_dirs(roots,
marker_file="midi_score.mid",
suffixes=None,
randomize=False,
seed=None,
verbose=False
):
try:
iter(roots)
if isinstance(roots, (str, Path)):
root_list = [roots]
else:
root_list = list(roots)
except TypeError:
root_list = [roots]
if isinstance(marker_file, (list, tuple)):
patterns = [p.lower() for p in marker_file if p]
else:
patterns = [marker_file.lower()] if marker_file else []
allowed = {s.lower() for s in (suffixes or ['.mid', '.midi', '.kar'])}
if verbose:
print("Settings:")
print(" Roots:", [str(r) for r in root_list])
print(" Marker patterns:", patterns or "<no marker filter>")
print(" Allowed suffixes:", allowed)
print(f" Randomize={randomize}, Seed={seed}")
results = defaultdict(list)
rng = random.Random(seed)
for root in root_list:
root_path = Path(root)
if not root_path.is_dir():
print(f"Warning: '{root_path}' is not a valid directory, skipping.")
continue
if verbose:
print(f"\nScanning root: {str(root_path)}")
all_dirs = list(root_path.rglob("*"))
dirs_iter = tqdm.tqdm(all_dirs, desc=f"Dirs in {root_path.name}", disable=not verbose)
for dirpath in dirs_iter:
if not dirpath.is_dir():
continue
children = list(dirpath.iterdir())
if any(child.is_dir() for child in children):
if verbose:
print(f"Skipping non-leaf: {str(dirpath)}")
continue
files = [f for f in children if f.is_file()]
names = [f.name.lower() for f in files]
if patterns:
matched = any(fnmatch(name, pat) for name in names for pat in patterns)
if not matched:
if verbose:
print(f"No marker in: {str(dirpath)}")
continue
if verbose:
print(f"Marker found in: {str(dirpath)}")
else:
if verbose:
print(f"Including leaf (no marker): {str(dirpath)}")
for f in files:
if f.suffix.lower() in allowed:
results[str(dirpath)].append(str(f))
if verbose:
print(f" Collected: {f.name}")
all_leaves = list(results.keys())
if randomize:
if verbose:
print("\nShuffling leaf directories")
rng.shuffle(all_leaves)
else:
all_leaves.sort()
final_dict = {}
for leaf in all_leaves:
file_list = results[leaf][:]
if randomize:
if verbose:
print(f"Shuffling files in: {leaf}")
rng.shuffle(file_list)
else:
file_list.sort()
final_dict[leaf] = file_list
if verbose:
print("\nScan complete. Found directories:")
for d, fl in final_dict.items():
print(f" {d} -> {len(fl)} files")
return final_dict
###################################################################################
PERCUSSION_GROUPS = {
1: { # Bass Drums
35: 'Acoustic Bass Drum',
36: 'Bass Drum 1',
},
2: { # Stick
37: 'Side Stick',
},
3: { # Snares
38: 'Acoustic Snare',
40: 'Electric Snare',
},
4: { # Claps
39: 'Hand Clap',
},
5: { # Floor Toms
41: 'Low Floor Tom',
43: 'High Floor Tom',
},
6: { # Hi-Hats
42: 'Closed Hi-Hat',
44: 'Pedal Hi-Hat',
46: 'Open Hi-Hat',
},
7: { # Toms
45: 'Low Tom',
47: 'Low-Mid Tom',
48: 'Hi-Mid Tom',
50: 'High Tom',
},
8: { # Cymbals
49: 'Crash Cymbal 1',
51: 'Ride Cymbal 1',
52: 'Chinese Cymbal',
55: 'Splash Cymbal',
57: 'Crash Cymbal 2',
59: 'Ride Cymbal 2',
},
9: { # Bells
53: 'Ride Bell',
},
10: { # Tambourine
54: 'Tambourine',
},
11: { # Cowbell
56: 'Cowbell',
},
12: { # Vibraslap
58: 'Vibraslap',
},
13: { # Bongos
60: 'Hi Bongo',
61: 'Low Bongo',
},
14: { # Congas
62: 'Mute Hi Conga',
63: 'Open Hi Conga',
64: 'Low Conga',
},
15: { # Timbales
65: 'High Timbale',
66: 'Low Timbale',
},
16: { # Agogô
67: 'High Agogo',
68: 'Low Agogo',
},
17: { # Cabasa
69: 'Cabasa',
},
18: { # Maracas
70: 'Maracas',
},
19: { # Whistles
71: 'Short Whistle',
72: 'Long Whistle',
},
20: { # Guiros
73: 'Short Guiro',
74: 'Long Guiro',
},
21: { # Claves
75: 'Claves',
},
22: { # Wood Blocks
76: 'Hi Wood Block',
77: 'Low Wood Block',
},
23: { # Cuica
78: 'Mute Cuica',
79: 'Open Cuica',
},
24: { # Triangles
80: 'Mute Triangle',
81: 'Open Triangle',
},
}
###################################################################################
print('Module loaded!')
print('=' * 70)
print('Enjoy! :)')
print('=' * 70)
###################################################################################
# This is the end of the TMIDI X Python module
###################################################################################