npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

tonal-guitar

v0.4.0

Published

Guitar fretboard, shapes, patterns, and sequences — built on Tonal.js

Readme

tonal-guitar npm version

Guitar fretboard, shapes, patterns, and sequences — built on Tonal.js

Install

npm install tonal-guitar @tonaljs/note @tonaljs/interval

Optional peer dependencies for scale/chord/key integration:

npm install @tonaljs/scale @tonaljs/chord @tonaljs/key

Usage

import {
  buildFrettedScale,
  get,
  walkPattern,
  thirds,
  toAsciiTab,
  toAlphaTeX,
} from "tonal-guitar";

Quick Start

import {
  buildFrettedScale,
  get,
  walkPattern,
  thirds,
  toAsciiTab,
  toAlphaTeX,
} from "tonal-guitar";

// 1. Get a shape and build a scale on the fretboard
const shape = get("E Shape");
const scale = buildFrettedScale(shape, "A");

// 2. Generate a pattern and walk it
const pattern = thirds(7); // ascending thirds over 7-note scale
const notes = walkPattern(scale, pattern);

// 3. Output as ASCII tab or AlphaTeX
console.log(toAsciiTab(notes));
console.log(toAlphaTeX(notes, { tempo: 100, duration: 8 }));

v0.2.0 Changes

  • buildFromScale pitch-correctness fix. buildFromScale now relabels a shape into the requested scale's interval frame before building, so minor-tonic (and other non-major) scale names produce correct pitch content instead of silently building the shape's original (usually major) frame at the new tonic. See buildFromScale for the before/after example.
  • isShapeCompatible chroma-set semantics. Compatibility is now computed via interval-chroma-set coverage (enharmonic-safe) instead of raw interval-string comparison. This does not loosen relative-major/minor matching -- a major-frame shape is still incompatible with a minor scale name.
  • New APIs: relabelShape (pure tier, src/transform.ts) and relabelShapeToScale (integration tier) — see Shape Relabeling.
  • New ScaleShape fields: optional quality and parentShape, set on shapes derived via relabelShape.
  • 10 new registered entries: 5 minor CAGED scale shapes and 5 minor pentatonic boxes — see Minor-Quality Entries.

See CHANGELOG.md for full details.

API

For automated chord/scale shape correctness and quality checks, see Audit — also viewable live on the deployed Guitar Lab /shapes page.

Tuning Constants

Pre-defined tunings as string[] arrays (low string to high string):

import { STANDARD, DROP_D, DADGAD, OPEN_G, STANDARD_7, STANDARD_8 } from "tonal-guitar";

STANDARD;   // => ["E2", "A2", "D3", "G3", "B3", "E4"]
DROP_D;     // => ["D2", "A2", "D3", "G3", "B3", "E4"]
DADGAD;     // => ["D2", "A2", "D3", "G3", "A3", "D4"]
OPEN_G;     // => ["D2", "G2", "D3", "G3", "B3", "D4"]
STANDARD_7; // => ["B1", "E2", "A2", "D3", "G3", "B3", "E4"]
STANDARD_8; // => ["F#1", "B1", "E2", "A2", "D3", "G3", "B3", "E4"]

Fretboard Math

noteAt(tuning: string[], stringIndex: number, fret: number) => string

Get the note name at a string/fret position:

noteAt(STANDARD, 0, 5); // => "A2" (5th fret, low E string)
noteAt(STANDARD, 1, 3); // => "C3" (3rd fret, A string)

fretFor(tuning: string[], stringIndex: number, targetNote: string) => number | null

Reverse lookup — find which fret a note is on a given string:

fretFor(STANDARD, 0, "A2"); // => 5
fretFor(STANDARD, 1, "C3"); // => 3

findNearestFret(tuning: string[], stringIndex: number, pitchClass: string) => number | null

Find the lowest fret where a pitch class appears on a string:

findNearestFret(STANDARD, 0, "A"); // => 5
findNearestFret(STANDARD, 1, "A"); // => 0

findFretInPosition(tuning: string[], stringIndex: number, pitchClass: string, referenceFret: number, span?: number) => number | null

