Melodic similarity

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)

Gallery generated by Sphinx-Gallery