Created
January 5, 2023 15:19
-
-
Save 0187773933/f92f3f0748b0aa309a6a7a4deba4f1a2 to your computer and use it in GitHub Desktop.
Tries to Find All Possible Transpositions for a Set of Handbells
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import sys | |
import json | |
import inspect | |
# pprint( inspect.getmembers( midi ) ) | |
from pathlib import Path | |
from pprint import pprint | |
import music21 | |
from music21 import * | |
def write_json( file_path , python_object ): | |
with open( file_path , 'w', encoding='utf-8' ) as f: | |
json.dump( python_object , f , ensure_ascii=False , indent=4 ) | |
def read_json( file_path ): | |
with open( file_path ) as f: | |
return json.load( f ) | |
def write_text( file_path , text_lines_list ): | |
with open( file_path , 'w', encoding='utf-8' ) as f: | |
f.writelines( text_lines_list ) | |
def read_text( file_path ): | |
with open( file_path ) as f: | |
return f.read().splitlines() | |
keys = [ "A" , "B" , "C" , "D" , "E" , "F" , "G" ] | |
key_modifiers = { | |
"####" : "quadruple-sharp" , | |
"###" : "triple-sharp" , | |
"##" : "double-sharp" , | |
"#~" : "one-and-a-half-sharp" , | |
"#" : "sharp" , | |
"~" : "half-sharp" , | |
"----" : "quadruple-flat" , | |
"---" : "triple-flat" , | |
"--" : "double-flat" , | |
"-`" : "one-and-a-half-flat" , | |
"-" : "flat" , | |
"`" : "half-flat" , | |
"" : "natural" , | |
} | |
# index 6 = natural | |
key_modifiers_order = [ '####' , '###' , '##' , '#~' , '#' , '~' , '' , '`' , '-' , '-`' , '--' , '---' , '----' ] | |
# Custom Handbell Range | |
lower_bound = [ "G" , 3 ] | |
upper_bound = [ "G" , 5 ] | |
# is_note_inside_range( [ transposed_note.name , transposed_note.octave ] ) | |
def is_note_inside_range( test_note ): | |
modifier = False | |
note = False | |
octave = test_note[ 1 ] | |
if len( test_note[ 0 ] ) > 1: | |
modifier = test_note[ 0 ][ 1 : ] | |
note = test_note[ 0 ][ 0 ] | |
else: | |
note = test_note[ 0 ] | |
# print( [ note , modifier , octave ] , lower_bound , upper_bound ) | |
if octave < lower_bound[ 1 ]: | |
return False | |
if octave > upper_bound[ 1 ]: | |
return False | |
if octave == lower_bound[ 1 ]: | |
if modifier == False: | |
return True | |
modifier_position = key_modifiers_order.index( modifier ) | |
if modifier_position > 6: | |
return False | |
if octave == upper_bound[ 1 ]: | |
if modifier == False: | |
return True | |
modifier_position = key_modifiers_order.index( modifier ) | |
if modifier_position < 6: | |
return False | |
return True | |
# custom to natural G3 and natural G5 | |
def check_if_notes_are_inside_range( notes ): | |
result = True | |
for index , note in enumerate( notes ): | |
# x_test = is_note_inside_range( [ note.name , note.octave ] ) | |
# note.name , note.octave , note.pitch , addLyric() , insertLyric() , note.nameWithOctave | |
modifier = note.name[ 1 : ] | |
modifier_position = key_modifiers_order.index( modifier ) | |
if note.octave < 3: | |
# print( "Octave is Below Lower Bound of 3!!!" ) | |
return False | |
if note.octave > 5: | |
# print( "Octave is Above Upper Bound of 5!!!" ) | |
return False | |
if note.octave == 3: | |
# print( "Octave is Equal to Lower Bound of 3!!!" ) | |
if modifier_position > 6: # aka "below" / "more flat" than the lower bound | |
# print( "Note is Below Lower Bound of G3-Natural" ) | |
return False | |
if note.octave == 5: | |
# print( "Octave is Equal to Upper Bound of 5!!!" , modifier ) | |
# key_modifiers_order.index( modifier ) | |
if modifier_position < 6: # aka "above" / "more sharp" than the upper bound | |
# print( "Note is Above Upper Bound of G5-Natural" ) | |
return False | |
return result | |
if __name__ == "__main__": | |
# 0.) Prep | |
input_midi = Path( sys.argv[ 1 ] ) | |
output_folder = input_midi.parent.joinpath( f"{input_midi.stem} - Keys" ) | |
output_folder.mkdir( exist_ok=True , parents=True ) | |
# 1.) Import Original Midi | |
midi = converter.parse( str( input_midi ) ) | |
original_key = midi.analyze( "key" ) | |
original_key_tonic = original_key.tonic | |
original_key_tonic_string = str( original_key_tonic ) | |
# 2.) Generate A Reasonable Amount of Up and Down Semi-Tone / M2 / minor-second / Half-Step Transpositions | |
possible_transpositions = [] | |
down_step_finished = False | |
up_step_finished = False | |
transposition_span = 50 | |
for i in range( 1 , transposition_span ): | |
print( "Step" , i , "of" , transposition_span ) | |
if down_step_finished == False: | |
try: | |
down_step = ( i * -1 ) | |
# print( f"Transposing Down [{i}] Semi-Tones" ) | |
down_test = midi.transpose( ( down_step ) ) | |
possible_transpositions.append( [ down_test , f"Down-{i}" ] ) | |
except Exception as e: | |
# print( e ) | |
# print( "Down Step Finished" ) | |
down_step_finished = True | |
if up_step_finished == False: | |
try: | |
# print( f"Transposing Up [{i}] Semi-Tones" ) | |
up_test = midi.transpose( i ) | |
possible_transpositions.append( [ up_test , f"Up-{i}" ] ) | |
except Exception as e: | |
print( e ) | |
print( "Up Step Finished" ) | |
up_step_finished = True | |
if down_step_finished == True and up_step_finished == True: | |
break | |
print( "Total Transpositions ===" , len( possible_transpositions ) ) | |
# for index , transposition in enumerate( possible_transpositions ): | |
# transposition[ 0 ].write( "midi" , fp=str(output_folder.joinpath( f"{input_midi.stem} - {transposition[ 1 ]}.mid" ) ) ) | |
# # transposition[ 0 ].show() | |
# 3.) Restrict To Handbell Range ? , might be off by one on either side of the bounds | |
possible_handbell_transpositions = [] | |
for index , transposition in enumerate( possible_transpositions ): | |
transposed_notes = [ x for x in transposition[ 0 ].flat if isinstance( x , music21.note.Note ) ] | |
transposed_notes_inside_range = check_if_notes_are_inside_range( transposed_notes ) | |
if transposed_notes_inside_range == True: | |
possible_handbell_transpositions.append( transposition ) | |
print( "Total Handbell Transpositions ===" , len( possible_handbell_transpositions ) ) | |
for index , transposition in enumerate( possible_handbell_transpositions ): | |
transposition[ 0 ].write( "midi" , fp=str(output_folder.joinpath( f"{input_midi.stem} - Handbell - {transposition[ 1 ]}.mid" ) ) ) | |
# transposition[ 0 ].show() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment