Skip to content

Segment

fantastic_segmenter

fantastic_segmenter(
    score: Score, phrase_gap: float, units: str
) -> List[Score]

Segment melody into phrases based on IOI gaps.

Parameters:

  • score (Score) –

    Score object containing melody to segment

  • 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:

  • list[Score]

    List of Score objects representing phrases

Source code in amads/melody/segment.py
 6
 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
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
def fantastic_segmenter(
    score: Score, phrase_gap: float, units: str
) -> List[Score]:
    """Segment melody into phrases based on IOI gaps.
    Parameters
    ----------
    score : Score
        Score object containing melody to segment
    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
    -------
    list[Score]
        List of Score objects representing phrases
    """
    assert units in ["seconds", "quarters"]
    if units == "seconds":
        raise NotImplementedError(
            "Seconds are not yet implemented, see issue #75: "
            "https://github.com/music-computing/amads/issues/75"
        )
    if units == "quarters":
        # Extract notes from score
        notes = score.get_sorted_notes()

        # Create a dictionary to store IOI information
        ioi_data = {}

        # Calculate IOIs
        for i, note in enumerate(notes):
            # Initialize entry for this note
            ioi_data[note] = None

            # first note has no IOI by convention
            if i > 0:
                ioi_data[note] = note.onset - notes[i - 1].onset
            else:
                ioi_data[note] = None

        phrases = []
        current_phrase = []
        for note in notes:
            # Check whether we need to make a new phrase
            need_new_phrase = (
                len(current_phrase) > 0
                and ioi_data[note]
                is not None  # Check current note's IOI instead of previous note
                and ioi_data[note] > phrase_gap
            )
            if need_new_phrase:
                # Create new score for the phrase
                phrase_score = Score(onset=0, duration=None)
                part = Part(
                    parent=None, onset=0, duration=None
                )  # parent=None is required
                start_time = current_phrase[0].onset
                # Adjust note timings relative to phrase start
                for phrase_note in current_phrase:
                    # make a parentless copy of the note so we can adjust its onset
                    # before inserting it into the new part in proper time order
                    new_note = phrase_note.insert_copy_into(None)
                    new_note.onset -= start_time
                    part.insert(new_note)
                phrase_score.insert(part)  # This will set the parent
                phrases.append(phrase_score)
                current_phrase = []
            current_phrase.append(note)

        # Append final phrase
        if len(current_phrase) > 0:
            phrase_score = Score(onset=0, duration=None)
            part = Part(
                parent=None, onset=0, duration=None
            )  # parent=None is required
            start_time = current_phrase[0].onset
            for phrase_note in current_phrase:
                new_note = phrase_note.insert_copy_into(None)
                new_note.onset -= start_time
                part.insert(new_note)
            phrase_score.insert(part)  # This will set the parent
            phrases.append(phrase_score)

        return phrases