Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Catalog · How the catalog works
Anything · Lazarus or Delphi
Random projectRandom project

PascalChordSVG

A CLI and a library to create SVG files of guitar chords.

View on GitHub

Member rating

No ratings yet.

Sign in to rate this project.

README

ChordSVG

A Free Pascal command-line tool and reusable library for generating SVG guitar chord diagrams. Produces clean, print-ready chord charts as standalone SVG files or multi-chord page sheets.


Contents


Features

  • Single chord diagrams — one SVG per chord, native 196×340 px
  • Multi-chord sheets — fixed 3×3 portrait (9 chords) or 4×2 landscape (8 chords) grid, auto-scaled to fill the page
  • Three built-in themes — Dark (navy/gold), Light (cream/blue), Black & White (print-optimised)
  • Title and subtitle — rendered above the chord grid; page numbers appended automatically on multi-page output
  • Input file format — plain-text .txt files with directives, chord lines, page breaks, and inline comments
  • Two output modes — one SVG per page (--pages) or one SVG per chord (--individual)
  • Barre chord detection — automatically draws a pill-shaped barre bar when two or more strings share the lowest fret
  • High-position chords — fret window shifts automatically; position label (e.g. 5fr) rendered beside the grid
  • Reusable library — all logic lives in ChordSVGLib.pas; the CLI is a thin wrapper

Requirements

  • Free Pascal Compiler 3.2.0 or later (fpc)
  • Standard FPC RTL units: SysUtils, StrUtils, Math, Classes
  • No external dependencies

Tested on Linux x86-64 with FPC 3.2.2. Should compile on macOS and Windows without modification.


Building

bash
fpc chordsvg.pas

FPC resolves ChordSVGLib.pas automatically because it is in the same directory. The compiled binary is chordsvg (or chordsvg.exe on Windows).

To compile just the library unit (e.g. for use in another project):

bash
fpc ChordSVGLib.pas

Quick Start

bash
# Single chord to stdout
./chordsvg "C" "x 3 2 0 1 0"

# Single chord to file, light theme
./chordsvg "Am" "x 0 2 2 1 0" am.svg --light

# Inline sheet, dark theme, portrait
./chordsvg --sheet --title "Key of C" \
    "C:x 3 2 0 1 0" "Am:x 0 2 2 1 0" "F:1 1 2 3 3 1" "G:3 2 0 0 0 3" \
    sheet.svg

# From a text file — one SVG per page
./chordsvg --input chords.txt --pages output/sheet

# From a text file — one SVG per chord
./chordsvg --input chords.txt --individual output/chords/

CLI Reference

Single Chord

pascal
chordsvg "Name" "frets" [output.svg] [--dark|--light|--bw]

Renders one chord diagram. Output goes to stdout if no file is specified.

Argument Description
"Name" Chord display name, e.g. "Cmaj7"
"frets" Six space-separated fret values (see Fret Values)
output.svg Optional output file path (detected by .svg extension)
--dark / --light / --bw Colour theme (default: --dark)

Examples:

bash
./chordsvg "C"     "x 3 2 0 1 0"
./chordsvg "F"     "1 1 2 3 3 1" f_barre.svg
./chordsvg "Bm"    "x 2 4 4 3 2" bm.svg --bw
./chordsvg "Cmaj7" "x 3 2 0 0 0" --light

Fret Values

Frets are specified as six whitespace-separated tokens, ordered low-E (string 6) to high-E (string 1):

Token Meaning
x or - Muted string (shown as ✕ above nut)
0 Open string (shown as â—‹ above nut)
1–24 Fret number (filled dot on grid)

Inline Sheet

pascal
chordsvg --sheet [options] [output.svg] "Name:frets" "Name:frets" ...

Lays out multiple chords on a single page-sized SVG. Each chord argument uses a colon to separate the name from the fret string.

Flag Description
--sheet Enable sheet mode
--portrait Portrait orientation, 3×3 grid (default)
--landscape Landscape orientation, 4×2 grid
--dark / --light / --bw Colour theme
--title "text" Large title at top of page
--subtitle "text" Smaller italic subtitle below title
output.svg Output file (stdout if omitted)

