Utilities for calculating fingerings on string instruments: parse note names, convert to/from MIDI numbers, and compute all possible fingerings for a set of notes on a configurable instrument (e.g. violin, double bass with extensions).
npm install string-fingerings
The library provides:
C4, Bb3, F#5) and pitch classes (e.g. C, Db) with full enharmonic support (#, b, x, bb).Note — A note with a specific octave and enharmonic spelling (name, octave, alteration, number). Has text() and abcnote().PitchClass — Note name + alteration without octave (e.g. C, F#).NoteInput — Note | PitchClass; used when octave may be omitted.Instrument — Instrument definition: name, strings, stops, scaleLength, hardStretch, maxStretch, optional transposition.InstrumentString — One string: openNote (Note), optional additionalOpenSemitones (e.g. [-1,-2,-3,-4] for a C extension), optional per-string stops.Stop — A single fingering: stringIndex, stopIndex (semitones above open), noteNumber, naturalHarmonic.| Function | Description |
|---|---|
parse(noteText) |
Parse a note name (e.g. C4, Gb5) and return a Note. Throws on error. |
tryParse(noteText) |
Like parse but returns Note | string (error message). |
tryParseInput(noteText) |
Parse with optional octave: with octave → Note, without → PitchClass (e.g. C, C#). |
isNote(input) |
Type guard: true if NoteInput is a Note. |
noteNumber(noteName) |
MIDI number for a note name, or a string error message. |
nn(noteName) |
Same as noteNumber but throws on invalid input. |
noteName(noteNumber) |
Note name string (e.g. C4) for a MIDI number (0–127). |
abcnote(noteNumber) |
ABC notation for a MIDI number. |
Example
import { parse, noteNumber, noteName, tryParseInput, isNote } from 'string-fingerings'
parse('C4') // Note { name: 'C', octave: 5, number: 60, ... }
noteNumber('C4') // 60
noteName(60) // 'C4'
noteNumber('Bb3') // 58
const input = tryParseInput('G')
isNote(input) // false — it's a PitchClass
input.text() // 'G'
| Function | Description |
|---|---|
expandPitchClass(pc, minMidi, maxMidi) |
All Notes for that pitch class in the MIDI range. |
generateNoteCombinations(inputs, minMidi, maxMidi) |
All Note[] combinations; Note fixed, PitchClass expanded per octave (Cartesian product). |
Example
import { tryParseInput, expandPitchClass, generateNoteCombinations, parse } from 'string-fingerings'
const c = tryParseInput('C')
expandPitchClass(c, 55, 100) // [C4, C5, C6, C7] as Note[]
// One fixed note + one pitch class → several combinations
generateNoteCombinations([c, parse('G5')], 55, 100)
// e.g. [ [C4, G5], [C5, G5], [C6, G5], [C7, G5] ]
| Function | Description |
|---|---|
calculateFingerings(instrument, notes, validations?, includeNaturalHarmonics?) |
All valid fingerings for the given MIDI notes. Each fingering is a Stop[]. Optional validations: array of (instrument, fingering) => boolean. |
getStopRelPos(stopIndex) |
Relative position on the string (0–1) for a stop in semitones above open. |
fingeringHardness(instrument, fingering) |
Stretch “hardness” for a fingering (0.1, 0.5, or 1.0). 1.0 = beyond max stretch. |
hasNoGaps(_instrument, fingering) |
True if used strings are consecutive (no skipped string). |
hasPossibleStretch(instrument, fingering) |
True if fingering is within stretch limits (fingeringHardness !== 1.0). |
Example: define an instrument and get fingerings
import {
parse,
calculateFingerings,
hasNoGaps,
hasPossibleStretch,
type Instrument,
type InstrumentString,
} from 'string-fingerings'
const violin: Instrument<InstrumentString> = {
name: 'Violin',
stops: 24,
scaleLength: 330,
hardStretch: 78,
maxStretch: 92,
strings: [
{ openNote: parse('G3') },
{ openNote: parse('D4') },
{ openNote: parse('A4') },
{ openNote: parse('E5') },
],
}
// Single note: all ways to play that note
const midiNotes = [72] // C5
const fingerings = calculateFingerings(violin, midiNotes, [], false)
// e.g. [ [{ stringIndex: 0, stopIndex: 17, noteNumber: 72, naturalHarmonic: false }], ... ]
// Chord with validations: only fingerings with no gaps and possible stretch
const chord = [60, 64, 67] // C major
const chordFingerings = calculateFingerings(
violin,
chord,
[hasNoGaps, hasPossibleStretch],
false
)
Example: double bass with C extension
const bassWithCExt: Instrument<InstrumentString> = {
name: 'Double bass with C-ext',
stops: 24,
scaleLength: 1049,
hardStretch: 116,
maxStretch: 120,
strings: [
{
openNote: parse('E1'),
additionalOpenSemitones: [-1, -2, -3, -4], // D, Db, C, B
},
{ openNote: parse('A1') },
{ openNote: parse('D2') },
{ openNote: parse('G2') },
],
}
// D1 on C-ext + A1 open
calculateFingerings(bassWithCExt, [38, 45], [], false)
// e.g. [ [{ stringIndex: 0, stopIndex: -2, noteNumber: 38, ... }, { stringIndex: 1, stopIndex: 0, noteNumber: 45, ... }] ]
parse('C4') gives a note with number 60 and internal octave 5; text() returns 'C4'.