Skip to content

Instantly share code, notes, and snippets.

@duhaime
Last active Sep 28, 2021
Embed
What would you like to do?
MIDI
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,
forceSource=False,
quantizePost=False,
quarterLengthDivisors=(4,3), # smaller numbers mean fewer different note durations
).stripTies(inPlace=True)
else:
m = music21.midi.MidiFile()
m.open(path)
m.read()
m.close()
if kwargs.get('remove_percussion', True):
tracks = [t for t in m.tracks if not any([e.channel == 10 for e in t.events])]
else:
tracks = m.tracks
s = music21.stream.Score()
music21.midi.translate.midiTracksToStreams(tracks,
inputM21=s,
forceSource=False,
quantizePost=False,
ticksPerQuarter=m.ticksPerQuarterNote,
quarterLengthDivisors=(4,3),
)
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:
try:
pitch = i.pitch.midi
if kwargs.get('constrain'): pitch = constrain_pitch(pitch)
s += 'n{}_{} '.format(pitch, round(duration, 4))
except:
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 = music21.stream.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
else:
print('did not expect', i)
return stream
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