Note
Go to the end to download the full example code.
Melodic similarity#
This example demonstrates how we can calculate the similarity between two melodies using the melsim module, which is a Python wrapper for the melsim R package (sebsilas/melsim).
First, we’ll import the required modules.
import numpy as np
import pandas as pd
from amads.core.basics import Score
from amads.melody.similarity.melsim import (
check_r_packages_installed,
get_similarity,
r_get_similarity,
r_load_melody,
)
from amads.utils import check_python_package_installed
Check if all required dependencies are installed.
def test_check_dependencies():
check_python_package_installed("rpy2")
check_r_packages_installed(install_missing=True)
test_check_dependencies()
Installing CRAN package 'tibble'...
Installing CRAN package 'R6'...
Installing CRAN package 'remotes'...
Installing GitHub package 'melsim'...
R[write to console]: Using github PAT from envvar GITHUB_PAT. Use `gitcreds::gitcreds_set()` and unset GITHUB_PAT in .Renviron (or elsewhere) if you want to use the more secure git credential store instead.
R[write to console]: Downloading GitHub repo sebsilas/melsim@HEAD
itembankr (NA -> 612c220b9...) [GitHub]
viridisLite (NA -> 0.4.2 ) [CRAN]
RColorBrewer (NA -> 1.1-3 ) [CRAN]
labeling (NA -> 0.4.3 ) [CRAN]
farver (NA -> 2.1.2 ) [CRAN]
lattice (NA -> 0.22-7 ) [CRAN]
Matrix (NA -> 1.7-3 ) [CRAN]
nlme (NA -> 3.1-168 ) [CRAN]
withr (NA -> 3.0.2 ) [CRAN]
stringi (NA -> 1.8.7 ) [CRAN]
tidyselect (NA -> 1.2.1 ) [CRAN]
generics (NA -> 0.1.4 ) [CRAN]
proxy (NA -> 0.4-27 ) [CRAN]
scales (NA -> 1.4.0 ) [CRAN]
mgcv (NA -> 1.9-3 ) [CRAN]
MASS (NA -> 7.3-65 ) [CRAN]
isoband (NA -> 0.2.7 ) [CRAN]
gtable (NA -> 0.3.6 ) [CRAN]
cpp11 (NA -> 0.5.2 ) [CRAN]
stringr (NA -> 1.5.1 ) [CRAN]
purrr (NA -> 1.0.4 ) [CRAN]
dplyr (NA -> 1.1.4 ) [CRAN]
Rcpp (NA -> 1.0.14 ) [CRAN]
cba (NA -> 0.2-25 ) [CRAN]
ggplot2 (NA -> 3.5.2 ) [CRAN]
assertthat (NA -> 0.2.1 ) [CRAN]
dtw (NA -> 1.23-1 ) [CRAN]
emdist (NA -> 0.3-3 ) [CRAN]
tictoc (NA -> 1.2.1 ) [CRAN]
yaml (NA -> 2.3.10 ) [CRAN]
logging (NA -> 0.10-108 ) [CRAN]
tidyr (NA -> 1.3.1 ) [CRAN]
purrrlyr (NA -> 0.0.8 ) [CRAN]
R[write to console]: Downloading GitHub repo sebsilas/itembankr@HEAD
hrep (NA -> 461f98d03...) [GitHub]
withr (NA -> 3.0.2 ) [CRAN]
stringi (NA -> 1.8.7 ) [CRAN]
generics (NA -> 0.1.4 ) [CRAN]
cpp11 (NA -> 0.5.2 ) [CRAN]
tidyselect (NA -> 1.2.1 ) [CRAN]
stringr (NA -> 1.5.1 ) [CRAN]
purrr (NA -> 1.0.4 ) [CRAN]
dplyr (NA -> 1.1.4 ) [CRAN]
timechange (NA -> 0.3.0 ) [CRAN]
Rcpp (NA -> 1.0.14 ) [CRAN]
later (NA -> 1.4.2 ) [CRAN]
fastmap (NA -> 1.2.0 ) [CRAN]
digest (NA -> 0.6.37 ) [CRAN]
base64enc (NA -> 0.1-3 ) [CRAN]
htmltools (NA -> 0.5.8.1 ) [CRAN]
lazyeval (NA -> 0.2.2 ) [CRAN]
jsonlite (NA -> 2.0.0 ) [CRAN]
promises (NA -> 1.3.2 ) [CRAN]
rappdirs (NA -> 0.3.3 ) [CRAN]
fs (NA -> 1.6.6 ) [CRAN]
sass (NA -> 0.4.10 ) [CRAN]
mime (NA -> 0.13 ) [CRAN]
memoise (NA -> 2.0.1 ) [CRAN]
cachem (NA -> 1.1.0 ) [CRAN]
tinytex (NA -> 0.57 ) [CRAN]
jquerylib (NA -> 0.1.4 ) [CRAN]
fontawesome (NA -> 0.5.3 ) [CRAN]
bslib (NA -> 0.9.0 ) [CRAN]
xfun (NA -> 0.52 ) [CRAN]
highr (NA -> 0.11 ) [CRAN]
evaluate (NA -> 1.0.3 ) [CRAN]
yaml (NA -> 2.3.10 ) [CRAN]
rmarkdown (NA -> 2.29 ) [CRAN]
knitr (NA -> 1.50 ) [CRAN]
lattice (NA -> 0.22-7 ) [CRAN]
viridisLite (NA -> 0.4.2 ) [CRAN]
RColorBrewer (NA -> 1.1-3 ) [CRAN]
labeling (NA -> 0.4.3 ) [CRAN]
farver (NA -> 2.1.2 ) [CRAN]
Matrix (NA -> 1.7-3 ) [CRAN]
nlme (NA -> 3.1-168 ) [CRAN]
MASS (NA -> 7.3-65 ) [CRAN]
tidyr (NA -> 1.3.1 ) [CRAN]
snakecase (NA -> 0.11.1 ) [CRAN]
lubridate (NA -> 1.9.4 ) [CRAN]
hms (NA -> 1.1.3 ) [CRAN]
crosstalk (NA -> 1.2.1 ) [CRAN]
httpuv (NA -> 1.6.16 ) [CRAN]
htmlwidgets (NA -> 1.6.4 ) [CRAN]
GPArotation (NA -> 2025.3-1 ) [CRAN]
mnormt (NA -> 2.1.1 ) [CRAN]
scales (NA -> 1.4.0 ) [CRAN]
mgcv (NA -> 1.9-3 ) [CRAN]
isoband (NA -> 0.2.7 ) [CRAN]
gtable (NA -> 0.3.6 ) [CRAN]
signal (NA -> 1.8-1 ) [CRAN]
logging (NA -> 0.10-108 ) [CRAN]
assertthat (NA -> 0.2.1 ) [CRAN]
janitor (NA -> 2.2.1 ) [CRAN]
DT (NA -> 0.33 ) [CRAN]
psych (NA -> 2.5.3 ) [CRAN]
ggplot2 (NA -> 3.5.2 ) [CRAN]
tuneR (NA -> 1.4.7 ) [CRAN]
XML (NA -> 3.99-0.18 ) [CRAN]
R[write to console]: Downloading GitHub repo pmcharrison/hrep@HEAD
abcR (NA -> ddb23ba8e...) [GitHub]
cpp11 (NA -> 0.5.2 ) [CRAN]
prettyunits (NA -> 1.2.0 ) [CRAN]
bit (NA -> 4.6.0 ) [CRAN]
progress (NA -> 1.2.3 ) [CRAN]
withr (NA -> 3.0.2 ) [CRAN]
tzdb (NA -> 0.5.0 ) [CRAN]
tidyselect (NA -> 1.2.1 ) [CRAN]
hms (NA -> 1.1.3 ) [CRAN]
crayon (NA -> 1.5.3 ) [CRAN]
bit64 (NA -> 4.6.0-1 ) [CRAN]
fastmap (NA -> 1.2.0 ) [CRAN]
vroom (NA -> 1.6.5 ) [CRAN]
clipr (NA -> 0.8.0 ) [CRAN]
cachem (NA -> 1.1.0 ) [CRAN]
backports (NA -> 1.5.0 ) [CRAN]
rbibutils (NA -> 2.3 ) [CRAN]
Rcpp (NA -> 1.0.14 ) [CRAN]
readr (NA -> 2.1.5 ) [CRAN]
data.table (NA -> 1.17.2 ) [CRAN]
memoise (NA -> 2.0.1 ) [CRAN]
purrr (NA -> 1.0.4 ) [CRAN]
checkmate (NA -> 2.3.2 ) [CRAN]
Rdpack (NA -> 2.6.4 ) [CRAN]
plyr (NA -> 1.8.9 ) [CRAN]
R[write to console]: Downloading GitHub repo pmcharrison/abcR@HEAD
withr (NA -> 3.0.2 ) [CRAN]
tidyselect (NA -> 1.2.1 ) [CRAN]
generics (NA -> 0.1.4 ) [CRAN]
fastmap (NA -> 1.2.0 ) [CRAN]
digest (NA -> 0.6.37 ) [CRAN]
base64enc (NA -> 0.1-3 ) [CRAN]
backports (NA -> 1.5.0 ) [CRAN]
purrr (NA -> 1.0.4 ) [CRAN]
dplyr (NA -> 1.1.4 ) [CRAN]
htmltools (NA -> 0.5.8.1) [CRAN]
checkmate (NA -> 2.3.2 ) [CRAN]
R[write to console]: Installing 11 packages: withr, tidyselect, generics, fastmap, digest, base64enc, backports, purrr, dplyr, htmltools, checkmate
R[write to console]: Running `R CMD build`...
* checking for file ‘/tmp/RtmpP5JrHC/remotes17e972e9fe94/pmcharrison-abcR-ddb23ba/DESCRIPTION’ ... OK
* preparing ‘abcR’:
* checking DESCRIPTION meta-information ... OK
* checking for LF line-endings in source and make files and shell scripts
* checking for empty or unneeded directories
Omitted ‘LazyData’ from DESCRIPTION
* building ‘abcR_0.0.0.9007.tar.gz’
R[write to console]: Installing package into ‘/usr/local/lib/R/site-library’
(as ‘lib’ is unspecified)
R[write to console]: Installing 24 packages: cpp11, prettyunits, bit, progress, withr, tzdb, tidyselect, hms, crayon, bit64, fastmap, vroom, clipr, cachem, backports, rbibutils, Rcpp, readr, data.table, memoise, purrr, checkmate, Rdpack, plyr
R[write to console]: Skipping install of 'abcR' from a github remote, the SHA1 (ddb23ba8) has not changed since last install.
Use `force = TRUE` to force installation
R[write to console]: Running `R CMD build`...
* checking for file ‘/tmp/RtmpP5JrHC/remotes17e9791eb92f/pmcharrison-hrep-461f98d/DESCRIPTION’ ... OK
* preparing ‘hrep’:
* checking DESCRIPTION meta-information ... OK
* installing the package to process help pages
* saving partial Rd database
* checking for LF line-endings in source and make files and shell scripts
* checking for empty or unneeded directories
NB: this package now depends on R (>= 3.5.0)
WARNING: Added dependency on R >= 3.5.0 because serialized objects in
serialize/load version 3 cannot be read in older versions of R.
File(s) containing such objects:
‘hrep/inst/stability-tests/pc-chord-alphabet.rds’
‘hrep/inst/stability-tests/pc-chord-type-alphabet.rds’
‘hrep/inst/stability-tests/pc-set-alphabet.rds’
* building ‘hrep_0.16.1.tar.gz’
R[write to console]: Installing package into ‘/usr/local/lib/R/site-library’
(as ‘lib’ is unspecified)
R[write to console]: Installing 64 packages: withr, stringi, generics, cpp11, tidyselect, stringr, purrr, dplyr, timechange, Rcpp, later, fastmap, digest, base64enc, htmltools, lazyeval, jsonlite, promises, rappdirs, fs, sass, mime, memoise, cachem, tinytex, jquerylib, fontawesome, bslib, xfun, highr, evaluate, yaml, rmarkdown, knitr, lattice, viridisLite, RColorBrewer, labeling, farver, Matrix, nlme, MASS, tidyr, snakecase, lubridate, hms, crosstalk, httpuv, htmlwidgets, GPArotation, mnormt, scales, mgcv, isoband, gtable, signal, logging, assertthat, janitor, DT, psych, ggplot2, tuneR, XML
R[write to console]: Skipping install of 'hrep' from a github remote, the SHA1 (461f98d0) has not changed since last install.
Use `force = TRUE` to force installation
R[write to console]: Running `R CMD build`...
* checking for file ‘/tmp/RtmpP5JrHC/remotes17e9a00e14e/sebsilas-itembankr-612c220/DESCRIPTION’ ... OK
* preparing ‘itembankr’:
* checking DESCRIPTION meta-information ... OK
* checking for LF line-endings in source and make files and shell scripts
* checking for empty or unneeded directories
* building ‘itembankr_0.5.4.tar.gz’
R[write to console]: Installing package into ‘/usr/local/lib/R/site-library’
(as ‘lib’ is unspecified)
R[write to console]: Installing 32 packages: viridisLite, RColorBrewer, labeling, farver, lattice, Matrix, nlme, withr, stringi, tidyselect, generics, proxy, scales, mgcv, MASS, isoband, gtable, cpp11, stringr, purrr, dplyr, Rcpp, cba, ggplot2, assertthat, dtw, emdist, tictoc, yaml, logging, tidyr, purrrlyr
R[write to console]: Skipping install of 'itembankr' from a github remote, the SHA1 (612c220b) has not changed since last install.
Use `force = TRUE` to force installation
R[write to console]: Running `R CMD build`...
* checking for file ‘/tmp/RtmpP5JrHC/remotes17e94bd16cab/sebsilas-melsim-74a8b0d/DESCRIPTION’ ... OK
* preparing ‘melsim’:
* checking DESCRIPTION meta-information ... OK
* checking for LF line-endings in source and make files and shell scripts
* checking for empty or unneeded directories
* building ‘melsim_0.9.0.tar.gz’
R[write to console]: Installing package into ‘/usr/local/lib/R/site-library’
(as ‘lib’ is unspecified)
Create example melodies for comparison. We’ll start with a C major scale and create variations by altering different notes.
# Create a C major scale melody (C4 to C5) with quarter note durations
c_major_scale = Score.from_melody(
pitches=[60, 62, 64, 65, 67, 69, 71, 72], durations=1.0
)
# Create variations by altering different notes
modified_scale = Score.from_melody(
pitches=[60, 62, 64, 66, 67, 71, 72], durations=1.0 # F4->F#4
)
third_scale = Score.from_melody(
pitches=[60, 62, 64, 66, 67, 68, 71, 72], durations=1.0 # F4->F#4, A4->Ab4
)
fourth_scale = Score.from_melody(
pitches=[60, 62, 64, 66, 67, 68, 70, 72], durations=1.0 # F4->F#4, A4->Ab4, B4->Bb4
)
melodies = [c_major_scale, modified_scale, third_scale, fourth_scale]
Perform a simple similarity comparison between two melodies using Jaccard similarity.
get_similarity(c_major_scale, modified_scale, "Jaccard", "pitch")
0.6666666666666667
Now perform pairwise comparisons across all melodies using different similarity measures.
# Load melodies into R
for i, melody in enumerate(melodies):
r_load_melody(melody, f"melody_{i + 1}")
similarity_measures = ["cosine", "Simpson"]
for method in similarity_measures:
n = len(melodies)
sim_matrix = np.zeros((n, n))
melody_names = [f"melody_{i + 1}" for i in range(n)]
for i in range(n):
for j in range(i + 1, n):
similarity = r_get_similarity(
f"melody_{i + 1}", f"melody_{j + 1}", method, "pitch"
)
sim_matrix[i, j] = similarity
sim_matrix[j, i] = similarity
sim_matrix[i, i] = 1.0
sim_df = pd.DataFrame(sim_matrix, index=melody_names, columns=melody_names)
print(f"\nPairwise {method} similarities:")
print(sim_df)
2025-05-22 12:12:50.65065 INFO::Sliding on...
2025-05-22 12:12:50.681079 INFO::Sliding on...
2025-05-22 12:12:50.700585 INFO::Sliding on...
Pairwise cosine similarities:
melody_1 melody_2 melody_3 melody_4
melody_1 1.000000 0.999943 0.999972 0.999960
melody_2 0.999943 1.000000 0.999910 0.999860
melody_3 0.999972 0.999910 1.000000 0.999988
melody_4 0.999960 0.999860 0.999988 1.000000
Pairwise Simpson similarities:
melody_1 melody_2 melody_3 melody_4
melody_1 1.000000 0.857143 0.750 0.625000
melody_2 0.857143 1.000000 1.000 0.857143
melody_3 0.750000 1.000000 1.000 0.875000
melody_4 0.625000 0.857143 0.875 1.000000
Finally, explore other types of melodic similarity measures.
# Compare intervallic similarity
get_similarity(c_major_scale, modified_scale, "Euclidean", "int")
2025-05-22 12:12:50.887129 INFO::Sliding on...
0.28989794855663564
Compare IOI class similarity (expected to be 1 as IOIs are identical)
get_similarity(c_major_scale, modified_scale, "Canberra", "ioi_class")
2025-05-22 12:12:50.901491 INFO::Sliding on...
1.0
Total running time of the script: (1 minutes 42.321 seconds)