Melsim
Melsim is an R package for pairwise melodic similarity (Silas & Frieler), building on SIMILE (Müllensiefen & Frieler, 2006). melody-features wraps it so you can call melsim from Python on MIDI files (or, via a legacy helper, on note arrays).
Important notes
Melsim is not part of
get_all_features(). Similarity is modular: you choose which melodies, measures, and transformations to compare.You need a working R install, plus the melsim R package and its dependencies (the wrapper can install those for you).
Measure and transformation names are case-sensitive.
Requirements and setup
Install R and ensure
Rscriptis on yourPATH.From Python, install the CRAN/GitHub packages the wrapper expects:
from melody_features.melsim_wrapper.melsim import install_dependencies
install_dependencies()
This installs required CRAN packages (dplyr, proxy, dtw, …) and
melsim from GitHub (sebsilas/melsim). To only check what is missing:
from melody_features.melsim_wrapper.melsim import check_r_packages_installed
check_r_packages_installed(install_missing=False)
# raises ImportError listing missing packages; pass install_missing=True to install
Main entry point: get_similarity_from_midi
Prefer get_similarity_from_midi().
It reads MIDI through melsim’s own reader and returns either a single score
or a dictionary of pairwise scores.
Inputs for midi_path1:
one MIDI path (then
midi_path2is required)a directory of
.mid/.midifiles → all pairwise comparisonsa list of MIDI paths → all pairwise comparisons
Useful parameters:
method— string or list of measure names (default"opti3")transformation— string or list; domain to compare (e.g."pitch","parsons"). Required for most measures other than compositeopti3; defaults to"pitch"in multi-file runs when omittedoutput_file— optional path; multi-file results are also written as JSON recordsbatch_size/r_timeout— batch size for R calls and timeout in seconds
Compare two files
import melody_features as mf
from melody_features.melsim_wrapper.melsim import get_similarity_from_midi
files = mf.get_corpus_files("essen", max_files=2)
a, b = files[0], files[1]
# Composite measure (no transformation needed)
score = get_similarity_from_midi(a, b, method="opti3")
print(f"opti3: {score:.3f}")
jaccard = get_similarity_from_midi(
a, b, method="Jaccard", transformation="pitch"
)
edit = get_similarity_from_midi(
a, b, method="edit_sim", transformation="parsons"
)
print(f"Jaccard(pitch)={jaccard:.3f}, edit_sim(parsons)={edit:.3f}")
Returns: float for a single pair and a single method.
Pairwise corpus (or directory)
import melody_features as mf
from melody_features.melsim_wrapper.melsim import get_similarity_from_midi
files = mf.get_corpus_files("essen", max_files=10)
results = get_similarity_from_midi(
files,
method=["Jaccard", "edit_sim"],
transformation=["pitch", "parsons"],
output_file="essen10_similarity.json",
)
# results: dict[(file1, file2, method, transformation)] -> float
for (f1, f2, method, trans), score in list(results.items())[:3]:
print(f"{f1} vs {f2} | {method}/{trans}: {score:.3f}")
You can pass a directory path instead of a list; every .mid / .midi
inside is included. You need at least two files.
JSON output (when output_file is set) is a list of records with
file1, file2, method, transformation, and similarity.
Worked example in the repo
src/melody_features/melsim_wrapper/example.py shows the same patterns
(two-file calls, then a small Essen subset with multiple methods and
transforms). Run it from the repository root after
install_dependencies().
Similarity measures
Names are case-sensitive:
# |
Name |
|---|---|
1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
Transformations
A transformation is the representation domain used for the comparison (called “transformation” in melsim):
# |
Name |
Typical use |
|---|---|---|
1 |
|
Raw MIDI pitch sequence |
2 |
|
Pitch intervals |
3 |
|
Coarse interval classes |
4 |
|
Up / down / repeat contour (Parsons code) |
5 |
|
Pitch class |
6 |
|
Inter-onset interval classes |
7 |
|
Duration classes |
8 |
|
Joint interval × IOI class |
9 |
|
Implied harmonic context |
10 |
|
Inter-onset intervals |
11 |
|
Phrase segmentation |
For multi-file runs, if you omit transformation, the wrapper defaults to
["pitch"]. For opti3, transformation is unused.
Legacy: arrays instead of MIDI paths
get_similarity() accepts pitch /
onset / offset arrays for two melodies. Prefer
get_similarity_from_midi when you have files; the MIDI path is faster and
stays consistent with melsim’s reader.
import numpy as np
from melody_features.melsim_wrapper.melsim import get_similarity
from melody_features.io.midi import load_midi
m1 = load_midi("a.mid")
m2 = load_midi("b.mid")
score = get_similarity(
np.array(m1.pitches), np.array(m1.starts), np.array(m1.ends),
np.array(m2.pitches), np.array(m2.starts), np.array(m2.ends),
method="Jaccard",
transformation="pitch",
)
API reference
Full signatures and docstrings:
See also
Upstream package: https://github.com/sebsilas/melsim
Repo demo:
src/melody_features/melsim_wrapper/example.pyInstallation — optional R / melsim system requirements
Corpora — bundled MIDI for trials