Skip to content

Interval Distributions

interval_direction_distribution_1

interval_direction_distribution_1(
    score: Score,
    name: str = "Interval Direction Distribution",
    weighted: bool = True,
    miditoolbox_compatible: bool = True,
) -> Distribution

Returns the proportion of upward intervals for each interval size

Currently, intervals greater than an octave will be ignored.

Parameters:

  • score (Score) –

    The music Score object to analyze

  • name (str, default: 'Interval Direction Distribution' ) –

    A name for the resulting distribution (title in distribution plot)

  • weighted (bool, default: True ) –

    If True, the interval distribution is weighted by note durations in seconds that are modified according to Parncutt's durational accent model (1994), by default True.

  • miditoolbox_compatible (bool, default: True ) –

    Invokes interval_distribution_1 using miditoolbox_compatible=True, which performs normalization slightly differently (see interval_distribution_1. Default is False, which simply skips division when the total count is zero (this also returns a zero matrix when the count is zero).

Returns:

  • Distribution

    A 12-element distribution representing the proportion of upward intervals for each interval size. The components are spaced at semitone distances with the first component representing a minor second (not unison) and the last component the octave. If the score is empty, the function returns a list with all elements set to zero.

Source code in amads/pitch/ivdirdist1.py
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
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
def interval_direction_distribution_1(
    score: Score,
    name: str = "Interval Direction Distribution",
    weighted: bool = True,
    miditoolbox_compatible: bool = True,
) -> Distribution:
    """
    Returns the proportion of upward intervals for each interval size

    Currently, intervals greater than an octave will be ignored.

    Parameters
    ----------
    score : Score
        The music Score object to analyze
    name : str
        A name for the resulting distribution (title in distribution plot)
    weighted : bool, optional
        If True, the interval distribution is weighted by note durations
        in seconds that are modified according to Parncutt's durational
        accent model (1994), by default True.
    miditoolbox_compatible : bool
        Invokes interval_distribution_1 using miditoolbox_compatible=True,
        which performs normalization slightly differently (see
        [interval_distribution_1]
        [amads.pitch.ivdist1.interval_distribution_1].
        Default is False, which simply skips division when the total
        count is zero (this also returns a zero matrix when the count
        is zero).

    Returns
    -------
    Distribution
        A 12-element distribution representing the proportion of
        upward intervals for each interval size. The components
        are spaced at semitone distances with the first component
        representing a minor second (not unison) and the last
        component the octave. If the score is empty, the function
        returns a list with all elements set to zero.
    """

    id = interval_distribution_1(score, name, weighted, miditoolbox_compatible)
    id = id.data  # we only need the data from the distribution
    idd = [0.0] * 12

    for i in range(12):
        # id[i + 13] is the upward interval
        # id[11 - i] is the downward interval
        if (id[i + 13] + id[11 - i]) != 0:
            idd[i] = id[i + 13] / (id[i + 13] + id[11 - i])
        else:
            idd[i] = 0

    x_categories = [str(i) for i in range(1, 13)]
    return Distribution(
        name,
        idd,
        "interval_direction",
        [12],
        x_categories,  # type: ignore
        "Interval Size",
        None,
        "Proportion",
    )

interval_distribution_1

interval_distribution_1(
    score: Score,
    name: str = "Interval Distribution",
    weighted: bool = True,
    miditoolbox_compatible: bool = True,
) -> Distribution

Returns the interval distribution of a musical score.

Currently, intervals greater than an octave will be ignored.

Parameters:

  • score (Score) –

    The musical score to analyze

  • name (str, default: 'Interval Distribution' ) –

    A name for the distribution and plot title.

  • weighted (bool, default: True ) –

    If True, the interval distribution is weighted by note durations in seconds that are modified according to Parncutt's durational accent model (1994), by default True.

  • miditoolbox_compatible (bool, default: True ) –

    Matlab MIDI Toolbox avoids zero division by dividing counts by the total count plus (1e-12 times the number of bins). True enables this behavior. Default is False, which simply skips division when the total count is zero (this also returns a zero matrix when the count is zero).

Returns:

  • Distribution

    A 25-bin distribution representing the probabilities of each pitch interval. The bins are spaced at semitone distances with the first bin representing the downward octave and the last bin representing the upward octave. If the score is empty, the function returns a list with all elements set to zero.

Raises:

  • ValueError

    If the score is not monophonic (e.g. contains chords)

Source code in amads/pitch/ivdist1.py
 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
 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
def interval_distribution_1(
    score: Score,
    name: str = "Interval Distribution",
    weighted: bool = True,
    miditoolbox_compatible: bool = True,
) -> Distribution:
    """
    Returns the interval distribution of a musical score.

    Currently, intervals greater than an octave will be ignored.

    Parameters
    ----------
    score : Score
        The musical score to analyze
    name : str
        A name for the distribution and plot title.
    weighted : bool, optional
        If True, the interval distribution is weighted by note durations
        in seconds that are modified according to Parncutt's durational
        accent model (1994), by default True.
    miditoolbox_compatible : bool
        Matlab MIDI Toolbox avoids zero division by dividing counts
        by the total count plus (1e-12 times the number of bins).
        True enables this behavior. Default is False, which simply skips
        division when the total count is zero (this also returns a
        zero matrix when the count is zero).

    Returns
    -------
    Distribution
        A 25-bin distribution representing the probabilities of each pitch
        interval. The bins are spaced at semitone distances with the first
        bin representing the downward octave and the last bin representing
        the upward octave. If the score is empty, the function returns a
        list with all elements set to zero.

    Raises
    ------
    ValueError
        If the score is not monophonic (e.g. contains chords)
    """
    if not score.ismonophonic():
        raise ValueError("Error: Score must be monophonic")

    score = cast(Score, score.merge_tied_notes())
    if weighted:
        score.convert_to_seconds()

    bin_centers = [float(i - 12) for i in range(25)]  # 25 bins from -12 to +12
    bin_boundaries = [i - 12 - 0.5 for i in range(26)]  # boundaries
    x_categories = [str(c) for c in bin_centers]
    h = Histogram1D(bin_centers, bin_boundaries, "linear", True)

    for p in score.find_all(Part):
        part: Part = cast(Part, p)
        prev_pitch = None
        prev_dur = None
        for n in part.find_all(Note):
            note: Note = cast(Note, n)
            if prev_pitch is not None:
                iv = round(note.key_num - prev_pitch)
                if miditoolbox_compatible:
                    iv = (abs(iv) % 12) * ((iv > 0) - (iv < 0))
                # otherwise, diff may be ignored by h.add_point
                if weighted:
                    dur = duraccent(note)
                    # prev_dur cannot be None here since prev_pitch is not None
                    h.add_point(iv, prev_dur + dur)  # type: ignore
                    prev_dur = dur
                else:
                    h.add_point(iv, 1.0)
            prev_pitch = note.key_num
            if weighted and prev_dur is None:
                prev_dur = duraccent(note)

    if miditoolbox_compatible:  # miditoolbox "normalization"
        total = sum(h.bins) + len(h.bins) * 1e-12
        h.bins = [b / total for b in h.bins]
    else:  # normalize normally
        h.normalize()

    return Distribution(
        name,
        h.bins,
        "interval",
        [len(h.bins)],
        x_categories,  # type: ignore
        "Interval (semitones)",
        None,
        "Proportion",
    )

interval_distribution_2

interval_distribution_2(
    score: Score,
    name: str = "Interval Transition Distribution",
    weighted: bool = True,
    miditoolbox_compatible: bool = True,
) -> Distribution

Returns the 2nd-order interval distribution of a musical score.

Currently, intervals greater than an octave will be ignored.

Parameters:

  • score (Score) –

    The musical score to analyze

  • name (str, default: 'Interval Transition Distribution' ) –

    A name for the distribution and plot title.

  • weighted (bool, default: True ) –

    If True, the interval distribution is weighted by note durations in seconds that are modified according to Parncutt's durational accent model (1994), by default True.

  • miditoolbox_compatible (bool, default: True ) –

    miditoolbox_compatible introduces four changes to emulate ivdist2 in Midi Toolbox: (1) avoid zero division by dividing counts by (total count plus (1e-12 time the number of bins), as opposed to simply skipping division when all bins are zero, (2) assume octave (but not direction) equivalence, so the intervals +1 and +13 update the same bin (as opposed to ignoring intervals larger than an octave), (3) a zero interval (unison) is inserted at the beginning of the sequence, (4) the weight is the sum of the modified durations of the second interval (as opposed to taking the sum of the modified durations of all three notes).

Returns:

  • Distribution

    A 25x25 distribution where where (i,j) represents the normalized probabilities of transitioning from interval i to interval j. The bins are spaced at semitone distances with the first bin representing the downward octave and the last bin representing the upward octave. If the score is empty, the function returns a list with all elements set to zero.

Raises:

  • ValueError

    If the score is not monophonic (e.g. contains chords)

Source code in amads/pitch/ivdist2.py
 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
 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
def interval_distribution_2(
    score: Score,
    name: str = "Interval Transition Distribution",
    weighted: bool = True,
    miditoolbox_compatible: bool = True,
) -> Distribution:
    """
    Returns the 2nd-order interval distribution of a musical score.

    Currently, intervals greater than an octave will be ignored.

    Parameters
    ----------
    score : Score
        The musical score to analyze
    name : str
        A name for the distribution and plot title.
    weighted : bool, optional
        If True, the interval distribution is weighted by note durations
        in seconds that are modified according to Parncutt's durational
        accent model (1994), by default True.
    miditoolbox_compatible : bool
        miditoolbox_compatible introduces four changes to emulate `ivdist2`
        in Midi Toolbox: (1) avoid zero division by dividing counts
        by (total count plus (1e-12 time the number of bins), as
        opposed to simply skipping division when all bins are zero,
        (2) assume octave (but not direction) equivalence, so the
        intervals +1 and +13 update the same bin (as opposed to ignoring
        intervals larger than an octave), (3) a zero interval (unison) is
        inserted at the beginning of the sequence, (4) the weight is the
        sum of the modified durations of the second interval (as opposed to
        taking the sum of the modified durations of all three notes).

    Returns
    -------
    Distribution
        A 25x25 distribution where where (i,j) represents the normalized
        probabilities of transitioning from interval i to interval j.
        The bins are spaced at semitone distances with the first bin
        representing the downward octave and the last bin representing
        the upward octave. If the score is empty, the function returns
        a list with all elements set to zero.

    Raises
    ------
    ValueError
        If the score is not monophonic (e.g. contains chords)
    """
    if not score.ismonophonic():
        raise ValueError("Error: Score must be monophonic")

    score = cast(Score, score.merge_tied_notes())
    if weighted:
        score.convert_to_seconds()  # need seconds for duraccent function

    bin_centers = [float(i - 12) for i in range(25)]  # 25 bins from -12 to +12
    bin_boundaries = [i - 12 - 0.5 for i in range(26)]  # boundaries
    x_categories = [str(c) for c in bin_centers]
    y_categories = x_categories
    h = Histogram2D(bin_centers, bin_boundaries, "linear", True)
    for p in score.find_all(Part):
        part: Part = cast(Part, p)
        dur = 0.0  # (this value is never used)
        prev_iv = 0 if miditoolbox_compatible else None  # previous interval
        prev_pitch = None
        prev_dur = 0
        prev_prev_dur = 0
        prev_bin = None
        for n in part.find_all(Note):
            note: Note = cast(Note, n)
            if weighted:
                dur = duraccent(note)
            if prev_pitch is not None:
                iv = round(note.key_num - prev_pitch)
                if miditoolbox_compatible:
                    iv = (abs(iv) % 12) * ((iv > 0) - (iv < 0))
                if weighted:
                    dur = duraccent(note)
                    w = prev_dur + dur
                    if not miditoolbox_compatible:
                        w += prev_prev_dur
                    prev_bin = h.add_point_2d(prev_iv, iv, w, prev_bin)
                    prev_prev_dur = prev_dur
                else:
                    prev_bin = h.add_point_2d(prev_iv, iv, 1.0, prev_bin)
                prev_iv = None if prev_bin is None else iv
            prev_pitch = note.key_num
            prev_dur = dur
    if miditoolbox_compatible:
        total = sum(sum(bin) for bin in h.bins) + (
            len(h.bins) * len(h.bins) * 1e-12
        )
        h.bins = [[c / total for c in row] for row in h.bins]
    else:  # normalize normally
        h.normalize()

    return Distribution(
        name,
        h.bins,
        "interval_transition",
        [25, 25],
        x_categories,  # type: ignore
        "Interval (from)",
        y_categories,  # type: ignore
        "Interval (to)",
    )

interval_size_distribution_1

interval_size_distribution_1(
    score: Score,
    name: str = "Interval Size Distribution",
    weighted: bool = True,
    miditoolbox_compatible: bool = True,
) -> Distribution

Returns the interval size distribution of a musical score.

Intervals greater than one octave are ignored.

Parameters:

  • score (Score) –

    The musical score to analyze

  • name (str, default: 'Interval Size Distribution' ) –

    A name for the distribution and plot title.

  • weighted (bool, default: True ) –

    If True, the interval distribution is weighted by note durations in seconds that are modified according to Parncutt's durational accent model (1994), by default True.

  • miditoolbox_compatible (bool, default: True ) –

    Invokes interval_distribution_1 using miditoolbox_compatible=True, which performs normalization slightly differently (see interval_distribution_1. Default is False, which simply skips division when the total count is zero (this also returns a zero matrix when the count is zero).

Returns:

  • Distribution

    A 13-element distribution representing proportions of interval sizes. The first element corresponds to unison intervals, and the last element corresponds to octave intervals. If the score is empty, the function returns a Distribution with all elements set to zero.

Source code in amads/pitch/ivsizedist1.py
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
def interval_size_distribution_1(
    score: Score,
    name: str = "Interval Size Distribution",
    weighted: bool = True,
    miditoolbox_compatible: bool = True,
) -> Distribution:
    """
    Returns the interval size distribution of a musical score.

    Intervals greater than one octave are ignored.

    Parameters
    ----------
    score : Score
        The musical score to analyze
    name : str
        A name for the distribution and plot title.
    weighted : bool, optional
        If True, the interval distribution is weighted by note durations
        in seconds that are modified according to Parncutt's durational
        accent model (1994), by default True.
    miditoolbox_compatible : bool
        Invokes interval_distribution_1 using miditoolbox_compatible=True,
        which performs normalization slightly differently (see
        [interval_distribution_1]
        [amads.pitch.ivdist1.interval_distribution_1].
        Default is False, which simply skips division when the total
        count is zero (this also returns a zero matrix when the count
        is zero).

    Returns
    -------
    Distribution
        A 13-element distribution representing proportions of interval sizes.
        The first element corresponds to unison intervals, and the last
        element corresponds to octave intervals. If the score is empty,
        the function returns a Distribution with all elements set to zero.
    """
    id = interval_distribution_1(score, name, weighted, miditoolbox_compatible)
    id = id.data  # we only need the data from the distribution
    isd = [0.0] * 13

    isd[0] = id[12]
    for i in range(1, 13):
        isd[i] = id[i + 12] + id[12 - i]  # merge upward and downward bins
    # note that isd is normalized because it sums to the same value as isd
    x_categories = [str(i) for i in range(13)]
    return Distribution(
        name,
        isd,
        "interval_size",
        [12],
        x_categories,  # type: ignore
        "Interval Size",
        None,
        "Proportion",
    )