-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathNegativeHarmonizer.py
102 lines (76 loc) · 3.53 KB
/
NegativeHarmonizer.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
import argparse
import os
import mido
def get_mirror_axis(tonic):
return tonic + 3.5
def mirror_note_over_axis(note, axis):
original_note_distance = axis - note
return int(axis + original_note_distance)
def find_average_octave_of_tracks(mid):
octaves = {}
for i, track in enumerate(mid.tracks):
track_avg_notes = []
for message in track:
if message.type == 'note_on':
track_avg_notes.append(message.note)
if len(track_avg_notes) > 0:
track_avg_note = sum(track_avg_notes) / len(track_avg_notes)
octaves[i] = track_avg_note
try:
track_name = track.name
except AttributeError:
track_name = i
print(track_name, track_avg_note)
return octaves
def mirror_all_notes(mid, mirror_axis, ignored_channels):
for track in mid.tracks:
for message in track:
if message.type == 'note_on' or message.type == 'note_off':
if message.channel not in ignored_channels:
mirrored_note = mirror_note_over_axis(message.note, mirror_axis)
message.note = mirrored_note
return
def transpose_back_to_original_octaves(mid, original_octaves, new_octaves, ignored_channels):
for i, track in enumerate(mid.tracks):
if i in original_octaves:
notes_distance = original_octaves[i] - new_octaves[i]
octaves_to_transpose = round(notes_distance / 12)
for message in track:
if message.type == 'note_on' or message.type == 'note_off':
if message.channel not in ignored_channels:
transposed_note = message.note + (octaves_to_transpose * 12)
message.note = int(transposed_note)
def invert_tonality(mid, tonic, ignored_channels, adjust_octaves):
mirror_axis = get_mirror_axis(tonic)
if adjust_octaves:
print("---")
print("original average note values:")
original_octaves = find_average_octave_of_tracks(mid)
mirror_all_notes(mid, mirror_axis, ignored_channels)
if adjust_octaves:
print("---")
print("new average note values:")
new_octaves = find_average_octave_of_tracks(mid)
transpose_back_to_original_octaves(mid, original_octaves, new_octaves, ignored_channels)
print("---")
print("adjusted average note values:")
find_average_octave_of_tracks(mid)
return
def main(input_file, tonic, ignored_channels, adjust_octaves):
root, ext = os.path.splitext(input_file)
mid = mido.MidiFile(input_file)
invert_tonality(mid, tonic, ignored_channels, adjust_octaves)
mid.save(root + "_negative" + ext)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Negative Harmonize a midi file.')
parser.add_argument('file', metavar='f',
help='the input midi file (with no extension .mid)')
parser.add_argument('--tonic', type=int, default=60,
help='the tonic')
parser.add_argument('--ignore', type=int, nargs="+", default=[],
help='the midi channels to ignore (usually 9 for drums)')
parser.add_argument('--adjust-octaves', dest='adjust_octaves', action='store_true',
help='transpose octaves to keep bass instruments low')
parser.set_defaults(adjust_octaves=False)
args = parser.parse_args()
main(input_file=args.file, tonic=args.tonic, ignored_channels=args.ignore, adjust_octaves=args.adjust_octaves)