Finding a compact representation of melody and rhythm.
First we'll create some functions to turn single notes, chords and rests into these Triplets.
# test
from testing import test_eq
quarter = duration.Duration("quarter")
def loud(n: note.Note, velocity=120) -> note.Note:
n.volume = volume.Volume(velocity=velocity)
return n
test_eq(
("C3", "quarter", 120), _parse_single_note(loud(note.Note("C3", duration=quarter)))
)
test_eq(None, _parse_single_note(note.Note("C3", duration=duration.Duration(34.0))))
test_eq(
("1.3.5", "quarter", 80),
_parse_chord(loud(chord.Chord([1, 3, 5], duration=quarter), velocity=80)),
)
test_eq(None, _parse_chord(chord.Chord([1, 3, 5], duration=duration.Duration(0.0))))
test_eq(("R", "quarter", 0), _parse_rest(note.Rest(duration=quarter)))
test_eq(None, _parse_rest(note.Rest(duration=duration.Duration(0.0))))
As a normalization step, we need to transpose all the music to the same key, so we'll choose C for convenience.
# test
from testing import test_eq
s = converter.parse("data/ff4-main.mid")
originalKey = s.analyze("key")
transposed, mode = _transpose_to_C(s)
transposedKey = transposed.analyze("key")
test_eq(key.Key("CM"), transposedKey)
test_eq(originalKey.mode, mode)
# test
from testing import test_eq
s = converter.parse("data/rugrats.mid")
row = _parse_part(s.parts[0].recurse(), "major")
test_eq("piano", row["instrument"])
test_eq(102.0, row["bpm"])
test_eq("major", row["mode"])
test_eq("4/4", row["time_signature"])
test_eq(
[("R", "eighth", 0), ("R", "breve", 0), ("C3", "eighth", 103)], row["notes"][0:3]
)
!ls data
# test
from testing import test_eq, path
df = parse_midi_file(path("data/ff4-main.mid"))
test_eq(1, len(df))
test_eq("piano", df.get("instrument")[0])
triplets = row_to_triplets(df, 0)
test_eq(
[("E5", "eighth", 110), ("B4", "eighth", 110), ("G4", "eighth", 110)], triplets[0:3]
)
test_eq(1, len(df))
df = parse_midi_file(path("data/rugrats.mid"))
test_eq(1, len(df))
Turning triplets back into MIDI streams¶
Another interesting thing is to be able to go from triplets to MIDI streams, display their scores and even an audible IPython widget to play the audio.
But first of all, some utilities to turn these triplets into music21
elements:
Now let's try to visualize and listen to these streams.
If you're running Mac or Linux, uncomment the right brew
or apt
call:
# skip
# hide
#!brew install fluidsynth
#!apt install fluidsynth
#!wget ftp://ftp.osuosl.org/pub/musescore/soundfont/MuseScore_General/MuseScore_General.sf3
#!pip install midi2audio
# skip
# hide
%load_ext music21.ipython21.ipExtension
import json, random
from IPython.core.display import display, HTML, Javascript
from IPython.display import Audio
from midi2audio import FluidSynth
def show_score(score):
xml = open(score.write("musicxml")).read()
show_xml(xml)
def show_xml(xml):
DIV_ID = "OSMD-div-" + str(random.randint(0, 1000000))
# print("DIV_ID", DIV_ID)
msg = "loading OpenSheetMusicDisplay"
msg = ""
display(
HTML('<div style="background: white" id="' + DIV_ID + '">{}</div>'.format(msg))
)
# print('xml length:', len(xml))
script = """
console.log("loadOSMD()");
function loadOSMD() {
return new Promise(function(resolve, reject){
if (window.opensheetmusicdisplay) {
console.log("already loaded")
return resolve(window.opensheetmusicdisplay)
}
console.log("loading osmd for the first time")
// OSMD script has a 'define' call which conflicts with requirejs
var _define = window.define // save the define object
window.define = undefined // now the loaded script will ignore requirejs
var s = document.createElement( 'script' );
s.setAttribute( 'src', "https://cdn.jsdelivr.net/npm/opensheetmusicdisplay@0.3.1/build/opensheetmusicdisplay.min.js" );
//s.setAttribute( 'src', "/custom/opensheetmusicdisplay.js" );
s.onload=function(){
window.define = _define
console.log("loaded OSMD for the first time",opensheetmusicdisplay)
resolve(opensheetmusicdisplay);
};
document.body.appendChild( s ); // browser will try to load the new script tag
})
}
loadOSMD().then((OSMD)=>{
console.log("loaded OSMD",OSMD)
var div_id = "";
console.log(div_id)
window.openSheetMusicDisplay = new OSMD.OpenSheetMusicDisplay(div_id);
openSheetMusicDisplay
.load()
.then(
function() {
console.log("rendering data")
openSheetMusicDisplay.render();
}
);
})
""".replace(
"", DIV_ID
).replace(
"", json.dumps(xml)
)
display(Javascript(script))
return DIV_ID
def view_song(triplets, name="song"):
stream = triplets_to_stream(triplets)
write_midi(stream, f"{name}")
FluidSynth("MuseScore_General.sf3").midi_to_audio(f"{name}.mid", f"{name}.wav")
show_score(stream)
return Audio(f"{name}.wav")
# skip
triplets = row_to_triplets(df, 0)
view_song(triplets)