tschords

tschords - typesetting chord diagrams for fretted string instruments

DDG - 2022-01-19: Below is a simple Tcl terminal application which allows to typeset chord diagrams for fretted string instruments such as Ukulele and Guitar.

Here an example use case on how to create a Fadd9-Chord diagram on a Bariton Ukulele and thereafter a Dsus4 chord diagram for a standard Guitar tuning without string note indicators at the bottom. Finally at the right a standard G-Chord on the diagram without fingerings. The cross on top means that the string should be not used, the open circle indicates that the string should ring free, the black circle on top indicates the root note of the chord which can be used in root-note/strum patterns.

tclsh tschords.tcl -file DGBE-Fadd9.svg -chord Fadd9 -positions 3213 \
     -fingering RMIP -root R000 -nfrets 4 -width 200 -height 240 -tuning DGBE
tclsh tschords.tcl -file EADGBE-Dsus4.svg -chord Dsus4 -positions XX0233 \ 
      -fingering 000IRP -root 00R000 -nfrets 5 -width 200 -height 240 -tuning ""
tclsh tschords.tcl -file EADGBE-G.svg -chord G -positions 300023 \
      -fingering "" -root R0000R -nfrets 5 -width 200 -height 240 -tuning ""

tschords-dgbe-Fadd9 tschords-eadgbe-Dsus4 tschords-eadgbe-G

Source code

#!/usr/bin/env tclsh
#  Created By    : Detlef Groth
#  Created       : Tue Jan 18 05:24:20 2022
#  Last Modified : <220119.0627>
#
#  Description         : Simple Chord chart generator for fretted instrruments
#        
#  Copyright (c) 2022 Detlef Groth.
# 
#  License: MIT
#  
##############################################################################

