Compare commits
3 Commits
Author | SHA1 | Date | |
---|---|---|---|
a57aed0715 | |||
2712f9ff52 | |||
8271129b90 |
@ -1,6 +1,6 @@
|
||||
# Package
|
||||
|
||||
version = "0.5.0"
|
||||
version = "0.5.4"
|
||||
author = "Jonathan Bernard"
|
||||
description = "Chord chart formatter compatible with Planning Center Online"
|
||||
license = "MIT"
|
||||
@ -12,3 +12,9 @@ bin = @["pco_chords"]
|
||||
# Dependencies
|
||||
|
||||
requires "nim >= 1.6.6", "docopt", "zero_functional"
|
||||
|
||||
# Dependencies from git.jdbernard.com/jdb/nim-package
|
||||
requires "update_nim_package_version"
|
||||
|
||||
task updateVersion, "Update the version of this tool.":
|
||||
exec "update_nim_package_version pco_chords 'src/pco_chordspkg/cliconstants.nim'"
|
||||
|
@ -38,7 +38,12 @@ when isMainModule:
|
||||
if args["--transpose"]: parseInt($args["--transpose"])
|
||||
else: 0
|
||||
|
||||
let outputHtml = ast.toHtml(transpose, args["--number-chart"])
|
||||
let outputHtml =
|
||||
if args["--large-print"]:
|
||||
ast.toHtml(transpose, args["--number-chart"],
|
||||
stylesheets = @[LARGE_PRINT_STYLESHEET])
|
||||
else:
|
||||
ast.toHtml(transpose, args["--number-chart"])
|
||||
|
||||
if args["--output"]: writeFile($args["--output"], outputHtml)
|
||||
else: stdout.write(outputHtml)
|
||||
|
@ -23,7 +23,7 @@ type
|
||||
ccnkRedefineKey,
|
||||
ccnkNone
|
||||
|
||||
ChordChartPitch* = enum Af, A, Bf, B, C, Df, D, Ef, E, F, Fs, G
|
||||
ChordChartPitch* = enum Af, A, Bf, B, C, Df, D, Ef, E, F, Gf, G
|
||||
|
||||
ChordChartChord* = object
|
||||
original*: Option[string]
|
||||
@ -77,20 +77,60 @@ iterator pairs*(ccmd: ChordChartMetadata): tuple[key, value: string] =
|
||||
|
||||
func kind*(ccn: ChordChartNode): ChordChartNodeKind = ccn.kind
|
||||
|
||||
func `$`*(pitch: ChordChartPitch): string =
|
||||
case pitch
|
||||
of Af: "Ab"
|
||||
of A: "A"
|
||||
of Bf: "Bb"
|
||||
of B: "B"
|
||||
of C: "C"
|
||||
of Df: "Db"
|
||||
of D: "D"
|
||||
of Ef: "Eb"
|
||||
of E: "E"
|
||||
of F: "F"
|
||||
of Fs: "F#"
|
||||
of G: "G"
|
||||
func `$`(pitch: ChordChartPitch): string =
|
||||
["A♭", "A", "B♭", "B", "C", "D♭", "D", "E♭", "E", "F", "G♭", "G"][ord(pitch)]
|
||||
|
||||
func renderPitchInKey*(
|
||||
pitch: ChordChartPitch,
|
||||
key: ChordChartPitch,
|
||||
useSharps: Option[bool] = none[bool]()): string =
|
||||
|
||||
var scaleNames: array[(ord(high(ChordChartPitch)) + 1), string]
|
||||
if useSharps.isNone:
|
||||
scaleNames = case key
|
||||
of A, B, D, E, G:
|
||||
["G#", "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G"]
|
||||
of Af, Bf, C, Df, Ef, F:
|
||||
["A♭", "A", "B♭", "B", "C", "D♭", "D", "E♭", "E", "F", "G♭", "G"]
|
||||
of Gf:
|
||||
["A♭", "A", "B♭", "C♭", "C", "D♭", "D", "E♭", "E", "F", "G♭", "G"]
|
||||
|
||||
elif useSharps.isSome and useSharps.get:
|
||||
scaleNames = case key
|
||||
of A, B, C, D, E, G:
|
||||
["G#", "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G"]
|
||||
of Af:
|
||||
["G#", "A", "A#", "B", "B#", "C#", "D", "D#", "E", "E#", "F#", "F𝄪"]
|
||||
of Bf:
|
||||
["G#", "G𝄪", "A#", "B", "B#", "C#", "C𝄪", "D#", "E", "E#", "F#", "F𝄪"]
|
||||
of Df:
|
||||
["G#", "A", "A#", "B", "B#", "C#", "D", "D#", "E", "E#", "F#", "G"]
|
||||
of Ef:
|
||||
["G#", "A", "A#", "B", "B#", "C#", "C𝄪", "D#", "E", "E#", "F#", "F𝄪"]
|
||||
of Gf:
|
||||
["G#", "A", "A#", "B", "C", "C#", "D", "D#", "E", "E#", "F#", "G"]
|
||||
of F:
|
||||
["G#", "G𝄪", "A#", "B", "B#", "C#", "C𝄪", "D#", "D𝄪", "E#", "F#", "F𝄪"]
|
||||
|
||||
else: # useSharps.isSome and not useSharps.get
|
||||
scaleNames = case key
|
||||
of C, Af, Bf, Df, Ef, F:
|
||||
["A♭", "A", "B♭", "B", "C", "D♭", "D", "E♭", "E", "F", "G♭", "G"]
|
||||
of A:
|
||||
["A♭", "B𝄫", "B♭", "C♭", "C", "D♭", "E𝄫", "E♭", "F♭", "F", "G♭", "G"]
|
||||
of B:
|
||||
["A♭", "A", "B♭", "C♭", "C", "D♭", "D", "E♭", "F♭", "F", "G♭", "G"]
|
||||
of D:
|
||||
["A♭", "B𝄫", "B♭", "C♭", "C", "D♭", "E𝄫", "E♭", "F♭", "F", "G♭", "A𝄫"]
|
||||
of E:
|
||||
["A♭", "B𝄫", "B♭", "C♭", "C", "D♭", "D", "E♭", "F♭", "F", "G♭", "G"]
|
||||
of G:
|
||||
["A♭", "B𝄫", "B♭", "C♭", "D𝄫", "D♭", "E𝄫", "E♭", "F♭", "F", "G♭", "A𝄫"]
|
||||
of Gf:
|
||||
["A♭", "A", "B♭", "C♭", "C", "D♭", "D", "E♭", "E", "F", "G♭", "G"]
|
||||
|
||||
|
||||
return scaleNames[ord(pitch)]
|
||||
|
||||
func `+`*(pitch: ChordChartPitch, steps: int): ChordChartPitch =
|
||||
cast[ChordChartPitch]((ord(pitch) + steps) mod 12)
|
||||
@ -193,29 +233,34 @@ proc parsePitch*(ctx: ParserContext, keyValue: string): ChordChartPitch =
|
||||
of "gs", "gis", "g#", "ab", "a♭", "af", "aes": return ChordChartPitch.Af
|
||||
of "g𝄪", "a", "a♮", "b𝄫": return ChordChartPitch.A
|
||||
of "as", "ais", "a#", "bf", "bb", "b♭", "bes", "c𝄫": return ChordChartPitch.Bf
|
||||
of "a𝄪", "b", "cb", "ces", "cf": return ChordChartPitch.B
|
||||
of "a𝄪", "b", "c♭", "cb", "ces", "cf": return ChordChartPitch.B
|
||||
of "bs", "bis", "b#", "c", "d𝄫": return ChordChartPitch.C
|
||||
of "b𝄪", "cs", "cis", "c#", "df", "db", "des": return ChordChartPitch.Df
|
||||
of "b𝄪", "cs", "cis", "c#", "d♭", "df", "db", "des": return ChordChartPitch.Df
|
||||
of "c𝄪", "d", "e𝄫": return ChordChartPitch.D
|
||||
of "ds", "dis", "d#", "ef", "eb", "ees", "f𝄫": return ChordChartPitch.Ef
|
||||
of "d𝄪", "e", "fes", "ff": return ChordChartPitch.E
|
||||
of "ds", "dis", "d#", "ef", "e♭", "eb", "ees", "f𝄫": return ChordChartPitch.Ef
|
||||
of "d𝄪", "e", "f♭", "fes", "ff": return ChordChartPitch.E
|
||||
of "es", "eis", "e#", "f", "g𝄫": return ChordChartPitch.F
|
||||
of "e𝄪", "fs", "fis", "f#", "gf", "gb", "ges": return ChordChartPitch.Fs
|
||||
of "e𝄪", "fs", "fis", "f#", "g♭", "gf", "gb", "ges": return ChordChartPitch.Gf
|
||||
of "f𝄪", "g", "a𝄫": return ChordChartPitch.G
|
||||
of "1": return ctx.curKeyCenter
|
||||
of "2": return ctx.curKeyCenter + 2
|
||||
of "3": return ctx.curKeyCenter + 4
|
||||
of "4": return ctx.curKeyCenter + 5
|
||||
of "5": return ctx.curKeyCenter + 7
|
||||
of "6": return ctx.curKeyCenter + 9
|
||||
of "7": return ctx.curKeyCenter + 11
|
||||
of "#7", "1", "𝄫2": return ctx.curKeyCenter
|
||||
of "𝄪7", "#1", "b2", "♭2": return ctx.curKeyCenter + 1
|
||||
of "𝄪1", "2", "𝄫3": return ctx.curKeyCenter + 2
|
||||
of "#2", "b3", "♭3", "𝄫4": return ctx.curKeyCenter + 3
|
||||
of "𝄪2", "3", "b4", "♭4": return ctx.curKeyCenter + 4
|
||||
of "#3", "4", "𝄫5": return ctx.curKeyCenter + 5
|
||||
of "𝄪3", "#4", "b5", "♭5": return ctx.curKeyCenter + 6
|
||||
of "𝄪4", "5", "𝄫6": return ctx.curKeyCenter + 7
|
||||
of "#5", "b6", "♭6": return ctx.curKeyCenter + 8
|
||||
of "𝄪5", "6", "𝄫7": return ctx.curKeyCenter + 9
|
||||
of "#6", "b7", "♭7", "𝄫1": return ctx.curKeyCenter + 10
|
||||
of "7", "b1", "♭1": return ctx.curKeyCenter + 11
|
||||
else: raise ctx.makeError(keyValue.strip & " is not a recognized key.")
|
||||
|
||||
# see regexr.com/70nv1
|
||||
let CHORD_REGEX =
|
||||
"([A-G1-7][b#♭♮𝄫𝄪]?)" & # chord root
|
||||
"(([mM1-9#b♭♮𝄫𝄪Δ+oøoø][0-9]?|min|maj|aug|dim|sus|6\\/9|\\([1-9#b♭]+\\))*)" & # chord flavor/type
|
||||
"(\\/([A-G1-7][b#♭♮𝄫𝄪]?))?" # optional bass
|
||||
"([b#♭♮𝄫𝄪]?[A-G1-7][b#♭♮𝄫𝄪]?)" & # chord root
|
||||
"((min|maj|aug|dim|sus|6\\/9|[mM1-9#b♭♮𝄫𝄪Δ+oøoø][0-9]?|\\([1-9#b♭]+\\))*)" & # chord flavor/type
|
||||
"(\\/([b#♭♮𝄫𝄪]?[A-G1-7][b#♭♮𝄫𝄪]?))?" # optional bass
|
||||
let CHORD_PAT = re(CHORD_REGEX)
|
||||
|
||||
proc parseChord*(
|
||||
@ -413,7 +458,7 @@ proc parseBody(ctx: var ParserContext): seq[ChordChartNode] =
|
||||
m = line.match(REDEFINE_KEY_PAT)
|
||||
if m.isSome:
|
||||
let newKeyInt = (
|
||||
cast[int](ctx.curKeyCenter) +
|
||||
ord(ctx.curKeyCenter) +
|
||||
parseInt(m.get.captures[0])
|
||||
) mod 12
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
const PCO_CHORDS_VERSION* = "0.5.0"
|
||||
const PCO_CHORDS_VERSION* = "0.5.4"
|
||||
|
||||
const USAGE* = """Usage:
|
||||
pco_chords [options]
|
||||
@ -23,6 +23,7 @@ Options:
|
||||
--help Print this usage information
|
||||
--debug Enable debug logging.
|
||||
--echo-args Echo the input parameters.
|
||||
--large-print Use the large type styling.
|
||||
"""
|
||||
|
||||
const ONLINE_HELP* = """
|
||||
|
@ -52,7 +52,7 @@ h3 .section-text {
|
||||
.line {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-end;
|
||||
align-items: end;
|
||||
|
||||
margin-left: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
@ -65,10 +65,104 @@ h3 .section-text {
|
||||
}
|
||||
|
||||
.word.space-after { margin-right: 0.5em; }
|
||||
.word.no-chord { align-self: flex-end; }
|
||||
|
||||
.chord {
|
||||
font-weight: 600;
|
||||
margin-right: 0.5em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.chord .flavor {
|
||||
font-variant-position: super;
|
||||
}
|
||||
|
||||
.song-order h3 {
|
||||
font-style: italic;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.song-order li {
|
||||
list-style: none;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
@media screen {
|
||||
body { margin: 1em; }
|
||||
}
|
||||
|
||||
@media print {
|
||||
.page-contents { column-count: 2; }
|
||||
}
|
||||
</style>
|
||||
"""
|
||||
|
||||
const LARGE_PRINT_STYLESHEET* = """
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: sans-serif;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.page-contents {
|
||||
column-width: 336px;
|
||||
column-width: 3.5in;
|
||||
}
|
||||
|
||||
.column-break { margin-bottom: auto; }
|
||||
|
||||
h2 { font-size: 1.25em; }
|
||||
|
||||
section {
|
||||
break-inside: avoid;
|
||||
padding-top: 1em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.125rem;
|
||||
margin-bottom: 0.5em;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
h3 .section-text {
|
||||
font-style: italic;
|
||||
font-size: 1em;
|
||||
font-weight: normal;
|
||||
margin: 0 0.5em;
|
||||
}
|
||||
|
||||
.line {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
|
||||
margin-left: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.word {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.word.space-after { margin-right: 0.5em; }
|
||||
.word.no-chord { align-self: flex-end; }
|
||||
|
||||
.lyric {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.chord {
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.chord .flavor {
|
||||
@ -113,9 +207,9 @@ func scaleDegree(root: ChordChartPitch, p: ChordChartPitch): string =
|
||||
else: raise newException(Exception, "Impossible")
|
||||
|
||||
func format(ctx: FormatContext, chord: ChordChartChord, useNumber = false): string =
|
||||
if not useNumber and chord.original.isSome: return chord.original.get
|
||||
##if not useNumber and chord.original.isSome: return chord.original.get
|
||||
|
||||
elif useNumber:
|
||||
if useNumber:
|
||||
result = "<span class=root>" &
|
||||
ctx.currentKey.scaleDegree(chord.rootPitch) & "</span>"
|
||||
|
||||
@ -127,11 +221,15 @@ func format(ctx: FormatContext, chord: ChordChartChord, useNumber = false): stri
|
||||
ctx.currentKey.scaleDegree(chord.bassPitch.get) & "</span>"
|
||||
|
||||
else:
|
||||
result = $chord.rootPitch
|
||||
if chord.flavor.isSome: result &= chord.flavor.get
|
||||
if chord.bassPitch.isSome: result &= "/" & $chord.bassPitch.get
|
||||
#debug "transposed " & $ctx.sourceKey & " -> " & $ctx.currentKey &
|
||||
# " (distance " & $distance & ")\p\tchord: " & $chord & " to " & result
|
||||
result = "<span class=root>" &
|
||||
renderPitchInKey(chord.rootPitch, ctx.currentKey) & "</span>"
|
||||
|
||||
if chord.flavor.isSome:
|
||||
result &= "<span class=flavor>" & chord.flavor.get & "</span>"
|
||||
|
||||
if chord.bassPitch.isSome:
|
||||
result &= "<span class=bass>/" &
|
||||
renderPitchInKey(chord.bassPitch.get, ctx.currentKey) & "</span>"
|
||||
|
||||
proc transpose(ctx: FormatContext, chord: ChordChartChord): ChordChartChord =
|
||||
result = chord
|
||||
@ -187,6 +285,7 @@ proc toHtml(ctx: var FormatContext, node: ChordChartNode, indent: string): strin
|
||||
result &= "<span class='word"
|
||||
#if node.spaceBefore: result&=" space-before"
|
||||
if node.spaceAfter: result&=" space-after"
|
||||
if node.chord.isNone: result&=" no-chord"
|
||||
result &= "'>"
|
||||
|
||||
if node.chord.isSome:
|
||||
@ -250,7 +349,7 @@ proc toHtml*(
|
||||
result = """<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>""" & cc.metadata.title & "</title>\p"
|
||||
<title>""" & cc.metadata.title & " — " & $ctx.currentKey & "</title>\p"
|
||||
|
||||
for ss in stylesheets:
|
||||
if ss.startsWith("<style>"): result &= ss
|
||||
|
Loading…
x
Reference in New Issue
Block a user