@libraz/libcantus - v1.1.0
    Preparing search index...

    Class Score

    Sounding notes with the context that gives them meaning: a meter map, a tempo map, and optionally the key they are read in.

    This is the library's entry point for music that already exists. A DAW plug-in, a transcription tool, or a MIDI importer holds NoteEvent[] before it holds anything else, and every question worth asking of it — what chords are these, where do the phrases end, what key is this — took a separate function and a hand-wired chain of intermediate results. A Score carries the notes and their context together, so each question is one call and the answers stay in the class API.

    The notes are held in time order, and their onsets are unbounded below: beat 0 is the first downbeat, so a pickup sounds at a negative beat and is kept there rather than being shifted onto the grid.

    import { Score } from '@libraz/libcantus';
    const score = Score.of([
    { pitch: 60, startBeat: 0, durationBeat: 2 },
    { pitch: 64, startBeat: 0, durationBeat: 2 },
    { pitch: 67, startBeat: 0, durationBeat: 2 },
    ]);
    score.totalBeats; // 2
    Index
    • Wrap plain score data.

      Parameters

      • data: ScoreData

        The notes and their context; copied, never retained.

      Returns Score

      If a note, the meter map or the tempo map carries a value the analysis cannot hold.

    • get totalBeats(): number

      Where the last note stops sounding.

      Measured from beat 0 rather than from the first onset, so a score that opens with a pickup reports the beat its music ends on and not its length. An empty score ends at 0.

      Returns number

    • Build a score from note events.

      A bare time signature becomes a one-entry meter map taking effect at beat 0, and a bare bpm a one-entry tempo map at beat 0; a score given neither is read in 4/4 at 120 bpm, which is what every meter-aware function in the library assumes when its caller names nothing.

      Parameters

      • notes: readonly NoteEvent[]

        The sounding notes, in any order.

      • Optionalopts: ScoreOptions

        The meter, tempo, and key to read them against.

      Returns Score

      The score.

      import { Score } from '@libraz/libcantus';
      const score = Score.of([{ pitch: 60, startBeat: 0, durationBeat: 4 }], {
      meters: { numerator: 3, denominator: 4 },
      tempo: 90,
      });
      score.meterAt(0).numerator; // 3
    • Read note events whose times are in ticks rather than beats.

      Onsets and durations are converted; nothing else about the events changes. A negative tick count is a pickup and stays one, since beat 0 is the first downbeat at either resolution.

      Parameters

      • events: readonly NoteEvent[]

        The events, with tick-valued onsets and durations.

      • ppq: number

        Ticks per quarter note.

      • Optionalopts: ScoreOptions

        The meter, tempo, and key to read them against.

      Returns Score

      The score, in beats.

      import { Score } from '@libraz/libcantus';
      const score = Score.fromTicks([{ pitch: 60, startBeat: 480, durationBeat: 960 }], 480);
      score.notes[0]?.durationBeat; // 2
    • The stretch of the score between two beats.

      A note is kept when its onset falls in [fromBeat, toBeat); the notes keep their absolute onsets, so the slice stays in the score's own time and a slice taken across a meter or tempo change is still read against it.

      Parameters

      • fromBeat: number

        First beat of the stretch, inclusive.

      • toBeat: number

        End of the stretch, exclusive.

      Returns Score

      The score holding those notes.

    • The score moved along the timeline.

      The meter and tempo maps stay where they are: they describe the bars and the pulse the music is played against, and moving the notes over them is how a phrase is heard a bar later. A negative shift moves music before the first downbeat, which is where a pickup lives.

      Parameters

      • beats: number

        How far to move, in quarter-note beats; negative moves earlier.

      Returns Score

      The moved score.

    • The score moved by a number of semitones.

      A carried key moves with the notes, so the score that comes back is read in the key it now sounds in and every analysis taken from it — its timeline, its numerals, its phrases — is taken against that key rather than against the one it was written in.

      Parameters

      • semitones: number

        The signed semitone offset.

      Returns Score

      The transposed score.

      If a note would leave the MIDI range.

    • The score moved by a spelled interval, keeping the spelling.

      Note events carry a MIDI pitch and no spelling of their own, so the interval contributes its size and its direction; a carried key is what keeps the spelling, and it moves with the notes — by the interval itself, so an augmented fourth and a diminished fifth land on differently spelled keys.

      Parameters

      • interval: IntervalLike

        An interval name (e.g. 'A4', '-m3'), plain interval data, or an Interval; a descending interval moves down.

      Returns Score

      The transposed score.

    • The notes pulled onto a grid.

      Onsets and durations are both snapped to the nearest multiple of the grid, counted from beat 0 in both directions, so a pickup is quantized against the same grid the downbeat is. A duration that would round to nothing keeps one grid unit, since a note quantized out of existence is a note lost.

      Parameters

      • grid: number

        The grid unit in quarter-note beats (0.25 for sixteenths).

      Returns Score

      The quantized score.

      If the grid is not a positive length.

      import { Score } from '@libraz/libcantus';
      const score = Score.of([{ pitch: 60, startBeat: 0.98, durationBeat: 1.03 }]);
      score.quantize(0.5).notes[0]?.startBeat; // 1
    • The notes placed on a groove template's slots.

      The template's per-bar grid is laid out against the score's opening signature, which is the signature the template must have been extracted under; a template carrying a different one is refused rather than drifting.

      Parameters

      • template: GrooveTemplate

        The groove template, from extractGrooveTemplate.

      Returns Score

      The score with the template's feel imposed.

      If the template was extracted under another meter.

    • The feel of the score's own timing, as a template another score can take on.

      The other direction of Score.groove: a played-in take is measured slot by slot for how far ahead of or behind the grid it sits and how hard it is struck, and the reading is handed back as a template. Only the subdivision is named, because the score already holds the meter the grid is laid out against — its opening signature, which is the one the template records and the one an apply-time meter is checked against.

      Parameters

      • Optionalsubdivision: number

        Grid steps per quarter-note beat; a sixteenth-note grid by default, which is where swing, push and drag are audible.

      Returns GrooveTemplate

      The template these notes describe.

      If the subdivision is not a positive whole number.

      import { Score } from '@libraz/libcantus';
      const played = Score.of([{ pitch: 36, startBeat: 0.02, durationBeat: 1 }]);
      Score.of([{ pitch: 36, startBeat: 0, durationBeat: 1 }]).groove(played.grooveTemplate());
    • The key held longest across the score, when one can be read.

      A score carrying a key answers with it. Otherwise the key regions are searched for and the one holding for the most beats wins, so a piece that digresses is named by the key it keeps coming back to rather than by the key it opens on. A score with nothing sounding has no key at all.

      Returns Key | undefined

      The prevailing key, or undefined when nothing sounds.

    • Every key region the score passes through, in time order.

      A score that was told its key answers with that key across the whole of it, as Score.key and Score.timeline do: the caller has already answered the question, and searching for a modulation away from a stated key would make the option read as a hint where it was given as an instruction. Only totalBeats is read from the options there, since the rest tune a search that is not run.

      Parameters

      Returns KeyRegion[]

      The regions; empty when nothing sounds.

    • Every key the score could be in, ranked, read as one key across the whole of it.

      Score.keys divides the score into the regions it passes through; this ranks the readings of the score entire, so a caller can see what the winner beat and by how much. Each note counts for its duration times its velocity — the measure chord inference already weighs by — so the sustained harmony that establishes a key outweighs a run of ornaments over it, which is what makes a score the right thing to ask rather than a bare histogram of its pitches.

      Parameters

      • Optionalopts: Omit<DetectKeyOptions, "weights">

        Which profile to rank with, whether the church modes take part, whether each candidate explains itself, and the budget; the weights come from the notes themselves, so they are not on offer.

      Returns DetectedKeyMatch[]

      The candidates, best first; empty when nothing sounds.

      import { Score } from '@libraz/libcantus';
      const score = Score.of([{ pitch: 60, startBeat: 0, durationBeat: 4 }]);
      score.detectKeys()[0]?.key.rootPc; // 0
    • The cadences that close the score's phrases, ranked by how much structure each one closes.

      Score.phrases says where the phrases end; this says which of those endings matter. Two chords alone cannot tell the close of a piece from a passing confirmation halfway through a hyperbar, and a phrase reading can, because it knows what the cadence closes and where that sits. Phrases no cadence closes are left out.

      Parameters

      Returns StructuralCadence[]

      The cadences, heaviest first; ties keep the earlier one.

    • The motifs the melody repeats.

      The plain analysis record comes back rather than a Motif, as it does from every other reader here that is not Score.timeline or Score.key: a Motif holds a bare cell of notes, so wrapping the record would drop the intervals, the rhythmic profile, every later occurrence and the rationale — the findings the extraction was asked for. A record is a cell, so Motif.fromData(found) reaches the transformations without anything being rebuilt by hand.

      A melody is one line, so a score holding several is read as its top voice: notes struck together are one event and the highest of them stands for it. A piano part read whole therefore answers for the voice a listener follows rather than for a line assembled from the insides of its chords. Split the score into lines first to have each of them answered for.

      Parameters

      Returns MotifData[]

      The motifs found, longest first.

      import { Motif, Score } from '@libraz/libcantus';
      const score = Score.of([
      { pitch: 60, startBeat: 0, durationBeat: 1 },
      { pitch: 62, startBeat: 1, durationBeat: 1 },
      { pitch: 64, startBeat: 2, durationBeat: 1 },
      { pitch: 60, startBeat: 3, durationBeat: 1 },
      { pitch: 62, startBeat: 4, durationBeat: 1 },
      { pitch: 64, startBeat: 5, durationBeat: 1 },
      ]);
      const found = score.motifs()[0];
      found?.occurrences.length; // 2
      found === undefined ? undefined : Motif.fromData(found).notes.length; // 3
    • The shape the melody traces.

      A contour is the shape of one line, so a score holding several is read as its top voice: notes struck together are one event and the highest of them stands for it, which keeps a chord from being measured as a climb through its own notes. Split the score into lines first to have each of them answered for; Score.voices is what reads the polyphony as polyphony.

      Returns MelodicContour

    • Each note's role in the harmony sounding under it.

      The chord under each note and the key it is read in both come from the score: the harmony from its own chord timeline, and the key from the argument, then the carried key, then the regions the analysis found. A score with no readable harmony — nothing sounding, or too little to name a chord — reads every note against C major and no chord at all, which labels the notes as the non-chord tones they are rather than failing.

      A score holds a whole piece rather than a single line, so the notes are read as the polyphony they are: each note is classified in its own voice, against everything else sounding under it. That is what a suspension needs — a dissonance is dissonant against something — and it is what keeps a note of one voice from being heard as the passing tone of another.

      Parameters

      • Optionalkey: KeyLike

        The key to read the notes in, as a key name, a plain key/scale, or a Key; defaults to the score's own.

      Returns AnalyzedNote[]

      One annotation per note, in the score's own time order.

    • The notes carrying the ids the voice analysis reports them back under.

      Score.voices answers about the notes; this hands back the notes themselves with the handle each answer names, so a caller pairing an annotation or a conflict with the note it describes has something to pair it with. The id is the note's position in the score's own time order, which is not necessarily the order the notes arrived in.

      Returns IdentifiedVoiceNote[]

      One voice note per note, in the score's own time order.

    • Whether an instrument can play the score, and what makes it hard.

      The third layer asks how much time separates two notes, and the score answers it from its whole tempo map: a passage under an accelerando is judged at the tempo actually in force across it, the same integral Score.secondsAt reports. The beats the issues name are the score's own.

      Parameters

      Returns PlayabilityReport

      Difficulty, the issues found, and each note's placement.

      If the value names no instrument, or the instrument it names is contradictory.

      import { Instrument, Score } from '@libraz/libcantus';
      const score = Score.of([{ pitch: 27, startBeat: 0, durationBeat: 1 }]);
      score.playability(Instrument.bass4()).issues[0]?.type; // 'noteOutOfRange'
    • Where a beat falls in seconds, read through the tempo map.

      Every tempo segment the span crosses is integrated, so a beat after a tempo change is not the change's tempo applied to the whole span. A beat inside a pickup reports negative elapsed time, at the opening tempo.

      Parameters

      • beat: number

        Position in quarter-note beats.

      Returns number

      Elapsed seconds from the tempo map's origin.

    • Where a beat falls in bars, read through the meter map.

      Parameters

      • beat: number

        Position in quarter-note beats.

      Returns BarPosition

      The 0-based bar and the quarter-note offset inside it; a pickup reports bar -1.

    • The score's beats as MIDI ticks, for a writer that counts in them.

      Parameters

      • ppq: number

        Ticks per quarter note.

      Returns NoteEvent[]

      The notes with tick-valued onsets and durations, in time order.

    • Whether another score holds the same notes in the same context.

      The comparison is made through the other score's public data, so two scores built by different copies of the module still compare.

      Parameters

      • other: Score

        The score to compare.

      Returns boolean

      True when the notes, the meter, the tempo and the read key match.