Find a pitch class within a position window centered on a reference fret:

findFretInPosition(STANDARD, 0, "A", 5, 5); // => 5

findNote(pitchClass: string, tuning?: string[], fretRange?: [number, number]) => FretboardPosition[]

Find all positions of a pitch class across the fretboard:

findNote("A"); // => [{string: 0, fret: 5, note: "A2", midi: 45}, ...]
findNote("A", STANDARD, [0, 12]); // limit to first 12 frets

fretboard(tuning?: string[], fretRange?: [number, number]) => FretboardPosition[]

Generate the complete fretboard grid:

fretboard(STANDARD, [0, 4]); // every note on strings 0-5, frets 0-4

Shape Registry

Built-in shapes are registered at import time: CAGED scale shapes (5), CAGED chord shapes (5), 3NPS patterns (7), pentatonic boxes (5) -- plus (v0.2.0) 5 minor CAGED scale shapes and 5 minor pentatonic boxes, derived from the major-frame shapes via relabelShape, and 5 minor + 5 major blues boxes, derived via addPassingTone. See Minor-Quality Entries and Blues Boxes below.

get(name: string) => ScaleShape | undefined

Retrieve a scale shape by name:

get("E Shape"); // => { name: "E Shape", system: "caged", strings: [...], ... }
get("3NPS Pattern 1 (Ionian)"); // => { name: "3NPS Pattern 1 (Ionian)", system: "3nps", ... }

all() => ScaleShape[]

Get all registered scale shapes.

names() => string[]

Get all registered scale shape names:

names(); // => ["E Shape", "D Shape", "C Shape", ...]

add(shape: ScaleShape) => ScaleShape

Register a custom shape:

add({
  name: "My Custom Shape",
  system: "custom",
  strings: [["1P", "2M"], ["4P", "5P"], null, null, null, null],
  rootString: 0,
});

removeAll() => void

Clear all registered shapes.

chordShapes

Separate registry for chord shapes with the same API: chordShapes.get(), chordShapes.all(), chordShapes.names(), chordShapes.add(), chordShapes.removeAll().

Build Engine

buildFrettedScale(shape: ScaleShape, root: string, tuning?: string[]) => FrettedScale

Apply a scale shape to a root note and tuning, returning all fretted positions:

const scale = buildFrettedScale(get("E Shape"), "A");
// => {
//   empty: false,
//   root: "A",
//   scaleType: "",
//   scaleName: "",
//   shapeName: "E Shape",
//   tuning: ["E2", "A2", "D3", "G3", "B3", "E4"],
//   notes: [
//     { string: 0, fret: 5, note: "A2", pc: "A", interval: "1P", degree: 1, midi: 45, ... },
//     { string: 0, fret: 7, note: "B2", pc: "B", interval: "2M", degree: 2, midi: 47, ... },
//     ...
//   ]
// }

Each FrettedNote contains:

  • string — 0-indexed string (0 = lowest)
  • fret — fret number
  • note — full note name with octave (e.g. "A2")
  • pc — pitch class (e.g. "A")
  • interval — interval from root (e.g. "1P", "3M")
  • scaleIndex — 0-based position in scale
  • degree — 1-based degree (scaleIndex + 1)
  • intervalNumber — numeric part of interval (e.g. 3 for "3M")
  • midi — MIDI number

Returns a FrettedScale with empty: true for invalid inputs.

applyChordShape(shape: ChordShape, root: string, tuning?: string[]) => Fingering

Apply a chord shape to a root, returning a Fingering with per-string fret numbers:

const chord = applyChordShape(chordShapes.get("E Shape"), "A");
// => {
//   positions: [...FrettedNote[]],
//   frets: [5, 7, 7, 6, 5, 5],
//   root: "A",
//   shapeName: "E Shape",
//   startFret: 5
// }

Shape Relabeling

relabelShape(shape: ScaleShape, targetIntervals: string[], options?: RelabelOptions) => ScaleShape | undefined