Examples:

bash
./chordsvg --sheet \
    "C:x 3 2 0 1 0" "Am:x 0 2 2 1 0" "F:1 1 2 3 3 1" "G:3 2 0 0 0 3" \
    sheet.svg

./chordsvg --sheet --landscape --bw --title "Beginner Chords" \
    "D:x x 0 2 3 2" "E:0 2 2 1 0 0" "A:x 0 2 2 2 0" \
    beginner.svg

./chordsvg --sheet --light --title "Barre Chords" --subtitle "E-shape" \
    "F:1 1 2 3 3 1" "G:3 3 5 5 4 3" "A:5 5 7 7 6 5" sheet_barre.svg

Input File Mode

pascal
chordsvg --input <file.txt> (--pages [stem] | --individual [dir/]) [options]

Reads chord definitions from a plain-text file. Requires either --pages or --individual to specify the output mode.

Flag Description
--input <file> Path to the chord input file
--pages [stem] Write one SVG per page; files named stem_1.svg, stem_2.svg, … Default stem: page
--individual [dir/] Write one SVG per chord into a directory. Default: current directory
--dark / --light / --bw Theme override (file directives take priority)
--portrait / --landscape Orientation override (file directives take priority)
--title "text" Title override (file title: directive takes priority)
--subtitle "text" Subtitle override (file subtitle: directive takes priority)

Examples:

bash
# Pages output — files: page_1.svg, page_2.svg, ...
./chordsvg --input chords.txt --pages

# Custom stem — files: book_1.svg, book_2.svg, ...
./chordsvg --input chords.txt --pages book

# Individual SVGs into a subdirectory
./chordsvg --input chords.txt --individual chords/

# Override theme from CLI even if file sets one
./chordsvg --input chords.txt --pages --bw

Note: When both the input file and the CLI specify a theme or orientation, the file directive takes priority. This lets you lock a file's appearance without worrying about CLI flags.


Input File Format

