npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

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

About

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

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

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

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

Open Software & Tools

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

© 2026 – Pkg Stats / Ryan Hefner

chordpro-core

v0.4.0

Published

Dependency-free TypeScript reference implementation of the ChordPro format

Downloads

1,263

Readme

chordpro-core

A standalone, dependency-free TypeScript reference implementation of ChordPro parsing, the data model, and transposition — plus a set of clearly-separated auxiliary helpers that applications commonly need but the format specification deliberately leaves out.

Try it live → — interactive playground, all features in the browser.

Guiding policy: Recognize and round-trip the entire format; implement semantics only for the well-defined subset; never silently drop anything.

What this is

A reference implementation of parsing, the data model, and transposition for ChordPro 6. It turns text into data and data back into text. It contains no UI code, no framework imports, no DOM access, no CSS — those belong in your application.

The rendering helpers (renderText, renderHtml) produce a rendering, not the rendering. The ChordPro spec explicitly leaves font/layout decisions to the formatter; this library does not attempt to match the reference program's PDF output.

Tier 1 — Format core (the reference implementation)

Strictly what the public ChordPro specification defines: the line model, every directive, the chord grammar (strict/relaxed), annotations, environments, and transposition. Correctness is measured against the spec.

Tier 2 — Auxiliary helpers (NOT part of the format)

Clearly labelled utilities real applications need but the spec does not cover: parseFreeText, tokenize, guessKey, getChordShape, toNashville/fromNashville.


Interactive playground

jcthalys.github.io/chordpro-core/ — try every public API function live in the browser:

  • RenderedrenderHtml output with chord-over-lyrics layout, styled by the playground's own CSS (demonstrating the library's class-name design)
  • TextrenderText monospace output
  • AST — the full parsed Song as JSON
  • Tokenstokenize output with color-coded token types and a legend
  • Round-tripserialize(parse(x)) side-by-side with the original, with content-preservation indicators
  • Chord inspectorparseChord output + getChordShape diagram for any chord you type
  • Free-textparseFreeText converts loose chord sheets to canonical ChordPro, with metadata pills
  • DirectivesKNOWN_DIRECTIVES rendered as a reference table

Run locally:

# From the repo root — builds the library first, then starts the playground dev server
npm run playground

Install

npm install chordpro-core

Zero runtime dependencies. Requires Node 18+ or any modern bundler.


Quick start

import { parse, transpose, tokenize, renderHtml, parseFreeText } from 'chordpro-core';

// Parse a ChordPro file
const song = parse(`
{title: Amazing Grace}
{key: G}
{start_of_verse}
[G]Amazing [G7]grace, how [C]sweet the [G]sound
{end_of_verse}
`);

console.log(song.metadata.get('title')); // "Amazing Grace"

// Transpose up 2 semitones
const transposed = transpose(song, 2);

// Render to HTML
const html = renderHtml(transposed);

// Tokenize for syntax highlighting
const tokens = tokenize(source);
// Each token has: type, text, start, end — tiles the source exactly.

// Convert loose human-written text to ChordPro
const { chordpro, metadata, warnings } = parseFreeText(`
Amazing Grace
Traditional

G     D     Em    C
Amazing grace, how sweet the sound
`);

API

Tier 1 — Format core

parse(source, options?): Song

Parse ChordPro text. Never throws; records Warning[] and recovers.

interface ParseOptions {
  chordMode?: 'strict' | 'relaxed'; // default: 'strict'
  onUnknownDirective?: 'warn' | 'ignore'; // default: 'warn'
}

serialize(song): string

Serialize a Song back to ChordPro source. Round-trips all content faithfully: original directive spellings, chord names, comments, metadata order, and the entire preserve-only tail (legacy font/colour directives, delegated blocks, etc.).

parseChord(name, options?): Chord

Parse a single chord name. Never throws. Returns parsed: false on strict failure.

interface ChordParseOptions {
  mode?: 'strict' | 'relaxed';
}

transpose(song, semitones, options?): Song

Return a new Song with all chords transposed. Original song is not mutated.

transposeChord(chord, semitones, options?): Chord

Transpose a single chord. Unparsed chords pass through unchanged.

interface TransposeOptions {
  preferSharps?: boolean;
  preferFlats?: boolean;
}

soundingKey(song): string | null

Returns the key the song sounds in after capo transposition. Reads {key} and {capo} from song.metadata and transposes up by capo frets. Returns null when no {key} directive is present. If capo is 0 or absent, returns the key unchanged. Accidental style follows the written key (flat keys stay flat-preferred):

soundingKey(parse('{key: G}\n{capo: 2}'))  // → "A"
soundingKey(parse('{key: Am}\n{capo: 3}')) // → "Cm"
soundingKey(parse('{key: Bb}\n{capo: 1}')) // → "B"
soundingKey(parse('{capo: 2}'))            // → null  (no key)

