The melody, in time order. Notes that never sound are dropped.
Cell-length bounds and the recurrence threshold; see ExtractMotifsOptions.
The motifs found, longest first, then by how often they recur, then by where they first appear. Empty when nothing recurs.
import { extractMotifs } from '@libraz/libcantus';
const notes = [
{ pitch: 60, startBeat: 0, durationBeat: 1 },
{ pitch: 62, startBeat: 1, durationBeat: 1 },
{ pitch: 64, startBeat: 2, durationBeat: 1 },
{ pitch: 67, startBeat: 4, durationBeat: 1 },
{ pitch: 69, startBeat: 5, durationBeat: 1 },
{ pitch: 71, startBeat: 6, durationBeat: 1 },
];
const motifs = extractMotifs(notes);
motifs[0]?.occurrences.map((o) => o.startBeat); // [0, 4]
Find the cells a melody keeps coming back to.
Every window of
minNotestomaxNotesconsecutive notes is reduced to its interval sequence and its rhythmic profile, and windows sharing both are statements of one motif — so a restatement a fifth higher, or one written in doubled note values, is found as the same cell rather than as a new one. Overlapping statements of a repetitive figure are counted once, and a cell whose statements all sit inside the statements of a longer cell that recurs at least as often is dropped, so the answer names the longest thing that recurs rather than every fragment of it.The melody is expected to be monophonic, as analyzeVoice expects a voice to be; split polyphonic material into lines first. Notes struck together are read as one event, the highest of them standing for it, so a chord is reduced to its top voice rather than read as a line of its own. Onsets are expected to be quantised, since two statements must agree on their rhythm to be recognised as one motif.