Music

Guitar Chords

Interactive chord player with Web Audio synthesis and famous progressions. Press Space to strum.

Root Note
Chord Type
Available Chords
Select a chord to begin

Space / Enter = strum down  ·  Shift+Space = strum up  ·  ← → = prev/next chord

Keyboard Shortcuts
C D E F G A B — select & strum major chord
Shift + C/D/E… — select & strum minor chord (Cm, Dm…)
1 2 3 4 5 6 — pluck string 1–6 (low E → high e)
Space / Enter — strum current chord (down)
Shift + Space — strum current chord (up)
← → — navigate to prev/next chord
Developer Reference

Core Algorithm & Standalone Script

Standalone, zero-dependency JavaScript implementation powering this tool. Free to inspect, copy, and build upon.

// Web Audio Context & Karplus-Strong Synthesis
    let audioCtx = null;

    function getCtx() {
      if (!audioCtx) {
        audioCtx = new (window.AudioContext || window.webkitAudioContext)();
      }
      if (audioCtx.state === 'suspended') {
        audioCtx.resume();
      }
      return audioCtx;
    }

    function pluckString(freq, vol, startTime) {
      const ctx = getCtx();
      const t = startTime != null ? startTime : ctx.currentTime;
      const dur = 2.0;
      const N = Math.round(ctx.sampleRate / freq);
      const buf = ctx.createBuffer(1, N, ctx.sampleRate);
      const data = buf.getChannelData(0);
      for (let i = 0; i < N; i++) {
        data[i] = Math.random() * 2 - 1;
      }
      const src = ctx.createBufferSource();
      src.buffer = buf;
      src.loop = true;
      const filt = ctx.createBiquadFilter();
      filt.type = 'lowpass';
      filt.frequency.value = freq * 4;
      filt.Q.value = 15;
      const gain = ctx.createGain();
      gain.gain.setValueAtTime(vol, t);
      gain.gain.exponentialRampToValueAtTime(0.001, t + dur);
      src.connect(filt);
      filt.connect(gain);
      gain.connect(ctx.destination);
      src.start(t);
      src.stop(t + dur);
    }

    function strumChord(frets, direction) {
      const baseFreqs = [82.41, 110.0, 146.83, 196.0, 246.94, 329.63];
      const S = Math.pow(2, 1 / 12);
      const order = direction === 'up' ? [5, 4, 3, 2, 1, 0] : [0, 1, 2, 3, 4, 5];
      const ctx = getCtx();
      order.forEach((si, i) => {
        if (frets[si] < 0) return;
        const freq = baseFreqs[si] * Math.pow(S, frets[si]);
        pluckString(freq, 0.4, ctx.currentTime + i * 0.022);
      });
    }

    // Chord Data
    const CHORDS = [
      { id: 'C', root: 'C', type: 'major', name: 'C Major', frets: [-1, 3, 2, 0, 1, 0] },
      { id: 'D', root: 'D', type: 'major', name: 'D Major', frets: [-1, -1, 0, 2, 3, 2] },
      { id: 'E', root: 'E', type: 'major', name: 'E Major', frets: [0, 2, 2, 1, 0, 0] },
      { id: 'F', root: 'F', type: 'major', name: 'F Major', frets: [1, 1, 2, 3, 3, 1] },
      { id: 'G', root: 'G', type: 'major', name: 'G Major', frets: [3, 2, 0, 0, 0, 3] },
      { id: 'A', root: 'A', type: 'major', name: 'A Major', frets: [-1, 0, 2, 2, 2, 0] },
      { id: 'B', root: 'B', type: 'major', name: 'B Major', frets: [-1, 2, 4, 4, 4, 2] },
      { id: 'Am', root: 'A', type: 'minor', name: 'A Minor', frets: [-1, 0, 2, 2, 1, 0] },
      { id: 'Bm', root: 'B', type: 'minor', name: 'B Minor', frets: [-1, 2, 4, 4, 3, 2] },
      { id: 'Cm', root: 'C', type: 'minor', name: 'C Minor', frets: [-1, 3, 5, 5, 4, 3] },
      { id: 'Dm', root: 'D', type: 'minor', name: 'D Minor', frets: [-1, -1, 0, 2, 3, 1] },
      { id: 'Em', root: 'E', type: 'minor', name: 'E Minor', frets: [0, 2, 2, 0, 0, 0] },
      { id: 'Fm', root: 'F', type: 'minor', name: 'F Minor', frets: [1, 1, 3, 3, 2, 1] },
      { id: 'Gm', root: 'G', type: 'minor', name: 'G Minor', frets: [3, 5, 5, 3, 3, 3] },
      { id: 'A7', root: 'A', type: '7th', name: 'A7', frets: [-1, 0, 2, 0, 2, 0] },
      { id: 'B7', root: 'B', type: '7th', name: 'B7', frets: [-1, 2, 1, 2, 0, 2] },
      { id: 'C7', root: 'C', type: '7th', name: 'C7', frets: [-1, 3, 2, 3, 1, 0] },
      { id: 'D7', root: 'D', type: '7th', name: 'D7', frets: [-1, -1, 0, 2, 1, 2] },
      { id: 'E7', root: 'E', type: '7th', name: 'E7', frets: [0, 2, 0, 1, 0, 0] },
      { id: 'G7', root: 'G', type: '7th', name: 'G7', frets: [3, 2, 0, 0, 0, 1] },
      { id: 'Cmaj7', root: 'C', type: 'maj7', name: 'C Maj7', frets: [-1, 3, 2, 0, 0, 0] },
      { id: 'Emaj7', root: 'E', type: 'maj7', name: 'E Maj7', frets: [0, 2, 1, 1, 0, 0] },
      { id: 'Gmaj7', root: 'G', type: 'maj7', name: 'G Maj7', frets: [3, 2, 0, 0, 0, 2] },
      { id: 'Amaj7', root: 'A', type: 'maj7', name: 'A Maj7', frets: [-1, 0, 2, 1, 2, 0] },
      { id: 'Am7', root: 'A', type: 'min7', name: 'A Min7', frets: [-1, 0, 2, 0, 1, 0] },
      { id: 'Dm7', root: 'D', type: 'min7', name: 'D Min7', frets: [-1, -1, 0, 2, 1, 1] },
      { id: 'Em7', root: 'E', type: 'min7', name: 'E Min7', frets: [0, 2, 2, 0, 3, 0] },
      { id: 'Asus2', root: 'A', type: 'sus', name: 'A Sus2', frets: [-1, 0, 2, 2, 0, 0] },
      { id: 'Dsus4', root: 'D', type: 'sus', name: 'D Sus4', frets: [-1, -1, 0, 2, 3, 3] },
      { id: 'E5', root: 'E', type: 'power', name: 'E Power', frets: [0, 2, 2, -1, -1, -1] },
      { id: 'A5', root: 'A', type: 'power', name: 'A Power', frets: [-1, 0, 2, 2, -1, -1] },
    ];

    // Progressions Data
    const PROGRESSIONS = [
      { name: 'I–IV–V–I (C Major)', chords: ['C', 'F', 'G', 'C'], bpm: 80 },
      { name: 'I–V–vi–IV (G Major)', chords: ['G', 'D', 'Am', 'C'], bpm: 80 },
      { name: '12-Bar Blues (E)', chords: ['E', 'E', 'E', 'E', 'A', 'A', 'E', 'E', 'B7', 'A', 'E', 'B7'], bpm: 80 },
      { name: 'I–vi–IV–V (50s Pop)', chords: ['C', 'Am', 'F', 'G'], bpm: 90 },
      { name: 'ii–V–I (Jazz)', chords: ['Dm7', 'G7', 'Cmaj7', 'Cmaj7'], bpm: 100 },
      { name: 'vi–IV–I–V (Modern)', chords: ['Am', 'F', 'C', 'G'], bpm: 85 },
      { name: 'I–IV–vi–V (Country)', chords: ['G', 'C', 'Em', 'D'], bpm: 90 },
      { name: 'i–VII–VI–VII (Minor Rock)', chords: ['Am', 'G', 'F', 'G'], bpm: 85 },
      { name: 'I–bVII–IV (Power Rock)', chords: ['E', 'D', 'A', 'A'], bpm: 105 },
      { name: 'i–iv–V–i (Flamenco)', chords: ['Am', 'Dm', 'E7', 'Am'], bpm: 95 },
      { name: 'Andalusian Cadence', chords: ['Am', 'G', 'F', 'E'], bpm: 90 },
      { name: 'Folk (G–C–D)', chords: ['G', 'C', 'D', 'G'], bpm: 95 },
      { name: 'Power Chord Groove', chords: ['E5', 'A5', 'E5', 'A5'], bpm: 120 },
      { name: 'Canon Progression', chords: ['D', 'A', 'Bm', 'Fm'], bpm: 72 },
      { name: 'I–IV–I–V (Blues Basics)', chords: ['A', 'D', 'A', 'E7'], bpm: 75 },
    ];

    // State
    let selectedChordId = 'C';
    let strumDirection = 'down';
    let filterRoot = 'all';
    let filterType = 'all';
    let currentProgressionPlaying = null;

    // Initialize
    function init() {
      renderChordGrid();
      renderProgressions();
      setupEventListeners();
      drawChordDiagram();
      updateChordInfo();
    }

    function renderChordGrid() {
      const grid = document.getElementById('chordGrid');
      grid.innerHTML = '';
      const filtered = CHORDS.filter(
        (c) => (filterRoot === 'all' || c.root === filterRoot) && (filterType === 'all' || c.type === filterType)
      );
      filtered.forEach((chord) => {
        const card = document.createElement('button');
        card.className = `chord-card ${chord.id === selectedChordId ? 'selected' : ''}`;
        card.textContent = chord.id;
        card.onclick = () => selectChord(chord.id);
        grid.appendChild(card);
      });
    }

    function selectChord(chordId) {
      selectedChordId = chordId;
      renderChordGrid();
      drawChordDiagram();
      updateChordInfo();
    }

    function drawChordDiagram() {
      const chord = CHORDS.find((c) => c.id === selectedChordId);
      if (!chord) return;

      const svg = document.getElementById('chordDiagram');
      svg.innerHTML = '';

      const frets = chord.frets;
      const stringCount = 6;
      const stringSpacing = 16;
      const fretSpacing = 22;
      const startX = 20;
      const startY = 20;

      // Draw strings (vertical lines)
      for (let i = 0; i < stringCount; i++) {
        const x = startX + i * stringSpacing;
        const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
        line.setAttribute('x1', x);
        line.setAttribute('y1', startY);
        line.setAttribute('x2', x);
        line.setAttribute('y2', startY + 4 * fretSpacing + 2);
        line.setAttribute('stroke', '#555');
        line.setAttribute('stroke-width', '1');
        svg.appendChild(line);
      }

      // Draw frets (horizontal lines)
      for (let i = 0; i < 5; i++) {
        const y = startY + i * fretSpacing;
        const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
        line.setAttribute('x1', startX);
        line.setAttribute('y1', y);
        line.setAttribute('x2', startX + (stringCount - 1) * stringSpacing);
        line.setAttribute('y2', y);
        line.setAttribute('stroke', '#333');
        line.setAttribute('stroke-width', i === 0 ? '3' : '1');
        svg.appendChild(line);
      }

      // Draw finger positions and open/muted indicators
      for (let i = 0; i < stringCount; i++) {
        const x = startX + i * stringSpacing;
        const fret = frets[i];

        if (fret === -1) {
          // Muted
          const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
          text.setAttribute('x', x);
          text.setAttribute('y', startY - 5);
          text.setAttribute('text-anchor', 'middle');
          text.setAttribute('font-size', '12');
          text.setAttribute('fill', '#999');
          text.setAttribute('font-weight', 'bold');
          text.textContent = '×';
          svg.appendChild(text);
        } else if (fret === 0) {
          // Open
          const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
          text.setAttribute('x', x);
          text.setAttribute('y', startY - 5);
          text.setAttribute('text-anchor', 'middle');
          text.setAttribute('font-size', '12');
          text.setAttribute('fill', '#999');
          text.setAttribute('font-weight', 'bold');
          text.textContent = 'o';
          svg.appendChild(text);
        } else {
          // Finger position
          const y = startY + (fret - 0.5) * fretSpacing;
          const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
          circle.setAttribute('cx', x);
          circle.setAttribute('cy', y);
          circle.setAttribute('r', '6');
          circle.setAttribute('fill', 'var(--accent)');
          svg.appendChild(circle);
        }
      }

      // Update display name
      document.getElementById('chordDisplayName').textContent = chord.name;
    }

    function updateChordInfo() {
      const chord = CHORDS.find((c) => c.id === selectedChordId);
      if (!chord) return;
      const fretStr = chord.frets.map((f) => (f === -1 ? '·' : f)).join('–');
      document.getElementById(
        'chordDetails'
      ).textContent = `Root: ${chord.root} · Type: ${chord.type} · Frets: ${fretStr}`;
    }

    function renderProgressions() {
      const list = document.getElementById('progressionsList');
      list.innerHTML = '';
      PROGRESSIONS.forEach((prog, idx) => {
        const card = document.createElement('div');
        card.className = 'progression-card';
        const info = document.createElement('div');
        info.className = 'progression-info';
        const name = document.createElement('div');
        name.className = 'progression-name';
        name.textContent = prog.name;
        const chords = document.createElement('div');
        chords.className = 'progression-chords';
        chords.textContent = prog.chords.join(' → ');
        info.appendChild(name);
        info.appendChild(chords);
        const btn = document.createElement('button');
        btn.className = 'btn-play-prog';
        btn.textContent = 'PLAY';
        btn.onclick = () => playProgression(idx, btn);
        card.appendChild(info);
        card.appendChild(btn);
        list.appendChild(card);
      });
    }

    function playProgression(idx, btn) {
      if (currentProgressionPlaying === idx) {
        currentProgressionPlaying = null;
        btn.classList.remove('playing');
        return;
      }
      currentProgressionPlaying = idx;
      btn.classList.add('playing');
      const prog = PROGRESSIONS[idx];
      const beatDur = (60 / prog.bpm) * 4;
      let delay = 0;
      prog.chords.forEach((chordId) => {
        setTimeout(() => {
          if (currentProgressionPlaying !== idx) return;
          const chord = CHORDS.find((c) => c.id === chordId);
          if (chord) strumChord(chord.frets, 'down');
        }, delay * 1000);
        delay += beatDur;
      });
      setTimeout(() => {
        if (currentProgressionPlaying === idx) {
          currentProgressionPlaying = null;
          btn.classList.remove('playing');
        }
      }, delay * 1000);
    }

    function setupEventListeners() {
      // Tab switching
      document.querySelectorAll('.tab').forEach((tab) => {
        tab.addEventListener('click', (e) => {
          const tabName = e.target.dataset.tab;
          document.querySelectorAll('.tab').forEach((t) => t.classList.remove('active'));
          document.querySelectorAll('.tab-content').forEach((tc) => tc.classList.remove('active'));
          e.target.classList.add('active');
          document.getElementById(tabName).classList.add('active');
        });
      });

      // Root filters
      document.querySelectorAll('#rootFilters .btn-filter').forEach((btn) => {
        btn.addEventListener('click', (e) => {
          document.querySelectorAll('#rootFilters .btn-filter').forEach((b) => b.classList.remove('active'));
          e.target.classList.add('active');
          filterRoot = e.target.dataset.root;
          renderChordGrid();
        });
      });

      // Type filters
      document.querySelectorAll('#typeFilters .btn-filter').forEach((btn) => {
        btn.addEventListener('click', (e) => {
          document.querySelectorAll('#typeFilters .btn-filter').forEach((b) => b.classList.remove('active'));
          e.target.classList.add('active');
          filterType = e.target.dataset.type;
          renderChordGrid();
        });
      });

      // Strum direction
      document.querySelectorAll('.btn-dir').forEach((btn) => {
        btn.addEventListener('click', (e) => {
          document.querySelectorAll('.btn-dir').forEach((b) => b.classList.remove('active'));
          e.target.classList.add('active');
          strumDirection = e.target.dataset.direction;
        });
      });

      // Strum button
      document.getElementById('strumBtn').addEventListener('click', () => {
        const chord = CHORDS.find((c) => c.id === selectedChordId);
        if (chord) strumChord(chord.frets, strumDirection);
      });

      // ── Keyboard controls ──────────────────────────────────────────
      // Standard online guitar keyboard layout:
      //   A–G  = select & strum that major chord instantly
      //   1–6  = pluck individual strings (low E → high e)
      //   Space / Enter = strum current chord down
      //   Shift+Space   = strum current chord up
      //   ← →           = prev/next chord in filtered list

      const STRING_KEYS = {'1':0,'2':1,'3':2,'4':3,'5':4,'6':5};
      const BASE_FREQS  = [82.41, 110.0, 146.83, 196.0, 246.94, 329.63];
      const SEMITONE    = Math.pow(2, 1/12);

      function pluckStringN(stringIndex) {
        const chord = CHORDS.find((c) => c.id === selectedChordId);
        if (!chord) return;
        const fret = chord.frets[stringIndex];
        if (fret < 0) return; // muted
        const freq = BASE_FREQS[stringIndex] * Math.pow(SEMITONE, fret);
        pluckString(freq, 0.5, null);
        // flash the strum button briefly as visual feedback
        const btn = document.getElementById('strumBtn');
        btn.style.background = 'var(--success)';
        setTimeout(() => btn.style.background = '', 150);
      }

      document.addEventListener('keydown', (e) => {
        // Ignore when typing in inputs
        if (e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT' || e.target.tagName === 'TEXTAREA') return;

        // 1–6: pluck individual strings
        if (STRING_KEYS.hasOwnProperty(e.key)) {
          e.preventDefault();
          pluckStringN(STRING_KEYS[e.key]);
          return;
        }

        // A–G letter keys: jump to + strum that major chord
        const LETTER_TO_CHORD = { a:'A', b:'B', c:'C', d:'D', e:'E', f:'F', g:'G' };
        const lk = e.key.toLowerCase();
        if (!e.ctrlKey && !e.metaKey && !e.altKey && LETTER_TO_CHORD[lk]) {
          e.preventDefault();
          // Shift+letter = minor (e.g. Shift+A = Am)
          const targetId = e.shiftKey ? LETTER_TO_CHORD[lk] + 'm' : LETTER_TO_CHORD[lk];
          const found = CHORDS.find((c) => c.id === targetId);
          if (found) {
            selectChord(found.id);
            strumChord(found.frets, strumDirection);
          }
          return;
        }

        // Space / Enter: strum current chord
        if (e.code === 'Space' || e.code === 'Enter') {
          e.preventDefault();
          const dir = e.shiftKey ? 'up' : strumDirection;
          const chord = CHORDS.find((c) => c.id === selectedChordId);
          if (chord) strumChord(chord.frets, dir);
          return;
        }

        // Arrow keys: navigate chords
        if (e.code === 'ArrowRight' || e.code === 'ArrowLeft') {
          e.preventDefault();
          const filtered = CHORDS.filter(
            (c) =>
              (filterRoot === 'all' || c.root === filterRoot) &&
              (filterType === 'all' || c.type === filterType)
          );
          const idx = filtered.findIndex((c) => c.id === selectedChordId);
          if (e.code === 'ArrowRight' && idx < filtered.length - 1) selectChord(filtered[idx + 1].id);
          if (e.code === 'ArrowLeft'  && idx > 0)                    selectChord(filtered[idx - 1].id);
        }
      });
    }

    // Start
    init();