soundingKeyOf(key, capo): string

Lower-level helper for consumers who have key and capo as separate strings/numbers. Transposes key (e.g. "G", "Am", "Bb") up by capo semitones. Preserves qualifier (m, dim, etc.) and derives accidental preference from the written key.

soundingKeyOf('G', 2)   // → "A"
soundingKeyOf('Am', 3)  // → "Cm"
soundingKeyOf('Eb', 2)  // → "F"

renderText(song, options?): string

Chords-over-lyrics monospace text. Tab blocks verbatim.

renderHtml(song, options?): string

Semantic HTML string with class-annotated elements. No inline styles. No colors. Your application maps token types to its own theme.

Class names: .cp-song .cp-header .cp-title .cp-artist .cp-key .cp-section .cp-section--chorus .cp-section--verse .cp-section--bridge .cp-lyric-line .cp-chord .cp-annotation .cp-lyric .cp-comment .cp-tab .cp-grid .cp-blank .cp-chorus-ref

DIRECTIVE_ALIASES: Readonly<Record<string,string>>

Alias → canonical name map. Use to build a data-driven directive toolbar.

KNOWN_DIRECTIVES: ReadonlyArray<DirectiveInfo>

All recognized directives with name, aliases, category, and takesArg. Use to avoid keeping a hand-copy of the directive list in your app.


Tier 2 — Auxiliary helpers (not part of the format)

parseFreeText(input): ParseFreeTextResult

Convert loose human-written text to canonical ChordPro.

Detection rules (in order):

  1. First content line → title
  2. Second content line → artist
  3. Metadata lines (see table below) → corresponding ChordPro directives
  4. Section headings (see table below) → start_of_*/end_of_* pairs
  5. Chord-above-lyrics pairs — merged into inline [C]word tokens using Unicode-aware column alignment
  6. Everything else → lyric content

Repeat markers ((x2), (2x), (3 vezes), …) are detected:

  • On a section heading line → {meta: repeat N} emitted first inside the section
  • On a standalone line → {comment: (xN)}
  • At the end of a lyric → [*xN] annotation appended

Metadata lines recognized (all case-insensitive, Portuguese and English):

| Input line | Directive emitted | |---|---| | Tom: G / Key: G | {key: G} | | Tom: A (Capo 2) | {key: A} + {capo: 2} | | Tom com Capo 2: G | {key: G} + {capo: 2} | | Tom (Capo 2): G | {key: G} + {capo: 2} | | Tom real: A | {meta: tom_real A} | | Capo: 2 / Capo 2 | {capo: 2} | | Afinação: meio tom abaixo | {meta: afinacao meio tom abaixo} | | BPM: 76 / Tempo: 76 | {tempo: 76} | | Andamento: 120 | {tempo: 120} | | Andamento: Moderato | {meta: andamento Moderato} | | Ritmo: Baião | {meta: ritmo Baião} | | Compasso: 3/4 | {time: 3/4} | | Compasso: Binário | {meta: compasso Binário} | | Fórmula de compasso: 4/4 | {time: 4/4} | | Artista: X | {artist: X} | | Título: X / Titulo: X | {title: X} | | Álbum: X / Album: X | {album: X} | | Ano: 1994 | {year: 1994} | | Compositor: X / Composição: X | {composer: X} | | Letrista: X | {lyricist: X} | | Copyright: X | {copyright: X} | | CCLI: N | {ccli: N} |

Section headings recognized:

| Heading | Env | Notes | |---|---|---| | Verse N / Verso N / Estrofe N | verse | numbered or bare | | Chorus / Coro / Refrão / Refrao | chorus | | | Pre-Chorus / Pré-Refrão / Pre-Refrao / Pré-Coro / Pre-Coro | prechorus | | | Bridge / Ponte | bridge | | | Intro / Introdução / Introducao / Abertura | verse | with label | | Outro / Final / Finalização / Finalizacao / Coda | verse | with label | | Solo N / Instrumental / Riff N / Interlúdio N / Interludio N | verse | with label |

All headings accept optional [brackets], optional trailing colon, optional number suffix, and optional (xN) repeat count.

English and Portuguese headings are supported.

interface ParseFreeTextResult {
  chordpro: string;
  metadata: Map<string, string>;
  warnings: Warning[];
}

tokenize(source): Token[]

Typed tokens for editor syntax highlighting. Offsets tile the source exactly (concatenating text in order reproduces the input).

interface Token {
  type: 'metadata-directive' | 'section-open' | 'section-end'
      | 'directive' | 'chord' | 'annotation' | 'comment' | 'lyric';
  text: string;
  start: number;
  end: number;
}

No colors, no class names — your app maps token type to its own theme.

guessKey(input): KeyGuess | null

