← banjotuna

Add chord diagram generation for all keys and chord types

a7035e7844a3fcff453d35ad8ab0c108e1096bac · Claude · Feb 1, 2026 17:02

Features:
- 12 keys (A through G#/Ab with enharmonic notation)
- 8 chord types: Major, Minor, Minor7, 6th, 7th, Dim, Sus4, Aug
- SVG chord diagrams with finger positions and degree labels
- Toggle between numeric (1-2-3-4) and PIMA finger notation
- Root notes highlighted with filled circles for print clarity
- Barre chord detection and display
- Works with all 24 tunings including 4-string banjo
- Print-friendly layout (4 chords per row)

https://claude.ai/code/session_017ri8N4m6j39ajt5xUaz65n

+1173 −2 · 2 files changed

  • CHORD_GENERATION_SPEC.md +355 −0
  • index.html +818 −2
diff --git a/CHORD_GENERATION_SPEC.md b/CHORD_GENERATION_SPEC.md
new file mode 100644
index 0000000..82fbc1c
--- /dev/null
+++ b/CHORD_GENERATION_SPEC.md
@@ -0,0 +1,355 @@
+# Banjo Chord Generation - Project Specification
+
+## Project Overview
+
+This specification describes the addition of a **Chord Generation Module** to the existing Interactive Banjo Tunings Reference application. The feature will generate chord diagrams for all 12 keys and 8 chord types, displayed in standard chord grid format, integrated with the existing tuning system.
+
+---
+
+## Current Architecture Summary
+
+**File Structure:** Single HTML file (`index.html`) with embedded CSS (~800 lines) and JavaScript (~970 lines)
+
+**Key Existing Components:**
+- `CHROMATIC_SCALE`: ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']
+- `getNoteAtFret(openNote, fret)`: Core function for note calculation
+- `normalizeNote(note)`: Converts any notation to canonical form
+- `displayNote(note)`: Applies display preferences (e.g., G# → Ab)
+- 24 tuning definitions with full metadata
+- SVG-based fretboard visualization
+- Capo transposition support
+- Solfège notation toggle
+
+---
+
+## Requirements
+
+### 1. Keys (12 Total)
+
+| Key | Display Format |
+|-----|---------------|
+| A | A |
+| A#/Bb | A#/Bb |
+| B | B |
+| C | C |
+| C#/Db | C#/Db |
+| D | D |
+| D#/Eb | D#/Eb |
+| E | E |
+| F | F |
+| F#/Gb | F#/Gb |
+| G | G |
+| G#/Ab | G#/Ab |
+
+**Display:** Use slash notation for enharmonic equivalents (A#/Bb)
+
+### 2. Chord Types (8 Per Key)
+
+| Type | Symbol | Intervals | Example (C) |
+|------|--------|-----------|-------------|
+| Major | (none) | 1, 3, 5 | C, E, G |
+| Minor | m | 1, ♭3, 5 | C, E♭, G |
+| Minor 7 | m7 | 1, ♭3, 5, ♭7 | C, E♭, G, B♭ |
+| Sixth | 6 | 1, 3, 5, 6 | C, E, G, A |
+| Seventh | 7 | 1, 3, 5, ♭7 | C, E, G, B♭ |
+| Diminished | dim | 1, ♭3, ♭5 | C, E♭, G♭ |
+| Suspended 4 | sus4 | 1, 4, 5 | C, F, G |
+| Augmented | aug | 1, 3, #5 | C, E, G# |
+
+**Total:** 12 keys × 8 types = **96 chord diagrams per tuning**
+
+### 3. Chord Intervals (Semitones from Root)
+
+```javascript
+CHORD_INTERVALS = {
+    'major':  [0, 4, 7],           // 1, 3, 5
+    'minor':  [0, 3, 7],           // 1, ♭3, 5
+    'm7':     [0, 3, 7, 10],       // 1, ♭3, 5, ♭7
+    '6':      [0, 4, 7, 9],        // 1, 3, 5, 6
+    '7':      [0, 4, 7, 10],       // 1, 3, 5, ♭7
+    'dim':    [0, 3, 6],           // 1, ♭3, ♭5
+    'sus4':   [0, 5, 7],           // 1, 4, 5
+    'aug':    [0, 4, 8]            // 1, 3, #5
+}
+```
+
+### 4. Chord Diagram Display (Grid Format)
+
+#### Layout Structure
+```
+         ○              ← Open string indicator
+    ┌────┬────┬────┬────┬────┐
+    │    │    │    │    │    │  ← Thick top line (nut)
+    ├────┼────┼────┼────┼────┤
+    │    │ ①  │    │ ②  │    │  ← Fret 1 (circled = finger position)
+    ├────┼────┼────┼────┼────┤
+    │    │    │ ③  │    │    │  ← Fret 2
+    ├────┼────┼────┼────┼────┤
+    │    │    │    │    │    │  ← Fret 3
+    ├────┼────┼────┼────┼────┤
+    │    │    │    │    │    │  ← Fret 4
+    └────┴────┴────┴────┴────┘
+      5    4    3    2    1     ← String numbers (5th string on left)
+      G    D    G    B    D     ← Open string notes
+      5    1    5    3    1     ← Chord degree indicators
+```
+
+#### Visual Elements
+
+| Element | Symbol | Description |
+|---------|--------|-------------|
+| Open string | ○ | Circle above nut - string played open |
+| Muted string | ✕ | X above nut - string not played |
+| Finger position | ①②③④ | Circled number showing which finger |
+| Barre | ⌒ | Arc connecting multiple positions on same fret |
+| Nut | ━━━━ | Thick horizontal line at top |
+| Fret lines | ──── | Thinner horizontal lines |
+| Strings | │ | Vertical lines, left to right = low to high |
+
+#### Finger Notation (Toggleable)
+
+**Numbered (Default):**
+- 1 = Index finger
+- 2 = Middle finger
+- 3 = Ring finger
+- 4 = Pinky
+- T = Thumb (rare)
+
+**PIMA (Classical Guitar):**
+- P = Pulgar (Thumb)
+- I = Índice (Index)
+- M = Medio (Middle)
+- A = Anular (Ring)
+
+### 5. Fret Range Display
+
+- **Default:** Show frets 1-5 (with nut)
+- **Higher positions:** If chord requires frets 5+, show appropriate range with fret number indicator
+- **Example:** If chord starts at fret 5, show "5fr" next to first fret line
+
+### 6. Note Highlighting
+
+For print-friendly display (black & white), use:
+- **Root notes:** Solid filled circle (black fill)
+- **Third/Fifth/etc:** Hollow circle with black outline
+- **Optional:** Gray fill for secondary chord tones
+
+### 7. Chord Degree Labels
+
+Show scale degrees below each string:
+- 1 = Root
+- 3 = Third (or ♭3 for minor)
+- 5 = Fifth (or ♭5 for dim, #5 for aug)
+- 6 = Sixth
+- 7 = Seventh (or ♭7)
+- 4 = Fourth (for sus4)
+
+---
+
+## Data Structure Design
+
+### Chord Definition
+
+```javascript
+const CHORD_DATA = {
+    // Key: chord fingering data for a specific tuning
+    // This will be generated algorithmically based on tuning
+};
+
+// Chord type definitions
+const CHORD_TYPES = {
+    'major': { symbol: '', name: 'Major', intervals: [0, 4, 7] },
+    'minor': { symbol: 'm', name: 'Minor', intervals: [0, 3, 7] },
+    'm7':    { symbol: 'm7', name: 'Minor 7', intervals: [0, 3, 7, 10] },
+    '6':     { symbol: '6', name: 'Sixth', intervals: [0, 4, 7, 9] },
+    '7':     { symbol: '7', name: 'Seventh', intervals: [0, 4, 7, 10] },
+    'dim':   { symbol: 'dim', name: 'Diminished', intervals: [0, 3, 6] },
+    'sus4':  { symbol: 'sus4', name: 'Suspended 4', intervals: [0, 5, 7] },
+    'aug':   { symbol: 'aug', name: 'Augmented', intervals: [0, 4, 8] }
+};
+
+// Key definitions with enharmonic display
+const KEYS = [
+    { root: 'A',  display: 'A' },
+    { root: 'A#', display: 'A#/B♭' },
+    { root: 'B',  display: 'B' },
+    { root: 'C',  display: 'C' },
+    { root: 'C#', display: 'C#/D♭' },
+    { root: 'D',  display: 'D' },
+    { root: 'D#', display: 'D#/E♭' },
+    { root: 'E',  display: 'E' },
+    { root: 'F',  display: 'F' },
+    { root: 'F#', display: 'F#/G♭' },
+    { root: 'G',  display: 'G' },
+    { root: 'G#', display: 'G#/A♭' }
+];
+```
+
+### Chord Shape Structure
+
+```javascript
+// A chord shape definition
+{
+    key: 'G',
+    type: 'major',
+    tuning: 'Open G',
+    positions: [
+        { string: 5, fret: 0, finger: null, degree: 5 },    // Open G
+        { string: 4, fret: 0, finger: null, degree: 1 },    // Open D
+        { string: 3, fret: 0, finger: null, degree: 5 },    // Open G
+        { string: 2, fret: 0, finger: null, degree: 3 },    // Open B
+        { string: 1, fret: 0, finger: null, degree: 1 }     // Open D
+    ],
+    barre: null,  // or { fret: 2, fromString: 1, toString: 4 }
+    baseFret: 1,  // First fret shown in diagram
+    muted: []     // Array of muted string numbers
+}
+```
+
+---
+
+## UI Design
+
+### New Tab: "Chords"
+
+Add as 4th tab alongside existing Tunings, Fretboards, Reference tabs.
+
+#### Controls
+1. **Tuning Selector** - Dropdown to select tuning (inherit from existing)
+2. **Key Selector** - Buttons or dropdown for 12 keys
+3. **Chord Type Filter** - Buttons to filter by type (or show all)
+4. **Finger Notation Toggle** - Switch between 1234 and PIMA
+5. **View Mode** - Grid view (all chords) or Single chord view
+
+#### Layout Options
+- **Grid View:** 4×2 grid showing all 8 chord types for selected key
+- **Full View:** All 96 chords organized by key (for printing)
+
+### Print Layout
+
+Optimize for A4/Letter paper:
+- 4 chord diagrams per row
+- Key headers with separator lines
+- Page breaks between keys (optional)
+
+---
+
+## Implementation Phases
+
+### Phase 1: Core Music Theory
+- [ ] Add CHORD_TYPES constant with intervals
+- [ ] Add KEYS constant
+- [ ] Create `getChordNotes(root, type)` function
+- [ ] Create `findChordPositions(tuning, chordNotes)` algorithm
+
+### Phase 2: Chord Diagram SVG Generator
+- [ ] Create `generateChordDiagramSVG(chordShape)` function
+- [ ] Implement finger position circles
+- [ ] Implement open/muted string indicators
+- [ ] Implement barre chord arcs
+- [ ] Implement degree labels
+- [ ] Add root note highlighting
+
+### Phase 3: UI Integration
+- [ ] Add Chords tab to navigation
+- [ ] Create chord grid container
+- [ ] Add key selector
+- [ ] Add chord type filter
+- [ ] Add finger notation toggle (1234/PIMA)
+- [ ] Integrate with existing tuning selector
+
+### Phase 4: Chord Calculation Algorithm
+- [ ] Implement voicing algorithm for each tuning
+- [ ] Handle 5-string banjo specifics (short 5th string)
+- [ ] Generate optimal fingerings
+- [ ] Support alternative voicings
+
+### Phase 5: Polish & Print
+- [ ] Add print-specific styles
+- [ ] Optimize diagram sizing
+- [ ] Add batch export option
+- [ ] Test across all tunings
+
+---
+
+## Technical Considerations
+
+### 5th String Handling
+The 5th string on a 5-string banjo starts at fret 5, not the nut. Chord diagrams must account for this:
+- If chord uses frets 1-4, 5th string cannot be fretted (only played open at its pitch)
+- Show 5th string position correctly based on `shortStringStartFret` from tuning data
+
+### Chord Voicing Algorithm
+Finding optimal chord voicings is complex. Considerations:
+1. Playability (finger stretch limits)
+2. Voice leading (smooth transitions)
+3. Open strings where beneficial
+4. Avoiding string muting conflicts
+
+**Approach:** Start with a database of common chord shapes, then algorithmically transpose based on tuning.
+
+### Capo Support
+Extend existing capo logic:
+- With capo at fret 2, G tuning becomes A tuning
+- Chord shapes remain same, key shifts up
+- Display effective key with capo position
+
+---
+
+## File Organization
+
+**Option A: Single File (Recommended)**
+Keep everything in `index.html` for maximum portability:
+- Add chord CSS to `<style>` section (~150 lines)
+- Add chord JS to `<script>` section (~400 lines)
+
+**Option B: Split Files**
+If size becomes unwieldy:
+```
+/index.html        - Main app
+/chords.js         - Chord data and logic
+/chords.css        - Chord diagram styles (optional)
+```
+Note: Split requires serving from HTTP server for local development.
+
+---
+
+## Appendix: Common Open G Chord Voicings
+
+Example chord data for Open G tuning (gDGBD):
+
+```javascript
+// These are reference voicings to be expanded algorithmically
+const OPEN_G_CHORDS = {
+    'G_major': {
+        positions: [0, 0, 0, 0, 0],  // All open
+        fingers: [null, null, null, null, null],
+        degrees: ['5', '1', '5', '3', '1']
+    },
+    'C_major': {
+        positions: [null, 2, 1, 0, 2],  // x-2-1-0-2
+        fingers: [null, 2, 1, null, 3],
+        degrees: ['x', '5', '1', '3', '5']
+    },
+    'D_major': {
+        positions: [0, 0, 2, 3, 2],  // 0-0-2-3-2
+        fingers: [null, null, 1, 3, 2],
+        degrees: ['3', '1', '5', '1', '5']
+    }
+    // ... etc
+};
+```
+
+---
+
+## Next Steps
+
+1. Review this specification
+2. Confirm UI/UX preferences
+3. Begin Phase 1 implementation
+4. Iterate based on feedback
+
+---
+
+*Document created: 2026-02-01*
+*For: Interactive Banjo Tunings Reference v1.0*
diff --git a/index.html b/index.html
index 667d2b2..fcde5ab 100644
--- a/index.html
+++ b/index.html
@@ -793,6 +793,167 @@
                 font-size: 22px;
             }
         }
+
+        /* ============================================
+           CHORD DIAGRAM STYLES
+           ============================================ */
+        .chord-controls {
+            display: flex;
+            flex-wrap: wrap;
+            gap: 15px;
+            padding: 15px;
+            background: white;
+            border: 2px solid var(--border-color);
+            margin-bottom: 20px;
+            align-items: flex-end;
+        }
+
+        .chord-control-group {
+            display: flex;
+            flex-direction: column;
+            gap: 5px;
+        }
+
+        .chord-control-group label {
+            font-size: 12px;
+            font-weight: bold;
+            color: var(--text-secondary);
+        }
+
+        .key-buttons {
+            display: flex;
+            flex-wrap: wrap;
+            gap: 5px;
+        }
+
+        .key-btn {
+            padding: 8px 12px;
+            border: 2px solid var(--border-color);
+            background: white;
+            cursor: pointer;
+            font-size: 13px;
+            font-weight: bold;
+            border-radius: 4px;
+            transition: all 0.2s;
+            min-width: 45px;
+            text-align: center;
+        }
+
+        .key-btn:hover {
+            background: #f0f0f0;
+        }
+
+        .key-btn.active {
+            background: var(--bg-dark);
+            color: white;
+            border-color: var(--bg-dark);
+        }
+
+        .chord-type-buttons {
+            display: flex;
+            flex-wrap: wrap;
+            gap: 5px;
+        }
+
+        .chord-type-btn {
+            padding: 6px 10px;
+            border: 2px solid var(--border-color);
+            background: white;
+            cursor: pointer;
+            font-size: 12px;
+            border-radius: 4px;
+            transition: all 0.2s;
+        }
+
+        .chord-type-btn:hover {
+            background: #f0f0f0;
+        }
+
+        .chord-type-btn.active {
+            background: var(--g-family);
+            color: white;
+            border-color: var(--g-family);
+        }
+
+        .chord-grid {
+            display: grid;
+            grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
+            gap: 20px;
+            padding: 10px;
+        }
+
+        .chord-diagram-container {
+            background: white;
+            border: 2px solid var(--border-color);
+            border-radius: 8px;
+            padding: 15px;
+            text-align: center;
+        }
+
+        .chord-diagram-title {
+            font-size: 18px;
+            font-weight: bold;
+            margin-bottom: 10px;
+            color: var(--text-primary);
+        }
+
+        .chord-diagram-subtitle {
+            font-size: 12px;
+            color: var(--text-muted);
+            margin-bottom: 10px;
+        }
+
+        .chord-notes-display {
+            font-size: 11px;
+            color: var(--text-secondary);
+            margin-top: 8px;
+            padding-top: 8px;
+            border-top: 1px solid var(--border-color);
+        }
+
+        .finger-toggle {
+            display: flex;
+            gap: 10px;
+            align-items: center;
+        }
+
+        .finger-toggle label {
+            display: flex;
+            align-items: center;
+            gap: 5px;
+            cursor: pointer;
+            font-size: 13px;
+        }
+
+        .chord-info-box {
+            background: #f9f9f9;
+            border: 1px solid var(--border-color);
+            padding: 10px 15px;
+            margin-bottom: 20px;
+            font-size: 13px;
+        }
+
+        .no-chord-message {
+            padding: 40px;
+            text-align: center;
+            color: var(--text-muted);
+            font-style: italic;
+        }
+
+        @media print {
+            .chord-controls {
+                display: none;
+            }
+
+            .chord-grid {
+                grid-template-columns: repeat(4, 1fr);
+            }
+
+            .chord-diagram-container {
+                break-inside: avoid;
+                page-break-inside: avoid;
+            }
+        }
     </style>
 </head>
 <body>
@@ -856,6 +1017,7 @@
         <div class="tabs no-print">
             <button class="tab active" data-tab="tunings">Tunings</button>
             <button class="tab" data-tab="fretboards">Fretboards</button>
+            <button class="tab" data-tab="chords">Chords</button>
             <button class="tab" data-tab="reference">Reference</button>
         </div>
 
@@ -906,6 +1068,57 @@
             </div>
         </div>
 
+        <!-- Chords Tab -->
+        <div id="chords-tab" class="tab-content">
+            <div class="chord-info-box no-print">
+                <strong>CHORD DIAGRAMS:</strong>
+                Vertical lines = strings (5th on left to 1st on right) | Horizontal lines = frets<br>
+                &#9675; = Open string | &#10005; = Muted string | Circled numbers = finger positions<br>
+                Thick top line = nut | Numbers below strings show chord degrees (1=root, 3=third, 5=fifth)<br>
+                Root notes shown with filled circles for easy identification
+            </div>
+
+            <div class="chord-controls no-print">
+                <div class="chord-control-group">
+                    <label>Tuning</label>
+                    <select id="chordTuningSelect" style="padding: 8px; font-size: 14px; min-width: 150px;">
+                    </select>
+                </div>
+
+                <div class="chord-control-group">
+                    <label>Key</label>
+                    <div class="key-buttons" id="keyButtons">
+                        <!-- Keys populated by JS -->
+                    </div>
+                </div>
+
+                <div class="chord-control-group">
+                    <label>Chord Type</label>
+                    <div class="chord-type-buttons" id="chordTypeButtons">
+                        <!-- Chord types populated by JS -->
+                    </div>
+                </div>
+
+                <div class="chord-control-group">
+                    <label>Finger Notation</label>
+                    <div class="finger-toggle">
+                        <label>
+                            <input type="radio" name="fingerNotation" value="numbers" checked>
+                            1-2-3-4
+                        </label>
+                        <label>
+                            <input type="radio" name="fingerNotation" value="pima">
+                            PIMA
+                        </label>
+                    </div>
+                </div>
+            </div>
+
+            <div id="chordContainer" class="chord-grid">
+                <!-- Chord diagrams rendered here -->
+            </div>
+        </div>
+
         <!-- Reference Tab -->
         <div id="reference-tab" class="tab-content">
             <div class="reference-section">
@@ -1098,6 +1311,423 @@
             return CHROMATIC_SCALE[newIndex];
         }
 
+        // ============================================
+        // CHORD DATA & FUNCTIONS
+        // ============================================
+
+        // 12 keys with display names (using slash for enharmonics)
+        const CHORD_KEYS = [
+            { root: 'C',  display: 'C' },
+            { root: 'C#', display: 'C#/Db' },
+            { root: 'D',  display: 'D' },
+            { root: 'D#', display: 'D#/Eb' },
+            { root: 'E',  display: 'E' },
+            { root: 'F',  display: 'F' },
+            { root: 'F#', display: 'F#/Gb' },
+            { root: 'G',  display: 'G' },
+            { root: 'G#', display: 'G#/Ab' },
+            { root: 'A',  display: 'A' },
+            { root: 'A#', display: 'A#/Bb' },
+            { root: 'B',  display: 'B' }
+        ];
+
+        // 8 chord types with intervals (semitones from root)
+        const CHORD_TYPES = {
+            'major': { symbol: '', name: 'Major', intervals: [0, 4, 7], degrees: ['1', '3', '5'] },
+            'minor': { symbol: 'm', name: 'Minor', intervals: [0, 3, 7], degrees: ['1', '♭3', '5'] },
+            'm7':    { symbol: 'm7', name: 'Minor 7', intervals: [0, 3, 7, 10], degrees: ['1', '♭3', '5', '♭7'] },
+            '6':     { symbol: '6', name: 'Sixth', intervals: [0, 4, 7, 9], degrees: ['1', '3', '5', '6'] },
+            '7':     { symbol: '7', name: 'Seventh', intervals: [0, 4, 7, 10], degrees: ['1', '3', '5', '♭7'] },
+            'dim':   { symbol: 'dim', name: 'Diminished', intervals: [0, 3, 6], degrees: ['1', '♭3', '♭5'] },
+            'sus4':  { symbol: 'sus4', name: 'Sus4', intervals: [0, 5, 7], degrees: ['1', '4', '5'] },
+            'aug':   { symbol: 'aug', name: 'Augmented', intervals: [0, 4, 8], degrees: ['1', '3', '#5'] }
+        };
+
+        // Finger notation mapping
+        const FINGER_NOTATION = {
+            numbers: { 1: '1', 2: '2', 3: '3', 4: '4', T: 'T' },
+            pima: { 1: 'I', 2: 'M', 3: 'A', 4: 'P', T: 'P' }
+        };
+
+        // Get all notes in a chord
+        function getChordNotes(rootNote, chordType) {
+            const type = CHORD_TYPES[chordType];
+            if (!type) return [];
+
+            const rootIndex = CHROMATIC_SCALE.indexOf(normalizeNote(rootNote));
+            if (rootIndex === -1) return [];
+
+            return type.intervals.map(interval => {
+                return CHROMATIC_SCALE[(rootIndex + interval) % 12];
+            });
+        }
+
+        // Check if a note is in a chord
+        function isNoteInChord(note, chordNotes) {
+            const normalizedNote = normalizeNote(note);
+            return chordNotes.includes(normalizedNote);
+        }
+
+        // Get the degree of a note in a chord
+        function getNoteDegree(note, rootNote, chordType) {
+            const type = CHORD_TYPES[chordType];
+            if (!type) return null;
+
+            const normalizedNote = normalizeNote(note);
+            const rootIndex = CHROMATIC_SCALE.indexOf(normalizeNote(rootNote));
+            const noteIndex = CHROMATIC_SCALE.indexOf(normalizedNote);
+
+            if (rootIndex === -1 || noteIndex === -1) return null;
+
+            const interval = (noteIndex - rootIndex + 12) % 12;
+            const degreeIndex = type.intervals.indexOf(interval);
+
+            if (degreeIndex === -1) return null;
+            return type.degrees[degreeIndex];
+        }
+
+        // Find chord voicing for a given tuning
+        function findChordVoicing(tuningData, rootNote, chordType, maxFret = 5) {
+            const chordNotes = getChordNotes(rootNote, chordType);
+            if (chordNotes.length === 0) return null;
+
+            const strings = tuningData.strings;
+            const numStrings = strings.length;
+            const shortStringIdx = tuningData.shortStringIndex;
+            const shortStringStart = tuningData.shortStringStartFret || 6;
+
+            // Find playable positions for each string
+            const stringOptions = strings.map((openNote, stringIdx) => {
+                const options = [];
+                const normalizedOpen = normalizeNote(openNote);
+
+                // Check open string
+                if (isNoteInChord(normalizedOpen, chordNotes)) {
+                    // For short string (5th string), it can only be played open if chord position allows
+                    if (stringIdx !== shortStringIdx || shortStringIdx === null) {
+                        options.push({ fret: 0, note: normalizedOpen });
+                    }
+                }
+
+                // Check fretted positions
+                for (let fret = 1; fret <= maxFret; fret++) {
+                    // Skip frets before short string starts
+                    if (stringIdx === shortStringIdx && fret < shortStringStart) {
+                        continue;
+                    }
+
+                    const frettedNote = getNoteAtFret(openNote, fret);
+                    if (isNoteInChord(frettedNote, chordNotes)) {
+                        options.push({ fret, note: frettedNote });
+                    }
+                }
+
+                return options;
+            });
+
+            // Find the best voicing using a simple algorithm
+            // Prioritize: 1) Lower frets, 2) More open strings, 3) Smaller stretch
+            const voicing = findBestVoicing(stringOptions, chordNotes, rootNote, numStrings, shortStringIdx);
+
+            if (!voicing) return null;
+
+            // Assign fingers based on fret positions
+            const positions = voicing.map((pos, idx) => {
+                if (pos === null) {
+                    return { string: numStrings - idx, fret: null, muted: true, note: null, degree: null };
+                }
+
+                const degree = getNoteDegree(pos.note, rootNote, chordType);
+                const isRoot = normalizeNote(pos.note) === normalizeNote(rootNote);
+
+                return {
+                    string: numStrings - idx,
+                    fret: pos.fret,
+                    muted: false,
+                    note: pos.note,
+                    degree: degree,
+                    isRoot: isRoot
+                };
+            });
+
+            // Assign finger numbers
+            assignFingers(positions);
+
+            // Determine base fret for display
+            const frettedPositions = positions.filter(p => p.fret > 0);
+            const minFret = frettedPositions.length > 0 ? Math.min(...frettedPositions.map(p => p.fret)) : 1;
+            const maxFretUsed = frettedPositions.length > 0 ? Math.max(...frettedPositions.map(p => p.fret)) : 1;
+            const baseFret = minFret > 4 ? minFret : 1;
+
+            // Check for barre chord
+            const barre = detectBarre(positions);
+
+            return {
+                positions,
+                baseFret,
+                barre,
+                chordNotes: chordNotes.map(n => displayNote(n))
+            };
+        }
+
+        function findBestVoicing(stringOptions, chordNotes, rootNote, numStrings, shortStringIdx) {
+            // Simple greedy algorithm to find a playable voicing
+            // Try to include all chord notes while minimizing stretch
+
+            const voicing = new Array(numStrings).fill(null);
+            const usedNotes = new Set();
+
+            // First pass: find positions for each string preferring open strings
+            for (let i = 0; i < numStrings; i++) {
+                const options = stringOptions[i];
+                if (options.length === 0) {
+                    voicing[i] = null; // Will be muted
+                    continue;
+                }
+
+                // Prefer open strings, then lower frets
+                const sorted = [...options].sort((a, b) => a.fret - b.fret);
+
+                // Prefer root note if available
+                const rootOption = sorted.find(opt => normalizeNote(opt.note) === normalizeNote(rootNote));
+                if (rootOption && !usedNotes.has(rootOption.note + '-' + i)) {
+                    voicing[i] = rootOption;
+                    usedNotes.add(rootOption.note);
+                } else {
+                    voicing[i] = sorted[0];
+                    usedNotes.add(sorted[0].note);
+                }
+            }
+
+            // Validate: check if all chord notes are present
+            const presentNotes = new Set(voicing.filter(v => v !== null).map(v => v.note));
+            const allNotesPresent = chordNotes.every(note => presentNotes.has(note));
+
+            // Check stretch (max fret distance between fingers)
+            const frettedFrets = voicing.filter(v => v !== null && v.fret > 0).map(v => v.fret);
+            const stretch = frettedFrets.length > 1 ? Math.max(...frettedFrets) - Math.min(...frettedFrets) : 0;
+
+            // If stretch is too large (> 4 frets) or not all notes present, try alternative
+            if (stretch > 4 || !allNotesPresent) {
+                // Try to find a better voicing with barres or different positions
+                return findAlternativeVoicing(stringOptions, chordNotes, rootNote, numStrings);
+            }
+
+            return voicing;
+        }
+
+        function findAlternativeVoicing(stringOptions, chordNotes, rootNote, numStrings) {
+            // Try different starting frets for barre chords
+            for (let startFret = 1; startFret <= 7; startFret++) {
+                const voicing = new Array(numStrings).fill(null);
+
+                for (let i = 0; i < numStrings; i++) {
+                    const options = stringOptions[i].filter(opt =>
+                        opt.fret === 0 || (opt.fret >= startFret && opt.fret <= startFret + 4)
+                    );
+
+                    if (options.length > 0) {
+                        // Prefer options closer to startFret
+                        const sorted = [...options].sort((a, b) => {
+                            if (a.fret === 0 && b.fret === 0) return 0;
+                            if (a.fret === 0) return 1; // Open strings less preferred in barre position
+                            if (b.fret === 0) return -1;
+                            return Math.abs(a.fret - startFret) - Math.abs(b.fret - startFret);
+                        });
+                        voicing[i] = sorted[0];
+                    }
+                }
+
+                // Validate
+                const presentNotes = new Set(voicing.filter(v => v !== null).map(v => v.note));
+                const hasRoot = voicing.some(v => v !== null && normalizeNote(v.note) === normalizeNote(rootNote));
+                const hasEnoughNotes = presentNotes.size >= Math.min(3, chordNotes.length);
+
+                if (hasRoot && hasEnoughNotes) {
+                    return voicing;
+                }
+            }
+
+            // Fallback: return whatever we can find
+            const voicing = new Array(numStrings).fill(null);
+            for (let i = 0; i < numStrings; i++) {
+                if (stringOptions[i].length > 0) {
+                    voicing[i] = stringOptions[i][0];
+                }
+            }
+            return voicing;
+        }
+
+        function assignFingers(positions) {
+            // Assign finger numbers based on fret positions
+            const fretted = positions.filter(p => p.fret > 0).sort((a, b) => a.fret - b.fret);
+
+            if (fretted.length === 0) return;
+
+            // Group by fret (for barres)
+            const fretGroups = {};
+            fretted.forEach(p => {
+                if (!fretGroups[p.fret]) fretGroups[p.fret] = [];
+                fretGroups[p.fret].push(p);
+            });
+
+            let fingerNum = 1;
+            const sortedFrets = Object.keys(fretGroups).map(Number).sort((a, b) => a - b);
+
+            sortedFrets.forEach(fret => {
+                const group = fretGroups[fret];
+                if (group.length > 1) {
+                    // Potential barre - use same finger for all
+                    group.forEach(p => p.finger = fingerNum);
+                } else {
+                    group[0].finger = fingerNum;
+                }
+                fingerNum++;
+                if (fingerNum > 4) fingerNum = 4; // Max 4 fingers
+            });
+        }
+
+        function detectBarre(positions) {
+            // Detect if there's a barre (same fret on multiple strings)
+            const fretGroups = {};
+            positions.forEach((p, idx) => {
+                if (p.fret > 0) {
+                    if (!fretGroups[p.fret]) fretGroups[p.fret] = [];
+                    fretGroups[p.fret].push(idx);
+                }
+            });
+
+            for (const [fret, indices] of Object.entries(fretGroups)) {
+                if (indices.length >= 2) {
+                    const sortedIndices = indices.sort((a, b) => a - b);
+                    const isConsecutive = sortedIndices.every((val, idx) =>
+                        idx === 0 || val - sortedIndices[idx - 1] <= 2
+                    );
+
+                    if (isConsecutive) {
+                        return {
+                            fret: parseInt(fret),
+                            fromString: Math.min(...sortedIndices),
+                            toString: Math.max(...sortedIndices)
+                        };
+                    }
+                }
+            }
+
+            return null;
+        }
+
+        // Generate SVG chord diagram
+        function generateChordDiagramSVG(voicing, chordName, tuningName, fingerNotation = 'numbers') {
+            const width = 160;
+            const height = 200;
+            const marginLeft = 30;
+            const marginTop = 40;
+            const marginRight = 20;
+            const marginBottom = 30;
+
+            const positions = voicing.positions;
+            const numStrings = positions.length;
+            const numFrets = 5;
+            const baseFret = voicing.baseFret;
+
+            const stringSpacing = (width - marginLeft - marginRight) / (numStrings - 1);
+            const fretSpacing = (height - marginTop - marginBottom) / numFrets;
+
+            let svg = `<svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" xmlns="http://www.w3.org/2000/svg">`;
+
+            // Background
+            svg += `<rect width="${width}" height="${height}" fill="white"/>`;
+
+            // Nut (thick line at top) - only show if baseFret is 1
+            if (baseFret === 1) {
+                svg += `<rect x="${marginLeft - 5}" y="${marginTop - 3}" width="${(numStrings - 1) * stringSpacing + 10}" height="6" fill="#1a1a1a"/>`;
+            } else {
+                // Show fret number for higher positions
+                svg += `<text x="${marginLeft - 20}" y="${marginTop + fretSpacing / 2}" font-size="12" font-weight="bold" text-anchor="middle">${baseFret}fr</text>`;
+            }
+
+            // Fret lines
+            for (let i = 0; i <= numFrets; i++) {
+                const y = marginTop + i * fretSpacing;
+                svg += `<line x1="${marginLeft}" y1="${y}" x2="${marginLeft + (numStrings - 1) * stringSpacing}" y2="${y}" stroke="#333" stroke-width="${i === 0 ? 2 : 1}"/>`;
+            }
+
+            // String lines
+            for (let i = 0; i < numStrings; i++) {
+                const x = marginLeft + i * stringSpacing;
+                svg += `<line x1="${x}" y1="${marginTop}" x2="${x}" y2="${marginTop + numFrets * fretSpacing}" stroke="#333" stroke-width="1.5"/>`;
+            }
+
+            // Open/muted string indicators and finger positions
+            positions.forEach((pos, idx) => {
+                // Reverse index for display (5th string on left)
+                const displayIdx = numStrings - 1 - idx;
+                const x = marginLeft + displayIdx * stringSpacing;
+
+                if (pos.muted) {
+                    // X for muted string
+                    svg += `<text x="${x}" y="${marginTop - 12}" font-size="14" font-weight="bold" text-anchor="middle" fill="#666">✕</text>`;
+                } else if (pos.fret === 0) {
+                    // Open string circle
+                    svg += `<circle cx="${x}" cy="${marginTop - 15}" r="6" fill="none" stroke="#333" stroke-width="2"/>`;
+                } else {
+                    // Fretted position - adjust for base fret
+                    const displayFret = pos.fret - baseFret + 1;
+                    if (displayFret >= 1 && displayFret <= numFrets) {
+                        const y = marginTop + (displayFret - 0.5) * fretSpacing;
+
+                        // Root notes get filled circle
+                        if (pos.isRoot) {
+                            svg += `<circle cx="${x}" cy="${y}" r="10" fill="#1a1a1a" stroke="#1a1a1a" stroke-width="2"/>`;
+                            // Finger number in white
+                            if (pos.finger) {
+                                const fingerLabel = FINGER_NOTATION[fingerNotation][pos.finger] || pos.finger;
+                                svg += `<text x="${x}" y="${y + 4}" font-size="11" font-weight="bold" text-anchor="middle" fill="white">${fingerLabel}</text>`;
+                            }
+                        } else {
+                            svg += `<circle cx="${x}" cy="${y}" r="10" fill="white" stroke="#1a1a1a" stroke-width="2"/>`;
+                            // Finger number
+                            if (pos.finger) {
+                                const fingerLabel = FINGER_NOTATION[fingerNotation][pos.finger] || pos.finger;
+                                svg += `<text x="${x}" y="${y + 4}" font-size="11" font-weight="bold" text-anchor="middle">${fingerLabel}</text>`;
+                            }
+                        }
+                    }
+                }
+            });
+
+            // Barre indicator (arc connecting notes on same fret)
+            if (voicing.barre) {
+                const barre = voicing.barre;
+                const displayFret = barre.fret - baseFret + 1;
+                if (displayFret >= 1 && displayFret <= numFrets) {
+                    const y = marginTop + (displayFret - 0.5) * fretSpacing;
+                    const x1 = marginLeft + (numStrings - 1 - barre.toString) * stringSpacing;
+                    const x2 = marginLeft + (numStrings - 1 - barre.fromString) * stringSpacing;
+
+                    // Draw barre as a rounded rectangle
+                    svg += `<rect x="${Math.min(x1, x2) - 10}" y="${y - 8}" width="${Math.abs(x2 - x1) + 20}" height="16" rx="8" fill="#1a1a1a" opacity="0.8"/>`;
+                }
+            }
+
+            // Degree labels below strings
+            positions.forEach((pos, idx) => {
+                const displayIdx = numStrings - 1 - idx;
+                const x = marginLeft + displayIdx * stringSpacing;
+                const y = marginTop + numFrets * fretSpacing + 18;
+
+                if (pos.degree && !pos.muted) {
+                    svg += `<text x="${x}" y="${y}" font-size="10" text-anchor="middle" fill="#666">${pos.degree}</text>`;
+                } else if (pos.muted) {
+                    svg += `<text x="${x}" y="${y}" font-size="10" text-anchor="middle" fill="#999">-</text>`;
+                }
+            });
+
+            svg += '</svg>';
+            return svg;
+        }
+
         // ============================================
         // TUNINGS DATA (24 tunings including 4-string)
         // ============================================
@@ -1414,7 +2044,12 @@
             showNotes: true,
             selectedFretboardTuning: 'Open G',
             fretboardFamilyFilter: 'all',
-            useSolfege: false
+            useSolfege: false,
+            // Chord tab state
+            chordTuning: 'Open G',
+            chordKey: 'G',
+            chordTypeFilter: 'all',
+            fingerNotation: 'numbers'
         };
 
         function loadState() {
@@ -1978,13 +2613,18 @@
                 showNotes: true,
                 selectedFretboardTuning: 'Open G',
                 fretboardFamilyFilter: 'all',
-                useSolfege: false
+                useSolfege: false,
+                chordTuning: 'Open G',
+                chordKey: 'G',
+                chordTypeFilter: 'all',
+                fingerNotation: 'numbers'
             };
             saveState();
             applyStateToUI();
             populateTuningList();
             renderTunings();
             renderFretboard();