Pure-tier primitive that rewrites a shape's per-string interval labels into a different, rotation-compatible interval frame (e.g. turning a major-frame CAGED shape into its natural-minor labeling). Geometry (which string/fret each note lands on) is unchanged -- only strings labels, rootString, name, quality, and parentShape change. Returns a new ScaleShape (the input is never mutated), or undefined when no valid relabeling exists (e.g. relabeling a 7-note shape into a 5-note pentatonic frame).

import { relabelShape, get } from "tonal-guitar";

const em = relabelShape(get("G Shape"), ["1P", "2M", "3m", "4P", "5P", "6m", "7m"], {
  name: "Em Shape",
  quality: "minor",
  parentShape: "G Shape",
});
// em.rootString === 0, em.quality === "minor", em.parentShape === "G Shape"
interface RelabelOptions {
  name?: string;        // override the derived name (default: input shape.name)
  quality?: string;      // value written to result.quality
  parentShape?: string;  // value written to result.parentShape (default: shape.name)
}

This is the primitive used to derive the 10 registered minor-quality entries -- see Minor-Quality Entries below, and relabelShapeToScale (in Tonal.js Integration) for the scale-name-driven wrapper.

Minor-Quality Entries

The 5 CAGED and 5 pentatonic major-frame source shapes are each relabeled at import time (via relabelShape) into a paired minor-frame entry, registered under a distinct name. Geometry is identical to the parent shape -- only interval labels, rootString, and quality/parentShape metadata differ.

Minor CAGED shapes

| Parent shape | Registered minor name | rootString | quality | | --- | --- | --- | --- | | "E Shape" | "Dm Shape" | 2 | "minor" | | "D Shape" | "Cm Shape" | 1 | "minor" | | "C Shape" | "Am Shape" | 1 | "minor" | | "A Shape" | "Gm Shape" | 0 | "minor" | | "G Shape" | "Em Shape" | 0 | "minor" |

Minor pentatonic boxes

| Parent shape | Registered minor name | rootString | quality | | --- | --- | --- | --- | | "Pentatonic Box 1" | "Pentatonic Box 1 Minor" | 0 | "minor-pentatonic" | | "Pentatonic Box 2" | "Pentatonic Box 2 Minor" | 2 | "minor-pentatonic" | | "Pentatonic Box 3" | "Pentatonic Box 3 Minor" | 1 | "minor-pentatonic" | | "Pentatonic Box 4" | "Pentatonic Box 4 Minor" | 1 | "minor-pentatonic" | | "Pentatonic Box 5" | "Pentatonic Box 5 Minor" | 0 | "minor-pentatonic" |

get("Em Shape");
// => { name: "Em Shape", system: "caged", quality: "minor", parentShape: "G Shape", rootString: 0, ... }

// Same fret positions as the major-frame parent at the relative-major root:
buildFrettedScale(get("G Shape"), "C").notes;
buildFrettedScale(get("Em Shape"), "A").notes; // same {string, fret} pairs, A=1P, C=3m

Blues Boxes

addPassingTone(shape: ScaleShape, tone: string, options?: PassingToneOptions) => ScaleShape | undefined

Adds a chromatic passing tone to any scale shape -- e.g. the b5 ("5d") that turns a minor pentatonic box into a minor blues box, the b3 ("3m") that turns a major pentatonic box into a major blues box, or a "7M" on a mixolydian shape for bebop dominant.

Placement rule: the tone goes on the same string, one fret above every note a semitone below it (the b5 sits right above each 4P). Tones inside the shape's existing pitch range are always kept, even if they need a one-fret stretch; a tone that would become the shape's new highest note is kept only if it fits within the existing fret span. Fret geometry is evaluated in options.tuning (default STANDARD). Returns undefined for an invalid tone, a shape that doesn't build, or when nothing can be placed (no semitone-below neighbor, or the pitch is already present).

import { addPassingTone, get } from "tonal-guitar";

const blues = addPassingTone(get("Em Shape"), "5d", { name: "Em Blues", quality: "minor-blues" });

interface PassingToneOptions {
  name?: string;         // override the result name (default: shape.name)
  quality?: string;      // value written to result.quality
  parentShape?: string;  // value written to result.parentShape (default: shape.name)
  tuning?: string[];     // tuning for the stretch rule (default: STANDARD)
}

