asigalov61 commited on
Commit
2745d37
Β·
verified Β·
1 Parent(s): 4dea8d5

Upload TMIDIX.py

Browse files
Files changed (1) hide show
  1. TMIDIX.py +448 -0
TMIDIX.py CHANGED
@@ -1522,6 +1522,8 @@ from array import array
1522
  from pathlib import Path
1523
  from fnmatch import fnmatch
1524
 
 
 
1525
  ###################################################################################
1526
  #
1527
  # Original TMIDI Tegridy helper functions
@@ -15168,6 +15170,452 @@ def chords_to_escore_notes(chords,
15168
 
15169
  ###################################################################################
15170
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15171
  print('Module loaded!')
15172
  print('=' * 70)
15173
  print('Enjoy! :)')
 
1522
  from pathlib import Path
1523
  from fnmatch import fnmatch
1524
 
1525
+ from typing import List, Optional
1526
+
1527
  ###################################################################################
1528
  #
1529
  # Original TMIDI Tegridy helper functions
 
15170
 
15171
  ###################################################################################
15172
 
15173
+ def _median(vals: List[float]) -> float:
15174
+ return statistics.median(vals) if vals else 0.0
15175
+
15176
+ ###################################################################################
15177
+
15178
+ def _mad(vals: List[float], center: Optional[float] = None) -> float:
15179
+ if not vals:
15180
+ return 0.0
15181
+ if center is None:
15182
+ center = _median(vals)
15183
+ return _median([abs(x - center) for x in vals])
15184
+
15185
+ ###################################################################################
15186
+
15187
+ def _rep(vals: List[float], method: str) -> float:
15188
+ if method == "mean":
15189
+ return statistics.mean(vals)
15190
+ if method == "mode":
15191
+ try:
15192
+ return statistics.mode(vals)
15193
+ except statistics.StatisticsError:
15194
+ return statistics.median(vals)
15195
+ return statistics.median(vals)
15196
+
15197
+ ###################################################################################
15198
+
15199
+ def _round_int(v: float, mode: Optional[str]) -> int:
15200
+ if mode is None or mode == "round":
15201
+ return int(round(v))
15202
+ if mode == "ceil":
15203
+ return int(math.ceil(v))
15204
+ if mode == "floor":
15205
+ return int(math.floor(v))
15206
+ raise ValueError("Unknown round mode")
15207
+
15208
+ ###################################################################################
15209
+
15210
+ def even_out_values_in_list_of_lists(
15211
+ data: List[List[float]],
15212
+ rep_method: str = "median",
15213
+ z_thresh: float = 3.0,
15214
+ local_ratio_thresh: float = 3.0,
15215
+ gap_ratio_thresh: float = 0.45,
15216
+ merge_factor: float = 1.0,
15217
+ span_tol_factor: float = 1.0,
15218
+ min_cluster_size: int = 1,
15219
+ round_mode: Optional[str] = "round",
15220
+ max_k_for_kmeans: int = 5
15221
+ ) -> List[List[int]]:
15222
+
15223
+ # small deterministic 1D kmeans for fallback
15224
+ def _kmeans_1d(values, k, rng_seed=42, max_iter=100):
15225
+ n = len(values)
15226
+ if k <= 1 or k >= n:
15227
+ return [statistics.mean(values)]*k if k>0 else [], list(range(n))
15228
+ rng = random.Random(rng_seed)
15229
+ centroids = [rng.choice(values)]
15230
+ for _ in range(1, k):
15231
+ d2 = [min((v - c)**2 for c in centroids) for v in values]
15232
+ total = sum(d2)
15233
+ if total == 0:
15234
+ centroids.append(rng.choice(values))
15235
+ continue
15236
+ r = rng.random() * total
15237
+ cum = 0.0
15238
+ for v, w in zip(values, d2):
15239
+ cum += w
15240
+ if cum >= r:
15241
+ centroids.append(v)
15242
+ break
15243
+ labels = [0]*n
15244
+ for _ in range(max_iter):
15245
+ changed = False
15246
+ for i, v in enumerate(values):
15247
+ best = min(range(k), key=lambda j: abs(v - centroids[j]))
15248
+ if labels[i] != best:
15249
+ labels[i] = best
15250
+ changed = True
15251
+ new_centroids = []
15252
+ for j in range(k):
15253
+ members = [v for v, lab in zip(values, labels) if lab == j]
15254
+ new_centroids.append(sum(members)/len(members) if members else rng.choice(values))
15255
+ if all(abs(a-b) < 1e-12 for a,b in zip(centroids, new_centroids)):
15256
+ break
15257
+ centroids = new_centroids
15258
+ if not changed:
15259
+ break
15260
+ return centroids, labels
15261
+
15262
+ def _silhouette_1d(values, labels):
15263
+ n = len(values)
15264
+ if n <= 1:
15265
+ return 0.0
15266
+ clusters = {}
15267
+ for v, lab in zip(values, labels):
15268
+ clusters.setdefault(lab, []).append(v)
15269
+ if len(clusters) == 1:
15270
+ return 0.0
15271
+ total_s = 0.0
15272
+ for i, v in enumerate(values):
15273
+ lab = labels[i]
15274
+ own = clusters[lab]
15275
+ a = 0.0
15276
+ if len(own) > 1:
15277
+ a = sum(abs(v - u) for u in own if u is not v) / (len(own)-1)
15278
+ b = float("inf")
15279
+ for other_lab, members in clusters.items():
15280
+ if other_lab == lab:
15281
+ continue
15282
+ dist = sum(abs(v - u) for u in members) / len(members)
15283
+ if dist < b:
15284
+ b = dist
15285
+ denom = max(a, b)
15286
+ s = 0.0 if denom == 0 else (b - a) / denom
15287
+ total_s += s
15288
+ return total_s / n
15289
+
15290
+ out: List[List[int]] = []
15291
+
15292
+ for row in data:
15293
+ if not row:
15294
+ out.append([])
15295
+ continue
15296
+ if len(row) == 1:
15297
+ out.append([_round_int(float(row[0]), round_mode)])
15298
+ continue
15299
+
15300
+ indexed = sorted(((float(v), idx) for idx, v in enumerate(row)), key=lambda x: x[0])
15301
+ sorted_vals = [v for v, _ in indexed]
15302
+ n = len(sorted_vals)
15303
+
15304
+ if all(v == sorted_vals[0] for v in sorted_vals):
15305
+ rep_int = _round_int(_rep(sorted_vals, rep_method), round_mode)
15306
+ out.append([rep_int]*n)
15307
+ continue
15308
+
15309
+ # adjacent diffs and robust stats
15310
+ diffs = [sorted_vals[i+1] - sorted_vals[i] for i in range(n-1)]
15311
+ med = _median(diffs)
15312
+ mad = _mad(diffs, med)
15313
+ z_scores = [(d - med) / (mad + 1e-12) for d in diffs]
15314
+
15315
+ # initial cut heuristics
15316
+ cuts = set(idx for idx, z in enumerate(z_scores) if z > z_thresh)
15317
+ if diffs:
15318
+ # percentile fallback
15319
+ k_idx = max(0, min(len(diffs)-1, int(round(len(diffs)*0.9)) - 1))
15320
+ pcut = sorted(diffs)[k_idx]
15321
+ cuts.update(idx for idx, d in enumerate(diffs) if d >= pcut and d > 0)
15322
+ # global gap ratio
15323
+ maxd = max(diffs)
15324
+ cuts.update(idx for idx, d in enumerate(diffs) if d >= maxd * gap_ratio_thresh and d > 0)
15325
+ # local relative gap
15326
+ for i, d in enumerate(diffs):
15327
+ neigh = []
15328
+ for j in range(i-2, i+3):
15329
+ if j != i and 0 <= j < len(diffs):
15330
+ neigh.append(diffs[j])
15331
+ if not neigh:
15332
+ continue
15333
+ local_med = _median(neigh)
15334
+ denom = local_med if local_med > 1e-12 else (med if med > 1e-12 else 1.0)
15335
+ if d / denom >= local_ratio_thresh and d > 0:
15336
+ cuts.add(i)
15337
+
15338
+ # build clusters from cuts
15339
+ if not cuts:
15340
+ clusters = [sorted_vals[:]]
15341
+ else:
15342
+ clusters = []
15343
+ start = 0
15344
+ for i in range(len(diffs)):
15345
+ if i in cuts:
15346
+ clusters.append(sorted_vals[start:i+1])
15347
+ start = i+1
15348
+ clusters.append(sorted_vals[start:])
15349
+
15350
+ # --- Merge Logic (Extracted) ---
15351
+ def _do_merges(clusters_in):
15352
+ # compute representatives and cluster spans
15353
+ reps = [_rep(cl, rep_method) for cl in clusters_in]
15354
+ spans = [ (max(cl)-min(cl)) if len(cl)>1 else 0.0 for cl in clusters_in ]
15355
+
15356
+ # merge-adjacent rule
15357
+ row_mad = _mad(sorted_vals, _median(sorted_vals))
15358
+ merge_tol = max(1.0, merge_factor * row_mad)
15359
+
15360
+ merged = []
15361
+ merged_reps = []
15362
+ merged_spans = []
15363
+ k = 0
15364
+ while k < len(clusters_in):
15365
+ cur = clusters_in[k][:]
15366
+ cur_rep = reps[k]
15367
+ cur_span = spans[k]
15368
+ # Merge adjacent clusters if reps are close
15369
+ while k+1 < len(clusters_in) and abs(cur_rep - reps[k+1]) <= merge_tol:
15370
+ cur += clusters_in[k+1]
15371
+ cur_rep = _rep(cur, rep_method)
15372
+ cur_span = max(cur_span, spans[k+1], max(cur)-min(cur))
15373
+ k += 1
15374
+ merged.append(cur)
15375
+ merged_reps.append(cur_rep)
15376
+ merged_spans.append(cur_span)
15377
+ k += 1
15378
+
15379
+ current_clusters = merged
15380
+ current_reps = merged_reps
15381
+ current_spans = merged_spans
15382
+
15383
+ # span-based merge
15384
+ span_tol = max(1.0, span_tol_factor * row_mad)
15385
+ if len(current_clusters) > 1:
15386
+ i = 0
15387
+ while i < len(current_clusters):
15388
+ if len(current_clusters[i]) < min_cluster_size or current_spans[i] <= span_tol:
15389
+ left = i-1 if i-1 >= 0 else None
15390
+ right = i+1 if i+1 < len(current_clusters) else None
15391
+
15392
+ merged_flag = False
15393
+ # Try merge right
15394
+ if left is None and right is not None:
15395
+ if abs(current_reps[i] - current_reps[right]) <= merge_tol:
15396
+ current_clusters[right] = current_clusters[i] + current_clusters[right]
15397
+ del current_clusters[i]
15398
+ current_reps = [_rep(cl, rep_method) for cl in current_clusters]
15399
+ current_spans = [ (max(cl)-min(cl)) if len(cl)>1 else 0.0 for cl in current_clusters]
15400
+ merged_flag = True
15401
+ # Try merge left
15402
+ elif right is None and left is not None:
15403
+ if abs(current_reps[i] - current_reps[left]) <= merge_tol:
15404
+ current_clusters[left] = current_clusters[left] + current_clusters[i]
15405
+ del current_clusters[i]
15406
+ current_reps = [_rep(cl, rep_method) for cl in current_clusters]
15407
+ current_spans = [ (max(cl)-min(cl)) if len(cl)>1 else 0.0 for cl in current_clusters]
15408
+ merged_flag = True
15409
+ i -= 1 # Adjust index since we removed an element before i
15410
+ # Try merge closest neighbor
15411
+ elif left is not None and right is not None:
15412
+ dist_left = abs(current_reps[i] - current_reps[left])
15413
+ dist_right = abs(current_reps[i] - current_reps[right])
15414
+
15415
+ target = -1
15416
+ if dist_left <= dist_right:
15417
+ if dist_left <= merge_tol:
15418
+ target = left
15419
+ else:
15420
+ if dist_right <= merge_tol:
15421
+ target = right
15422
+
15423
+ if target != -1:
15424
+ if target == left:
15425
+ current_clusters[left] = current_clusters[left] + current_clusters[i]
15426
+ del current_clusters[i]
15427
+ merged_flag = True
15428
+ i -= 1
15429
+ else: # target == right
15430
+ current_clusters[right] = current_clusters[i] + current_clusters[right]
15431
+ del current_clusters[i]
15432
+ merged_flag = True
15433
+
15434
+ current_reps = [_rep(cl, rep_method) for cl in current_clusters]
15435
+ current_spans = [ (max(cl)-min(cl)) if len(cl)>1 else 0.0 for cl in current_clusters]
15436
+
15437
+ if not merged_flag:
15438
+ i += 1
15439
+ else:
15440
+ i += 1
15441
+ return current_clusters
15442
+
15443
+ # Apply merges to initial heuristic clusters
15444
+ clusters = _do_merges(clusters)
15445
+
15446
+ # final fallback: kmeans
15447
+ # Check condition again because merges might have reduced cluster count
15448
+ if len(clusters) <= 2 and n >= 4:
15449
+ best_score = -1.0
15450
+ best_labels = None
15451
+ best_k = 1
15452
+ max_k = min(max_k_for_kmeans, n)
15453
+ for k_try in range(1, max_k+1):
15454
+ _, labels = _kmeans_1d(sorted_vals, k_try)
15455
+ score = 0.0 if k_try==1 else _silhouette_1d(sorted_vals, labels)
15456
+ if score > best_score + 1e-12:
15457
+ best_score = score
15458
+ best_labels = labels[:]
15459
+ best_k = k_try
15460
+
15461
+ if best_labels is not None and best_k > 1 and best_score > 0:
15462
+ clusters_k = []
15463
+ cur_lab = best_labels[0]
15464
+ cur_cl = [sorted_vals[0]]
15465
+ for v, lab in zip(sorted_vals[1:], best_labels[1:]):
15466
+ if lab == cur_lab:
15467
+ cur_cl.append(v)
15468
+ else:
15469
+ clusters_k.append(cur_cl)
15470
+ cur_cl = [v]
15471
+ cur_lab = lab
15472
+ clusters_k.append(cur_cl)
15473
+
15474
+ # Only replace if Kmeans found more clusters than we currently have
15475
+ # AND crucially, run the merge logic on these new clusters
15476
+ if len(clusters_k) > len(clusters):
15477
+ clusters = _do_merges(clusters_k)
15478
+
15479
+ # compute final reps and round to ints, map back to original order
15480
+ final_reps = [_rep(cl, rep_method) for cl in clusters]
15481
+ rep_values_sorted = []
15482
+ for rep, cl in zip(final_reps, clusters):
15483
+ rep_int = _round_int(rep, round_mode)
15484
+ rep_values_sorted.extend([rep_int] * len(cl))
15485
+
15486
+ row_ints = [0]*n
15487
+ for (_, orig_idx), rep_int in zip(indexed, rep_values_sorted):
15488
+ row_ints[orig_idx] = rep_int
15489
+
15490
+ out.append(row_ints)
15491
+
15492
+ return out
15493
+
15494
+ ###################################################################################
15495
+
15496
+ def even_out_durations_in_escore_notes(escore_notes, min_notes_gap=0):
15497
+
15498
+ escore_notes = fix_escore_notes_durations(escore_notes,
15499
+ min_notes_gap=min_notes_gap
15500
+ )
15501
+
15502
+ score = [e for e in escore_notes if e[3] != 9]
15503
+ drums = [e for e in escore_notes if e[3] == 9]
15504
+
15505
+ cscore = chordify_score([1000, score])
15506
+
15507
+ durs = []
15508
+
15509
+ for c in cscore:
15510
+ durs.append([e[2] for e in c])
15511
+
15512
+ ndurs = even_out_values_in_list_of_lists(durs)
15513
+
15514
+ output_score = []
15515
+
15516
+ for i, c in enumerate(cscore):
15517
+ cc = copy.deepcopy(c)
15518
+ ndur = ndurs[i]
15519
+
15520
+ for j, e in enumerate(cc):
15521
+ e[2] = ndur[j]
15522
+
15523
+ output_score.extend(cc)
15524
+
15525
+ return sorted(output_score + drums, key=lambda x: (x[1], -x[4]))
15526
+
15527
+ ###################################################################################
15528
+
15529
+ def even_out_velocities_in_escore_notes(escore_notes, use_pitches=False):
15530
+
15531
+ score = [e for e in escore_notes if e[3] != 9]
15532
+ drums = [e for e in escore_notes if e[3] == 9]
15533
+
15534
+ cscore = chordify_score([1000, score])
15535
+
15536
+ vels = []
15537
+
15538
+ for c in cscore:
15539
+ if use_pitches:
15540
+ vels.append([max(40, e[4]) for e in c])
15541
+
15542
+ else:
15543
+ vels.append([e[5] for e in c])
15544
+
15545
+ nvels = even_out_values_in_list_of_lists(vels)
15546
+
15547
+ output_score = []
15548
+
15549
+ for i, c in enumerate(cscore):
15550
+ cc = copy.deepcopy(c)
15551
+ nvel = nvels[i]
15552
+
15553
+ for j, e in enumerate(cc):
15554
+ e[5] = nvel[j]
15555
+
15556
+ output_score.extend(cc)
15557
+
15558
+ return sorted(output_score + drums, key=lambda x: (x[1], -x[4]))
15559
+
15560
+ ###################################################################################
15561
+
15562
+ # Most probable MIDI velocity per GM instrument (0-127) + drums (128)
15563
+ INSTRUMENTS_VELOCITIES_MAP = {
15564
+
15565
+ # 0–7 Pianos
15566
+ 0: 82, 1: 82, 2: 84, 3: 84, 4: 86, 5: 86, 6: 88, 7: 88,
15567
+
15568
+ # 8–15 Chromatic Percussion
15569
+ 8: 78, 9: 78, 10: 80, 11: 80, 12: 82, 13: 82, 14: 84, 15: 84,
15570
+
15571
+ # 16–23 Organs
15572
+ 16: 76, 17: 76, 18: 78, 19: 78, 20: 78, 21: 78, 22: 80, 23: 80,
15573
+
15574
+ # 24–31 Guitars
15575
+ 24: 80, 25: 80, 26: 82, 27: 82, 28: 84, 29: 84, 30: 86, 31: 86,
15576
+
15577
+ # 32–39 Basses
15578
+ 32: 84, 33: 84, 34: 86, 35: 86, 36: 88, 37: 88, 38: 90, 39: 90,
15579
+
15580
+ # 40–47 Strings
15581
+ 40: 74, 41: 74, 42: 74, 43: 74, 44: 76, 45: 76, 46: 76, 47: 76,
15582
+
15583
+ # 48–55 Ensemble
15584
+ 48: 76, 49: 76, 50: 78, 51: 78, 52: 78, 53: 78, 54: 80, 55: 80,
15585
+
15586
+ # 56–63 Brass
15587
+ 56: 86, 57: 86, 58: 88, 59: 88, 60: 90, 61: 90, 62: 92, 63: 92,
15588
+
15589
+ # 64–71 Reed
15590
+ 64: 74, 65: 74, 66: 76, 67: 76, 68: 76, 69: 76, 70: 78, 71: 78,
15591
+
15592
+ # 72–79 Pipe
15593
+ 72: 72, 73: 72, 74: 74, 75: 74, 76: 74, 77: 74, 78: 76, 79: 76,
15594
+
15595
+ # 80–87 Synth Lead
15596
+ 80: 88, 81: 88, 82: 90, 83: 90, 84: 90, 85: 90, 86: 92, 87: 92,
15597
+
15598
+ # 88–95 Synth Pad
15599
+ 88: 70, 89: 70, 90: 72, 91: 72, 92: 74, 93: 74, 94: 74, 95: 74,
15600
+
15601
+ # 96–103 Synth Effects
15602
+ 96: 76, 97: 76, 98: 78, 99: 78, 100: 80, 101: 80, 102: 82, 103: 82,
15603
+
15604
+ # 104–111 Ethnic
15605
+ 104: 74, 105: 74, 106: 76, 107: 76, 108: 76, 109: 76, 110: 78, 111: 78,
15606
+
15607
+ # 112–119 Percussive
15608
+ 112: 86, 113: 86, 114: 88, 115: 88, 116: 88, 117: 88, 118: 90, 119: 90,
15609
+
15610
+ # 120–127 Sound Effects
15611
+ 120: 72, 121: 72, 122: 74, 123: 74, 124: 74, 125: 74, 126: 76, 127: 76,
15612
+
15613
+ # 128 Drums (GM Channel 10)
15614
+ 128: 92,
15615
+ }
15616
+
15617
+ ###################################################################################
15618
+
15619
  print('Module loaded!')
15620
  print('=' * 70)
15621
  print('Enjoy! :)')