proc svgChord {argv} {
    array set args {
                    -file      out.svg
                    -tuning    EADGBE
                    -fingering 0RM0I0
                    -positions X32010
                    -root      0R00R0
                    -chord     Cmaj
                    -width     400
                    -height    480
                    -nfrets    6
                }
    array set args $argv
    set height $args(-height)
    set width $args(-width)
    set hmargin [expr  {$width/12}]
    set vmargin [expr  {$height/15}]
    set ystep [expr {$height/15}]
    set xstep [expr {($width-2*$hmargin+1)/([string length $args(-positions)]-1)}]    
    # not used yet
    if {$width<400} {
        set stroke1 6
        set stroke2 2
    }
    # scaling for font in dependence of given width
    set fsunit [expr {int((6*$width)/400)}]
    set cy 0 ;# currenty y position in chart
    set out [open $args(-file) w 0600]
    puts $out "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:svgjs=\"http://svgjs.com/svgjs\" preserveAspectRatio=\"xMidYMid meet\" viewBox=\"0 0 $width $height\">"
    # grey background
    puts $out "<rect width=\"100%\" height=\"100%\" fill=\"#eeeeee\"></rect>"
    # Display Chord on top
    if {$args(-chord) ne ""} {
        incr cy $ystep
        puts $out "<text x=\"[expr {$width/2}]\" y=\"$cy\" font-family=\"serif\" font-size=\"[expr {$fsunit*8}]\" text-anchor=\"middle\" dominant-baseline=\"central\" fill=\"#000000\">$args(-chord)</text>"    
    }
    incr cy $ystep
    # strings
    incr cy [expr {int($ystep*0.9)}]
    set ystart $cy
    # draw nut
    puts $out "<line x1=\"[expr {$hmargin-2}]\" y1=\"$cy\" x2=\"[expr {$width-$hmargin+2}]\" y2=\"$cy\" stroke-width=\"8\" stroke=\"#000000\"></line>"
    # available space
    if {$args(-tuning) ne ""} {
        set yrange [expr {$height-$vmargin*2-$cy}]
    } else {
        set yrange [expr {$height-$vmargin-$cy}]
    }
    # draw frets
    set ystep [expr {$yrange/$args(-nfrets)}]
    set x1 $hmargin
    set x2 [expr {$width-$hmargin}]
    for {set i 0} {$i < $args(-nfrets)} {incr i} {
        incr cy $ystep
        puts $out "<line x1=\"$x1\" y1=\"$cy\" x2=\"$x2\" y2=\"$cy\" stroke-width=\"3\" stroke=\"#000000\"></line>"
        set y1a $cy
        incr y1a [expr {int($ystep/2)}]
        set x1a [expr {$width/2}]
        if {$i in [list 1 3]} {
            # position markers
            if {$i == 1 || ($i == 3 && $args(-nfrets) > 4)} {
                puts $out "<text x=\"$x1a\" y=\"$y1a\" font-family=\"sans-serif\" font-size=\"[expr {$fsunit*8}]\" text-anchor=\"middle\" dominant-baseline=\"central\" fill=\"#000000\"> \u00D7</text>"
            }
        }
    }
    set yend $cy
    set x1 $hmargin
    # draw draw strings and fingerboard
    set notes [lrange [split [regsub -all {([A-Z])} $args(-tuning) ";\\1"] ";"] 1 end]
    for {set x 0} {$x < [string length $args(-positions)]} {incr x} {
        # draw strings
        puts $out "<line x1=\"$x1\" y1=\"$ystart\" x2=\"$x1\" y2=\"$yend\" stroke-width=\"3\" stroke=\"#000000\"></line>"
        set pos [string range $args(-positions) $x $x]
        # draw indicators above the nut
        if { $pos == "X"} {
            puts $out "<text x=\"$x1\" y=\"[expr {$ystart-0.35*$ystep}]\" font-family=\"sans-serif\" font-size=\"[expr {$fsunit*6}]\" text-anchor=\"middle\" dominant-baseline=\"central\" fill=\"#000000\">\u00D7</text>"
        } elseif { $pos == "0"} {
            puts $out "<circle r=\"[expr {$fsunit*1.7}]\" cx=\"$x1\" cy=\"[expr {$ystart-0.35*$ystep}]\" fill=\"white\" stroke-width=\"2\" stroke=\"black\"></circle>"   
        }
        # root note indicator
        set root [string range $args(-root) $x $x] 
        if {$root == "R"} {
            puts $out "<circle r=\"[expr {$fsunit*1.7}]\" cx=\"$x1\" cy=\"[expr {$ystart-0.35*$ystep}]\" fill=\"black\" stroke-width=\"2\" stroke=\"black\"></circle>"   
        }
        if {[llength $notes] >= $x} {
             set tun [lindex $notes $x]
             puts $out "<text x=\"$x1\" y=\"[expr {$yend+0.35*$ystep}]\" font-family=\"serif\" font-size=\"[expr {$fsunit*6}]\" text-anchor=\"middle\" dominant-baseline=\"central\" fill=\"#000000\">$tun</text>"
         }
        incr x1 $xstep
    }
    # draw fingerings
    set cx $hmargin
    set cy $ystart
    for {set y 0} {$y < $args(-nfrets)} {incr y} {
        for {set x 0} {$x < [string length $args(-positions)]} {incr x} {
            set pos [string range $args(-positions) $x $x]
            if {[expr {$y+1}] == $pos} {
                puts $out "<circle r=\"[expr {$fsunit*3}]\" cx=\"$cx\" cy=\"[expr {$cy+0.5*$ystep}]\" fill=\"black\" stroke-width=\"2\" stroke=\"black\"></circle>" 
                if {[string length $args(-fingering)] >= $x} {
                    set fing [string range $args(-fingering) $x $x]
                    puts $out "<text x=\"$cx\" y=\"[expr {$cy+0.51*$ystep}]\" font-family=\"sans-serif\" font-size=\"[expr {$fsunit*4}]\" text-anchor=\"middle\" dominant-baseline=\"central\" fill=\"#FFFFFF\">$fing</text>"
                }
            }
            incr cx $xstep
        }
        incr cy $ystep
        set cx $hmargin
    }

    puts $out "</svg>"
    close $out
}

if {[info exists argv] && [llength $argv] == 0} {
    puts "tschord.tcl - drawing chord diagrams for fretted instruments"
    puts "Author: Detlef Groth, Caputh-Schwielowsee, Germany"
    puts "License: MIT\n"
    puts "Usage: tschords.tcl -file file.svg -tuning EADGBE \
          -fingering 00MRI0 -positions x02210 -chord Am"
} else {
    svgChord $argv      
}

TODO's

  • adding capo lines
  • adding fret indicator
  • drawing notes instead of fingerings for instance pentatonic scale
  • code cleaning using the tsvg package
  • direct PDF output using pdf4tcl
  • canvas output
  • direct png output using gdtcl

Discussions

DDG - 2022-01-19: Please discuss here.