Registered blues boxes (all system: "pentatonic", box numbers shared with the pentatonic boxes):

| Registered name | Derived from | quality | b5 frets in A minor blues | | --- | --- | --- | --- | | "Blues Box 1 Minor" | "Pentatonic Box 1 Minor" + 5d | "minor-blues" | A:6, G:8 | | "Blues Box 2 Minor" | "Pentatonic Box 2 Minor" + 5d | "minor-blues" | low E:11, G:8 | | "Blues Box 3 Minor" | "Pentatonic Box 3 Minor" + 5d | "minor-blues" | low E:11, D:13, high E:11 | | "Blues Box 4 Minor" | "Pentatonic Box 4 Minor" + 5d | "minor-blues" | D:13, B:16 | | "Blues Box 5 Minor" | "Pentatonic Box 5 Minor" + 5d | "minor-blues" | A:6, B:4 | | "Blues Box N Major" | "Blues Box N Minor" relabeled | "major-blues" | same frets (C major blues = A minor blues) |

They work with Tonal's scale names: buildFromScale(get("Blues Box 1 Minor"), "E blues") ("blues" is Tonal's alias for "minor blues"), modeShapes("A blues"), isShapeCompatible(shape, "C major blues"), and relatedScales (minor blues ↔ relative major blues).

Pattern Generators

All generators return number[] degree sequences.

ascendingIntervals(scaleLength: number, interval: number, octaves?: number) => number[]

Generate ascending interval pattern:

ascendingIntervals(7, 2); // 3rds: [1,3, 2,4, 3,5, 4,6, 5,7, 6,8]
ascendingIntervals(7, 3); // 4ths: [1,4, 2,5, 3,6, 4,7, 5,8]

descendingIntervals(scaleLength: number, interval: number, startDegree?: number) => number[]

Generate descending interval pattern:

descendingIntervals(7, 2); // 3rds: [8,6, 7,5, 6,4, 5,3, 4,2, 3,1]

ascendingLinear(from: number, to: number) => number[]

ascendingLinear(1, 8); // => [1, 2, 3, 4, 5, 6, 7, 8]

descendingLinear(from: number, to: number) => number[]

descendingLinear(8, 1); // => [8, 7, 6, 5, 4, 3, 2, 1]

grouping(scaleLength: number, groupSize: number, step?: number) => number[]

Generate overlapping groups:

grouping(7, 4); // => [1,2,3,4, 2,3,4,5, 3,4,5,6, 4,5,6,7]

thirds(scaleLength) / fourths(scaleLength) / sixths(scaleLength)

Convenience wrappers:

thirds(7);  // same as ascendingIntervals(7, 2)
fourths(7); // same as ascendingIntervals(7, 3)
sixths(7);  // same as ascendingIntervals(7, 5)

Pattern Walker

walkPattern(scale: FrettedScale, pattern: number[], options?: WalkOptions) => FrettedNote[]

Walk a degree pattern through a fretted scale, picking concrete notes:

const scale = buildFrettedScale(get("E Shape"), "A");
const notes = walkPattern(scale, [1, 3, 5, 7, 6, 5, 4, 3, 2, 1]);

Options:

  • direction: "auto" (default) — auto-detects ascending/descending from consecutive degrees. "up", "down", "nearest" for fixed direction.

Auto-direction compares consecutive pattern degrees: [1,3,5,7] ascends, then [7,6,5,4,3,2,1] descends. The walker picks the right octave at each step.

Sequences

applySequence(scale: FrettedScale, sequence: number[], options?: SequenceOptions) => FrettedNote[][]

Apply a degree sequence to a fretted scale, optionally shifting incrementally across degrees:

const passes = applySequence(scale, SEQ_1235, {
  incremental: true, // shift pattern across each degree
  boundToShape: true, // stop when notes go outside the shape
});
// Pass 1: [1,2,3,5], Pass 2: [2,3,4,6], Pass 3: [3,4,5,7], ...

Options:

  • incremental — shift pattern by +1 each pass (default: false)
  • boundToShape — stop passes when notes exceed shape range (default: true)
  • startDegree — starting degree (default: 1)
  • passes — max number of passes (default: all that fit)

