Quick start

These docs use the conventional short import:

import melody_features as mf

Batch feature extraction

The main entry point is melody_features.get_all_features().

Returns: pandas.DataFrame

Accepts as input:

  • a directory of MIDI files (str / Path)

  • a single MIDI path

  • a list of MIDI paths

  • a list of Melody objects

import melody_features as mf
import pandas as pd

results = mf.get_all_features(input="path/to/your/midi/files", skip_idyom=True)
assert isinstance(results, pd.DataFrame)
print(results.iloc[:1,].to_json(indent=4, orient="records"))

By default this is wide format: one row per melody, one column per feature (namespaced as {family}.{feature_name}, e.g. absolute_pitch.pitch_range). Pass long_format=True for one row per melody/feature. Minimal sample tables for both shapes are in Usage.

A notebook walkthrough is available in notebooks/example.ipynb. For signatures, return types, and configuration detail, see Usage.

Long format

Pass long_format=True for one row per melody/feature combination.

Returns: pandas.DataFrame with columns including melody_num, melody_id, feature_name, value, and (by default) joined metadata (family, source, domain, type, …).

import melody_features as mf

long_results = mf.get_all_features(
    input="path/to/your/midi/files",
    skip_idyom=True,
    long_format=True,       # tidy long table
    join_metadata=True,     # default; set False for minimal columns
)

descriptors = long_results[
    (long_results["type"] == "Descriptor")
    & (long_results["source"].str.contains("jSymbolic", na=False))
]

You can also reshape an existing wide DataFrame:

import melody_features as mf

wide_results = mf.get_all_features("path/to/your/midi/files", skip_idyom=True)
long_results = mf.to_long_format(wide_results)   # -> DataFrame
metadata = mf.get_feature_metadata()             # -> DataFrame

Individual features

load_midi() loads one file and returns a single Melody or None (never a list).

Feature functions take note lists rather than a Melody argument, but Melody exposes those lists as attributes (melody.pitches, melody.starts, …), so you load once and pass attributes through:

import melody_features as mf
from melody_features.io.midi import load_midi

midi_path = mf.get_corpus_files("essen", max_files=1)[0]  # list[Path] -> Path
melody = load_midi(midi_path)                             # Optional[Melody]
if melody is None:
    raise RuntimeError(f"could not load {midi_path}")

span = mf.pitch_range(melody.pitches)                     # list[int] -> int
print(span)

Use melody_features.list_available_features() (returns list[str], or list[dict] when detailed=True) to browse the catalogue. Features that need corpus statistics or IDyOM are easiest via mf.get_all_features and a Config (including multiple named IDyOM runs — see Usage).

Next steps