├── .gitignore
├── .gitmodules
├── .idea
├── NegativeHarmonizer.iml
├── misc.xml
├── modules.xml
└── vcs.xml
├── NegativeHarmonizer.py
├── README.md
└── midi_files
├── fugue-c-major.mid
└── fugue-c-major_negative.mid
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/workspace.xml
2 | midi_files/
3 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "python-midi"]
2 | path = python-midi
3 | url = https://github.com/vishnubob/python-midi
4 |
--------------------------------------------------------------------------------
/.idea/NegativeHarmonizer.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/NegativeHarmonizer.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import os
3 |
4 | import mido
5 |
6 |
7 | def get_mirror_axis(tonic):
8 | return tonic + 3.5
9 |
10 |
11 | def mirror_note_over_axis(note, axis):
12 | original_note_distance = axis - note
13 | return int(axis + original_note_distance)
14 |
15 |
16 | def find_average_octave_of_tracks(mid):
17 | octaves = {}
18 | for i, track in enumerate(mid.tracks):
19 | track_avg_notes = []
20 | for message in track:
21 | if message.type == 'note_on':
22 | track_avg_notes.append(message.note)
23 | if len(track_avg_notes) > 0:
24 | track_avg_note = sum(track_avg_notes) / len(track_avg_notes)
25 | octaves[i] = track_avg_note
26 | try:
27 | track_name = track.name
28 | except AttributeError:
29 | track_name = i
30 | print(track_name, track_avg_note)
31 | return octaves
32 |
33 |
34 | def mirror_all_notes(mid, mirror_axis, ignored_channels):
35 | for track in mid.tracks:
36 | for message in track:
37 | if message.type == 'note_on' or message.type == 'note_off':
38 | if message.channel not in ignored_channels:
39 | mirrored_note = mirror_note_over_axis(message.note, mirror_axis)
40 | message.note = mirrored_note
41 | return
42 |
43 |
44 | def transpose_back_to_original_octaves(mid, original_octaves, new_octaves, ignored_channels):
45 | for i, track in enumerate(mid.tracks):
46 | if i in original_octaves:
47 | notes_distance = original_octaves[i] - new_octaves[i]
48 | octaves_to_transpose = round(notes_distance / 12)
49 | for message in track:
50 | if message.type == 'note_on' or message.type == 'note_off':
51 | if message.channel not in ignored_channels:
52 | transposed_note = message.note + (octaves_to_transpose * 12)
53 | message.note = int(transposed_note)
54 |
55 |
56 | def invert_tonality(mid, tonic, ignored_channels, adjust_octaves):
57 | mirror_axis = get_mirror_axis(tonic)
58 |
59 | if adjust_octaves:
60 | print("---")
61 | print("original average note values:")
62 | original_octaves = find_average_octave_of_tracks(mid)
63 |
64 | mirror_all_notes(mid, mirror_axis, ignored_channels)
65 |
66 | if adjust_octaves:
67 | print("---")
68 | print("new average note values:")
69 | new_octaves = find_average_octave_of_tracks(mid)
70 |
71 | transpose_back_to_original_octaves(mid, original_octaves, new_octaves, ignored_channels)
72 |
73 | print("---")
74 | print("adjusted average note values:")
75 | find_average_octave_of_tracks(mid)
76 | return
77 |
78 |
79 | def main(input_file, tonic, ignored_channels, adjust_octaves):
80 | root, ext = os.path.splitext(input_file)
81 | mid = mido.MidiFile(input_file)
82 |
83 | invert_tonality(mid, tonic, ignored_channels, adjust_octaves)
84 | mid.save(root + "_negative" + ext)
85 |
86 |
87 | if __name__ == '__main__':
88 | parser = argparse.ArgumentParser(description='Negative Harmonize a midi file.')
89 |
90 | parser.add_argument('file', metavar='f',
91 | help='the input midi file (with no extension .mid)')
92 | parser.add_argument('--tonic', type=int, default=60,
93 | help='the tonic')
94 | parser.add_argument('--ignore', type=int, nargs="+", default=[],
95 | help='the midi channels to ignore (usually 9 for drums)')
96 | parser.add_argument('--adjust-octaves', dest='adjust_octaves', action='store_true',
97 | help='transpose octaves to keep bass instruments low')
98 | parser.set_defaults(adjust_octaves=False)
99 |
100 | args = parser.parse_args()
101 |
102 | main(input_file=args.file, tonic=args.tonic, ignored_channels=args.ignore, adjust_octaves=args.adjust_octaves)
103 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NegativeHarmonizer
2 | NegativeHarmonizer is a simple python program to invert the tonality of a music file. This works very well for compositions like Bach's fugues. If you try it on popular music, you'll probably need to transpose the instruments back into their proper ranges, as the bass will be on the top. Beyond the fun of negative-harmonizing songs you know, this is a useful tool for composers and producers to add variations to their work.
3 |
4 | Dependencies:
5 | * Python 3
6 | * mido
7 | * `pip install mido`
8 |
9 | Usage:
10 |
11 | `python NegativeHarmonizer.py midifile.mid --tonic 60 --ignore 9 --adjust-octaves`
12 |
13 | This example command
14 | 1. creates a new midifile named midifile_negative.mid
15 | 2. flipped over middle C (midi note number 60)
16 | 3. channel 9 is unaltered because we don't want to change the drums.
17 | 4. the tracks are transposed to be close to their original octave, so the bass guitar will stay in the bass range etc.
18 |
19 |
20 | You can hear more examples of what can be done with NegativeHarmonizer on my YouTube channel (with some neat Tonnetz Lattice visualizations):
21 |
22 |
23 |
24 | Some background on Negative Harmony from Jacob Collier: https://youtu.be/DnBr070vcNE?t=1m31s
25 |
26 | # Real-time MIDI plug-in for DAWs
27 | You can find the plug-in version here: https://github.com/lukemcraig/negative-harmonizer-plugin
28 |
--------------------------------------------------------------------------------
/midi_files/fugue-c-major.mid:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lukemcraig/NegativeHarmonizer/cb6d18d69f4adce25a0d64b828f5537dae03988b/midi_files/fugue-c-major.mid
--------------------------------------------------------------------------------
/midi_files/fugue-c-major_negative.mid:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lukemcraig/NegativeHarmonizer/cb6d18d69f4adce25a0d64b828f5537dae03988b/midi_files/fugue-c-major_negative.mid
--------------------------------------------------------------------------------