Skip to content

Instantly share code, notes, and snippets.

@0187773933
Created January 5, 2023 15:19
Show Gist options
  • Save 0187773933/f92f3f0748b0aa309a6a7a4deba4f1a2 to your computer and use it in GitHub Desktop.
Save 0187773933/f92f3f0748b0aa309a6a7a4deba4f1a2 to your computer and use it in GitHub Desktop.
Tries to Find All Possible Transpositions for a Set of Handbells
#!/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