Contour
HuronContour
¶
HuronContour(
pitches: list[int], times: list[float], method: str = "amads"
)
The contour classification scheme proposed by Huron (1996) [1]
The classification scheme is also included in the FANTASTIC toolbox
of Müllensiefen (2009) [2] (as Feature 19 Huron Contour: h.contour).
Huron categorises melodies by identifying the start, mean, and end pitches and describing contour in terms of the two directions: start-mean, and mean-end.
Parameters:
-
pitches(list[int]) –Pitch values in any numeric format (e.g., MIDI numbers).
-
times(list[float]) –Onset times in any consistent, proportional scheme (e.g., seconds, quarter notes, etc.)
Raises:
-
ValueError–If the
timesandpitchesparameters are not the same length.
Examples:
>>> happy_birthday_pitches = [
... 60, 60, 62, 60, 65, 64, 60, 60, 62, 60, 67, 65,
... 60, 60, 72, 69, 65, 64, 62, 70, 69, 65, 67, 65
... ]
>>> happy_birthday_times = [
... 0, 0.75, 1, 2, 3, 4, 6, 6.75, 7, 8, 9, 10,
... 12, 12.75, 13, 14, 15, 16, 17, 18, 18.75, 19, 20, 21
... ]
>>> hc = HuronContour(
... happy_birthday_pitches,
... happy_birthday_times,
... )
>>> hc.first_pitch
60
>>> hc.mean_pitch
65
>>> hc.last_pitch
65
>>> hc.first_to_mean
5
>>> hc.mean_to_last
0
>>> hc.contour_class
'Ascending-Horizontal'
References
-
Huron, D (2006). The Melodic Arch in Western Folksongs. Computing in Musicology 10.
-
Müllensiefen, D. (2009). Fantastic: Feature ANalysis Technology Accessing STatistics (In a Corpus): Technical Report v1.5
Source code in amads/melody/contour/huron_contour.py
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | |
Functions¶
calculate_mean_attributes
¶
calculate_mean_attributes()
Calculate the mean and populate the remaining attributes.
Note that the mean pitch is rounded to the nearest integer, and that this rounding happens before calculating comparisons.
Source code in amads/melody/contour/huron_contour.py
106 107 108 109 110 111 112 113 114 115 116 117 118 119 | |
class_label
¶
class_label()
Classify a contour into Huron's categories.
This is based simply on the two directions from start to mean and mean to last. Huron proposes shorthands for some of these as follows: "Ascending-Descending" = "Convex", "Ascending-Horizontal" = None, "Ascending-Ascending": None, "Horizontal-Descending": None, "Horizontal-Horizontal": "Horizontal", "Horizontal-Ascending": None, "Descending-Descending": "Descending", "Descending-Ascending": "Concave"
Where no shorthand is provided, this method return the longhand.
Returns:
-
str–String, exactly as reported in the FANTASTIC library.
Source code in amads/melody/contour/huron_contour.py
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | |
ParsonsContour
¶
ParsonsContour(
pitches: list[int],
character_dict: Optional[dict] = None,
initial_asterisk: bool = False,
)
Implementation of the basic Parsons contour classification scheme.
Parsons categorises each step by direction only.
Nothing more, nothing less.
Author: Mark Gotham
Parameters:
-
pitches(list[int]) –A list of integers representing pitches (assumed to be MIDI numbers or equivalent, not pitch classes)
-
character_dict(Optional[dict], default:None) –A dict specifying which characters to use when mapped to a string. Must include keys for [1, 0, -1] corresponding to up, repeat, and down. The default is Parsons' own values: {1: "u", 0: "r", -1: "d"}. Other options could include
<,=,>. -
initial_asterisk(bool, default:False) –Optionally, include an initial
*for the start of the sequence (no previous interval).
Examples:
>>> happy = [60, 60, 62, 60, 65, 64, 60, 60, 62, 60, 67, 65, 60, 60, 72, 69, 65, 64, 62, 70, 69, 65, 67, 65]
>>> pc = ParsonsContour(happy)
>>> pc.interval_sequence
[None, 0, 2, -2, 5, -1, -4, 0, 2, -2, 7, -2, -5, 0, 12, -3, -4, -1, -2, 8, -1, -4, 2, -2]
>>> pc.interval_sequence_sign
[None, 0, 1, -1, 1, -1, -1, 0, 1, -1, 1, -1, -1, 0, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1]
>>> pc.as_string
'rududdrududdrudddduddud'
>>> twinkle_ints = [72, 72, 79, 79, 81, 81, 79, 77, 77, 76, 76, 74, 74, 72]
>>> pc = ParsonsContour(twinkle_ints)
>>> pc.as_string
'rururddrdrdrd'
>>> pc_no_asterisk = ParsonsContour(twinkle_ints, initial_asterisk=True)
>>> pc_no_asterisk.as_string
'*rururddrdrdrd'
>>> pc_symbols = ParsonsContour(twinkle_ints, {1: "<", 0: "=", -1: ">"})
>>> pc_symbols.as_string
'=<=<=>>=>=>=>'
References
[1] Parsons, Denys. 1975. The Directory of Tunes and Musical Themes.
Source code in amads/melody/contour/parsons_contour.py
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 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | |
Functions¶
make_string
¶
make_string()
Create a flat, string representation of the contour directions.
Source code in amads/melody/contour/parsons_contour.py
100 101 102 103 104 105 106 107 108 | |
StepContour
¶
StepContour(
pitches: list[int],
durations: list[float],
step_contour_length: int = _step_contour_length,
)
Class for calculating and analyzing the step contour of a melody.
Also related features, as implemented in the FANTASTIC toolbox of Müllensiefen (2009) [1] (as features 20–22). Exemplified in Steinbeck (1982) [2], Juhász (2000) [3], Eerola and Toiviainen (2004) [4].
A step contour is a list of MIDI pitch values, repeated proportionally to the duration (measured in tatums) of each note relative to the total melody length. This list is normalized to a user defined length, defaulting to 64 steps as used in FANTASTIC. Rests are considered as extending the duration of the previous note.
Author: David Whyatt
Examples:
>>> pitches = [60, 64, 67] # C4, E4, G4
>>> durations = [2.0, 1.0, 1.0] # First note is 2 beats, others are 1 beat
>>> sc = StepContour(pitches, durations)
>>> len(sc.contour) # Default length is 64
64
>>> pitches = [60, 62, 64, 65, 67] # C4, D4, E4, F4, G4
>>> durations = [1.0, 1.0, 1.0, 1.0, 1.0] # Notes have equal durations
>>> sc = StepContour(pitches, durations)
>>> sc.contour[:8] # First 8 values of 64-length contour
[60, 60, 60, 60, 60, 60, 60, 60]
>>> sc.global_variation # Standard deviation of contour
2.3974
>>> sc.global_direction # Correlation with ascending line
0.9746
>>> sc.local_variation # Average absolute difference between adjacent values
0.1111
Parameters:
-
pitches(list[int]) –List of pitch values in any numeric format (e.g., MIDI numbers).
-
durations(list[float]) –List of note durations measured in tatums
-
step_contour_length(int, default:_step_contour_length) –Length of the output step contour vector (default is 64)
References
- Müllensiefen, D. (2009). Fantastic: Feature ANalysis Technology Accessing STatistics (In a Corpus): Technical Report v1.5
- W. Steinbeck, Struktur und Ähnlichkeit: Methoden automatisierter Melodieanalyse. Bärenreiter, 1982.
- Juhász, Z. 2000. A model of variation in the music of a Hungarian ethnic group. Journal of New Music Research 29(2):159-172.
- Eerola, T. & Toiviainen, P. (2004). MIDI Toolbox: MATLAB Tools for Music Research. University of Jyväskylä: Kopijyvä, Jyväskylä, Finland.
Examples:
>>> sc = StepContour([60, 62], [2.0, 2.0], step_contour_length=4)
>>> sc.contour
[60, 60, 62, 62]
Source code in amads/melody/contour/step_contour.py
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | |
Attributes¶
global_variation
property
¶
global_variation: float
Calculate the global variation of the step contour by taking the standard deviation of the step contour vector.
Returns:
-
float–Float value representing the global variation of the step contour
Examples:
>>> sc = StepContour([60, 62, 64], [1.0, 1.0, 1.0])
>>> sc.global_variation
1.64
global_direction
property
¶
global_direction: float
Calculate the global direction of the step contour by taking the correlation between the step contour vector and an ascending linear function y = x.
Returns:
-
float–Float value representing the global direction of the step contour Returns 0.0 if the contour is flat
Examples:
>>> sc = StepContour([60, 62, 64], [1.0, 1.0, 1.0])
>>> sc.global_direction
0.943
>>> sc = StepContour([60, 60, 60], [1.0, 1.0, 1.0])
>>> sc.global_direction
0.0
>>> sc = StepContour([64, 62, 60], [1.0, 1.0, 1.0]) # Descending melody
>>> sc.global_direction
-0.943
local_variation
property
¶
local_variation: float
Calculate the local variation of the step contour, by taking the mean absolute difference between adjacent values.
Returns:
-
float–Float value representing the local variation of the step contour
Examples:
>>> sc = StepContour([60, 62, 64], [1.0, 1.0, 1.0])
>>> sc.local_variation
0.0634
Functions¶
_normalize_durations
¶
_normalize_durations(durations: list[float]) -> list[float]
Helper function to normalize note durations to fit within 4 bars of 4/4 time (64 tatums total by default).
Parameters:
-
durations(list[float]) –List of duration values measured in tatums
Returns:
-
list[float]–List of normalized duration values
Examples:
>>> sc = StepContour([60], [1.0])
>>> sc._normalize_durations([2.0, 2.0])
[32.0, 32.0]
Source code in amads/melody/contour/step_contour.py
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | |
_expand_to_vector
classmethod
¶
_expand_to_vector(
pitches: list[int],
normalized_durations: list[float],
step_contour_length: int,
) -> list[int]
Helper function that resamples the melody to a vector of length step_contour_length.
Parameters:
-
pitches(list[int]) –List of pitch values
-
normalized_durations(list[float]) –List of normalized duration values (should sum to step_contour_length)
-
step_contour_length(int) –Length of the output step contour vector
Returns:
-
list[int]–List of length step_contour_length containing repeated pitch values
Examples:
>>> StepContour._expand_to_vector([60, 62], [2.0, 2.0], step_contour_length=4)
[60, 60, 62, 62]
Source code in amads/melody/contour/step_contour.py
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | |
_calculate_contour
¶
_calculate_contour(
pitches: list[int], durations: list[float]
) -> list[int]
Calculate the step contour from input pitches and durations.
Examples:
>>> sc = StepContour([60, 62], [2.0, 2.0], step_contour_length=4)
>>> sc._calculate_contour([60, 62], [2.0, 2.0])
[60, 60, 62, 62]
Source code in amads/melody/contour/step_contour.py
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 | |
InterpolationContour
¶
InterpolationContour(
score: Optional[Score] = None,
onsets: Optional[Sequence[float]] = None,
pitches: Optional[Sequence[int]] = None,
method: str = "amads",
)
Class for calculating and analyzing interpolated contours of melodies.
As implemented in the FANTASTIC toolbox of Müllensiefen (2009) [1] (as features 23–27). This representation was first formalised by Steinbeck (1982) [2], and informed a variant of the present implementation in Müllensiefen & Frieler (2004) [3].
Includes a modified version of the FANTASTIC method that is better suited to short melodies than the original implementation. This 'AMADS' method defines turning points using reversals, and is the default method. All features are returned for either method.
An interpolation contour is produced by first identifying turning points in the melody, and then interpolating a linear gradient between each turning point. The resulting list of values represents the gradient of the melody at evenly spaced points in time.
Author: David Whyatt
Parameters:
-
score(Score, default:None) –If
pitchesandonsetsare provided, use them. If not and ascoreis use that. -
pitches(list[int], default:None) –Pitch values in any numeric format (e.g., MIDI numbers).
-
onsets(list[float], default:None) –Onset onsets in any consistent, proportional scheme (e.g., seconds, quarter notes, etc.)
-
method(str, default:'amads') –Method to use for contour calculation, either "fantastic" or "amads". Defaults to "amads". The FANTASTIC method is the original implementation, and identifies turning points using contour extrema via a series of rules. The AMADS method instead identifies reversals for all melody lengths, and is the default method.
Raises:
-
ValueError–If neither
onsetsandpitchesor a score parameter are provided. If theonsetsandpitchesparameters are not the same length. If method is not "fantastic" or "amads"
Examples:
>>> happy_birthday_pitches = [
... 60, 60, 62, 60, 65, 64, 60, 60, 62, 60, 67, 65,
... 60, 60, 72, 69, 65, 64, 62, 70, 69, 65, 67, 65
... ]
>>> happy_birthday_onsets = [
... 0, 0.75, 1, 2, 3, 4, 6, 6.75, 7, 8, 9, 10,
... 12, 12.75, 13, 14, 15, 16, 17, 18, 18.75, 19, 20, 21
... ]
>>> ic = InterpolationContour(
... pitches=happy_birthday_pitches,
... onsets=happy_birthday_onsets,
... method="fantastic",
... )
>>> ic.direction_changes
0.6
>>> ic.class_label
'ccbc'
>>> round(ic.mean_gradient, 6)
2.702857
>>> round(ic.gradient_std, 6)
5.65564
>>> ic.global_direction
1
References
-
Müllensiefen, D. (2009). Fantastic: Feature ANalysis Technology Accessing STatistics (In a Corpus): Technical Report v1.5
-
W. Steinbeck, Struktur und Ähnlichkeit: Methoden automatisierter Melodieanalyse. Bärenreiter, 1982.
-
Müllensiefen, D. & Frieler, K. (2004). Cognitive Adequacy in the Measurement of Melodic Similarity: Algorithmic vs. Human Judgments
Source code in amads/melody/contour/interpolation_contour.py
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | |
Attributes¶
global_direction
property
¶
global_direction: int
Calculate the global direction of the interpolation contour.
Takes the sign of the sum of all contour values. Can be invoked for either FANTASTIC or AMADS method.
Returns:
-
int–1 if sum is positive, 0 if sum is zero, -1 if sum is negative
Examples:
Flat overall contour direction (returns the same using FANTASTIC method)
>>> ic = InterpolationContour(pitches=[60, 62, 64, 62, 60], onsets=[0, 1, 2, 3, 4])
>>> ic.global_direction
0
Upwards contour direction (returns the same using FANTASTIC method)
>>> ic = InterpolationContour(pitches=[60, 62, 64, 65, 67], onsets=[0, 1, 2, 3, 4])
>>> ic.global_direction
1
Downwards contour direction (returns the same using FANTASTIC method)
>>> ic = InterpolationContour(pitches=[67, 65, 67, 62, 60], onsets=[0, 1, 2, 3, 4])
>>> ic.global_direction
-1
mean_gradient
property
¶
mean_gradient: float
Calculate the absolute mean gradient of the interpolation contour. Can be invoked for either FANTASTIC or AMADS method.
Returns:
-
float–Mean of the absolute gradient values
Examples:
Steps of 2 semitones per second
>>> ic = InterpolationContour(pitches=[60, 62, 64, 62, 60], onsets=[0, 1, 2, 3, 4])
>>> ic.mean_gradient
2.0
FANTASTIC method returns 0.0 for this example
>>> ic = InterpolationContour(pitches=[60, 62, 64, 62, 60], onsets=[0, 1, 2, 3, 4], method="fantastic")
>>> ic.mean_gradient
0.0
gradient_std
property
¶
gradient_std: float
Calculate the standard deviation of the interpolation contour gradients.
Can be invoked for either FANTASTIC or AMADS method.
Returns:
-
float–Standard deviation of the gradient values (by default, using Bessel's correction)
Examples:
>>> ic = InterpolationContour(pitches=[60, 62, 64, 62, 60], onsets=[0, 1, 2, 3, 4])
>>> round(ic.gradient_std, 7)
2.0254787
FANTASTIC method returns 0.0 for this example
>>> ic = InterpolationContour(pitches=[60, 62, 64, 62, 60], onsets=[0, 1, 2, 3, 4], method="fantastic")
>>> ic.gradient_std
0.0
direction_changes
property
¶
direction_changes: float
Calculate the proportion of interpolated gradient values that consistute a change in direction. For instance, a gradient value of -0.5 to 0.25 is a change in direction. Can be invoked for either FANTASTIC or AMADS method.
Returns:
-
float–Ratio of the number of changes in contour direction relative to the number of different interpolated gradient values
Examples:
>>> ic = InterpolationContour(pitches=[60, 62, 64, 62, 60], onsets=[0, 1, 2, 3, 4])
>>> ic.direction_changes
1.0
FANTASTIC method returns 0.0 for this example
>>> ic = InterpolationContour(pitches=[60, 62, 64, 62, 60], onsets=[0, 1, 2, 3, 4], method="fantastic")
>>> ic.direction_changes
0.0
class_label
property
¶
class_label: str
Classify an interpolation contour into gradient categories.
Can be invoked for either FANTASTIC or AMADS method.
The contour is sampled at 4 equally spaced points and each gradient is normalized to units of pitch change per second (expressed in units of semitones per 0.25 seconds.) The result is then classified into one of 5 categories:
- 'a': Strong downward (-2) - normalized gradient <= -1.45
- 'b': Downward (-1) - normalized gradient between -1.45 and -0.45
- 'c': Flat (0) - normalized gradient between -0.45 and 0.45
- 'd': Upward (1) - normalized gradient between 0.45 and 1.45
- 'e': Strong upward (2) - normalized gradient >= 1.45
Returns:
-
str–String of length 4 containing letters a-e representing the gradient categories at 4 equally spaced points in the contour
Examples:
Upwards, then downwards contour
>>> ic = InterpolationContour(pitches=[60, 62, 64, 62, 60], onsets=[0, 1, 2, 3, 4])
>>> ic.class_label
'ddbb'
FANTASTIC method returns 'cccc' for this example, as though the contour is flat
>>> ic = InterpolationContour(pitches=[60, 62, 64, 62, 60], onsets=[0, 1, 2, 3, 4], method="fantastic")
>>> ic.class_label
'cccc'
Functions¶
get_onsets_and_pitches
¶
get_onsets_and_pitches(score: Score) -> tuple[list[float], list[int]]
Extract onset times and pitches from a Score object.
Parameters:
-
score(Score) –The Score object to extract data from
Returns:
-
tuple[list[float], list[int]]–A tuple containing (onset_times, pitch_values)
Source code in amads/melody/contour/interpolation_contour.py
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | |
_is_turning_point_fantastic
staticmethod
¶
_is_turning_point_fantastic(pitches: list[int], i: int) -> bool
Helper method to determine if a point is a turning point in FANTASTIC method.
Source code in amads/melody/contour/interpolation_contour.py
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 | |
calculate_interpolation_contour
staticmethod
¶
calculate_interpolation_contour(
pitches: list[int], onsets: list[float], method: str = "amads"
) -> list[float]
Calculate the interpolation contour representation of a melody [1].
Returns:
-
list[float]–Array containing the interpolation contour representation
Source code in amads/melody/contour/interpolation_contour.py
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | |
_calculate_fantastic_contour
staticmethod
¶
_calculate_fantastic_contour(
pitches: list[int], onsets: list[float]
) -> list[float]
Calculate the interpolation contour using the FANTASTIC method.
Utilises the helper function _is_turning_point_fantastic to identify turning points.
Source code in amads/melody/contour/interpolation_contour.py
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 | |
_remove_repeated_notes
staticmethod
¶
_remove_repeated_notes(
pitches: list[int], onsets: list[float]
) -> tuple[list[int], list[float]]
Helper function to remove repeated notes, keeping only the middle occurrence.
This is used for the AMADS method to produce the interpolated gradient values at the middle of a sequence of repeated notes, should there be a reversal between the repeated notes.
Source code in amads/melody/contour/interpolation_contour.py
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 | |
_calculate_amads_contour
staticmethod
¶
_calculate_amads_contour(
pitches: list[int], onsets: list[float]
) -> list[float]
Calculate the interpolation contour using the AMADS method.
Utilises the helper function _remove_repeated_notes.
Source code in amads/melody/contour/interpolation_contour.py
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 | |
plot
¶
plot(ax=None)
Plot the melody notes and the interpolation contour gradients.
Displays two subplots (if ax is None):
- Top: pitch values at their onset times as a scatter/step plot, with all original melody notes connected by lines.
- Bottom: the interpolation contour — the piecewise-constant
gradient values produced by
calculate_interpolation_contour, plotted as a step function over normalised time (0–1).
Parameters:
-
ax(array-like of matplotlib.axes.Axes, default:None) –A pair of Axes
[ax_melody, ax_contour]to draw on. IfNone, a new figure with two vertically stacked subplots is created withfigsize=(8, 5).
Returns:
-
tuple[Axes, Axes]–(ax_melody, ax_contour)— the two axes, suitable for further customisation or embedding in a larger figure.
Raises:
-
ImportError–If matplotlib is not installed.
Examples:
>>> ic = InterpolationContour(
... pitches=[60, 62, 64, 62, 60],
... onsets=[0, 1, 2, 3, 4],
... )
>>> ax_melody, ax_contour = ic.plot()
Source code in amads/melody/contour/interpolation_contour.py
521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 | |
PolynomialContour
¶
PolynomialContour(
score: Optional[Score] = None,
onsets: Optional[Sequence[float]] = None,
pitches: Optional[Sequence[int]] = None,
)
A class for computing polynomial contour.
As described in the FANTASTIC toolbox [1]. This approach is discussed in detail in Müllensiefen and Wiggins (2011) [2].
Polynomial Contour is constructed in 3 simple steps:
-
First, the onsets are first centred around the origin of the time axis, making a symmetry between the first onset and the last.
-
Then, a polynomial model is fit, seeking to predict the pitch values from a least squares regression of the centred onset times.
-
Finally, the best model is selected using Bayes' Information Criterion, stepwise and in a backwards direction.
The final output is the coefficients of the first three non-constant terms, i.e. [c1, c2, c3] from p = c0 + c1t + c2t^2 + c3t^3.
Author: David Whyatt
Attributes:
-
coefficients(list[float]) –The polynomial contour coefficients. Returns the first 3 non-constant coefficients [c1, c2, c3] of the final selected polynomial contour model. The constant term is not included as per the FANTASTIC toolbox specification.
References
- Müllensiefen, D. (2009). Fantastic: Feature ANalysis Technology Accessing STatistics (In a Corpus): Technical Report v1.5
- Müllensiefen, D., & Wiggins, G.A. (2011). Polynomial functions as a representation of melodic phrase contour.
Examples:
Single note melodies return [0.0, 0.0, 0.0] since there is no contour:
>>> pc = PolynomialContour(onsets=[1.0], pitches=[60])
>>> pc.coefficients
[0.0, 0.0, 0.0]
Real melody examples:
>>> test_pitches = [62, 64, 65, 67, 64, 60, 62]
>>> test_case = Score.from_melody(pitches=test_pitches, durations=[1.0, 1.0, 1.0, 1.0, 2.0, 1.0, 1.0]) # duration
>>> test_case_pc = PolynomialContour(test_case)
>>> test_case_pc.onsets
[0.0, 1.0, 2.0, 3.0, 4.0, 6.0, 7.0]
>>> [round(x, 7) for x in test_case_pc.coefficients] # Verified against FANTASTIC toolbox
[-1.5014826, -0.2661533, 0.122057]
The same result if this data comes from a score or directly.
>>> test_onsets = [0.0, 1.0, 2.0, 3.0, 4.0, 6.0, 7.0]
>>> test_case_pc.onsets == test_onsets
True
>>> test_pitches = [62, 64, 65, 67, 64, 60, 62]
>>> test_2 = PolynomialContour(onsets=test_onsets, pitches=test_pitches)
>>> test_2.onsets == test_onsets
True
>>> [round(x, 7) for x in test_2.coefficients] # Verified against FANTASTIC toolbox
[-1.5014826, -0.2661533, 0.122057]
>>> twinkle = Score.from_melody([60, 60, 67, 67, 69, 69, 67, 65, 65, 64, 64, 62, 62, 60],
... [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 2.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 2.0])
>>> pc3 = PolynomialContour(twinkle)
>>> [round(x, 7) for x in pc3.coefficients] # Verified against FANTASTIC toolbox
[-0.9535562, 0.2120971, 0.0]
Source code in amads/melody/contour/polynomial_contour.py
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | |
Functions¶
calculate_coefficients
¶
calculate_coefficients(
onsets: list[float], pitches: list[int]
) -> list[float]
Calculate polynomial contour coefficients for the melody. Main method for the PolynomialContour class.
Parameters:
-
onsets(list[float]) –List of onset times from the score
-
pitches(list[int]) –List of pitch values from the score
Returns:
-
list[float]–First 3 coefficients [c1, c2, c3] of the polynomial contour, with zeros padded if needed. For melodies with fewer than 2 notes, returns [0.0, 0.0, 0.0] since there is no meaningful contour to analyze.
Source code in amads/melody/contour/polynomial_contour.py
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | |
get_onsets_and_pitches
¶
get_onsets_and_pitches(score: Score) -> tuple[list[float], list[int]]
Extract onset times and pitches from a Score object.
Parameters:
-
score(Score) –The Score object to extract data from
Returns:
-
tuple[list[float], list[int]]–A tuple containing (onset_times, pitch_values)
Source code in amads/melody/contour/polynomial_contour.py
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | |
center_onset_times
¶
center_onset_times(onsets: list[float]) -> list[float]
Center onset times around their midpoint. This produces a symmetric axis of onset times, which is used later to fit the polynomial.
For single-note melodies, returns [0.0] since there is no meaningful contour to analyze.
Parameters:
-
onsets(list[float]) –List of onset times to center
Returns:
-
list[float]–List of centered onset times. Returns [0.0] for single-note melodies.
Source code in amads/melody/contour/polynomial_contour.py
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 | |
fit_polynomial
¶
fit_polynomial(
centered_onsets: list[float], pitches: list[int], m: int
) -> list[float]
Fit a polynomial model to the melody contour using least squares regression.
The polynomial has the form: p = c0 + c1t + c2t^2 + ... + cm*t^m
where m = n // 2 (n = number of notes) and t are centered onset times.
Parameters:
-
centered_onsets(list[float]) –List of centered onset times
-
pitches(list[int]) –List of pitch values
-
m(int) –Maximum polynomial degree to use
Returns:
-
list[float]–The coefficients [c0, c1, ..., cm] of the fitted polynomial
Source code in amads/melody/contour/polynomial_contour.py
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 | |
select_model
¶
select_model(
centered_onsets: list[float], pitches: list[int], m: int
) -> list[float]
Select the best polynomial model using BIC in an exhaustive search over all subsets of polynomial terms.
Tests all 2^(m+1) - 1 combinations of polynomial terms and selects the one with the best (lowest) BIC. The max degree is m = n // 2.
Note: the search space grows as O(2^m). This is fine for shot melodies (up to c.30 notes, m <= 15). Longer melodies will be slow and need a review of this method for performance.
Parameters:
-
centered_onsets(list[float]) –List of centered onset times
-
pitches(list[int]) –List of pitch values
-
m(int) –Maximum polynomial degree to consider
Returns:
-
list[float]–The coefficients [c1, c2, c3] of the selected polynomial model, padded with zeros if the selected degree is less than 3.
Source code in amads/melody/contour/polynomial_contour.py
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 | |
_calculate_bic
¶
_calculate_bic(coeffs: ndarray, x: ndarray, y: ndarray) -> float
Calculate BIC for a set of coefficients.
Emulates the FANTASTIC toolbox implementation, which uses stepAIC from the MASS package in R. Only non-zero coefficients are counted as parameters.
If the max value is 0, then a small epsilon is added to RSS. We do this before taking the log to guard against the case of a perfect fit (RSS = 0 → log(0) = -inf).
Parameters:
-
coeffs(ndarray) –Coefficient array (length must match x.shape[1])
-
x(ndarray) –Predictor matrix
-
y(ndarray) –Response vector
Returns:
-
float–BIC value
Source code in amads/melody/contour/polynomial_contour.py
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 | |
plot
¶
plot(ax=None)
Plot the melody contour and the fitted polynomial curve.
Displays pitch values at their centered onset times (scatter) with the selected polynomial fit overlaid (line). The y-axis is labelled with note names derived from the MIDI pitch numbers.
Parameters:
-
ax(Axes, default:None) –Axes to draw on. If None, a new figure and axes are created with
figsize=(8, 4).
Returns:
-
Axes–The axes containing the plot, suitable for further customisation or embedding in a larger figure.
Raises:
-
ImportError–If matplotlib is not installed.
Source code in amads/melody/contour/polynomial_contour.py
350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 | |
fantastic_pitch_features
¶
fantastic_pitch_features(score: Score) -> Dict
Extract pitch features from a melody.
Author: David Whyatt
Parameters:
-
score(Score) –The score to extract pitch features from.
Returns:
-
Dict–A dictionary of pitch features. Dictionary keys: - pitch_range: The range of pitches in the melody. - pitch_std: The standard deviation of the pitches in the melody. - pitch_entropy: A variant of the Shannon entropy of the pitches in the melody.
Source code in amads/melody/fantastic.py
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 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | |
fantastic_pitch_interval_features
¶
fantastic_pitch_interval_features(score: Score) -> Dict
Extract pitch interval features from a melody.
Author: David Whyatt
Parameters:
-
score(Score) –The score to extract pitch interval features from.
Returns:
-
Dict–A dictionary of pitch interval features. Dictionary keys: - absolute_interval_range: The range of absolute pitch intervals in the melody. - mean_absolute_interval: The mean of the absolute pitch intervals in the melody. - std_absolute_interval: The standard deviation of the absolute pitch intervals in the melody. - modal_interval: The modal absolute pitch interval in the melody. - interval_entropy: A variant of the Shannon entropy of the absolute pitch intervals in the melody.
Source code in amads/melody/fantastic.py
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | |
fantastic_duration_features
¶
fantastic_duration_features(score: Score) -> Dict
Extract duration features from a melody.
Author: David Whyatt
Parameters:
-
score(Score) –The score to extract duration features from.
Returns:
-
Dict–A dictionary of duration features. Dictionary keys:
Source code in amads/melody/fantastic.py
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | |
fantastic_global_features
¶
fantastic_global_features(score: Score) -> Dict
Extract global extension features from a melody.
Author: David Whyatt
Parameters:
-
score(Score) –The score to extract global extension features from.
Returns:
-
Dict–A dictionary of global extension features. Dictionary keys:
Source code in amads/melody/fantastic.py
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | |
fantastic_step_contour_features
¶
fantastic_step_contour_features(score: Score) -> Dict
Extract step contour features from a melody.
Author: David Whyatt
Parameters:
-
score(Score) –The score to extract step contour features from.
Returns:
-
Dict–A dictionary of step contour features. Dictionary keys: - global_variation: The global variation of the step contour. - global_direction: The global direction of the step contour. - local_variation: The local variation of the step contour.
Source code in amads/melody/fantastic.py
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 | |
fantastic_interpolation_contour_features
¶
fantastic_interpolation_contour_features(score: Score) -> Dict
Extract interpolation contour features from a melody.
Author: David Whyatt
Parameters:
-
score(Score) –The score to extract interpolation contour features from.
Returns:
-
Dict–A dictionary of interpolation contour features. Dictionary keys: - global_direction: The global direction of the interpolation contour. - mean_gradient: The mean gradient of the interpolation contour. - gradient_std: The standard deviation of the gradient of the interpolation contour. - direction_changes: The number of direction changes in the interpolation contour. - class_label: The class label of the interpolation contour.
Source code in amads/melody/fantastic.py
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 | |
fantastic_parsons_contour_features
¶
fantastic_parsons_contour_features(
score: Score,
character_dict: Dict = None,
initial_asterisk: bool = False,
) -> Dict
Extract Parsons contour features from a melody.
Author: David Whyatt
Parameters:
-
score(Score) –The score to extract Parsons contour features from.
Returns:
-
Dict–A dictionary of Parsons contour features. Dictionary keys: - interval_sequence: The interval sequence of the melody. - interval_sequence_sign: A representation of the direction of the interval sequence using -1, 0, and 1 to represent down, repeat, and up intervals respectively. - as_string: The Parsons contour as a string, using the characters u, r, and d to represent up, repeat, and down intervals respectively.
Source code in amads/melody/fantastic.py
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 | |
fantastic_polynomial_contour_features
¶
fantastic_polynomial_contour_features(score: Score) -> Dict
Extract polynomial contour features from a melody.
Author: David Whyatt
Parameters:
-
score(Score) –The score to extract polynomial contour features from.
Returns:
-
Dict–A dictionary of polynomial contour coefficients. Dictionary keys: - coefficients: The coefficients of the polynomial contour.
Source code in amads/melody/fantastic.py
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 | |
fantastic_huron_contour_features
¶
fantastic_huron_contour_features(score: Score) -> Dict
Extract Huron contour features from a melody.
Author: David Whyatt
Parameters:
-
score(Score) –The score to extract Huron contour features from.
Returns:
-
Dict–A dictionary of Huron contour features. Dictionary keys: - first_pitch: The first pitch of the melody. - mean_pitch: The mean pitch of the melody. - last_pitch: The last pitch of the melody. - first_to_mean: The difference between the first and mean pitch. - mean_to_last: The difference between the mean and last pitch. - contour_class: The class of the Huron contour.
Source code in amads/melody/fantastic.py
308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 | |
fantastic_count_mtypes
¶
fantastic_count_mtypes(
score: Score, segment: bool, phrase_gap: float, units: str
) -> NGramCounter
Count M-Types in a melody.
Author: David Whyatt
Parameters:
-
score(Score) –The score to count M-Types in.
-
segment(bool) –Whether to segment the melody into phrases.
-
phrase_gap(float) –The minimum IOI gap to consider a new phrase.
-
units(str) –The units of the phrase gap, either "seconds" or "quarters".
Returns:
-
NGramCounter–An NGramCounter object containing the counts of M-Types. This allows for the computation of the complexity measures, either by accessing the properties of the NGramCounter object or by using the
fantastic_mtype_summary_featuresfunction.
Source code in amads/melody/fantastic.py
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 | |
fantastic_mtype_summary_features
¶
fantastic_mtype_summary_features(
score: Score, segment: bool, phrase_gap: float, units: str
) -> Dict
Count M-Types in a melody and compute summary features.
This function provides an easy way to get all the complexity measures at once.
Author: David Whyatt
Parameters:
-
score(Score) –The score to count M-Types in.
-
segment(bool) –Whether to segment the melody into phrases.
-
phrase_gap(float) –The minimum IOI gap to consider a new phrase.
-
units(str) –The units of the phrase gap, either "seconds" or "quarters".
Returns:
-
Dict–A dictionary of summary features. Dictionary keys:
- mean_entropy: The mean entropy of the M-Types. - mean_productivity: The mean productivity of the M-Types. - yules_k: The mean Yules K statistic. - simpsons_d: The mean Simpson's D statistic. - sichels_s: The mean Sichels S statistic. - honores_h: The mean Honores H statistic.
Source code in amads/melody/fantastic.py
391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 | |