Diatonic coverage scoring across all 24 candidate keys (12 major + 12 minor). Returns the key whose scale best covers the chord roots in the song, weighted by tonic and dominant frequency. Returns null when fewer than 2 total chord occurrences are found. Minor keys include the qualifier: "Am" not "A".

interface KeyGuess { key: string; confidence: number }
// key examples: "G", "C", "Am", "Em", "Bb", "F#"

getChordShape(name, instrument, song?): DiagramData | null

Returns chord fingering data (no SVG, no drawing). Lookup priority:

  1. {define} / {define-guitar} / {define-ukulele} nodes in the song (instrument-selector respected)
  2. Built-in table: 110+ guitar shapes and 55+ ukulele shapes covering natural and accidental major/minor, dominant 7th, major 7th, minor 7th, suspended, add9, 9th, diminished, augmented, 6th, and power chords
  3. Algorithmic barre fallback (guitar only): any simple major/minor chord not in the table is derived from an E-shape or A-shape moveable barre pattern
type instrument = 'guitar' | 'ukulele'
interface DiagramData { baseFret: number; frets: number[]; fingers?: number[] }

toInline(song): Song

Convert a song from chords-above-lyrics format to inline [G]word format. Each chord-only line is merged with the immediately following lyric line using Unicode-aware column matching. Lines that already have inline chords, and all non-lyric lines, pass through unchanged.

toAbove(song): Song

Convert a song from inline [G]word format to chords-above-lyrics format. Each lyric line with inline chords is split into a chord-only line above and a lyric-only line below. Column widths follow the same formula as renderText. Lines that are already chord-only or have no chords pass through unchanged.

Both functions are pure transforms (return new Song, do not mutate input) and are idempotent. These are display-transform helpers — use them to switch the visual format for rendering without modifying the stored content:

// Display toggle — does not modify stored content
const display = showAbove ? toAbove(song) : toInline(song)

toSimpleText(song, options?): string

Convert a Song to natural human-readable plain text with no ChordPro directive syntax. Intended for display contexts where the user should see lyrics without markup, or as the inverse of parseFreeText.

  • Metadata emitted as natural-text lines: title and artist bare, others as Key: G, Capo: 2, BPM: 120, Composer: X, Lyricist: X, etc.
  • {meta: K V} pairs emitted as K: V
  • Sections emitted as plain headings (Verse 1, Chorus, Pre-Chorus, …) followed by content, separated by blank lines
  • options.chords: 'above' (default) or 'inline'
    • 'above': chord names on their own line without brackets — compatible with parseFreeText on re-import
    • 'inline': [G]word inline notation

Round-trip: parseFreeText(toSimpleText(song)) reconstructs the same metadata and section structure as the original song.

toDirectiveText(song, options?): string

Convert a Song to standard ChordPro with directive syntax. Equivalent to serialize() with the chord mode applied. options.chords controls the chord display format (default 'above').

toRawText(song, options?): string

Full-fidelity ChordPro serialization with chord mode applied. toRawText(song, { chords: 'inline' })serialize(song) for a song with inline chords.

interface ConverterOptions {
  chords?: 'above' | 'inline'; // default: 'above'
}

// Natural text — no directives
const sheet = toSimpleText(song);                        // chords above (default)
const sheet = toSimpleText(song, { chords: 'inline' }); // [G]word inline

// ChordPro with directives
const cp = toDirectiveText(song, { chords: 'inline' }); // ≈ serialize(song)

// Round-trip through parseFreeText
const { metadata, chordpro } = parseFreeText(toSimpleText(song));

toNashville(song, key) / fromNashville(song, key)

Nashville Number System conversion. Replaces chord roots with scale degrees (1–7) relative to the given key. Chromatic roots use a flat prefix: F in G → b7, Bb in G → b3. Qualifiers, extensions, and bass notes are preserved: Am7 in C → 6m7. fromNashville(toNashville(song, key), key) is identity on all diatonic chord names.


Supported directives

See FORMAT.md for the full list. All directives are parsed and preserved. The KNOWN_DIRECTIVES export gives you the list programmatically.

Strict vs relaxed chord modes

Strict (default): extension must be in the built-in table (e.g. Am7b5 ✓, Amystery ✗).

Relaxed: any trailing text after a recognized root and optional qualifier is accepted. Used by parseFreeText internally.

Free-text parser rules

See the parseFreeText section above. The output is guaranteed to re-parse with parse() with zero unknown-directive warnings for well-formed input.

Non-goals (v1)

  • PDF/page layout, fonts, actual chord-diagram image drawing
  • The reference program's config-file system
  • Rendering semantics for delegated ABC/Lilypond/SVG environments (preserve only)
  • Any UI component, editor widget, or styling

Contributing

Contributions welcome. Keep the Tier 1 / Tier 2 boundary clear; add tests for any new behavior; maintain the zero-runtime-dependencies rule.

License

MIT