flattenSequence(passes: FrettedNote[][]) => FrettedNote[]

Flatten multi-pass results into a single array:

const allNotes = flattenSequence(passes);

Built-in Sequences

import {
  ASCENDING_THIRDS,  // [1,3, 2,4, 3,5, 4,6, 5,7, 6,8]
  DESCENDING_THIRDS, // [8,6, 7,5, 6,4, 5,3, 4,2, 3,1]
  SEQ_1235,          // [1,2,3,5]
  SEQ_1234_GROUP,    // [1,2,3,4]
  SEQ_UP_DOWN,       // [1,2,3,4,3,2]
  SEQ_TRIAD_CLIMB,   // [1,3,5,3]
  SEQ_1357_DESC,     // [1,3,5,7,6,5,4,3,2,1]
} from "tonal-guitar";

Notation

parseChordFrets(input: string | (number | null)[]) => (number | null)[]

Parse chord fret notation:

parseChordFrets("x32010");       // => [null, 3, 2, 0, 1, 0]
parseChordFrets("8-10-10-9-8-8"); // => [8, 10, 10, 9, 8, 8]
parseChordFrets("x-3-2-0-1-0");  // => [null, 3, 2, 0, 1, 0]

formatChordFrets(frets: (number | null)[]) => string

Format fret array back to notation:

formatChordFrets([null, 3, 2, 0, 1, 0]); // => "x32010"
formatChordFrets([8, 10, 10, 9, 8, 8]);  // => "8-10-10-9-8-8"

parseScalePattern(input: string) => number[][]

Parse scale pattern shorthand:

parseScalePattern("5-8,5-7,5-7,5-7,5-8,5-8");
// => [[5,8],[5,7],[5,7],[5,7],[5,8],[5,8]]

Output Formatters

toAlphaTeX(notes: FrettedNote[], options?: AlphaTexOptions) => string

Format notes as AlphaTeX notation:

toAlphaTeX(notes, {
  title: "A Major Thirds",
  tempo: 120,
  duration: 8,
  key: "A",
});

Options:

  • title — piece title (default: "Exercise")
  • tempo — BPM (default: 120)
  • duration — default note duration: 4, 8, 16 (default: 8)
  • tuning — override tuning (default: STANDARD)
  • key — key signature (default: "C")
  • timeSignature — e.g. [4, 4]
  • notesPerBar — notes per bar (default: derived from duration)
  • noteDurations — per-note duration array
  • rhythmPattern — repeating duration cycle, e.g. [8, 8, 16, 16]

toAsciiTab(notes: FrettedNote[], options?: AsciiTabOptions) => string

Format notes as ASCII tablature:

toAsciiTab(notes);
// e|---5----|
// B|-----5--|
// G|---6----|
// D|-----7--|
// A|---7----|
// E|-5------|

Arpeggios & Chord Detection

These functions require @tonaljs/chord (optional peer dependency).

arpeggioFromShape(shape: ScaleShape, chordName: string, parentRoot: string, tuning?: string[]) => FrettedScale

Derive a chord-tone arpeggio from a scale shape. Builds the parent scale, then filters to notes whose pitch-class chroma belongs to the named chord.

import { arpeggioFromShape, get } from "tonal-guitar";

// Am7 arpeggio from the G Shape in the key of C major
const am7 = arpeggioFromShape(get("G Shape"), "Am7", "C");
// am7.root === "A"
// am7.scaleType === "minor seventh"
// am7.notes: 10 FrettedNotes, all with pc in {"A","C","E","G"}
// Each note's .interval is in the C-major (parent) frame, e.g. "6M" for A

The returned notes carry their parent-scale interval and degree fields unchanged — the chord name determines which pitch classes are kept, but the notes still report their position within the parent scale.

arpeggioFromScale(parent: FrettedScale, chordName: string) => FrettedScale

Same as arpeggioFromShape but takes an already-built parent FrettedScale. Use this when you want to reuse a scale you have already built:

const cMajor = buildFrettedScale(get("G Shape"), "C");
const am7 = arpeggioFromScale(cMajor, "Am7");
const em7 = arpeggioFromScale(cMajor, "Em7");

