Repository navigation
Expand file tree
/
Copy pathMusic.py
More file actions
1131 lines (1045 loc) · 50.4 KB
/
Copy pathMusic.py
File metadata and controls
1131 lines (1045 loc) · 50.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import numpy, copy
from collections import defaultdict, OrderedDict
from itertools import groupby, zip_longest
from music21 import *
from utils.TermColour import bcolors
from numpy.random import Generator, PCG64DXSM
rng = Generator(PCG64DXSM())
# https://github.com/evancchow/jazzml
# https://github.com/jisungk/deepjazz
def __parse_midi_details(data_fn):
print(f"\n=== {__parse_midi_details.__name__} ===")
# Parse the MIDI data for separate melody and accompaniment parts.
midi_data = converter.parse(data_fn)
assert len(midi_data.getElementsByClass(stream.Part)) == len(midi_data.parts)
allnotes = []
allchords = []
melody_voice = {}
idxPart = 0
timeSig = []
mmark = []
for part in midi_data.recurse().getElementsByClass(stream.Part):
print(f"\npart {idxPart}: {part}")
part.show("text")
idxPart += 1
idxMeasure = 0
for measure in part.recurse().getElementsByClass(stream.Measure):
print(f"measure {idxMeasure}: {measure}")
measure.show("text")
idxMeasure += 1
timeSig.append(measure.getElementsByClass(meter.TimeSignature))
mmark.append(measure.getElementsByClass(tempo.MetronomeMark))
idxVoice = 0
for voice in measure.recurse().getElementsByClass(stream.Voice):
print(f"voice {idxVoice}: {voice.offset}, {voice}")
voice.show("text")
idxVoice += 1
if voice.offset not in melody_voice:
melody_voice[voice.offset] = [voice]
else:
melody_voice[voice.offset].append(voice)
notes = voice.recurse().getElementsByClass(note.Note).notes
chords = voice.recurse().getElementsByClass(chord.Chord)
#for i in notes:
allnotes.append(notes)
#for i in chords:
allchords.append(chords)
"""
for voice in measure.getElementsByClass(stream.Voice):
print(f"voice {idxVoice}: {voice.offset}, {voice}")
voice.show("text")
idxVoice += 1
if voice.offset not in melody_voice:
melody_voice.add(voice.offset, voice)
notes = voice.getElementsByClass(note.Note).notes
chords = voice.getElementsByClass(chord.Chord)
for i in notes:
allnotes.append(i)
for i in chords:
allchords.append(i)
"""
#print(f"\nmmark:")
#mmark.stream().show("text")
#print(f"\ntimeSig:")
#timeSig.stream().show("text")
print(f"\n{len(timeSig)} timeSig, {len(mmark)} mmark")
print(f"\n{len(allchords)} chords, {len(allnotes)} notes, {len(melody_voice)} melody_voice") # 8075 chords, 15390 notes, 9766 melody_voice
print(f"\nmelody_voice: {melody_voice}")
def __parse_midi_part(data_fn, partIndex:int = None):
print(f"\n=== {__parse_midi_part.__name__} ===")
# Parse the MIDI data for separate melody and accompaniment parts.
# Part-Measure-Voice.
# melody_stream = midi_data[5] # For Metheny piece, Melody is Part #5.
midi_data = converter.parse(data_fn)
assert len(midi_data.getElementsByClass(stream.Part)) == len(midi_data.parts)
allnotes = []
allchords = []
melody_voice = []
idxPart = 0
timeSig: stream.Part = None
mmark: stream.Part = None
idxPart += 1
idxMeasure = 0
part = midi_data.getElementsByClass(stream.Part)[partIndex]
print(f"\npart {partIndex}:")
part.show("text")
for measure in part.getElementsByClass(stream.Measure):
print(f"measure {idxMeasure}: {measure}")
measure.show("text")
idxMeasure += 1
tmp = measure.getElementsByClass(meter.TimeSignature)
if tmp:
timeSig = tmp
tmp = measure.getElementsByClass(tempo.MetronomeMark)
if tmp:
mmark = tmp
idxVoice = 0
for voice in measure.getElementsByClass(stream.Voice()):
print(f"voice {idxVoice}: {voice.offset}, {voice}")
voice.show("text")
idxVoice += 1
melody_voice.insert(voice.offset, voice)
notes = voice.getElementsByClass(note.Note).notes
chords = voice.getElementsByClass(chord.Chord)
for i in notes:
allnotes.append(i)
for i in chords:
allchords.append(i)
print(f"\nmmark:")
mmark.stream().show("text")
print(f"\ntimeSig:")
timeSig.stream().show("text")
print(f"\n{len(allchords)} chords, {len(allnotes)} notes, {len(melody_voice)} melody_voice") # 82 chords, 232 notes, 84 melody_voice
#print(f"MetronomeMark: {mmark.number}, TimeSignature: {timeSig.numerator / timeSig.denominator}")
print("\nFullName, CommonName, Len, Offset")
for i in allchords:
print(f"{i.fullName}, {i.pitchedCommonName}, {i.quarterLength}, {float(i.offset)}")
for i in melody_voice:
if i.quarterLength == 0.0:
i.quarterLength = 0.25
# Change key signature to adhere to comp_stream (1 sharp, mode = major).
# Also add Electric Guitar.
melody_voice.insert(0, instrument.ElectricGuitar())
melody_voice.insert(0, key.KeySignature(sharps=1))
print(f"{len(melody_voice)} melody_voice")
# The accompaniment parts. Take only the best subset of parts from
# the original data. Maybe add more parts, hand-add valid instruments.
# Should at least add a string part (for sparse solos).
# Verified are good parts: 0, 1, 6, 7 '''
partIndices = [0, 1, 6, 7]
accompaniment = stream.Voice()
#print("\nenumerate(midi_data)")
#for i, j in enumerate(midi_data):
# if i in partIndices and not isinstance(j, metadata.Metadata):
# print(f"{i}: {j}, {j}")
# j.show("text")
#comp_stream = measure_stream.voices.stream()
accompaniment.append([j.flatten() for i, j in enumerate(midi_data) if i in partIndices and not isinstance(j, metadata.Metadata)])
print(f"\naccompaniment: {len(accompaniment)}")
#comp_stream.show("text")
# Full stream containing both the melody and the accompaniment.
# All parts are flattened.
full_stream = stream.Voice()
#full_stream = measure_stream.voices.stream()
for i in accompaniment:
full_stream.append(i)
full_stream.append(melody_voice)
print(f"\nfull_stream: {len(full_stream)}")
solo_stream = stream.Voice()
for part in full_stream:
#print(f"\npart: {part}")
#part.show("text")
curr_part = stream.Part()
if isinstance(part, stream.Part):
print(f"\ninstrument.Instrument:")
for i in part.getElementsByClass(instrument.Piano):
print(f"instrument: {i}")
i.show("text")
curr_part.append(i)
print(f"\ntempo.MetronomeMark:")
for i in part.getElementsByClass(tempo.MetronomeMark):
print(f"MetronomeMark: {i}")
i.show("text")
curr_part.append(i)
print(f"\nkey.KeySignature:")
for i in part.getElementsByClass(key.KeySignature):
print(f"KeySignature: {i}")
i.show("text")
curr_part.append(i)
print(f"\nmeter.TimeSignature:")
for i in part.getElementsByClass(meter.TimeSignature):
print(f"TimeSignature: {i}")
i.show("text")
curr_part.append(i)
print(f"\nmeter.getElementsByOffset:")
for i in part.getElementsByOffset(476, 548, includeEndBoundary=True):
print(f"offset: {i}")
i.show("text")
curr_part.append(i)
cp = curr_part.flatten()
solo_stream.insert(cp)
# Extract solo stream, assuming you know the positions ..ByOffset(i, j).
# Note that for different instruments (with stream.flat), you NEED to use
# stream.Part(), not stream.Voice().
# Accompanied solo is in range [478, 548)
#solo_stream = measure_stream.voices.stream()
print(f"\n{len(solo_stream)} solo_stream")
solo_stream.show("text")
# Group by measure so you can classify.
# Note that measure 0 is for the time signature, metronome, etc. which have
# an offset of 0.0.
melody_stream = solo_stream[-1]
print(f"\n{len(melody_stream)} melody_stream: {melody_stream}")
melody_stream.show("text")
measures = OrderedDict()
# Check data/original_metheny.log. All Measures are 4 distance away from one another, i.e., {0.0}, {4.0}, {8.0}, ...
offsetTuples = [(n.offset // 4, n) for n in melody_stream]
measureNum = 0 # for now, don't use real m. nums (119, 120)
for key_x, group in groupby(offsetTuples, lambda x: x[0]):
measures[measureNum] = [n[1] for n in group]
measureNum += 1
print(f"\nmeasures: {len(measures)} {measures}")
solo_stream[0].show("text")
chordStream = solo_stream[0] # Only piano has chord
chordStream.removeByClass(instrument.Piano)
chordStream.removeByClass(tempo.MetronomeMark)
chordStream.removeByClass(key.Key)
chordStream.removeByClass(meter.TimeSignature)
chordStream.removeByClass(note.Rest)
chordStream.removeByClass(note.Note)
print(f"\n{len(chordStream)} chordStream: {chordStream}")
chordStream.show("text")
# Check data/original_metheny.log. All Measures are 4 distance away from one another, i.e., {0.0}, {4.0}, {8.0}, ...
offsetTuples_chords = [(n.offset // 4, n) for n in chordStream]
#offsetTuples_chords = [(n.offset // 4, n) for n in allchords]
# Generate the chord structure. Use just track 1 (piano) since it is
# the only instrument that has chords.
# Group into 4s, just like before.
chords = OrderedDict()
measureNum = 0
for key_x, group in groupby(offsetTuples_chords, lambda x: x[0]):
chords[measureNum] = [n[1] for n in group]
measureNum += 1
print(f"\nchords: {len(chords)} {chords}")
# Fix for the below problem.
# 1) Find out why len(measures) != len(chords).
# ANSWER: resolves at end but melody ends 1/16 before last measure so doesn't
# actually show up, while the accompaniment's beat 1 right after does.
# Actually on second thought: melody/comp start on Ab, and resolve to
# the same key (Ab) so could actually just cut out last measure to loop.
# Decided: just cut out the last measure.
#del chords[len(chords) - 1]
assert len(chords) == len(measures), f"{len(chords)} chords, {len(measures)} measures" # AssertionError: 28 chords, 21 measures
print(f"{len(chords)} measures and chords")
return measures, chords
''' Helper function to parse a MIDI file into its measures and chords '''
def __parse_midi(data_fn):
"""
A common arrangement of nested Streams is a Score Stream containing one or more Part Streams, each Part Stream in turn containing one or more Measure Streams.
Parts > Measures > Voice
You might think that there should be a convenience property .measures to get all the measures. But the problem with that is that measure numbers would be quite different from index numbers.
For instance, most pieces (that don’t have pickup measures) begin with measure 1, not zero.
Sometimes there are measure discontinuities within a piece (e.g., some people number first and second endings with the same measure number).
For that reason, gathering Measures is best accomplished NOT with getElementsByClass(stream.Measure) but instead with either the measures() method (returning a Stream of Parts or Measures) or the measure() method (returning a single Measure).
"""
print(f"\n=== {__parse_midi.__name__} ===")
# Parse the MIDI data for separate melody and accompaniment parts.
midi_data = converter.parse(data_fn)
assert len(midi_data.getElementsByClass(stream.Part)) == len(midi_data.parts)
#print(f"Parts (getElementsByClass): {len(midi_data.getElementsByClass(stream.Part))}")
#print(f"\nParts (.parts): {len(midi_data.parts)}")
measure_part5_len = len(midi_data.getElementsByClass(stream.Part)[5].getElementsByClass(stream.Measure))
measure_part5 = midi_data.parts[5].measures(1, measure_part5_len)
measure_stream = midi_data.parts[5].getElementsByClass(stream.Measure)
print(f"\nMeasure (Part[5]): {len(measure_part5)} {measure_part5}")
#print(f"\nmeasure_part5.show:")
#measure_part5.show('text')
#print("recurse():")
#for el in midi_data.recurse().getElementsByClass(stream.Voice):
# print(el.offset, el, el.activeSite)
# Get melody part, compress into single voice.
#melody_stream = midi_data[5] # For Metheny piece, Melody is Part #5.
print(f"\nMelody Voice: {len(measure_part5.recurse().getElementsByClass(stream.Voice))}")
melody_voice = []
for el in measure_part5.recurse().getElementsByClass(stream.Voice):
#print(el.offset, el, el.activeSite)
print(f"el offset: {el.offset}, {el}")
melody_voice.insert(int(el.offset), el)
for i in melody_voice:
if i.quarterLength == 0.0:
i.quarterLength = 0.25
# Change key signature to adhere to comp_stream (1 sharp, mode = major).
# Also add Electric Guitar.
melody_voice.insert(0, instrument.ElectricGuitar())
melody_voice.insert(0, key.KeySignature(sharps=1))
print(f"{len(melody_voice)} melody_voice")
# The accompaniment parts. Take only the best subset of parts from
# the original data. Maybe add more parts, hand-add valid instruments.
# Should at least add a string part (for sparse solos).
# Verified are good parts: 0, 1, 6, 7 '''
partIndices = [0, 1, 6, 7]
accompaniment = stream.Voice()
#print("\nenumerate(midi_data)")
#for i, j in enumerate(midi_data):
# if i in partIndices and not isinstance(j, metadata.Metadata):
# print(f"{i}: {j}, {j}")
# j.show("text")
#comp_stream = measure_stream.voices.stream()
accompaniment.append([j.flatten() for i, j in enumerate(midi_data) if i in partIndices and not isinstance(j, metadata.Metadata)])
print(f"\naccompaniment: {len(accompaniment)}")
#comp_stream.show("text")
# Full stream containing both the melody and the accompaniment.
# All parts are flattened.
full_stream = stream.Voice()
#full_stream = measure_stream.voices.stream()
for i in range(len(accompaniment)):
full_stream.append(accompaniment[i])
full_stream.append(melody_voice)
print(f"\nfull_stream: {len(full_stream)}")
#full_stream.show("text")
solo_stream = stream.Voice()
for part in full_stream:
print(f"\npart: {part}")
#part.show("text")
curr_part = stream.Part()
if isinstance(part, stream.Part):
print(f"\ninstrument.Instrument:")
for i in part.getElementsByClass(instrument.Piano):
print(f"instrument: {i}")
i.show("text")
curr_part.append(i)
print(f"\ntempo.MetronomeMark:")
for i in part.getElementsByClass(tempo.MetronomeMark):
print(f"MetronomeMark: {i}")
i.show("text")
curr_part.append(i)
print(f"\nkey.KeySignature:")
for i in part.getElementsByClass(key.KeySignature):
print(f"KeySignature: {i}")
i.show("text")
curr_part.append(i)
print(f"\nmeter.TimeSignature:")
for i in part.getElementsByClass(meter.TimeSignature):
print(f"TimeSignature: {i}")
i.show("text")
curr_part.append(i)
print(f"\nmeter.getElementsByOffset:")
for i in part.getElementsByOffset(476, 548, includeEndBoundary=True):
print(f"offset: {i}")
i.show("text")
curr_part.append(i)
cp = curr_part.flatten()
solo_stream.insert(cp)
# Extract solo stream, assuming you know the positions ..ByOffset(i, j).
# Note that for different instruments (with stream.flat), you NEED to use
# stream.Part(), not stream.Voice().
# Accompanied solo is in range [478, 548)
#solo_stream = measure_stream.voices.stream()
print(f"\nsolo_stream: {len(solo_stream)}")
solo_stream.show("text")
# Group by measure so you can classify.
# Note that measure 0 is for the time signature, metronome, etc. which have
# an offset of 0.0.
melody_stream = solo_stream[-1]
print(f"\n{len(melody_stream)} melody_stream: {melody_stream}")
melody_stream.show("text")
measures = OrderedDict()
# Check data/original_metheny.log. All Measures are 4 distance away from one another, i.e., {0.0}, {4.0}, {8.0}, ...
offsetTuples = [(n.offset // 4, n) for n in melody_stream]
measureNum = 0 # for now, don't use real m. nums (119, 120)
for key_x, group in groupby(offsetTuples, lambda x: x[0]):
measures[measureNum] = [n[1] for n in group]
measureNum += 1
print(f"\nmeasures: {len(measures)} {measures}")
# Get the stream of chords.
# offsetTuples_chords: group chords by measure number.
chordStream = solo_stream[0] # Only piano has chord
chordStream.removeByClass(instrument.Piano)
chordStream.removeByClass(tempo.MetronomeMark)
chordStream.removeByClass(key.Key)
chordStream.removeByClass(meter.TimeSignature)
chordStream.removeByClass(note.Rest)
chordStream.removeByClass(note.Note)
print(f"\n{len(chordStream)} chordStream: {chordStream}")
chordStream.show("text")
# Check data/original_metheny.log. All Measures are 4 distance away from one another, i.e., {0.0}, {4.0}, {8.0}, ...
offsetTuples_chords = [(n.offset // 4, n) for n in chordStream]
# Generate the chord structure. Use just track 1 (piano) since it is
# the only instrument that has chords.
# Group into 4s, just like before.
chords = OrderedDict()
measureNum = 0
for key_x, group in groupby(offsetTuples_chords, lambda x: x[0]):
chords[measureNum] = [n[1] for n in group]
measureNum += 1
print(f"\nchords: {len(chords)} {chords}")
# Fix for the below problem.
# 1) Find out why len(measures) != len(chords).
# ANSWER: resolves at end but melody ends 1/16 before last measure so doesn't
# actually show up, while the accompaniment's beat 1 right after does.
# Actually on second thought: melody/comp start on Ab, and resolve to
# the same key (Ab) so could actually just cut out last measure to loop.
# Decided: just cut out the last measure.
#del chords[len(chords) - 1]
assert len(chords) == len(measures), f"{len(chords)} chords, {len(measures)} measures"
print(f"{len(chords)} measures and chords")
return measures, chords
''' Helper function to determine if a note is a scale tone. '''
def __is_scale_tone(chord, note):
# Method: generate all scales that have the chord notes th check if note is
# in names
# Derive major or minor scales (minor if 'other') based on the quality
# of the chord.
scaleType = scale.DorianScale() # i.e. minor pentatonic
if chord.quality == 'major':
scaleType = scale.MajorScale()
# Can change later to deriveAll() for flexibility. If so then use list
# comprehension of form [x for a in b for x in a].
scales = scaleType.derive(chord) # use deriveAll() later for flexibility
allPitches = list(set([pitch for pitch in scales.getPitches()]))
allNoteNames = [i.name for i in allPitches] # octaves don't matter
# Get note name. Return true if in the list of note names.
noteName = note.name
return (noteName in allNoteNames)
''' Helper function to determine if a note is an approach tone. '''
def __is_approach_tone(chord, note):
# Method: see if note is +/- 1 a chord tone.
for chordPitch in chord.pitches:
stepUp = chordPitch.transpose(1)
stepDown = chordPitch.transpose(-1)
if (note.name == stepDown.name or
note.name == stepDown.getEnharmonic().name or
note.name == stepUp.name or
note.name == stepUp.getEnharmonic().name):
return True
return False
''' Helper function to determine if a note is a chord tone. '''
def __is_chord_tone(lastChord, note):
return (note.name in (p.name for p in lastChord.pitches))
def parse_melody(fullMeasureNotes, fullMeasureChords):
print(f"\n=== {parse_melody.__name__} ===")
# Remove extraneous elements.x
measure = copy.deepcopy(fullMeasureNotes)
chords = copy.deepcopy(fullMeasureChords)
print(f"{len(measure)} measures, {len(chords)} chords")
measure.removeByNotOfClass([note.Note, note.Rest])
chords.removeByNotOfClass([chord.Chord])
assert len(measure) != 0 and len(chords) != 0, f"{bcolors.FAIL}{len(measure)} measures, {len(chords)} chords{bcolors.DEFAULT}"
# Information for the start of the measure.
# 1) measureStartTime: the offset for measure's start, e.g. 476.0.
# 2) measureStartOffset: how long from the measure start to the first element.
measureStartTime = measure[0].offset - (measure[0].offset % 4)
measureStartOffset = measure[0].offset - measureStartTime
# Iterate over the notes and rests in measure, finding the grammar for each
# note in the measure and adding an abstract grammatical string for it.
fullGrammar = ""
prevNote = None # Store previous note. Need for interval.
numNonRests = 0 # Number of non-rest elements. Need for updating prevNote.
for ix, nr in enumerate(measure):
# Get the last chord. If no last chord, then (assuming chords is of length
# >0) shift first chord in chords to the beginning of the measure.
try:
lastChord = [n for n in chords if n.offset <= nr.offset][-1]
except IndexError:
chords[0].offset = measureStartTime
lastChord = [n for n in chords if n.offset <= nr.offset][-1]
# FIRST, get type of note, e.g. R for Rest, C for Chord, etc.
# Dealing with solo notes here. If unexpected chord: still call 'C'.
elementType = ' '
# R: First, check if it's a rest. Clearly a rest --> only one possibility.
if isinstance(nr, note.Rest):
elementType = 'R'
# C: Next, check to see if note pitch is in the last chord.
elif nr.name in lastChord.pitchNames or isinstance(nr, chord.Chord):
elementType = 'C'
# L: (Complement tone) Skip this for now.
# S: Check if it's a scale tone.
elif __is_scale_tone(lastChord, nr):
elementType = 'S'
# A: Check if it's an approach tone, i.e. +-1 halfstep chord tone.
elif __is_approach_tone(lastChord, nr):
elementType = 'A'
# X: Otherwise, it's an arbitrary tone. Generate random note.
else:
elementType = 'X'
# SECOND, get the length for each element. e.g. 8th note = R8, but
# to simplify things you'll use the direct num, e.g. R,0.125
if (ix == (len(measure)-1)):
# formula for a in "a - b": start of measure (e.g. 476) + 4
diff = measureStartTime + 4.0 - nr.offset
else:
diff = measure[ix + 1].offset - nr.offset
# Combine into the note info.
noteInfo = "%s,%.3f" % (elementType, nr.quarterLength) # back to diff
# THIRD, get the deltas (max range up, max range down) based on where
# the previous note was, +- minor 3. Skip rests (don't affect deltas).
intervalInfo = ""
if isinstance(nr, note.Note):
numNonRests += 1
if numNonRests == 1:
prevNote = nr
else:
noteDist = interval.Interval(noteStart=prevNote, noteEnd=nr)
noteDistUpper = interval.add([noteDist, "m3"])
noteDistLower = interval.subtract([noteDist, "m3"])
intervalInfo = ",<%s,%s>" % (noteDistUpper.directedName,
noteDistLower.directedName)
# print "Upper, lower: %s, %s" % (noteDistUpper,
# noteDistLower)
# print "Upper, lower dnames: %s, %s" % (
# noteDistUpper.directedName,
# noteDistLower.directedName)
# print "The interval: %s" % (intervalInfo)
prevNote = nr
# Return. Do lazy evaluation for real-time performance.
grammarTerm = noteInfo + intervalInfo
fullGrammar += (grammarTerm + " ")
return fullGrammar.rstrip()
def __get_abstract_grammars(measures, chords):
# extract grammars
abstract_grammars = []
for ix in range(1, len(measures)):
m = stream.Voice()
for i in measures[ix]:
m.insert(i.offset, i)
c = stream.Voice()
for j in chords[ix]:
c.insert(j.offset, j)
parsed = parse_melody(m, c)
abstract_grammars.append(parsed)
return abstract_grammars
def get_musical_data(path):
''' Get musical data from a MIDI file '''
print(f"\n=== {get_musical_data.__name__} ===")
measures, chords = separate_parts_from_midi(path, 0) #__parse_midi(path) melody_index=5 will result in unmatched #chords=13 and #measures=19
abstract_grammars = __get_abstract_grammars(measures, chords)
print(f"{len(measures)} measures, {len(chords)} chords, {len(abstract_grammars)} abstract_grammars")
return chords, abstract_grammars
''' Get corpus data from grammatical data '''
def get_corpus_data(abstract_grammars):
corpus = [x for sublist in abstract_grammars for x in sublist.split(' ')]
values = set(corpus)
val_indices = dict((v, i) for i, v in enumerate(values))
indices_val = dict((i, v) for i, v in enumerate(values))
return corpus, values, val_indices, indices_val
def data_processing(corpus, values_indices, m = 60, Tx = 30):
# cut the corpus into semi-redundant sequences of Tx values
Tx = Tx
N_values = len(set(corpus))
X = numpy.zeros((m, Tx, N_values), dtype=numpy.bool)
Y = numpy.zeros((m, Tx, N_values), dtype=numpy.bool)
for i in range(m):
# for t in range(1, Tx):
random_idx = rng.choice(len(corpus) - Tx)
corp_data = corpus[random_idx:(random_idx + Tx)]
for j in range(Tx):
idx = values_indices[corp_data[j]]
if j != 0:
X[i, j, idx] = 1
Y[i, j-1, idx] = 1
Y = numpy.swapaxes(Y,0,1)
Y = Y.tolist()
return numpy.asarray(X), numpy.asarray(Y), N_values
def load_music_utils(file):
print(f"\n=== {load_music_utils.__name__} ===")
chords, abstract_grammars = get_musical_data(file)
corpus, tones, tones_indices, indices_tones = get_corpus_data(abstract_grammars)
X, Y, N_tones = data_processing(corpus, tones_indices, 60, 30)
print('number of training examples:', X.shape[0])
print('Tx (length of sequence):', X.shape[1])
print('total # of unique values:', N_tones)
print('shape of X:', X.shape)
print('Shape of Y:', Y.shape)
print('Number of chords', len(chords))
return (X, Y, N_tones, indices_tones, chords)
def separate_part(midi_file_path, part: int):
score = converter.parse(midi_file_path)
melody_stream = score[5] # For Metheny piece, Melody is Part #5.
print(f"{melody_stream.show('text')}")
#print(f"Melody Parts: {len(melody_stream.parts)}")
#print(f"Melody Voice: {len(melody_stream.getElementsByClass(stream.Voice))}")
#print(f"Melody Voice: {len(melody_stream.getElementsByClass(stream.Part)[0].getElementsByClass(stream.Voice))}")
for el in melody_stream.recurse():
print(el.offset, el, el.activeSite)
melody1, melody2 = melody_stream.recurse().getElementsByClass(stream.Voice)
for j in melody2:
melody1.insert(j.offset, j)
melody_voice = melody1
for i in melody_voice:
if i.quarterLength == 0.0:
i.quarterLength = 0.25
# Change key signature to adhere to comp_stream (1 sharp, mode = major).
# Also add Electric Guitar.
melody_voice.insert(0, instrument.ElectricGuitar())
melody_voice.insert(0, key.KeySignature(sharps=1))
# The accompaniment parts. Take only the best subset of parts from
# the original data. Maybe add more parts, hand-add valid instruments.
# Should add least add a string part (for sparse solos).
# Verified are good parts: 0, 1, 6, 7 '''
partIndices = [0, 1, 6, 7]
comp_stream = stream.Voice()
comp_stream.append([j.flat for i, j in enumerate(midi_data)
if i in partIndices])
# Full stream containing both the melody and the accompaniment.
# All parts are flattened.
full_stream = stream.Voice()
for i in range(len(comp_stream)):
full_stream.append(comp_stream[i])
full_stream.append(melody_voice)
print(f"\nfull_stream: {len(full_stream)}")
#full_stream.show("text")
# Extract solo stream, assuming you know the positions ..ByOffset(i, j).
# Note that for different instruments (with stream.flat), you NEED to use
# stream.Part(), not stream.Voice().
# Accompanied solo is in range [478, 548)
solo_stream = stream.Voice()
for part in full_stream:
print(f"\npart: {part}")
#part.show("text")
curr_part = stream.Part()
if isinstance(part, stream.Part):
print(f"\ninstrument.Instrument:")
for i in part.getElementsByClass(instrument.Piano):
print(f"instrument: {i}")
i.show("text")
curr_part.append(i)
print(f"\ntempo.MetronomeMark:")
for i in part.getElementsByClass(tempo.MetronomeMark):
print(f"MetronomeMark: {i}")
i.show("text")
curr_part.append(i)
print(f"\nkey.KeySignature:")
for i in part.getElementsByClass(key.KeySignature):
print(f"KeySignature: {i}")
i.show("text")
curr_part.append(i)
print(f"\nmeter.TimeSignature:")
for i in part.getElementsByClass(meter.TimeSignature):
print(f"TimeSignature: {i}")
i.show("text")
curr_part.append(i)
print(f"\nmeter.getElementsByOffset:")
for i in part.getElementsByOffset(476, 548, includeEndBoundary=True):
print(f"offset: {i}")
i.show("text")
curr_part.append(i)
cp = curr_part.flatten()
solo_stream.insert(cp)
# Group by measure so you can classify.
# Note that measure 0 is for the time signature, metronome, etc. which have
# an offset of 0.0.
melody_stream = solo_stream[-1]
measures = OrderedDict()
offsetTuples = [(int(n.offset / 4), n) for n in melody_stream]
measureNum = 0 # for now, don't use real m. nums (119, 120)
for key_x, group in groupby(offsetTuples, lambda x: x[0]):
measures[measureNum] = [n[1] for n in group]
measureNum += 1
# Get the stream of chords.
# offsetTuples_chords: group chords by measure number.
chordStream = solo_stream[0]
chordStream.removeByClass(note.Rest)
chordStream.removeByClass(note.Note)
offsetTuples_chords = [(int(n.offset / 4), n) for n in chordStream]
# Generate the chord structure. Use just track 1 (piano) since it is
# the only instrument that has chords.
# Group into 4s, just like before.
chords = OrderedDict()
measureNum = 0
for key_x, group in groupby(offsetTuples_chords, lambda x: x[0]):
chords[measureNum] = [n[1] for n in group]
measureNum += 1
# Fix for the below problem.
# 1) Find out why len(measures) != len(chords).
# ANSWER: resolves at end but melody ends 1/16 before last measure so doesn't
# actually show up, while the accompaniment's beat 1 right after does.
# Actually on second thought: melody/comp start on Ab, and resolve to
# the same key (Ab) so could actually just cut out last measure to loop.
# Decided: just cut out the last measure.
del chords[len(chords) - 1]
assert len(chords) == len(measures)
return measures, chords
def separate_parts_from_midi(midi_file_path: str, melody_index: int):
"""
Parses a MIDI file and separates it into individual parts.
Prints instrument names and returns the list of parts.
"""
print(f"\n=== {separate_parts_from_midi.__name__} ===")
#try:
# Parse the MIDI file into a score stream
# Score > Part > Measure
score = converter.parse(midi_file_path)
# Access individual parts
parts = score.parts
if not parts:
print("The MIDI file might be Type 0 (single track) or has no distinct parts.")
# If it's a single track, you might need to use more complex methods
# to algorithmically separate melody from accompaniment within that track.
return [score] # Return the whole score as a single part list
print(f"Found {len(parts)} parts in the MIDI file. melody_index: {melody_index}")
separated_parts = []
for i, part in enumerate(parts):
# Get the instrument name for identification
inst = part.getInstrument()
print(f"Part {i+1}: {part}, {inst.instrumentName}")
separated_parts.append(part)
# Example: Assign the first part as melody, the rest as accompaniment
if i == melody_index:
melody_part = part
else:
# You might need to combine other parts into an accompaniment stream
pass
# You can now work with individual part streams, for example:
print("Melody part:")
melody_part.show('text')
#accompaniment_stream = stream.Stream(separated_parts.pop(melody_index)) # Combine others
partIndices = [0, 1, 6, 7]
accompaniment = stream.Voice()
#print("\nenumerate(midi_data)")
#for i, j in enumerate(midi_data):
# if i in partIndices and not isinstance(j, metadata.Metadata):
# print(f"{i}: {j}, {j}")
# j.show("text")
#comp_stream = measure_stream.voices.stream()
accompaniment.append([j.flatten() for i, j in enumerate(score) if i in partIndices and not isinstance(j, metadata.Metadata)])
# Full stream containing both the melody and the accompaniment.
# All parts are flattened.
full_stream = stream.Voice()
full_stream.append(accompaniment)
full_stream.append(melody_part)
print(f"{len(accompaniment)} accompaniment_stream, {len(full_stream)} full_stream")
# Extract solo stream, assuming you know the positions ..ByOffset(i, j).
# Note that for different instruments (with stream.flat), you NEED to use
# stream.Part(), not stream.Voice().
# Accompanied solo is in range [478, 548)
solo_stream = stream.Voice()
for part in full_stream:
print(f"\npart: {part}")
#part.show("text")
curr_part = stream.Part()
if isinstance(part, stream.Part):
for i in part.getElementsByClass(instrument.Instrument):
print(f"Piano: {i}")
i.show("text")
curr_part.append(i)
for i in part.getElementsByClass(tempo.MetronomeMark):
print(f"MetronomeMark: {i}")
i.show("text")
curr_part.append(i)
for i in part.getElementsByClass(key.KeySignature):
print(f"KeySignature: {i}")
i.show("text")
curr_part.append(i)
for i in part.getElementsByClass(meter.TimeSignature):
print(f"TimeSignature: {i}")
i.show("text")
curr_part.append(i)
for i in part.getElementsByOffset(476, 548, includeEndBoundary=True):
print(f"offset: {i}")
i.show("text")
curr_part.append(i)
cp = curr_part.flatten()
solo_stream.insert(cp)
print(f"{len(solo_stream)} solo_stream")
# Group by measure so you can classify.
# Note that measure 0 is for the time signature, metronome, etc. which have
# an offset of 0.0.
melody_stream = solo_stream[-1]
print(f"\n{len(melody_stream)} melody_stream: {melody_stream}") # 140
#melody_stream = melody_part Line 459 will error
measures = OrderedDict()
offsetTuples = [(int(n.offset / 4), n) for n in melody_stream]
measureNum = 0 # for now, don't use real m. nums (119, 120)
for key_x, group in groupby(offsetTuples, lambda x: x[0]):
measures[measureNum] = [n[1] for n in group]
measureNum += 1
print(f"{len(measures)} measures")
# Get the stream of chords.
# offsetTuples_chords: group chords by measure number.
chordStream = solo_stream[0]
#chordStream = melody_part
print(f"{len(chordStream)} chordStream") # 140
chordStream.removeByClass(note.Rest)
chordStream.removeByClass(note.Note)
print(f"{len(chordStream)} chordStream") # 48
offsetTuples_chords = [(int(n.offset / 4), n) for n in chordStream]
# Generate the chord structure. Use just track 1 (piano) since it is
# the only instrument that has chords.
# Group into 4s, just like before.
chords = OrderedDict()
measureNum = 0
for key_x, group in groupby(offsetTuples_chords, lambda x: x[0]):
chords[measureNum] = [n[1] for n in group]
measureNum += 1
print(f"{len(chords)} chords")
# Fix for the below problem.
# 1) Find out why len(measures) != len(chords).
# ANSWER: resolves at end but melody ends 1/16 before last measure so doesn't
# actually show up, while the accompaniment's beat 1 right after does.
# Actually on second thought: melody/comp start on Ab, and resolve to
# the same key (Ab) so could actually just cut out last measure to loop.
# Decided: just cut out the last measure.
#del chords[len(chords) - 1]
assert len(chords) == len(measures), f"{bcolors.FAIL}{len(chords)} chords, {len(measures)} measures{bcolors.DEFAULT}"
return measures, chords
#except Exception as e:
# print(f"Error parsing MIDI file: {e}")
# return None, None
def separate_parts_from_midi1(midi_file_path, melody_index:int):
"""
Parse the MIDI file into a music21 Stream (Score) object.
Google search phrase: music21 Parse the MIDI data for separate melody and accompaniment parts.
"""
print(f"\n=== {separate_parts_from_midi1.__name__} ===")
# Parse the MIDI file into a score stream
# Score > Part > Measure
score = converter.parse(midi_file_path)
# Access individual parts
parts = score.parts
if not parts:
print("The MIDI file might be Type 0 (single track) or has no distinct parts.")
# If it's a single track, you might need to use more complex methods
# to algorithmically separate melody from accompaniment within that track.
return [score] # Return the whole score as a single part list
print(f"Found {len(parts)} parts in the MIDI file:")
separated_parts = []
for i, part in enumerate(parts):
# Get the instrument name for identification
inst = part.getInstrument()
print(f"Part {i+1}: {part}, {inst.instrumentName}")
separated_parts.append(part)
# Example: Assign the first part as melody, the rest as accompaniment
if i == melody_index:
melody_part = part
accompaniment_stream = stream.Stream(separated_parts.pop(melody_index)) # Combine others
# Full stream containing both the melody and the accompaniment.
# All parts are flattened.
full_stream = stream.Voice()
full_stream.append(accompaniment_stream)
full_stream.append(melody_part)
measures = OrderedDict()
offsetTuples = [(int(n.offset / 4), n) for n in melody_part]
measureNum = 0 # for now, don't use real m. nums (119, 120)
for key_x, group in groupby(offsetTuples, lambda x: x[0]):
measures[measureNum] = [n[1] for n in group]
measureNum += 1
# Get the stream of chords.
# offsetTuples_chords: group chords by measure number.
#chordStream = solo_stream[0]
chordStream = melody_part
chordStream.removeByClass(note.Rest)
chordStream.removeByClass(note.Note)
offsetTuples_chords = [(int(n.offset / 4), n) for n in chordStream]
# Generate the chord structure. Use just track 1 (piano) since it is
# the only instrument that has chords.
# Group into 4s, just like before.
chords = OrderedDict()
measureNum = 0
for key_x, group in groupby(offsetTuples_chords, lambda x: x[0]):
chords[measureNum] = [n[1] for n in group]
measureNum += 1
#for k,v in measures:
# [note.Note, note.Rest]
# Fix for the below problem.
# 1) Find out why len(measures) != len(chords).
# ANSWER: resolves at end but melody ends 1/16 before last measure so doesn't
# actually show up, while the accompaniment's beat 1 right after does.
# Actually on second thought: melody/comp start on Ab, and resolve to
# the same key (Ab) so could actually just cut out last measure to loop.
# Decided: just cut out the last measure.
#del chords[len(chords) - 1]
print(f"{len(chords)} chords, {len(measures)} measures")
assert len(chords) == len(measures)
return measures, chords
def separate_parts_from_midi2(midi_file_path):
"""
Parse the MIDI file into a music21 Stream (Score) object.
Google search phrase: music21 Parse the MIDI data for separate melody and accompaniment parts.
"""
print(f"\n=== {separate_parts_from_midi2.__name__} ===")
# Parse the MIDI file into a score stream
# Score > Part > Measure
score = converter.parse(midi_file_path)
# Access individual parts
parts = score.parts
if not parts:
print("The MIDI file might be Type 0 (single track) or has no distinct parts.")
# If it's a single track, you might need to use more complex methods
# to algorithmically separate melody from accompaniment within that track.
return [score] # Return the whole score as a single part list
print(f"Found {len(parts)} parts in the MIDI file:")
separated_parts = []
measures = []
for i, part in enumerate(parts):
# Get the instrument name for identification
inst = part.getInstrument()
print(f"Part {i+1}: {part}, {inst.instrumentName}")
separated_parts.append(part)
# Example: Assign the first part as melody, the rest as accompaniment
if i == 0:
melody_part = part
for measure in melody_part.recurse().getElementsByClass(stream.Measure):
measures.append(measure)
accompaniment_stream = stream.Stream(separated_parts[1:]) # Combine others
# Full stream containing both the melody and the accompaniment.
# All parts are flattened.
full_stream = stream.Voice()
full_stream.append(accompaniment_stream)
full_stream.append(melody_part)
#measures = OrderedDict()
#offsetTuples = [(int(n.offset / 4), n) for n in melody_part]
#measureNum = 0 # for now, don't use real m. nums (119, 120)
#for key_x, group in groupby(offsetTuples, lambda x: x[0]):
# measures[measureNum] = [n[1] for n in group]
# measureNum += 1
# Get the stream of chords.
# offsetTuples_chords: group chords by measure number.
#chordStream = solo_stream[0]
chordStream = melody_part
chordStream.removeByClass(note.Rest)
chordStream.removeByClass(note.Note)
offsetTuples_chords = [(int(n.offset / 4), n) for n in chordStream]
# Generate the chord structure. Use just track 1 (piano) since it is
# the only instrument that has chords.
# Group into 4s, just like before.
chords = OrderedDict()
measureNum = 0
for key_x, group in groupby(offsetTuples_chords, lambda x: x[0]):
chords[measureNum] = [n[1] for n in group]
measureNum += 1
# Fix for the below problem.
# 1) Find out why len(measures) != len(chords).
# ANSWER: resolves at end but melody ends 1/16 before last measure so doesn't
# actually show up, while the accompaniment's beat 1 right after does.
# Actually on second thought: melody/comp start on Ab, and resolve to
# the same key (Ab) so could actually just cut out last measure to loop.
# Decided: just cut out the last measure.
#del chords[len(chords) - 1]
print(f"{len(chords)} chords, {len(measures)} measures")
assert len(chords) == len(measures)
return measures, chords