Arpeggios & Chord Detection
Arpeggio derivation, chord-shape inference, and curated voicing data
This module adds arpeggio derivation from scale shapes, registry-driven shape inference, and curated chord-shape data (7th-chord CAGED, open/barre chords, and jazz shell voicings).
Two Interval Frames
Every FrettedNote carries its interval in the parent-scale frame, not the chord frame. The two frames use different tonic references:
| Frame | Tonic | Example (Am7 inside C major) |
|---|---|---|
| Parent frame | Parent scale root (C) | A=6M, C=1P, E=3M, G=5P |
| Chord frame | Chord tonic (A) | A=1P, C=3m, E=5P, G=7m |
When arpeggioFromScale filters C-major notes to the Am7 chord tones, the retained notes still carry their C-major interval and degree fields. The scaleType field on an arpeggio result is a source/type label (e.g. "minor seventh") — not a claim of scale-hood or complete coverage.
filterChordTones
filterChordTones(scale: FrettedScale, intervals: string[]) => FrettedScale
Filter a FrettedScale to notes whose interval (in the parent-scale frame) appears in the provided set. Zero Tonal peer dependencies — pure filter, no chord name resolution.
Never throws. Empty or no-match input returns the NoFrettedScale sentinel (with root, tuning, shapeName preserved for the no-match case).
arpeggioFromScale
arpeggioFromScale(parent: FrettedScale, chordName: string) => FrettedScale
Derive a chord-tone arpeggio from an already-built parent FrettedScale. Filtering is chroma-based and frame-safe — diatonic arpeggios (Am7 inside C major) work without manual interval translation.
Bare chord types (no tonic) fall back to parent.root. Returns NoFrettedScale for empty parent or unrecognised chord name.
arpeggioFromShape
arpeggioFromShape(shape: ScaleShape, chordName: string, parentRoot: string, tuning?: string[]) => FrettedScale
Convenience composition: builds the parent scale then derives the arpeggio.
inferShapeContext
inferShapeContext(input: InferenceInput, options?: InferenceOptions) => InferenceCandidate[]
Detect which registered scale shapes cover a given grip or arpeggio. Returns a ranked list of candidates.
Returns [] for empty input, all-muted input, or fewer than 3 distinct pitch classes (override with includeWeak: true). Only candidates with full coverage (all probe pitch classes present in the built scale) are included.
Candidates are ranked by descending score, then shape name, then shapeRoot, then anchorFret.
ChordShape Harmonic Metadata
ChordShape carries optional fields for harmonic context:
canonicalRoot on open shapes is informational — it does not restrict applyChordShape. Apply any shape to any root; the transposed fingering is returned.
Query with the chordShapes.query method (conjunctive filter):
Curated Data Sets
Three new chord-shape sets are registered at import time:
- caged-chords-7th — movable E/A/D-form shapes for maj7, m7, 7, m7b5. No
canonicalRootset. - open-chords — open-position and barre shapes from tombatossals/chords-db (MIT). Covers M, m, 7, maj7, m7, dim, aug, sus2, sus4, m7b5.
- jazz-shells — 3-note shell voicings (root + 3rd + 7th, 5th omitted) for maj7, m7, 7, m7b5. Two string sets (654 and 543), two orderings (R37 and R73) each.
Grouped Formatter Input
Both toAlphaTeX and toAsciiTab now accept FrettedNote[][]. Each inner array is one simultaneous beat (strum/chord).
Flat FrettedNote[] is still accepted and produces identical output. notesPerBar, noteDurations, and rhythmPattern index by beat (group) under grouped input, not by individual note.
In the grouped path, ASCII tab uses column-width alignment based on the widest fret in each beat group, so multi-digit frets (e.g. fret 10) stay aligned across all string rows.