A JavaScript library for parsing and formatting chord sheets
Contents
- Installation
- How to ...?
- Try it online
- Supported ChordPro directives
- Project board
- API docs
- Contributing
ChordSheetJS is on npm, to install run:
npm install chordsheetjsLoad with import:
import ChordSheetJS from 'chordsheetjs';or require():
var ChordSheetJS = require('chordsheetjs').default;If you're not using a build tool, you can download and use the bundle.js from the
latest release:
<script src="bundle.js"></script>
<script>
// ChordSheetJS is available in global namespace now
const parser = new ChordSheetJS.ChordProParser();
</script>const chordSheet = `
Am C/G F C
Let it be, let it be, let it be, let it be
C G F C/E Dm C
Whisper words of wisdom, let it be`.substring(1);
const parser = new ChordSheetJS.ChordsOverWordsParser();
const song = parser.parse(chordSheet);ChordSheetJS recognizes non-chord tokens in its chords-over-words dialect. These tokens remain in the chord row,
but are not transposed, normalized, included in Song#getChords, or used for chord diagrams:
/,x, and-are rhythm symbols.|,||,|.,|:,:|, and:|:are barlines.:||is supported as a ChordSheetJS repeat-barline extension.- Repeat counts such as
(2x)and(6x)are instructions. N.C.,N.C,N/C, andNCare no-chord markers.
For example, D2 / / / :|| (6x) converts to [D2][/][/][/][:||][(6x)] in ChordPro and back without
changing the tokens. ChordPro permits bracketed display content, but :|| and unstarred repeat counts are
ChordSheetJS conventions rather than official semantic ChordPro constructs. Explicit ChordPro annotations such as
[*Coda] remain annotations.
const chordSheet = `
[Chorus]
Am C/G F C
Let it be, let it be, let it be, let it be
C G F C/E Dm C
Whisper words of wisdom, let it be`.substring(1);
const parser = new ChordSheetJS.UltimateGuitarParser();
const song = parser.parse(chordSheet);const chordSheet = `
{title: Let it be}
{subtitle: ChordSheetJS example version}
{start_of_chorus: Chorus}
Let it [Am]be, let it [C/G]be, let it [F]be, let it [C]be
[C]Whisper words of [G]wisdom, let it [F]be [C/E] [Dm] [C]
{end_of_chorus}`.substring(1);
const parser = new ChordSheetJS.ChordProParser();
const song = parser.parse(chordSheet);const formatter = new ChordSheetJS.TextFormatter();
const disp = formatter.format(song);const formatter = new ChordSheetJS.HtmlTableFormatter();
const disp = formatter.format(song);const formatter = new ChordSheetJS.HtmlDivFormatter();
const disp = formatter.format(song);const formatter = new ChordSheetJS.ChordProFormatter();
const disp = formatter.format(song);const formatter = new ChordSheetJS.ChordsOverWordsFormatter();
const disp = formatter.format(song);Note:
PdfFormatteris currently in beta. Its API may change in future releases.
PDF support is available as a separate entry point to keep the main bundle small.
To use it, install jspdf as a dependency:
npm install jspdfThen import PdfFormatter from chordsheetjs/pdf:
import { PdfFormatter } from 'chordsheetjs/pdf';
const formatter = new PdfFormatter();
const doc = formatter.format(song);
doc.save('song.pdf');Note:
MeasuredHtmlFormatteris currently in beta. Its API may change in future releases.
Creates HTML output with precise text measurement for accurate chord positioning.
const formatter = new ChordSheetJS.MeasuredHtmlFormatter();
const disp = formatter.format(song);By default, a {capo} directive is informational only: the chords are rendered exactly as written, because a
capo tells the player which shapes to play, not which chords sound. Set decapo: true to eliminate the capo
instead: all chords are transposed up by the capo amount, the {key} directive is updated to the sounding key
and the {capo} directive is dropped.
// {key: A}, {capo: 1}, [A]This is an [F#m]example
new ChordSheetJS.TextFormatter().format(song);
// A F#m
new ChordSheetJS.TextFormatter({ decapo: true }).format(song);
// A# GmThe PdfFormatter and MeasuredHtmlFormatter are powered by a layout engine that handles text measurement
and precise positioning of chords above lyrics. The layout engine uses measurers to calculate text dimensions:
DomMeasurer- Measures text using the browser's DOMCanvasMeasurer- Measures text using HTML CanvasJsPdfMeasurer- Measures text using jsPDF (for PDF output, available fromchordsheetjs/pdf)
These are used internally by the measurement-based formatters but can also be accessed directly for advanced use cases.
Token fonts are optional and inherit from the final configured chord font. Existing configurations that only set
fonts.chord continue to apply to barlines, instructions, and no-chord markers. Rhythm symbols use the chord font with
a default weight of 500; the exact single barline | uses the same lighter font role. Optional overrides can be
provided without duplicating the complete chord font:
const formatter = new PdfFormatter({
fonts: {
chord: { name: 'Helvetica', style: 'bold', size: 13, weight: 700, color: '#000' },
instruction: { inherit: 'chord', style: 'italic' },
barline: { inherit: 'chord' },
},
});Font inheritance is resolved after defaults and user configuration are merged, so later fonts.chord overrides also
flow through to token fonts.
A song map is the ordered list of section occurrences that describes in which order a song is performed. It can be generated from a linear chart. Generating a song map does not change the song.
const song = new ChordProParser().parse(chordSheet);
const songMap = SongMapGenerator.generate(song);
songMap.toString();
// 'V1 C1 V2 C1'
songMap.sections.map((section) => [section.token, section.displayLabel]);
// [['V1', 'Verse 1'], ['C1', 'Chorus'], ['V2', 'Verse 2']]
songMap.occurrences.map((occurrence) => [occurrence.token, occurrence.origin]);
// [['V1', 'source'], ['C1', 'source'], ['V2', 'source'], ['C1', 'recall']]A paragraph that is not part of a section becomes an anonymous section with an S token, so every piece of chart
content has a place in the map.
Every occurrence refers to one section, so the same section can occur multiple times. Sections are identified by section type, label and content: a repeated block with exactly equal content is another occurrence of the same section, a block with a conflicting label is a new section.
Recognized repeat instructions (2x, x2, (2x), Repeat 2x, Repeat 2 times) in a section label or in a comment
inside a section become explicit occurrences. The instruction itself is preserved in the label:
// {start_of_chorus: Chorus (2x)}
songMap.toString();
// 'C1 C1'
songMap.occurrences.map((occurrence) => occurrence.origin);
// ['source', 'repeat']Instructions that do not describe a finite number of performances (0x, -1x, 1.5x, repeat until cue) and
instructions that do not identify one occurrence do not create occurrences; they report a diagnostic.
When generation cannot resolve something, it reports a diagnostic instead of guessing:
songMap.diagnostics.map((diagnostic) => [diagnostic.type, diagnostic.message]);
// [['missing_recall_target', 'A chorus recall has no preceding chorus to refer to']]A song map can be stored in the chart itself with the OnSong {flow: ...} directive. Both the shorthand
(V1 C V2 C) and the comma separated longhand (Verse 1, Chorus, Verse 2, Chorus) are read; items can refer to a
section token, a section label or an OnSong style label abbreviation.
const song = new ChordProParser().parse(chordSheet);
const songMap = FlowDirective.parse(song);
// null when the song has no flow directive
songMap.toString();
// 'C1 V1 C1 B1 C1'
FlowDirective.format(songMap);
// '{flow: C1 V1 C1 B1 C1}'A repeat in a flow must be marked as one, either with parentheses or with the word repeat:
Verse 1, Chorus, (Repeat 2x) renders the chorus twice. A reference that does not resolve to exactly one section
does not become an occurrence; it reports an invalid_reference or ambiguous_reference diagnostic. Items that are
not section references, such as OnSong page breaks and transpose instructions, report unsupported_flow_item.
By default the display formatters render the chart as written. With renderOrder: 'mapped' they render the song map
instead: one paragraph per occurrence, in performance order. The {flow} directive of the song is followed when it
has one, else the map that is generated from the chart.
new TextFormatter({ renderOrder: 'mapped' }).format(song);Because every occurrence is rendered, {chorus} recalls and repeat instructions are written out in full, which makes
expandChorusDirective redundant in this mode. Content that is not part of a section, such as a comment between two
sections, travels along with the next section that the map performs, and is rendered last when the map performs no
section after it. A section that the song map does not mention, for example because a flow directive leaves it out or
refers to it with an unresolvable reference, is not rendered.
The active song map and the paragraphs it describes are also available directly:
song.songMap;
song.mappedBodyParagraphs;A formatter reports what mapped rendering could not resolve or had to leave out, so a flow directive with a typo does not silently drop a section:
const formatter = new TextFormatter({ renderOrder: 'mapped' });
formatter.format(song);
formatter.diagnostics.map((diagnostic) => [diagnostic.type, diagnostic.sectionToken]);
// [['invalid_reference', null], ['omitted_occurrence', 'C1']]The diagnostics are those of the rendered song map, plus an omitted_occurrence for every occurrence of which
formatter configuration excludes the content, for example a {start_of_chorus-guitar} section that is rendered
for another instrument. Source order rendering reports no diagnostics. PdfFormatter and MeasuredHtmlFormatter do not filter paragraphs by selector, so they report the song map
diagnostics only.
In mapped order every occurrence is rendered in full by default. With repeatPresentation: 'condensed' adjacent
occurrences of one section are rendered once, with the repeat count in the section label:
// {flow: V1 C1 C1}
new TextFormatter({ renderOrder: 'mapped', repeatPresentation: 'condensed' }).format(song);
// Verse 1
// G
// Verse one
//
// Chorus (2x)
// C
// Chorus lineA repeat instruction that the label already has is replaced, so a Chorus (2x) label stays Chorus (2x). A section
without a label is labelled with the repeat count alone, and a section without a section delimiter has nowhere to
show the repeat count, so its occurrences stay in full. Occurrences of one section that are not adjacent keep their
own position in the output.
Condensing is presentation only: the song map keeps one occurrence per performance, and renderOrder: 'source'
output is not affected.
A song map can be presented next to the chart, for example as a row of section tokens that a player highlights while
it follows along. ChordSheetJS does not render that presentation; it provides the data for it. songMap.view()
returns one entry per presented item:
// {flow: V1 C1 C1 B1}
songMap.view().map((entry) => [entry.token, entry.displayLabel, entry.repeatCount]);
// [['V1', 'Verse 1', 1], ['C1', 'Chorus', 1], ['C1', 'Chorus', 1], ['B1', 'Bridge', 1]]
songMap.view({ repeatPresentation: 'condensed' }).map(String);
// ['V1', 'C1 (2x)', 'B1']Every entry refers to the canonical occurrences it presents through entry.occurrences, so a condensed entry still
identifies each performance. entry.lineNumber is the source line of the first occurrence, and entry.type is the
section type, for example chorus. For a section without a label, entry.displayLabel falls back to the section type
and ordinal, so an entry reads Verse 2 (2x) where the chart itself has no label to extend and shows (2x).
A formatter exposes the song map it rendered, in the order and the repeat presentation of that render:
const formatter = new TextFormatter({ renderOrder: 'mapped', repeatPresentation: 'condensed' });
formatter.format(song);
formatter.songMapEntries.map((entry) => [entry.displayLabel, entry.omitted]);
// [['Verse 1', false], ['Chorus (2x)', false], ['Bridge', false]]entry.omitted marks an entry of which this formatter leaves the content out, which is the same occurrence that
reports an omitted_occurrence diagnostic. songMapEntries is empty for source order rendering.
The entries carry everything an accessible presentation needs: a name per occurrence (displayLabel), the repeat
count in text rather than in a visual marker, and a reading order that matches the rendered chart. Use entry.type
for a type colour only in addition to the label, never as the only way to tell sections apart.
Chord sheets (Songs) can be serialized to plain JavaScript objects, which can be converted to JSON, XML etc by
third-party libraries. The serialized object can also be deserialized back into a Song.
const serializedSong = new ChordSheetSerializer().serialize(song);
const deserialized = new ChordSheetSerializer().deserialize(serializedSong);The HTML formatters (HtmlTableFormatter and HtmlDivFormatter) can provide basic CSS to help with styling the output:
HtmlTableFormatter.cssString();
// .paragraph {
// margin-bottom: 1em;
// }
HtmlTableFormatter.cssString('.chordSheetViewer');
// .chordSheetViewer .paragraph {
// margin-bottom: 1em;
// }
HtmlTableFormatter.cssObject();
// '.paragraph': {
// marginBottom: '1em'
// }ChordSheetJS can render the chords of a sheet in a different notation system than the one they were written in.
This is controlled with the {chord_style} directive, in combination with the song {key}.
Four notation styles are supported:
| Style | Description | Example (key of C) |
|---|---|---|
symbol |
Letter notation (the default) | C, Dm, F, G, Am |
solfege |
Solfège / romance-language notation | Do, Rem, Fa, Sol |
numeral |
Roman numeral / functional notation | I, ii, IV, V, vi |
number |
Nashville number notation | 1, 2m, 4, 5, 6m |
The symbol and solfege styles are absolute (they name an actual pitch), while numeral and number are relative
to the key. Because of that, a song {key} is required to convert between an absolute and a relative style. Conversion
works in every direction: the chords in the source sheet may be written in any of the four styles.
const chordSheet = `
{key: C}
{chord_style: numeral}
Let it [Am]be, let it [F]be, let it [C]be
[C]Whisper words of [G]wisdom, let it [F]be`.substring(1);
const song = new ChordSheetJS.ChordProParser().parse(chordSheet);
new ChordSheetJS.TextFormatter().format(song);
// Am -> vi, F -> IV, C -> I, G -> VUsing {chord_style: number} on the same sheet would render Am as 6m, F as 4, C as 1 and G as 5.
Because conversion is bidirectional, a sheet written in Roman numerals can be rendered as chord symbols by combining a
concrete {key} with {chord_style: symbol}:
const chordSheet = `
{key: C}
{chord_style: symbol}
[I] [ii] [iii] [IV] [V] [vi] [vii]`.substring(1);
const song = new ChordSheetJS.ChordProParser().parse(chordSheet);
new ChordSheetJS.TextFormatter().format(song);
// I -> C, ii -> Dm, iii -> Em, IV -> F, V -> G, vi -> Am, vii -> BmThe TextFormatter, HtmlTableFormatter, HtmlDivFormatter and ChordsOverWordsFormatter apply {chord_style}
automatically. The ChordProFormatter preserves the original chord notation by default (so a round-trip stays
lossless); pass applyChordStyle: true to have it convert the chords as well:
new ChordSheetJS.ChordProFormatter({ applyChordStyle: true }).format(song);You can also convert a single chord programmatically, without a sheet, using the Chord methods
(see Parsing and modifying chords below), e.g. Chord.parse('2/4').toChordSymbol('E').
Chord-line tokens use semantic classes instead of the .chord class:
.rhythm-symbolwith variants such as.rhythm-symbol-continuation.barlinewith variants such as.barline-singleand.barline-repeat-end.instructionwith.instruction-repeat-count.no-chordwith.no-chord-marker
This class distinction is a breaking styling change for consumers that previously selected all chord-row content via
.chord. Add the semantic classes to existing selectors when those tokens should retain chord-like presentation.
The legacy ChordLyricsPair#isRhythmSymbol property now applies only to rhythm symbols; use tokenKind and
tokenVariant when handling barlines, instructions, or no-chord markers.
import { Chord } from 'chordsheetjs';const chord = Chord.parse('Ebsus4/Bb');Parse numeric chords (Nashville system):
const chord = Chord.parse('b1sus4/#3');Use #toString() to convert the chord to a chord string (eg Dsus/F#)
const chord = Chord.parse('Ebsus4/Bb');
chord.toString(); // --> "Ebsus4/Bb"var chord2 = chord.clone();Normalizes keys B#, E#, Cb and Fb to C, F, B and E
const chord = Chord.parse('E#/B#');
normalizedChord = chord.normalize();
normalizedChord.toString(); // --> "F/C"Deprecated
Convert # to b and vice versa
const chord = parseChord('Eb/Bb');
const chord2 = chord.switchModifier();
chord2.toString(); // --> "D#/A#"Set the chord to a specific modifier (# or b)
const chord = Chord.parse('Eb/Bb');
const chord2 = chord.useModifier('#');
chord2.toString(); // --> "D#/A#"const chord = Chord.parse('Eb/Bb');
const chord2 = chord.useModifier('b');
chord2.toString(); // --> "Eb/Bb"const chord = Chord.parse('Eb/Bb');
const chord2 = chord.transposeUp();
chord2.toString(); // -> "E/B"const chord = Chord.parse('Eb/Bb');
const chord2 = chord.transposeDown();
chord2.toString(); // -> "D/A"const chord = Chord.parse('C/E');
const chord2 = chord.transpose(4);
chord2.toString(); // -> "E/G#"const chord = Chord.parse('C/E');
const chord2 = chord.transpose(-4);
chord2.toString(); // -> "Ab/C"const numericChord = Chord.parse('2/4');
const chordSymbol = numericChord.toChordSymbol('E');
chordSymbol.toString(); // -> "F#/A"All directives are parsed and are added to Song.metadata. The list below indicates whether formatters actually
use those to change the generated output.
✔️ = supported
🕑 = will be supported in a future version
✖️ = currently no plans to support it in the near future
| Directive | Support |
|---|---|
| title (short: t) | ✔️ |
| subtitle | ✔️ |
| artist | ✔️ |
| composer | ✔️ |
| lyricist | ✔️ |
| copyright | ✔️ |
| album | ✔️ |
| year | ✔️ |
| key | ✔️ |
| time | ✔️ |
| tempo | ✔️ |
| duration | ✔️ |
| capo | ✔️ |
| chord_style | ✔️ |
| meta | ✔️ |
| Directive | Support |
|---|---|
| comment (short: c) | ✔️ |
| comment_italic (short: ci) | ✖️ |
| comment_box (short: cb) | ✖️ |
| chorus | ✖️ |
| image | ✖️ |
| Directive | Support |
|---|---|
| start_of_chorus (short: soc) | ✔️ |
| end_of_chorus (short: eoc) | ✔️ |
| start_of_verse | ✔️ |
| end_of_verse | ✔️ |
| start_of_tab (short: sot) | ✔️ |
| end_of_tab (short: eot) | ✔️ |
| start_of_grid | ✔️ |
| end_of_grid | ✔️ |
| Directive | Support |
|---|---|
| define | ✔️ |
| chord | ✔️ |
| Directive | Support |
|---|---|
| textfont | ✔️ |
| textsize | ✔️ |
| textcolour | ✔️ |
| chordfont | ✔️ |
| chordsize | ✔️ |
| chordcolour | ✔️ |
| tabfont | ✖️ |
| tabsize | ✖️ |
| tabcolour | ✖️ |
| Directive | Support |
|---|---|
| new_page (short: np) | ✖️ |
| new_physical_page (short: npp) | ✖️ |
| column_break (short: cb) | ✖️ |
| grid (short: g) | ✖️ |
| no_grid (short: ng) | ✖️ |
| titles | ✖️ |
| columns (short: col) | ✖️ |
| Directive | Support |
|---|---|
| x_ | ✔️ |
For more information, see the API docs.