Bare chord types (no tonic) fall back to parent.root:

arpeggioFromScale(cMajor, "m7"); // tonic = "C"

filterChordTones(scale: FrettedScale, intervals: string[]) => FrettedScale

Low-level parent-frame filter. Retains notes whose interval field (relative to the parent root) is in the provided set. Use this when you have already translated intervals to the parent frame, or when you want zero Tonal dependencies:

import { filterChordTones, buildFrettedScale, get } from "tonal-guitar";

// Am7 in C major, parent-frame intervals: A=6M, C=1P, E=3M, G=5P
const cMajor = buildFrettedScale(get("G Shape"), "C");
filterChordTones(cMajor, ["6M", "1P", "3M", "5P"]);

inferShapeContext(input: InferenceInput, options?: InferenceOptions) => InferenceCandidate[]

Detect which registered scale shapes cover a given grip or arpeggio. Accepts a compact fret string, a fret array, or a FrettedScale:

import { inferShapeContext } from "tonal-guitar";

// From a chord grip
const candidates = inferShapeContext("x32010");
// candidates[0].shape       — best matching ScaleShape
// candidates[0].shapeRoot   — parent-scale root (e.g. "C")
// candidates[0].score       — total weighted match score
// candidates[0].breakdown   — transparent per-term breakdown

// From an arpeggio, filtered to CAGED system, top 3
const arp = arpeggioFromShape(get("G Shape"), "Am7", "C");
inferShapeContext(arp, { system: "caged", limit: 3 });

Returns [] when fewer than 3 distinct pitch classes are present (use { includeWeak: true } to override).

Strummed voicing rendering

Both toAlphaTeX and toAsciiTab now accept FrettedNote[][] — each inner array is one simultaneous beat (strum/chord):

import { applyChordShape, chordShapes, toAlphaTeX, toAsciiTab } from "tonal-guitar";

const voicing = applyChordShape(chordShapes.get("C Major Open"), "C");

// Render as strummed chord
toAlphaTeX([voicing.positions], { duration: 4 });
// => header + :4 (3.5 2.4 0.3 1.2 0.1) |

toAsciiTab([voicing.positions]);
// e|0|
// B|1|
// G|0|
// D|2|
// A|3|
// E|-|

Flat FrettedNote[] (original form) is still accepted and produces identical output.

Tonal.js Integration

These functions require optional peer dependencies (@tonaljs/scale, @tonaljs/chord, @tonaljs/key).

buildFromScale(shape: ScaleShape, scaleName: string, tuning?: string[]) => FrettedScale

Build a fretted scale using Tonal's Scale.get() for validation. Populates scaleType and scaleName:

const scale = buildFromScale(get("E Shape"), "A major");
scale.scaleType; // => "major"
scale.scaleName; // => "A major"

buildFromScale(get("Pentatonic Box 1"), "A minor pentatonic");

v0.2.0 behavior change (pitch-correctness fix). buildFromScale now relabels shape into scaleName's interval frame (via relabelShape) before building. Previously, buildFromScale(get("E Shape"), "A minor") applied the major-frame shape as-is at the "A" tonic, silently producing A-major pitch classes tagged scaleType: "aeolian". As of v0.2.0 the same call produces correct A-natural-minor notes (A=1P, C=3m, E=5P) in the relabeled geometry. If the shape can't be relabeled into the requested frame, relabelShape returns undefined and buildFromScale falls back to its pre-fix behavior (building the shape as-is), so no previously-working call regresses to empty. See Shape Relabeling above.

// v0.2.0: minor tonics now produce correct pitch content --
// pre-fix this call produced A-major pitch classes (A, B, C#, D, E, F#, G#)
// mislabeled as "aeolian"; post-fix it produces A-natural-minor pitch classes.
const aMinor = buildFromScale(get("E Shape"), "A minor");
[...new Set(aMinor.notes.map((n) => n.pc))].sort();
// => ["A", "B", "C", "D", "E", "F", "G"]
aMinor.notes.find((n) => n.pc === "C").interval; // => "3m"

