Upload TMIDIX.py
Browse files
TMIDIX.py
CHANGED
|
@@ -48,7 +48,7 @@ r'''
|
|
| 48 |
|
| 49 |
###################################################################################
|
| 50 |
|
| 51 |
-
__version__ = "26.5.
|
| 52 |
|
| 53 |
###################################################################################
|
| 54 |
|
|
@@ -18964,6 +18964,286 @@ def remove_repeating_patterns(lst: list[int]) -> list[int]:
|
|
| 18964 |
|
| 18965 |
###################################################################################
|
| 18966 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18967 |
print('Module loaded!')
|
| 18968 |
print('=' * 70)
|
| 18969 |
print('Enjoy! :)')
|
|
|
|
| 48 |
|
| 49 |
###################################################################################
|
| 50 |
|
| 51 |
+
__version__ = "26.5.19" # TMIDIX version
|
| 52 |
|
| 53 |
###################################################################################
|
| 54 |
|
|
|
|
| 18964 |
|
| 18965 |
###################################################################################
|
| 18966 |
|
| 18967 |
+
def find_repeating_non_overlapping_patterns(arr, min_len):
|
| 18968 |
+
"""
|
| 18969 |
+
Finds all repeating non-overlapping patterns of min_len and longer.
|
| 18970 |
+
Excludes overlapping subpatterns from longest to shortest.
|
| 18971 |
+
|
| 18972 |
+
:param arr: List[int] - The input sequence
|
| 18973 |
+
:param min_len: int - The minimum length of patterns
|
| 18974 |
+
:return: Dict[Tuple[int, ...], int] - Dictionary of patterns and their valid counts
|
| 18975 |
+
"""
|
| 18976 |
+
n = len(arr)
|
| 18977 |
+
if n < min_len * 2:
|
| 18978 |
+
return {}
|
| 18979 |
+
|
| 18980 |
+
# Tracks indices consumed by longer patterns to exclude shorter overlapping subpatterns
|
| 18981 |
+
consumed = [False] * n
|
| 18982 |
+
|
| 18983 |
+
# Max possible repeating length is half the array
|
| 18984 |
+
max_len = n // 2
|
| 18985 |
+
|
| 18986 |
+
result = {}
|
| 18987 |
+
|
| 18988 |
+
for L in range(max_len, min_len - 1, -1):
|
| 18989 |
+
# 1. Group all starting indices by their pattern tuple
|
| 18990 |
+
groups = {}
|
| 18991 |
+
for i in range(n - L + 1):
|
| 18992 |
+
pat = tuple(arr[i:i + L])
|
| 18993 |
+
if pat in groups:
|
| 18994 |
+
groups[pat].append(i)
|
| 18995 |
+
else:
|
| 18996 |
+
groups[pat] = [i]
|
| 18997 |
+
|
| 18998 |
+
# 2. Process each pattern group
|
| 18999 |
+
for pat, indices in groups.items():
|
| 19000 |
+
count = 0
|
| 19001 |
+
last_end = -1
|
| 19002 |
+
|
| 19003 |
+
# Sort indices to process left-to-right
|
| 19004 |
+
indices.sort()
|
| 19005 |
+
|
| 19006 |
+
for i in indices:
|
| 19007 |
+
# Skip if this occurrence is inside an already consumed longer pattern
|
| 19008 |
+
if consumed[i]:
|
| 19009 |
+
continue
|
| 19010 |
+
|
| 19011 |
+
# If it doesn't overlap with the previous selected occurrence of THIS pattern
|
| 19012 |
+
if i >= last_end:
|
| 19013 |
+
count += 1
|
| 19014 |
+
last_end = i + L
|
| 19015 |
+
|
| 19016 |
+
# 3. If we found repeating occurrences, record it and consume the indices
|
| 19017 |
+
if count >= 2:
|
| 19018 |
+
result[pat] = count
|
| 19019 |
+
|
| 19020 |
+
# We must re-iterate to mark the exact consumed indices
|
| 19021 |
+
last_end = -1
|
| 19022 |
+
for i in indices:
|
| 19023 |
+
if consumed[i]:
|
| 19024 |
+
continue
|
| 19025 |
+
if i >= last_end:
|
| 19026 |
+
# Mark these indices as consumed for shorter patterns
|
| 19027 |
+
for k in range(i, i + L):
|
| 19028 |
+
consumed[k] = True
|
| 19029 |
+
last_end = i + L
|
| 19030 |
+
|
| 19031 |
+
return result
|
| 19032 |
+
|
| 19033 |
+
###################################################################################
|
| 19034 |
+
|
| 19035 |
+
def get_chord_name(intervals):
|
| 19036 |
+
if not intervals:
|
| 19037 |
+
return "Unknown"
|
| 19038 |
+
|
| 19039 |
+
ivs = sorted(intervals)
|
| 19040 |
+
root = ivs[0]
|
| 19041 |
+
|
| 19042 |
+
# Standard note names for roots 0 through 11
|
| 19043 |
+
root_names = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']
|
| 19044 |
+
R = root_names[root]
|
| 19045 |
+
|
| 19046 |
+
# 1. Single note
|
| 19047 |
+
if len(ivs) == 1:
|
| 19048 |
+
return R
|
| 19049 |
+
|
| 19050 |
+
# Normalize intervals relative to the root (so root always acts as 0)
|
| 19051 |
+
norm_ivs = sorted([i - root for i in ivs])
|
| 19052 |
+
|
| 19053 |
+
# 2. Dyads (Intervals / Power Chords)
|
| 19054 |
+
if len(norm_ivs) == 2:
|
| 19055 |
+
if norm_ivs[1] == 1: return f"{R}(addb9)"
|
| 19056 |
+
if norm_ivs[1] == 2: return f"{R}(add9)"
|
| 19057 |
+
if norm_ivs[1] == 3: return f"{R}m" # minor 3rd implies minor chord
|
| 19058 |
+
if norm_ivs[1] == 4: return f"{R}" # major 3rd implies major chord
|
| 19059 |
+
if norm_ivs[1] == 5: return f"{R}(add11)" # perfect 4th implies add11
|
| 19060 |
+
if norm_ivs[1] == 6: return f"{R}(b5)"
|
| 19061 |
+
if norm_ivs[1] == 7: return f"{R}5"
|
| 19062 |
+
if norm_ivs[1] == 8: return f"{R}(#5)"
|
| 19063 |
+
if norm_ivs[1] == 9: return f"{R}6"
|
| 19064 |
+
if norm_ivs[1] == 10: return f"{R}(m7)"
|
| 19065 |
+
if norm_ivs[1] == 11: return f"{R}(maj7)"
|
| 19066 |
+
return f"{R}(add{norm_ivs[1]})"
|
| 19067 |
+
|
| 19068 |
+
# Map normalized semitones to standard chord degrees
|
| 19069 |
+
degree_map = {
|
| 19070 |
+
1: "b9", 2: "9", 3: "m3", 4: "M3", 5: "11", 6: "b5",
|
| 19071 |
+
7: "P5", 8: "#5", 9: "6", 10: "m7", 11: "M7"
|
| 19072 |
+
}
|
| 19073 |
+
|
| 19074 |
+
degrees_set = set([degree_map[iv] for iv in norm_ivs if iv != 0])
|
| 19075 |
+
|
| 19076 |
+
# Helper variables
|
| 19077 |
+
has_m3 = "m3" in degrees_set
|
| 19078 |
+
has_M3 = "M3" in degrees_set
|
| 19079 |
+
has_P5 = "P5" in degrees_set
|
| 19080 |
+
has_b5 = "b5" in degrees_set
|
| 19081 |
+
has_sharp5 = "#5" in degrees_set
|
| 19082 |
+
has_m7 = "m7" in degrees_set
|
| 19083 |
+
has_M7 = "M7" in degrees_set
|
| 19084 |
+
has_9 = "9" in degrees_set
|
| 19085 |
+
has_b9 = "b9" in degrees_set
|
| 19086 |
+
has_6 = "6" in degrees_set # Corrected: 9 semitones is a 6, not a 13
|
| 19087 |
+
has_11 = "11" in degrees_set
|
| 19088 |
+
|
| 19089 |
+
# 4. Handle Whole-tone clusters (5 or 6 notes of even intervals)
|
| 19090 |
+
# Fixed logic: strictly checks if all intervals are even semitones
|
| 19091 |
+
if len(norm_ivs) >= 5 and all(i % 2 == 0 for i in norm_ivs):
|
| 19092 |
+
return f"{R}(whole-tone cluster)"
|
| 19093 |
+
|
| 19094 |
+
# 3. Determine Base Triad Quality
|
| 19095 |
+
base = ""
|
| 19096 |
+
triad_degrees = []
|
| 19097 |
+
|
| 19098 |
+
if has_M3 and has_P5:
|
| 19099 |
+
base = ""
|
| 19100 |
+
triad_degrees = ["M3", "P5"]
|
| 19101 |
+
elif has_M3 and has_sharp5:
|
| 19102 |
+
base = "aug"
|
| 19103 |
+
triad_degrees = ["M3", "#5"]
|
| 19104 |
+
elif has_M3 and has_b5:
|
| 19105 |
+
base = "(b5)"
|
| 19106 |
+
triad_degrees = ["M3", "b5"]
|
| 19107 |
+
elif has_M3:
|
| 19108 |
+
base = ""
|
| 19109 |
+
triad_degrees = ["M3"]
|
| 19110 |
+
elif has_m3 and has_P5:
|
| 19111 |
+
base = "m"
|
| 19112 |
+
triad_degrees = ["m3", "P5"]
|
| 19113 |
+
elif has_m3 and has_b5:
|
| 19114 |
+
base = "dim"
|
| 19115 |
+
triad_degrees = ["m3", "b5"]
|
| 19116 |
+
elif has_m3 and has_sharp5:
|
| 19117 |
+
base = "m#5"
|
| 19118 |
+
triad_degrees = ["m3", "#5"]
|
| 19119 |
+
elif has_m3:
|
| 19120 |
+
base = "m"
|
| 19121 |
+
triad_degrees = ["m3"]
|
| 19122 |
+
else:
|
| 19123 |
+
# No 3rd present (Ambiguous clusters or sus4/add11 combinations)
|
| 19124 |
+
extensions = []
|
| 19125 |
+
for iv in norm_ivs:
|
| 19126 |
+
if iv == 0: continue
|
| 19127 |
+
if iv == 5: extensions.append("sus4") # 4th implies sus4
|
| 19128 |
+
elif iv == 7: continue # 5th is assumed
|
| 19129 |
+
elif iv == 6: extensions.append("b5")
|
| 19130 |
+
elif iv == 8: extensions.append("#5")
|
| 19131 |
+
elif iv == 2: extensions.append("add9")
|
| 19132 |
+
elif iv == 9: extensions.append("6")
|
| 19133 |
+
elif iv == 10: extensions.append("m7")
|
| 19134 |
+
elif iv == 11: extensions.append("M7")
|
| 19135 |
+
elif iv == 1: extensions.append("addb9")
|
| 19136 |
+
else: extensions.append(f"add{iv}")
|
| 19137 |
+
|
| 19138 |
+
if not extensions: return R
|
| 19139 |
+
|
| 19140 |
+
# Handle sus4 & m7 combo standard naming convention
|
| 19141 |
+
if "sus4" in extensions and "m7" in extensions:
|
| 19142 |
+
extensions.remove("sus4")
|
| 19143 |
+
extensions.remove("m7")
|
| 19144 |
+
return f"{R}7sus4{''.join(extensions)}"
|
| 19145 |
+
|
| 19146 |
+
return f"{R}{''.join(extensions)}"
|
| 19147 |
+
|
| 19148 |
+
# 5. Determine Seventh Chord Quality
|
| 19149 |
+
seventh = ""
|
| 19150 |
+
if has_m7:
|
| 19151 |
+
if base == "":
|
| 19152 |
+
seventh = "7"
|
| 19153 |
+
elif base == "m":
|
| 19154 |
+
if has_b5:
|
| 19155 |
+
seventh = "7b5"
|
| 19156 |
+
triad_degrees.append("b5")
|
| 19157 |
+
else:
|
| 19158 |
+
seventh = "7"
|
| 19159 |
+
elif base == "dim":
|
| 19160 |
+
seventh = "7"
|
| 19161 |
+
elif base == "m#5":
|
| 19162 |
+
seventh = "7"
|
| 19163 |
+
elif base == "(b5)":
|
| 19164 |
+
seventh = "7"
|
| 19165 |
+
else:
|
| 19166 |
+
seventh = "7"
|
| 19167 |
+
elif has_M7:
|
| 19168 |
+
if base == "":
|
| 19169 |
+
seventh = "maj7"
|
| 19170 |
+
elif base == "m":
|
| 19171 |
+
seventh = "maj7"
|
| 19172 |
+
elif base == "dim":
|
| 19173 |
+
seventh = "M7"
|
| 19174 |
+
elif base == "aug":
|
| 19175 |
+
seventh = "maj7"
|
| 19176 |
+
elif base == "m#5":
|
| 19177 |
+
seventh = "maj7"
|
| 19178 |
+
elif base == "(b5)":
|
| 19179 |
+
seventh = "maj7"
|
| 19180 |
+
|
| 19181 |
+
# 6. Identify Extensions and Alterations
|
| 19182 |
+
extensions = []
|
| 19183 |
+
alterations = []
|
| 19184 |
+
|
| 19185 |
+
for iv in norm_ivs:
|
| 19186 |
+
if iv == 0: continue
|
| 19187 |
+
d = degree_map[iv]
|
| 19188 |
+
if d in triad_degrees: continue
|
| 19189 |
+
if d == "m7" and has_m7: continue
|
| 19190 |
+
if d == "M7" and has_M7: continue
|
| 19191 |
+
|
| 19192 |
+
if d == "9":
|
| 19193 |
+
extensions.append(d)
|
| 19194 |
+
elif d == "b9":
|
| 19195 |
+
alterations.append(d)
|
| 19196 |
+
elif d == "11":
|
| 19197 |
+
# Fixed addadd11 bug
|
| 19198 |
+
if has_m7 or has_M7:
|
| 19199 |
+
extensions.append(d)
|
| 19200 |
+
else:
|
| 19201 |
+
extensions.append("add11")
|
| 19202 |
+
elif d == "6":
|
| 19203 |
+
if has_m7 or has_M7:
|
| 19204 |
+
extensions.append(d)
|
| 19205 |
+
else:
|
| 19206 |
+
extensions.append("6")
|
| 19207 |
+
# Prevent conflicting and redundant b5/#5 alteration additions
|
| 19208 |
+
elif d == "P5" and has_b5 and not has_sharp5:
|
| 19209 |
+
alterations.append("#5")
|
| 19210 |
+
elif d == "P5":
|
| 19211 |
+
continue
|
| 19212 |
+
elif d == "#5":
|
| 19213 |
+
if "aug" in base:
|
| 19214 |
+
continue
|
| 19215 |
+
# If both b5 and #5 exist, we don't double-annotate #5 unless in 7th context
|
| 19216 |
+
if has_b5:
|
| 19217 |
+
continue
|
| 19218 |
+
alterations.append(d)
|
| 19219 |
+
elif d == "b5" and "(b5)" in base:
|
| 19220 |
+
continue
|
| 19221 |
+
|
| 19222 |
+
# 7. Formatting Rules
|
| 19223 |
+
# A 6th chord takes priority if no 7th is present
|
| 19224 |
+
is_sixth = "6" in extensions and not seventh
|
| 19225 |
+
if is_sixth:
|
| 19226 |
+
name = f"{R}{base}6"
|
| 19227 |
+
extensions.remove("6")
|
| 19228 |
+
if extensions:
|
| 19229 |
+
name += f"(add{''.join(extensions)})"
|
| 19230 |
+
if alterations:
|
| 19231 |
+
name += f"({','.join(alterations)})"
|
| 19232 |
+
return name
|
| 19233 |
+
|
| 19234 |
+
# Build the core chord name
|
| 19235 |
+
name = f"{R}{base}{seventh}"
|
| 19236 |
+
|
| 19237 |
+
if extensions:
|
| 19238 |
+
name += "".join(extensions)
|
| 19239 |
+
|
| 19240 |
+
if alterations:
|
| 19241 |
+
name += f"({','.join(alterations)})"
|
| 19242 |
+
|
| 19243 |
+
return name
|
| 19244 |
+
|
| 19245 |
+
###################################################################################
|
| 19246 |
+
|
| 19247 |
print('Module loaded!')
|
| 19248 |
print('=' * 70)
|
| 19249 |
print('Enjoy! :)')
|