-
Notifications
You must be signed in to change notification settings - Fork 0
/
VeeHarmGen.py
3436 lines (2885 loc) · 151 KB
/
VeeHarmGen.py
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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# VeeHarmGen.py
#
# Given an input compressed musicxml file (.mxl) with a melody stave:
# if in demo mode it calculates chord symbols for different harmonic rhythms and chord types and saves the result.
# If not in demo mode:
# if the input has no chord symbols then
# it calculates chord symbols for different harmonic rhythms and
# saves the result.
# else
# for each style:
# for each placeholder chords it calculates a new chord in the current style and
# saves the result.
#
# free and open-source software, Paul Wardley Davies, see license.txt
# standard libraries
import argparse
import ast
import bisect
import copy
import datetime
import math
import music21
import numpy
import operator
import os
from pathlib import Path
import re
import sys
import VeeHarmGen_utilities
from collections import Counter
from enum import Enum
from fractions import Fraction
from itertools import islice
from music21 import *
from music21 import stream, note, harmony
from music21.harmony import ChordSymbol, NoChord
from VeeHarmGen_utilities import *
# VEEHARMGEN_VERSION
__version__ = "3.0.0"
# on ubuntu 20.04 default python 3.8.10 (without removesuffix)
def remove_suffix(input_string, suffix):
if suffix and input_string.endswith(suffix):
return input_string[:-len(suffix)]
return input_string
class Chord_Output(Enum):
ON_KEY_MOST = 100
ON_KEY_LEAST = 101
ON_KEY_FIRST = 102
ON_KEY_LAST = 103
PEDAL = 111
NEXT_INTERVAL_SECOND_OR_THIRD = 123
NEXT_INTERVAL_FOURTH_OR_FIFTH = 145
NEXT_INTERVAL_SIXTH_OR_SEVENTH = 167
DESCENDING_BASS_TETRA = 174
DESCENDING_CHROMATIC_BASS_TETRA = 175
DESCENDING_BASS = 176
DESCENDING_CHROMATIC_BASS = 177
ASCENDING_BASS_TETRA_1 = 181
ASCENDING_BASS_TETRA_2 = 182
ASCENDING_BASS_TETRA_3 = 183
SUSPENDED2 = 200
ADD2 = 202
MINOR = 300
MAJOR = 301
SUSPENDED4 = 400
ADD4 = 404
FIFTH = 500
SIXTH = 600
ADDSIXTH = 606
SEVENTH = 700
NINTH = 900
ELEVENTH = 911
THIRTEENTH = 913
class Harmonic_Rhythm(Enum):
BEAT1 = 0
BEAT2 = 1
BAR1 = 2
BAR2 = 3
BAR4 = 4
class Harmonic_Rhythm(Enum):
BEAT1 = 0
BEAT2 = 1
BAR1 = 2
BAR2 = 3
BAR4 = 4
def get_chord(initial_offset, offset_chord, prev_sho_cho):
"""
given offset and offset_chord dictionary, return short chord (e.g. Am or C) at offset
:param initial_offset: e.g. 3.0
:param chords_offset : e.g. {'F': 0.0, 'F': 1.0, 'G': 2.0, 'E': 3.0, 'F': 4.0, 'G': 5.0, 'A': 6.0} etc
:param: prev_sho_cho: previous short chord
:return: short_chord e.g. 'E'
"""
# harmonic rhythm = 2nd key - 1st key
harmonic_rhythm = list(offset_chord.keys())[1] - next(iter(offset_chord))
offset = initial_offset
negative_offset = False
while True:
val = offset_chord.get(offset)
if val != None:
break
offset = offset - 0.125
if offset < 0.0:
negative_offset = True
break
if not negative_offset:
try:
short_chord = offset_chord[offset]
except KeyError:
print('exit: KeyError. initial_offset, offset, offset_chord', initial_offset, offset, offset_chord)
sys.exit()
else:
short_chord = prev_sho_cho
return short_chord
def get_chord_from_pitch_to_chord_by_rank(number, v):
"""
given a pitch_to_chord v and a rank number return the short chord (e.g. Am or C) for the rank number
:param number
:param v: a pitch_to_chord : e.g. {'1000 0000 0000': {'C': 2, 'Am7': 1, 'Am': 1}, '0010 0000 0000': {'F': 2, 'G': 1}, '1010 0000 0001': {'G': 1}, '0000 1000 0000': {'C': 1, 'Am': 1}, '0000 1100 0000': {'F': 1}, '1000 0000 0001': {'G': 1}, '1010 1100 0000': {'C': 1}, '0010 1100 0000': {'G': 1}, '1010 1101 0000': {'C': 1}, '0000 0100 0100': {'F': 1}, '0010 1000 0000': {'G': 1}}
:return: short_chord e.g. 'E'
"""
# print('get_chord_from_pitch_to_chord_by_rank(number, v)', number, v)
# print('type(v), v', type(v), v)
# sort
sorted_v = dict(sorted(v.items(), key=operator.itemgetter(1), reverse=True))
# get count of entries in the dictionary
sorted_v_len = len(sorted_v)
# convert rank to iter_num
iter_num = sorted_v_len - math.ceil((number * (sorted_v_len / 100.0)))
if (iter_num < 0) or (iter_num > sorted_v_len): iter_num = 0 # bug if sorted_v_len = 7 then iter_num = -1
# access the nth element of dict sorted_v where n is iter_num
# but check if a subsequent element with the same frequency has a shorter length use that
# i.e. use the simplest chord with the same frequency
iter_num_1 = 0
for ch1, f1 in sorted_v.items():
if iter_num_1 == iter_num:
ch = ch1
f = f1
if iter_num_1 > iter_num:
if f1 == f and (len(ch1) < len(ch)):
ch = ch1
iter_num_1 = iter_num_1 + 1
print('number, sorted_v_len, iter_num, ch, sorted_v', number, sorted_v_len, iter_num, ch, sorted_v)
# print('type(ch), ch, type(f), f', type(ch), ch, type(f), f )
# input('Press Enter to continue...')
return ch
def get_chord_from_pitch_to_chord_by_nth_outcome(number, v):
"""
given a pitch_to_chord v and a nth_outcome number return the short chord (e.g. Am or C) for the nth_outcome number
:param number
:param v: a pitch_to_chord : e.g. {'1000 0000 0000': {'C': 2, 'Am7': 1, 'Am': 1}, '0010 0000 0000': {'F': 2, 'G': 1}, '1010 0000 0001': {'G': 1}, '0000 1000 0000': {'C': 1, 'Am': 1}, '0000 1100 0000': {'F': 1}, '1000 0000 0001': {'G': 1}, '1010 1100 0000': {'C': 1}, '0010 1100 0000': {'G': 1}, '1010 1101 0000': {'C': 1}, '0000 0100 0100': {'F': 1}, '0010 1000 0000': {'G': 1}}
:return: short_chord e.g. 'E'
"""
print('get_chord_from_pitch_to_chord_by_nth_outcome(number, v)', number, v)
# print('type(v), v', type(v), v)
# sort
sorted_v = dict(sorted(v.items(), key=operator.itemgetter(1), reverse=True))
# get count of entries in the dictionary
sorted_v_len = len(sorted_v)
print('sorted_v_len', sorted_v_len)
# given the the nth_outcome number, determine the iteration number for the dict sorted_v
if (number <= 0): iter_num = 0
elif (sorted_v_len == 1): iter_num = 0
elif (number < sorted_v_len): iter_num = number
# elif (number >= sorted_v_len): iter_num = sorted_v_len % number
elif (number >= sorted_v_len): iter_num = number % sorted_v_len
# access the nth element of dict sorted_v where n is iter_num
print('sorted_v', sorted_v)
print('iter_num, iter_num + 1', iter_num, iter_num + 1)
print('islice(iter(sorted_v), iter_num, iter_num + 1)',islice(iter(sorted_v), iter_num, iter_num + 1))
# print('next(islice(iter(sorted_v), iter_num, iter_num + 1))', next(islice(iter(sorted_v), iter_num, iter_num + 1)))
ch = next(islice(iter(sorted_v), iter_num, iter_num + 1))
print('type(ch), ch', type(ch), ch )
print('number, sorted_v_len, iter_num, ch, sorted_v', number, sorted_v_len, iter_num, ch, sorted_v)
# input('Press Enter to continue...')
# print('type(ch), ch, type(f), f', type(ch), ch, type(f), f )
# input('Press Enter to continue...')
# return the chord by nth_outcome
return ch
def get_chord_for_pitch_class(pitch_class, pitch_to_chord, output_filename, chord_choice, number):
"""
given pitch_class and pitch_to_chord dictionary, return short chord (e.g. Am or C) at offset
:param pitch_class: e.g. 1000 0000 0000
:param pitch_to_chord : e.g. {'1000 0000 0000': {'C': 2, 'Am7': 1, 'Am': 1}, '0010 0000 0000': {'F': 2, 'G': 1}, '1010 0000 0001': {'G': 1}, '0000 1000 0000': {'C': 1, 'Am': 1}, '0000 1100 0000': {'F': 1}, '1000 0000 0001': {'G': 1}, '1010 1100 0000': {'C': 1}, '0010 1100 0000': {'G': 1}, '1010 1101 0000': {'C': 1}, '0000 0100 0100': {'F': 1}, '0010 1000 0000': {'G': 1}}
:param output_filename (used in printed warnings)
:param chord_choice
:param number
:return: short_chord e.g. 'E'
if no chord pitch_class then short_chord = 'NC'
else
using pitch_class get value from pitch_to_chord
get first chord from value (TBD add pre-determined slice parameter)
short_chord = validated chord symbol
if invalid short_chord = 'NC'
return short_chord
"""
short_chord = 'C'
# if no chord pitch_class then short_chord = 'NC
if pitch_class == NO_CHORD_DISPLAY_PITCH_CLASSES:
short_chord = 'NC'
else:
# using pitch_class get value from pitch_to_chord
try:
v = pitch_to_chord[pitch_class]
except KeyError as error:
print('WARNING Invalid pitch_class so get_nearest_chord_from_pitch_to_chord', pitch_class, 'in', output_filename)
v = get_nearest_chord_for_pitch_class(pitch_class, pitch_to_chord, output_filename)
if v != None:
if chord_choice is Chord_Choice.RANK:
print('chord_choice is rank')
ch = get_chord_from_pitch_to_chord_by_rank(number, v)
print('rank ch is', ch)
# input('Press Enter to continue...')
if chord_choice is Chord_Choice.NTH_OUTCOME:
print('chord_choice is nth_outcome')
ch = get_chord_from_pitch_to_chord_by_nth_outcome(number, v)
print('nth_outcome ch is', ch)
# input('Press Enter to continue...')
short_chord = ch
try:
test_chord = harmony.ChordSymbol(ch)
except music21.Music21Exception:
print('WARNING Invalid chord symbol', ch)
short_chord = 'NC'
# input('Press Enter to continue...')
return short_chord
def get_first_note(a_stream):
"""
:param a_stream:
:return: the first note in the stream or None
"""
first_note = None
for n in a_stream.flatten():
if type(n) == music21.note.Note:
if first_note == None:
first_note = n
return first_note
def get_last_note(a_stream):
"""
:param a_stream:
:return: the last note in the stream or None
"""
last_note = None
for n in a_stream.flatten():
if type(n) == music21.note.Note:
last_note = n
return last_note
def stream_has_a_note(a_stream):
"""
return True if stream has a note
:param a_stream:
:return: True if stream has a note
"""
stream_has_note = False
for n in a_stream.flatten():
if type(n) == music21.note.Note:
stream_has_note = True
return stream_has_note
def has_chord_symbols(a_stream):
"""
return True if stream has a chord_symbol
:param a_stream:
:return: True if stream has a chord_symbol
"""
stream_has_chord_symbol = False
stream_copy = copy.deepcopy(a_stream)
for n in stream_copy.flatten():
if type(n) == music21.harmony.ChordSymbol:
stream_has_chord_symbol = True
if stream_has_chord_symbol:
print(a_stream, 'has chord symbols')
else:
print(a_stream, 'does NOT have chord symbols')
return stream_has_chord_symbol
class SongSectionValues:
"""
A class that stores SongSectionValues
such as duration types and tone range in a section of a song.
Methods include __init__ and update with a note
"""
# class variables shared by all instances
analyze_choice = 'Krumhansl' # my default as least errors on GSTQ 1 bar (Music21 default is Aarden same as key)
current_song_length_offset = 0.0
# lists of the key finder offset chord dictionaries for each harmonic rhythm
key_finder_offset_chords_1_beat = []
key_finder_offset_chords_2_beat = []
key_finder_offset_chords_1_bar = []
key_finder_offset_chords_2_bar = []
key_finder_offset_chords_4_bar = []
last_bass = 'C'
letter_current = 'A'
number_of_song_sections = 0
# offset_section = {} # dictionary to hold offset to section mapping e.g. {0.0: 'intro 1'}
offsets = None
OUTPUT_PATH = 'output' + os.sep
section_letter = {} # dictionary to hold section to letter mapping e.g. {'verse': 'A', 'chorus': 'B'}
song_chords_1_beat = ''
song_chords_2_beat = ''
song_chords_1_bar = ''
song_chords_2_bar = ''
song_chords_4_bar = ''
song_key = None
# offset_chord Dictionaries
song_offset_chord_1_beat = '{'
song_offset_chord_2_beat = '{'
song_offset_chord_1_bar = '{'
song_offset_chord_2_bar = '{'
song_offset_chord_4_bar = '{'
song_offset_placeholder_chords = '{'
songTimeSig = None
structure_by_name_long = ''
structure_by_name = ''
structure_by_name_initial = ''
structure_by_letter = ''
the_instrument = music21.instrument.Instrument(instrumentName='Piano')
TIME_SIG_WANTED = '3/4'
transpose = None
def get_first_note(self):
"""
:return: last first in stream1
"""
return self.stream1[0]
def get_last_note(self):
"""
:return: last note in stream1
"""
return self.stream1[-1]
def get_stream(self, start_note_offset, end_note_offset):
"""
gets a subset of stream1 (the whole song)
:param start_note_offset: song offsets >= this included in stream
:param end_note_offset: song offsets < this included in stream
:return: stream
"""
sub_stream = stream.Stream()
for n in self.stream1.flatten():
if type(n) == music21.note.Note or type(n) == music21.note.Rest:
if (n.offset >= start_note_offset) and (n.offset < end_note_offset):
sub_stream.append(n)
return sub_stream
def increment_sections(self):
SongSectionValues.number_of_song_sections += 1
# class instantiation automatically invokes __init__
def __init__(self, song_key, song_stream):
"""
takes song_key
and initialises section data
"""
print('SongSectionValues.__init__(self, song_key, song_stream)', song_key, song_stream)
# update class variables
SongSectionValues.song_key = song_key
SongSectionValues.song_stream = song_stream
# set instance variable unique to each instance
self.dur_prev = 0
self.note_prev = None
self.DURATION_SET = []
self.DUR_PREV_DIFF = 0
self.DUR_RATIONAL = True
self.DUR_TUPLET = False
self.DUR_LEAST = 99.0
self.DUR_LONGEST = 0.01
self.REST_NOTE_LINE_OFFSET = None
# self.stream_raw = stream.Stream() # original stream NR see .song_stream
self.stream1 = stream.Stream() # only notes and rests not chord symbols
self.TONES_ON_KEY = True
self.TONE_PREV_INTERVAL = 0
self.TONE_RANGE_BOTTOM = 'B9'
self.TONE_RANGE_TOP = 'C0'
self.TONE_SCALE_SET = []
# when looping over several analysis classes need to initialise more variables
SongSectionValues.current_song_length_offset = 0.0
SongSectionValues.letter_current = 'A'
SongSectionValues.number_of_song_sections = 0
SongSectionValues.offset_section = {} # dictionary to hold offset to section mapping e.g. {0.0: 'intro 1'}
SongSectionValues.OUTPUT_PATH = 'output' + os.sep
SongSectionValues.section_letter = {} # dictionary to hold section to letter mapping e.g. {'verse': 'A', 'chorus': 'B'}
SongSectionValues.song_chords_1_beat = ''
SongSectionValues.song_chords_2_beat = ''
SongSectionValues.song_chords_1_bar = ''
SongSectionValues.song_chords_2_bar = ''
SongSectionValues.song_chords_4_bar = ''
# offset_chord Dictionaries
SongSectionValues.song_offset_chord_1_beat = '{'
SongSectionValues.song_offset_chord_2_beat = '{'
SongSectionValues.song_offset_chord_1_bar = '{'
SongSectionValues.song_offset_chord_2_bar = '{'
SongSectionValues.song_offset_chord_4_bar = '{'
SongSectionValues.song_offset_placeholder_chords = '{'
# SongSectionValues.songTimeSig = None
SongSectionValues.structure_by_name_long = ''
SongSectionValues.structure_by_name = ''
SongSectionValues.structure_by_name_initial = ''
SongSectionValues.structure_by_letter = ''
SongSectionValues.the_instrument = music21.instrument.Instrument(instrumentName='Piano')
# SongSectionValues.TIME_SIG_WANTED = '3/4'
SongSectionValues.offset_section = get_offset_section(song_stream)
print('# -----------------------------------------------------------------------')
def get_analyze_choice(self):
"""
get analyze_choice from class variable
"""
# return analyze_choice class variable
return SongSectionValues.analyze_choice
def set_analyze_choice(self, analyze_choice):
"""
given analyze_choice update class variable
"""
# update class variable
SongSectionValues.analyze_choice = analyze_choice
def set_instrument(self, instrument):
"""
given instrument update class variable
"""
# update class variable
SongSectionValues.the_instrument = music21.instrument.Instrument(instrumentName=instrument)
def set_time_sig(self, songTimeSig):
"""
given time signature update class variable
"""
# update class variable
SongSectionValues.songTimeSig = songTimeSig
def set_section(self, name):
"""
given a section name
update the class and instance variables
"""
# update class variables
self.increment_sections()
SongSectionValues.structure_by_name_long = SongSectionValues.structure_by_name_long + str(name) + '-'
SongSectionValues.structure_by_name = SongSectionValues.structure_by_name + truncate_section(name) + '-'
SongSectionValues.structure_by_name_initial = SongSectionValues.structure_by_name_initial + truncate_section(name)[0]
# if section not in section_letter dictionary: add to dictionary, increment value
if truncate_section(name) not in SongSectionValues.section_letter:
SongSectionValues.section_letter[truncate_section(name)] = SongSectionValues.letter_current
SongSectionValues.letter_current = chr((ord(SongSectionValues.letter_current) - ord('A') + 1) % 26 + ord('A'))
# add section letter to structure_by_letter
SongSectionValues.structure_by_letter = SongSectionValues.structure_by_letter + SongSectionValues.section_letter[truncate_section(name)]
# set instance variable unique to each instance
self.name = name
def show_text_in_stream(self, a_stream):
# print('show_text_in_stream ------------------------------------------------- stream.id = decimal, hex', song.id, hex(song.id))
print("show_text_in_stream ------------------------------------------------- stream.id", a_stream.id)
# song.show('text')
offset_end = 0.0
stream_copy = copy.deepcopy(a_stream)
for n in stream_copy.flatten():
# print('type(n) ', type(n) )
if type(n) == music21.clef.TrebleClef:
print('music21.clef.TrebleClef')
if type(n) == music21.expressions.TextExpression:
# print('music21.expressions.TextExpression')
print('TextExpression =', n.content)
if type(n) == music21.key.KeySignature:
# print('music21.key.KeySignature', song.tonic.name, song.mode)
print('music21.key.KeySignature', self.stream1.keySignature) # None
first = True
for sKS in a_stream.flatten().getElementsByClass('KeySignature'):
if first:
a_stream.KeySignature = sKS
print('First KeySignature:', a_stream.KeySignature) # e.g. <music21.key.KeySignature of 1 flat>
print('.sharps:', a_stream.KeySignature.sharps) # e.g. -1
print('.getScale(major):',
a_stream.KeySignature.getScale('major')) # e.g. <music21.scale.MajorScale F major>
first = False
else:
print('other KeySignature:', sKS)
if type(n) == music21.metadata.Metadata:
# Metadata represent data for a work or fragment, including
# title, composer, dates, and other relevant information.
print('music21.metadata.Metadata')
print('all =', a_stream.metadata.all())
# print('title =', song.metadata.title) # crash if none
# print('composer =', song.metadata.composer)
# print('date = ', song.metadata.date)
# print('lyricist = ', song.metadata.lyricist)
if type(n) == music21.meter.TimeSignature:
# get the timesignatures
first = True
for tSig in a_stream.getTimeSignatures(): # may not be required, .cf song_section_values missed n=3/4 as tsig=4/4 on God_Save_The_Queen.mxl
if first:
a_stream.TimeSig = tSig
print('First Time Signature:',
tSig) # eg First Time Signature: <music21.meter.TimeSignature 4/4>
first = False
else:
print('Other Time Signature:', tSig)
if type(n) == music21.note.Note or type(n) == music21.note.Rest:
self.show_text_of_note(n)
if type(n) == music21.tempo.MetronomeMark:
print('music21.tempo.MetronomeMark', n.number)
# min_note, max_note = calc_the_note_range(song)
# print('min_note, max_note', min_note, max_note)
pass
def show_text_of_note(self, n):
"""
takes a note and the beats to the bar
and print the text to the console
"""
beat_count = SongSectionValues.songTimeSig.numerator / (SongSectionValues.songTimeSig.denominator / 4)
# print('beat_count = ts.numerator / (ts.denominator / 4)', beat_count, ts.numerator, ts.denominator)
offset_end = n.offset + n.duration.quarterLength
# calculate offset_bar_end = beat_count * ( truncated (n.offset / beat_count) + 1)
truncated_bar = int('%.0f' % (n.offset / beat_count))
offset_bar_end = beat_count * (truncated_bar + 1)
# print('offset_bar_end = beat_count * (truncated_bar + 1)', offset_bar_end, beat_count, truncated_bar)
if offset_end > offset_bar_end:
# print('WARNING next duration: \t\t\t\t offset_end', offset_end, '>', 'offset_bar_end', offset_bar_end,'- Replace with tied note or rest to end of bar and rest at beginning of next bar.')
pass
# print("Note: %s%d %0.1f" % (n.pitch.name, n.pitch.octave, n.duration.quarterLength))
if type(n) == music21.note.Note:
print('offset %.4f' % n.offset, '\t bar %.4f' % ((n.offset / beat_count) + 1), '\t o', n.offset, '\t + ql',
n.duration.quarterLength, '\t = o_end %.4f' % offset_end, '\t note qLen lyric:\t', n.nameWithOctave,
'\t',
n.duration.quarterLength, '\t', n.lyric)
if type(n) == music21.note.Rest:
# print('offset_float %.4f' % n.offset, 'bar %.4f'% (n.offset / beat_count), 'rest quarterLength, offset_fraction, offset_end:', n.duration.quarterLength, n.offset, '%.4f' %offset_end )
print('offset %.4f' % n.offset, '\t bar %.4f' % ((n.offset / beat_count) + 1), '\t o', n.offset, '\t + ql',
n.duration.quarterLength, '\t = o_end %.4f' % offset_end, '\t rest quarterLength:',
n.duration.quarterLength)
def update_rest(self, n):
"""
add rest note to stream
"""
# append note
self.stream1.append(n)
def update(self, n, first_note_of_section):
"""
if triplet: DUR_TUPLET = True
if note.dur < DUR_LEAST: DUR_LEAST = note.dur
if note.dur > DUR_LONGEST: DUR_LONGEST = note.dur
if note not scale note: TONES_ON_KEY = True
if note.nameWithOctave < TONE_RANGE_BOTTOM: TONE_RANGE_BOTTOM = note.nameWithOctave
if note.nameWithOctave > TONE_RANGE_TOP: TONE_RANGE_TOP = note.nameWithOctave
"""
# complex durations
c1_6 = Fraction(1, 6)
c1_3 = Fraction(1, 3)
c2_3 = Fraction(2, 3)
c4_3 = Fraction(4, 3)
c8_3 = Fraction(8, 3)
if first_note_of_section:
# update (only on first note) REST_NOTE_LINE_OFFSET
prev_measure_offset = (math.trunc(n.offset / SongSectionValues.songTimeSig.beatCount) ) * SongSectionValues.songTimeSig.beatCount
note_offset_from_start_measure = n.offset - prev_measure_offset
# print('n.offset',n.offset,'ts.beatCount',ts.beatCount,'prev_measure_offset',prev_measure_offset,'note_offset_from_start_measure',note_offset_from_start_measure )
self.REST_NOTE_LINE_OFFSET = note_offset_from_start_measure
first_note_of_section = False
# append note
self.stream1.append(n)
else: # notes other than first note
# append note
self.stream1.append(n)
# update DUR_PREV_DIFF, TONE_PREV_INTERVAL
# from MarkMelGen.conf:
# DUR_PREV_DIFF - compare duration with previous duration, e.g. where 2, duration is >= 1/2 previous and <= 2 x previous etc ,
# where 0 and <= 1, do not compare with previous duration.
# if this_dur_Prev_diff is bigger, update DUR_PREV_DIFF
bigger = False
if self.dur_prev != 0: # do not work out for first note
if self.dur_prev < n.duration.quarterLength: # previous note is shorter e.g. dur_prev = 1.0 < n = 2.0
this_dur_Prev_diff = (float(n.duration.quarterLength)) / (float(Fraction(self.dur_prev)))
# this_dur_Prev_diff = e.g. (n = 2.0) / dur_prev = 1.0
if this_dur_Prev_diff > self.DUR_PREV_DIFF: bigger = True
if self.dur_prev > n.duration.quarterLength: # previous note is longer e.g. dur_prev = 4.0 < n = 2.0
this_dur_Prev_diff = (float(Fraction(self.dur_prev)) / float(
n.duration.quarterLength)) # this_dur_Prev_diff = e.g. (n = 2.0) * dur_prev = 4.0
if this_dur_Prev_diff > self.DUR_PREV_DIFF: bigger = True
if bigger: self.DUR_PREV_DIFF = this_dur_Prev_diff
# update TONE_PREV_INTERVAL: calc semitone_interval_with_prev_note for
aInterval = interval.Interval(self.note_prev, n)
AIntSemi = abs(aInterval.semitones)
if AIntSemi > self.TONE_PREV_INTERVAL: self.TONE_PREV_INTERVAL = AIntSemi
# any note update:
# DURATION_SET, DUR_RATIONAL, DUR_TUPLET, DUR_LEAST, DUR_LONGEST, TONES_ON_KEY, TONE_RANGE_BOTTOM, TONE_RANGE_TOP
# dur_prev
duration_found = False
for dur_from_set in self.DURATION_SET:
if Fraction(n.duration.quarterLength) == Fraction(dur_from_set):
duration_found = True
if not duration_found:
bisect.insort(self.DURATION_SET, str(n.duration.quarterLength))
# if triplet: DUR_TUPLET = True
if ((n.duration.quarterLength == c1_6) or (n.duration.quarterLength == c1_3) or (n.duration.quarterLength == c2_3)):
self.DUR_TUPLET = True
self.DUR_RATIONAL = False
# if note.dur < DUR_LEAST: DUR_LEAST = note.dur
if n.duration.quarterLength < self.DUR_LEAST: self.DUR_LEAST = n.duration.quarterLength
# if note.dur > DUR_LONGEST: DUR_LONGEST = note.dur
if n.duration.quarterLength > self.DUR_LONGEST: self.DUR_LONGEST = n.duration.quarterLength
# if note not scale note: TONES_ON_KEY = True
if self.song_key.mode == 'major':
sc = scale.MajorScale(self.song_key.tonic.name)
else:
sc = scale.MinorScale(self.song_key.tonic.name)
scale_degree = sc.getScaleDegreeFromPitch(n)
if scale_degree == None:
self.TONES_ON_KEY = False
# if note.nameWithOctave < TONE_RANGE_BOTTOM: TONE_RANGE_BOTTOM = note.nameWithOctave
# if n.nameWithOctave < self.TONE_RANGE_BOTTOM: self.TONE_RANGE_BOTTOM = n.nameWithOctave
# if note.nameWithOctave > TONE_RANGE_TOP: TONE_RANGE_TOP = note.nameWithOctave
# Following gave False with next line: A5 > G5, B5 > G5, C6 > G5 (assume bug with music21)
# if n.nameWithOctave > self.TONE_RANGE_TOP: self.TONE_RANGE_TOP = n.nameWithOctave
new_note = note.Note()
new_note.nameWithOctave = n.nameWithOctave
min_note = note.Note()
min_note.nameWithOctave = self.TONE_RANGE_BOTTOM
max_note = note.Note()
max_note.nameWithOctave = self.TONE_RANGE_TOP
if note.Note(n.nameWithOctave) < note.Note(min_note.nameWithOctave):
self.TONE_RANGE_BOTTOM = n.nameWithOctave
if note.Note(n.nameWithOctave) > note.Note(max_note.nameWithOctave):
self.TONE_RANGE_TOP = n.nameWithOctave
# TONE_SCALE_SET
tone_found = False
for tone_from_set in self.TONE_SCALE_SET:
if pitch.Pitch(n.name).ps == pitch.Pitch(tone_from_set).ps:
tone_found = True
if not tone_found:
bisect.insort(self.TONE_SCALE_SET, str(n.name))
self.dur_prev = n.duration.quarterLength # update self.dur_prev
self.note_prev = n
def print_placeholder_chords(self):
"""
populate song_offset_placeholder_chords with offsets and pitch class "chords" from input music and print e.g.
get first and last note
for each chord symbol stream item
get_pitch_classes_in_stream of the chord
append the offset and pitch_classes to the song_offset_placeholder_chords
e.g. song_offset_placeholder_chords = {0.0: '1000 0000 0000', 12.0: '0000 1100 0000', 24.0: '1000 0000 0001', 36.0: '1000 0000 0000'}
:return: void
"""
print('print_placeholder_chords(self)')
# print('song_key.name', song_key.name)
first_note = self.get_first_note()
first_note_offset = first_note.offset
print('first note offset', first_note.offset)
last_note = self.get_last_note()
last_note_offset = last_note.offset
last_note_duration_quarterLength = last_note.duration.quarterLength
end_of_last_note_offset = last_note_offset + last_note_duration_quarterLength
current_section_start_note_offset = SongSectionValues.current_song_length_offset
print('last_note_offset', last_note_offset,'last_note_duration_quarterLength',last_note_duration_quarterLength)
# self.show_text_in_stream(self.stream1)
# print('# section key', song_key.name)
looking_for_first_chord = True
next_note_is_first_chord_offset = False
next_note_is_chord_offset = False
start_note_offset = 0.0
last_note_duration = 0.0
tune_sig_to_chord = {}
first_chord = True
#
print('has_chord_symbols(self.song_stream)', has_chord_symbols(self.song_stream))
# input('Press Enter to continue...')
# for each stream element in a_song
for n in self.song_stream.flatten():
# print('type(n)',type(n))
if type(n) == music21.harmony.ChordSymbol or type(n) == music21.harmony.NoChord:
if type(n) == music21.harmony.NoChord:
print('NoChord', n.figure, n)
else:
# print('ChordSymbol ', n, n.figure, n.key, 'If writeAsChord False the harmony symbol is written',n.writeAsChord, n.romanNumeral )
print('ChordSymbol ', n.figure, n )
# if chord and chord not 'NC' and looking_for_first_chord:
if looking_for_first_chord and type(n) != music21.harmony.NoChord:
looking_for_first_chord = False
next_note_is_first_chord_offset = True
chord_1 = n.figure
map_chord = chord_1
else: # found a later chord
# if type(n) != music21.harmony.NoChord:
next_note_is_chord_offset = True
chord_2 = n.figure
# if type(n) == music21.harmony.NoChord:
# print('NoChord', n.figure, n)
if type(n) == music21.note.Note or type(n) == music21.note.Rest:
last_note_duration = n.duration.quarterLength
if type(n) == music21.note.Note:
print('note offset, duration, nameWithOctave', n.offset, n.duration.quarterLength, n.nameWithOctave,)
else:
print('rest offset, duration, ', n.offset, n.duration.quarterLength)
if next_note_is_first_chord_offset:
# get next note
start_note_offset = n.offset
looking_for_first_chord = False
next_note_is_first_chord_offset = False
if next_note_is_chord_offset:
next_note_is_chord_offset = False
end_note_offset = n.offset
shorter_stream = self.get_stream(start_note_offset, end_note_offset)
if stream_has_a_note(shorter_stream) :
# print('ANALYZE_CHOICE =', analyze_choice)
if map_chord != 'N.C.' and map_chord != 'NC':
# different_pitch_classes = get_different_pitch_classes_in_stream(shorter_stream)
key_chord = get_pitch_classes_in_stream(shorter_stream)
key = (display_pitch_classes(key_chord))
# convert key_chord to sho_cho
if first_chord:
SongSectionValues.song_offset_placeholder_chords = SongSectionValues.song_offset_placeholder_chords + str(
start_note_offset) + ": '" + key + "'"
first_chord = False
else: # subsequent chords
SongSectionValues.song_offset_placeholder_chords = SongSectionValues.song_offset_placeholder_chords + ", " + str(
start_note_offset) + ": '" + key + "'"
else: # no chord
# append offset and no chord to song_offset_placeholder_chords
if first_chord:
SongSectionValues.song_offset_placeholder_chords = SongSectionValues.song_offset_placeholder_chords + str(
start_note_offset) + ": '" + NO_CHORD_DISPLAY_PITCH_CLASSES + "'"
# start_note_offset) + ": '" + str(NO_CHORD_DISPLAY_PITCH_CLASSES).replace(" ", "") + "'"
first_chord = False
else: # subsequent chords
SongSectionValues.song_offset_placeholder_chords = SongSectionValues.song_offset_placeholder_chords + ", " + str(
start_note_offset) + ": '" + NO_CHORD_DISPLAY_PITCH_CLASSES + "'"
# start_note_offset) + ": '" + str(NO_CHORD_DISPLAY_PITCH_CLASSES).replace(" ", "") + "'"
map_chord = chord_2
start_note_offset = end_note_offset
# handle last chord / note(s)
# 20221224 last note chord wrong
map_chord = chord_1
if map_chord != 'N.C.' and map_chord != 'NC':
end_note_offset = start_note_offset + last_note_duration
shorter_stream = self.get_stream(start_note_offset, end_note_offset)
if stream_has_a_note(shorter_stream) :
# key_chord = shorter_stream.analyze(self.analyze_choice)
key_chord = get_pitch_classes_in_stream(shorter_stream)
key = (display_pitch_classes(key_chord))
# convert key_chord to sho_cho
# sho_cho = short_chord(key_chord.name)
SongSectionValues.song_offset_placeholder_chords = SongSectionValues.song_offset_placeholder_chords + ", " + str(
start_note_offset) + ": '" + key + "'"
else: # no chord
SongSectionValues.song_offset_placeholder_chords = SongSectionValues.song_offset_placeholder_chords + ", " + str(
start_note_offset) + ": '" + NO_CHORD_DISPLAY_PITCH_CLASSES + "'"
# print('tune_sig_to_chord with frequency=', tune_sig_to_chord)
print('SongSectionValues.song_offset_placeholder_chords', SongSectionValues.song_offset_placeholder_chords)
# input('Press Enter to continue...')
def print(self):
"""
get first and last note
calc harmonic rhythm, offset_increment
for each harmonic rhythm
for each offset_increment
analyse the short chord from the song key of the short stream
append the offset and short chord to the song_offset_chord
:return: void
"""
print('# section', self.number_of_song_sections, 'name =', self.name)
truncated_section_name = truncate_section(self.name)
printable_name = '[song_' + truncated_section_name + ']'
# song_key = self.stream1.analyze('key')
# print('ANALYZE_CHOICE =', self.analyze_choice)
song_key = self.stream1.analyze(self.analyze_choice)
# print('song_key.name', song_key.name)
first_note = self.get_first_note()
first_note_offset = first_note.offset
# print('first note offset', first_note.offset)
last_note = self.get_last_note()
last_note_offset = last_note.offset
last_note_duration_quarterLength = last_note.duration.quarterLength
current_section_start_note_offset = SongSectionValues.current_song_length_offset
# print('last_note_offset', last_note_offset,'last_note_duration_quarterLength',last_note_duration_quarterLength)
# self.show_text_in_stream(self.stream1)
# calc harmonic rhythm 1 bars per chord i.e. offset length of 1 bar
beat_count = SongSectionValues.songTimeSig.numerator / (SongSectionValues.songTimeSig.denominator / 4)
# print('Possible Harmonic rhythms ------------------------------------------------------------------------------')
# print('song_key.name', song_key.name)
# print('Possible Harmonic rhythms. Chord changes every: -------------------------------------------------------')
print('# section key', song_key.name)
print('Harmonic rhythms. Chord may change every: 1 beat, 2 beats, 1 bar, 2 bars or 4 bars.')
# chord each beat
print('1 beat ...')
chord_each_beat = ''
chord_count_init = 1.0
chord_count = 1.0
chord_count_inc = 1.0
first_chord = True
sho_cho = 'NC '
offset_increment = beat_count / SongSectionValues.songTimeSig.numerator
if SongSectionValues.songTimeSig.ratioString == '6/8':
print('Time Signature', SongSectionValues.songTimeSig)
chord_count_init = offset_increment
chord_count = offset_increment
chord_count_inc = offset_increment
if SongSectionValues.songTimeSig.ratioString == '9/8':
print('Time Signature 9/8')
chord_count_init = offset_increment
chord_count = offset_increment
chord_count_inc = offset_increment
if SongSectionValues.songTimeSig.ratioString == '2/2' or SongSectionValues.songTimeSig.ratioString == '12/8':
print('Time Signature', SongSectionValues.songTimeSig)
offset_increment = beat_count / 4
# e.g. for 2 / 2, beat_count = 4.0 , offset_increment = 1.0
# e.g. = 6 / 4 = 1.5
chord_count_init = 1.5
chord_count = 1.5
chord_count_inc = 1.5
# print('1 beat: offset_increment', offset_increment,'chord_count_inc',chord_count_inc)
if SongSectionValues.offsets != None:
offset_increment = SongSectionValues.offsets[Harmonic_Rhythm.BEAT1.value]
print('1 beat: offset_increment', offset_increment)
for start_note_offset in numpy.arange(current_section_start_note_offset, (last_note_offset + last_note_duration_quarterLength), offset_increment):
# print('start_note_offset', start_note_offset, 'end_note_offset', (start_note_offset + offset_increment))
shorter_stream = self.get_stream(start_note_offset, (start_note_offset + offset_increment) )
# self.show_text_in_stream(shorter_stream)
if stream_has_a_note(shorter_stream):
# print('ANALYZE_CHOICE =', self.analyze_choice)
song_key = shorter_stream.analyze(self.analyze_choice)
song_key_name = short_chord(song_key.name)
sho_cho = short_chord(song_key.name)
else:
song_key_name = sho_cho
# print('Chord = ', song_key.name)
chord_each_beat = chord_each_beat + song_key_name
if first_chord:
SongSectionValues.song_offset_chord_1_beat = SongSectionValues.song_offset_chord_1_beat + str(start_note_offset) + ": '" + str(sho_cho).replace(" ", "") + "'"
first_chord = False
else: # subsequent chords
SongSectionValues.song_offset_chord_1_beat = SongSectionValues.song_offset_chord_1_beat + ", " + str(start_note_offset) + ": '" + str(sho_cho).replace(" ", "") + "'"
if chord_count == beat_count:
chord_each_beat = chord_each_beat + '|'
chord_count = chord_count_init
else:
chord_count = chord_count + chord_count_inc