tonal-guitar
v0.4.0
Published
Guitar fretboard, shapes, patterns, and sequences — built on Tonal.js
Maintainers
Readme
tonal-guitar 
Guitar fretboard, shapes, patterns, and sequences — built on Tonal.js
Install
npm install tonal-guitar @tonaljs/note @tonaljs/intervalOptional peer dependencies for scale/chord/key integration:
npm install @tonaljs/scale @tonaljs/chord @tonaljs/keyUsage
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
buildFromScalepitch-correctness fix.buildFromScalenow 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.isShapeCompatiblechroma-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) andrelabelShapeToScale(integration tier) — see Shape Relabeling. - New
ScaleShapefields: optionalqualityandparentShape, set on shapes derived viarelabelShape. - 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"); // => 3findNearestFret(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"); // => 0findFretInPosition(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); // => 5findNote(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 fretsfretboard(tuning?: string[], fretRange?: [number, number]) => FretboardPosition[]
Generate the complete fretboard grid:
fretboard(STANDARD, [0, 4]); // every note on strings 0-5, frets 0-4Shape 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 numbernote— 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 scaledegree— 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=3mBlues 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 arrayrhythmPattern— 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 AThe 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).
buildFromScalenow relabelsshapeintoscaleName's interval frame (viarelabelShape) 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 taggedscaleType: "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,relabelShapereturnsundefinedandbuildFromScalefalls 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"); // => undefinedrelatedScales(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
4Avs5dacross 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_CORPUSincludes"aeolian", but@tonaljs/scalenormalizes 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 infoFrettedScale— a scale applied to the fretboard (withemptysentinel pattern)ScaleShape— a scale shape definition (intervals per string, plus optionalquality/parentShapemetadata set on entries derived viarelabelShape)ChordShape— a chord voicing definition (one interval per string + fingerings + optional harmonic metadata)RelabelOptions— options forrelabelShape/relabelShapeToScale(name,quality,parentShape)Barre— barre chord finger placementFingering— chord shape applied to a specific rootFretboardPosition— 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 forinferShapeContextInferenceOptions— options forinferShapeContextInferenceCandidate— a ranked shape-detection resultInferenceProbe— normalised internal probe used by the scoring coreScoreBreakdown— transparent per-term score breakdown on eachInferenceCandidateWalkOptions— options forwalkPatternSequenceOptions— options forapplySequenceAlphaTexOptions— options fortoAlphaTeXAsciiTabOptions— options fortoAsciiTabKeyAnalysis— result ofanalyzeInKey
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
- @tonaljs/scale — scale definitions and lookups
- @tonaljs/chord — chord detection and properties
- @tonaljs/mode — modal scale relationships
- @tonaljs/key — key signatures and analysis
License
MIT
