A python package for determining the proportion of in-key notes, as described in Weiss & Peretz (2022) to analyze sung improvisations. Other common methods are simpler and faster (e.g., Krumhansl-Kessler algorithm implemented in the MIDI Toolbox) but they require notes to be rounded to the nearest semitone (12 semitones/levels), which distorts intervals, and assumes that the performer is in tune. The approach here uses a more continuous probability density function (PDF, 1200 cents/levels) representing the tonal hierarchy, and makes no assumptions about tuning except for octave equivalence. Hence, a tonic can be returned to the nearest cent.
When given a set of MIDI pitches (e.g., 69.45 = A440 plus 45 cents) rounded to the nearest cent, and durations for each pitch (seconds), the package will convert the melody to pitch classes to the nearest cent (0.00-11.99) and transpose those pitches across the octave until they fit best with the PDF. This process happens twice (major, minor), and returns the best overall fit, yielding the mode. The best-fitting transposition can then be used to determine the tonic to the nearest cent. A video displaying this process is viewable on OSF.
With that information, the package then determines which notes are in- or out-of-key. However, the "chance" proportion of in-key notes changes depending on the number of notes, and to a lesser degree, on the durations of each note. Our solution was to calculate a null distribution of proportions using randomly generated pitches, which can then be used to calculate a percentile and z-score of the original melody.
As a final note, the major and minor keys were relevant to our initial study, but the package is designed so that others sets of intervals and weights can be used to generate a PDF besides the Krumhansl-Kessler ratings used here, and the function build_diatonic_PDF()
even gives the possibility to generate randomly shaped PDFs. If the scale repeats in octaves, it can be used to generate a PDF.
The package includes several tiers of functions:
Convenience:
run_PDF()
: A convenience function to build a model under some PDF distribution.PDF_major_minor()
: A convenience function that calculates the tonic using the best fit from either the major or minor settings, wrappingrun_PDF()
.PDF_null_notes()
: A convenience function that calculates a null distribution of models usingPDF_major_minor()
, but generating random pitches usinggenerate_random_pitches()
.z_score_null_notes()
: Convert the proportion of notes that are tonal in the actual performance into a z-score relative to a null distribution of proportions.
Plotting and saving:
plot_model_output()
: Plot the results ofrun_PDF()
.save_model_info()
: Save information generated byrun_PDF()
.plot_PIKN_distribution()
: Visualize proportion of in-key notes value in the null distribution.
Inner workings:
build_diatonic_PDF()
: Build a probability density function evaluated at each cent representing diatonic intervals.MIDI_to_transpose_array()
: Convert MIDI pitch values to cents and transpose across the octave in steps of 1 cent, then change to pitch classes from C=0 (resolution 1 cent).nll_tonality()
: Compute the negative log likelihood of values given a PDF of evaluated points.optimal_shift()
: Calculate the mean of the negative log likelihood array, weighting pitches optionally.min_idx_to_tonic()
: Calculate the tonic from the transposition minimum index (0-1199), where 0=C.notes_in_key_major_minor()
: Calculate the proportion of in-key notes for major or minor profiles.generate_random_pitches()
: Generate a sequence of random pitch values matching the length of another sequence.
A virtual environment with pip
and python
can be created and activated using Anaconda:
conda create -n test_pdft pip python=3.7
conda activate test_pdft
Then cd or provide the full path to the package and pip install the necessary dependencies (see setup.py
for a full list):
pip install ~/pdf_tonality
From within this virtual environment, you can now load the library in python, for example:
from pdf_tonality import pdf_tonality as pdft
Two examples are provided in the folder pdf_tonality/examples
. Example 1 provides a template for analyzing a melody, generating a null distribution, and calculating z-scores/percentiles from them. Example 2 will print to console the results from a rendition of "happy birthday" with varying amounts of pitch error generated at random. Both contain more detailed commentary alongside the code. To run them from within the virtual environment created in the installation above:
python ~/pdf_tonality/examples/example1.py
python ~/pdf_tonality/examples/example2.py