relabelShapeToScale(shape: ScaleShape, scaleName: string, options?: RelabelOptions) => ScaleShape | undefined

Integration-tier wrapper over the pure relabelShape primitive (see Shape Relabeling): resolves scaleName via Tonal's Scale.get() and relabels shape into that scale's interval frame. Enharmonic spelling is inherited from the target scale's own intervals.

relabelShapeToScale(get("G Shape"), "A minor", { name: "Em Shape", quality: "minor", parentShape: "G Shape" });
// equals the pre-registered get("Em Shape")

relabelShapeToScale(get("E Shape"), "bogus scale"); // => undefined

relatedScales(frettedScale: FrettedScale) => Array<{ root: string, scale: string }>

Find all modal relatives (scales sharing the same pitch classes):

const scale = buildFromScale(get("Pentatonic Box 1"), "A minor pentatonic");
relatedScales(scale);
// => [
//   { root: "C", scale: "major pentatonic" },
//   { root: "D", scale: "egyptian" },
//   { root: "E", scale: "malkos raga" },
//   { root: "G", scale: "ritusen" },
//   { root: "A", scale: "minor pentatonic" }
// ]

identifyChord(frets: (number | null)[], tuning?: string[]) => string[]

Identify chord names from fret positions:

identifyChord([null, 3, 2, 0, 1, 0]); // => ["C"]
identifyChord([0, 2, 2, 1, 0, 0]);    // => ["E"]

analyzeInKey(frets: (number | null)[], keyName: string, tuning?: string[]) => KeyAnalysis

Analyze a chord voicing in the context of a major key:

analyzeInKey([null, 3, 2, 0, 1, 0], "C");
// => { empty: false, chord: "C", numeral: "I", degree: 1 }

analyzeInKey([null, 3, 2, 0, 1, 0], "G");
// => { empty: false, chord: "C", numeral: "IV", degree: 4 }

Returns { empty: true } if the chord is not found in the key.

isShapeCompatible(shape: ScaleShape, scaleName: string) => boolean

Check whether a shape is compatible with a scale by interval-chroma coverage (root-relative, enharmonic-safe): a shape is compatible iff its chroma set is a non-empty subset of the scale's chroma set.

v0.2.0 behavior change. The comparison is now chroma-based rather than raw interval-string comparison (fixes enharmonic spelling mismatches like 4A vs 5d across Tonal scale types). This is not a relative-major/minor loosening -- a major-frame shape is still incompatible with a minor scale name. The relative-major/minor identity is expressed through the registered minor-quality entries instead (see Minor-Quality Entries).

isShapeCompatible(get("E Shape"), "A major"); // => true
isShapeCompatible(get("Pentatonic Box 1"), "A major"); // => true (subset)
isShapeCompatible(get("E Shape"), "A minor"); // => false (major frame ⊄ minor frame, root-relative)
isShapeCompatible(get("Em Shape"), "A minor"); // => true (minor frame ⊆ minor frame)
isShapeCompatible(get("Pentatonic Box 1 Minor"), "A minor"); // => true (minor-pent ⊆ natural-minor chromas)

scalesContainingChord(chord: string, options?: ScalesContainingChordOptions) => ScalesContainingChordResult

Find scales that contain a chord's tones -- "what can I play over this chord". Resolves chord via Tonal's Chord.get(), then sweeps 12 chromatic roots x a fixed corpus of 11 scale types (DEFAULT_SCALE_CORPUS: major, dorian, phrygian, lydian, mixolydian, aeolian, locrian, harmonic minor, melodic minor, major pentatonic, minor pentatonic), keeping every candidate whose pitch-class set is a (tolerant) superset of the chord's. Results are partitioned into rootAnchored (scale tonic matches the chord's root) and otherRoots (every other root), each ranked by fit (extraTones ascending, then corpus order, then root-chroma distance, then name).

import { scalesContainingChord } from "tonal-guitar";

const result = scalesContainingChord("Cmaj7");
result.chord; // => "Cmaj7"
result.root; // => "C"
result.rootAnchored.map((s) => s.name);
// => ["C major", "C lydian"]
// (C mixolydian and C dorian are excluded -- they don't contain the chord's B / E natural)

