Skip to content

Instantly share code, notes, and snippets.

Last active September 28, 2021 14:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save duhaime/b8947b22feb0cf2d7ab8c10daeb3bf9f to your computer and use it in GitHub Desktop.
Save duhaime/b8947b22feb0cf2d7ab8c10daeb3bf9f to your computer and use it in GitHub Desktop.
from fractions import Fraction
import music21
import glob
# remove leading / trailing whitespace
music21.defaults.ticksAtStart = 0
notes = set([12*j + i for i in [0,2,4,5,7,9,11] for j in [0,1,2,3,4,5,6,7,8,9,10] ])
def constrain_pitch(val):
# keep the pitch in C major, no incidentals
while val not in notes:
val = int(val) + 1
if val > max(notes):
val - 2
return val
def get_score(path, **kwargs):
# return a score object
if True:
s = music21.converter.parse(path,
quarterLengthDivisors=(4,3), # smaller numbers mean fewer different note durations
m = music21.midi.MidiFile()
if kwargs.get('remove_percussion', True):
tracks = [t for t in m.tracks if not any([ == 10 for e in])]
tracks = m.tracks
s =
return s
def midi_to_string(path, **kwargs):
d = {
'32nd': 1/32,
'16th': 1/16,
'eighth': 1/8,
'quarter': 1/4,
'half': 1/2,
'whole': 1,
score = get_score(path, **kwargs)
# transpose to c major / a minor
key = score.analyze('key')
assert key.mode in ['major', 'minor']
interval = 60 - key.tonic.midi
if key.mode == 'minor': interval -= 3
score = score.transpose(interval)
# convert midi to string
s = ''
last_offset = 0
for n in score.flat.notes:
# get the note/chord duration
duration = n.duration.components[0].type # n.duration.type returns complex for tied notes
if not duration or duration == 'zero': continue
duration = float(Fraction(d.get(duration, duration)))
# get the offset since the previous note/chord
delta = float(Fraction(n.offset - last_offset))
if delta: s += 'w{} '.format(round(delta, 4))
# add the note/chord to the string
for i in [n] if isinstance(n, music21.note.Note) else n.notes:
pitch = i.pitch.midi
if kwargs.get('constrain'): pitch = constrain_pitch(pitch)
s += 'n{}_{} '.format(pitch, round(duration, 4))
print(' * could not parse note', i)
last_offset = n.offset
return s
def string_to_midi(s):
# convert string back to midi
time = 0
stream =
for i in s.split():
if i.startswith('n'):
note, duration = i.lstrip('n').split('_')
n = music21.note.Note(int(note))
n.duration.quarterLength = float(duration) * 4
stream.insertIntoNoteOrChord(time, n)
elif i.startswith('w'):
duration = float(Fraction(i.lstrip('w')))
time += duration
print('did not expect', i)
return stream
Display the source blob
Display the rendered blob
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment