Skip to content

Pitch Distributions

pitch_class_distribution_1

pitch_class_distribution_1(
    score: Score,
    name: str = "Pitch Class Distribution",
    weighted: bool = True,
    miditoolbox_compatible: bool = False,
) -> Distribution

Calculate the pitch-class distribution of a note collection.

Parameters:

  • score (Score) –

    The musical score to analyze

  • name (str, default: 'Pitch Class Distribution' ) –

    Name for the distribution; plot title if plotted.

  • weighted (bool, default: True ) –

    If True, weight the pitch-class distribution by note durations in seconds that are modified according to Parncutt's durational accent model (1994), by default True.

  • miditoolbox_compatible (bool, default: False ) –

    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 12-element distribution representing the probabilities of each pitch class (C, C#, D, D#, E, F, F#, G, G#, A, A#, B). If the score is empty, the function returns a list with all elements set to zero.

Source code in amads/pitch/pcdist1.py
 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
def pitch_class_distribution_1(
    score: Score,
    name: str = "Pitch Class Distribution",
    weighted: bool = True,
    miditoolbox_compatible: bool = False,
) -> Distribution:
    """
    Calculate the pitch-class distribution of a note collection.

    Parameters
    ----------
    score : Score
        The musical score to analyze
    name : str
        Name for the distribution; plot title if plotted.
    weighted : bool, optional
        If True, weight the pitch-class distribution 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 12-element distribution representing the probabilities of each
        pitch class (C, C#, D, D#, E, F, F#, G, G#, A, A#, B). If the score
        is empty, the function returns a list with all elements set to zero.
    """
    score = cast(Score, score.merge_tied_notes())
    if weighted:
        score.convert_to_seconds()  # need seconds for duraccent calculation
    initial_value = 1e-12 if miditoolbox_compatible else 0.0
    bin_centers = [float(i) for i in range(12)]  # 25 bins from -12 to +12
    xcategories = CHROMATIC_NAMES
    h = Histogram1D(bin_centers, None, "linear", False, initial_value)

    for note in score.find_all(Note):
        note = cast(Note, note)
        h.add_point(
            round(note.pitch_class) % 12, duraccent(note) if weighted else 1.0
        )

    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()

    # xcategories is List[str], but Distribution takes int | float | str
    return Distribution(
        name,
        h.bins,
        "pitch_class",
        [12],
        xcategories,  # type: ignore
        "Pitch Class",
        None,
        "Proportion",
    )

pitch_class_distribution_2

pitch_class_distribution_2(
    score: Score,
    name: str = "Pitch Class Distribution",
    weighted: bool = True,
    miditoolbox_compatible: bool = False,
) -> Distribution

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

Parameters:

  • score (Score) –

    The musical score to analyze

  • name (str, default: 'Pitch Class Distribution' ) –

    Name for the distribution; plot title if plotted.

  • weighted (bool, default: True ) –

    If True, weight the pitch-class distribution by note durations in seconds that are modified according to Parncutt's durational accent model (1994), by default True.

  • miditoolbox_compatible (bool, default: False ) –

    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 12x12 distribution representing the transition probabilities of each pitch class (C, C#, D, D#, E, F, F#, G, G#, A, A#, B). If the score is empty, the function returns a distribution with all elements set to zero.

Source code in amads/pitch/pcdist2.py
 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
def pitch_class_distribution_2(
    score: Score,
    name: str = "Pitch Class Distribution",
    weighted: bool = True,
    miditoolbox_compatible: bool = False,
) -> Distribution:
    """Returns the 2nd order pitch-class distribution of a musical score.

    Parameters
    ----------
    score : Score
        The musical score to analyze
    name : str
        Name for the distribution; plot title if plotted.
    weighted : bool, optional
        If True, weight the pitch-class distribution 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 12x12 distribution representing the transition probabilities of
        each pitch class (C, C#, D, D#, E, F, F#, G, G#, A, A#, B). If the
        score is empty, the function returns a distribution with all
        elements set to zero.
    """
    score = cast(Score, score.merge_tied_notes())
    if weighted:
        score.convert_to_seconds()  # need seconds for duraccent calculation
    bin_centers = [float(i) for i in range(12)]  # 25 bins from -12 to +12
    x_categories = CHROMATIC_NAMES
    h = Histogram2D(bin_centers, None, "linear", False)

    # do not count transitions from one part to the next
    for p in score.find_all(Part):
        part: Part = cast(Part, p)
        prev_bin = None
        prev_pc = None
        prev_dur = None
        for n in part.find_all(Note):
            note: Note = cast(Note, n)
            pc = note.pitch_class
            if weighted and prev_pc is not None:
                dur = duraccent(note)
                w = prev_dur + dur  # type: ignore
                prev_dur = dur
            else:
                w = 1.0
            prev_bin = h.add_point_2d(prev_pc, pc, w, prev_bin)
            prev_pc = pc

    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,
        "pitch_class_transition",
        [12, 12],
        x_categories,  # type: ignore
        "Current Pitch Class",
        x_categories,  # type: ignore
        "Previous Pitch Class",
    )

pitch_mean

pitch_mean(score, weighted=False)

Compute the mean pitch or mean pitch weighted by duration (in quarters)

Parameters:

  • score (Score) –

    The pitch mean is computed for all pitches in the score. Groups of two or more tied notes are counted as one pitch occurrence.

  • weighted (bool, default: False ) –

    If true, pitches are weighted by their durations.

Source code in amads/pitch/pitch_mean.py
 7
 8
 9
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
def pitch_mean(score, weighted=False):
    """Compute the mean pitch or mean pitch weighted by duration (in quarters)

    Parameters
    ----------
    score : Score
        The pitch mean is computed for all pitches in the score. Groups of
        two or more tied notes are counted as one pitch occurrence.
    weighted : bool
        If true, pitches are weighted by their durations.
    """
    sum = 0
    count = 0
    if weighted:  # no need to merge tied notes. Rather than merging tied
        # notes, we use pitch * tied_duration = = pitch * (dur_1 + dur_2)
        # = pitch * dur_1 + pitch * dur_2, so we can just treat the
        # components of tied notes separately.
        for note in score.find_all(Note):
            sum += note.key_num * note.duration
            count += note.duration
    else:
        # Our problem is that we want to count only the first of any tied-note
        # group. We could use merge_tied_notes() to do this, but it is work to
        # make a new scoore and copy all notes. Instead, we keep a set of
        # tied-to notes and ignore any note encountered that is in the set.
        tied_to = set()
        for note in score.find_all(Note):
            if note.tie is not None:
                tied_to.add(note.tie)
            if note not in tied_to:
                sum += note.key_num
                count += 1
    return (sum / count) if sum > 0 else 0