+            initChordTab();
         }
 
         function applyStateToUI() {
@@ -1994,6 +2634,181 @@
             document.getElementById('showNotesToggle').checked = state.showNotes;
             document.getElementById('solfegeToggle').checked = state.useSolfege || false;
             document.getElementById('fretboardFamilyFilter').value = state.fretboardFamilyFilter || 'all';
+
+            // Chord tab UI
+            const chordTuningSelect = document.getElementById('chordTuningSelect');
+            if (chordTuningSelect) {
+                chordTuningSelect.value = state.chordTuning || 'Open G';
+            }
+
+            // Finger notation radio
+            const fingerRadio = document.querySelector(`input[name="fingerNotation"][value="${state.fingerNotation || 'numbers'}"]`);
+            if (fingerRadio) {
+                fingerRadio.checked = true;
+            }
+        }
+
+        // ============================================
+        // CHORD TAB FUNCTIONS
+        // ============================================
+        function populateChordTuningSelect() {
+            const select = document.getElementById('chordTuningSelect');
+            if (!select) return;
+
+            select.innerHTML = '';
+
+            // Group tunings by family
+            const families = {
+                'g-family': 'G-Based',
+                'c-family': 'C-Based',
+                'd-family': 'D-Based',
+                'other-family': 'Other (4-String)'
+            };
+
+            for (const [familyKey, familyName] of Object.entries(families)) {
+                const group = document.createElement('optgroup');
+                group.label = familyName;
+
+                for (const [name, data] of Object.entries(TUNINGS_DATA)) {
+                    if (data.family === familyKey) {
+                        const option = document.createElement('option');
+                        option.value = name;
+                        option.textContent = name;
+                        if (name === state.chordTuning) {
+                            option.selected = true;
+                        }
+                        group.appendChild(option);
+                    }
+                }
+
+                select.appendChild(group);
+            }
+        }
+
+        function populateKeyButtons() {
+            const container = document.getElementById('keyButtons');
+            if (!container) return;
+
+            container.innerHTML = CHORD_KEYS.map(key => {
+                const isActive = state.chordKey === key.root ? 'active' : '';
+                return `<button class="key-btn ${isActive}" data-key="${key.root}">${key.display}</button>`;
+            }).join('');
+
+            // Add click handlers
+            container.querySelectorAll('.key-btn').forEach(btn => {
+                btn.addEventListener('click', () => {
+                    state.chordKey = btn.dataset.key;
+                    saveState();
+                    populateKeyButtons();
+                    renderChords();
+                });
+            });
+        }
+
+        function populateChordTypeButtons() {
+            const container = document.getElementById('chordTypeButtons');
+            if (!container) return;
+
+            const typeEntries = Object.entries(CHORD_TYPES);
+            container.innerHTML = `
+                <button class="chord-type-btn ${state.chordTypeFilter === 'all' ? 'active' : ''}" data-type="all">All</button>
+                ${typeEntries.map(([key, type]) => {
+                    const isActive = state.chordTypeFilter === key ? 'active' : '';
+                    return `<button class="chord-type-btn ${isActive}" data-type="${key}">${type.name}</button>`;
+                }).join('')}
+            `;
+
+            // Add click handlers
+            container.querySelectorAll('.chord-type-btn').forEach(btn => {
+                btn.addEventListener('click', () => {
+                    state.chordTypeFilter = btn.dataset.type;
+                    saveState();
+                    populateChordTypeButtons();
+                    renderChords();
+                });
+            });
+        }
+
+        function renderChords() {
+            const container = document.getElementById('chordContainer');
+            if (!container) return;
+
+            const tuningData = TUNINGS_DATA[state.chordTuning];
+            if (!tuningData) {
+                container.innerHTML = '<div class="no-chord-message">Select a tuning to view chords</div>';
+                return;
+            }
+
+            const fingerNotation = document.querySelector('input[name="fingerNotation"]:checked')?.value || 'numbers';
+
+            // Determine which chord types to show
+            const typesToShow = state.chordTypeFilter === 'all'
+                ? Object.keys(CHORD_TYPES)
+                : [state.chordTypeFilter];
+
+            let html = '';
+
+            typesToShow.forEach(chordType => {
+                const type = CHORD_TYPES[chordType];
+                const chordName = `${state.chordKey}${type.symbol}`;
+
+                // Find voicing for this chord
+                const voicing = findChordVoicing(tuningData, state.chordKey, chordType, 12);
+
+                if (voicing) {
+                    const svg = generateChordDiagramSVG(voicing, chordName, state.chordTuning, fingerNotation);
+
+                    html += `
+                        <div class="chord-diagram-container">
+                            <div class="chord-diagram-title">${displayNote(state.chordKey)}${type.symbol}</div>
+                            <div class="chord-diagram-subtitle">${type.name}</div>
+                            ${svg}
+                            <div class="chord-notes-display">
+                                Notes: ${voicing.chordNotes.join(' - ')}
+                            </div>
+                        </div>
+                    `;
+                } else {
+                    html += `
+                        <div class="chord-diagram-container">
+                            <div class="chord-diagram-title">${displayNote(state.chordKey)}${type.symbol}</div>
+                            <div class="chord-diagram-subtitle">${type.name}</div>
+                            <div class="no-chord-message">Voicing not available for this tuning</div>
+                        </div>
+                    `;
+                }
+            });
+
+            container.innerHTML = html;
+        }
+
+        function setupChordEventListeners() {
+            // Tuning selector
+            const tuningSelect = document.getElementById('chordTuningSelect');
+            if (tuningSelect) {
+                tuningSelect.addEventListener('change', (e) => {
+                    state.chordTuning = e.target.value;
+                    saveState();
+                    renderChords();
+                });
+            }
+
+            // Finger notation toggle
+            document.querySelectorAll('input[name="fingerNotation"]').forEach(radio => {
+                radio.addEventListener('change', () => {
+                    state.fingerNotation = document.querySelector('input[name="fingerNotation"]:checked')?.value || 'numbers';
+                    saveState();
+                    renderChords();
+                });
+            });
+        }
+
+        function initChordTab() {
+            populateChordTuningSelect();
+            populateKeyButtons();
+            populateChordTypeButtons();
+            setupChordEventListeners();
+            renderChords();
         }
 
         // ============================================
@@ -2006,6 +2821,7 @@
             setupEventListeners();
             renderTunings();
             renderFretboard();
+            initChordTab();
         }
 
         document.addEventListener('DOMContentLoaded', init);