asigalov61 commited on
Commit
3ddc763
·
verified ·
1 Parent(s): b8e5af3

Upload TMIDIX.py

Browse files
Files changed (1) hide show
  1. TMIDIX.py +386 -1
TMIDIX.py CHANGED
@@ -51,7 +51,7 @@ r'''############################################################################
51
 
52
  ###################################################################################
53
 
54
- __version__ = "26.2.16"
55
 
56
  print('=' * 70)
57
  print('TMIDIX Python module')
@@ -16094,6 +16094,391 @@ def squash_monophonic_escore_notes_pitches(escore_notes,
16094
 
16095
  ###################################################################################
16096
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16097
  print('Module loaded!')
16098
  print('=' * 70)
16099
  print('Enjoy! :)')
 
51
 
52
  ###################################################################################
53
 
54
+ __version__ = "26.2.17"
55
 
56
  print('=' * 70)
57
  print('TMIDIX Python module')
 
16094
 
16095
  ###################################################################################
16096
 
16097
+ def humanize_velocities_in_escore_notes(escore_notes):
16098
+
16099
+ if not escore_notes:
16100
+ return []
16101
+
16102
+ notes = [list(n) for n in escore_notes]
16103
+ notes.sort(key=lambda x: (x[1], x[4])) # Sort by time, then pitch
16104
+
16105
+ # -----------------------------------------------------------
16106
+ # 1. GLOBAL ANALYSIS (Grid, Phrases, Arcs)
16107
+ # -----------------------------------------------------------
16108
+
16109
+ onset_times = sorted(list(set([n[1] for n in notes])))
16110
+
16111
+ # --- Grid Estimation ---
16112
+ estimated_grid = 120
16113
+ if len(onset_times) > 1:
16114
+ intervals = [onset_times[i+1] - onset_times[i] for i in range(len(onset_times)-1)]
16115
+ active_intervals = [i for i in intervals if i > 0 and i < 960]
16116
+ if active_intervals:
16117
+ rounded_intervals = [round(i / 10) * 10 for i in active_intervals]
16118
+ valid_intervals = [r for r in rounded_intervals if r > 0]
16119
+ if valid_intervals:
16120
+ estimated_grid = max(set(valid_intervals), key=valid_intervals.count)
16121
+
16122
+ # --- Beat Grid Calculation ---
16123
+ if estimated_grid <= 120:
16124
+ beat_grid = estimated_grid * 4
16125
+ elif estimated_grid <= 240:
16126
+ beat_grid = estimated_grid * 2
16127
+ else:
16128
+ beat_grid = estimated_grid
16129
+
16130
+ # --- Phrase Detection ---
16131
+ melodic_onsets = sorted(list(set([n[1] for n in notes if n[3] != 9])))
16132
+ phrase_gap_threshold = beat_grid * 3
16133
+
16134
+ phrases = []
16135
+ current_phrase = []
16136
+
16137
+ if not melodic_onsets: melodic_onsets = onset_times
16138
+
16139
+ for i, t in enumerate(melodic_onsets):
16140
+ if not current_phrase:
16141
+ current_phrase.append(t)
16142
+ else:
16143
+ prev_t = melodic_onsets[i-1]
16144
+ if t - prev_t > phrase_gap_threshold:
16145
+ phrases.append(current_phrase)
16146
+ current_phrase = [t]
16147
+ else:
16148
+ current_phrase.append(t)
16149
+ if current_phrase:
16150
+ phrases.append(current_phrase)
16151
+
16152
+ # --- Global Arc Calculation ---
16153
+ total_duration = onset_times[-1] - onset_times[0] if onset_times else 0
16154
+ global_progress_map = {}
16155
+ for t in onset_times:
16156
+ progress = (t - onset_times[0]) / total_duration if total_duration > 0 else 0
16157
+ global_arc = math.cos((progress - 0.5) * math.pi) * 6 # Range -6 to +6
16158
+ global_progress_map[t] = global_arc
16159
+
16160
+ # -----------------------------------------------------------
16161
+ # 2. INSTRUMENT SEPARATION & PROCESSING
16162
+ # -----------------------------------------------------------
16163
+
16164
+ instrument_tracks = defaultdict(list)
16165
+ for n in notes:
16166
+ instrument_tracks[(n[3], n[6])].append(n)
16167
+
16168
+ # Main Loop
16169
+ for key, track_notes in instrument_tracks.items():
16170
+ channel, inst_num = key
16171
+
16172
+ # --- Instrument Classification ---
16173
+ is_drum = (channel == 9) or (inst_num == 128)
16174
+ is_bass = (32 <= inst_num <= 39)
16175
+ is_keys = (inst_num <= 7) or (16 <= inst_num <= 23)
16176
+ is_plucked = (24 <= inst_num <= 31)
16177
+ is_solo_string = (40 <= inst_num <= 47)
16178
+ is_ensemble = (48 <= inst_num <= 55)
16179
+ is_synth_lead = (80 <= inst_num <= 87)
16180
+ is_synth_pad = (88 <= inst_num <= 95)
16181
+
16182
+ track_notes.sort(key=lambda x: (x[1], x[4]))
16183
+
16184
+ # ==========================================================
16185
+ # LOGIC A: DRUMS (UNIFORM & SEPARATED)
16186
+ # ==========================================================
16187
+ if is_drum:
16188
+ for n in track_notes:
16189
+ t, pitch = n[1], n[4]
16190
+
16191
+ is_kick = pitch in [35, 36]
16192
+ is_snare = pitch in [38, 40]
16193
+ is_hat = pitch in [42, 44, 46]
16194
+ is_tom = 41 <= pitch <= 50 and not is_hat and not is_snare
16195
+
16196
+ if is_kick:
16197
+ target_vel = 115
16198
+ elif is_snare:
16199
+ target_vel = 112
16200
+ elif is_hat:
16201
+ target_vel = 90
16202
+ elif is_tom:
16203
+ target_vel = 105
16204
+ else:
16205
+ target_vel = 100
16206
+
16207
+ position_in_beat = t % beat_grid
16208
+ if position_in_beat == 0:
16209
+ metric_mod = 0
16210
+ elif position_in_beat == (beat_grid // 2):
16211
+ metric_mod = -2
16212
+ else:
16213
+ metric_mod = -6
16214
+
16215
+ closest_t = min(onset_times, key=lambda x: abs(x - t))
16216
+ global_mod = global_progress_map.get(closest_t, 0)
16217
+
16218
+ final_vel = target_vel + metric_mod + global_mod + random.gauss(0, 2.0)
16219
+ n[5] = max(30, min(127, int(final_vel)))
16220
+
16221
+ continue
16222
+
16223
+ # ==========================================================
16224
+ # LOGIC B: BASS
16225
+ # ==========================================================
16226
+ elif is_bass:
16227
+ last_vel = 100
16228
+ for n in track_notes:
16229
+ t = n[1]
16230
+
16231
+ position_in_grid = t % beat_grid
16232
+ metric_strength = 4 if position_in_grid == 0 else (1 if position_in_grid == (beat_grid // 2) else -2)
16233
+
16234
+ target = 100 + metric_strength
16235
+
16236
+ smoothed = (target * 0.3) + (last_vel * 0.7)
16237
+ last_vel = smoothed
16238
+
16239
+ final_vel = smoothed + random.gauss(0, 2)
16240
+ n[5] = max(30, min(115, int(final_vel)))
16241
+ continue
16242
+
16243
+ # ==========================================================
16244
+ # LOGIC C: MELODIC INSTRUMENTS
16245
+ # ==========================================================
16246
+ else:
16247
+ # --- Role Detection (Base Velocity Target) ---
16248
+ # Determine the 'floor' velocity for this track
16249
+
16250
+ base_vel_target = 90 # Default
16251
+
16252
+ if is_solo_string or is_synth_lead:
16253
+ base_vel_target = 105 # Lead
16254
+ elif is_ensemble or is_synth_pad:
16255
+ base_vel_target = 80 # Pad/Backing
16256
+ elif is_plucked:
16257
+ # Check density for Guitar Solo vs Rhythm
16258
+ unique_onsets = set(nn[1] for nn in track_notes)
16259
+ avg_poly = len(track_notes) / len(unique_onsets) if unique_onsets else 1
16260
+ base_vel_target = 105 if avg_poly < 1.3 else 95
16261
+ elif is_keys:
16262
+ # Check if it looks like a Solo Piano piece or just a Piano track
16263
+ # If the whole score is basically this track -> Solo
16264
+ if len(track_notes) > (len(notes) * 0.8):
16265
+ base_vel_target = 100
16266
+ else:
16267
+ base_vel_target = 90
16268
+
16269
+ # --- Dispatch to specific logic ---
16270
+
16271
+ # CASE 1: PIANO / KEYS (RESTORED ORIGINAL LOGIC)
16272
+ if is_keys:
16273
+ last_vel_lh = base_vel_target
16274
+ last_vel_rh = base_vel_target
16275
+ prev_melody_pitch = None
16276
+
16277
+ time_slices = defaultdict(list)
16278
+ for n in track_notes:
16279
+ time_slices[n[1]].append(n)
16280
+ sorted_times = sorted(time_slices.keys())
16281
+
16282
+ for t_idx, t in enumerate(sorted_times):
16283
+ slice_notes = time_slices[t]
16284
+ slice_notes.sort(key=lambda x: x[4])
16285
+
16286
+ # Context Mods
16287
+ phrase_arc_mod = 0
16288
+ for p_times in phrases:
16289
+ if t in p_times:
16290
+ p_len = p_times[-1] - p_times[0]
16291
+ if p_len > 0:
16292
+ progress = (t - p_times[0]) / p_len
16293
+ phrase_arc_mod = math.cos((1.0 - progress) * math.pi) * 0.5 + 0.5
16294
+ phrase_arc_mod = (phrase_arc_mod - 0.5) * 10
16295
+ break
16296
+
16297
+ global_arc = global_progress_map.get(t, 0)
16298
+
16299
+ position_in_grid = t % beat_grid
16300
+ if position_in_grid == 0: metric_strength = 6
16301
+ elif position_in_grid == (beat_grid // 2): metric_strength = 2
16302
+ else: metric_strength = -3
16303
+
16304
+ # Hand Splitting Logic
16305
+ pitches = [n[4] for n in slice_notes]
16306
+ split_idx = 0
16307
+ max_gap = 0
16308
+
16309
+ if len(pitches) > 1:
16310
+ for i in range(len(pitches)-1):
16311
+ gap = pitches[i+1] - pitches[i]
16312
+ if gap > max_gap:
16313
+ max_gap = gap
16314
+ split_idx = i + 1
16315
+
16316
+ if max_gap < 12:
16317
+ avg_pitch = sum(pitches) / len(pitches)
16318
+ split_idx = 0 if avg_pitch >= 60 else len(pitches)
16319
+
16320
+ lh_notes = slice_notes[:split_idx]
16321
+ rh_notes = slice_notes[split_idx:]
16322
+
16323
+ # LH Processing
16324
+ if lh_notes:
16325
+ target_lh = base_vel_target + metric_strength + global_arc + phrase_arc_mod - 3
16326
+ smoothed_lh = (target_lh * 0.25) + (last_vel_lh * 0.75)
16327
+ last_vel_lh = smoothed_lh
16328
+
16329
+ for i, n in enumerate(lh_notes):
16330
+ offset = 0 if i == 0 else -5
16331
+ if n[2] < estimated_grid / 2: offset += 2
16332
+ vel = smoothed_lh + offset + random.gauss(0, 2)
16333
+ n[5] = max(30, min(115, int(vel)))
16334
+
16335
+ # RH Processing
16336
+ if rh_notes:
16337
+ target_rh = base_vel_target + metric_strength + global_arc + phrase_arc_mod + 3
16338
+ smoothed_rh = (target_rh * 0.35) + (last_vel_rh * 0.65)
16339
+ last_vel_rh = smoothed_rh
16340
+
16341
+ current_top_pitch = rh_notes[-1][4]
16342
+ num_rh = len(rh_notes)
16343
+ is_dense = num_rh >= 3
16344
+
16345
+ for i, n in enumerate(rh_notes):
16346
+ pitch = n[4]
16347
+ offset = 0
16348
+
16349
+ if i == num_rh - 1: # Top note melody
16350
+ offset = 12 if is_dense else 5
16351
+ if prev_melody_pitch is not None:
16352
+ diff = pitch - prev_melody_pitch
16353
+ if diff > 4: offset += 6
16354
+ elif diff > 0: offset += 3
16355
+ elif diff < -4: offset -= 4
16356
+ if pitch > 72: offset += (pitch - 72) * 0.2
16357
+ prev_melody_pitch = pitch
16358
+ elif i == 0:
16359
+ offset = -1
16360
+ else:
16361
+ offset = -8
16362
+
16363
+ if n[2] < estimated_grid / 2: offset += 2
16364
+ vel = smoothed_rh + offset + random.gauss(0, 2)
16365
+ n[5] = max(35, min(120, int(vel)))
16366
+
16367
+ # CASE 2: OTHER MELODIC (Generic Role-Based Logic)
16368
+ else:
16369
+ last_vel = base_vel_target
16370
+ prev_melody_pitch = None
16371
+
16372
+ time_slices = defaultdict(list)
16373
+ for n in track_notes:
16374
+ time_slices[n[1]].append(n)
16375
+ sorted_times = sorted(time_slices.keys())
16376
+
16377
+ for t_idx, t in enumerate(sorted_times):
16378
+ slice_notes = time_slices[t]
16379
+ slice_notes.sort(key=lambda x: x[4])
16380
+
16381
+ # Context
16382
+ phrase_arc_mod = 0
16383
+ for p_times in phrases:
16384
+ if t in p_times:
16385
+ p_len = p_times[-1] - p_times[0]
16386
+ if p_len > 0:
16387
+ progress = (t - p_times[0]) / p_len
16388
+ phrase_arc_mod = math.sin(progress * math.pi) * 6
16389
+ break
16390
+
16391
+ global_arc = global_progress_map.get(t, 0)
16392
+
16393
+ position_in_grid = t % beat_grid
16394
+ if position_in_grid == 0: metric_strength = 6
16395
+ elif position_in_grid == (beat_grid // 2): metric_strength = 2
16396
+ else: metric_strength = -3
16397
+
16398
+ core_vel = base_vel_target + metric_strength + global_arc + phrase_arc_mod
16399
+ smoothed = (core_vel * 0.35) + (last_vel * 0.65)
16400
+ last_vel = smoothed
16401
+
16402
+ num_notes = len(slice_notes)
16403
+
16404
+ for i, n in enumerate(slice_notes):
16405
+ pitch = n[4]
16406
+ offset = 0
16407
+
16408
+ if i == num_notes - 1:
16409
+ offset = 10
16410
+ if prev_melody_pitch is not None:
16411
+ diff = pitch - prev_melody_pitch
16412
+ if diff > 4: offset += 5
16413
+ prev_melody_pitch = pitch
16414
+ elif i == 0:
16415
+ offset = 0
16416
+ else:
16417
+ offset = -6
16418
+
16419
+ final_vel = smoothed + offset + random.gauss(0, 3)
16420
+ n[5] = max(30, min(127, int(final_vel)))
16421
+
16422
+ # -----------------------------------------------------------
16423
+ # 3. FINAL EXPRESSIVE SCALING
16424
+ # -----------------------------------------------------------
16425
+ for n in notes:
16426
+ if n[3] == 9:
16427
+ continue
16428
+
16429
+ v = n[5]
16430
+ center = 95
16431
+
16432
+ deviation = v - center
16433
+ final_v = center + (deviation * 1.1)
16434
+ final_v += random.randint(-1, 1)
16435
+
16436
+ n[5] = max(20, min(127, int(final_v)))
16437
+
16438
+ return notes
16439
+
16440
+ ###################################################################################
16441
+
16442
+ def most_common_ordered_set(values):
16443
+
16444
+ freq = Counter(values)
16445
+
16446
+ top_vals = {v for v, _ in freq.most_common()}
16447
+
16448
+ result = []
16449
+ seen = set()
16450
+
16451
+ for v in values:
16452
+ if v in top_vals and v not in seen:
16453
+ result.append(v)
16454
+ seen.add(v)
16455
+
16456
+ return result
16457
+
16458
+ ###################################################################################
16459
+
16460
+ def escore_notes_velocities(escore_notes, chan_idx=3, vels_idx=5):
16461
+
16462
+ output_list = []
16463
+
16464
+ all_vels = [e[vels_idx] for e in escore_notes]
16465
+ avg_vel = sum(all_vels) / len(all_vels)
16466
+ vels_span = max(all_vels) - min(all_vels)
16467
+
16468
+ output_list.append([-1, min(all_vels), avg_vel, max(all_vels), vels_span])
16469
+
16470
+ chan_groups = groupby(sorted(escore_notes, key=lambda x: x[chan_idx]), key=lambda x: x[chan_idx])
16471
+
16472
+ for cha, group in chan_groups:
16473
+ all_vels = [e[vels_idx] for e in list(group)]
16474
+ avg_vel = sum(all_vels) / len(all_vels)
16475
+ vels_span = max(all_vels) - min(all_vels)
16476
+ output_list.append([cha, min(all_vels), avg_vel, max(all_vels), vels_span])
16477
+
16478
+ return output_list
16479
+
16480
+ ###################################################################################
16481
+
16482
  print('Module loaded!')
16483
  print('=' * 70)
16484
  print('Enjoy! :)')