Module music_df.conversions.music_21

A function for converting music21 scores to music dataframes.

Functions

def music21_score_to_df(score: music21.stream.base.Score) ‑> pandas.DataFrame
Expand source code
def music21_score_to_df(score: Score) -> pd.DataFrame:
    rows = []
    time_sigs = score[TimeSignature].stream().flatten()  # type:ignore

    last_time_sig_offset = None
    for time_sig in time_sigs:
        if time_sig.offset == last_time_sig_offset:
            # Don't include duplicate time sigs across different parts
            continue

        rows.append(
            {
                "type": "time_signature",
                "onset": time_sig.offset,
                "other": {
                    "numerator": time_sig.numerator,
                    "denominator": time_sig.denominator,
                },
            }
        )
        last_time_sig_offset = time_sig.offset

    for part_i, part in enumerate(score[Part], start=1):
        notes = part[Note].stream().flatten()

        for note in notes:
            tie_to_next = False
            tie_to_prev = False

            if note.tie:
                if note.tie.type == "start":
                    tie_to_next = True
                elif note.tie.type == "stop":
                    tie_to_prev = True
                elif note.tie.type == "continue":
                    tie_to_next = True
                    tie_to_prev = True
                else:
                    raise NotImplementedError

            rows.append(
                {
                    "type": "note",
                    "onset": note.offset,
                    "release": note.offset + note.quarterLength,
                    "pitch": note.pitch.midi,
                    "tie_to_next": tie_to_next,
                    "tie_to_prev": tie_to_prev,
                    "voice": 1,  # TODO: (Malcolm 2024-03-28)
                    "part": part_i,
                    "spelling": note.pitch.name,
                }
            )

    measures = score[Measure]
    for measure in measures:
        rows.append({"type": "bar", "onset": measure.offset})

    df = pd.DataFrame(rows)
    df = sort_df(df)
    add_bar_durs(df)
    # This seems to be the easiest way of removing duplicate barlines across different parts
    df = df[~((df.type == "bar") & (df.bar_dur == 0))]
    df = df.drop("bar_dur", axis=1)
    df = df.reset_index(drop=True)
    return df