scalesContainingChord("Cm7").rootAnchored.map((s) => s.name);
// => ["C minor pentatonic", "C dorian", "C phrygian", "C minor"]
// ("C major" is excluded -- it doesn't contain the chord's Eb / Bb)

Containment is strict by default (tolerateMissing: 0). Pass tolerateMissing: N to admit scales missing up to N chord tones, recorded in each result's omittedTones:

const tolerant = scalesContainingChord("Cm7", { tolerateMissing: 1 });
tolerant.rootAnchored.find((s) => s.name === "C mixolydian").omittedTones; // => ["Eb"]

Never throws -- an unresolvable or empty chord name returns { chord: "", root: "", rootAnchored: [], otherRoots: [] }.

Note on "aeolian" naming. DEFAULT_SCALE_CORPUS includes "aeolian", but @tonaljs/scale normalizes that alias to its canonical dictionary entry (Scale.get("A aeolian") returns { type: "minor", name: "A minor", ... } -- chroma-identical, just relabeled). As a result, aeolian-swept candidates surface in the output as e.g. "A minor", not "A aeolian".

modeShapes(modeName: string, shapeSystem?: string) => ScaleShape[]

Get all registered shapes compatible with a mode/scale:

modeShapes("C major", "caged"); // => 5 (major-frame CAGED shapes; minor entries excluded)

// v0.2.0: minor tonics now resolve to the 10 registered minor-quality entries
modeShapes("A minor", "caged");
// => 5: "Dm Shape", "Cm Shape", "Am Shape", "Gm Shape", "Em Shape"
modeShapes("A minor pentatonic", "pentatonic");
// => the 5 "Pentatonic Box N Minor" entries
modeShapes("A minor").length; // => 10 (5 minor CAGED + 5 minor pentatonic)

// Chroma-subset coverage applies to any superset frame: the minor-pentatonic
// chroma set {0,3,5,7,10} is also a subset of dorian's {0,2,3,5,7,9,10}, so
modeShapes("A dorian");
// => the 5 "Pentatonic Box N Minor" entries (there is no first-class dorian entry --
// this is the minor-pentatonic entries' chroma set fitting dorian too)

Types

The package exports these TypeScript interfaces:

  • FrettedNote — a note on the fretboard with position, pitch, and interval info
  • FrettedScale — a scale applied to the fretboard (with empty sentinel pattern)
  • ScaleShape — a scale shape definition (intervals per string, plus optional quality/parentShape metadata set on entries derived via relabelShape)
  • ChordShape — a chord voicing definition (one interval per string + fingerings + optional harmonic metadata)
  • RelabelOptions — options for relabelShape/relabelShapeToScale (name, quality, parentShape)
  • Barre — barre chord finger placement
  • Fingering — chord shape applied to a specific root
  • FretboardPosition — absolute position on the fretboard (string, fret, note, midi)
  • VoicingFamily — voicing family enum ("caged", "shell", "open", "barre", etc.)
  • VoicingPatternDictionary — dictionary type for voicing patterns (used by jazz shells)
  • InferenceInput — input forms for inferShapeContext
  • InferenceOptions — options for inferShapeContext
  • InferenceCandidate — a ranked shape-detection result
  • InferenceProbe — normalised internal probe used by the scoring core
  • ScoreBreakdown — transparent per-term score breakdown on each InferenceCandidate
  • WalkOptions — options for walkPattern
  • SequenceOptions — options for applySequence
  • AlphaTexOptions — options for toAlphaTeX
  • AsciiTabOptions — options for toAsciiTab
  • KeyAnalysis — result of analyzeInKey

Development

To author new shapes visually instead of hand-writing src/data/*.ts, run npm run workbench to start the local Shape Workbench — a Vite + React app (Board → Editor → Export) for building and editing scale/chord shapes on a virtual fretboard. Exporting from the app writes a changeset to .workbench/changeset.json.

Run npm run shapes:merge -- .workbench/changeset.json to land that changeset into src/data/ and src/index.ts. Pass --dry-run to preview the resulting diff without writing, or --check to validate the changeset (e.g. in CI) without merging.

Related

License

MIT