Input files are plain UTF-8 text. Lines are processed in order; blank lines and comment lines (starting with #) are ignored.

pascal
# ── Directives ──────────────────────────────────────────────────────────────
title: Common Guitar Chords        # shown at top of every page
subtitle: Open & Barre Positions   # shown below title, italic
theme: bw                          # dark | light | bw
orientation: portrait              # portrait | landscape

# ── Chord lines ──────────────────────────────────────────────────────────────
# Format: Name<whitespace>fret1 fret2 fret3 fret4 fret5 fret6
C        x 3 2 0 1 0
Am       x 0 2 2 1 0
F        1 1 2 3 3 1        # inline comments are stripped
G        3 2 0 0 0 3

# ── Page break ───────────────────────────────────────────────────────────────
---                                # plain break, inherits global title
--- Minor Chords                   # break + per-page title override

Dm       x x 0 2 3 1
Em       0 2 2 0 0 0
Bm       x 2 4 4 3 2

Directives

Directive Values Description
title: Any text Page title (large, bold, centred)
subtitle: Any text Page subtitle (smaller, italic, centred)
theme: dark light bw Colour theme for all output
orientation: portrait landscape Page orientation

Page Breaks

A line beginning with --- starts a new page. Any text after --- (trimmed) becomes that page's title override, replacing the global title for that page only:

pascal
--- Minor Chords    →  page title becomes "Minor Chords"
---                 →  page uses global title (or no title)

Chord Name Rules

  • The chord name is the first whitespace-delimited token on the line
  • Names may contain letters, numbers, #, b, /, and most Unicode characters
  • When writing individual SVG files, characters outside [A-Za-z0-9\-_#] are replaced with _ in the filename; the display name is unaffected

Themes

Flag Background Dots / Barre Mute X Best for
--dark Navy #1a1a2e Warm gold #c8a96e Red #e05555 Screens, dark-mode docs
--light Cream #f5f0e8 Steel blue #2a6496 Red #c0392b Light documents, e-readers
--bw White #ffffff Black #000000 Black #000000 Laser/inkjet printing

All themes use Georgia serif for chord names and titles, and monospace for fret-position labels.


Page Layout

Grid dimensions

Orientation Columns Rows Chords per page
Portrait 3 3 9
Landscape 4 2 8

Chords are scaled uniformly to fill the grid area. Scale is capped at 1.0 (native size) so small chord counts are never enlarged.

Page size

Pages are sized at 96 dpi, matching CSS pixel dimensions for direct use in web contexts and browser-based PDF export:

Orientation Width Height
Portrait 794 px 1123 px
Landscape 1123 px 794 px

Header

When a title is present, 70 px are reserved at the top of the page. The title is rendered in 28 px bold Georgia, the subtitle in 16 px italic Georgia below it. When there are multiple pages, (N of M) is automatically appended to the subtitle.

Native cell size

A single chord diagram at scale 1.0 is 196 × 340 px:

pascal
Padding top (90 px)    — chord name + open/mute markers
Grid (5 frets × 38 px) — 190 px
Padding bottom (30 px)

Library API

All rendering logic is in ChordSVGLib.pas. Add it to your project with:

pascal
uses ChordSVGLib;

Types

TFretValue = -1..24

A single string's fret value. -1 = muted, 0 = open, 1–24 = fret number.

TChordFrets = array[0..5] of TFretValue

Six fret values ordered low-E (index 0) to high-E (index 5).

TChordEntry

pascal
TChordEntry = record
  Name  : string;       // display name, e.g. 'Cmaj7'
  Frets : TChordFrets;  // voicing
end;

TChordList = array of TChordEntry

Dynamic array of chord entries, used for sheet rendering.

TPage

pascal
TPage = record
  Chords        : TChordList;
  TitleOverride : string;   // '' = use global title
end;

A single page of a chord book. TitleOverride is populated from --- Title lines in input files.

TPageList = array of TPage

TTheme

pascal
TTheme = record
  Background   : string;   // diagram background colour
  ChordName    : string;   // chord name text colour
  Nut          : string;   // nut rectangle fill
  Fret         : string;   // fret line stroke
  Str          : string;   // string line stroke
  Dot          : string;   // fretted dot / barre fill
  Open         : string;   // open string circle stroke
  Mute         : string;   // muted string X stroke
  FretLbl      : string;   // fret position label colour
  TitleText    : string;   // page title colour
  SubtitleText : string;   // page subtitle colour
end;

All colour fields are CSS hex strings, e.g. '#1a1a2e'.

TFileSettings

pascal
TFileSettings = record
  ThemeOverride       : string;   // '' if not set in file
  OrientationOverride : string;   // '' | 'portrait' | 'landscape'
  Title               : string;
  Subtitle            : string;
end;

Constants

Constant Value Description
CS_NUM_STRINGS 6 Strings per diagram
CS_FRETS_SHOWN 5 Fret slots in grid
CS_CELL_W 196 Native cell width (px)
CS_CELL_H 340 Native cell height (px)
CS_PAGE_PORTRAIT_W/H 794 / 1123 Portrait page size (px)
CS_PAGE_LANDSCAPE_W/H 1123 / 794 Landscape page size (px)
CS_PAGE_MARGIN 40 Page margin (px)
CS_PORTRAIT_COLS/ROWS 3 / 3 Portrait grid
CS_LANDSCAPE_COLS/ROWS 4 / 2 Landscape grid
THEME_DARK — Built-in dark theme
THEME_LIGHT — Built-in light theme
THEME_BW — Built-in B&W theme

Parsing Functions

ParseFrets

pascal
function ParseFrets(const S: string; out Frets: TChordFrets): Boolean;

Parses a whitespace-separated fret string into a TChordFrets array.

pascal
var Frets: TChordFrets;
if ParseFrets('x 3 2 0 1 0', Frets) then
  WriteLn('Parsed OK');

Returns False if the token count is not exactly 6 or any value is out of range.


ParseChordArg

pascal
function ParseChordArg(const S: string; out Entry: TChordEntry): Boolean;

Parses a "Name:frets" CLI-style argument.

pascal
var Entry: TChordEntry;
ParseChordArg('Cmaj7:x 3 2 0 0 0', Entry);
// Entry.Name  = 'Cmaj7'
// Entry.Frets = [−1, 3, 2, 0, 0, 0]

ParseInputFile

pascal
function ParseInputFile(const Path: string;
  out Pages: TPageList;
  out FileSettings: TFileSettings): Boolean;

Reads a chord text file and returns a TPageList split at --- boundaries, plus any directives found.

pascal
var Pages: TPageList; Settings: TFileSettings;
if ParseInputFile('chords.txt', Pages, Settings) then
  WriteLn('Pages: ', Length(Pages));

ThemeFromName

pascal
function ThemeFromName(const Name: string): TTheme;

Returns the matching built-in theme. Unrecognised names return THEME_DARK.

pascal
Theme := ThemeFromName('bw');   // returns THEME_BW
Theme := ThemeFromName('dark'); // returns THEME_DARK

Rendering Functions

ChordToSVG

pascal
function ChordToSVG(const ChordName: string;
  const Frets: TChordFrets;
  const Theme: TTheme): string;

Returns a complete <?xml ...?><svg ...>...</svg> document for a single chord diagram at native size (196×340 px).

pascal
var Frets: TChordFrets; SVG: string;
ParseFrets('x 3 2 0 1 0', Frets);
SVG := ChordToSVG('C', Frets, THEME_DARK);
WriteLn(SVG);

ChordSheetToSVG

pascal
function ChordSheetToSVG(
  const Chords: TChordList;
  const Theme: TTheme;
  PageW, PageH: Integer;
  const Title, Subtitle: string;
  PageNum, PageCount: Integer): string;

Returns a complete SVG document for one page of the chord sheet grid.

pascal
var Chords: TChordList; SVG: string;
SetLength(Chords, 2);
Chords[0].Name := 'C';  ParseFrets('x 3 2 0 1 0', Chords[0].Frets);
Chords[1].Name := 'Am'; ParseFrets('x 0 2 2 1 0', Chords[1].Frets);

SVG := ChordSheetToSVG(Chords, THEME_LIGHT,
  CS_PAGE_PORTRAIT_W, CS_PAGE_PORTRAIT_H,
  'My Songs', 'Key of C',
  1, 1);   // page 1 of 1

Pass PageNum and PageCount to enable automatic (N of M) suffixes in multi-page output.


ChordsPerPage

pascal
function ChordsPerPage(IsLandscape: Boolean): Integer;

Returns the maximum number of chords that fit on one page (9 portrait, 8 landscape).


Output Helpers

WriteToFile

pascal
procedure WriteToFile(const SVGText, Path: string);

Writes SVG content to a file. Logs the path to StdErr on success; halts with exit code 3 on failure.


WritePagesOutput

pascal
procedure WritePagesOutput(
  const Pages: TPageList;
  const Theme: TTheme;
  PageW, PageH: Integer;
  const Stem, GlobalTitle, GlobalSubtitle: string);

Renders each page and writes Stem_1.svg, Stem_2.svg, … Resolves per-page titles from TPage.TitleOverride, falling back to GlobalTitle.


WriteIndividualOutput

pascal
procedure WriteIndividualOutput(
  const Pages: TPageList;
  const Theme: TTheme;
  const Dir: string);

Renders each chord individually and writes Dir/ChordName.svg. Duplicate chord names get a numeric suffix (C.svg, C_2.svg, …).


SafeFilename

pascal
function SafeFilename(const S: string): string;

Replaces characters outside [A-Za-z0-9\-_#] with underscores.

pascal
SafeFilename('F#m')    // → 'F#m'  (# is allowed)
SafeFilename('G/B')    // → 'G_B'
SafeFilename('Cmaj7')  // → 'Cmaj7'

EnsureTrailingSep

pascal
function EnsureTrailingSep(const Dir: string): string;

Appends DirectorySeparator if absent. Used internally by WriteIndividualOutput.


Advanced: DrawChordCell

pascal
procedure DrawChordCell(
  Lines: TStringList;
  const ChordName: string;
  const Frets: TChordFrets;
  const Theme: TTheme;
  OX, OY: Integer;
  Scale: Double;
  NameFontSize, LblFontSize: Integer);

Low-level procedure that appends SVG element strings (without wrapping <svg> tags) to an existing TStringList. Use this to embed chord diagrams into a custom SVG canvas at arbitrary positions and scales.

pascal
var Lines: TStringList; Frets: TChordFrets;
Lines := TStringList.Create;
Lines.Add('<svg xmlns="http://www.w3.org/2000/svg" width="600" height="340">');
Lines.Add('  <rect fill="#1a1a2e" width="600" height="340"/>');

ParseFrets('x 3 2 0 1 0', Frets);
DrawChordCell(Lines, 'C',  Frets, THEME_DARK,   0, 0, 1.0, 30, 14);
ParseFrets('x 0 2 2 1 0', Frets);
DrawChordCell(Lines, 'Am', Frets, THEME_DARK, 200, 0, 1.0, 30, 14);
ParseFrets('1 1 2 3 3 1', Frets);
DrawChordCell(Lines, 'F',  Frets, THEME_DARK, 400, 0, 1.0, 30, 14);

Lines.Add('</svg>');
WriteLn(Lines.Text);
Lines.Free;

Examples

Minimal single-chord program

pascal
program HelloChord;
{$mode objfpc}{$H+}
uses SysUtils, ChordSVGLib;
var Frets: TChordFrets;
begin
  ParseFrets('x 3 2 0 1 0', Frets);
  Write(ChordToSVG('C', Frets, THEME_DARK));
end.

Build a chord book from code

pascal
program ChordBook;
{$mode objfpc}{$H+}
uses SysUtils, ChordSVGLib;

procedure AddChord(var List: TChordList; const Name, FretsStr: string);
var N: Integer;
begin
  N := Length(List);
  SetLength(List, N + 1);
  List[N].Name := Name;
  ParseFrets(FretsStr, List[N].Frets);
end;

var
  Chords : TChordList;
  Pages  : TPageList;
  SVG    : string;
begin
  SetLength(Chords, 0);
  AddChord(Chords, 'C',  'x 3 2 0 1 0');
  AddChord(Chords, 'Am', 'x 0 2 2 1 0');
  AddChord(Chords, 'F',  '1 1 2 3 3 1');
  AddChord(Chords, 'G',  '3 2 0 0 0 3');

  SVG := ChordSheetToSVG(Chords, THEME_BW,
    CS_PAGE_PORTRAIT_W, CS_PAGE_PORTRAIT_H,
    'Beginner Chords', 'Open positions',
    1, 1);
  WriteToFile(SVG, 'beginner.svg');
end.

Process an input file programmatically

pascal
var
  Pages   : TPageList;
  Settings: TFileSettings;
  Theme   : TTheme;
  i       : Integer;
begin
  if not ParseInputFile('chords.txt', Pages, Settings) then
    Halt(1);

  Theme := ThemeFromName(Settings.ThemeOverride);

  for i := 0 to High(Pages) do
    WriteToFile(
      ChordSheetToSVG(Pages[i].Chords, Theme,
        CS_PAGE_PORTRAIT_W, CS_PAGE_PORTRAIT_H,
        Pages[i].TitleOverride, Settings.Subtitle,
        i + 1, Length(Pages)),
      Format('out_%d.svg', [i + 1]));
end.

File Structure

pascal
chordsvg/
├── ChordSVGLib.pas       Library unit — all types, rendering, parsing
├── chordsvg.pas          CLI wrapper — argument parsing only
├── example_chords.txt    Sample input file demonstrating all directives
└── README.md             This file

samples/                  Pre-generated SVG output samples
├── C_dark.svg            Single chord, dark theme
├── C_light.svg           Single chord, light theme
├── C_bw.svg              Single chord, B&W theme
├── F_dark.svg            Barre chord, dark theme
├── Am_bw.svg             Open chord, B&W theme
├── page_1.svg            Sheet page 1 (majors, B&W)
├── page_2.svg            Sheet page 2 (minors & 7ths, B&W)
├── page_3.svg            Sheet page 3 (power chords, B&W)
├── dark_portrait.svg     9-chord portrait, dark theme
└── dark_landscape.svg    8-chord landscape, dark theme

Licence

MIT Licence. See source file headers.

Copyright © 2025 Schmitty Guitars
https://schmittyguitars.com