From 25d7ddcd1bb03beadbae6ceb2354f1aab9b6e052 Mon Sep 17 00:00:00 2001 From: Jonathan Bernard Date: Mon, 2 Jun 2025 16:21:22 -0500 Subject: [PATCH] Major refactor of internal note storage to better support transposition. Refactor to store notes as key-agnostic scale degrees + alterations (flat, sharp, etc.). We now think of a pitch in different ways: - *Note*: the 7 notes diatonic to C major. Notes capture the principle in western harmony of the common scales having seven distinct diatonic "notes" and allows us to do arithmetic with them (do up three scale degrees). - *Pitch*: the 12 chromatic pitches, regardless key. Pitch allows us to assign unique names and ordinal values to each note of the chromatic scale, allowing us to do arithmetic with them. - *SpelledPitch*: a unique spelling of one of the chromatic pitches (*Note* + alteration) allowing the stylistic choice to use different *Notes* to describe a single *Pitch*. - *ScaleDegree*: a variant of *SpelledPitch* that uses the scale degree instead of the *Pitch* to store a pitch in a key-agnostic manner. To illustrate, the difference, consider the flat-six in the key of Eb. - *Note*: The 6th scale degree in Eb is C (1-E 2-F 3-G 4-A 5-B 6-C). - *Pitch*: In the chromatic scale, ignoring the key, this is the *Pitch* called B. - *SpelledPitch*: In the context of the key of Eb, because this is the *Note* C, we should spell this as Cb, not B. So the spelled pitch is *Note*(C), *Alteration*(flat). - *ScaleDegree*: This captures the key-agnostic representation that we used in the begining: *Number*(6) and *Alteration*(flat) With these four ways of representing a note, we can transpose any pitches that follow western 12-tone harmony arbitrarily between keys preserving the author's choice of chord function (remembering that this is the b6 and not the #5, in our example). Building on this new notational data model, the AST now uses the *ScaleDegree* relative to the provided key as the internal representation of a pitch. Formatting of a *ScaleDegree* always requires the key in which it is being rendered. Transposition is now only a matter or updating the current key. --- pco_chords.nimble | 2 +- src/pco_chordspkg/ast.nim | 174 ++++---------------- src/pco_chordspkg/cliconstants.nim | 2 +- src/pco_chordspkg/html.nim | 91 ++++------- src/pco_chordspkg/notation | Bin 0 -> 156568 bytes src/pco_chordspkg/notation.nim | 245 +++++++++++++++++++++++++++++ 6 files changed, 301 insertions(+), 213 deletions(-) create mode 100755 src/pco_chordspkg/notation create mode 100644 src/pco_chordspkg/notation.nim diff --git a/pco_chords.nimble b/pco_chords.nimble index 2e58e32..b13a6ea 100644 --- a/pco_chords.nimble +++ b/pco_chords.nimble @@ -1,6 +1,6 @@ # Package -version = "0.5.6" +version = "0.6.0" author = "Jonathan Bernard" description = "Chord chart formatter compatible with Planning Center Online" license = "MIT" diff --git a/src/pco_chordspkg/ast.nim b/src/pco_chordspkg/ast.nim index f2dfdf2..8fbdb1c 100644 --- a/src/pco_chordspkg/ast.nim +++ b/src/pco_chordspkg/ast.nim @@ -1,10 +1,11 @@ import std/[nre, strtabs, strutils] +import ./notation import zero_functional type ChordChartMetadata* = object title*: string - key*: ChordChartChord + key*: Key optionalProps: StringTableRef ChordChart* = ref object @@ -23,13 +24,11 @@ type ccnkRedefineKey, ccnkNone - ChordChartPitch* = enum Af, A, Bf, B, C, Df, D, Ef, E, F, Gf, G - ChordChartChord* = object original*: Option[string] - rootPitch*: ChordChartPitch + root*: ScaleDegree flavor*: Option[string] - bassPitch*: Option[ChordChartPitch] + bass*: Option[ScaleDegree] ChordChartNode* = ref object case kind: ChordChartNodeKind @@ -52,12 +51,12 @@ type of ccnkTransposeKey: transposeSteps*: int of ccnkRedefineKey: - newKey*: ChordChartPitch + newKey*: Key of ccnkNone: discard ParserContext = ref object lines: seq[string] - curKeyCenter: ChordChartPitch + curKey: Key curLineNum: int curSection: ChordChartNode unparsedLineParts: seq[string] @@ -77,97 +76,6 @@ iterator pairs*(ccmd: ChordChartMetadata): tuple[key, value: string] = func kind*(ccn: ChordChartNode): ChordChartNodeKind = ccn.kind -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: - # If we aren't told to use sharps or flats, we render the diatonic pitches - # according to standard theory (C# in the key of D, Db in the key of Ab) - # but we render non-diatonic notes with flats (prefer the b6 and b7 over - # the #5 and #6). - # - # TODO: In the future, we should also remember the scale degree of the - # chord when parsing. So, for example, in the key of D we would parse Bb as - # the b6 and A# as the #5. The pitch would be the same, but the scale - # degree would differ. This would allow us to preserve intentional choices - # in the chart when transposing. - scaleNames = case key - of C: - ["A♭", "A", "B♭", "B", "C", "D♭", "D", "E♭", "E", "F", "G♭", "G"] - of G: - ["A♭", "A", "B♭", "B", "C", "D♭", "D", "E♭", "E", "F", "F#", "G"] - of D: - ["A♭", "A", "B♭", "B", "C", "C#", "D", "E♭", "E", "F", "F#", "G"] - of A: - ["G#", "A", "B♭", "B", "C", "C#", "D", "E♭", "E", "F", "F#", "G"] - of E: - ["G#", "A", "B♭", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G"] - of B: - ["G#", "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G"] - of Gf: - ["A♭", "B𝄫", "B♭", "C♭", "D𝄫", "D♭", "E𝄫", "E♭", "F♭", "F", "G♭", "A𝄫"] - of Df: - ["A♭", "B𝄫", "B♭", "C♭", "C", "D♭", "E𝄫", "E♭", "F♭", "F", "G♭", "A𝄫"] - of Af: - ["A♭", "B𝄫", "B♭", "C♭", "C", "D♭", "E𝄫", "E♭", "F♭", "F", "G♭", "G"] - of Ef: - ["A♭", "B𝄫", "B♭", "C♭", "C", "D♭", "D", "E♭", "F♭", "F", "G♭", "G"] - of Bf: - ["A♭", "A", "B♭", "C♭", "C", "D♭", "D", "E♭", "F♭", "F", "G♭", "G"] - of F: - ["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 (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) - -func `-`*(a, b: ChordChartPitch): int = - result = ord(a) - ord(b) - if result < 0: result += 12 - func dump*(m: ChordChartMetadata, indent = ""): string = return indent & "Metadata\p" & join(m.pairs --> map(indent & " " & it.key & ": " & it.value), "\p") @@ -203,14 +111,14 @@ func dump*(ccn: ChordChartNode, indent = ""): string = let chord = ccn.chord.get result &= "[" if chord.original.isSome and chord.flavor.isNone and - chord.bassPitch.isNone: + chord.bass.isNone: result &= chord.original.get else: - result &= $chord.rootPitch + result &= $chord.root if chord.flavor.isSome: result &= "_" & chord.flavor.get - if chord.bassPitch.isSome: - result &= "/" & $chord.bassPitch.get + if chord.bass.isSome: + result &= "/" & $chord.bass.get result &= "]" if ccn.word.isSome: result &= ccn.word.get @@ -256,36 +164,7 @@ template addToCurSection(n: ChordChartNode): untyped = if ctx.curSection.kind == ccnkSection: ctx.curSection.sectionContents.add(n) else: result.add(n) -proc parsePitch*(ctx: ParserContext, keyValue: string): ChordChartPitch = - let normK = keyValue.strip.toLower - case normK - 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", "c♭", "cb", "ces", "cf": return ChordChartPitch.B - of "bs", "bis", "b#", "c", "d𝄫": return ChordChartPitch.C - of "b𝄪", "cs", "cis", "c#", "d♭", "df", "db", "des": return ChordChartPitch.Df - of "c𝄪", "d", "e𝄫": return ChordChartPitch.D - 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#", "g♭", "gf", "gb", "ges": return ChordChartPitch.Gf - of "f𝄪", "g", "a𝄫": return ChordChartPitch.G - 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 +# see https://regexr.com/8f4ru let CHORD_REGEX = "([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 @@ -293,7 +172,8 @@ let CHORD_REGEX = let CHORD_PAT = re(CHORD_REGEX) proc parseChord*( - ctx: ParserContext, chordValue: string + ctx: ParserContext, + chordValue: string ): Option[ChordChartChord] = let m = chordValue.match(CHORD_PAT) @@ -304,16 +184,16 @@ proc parseChord*( some(m.get.captures[1]) else: none[string]() - let bassPitch = + let bass = if m.get.captures.contains(4) and m.get.captures[4].len > 0: - some(ctx.parsePitch(m.get.captures[4])) - else: none[ChordChartPitch]() + some(toScaleDegree(ctx.curKey, parseSpelledPitch(m.get.captures[4]))) + else: none[ScaleDegree]() return some(ChordChartChord( original: some(chordValue), - rootPitch: ctx.parsePitch(m.get.captures[0]), + root: toScaleDegree(ctx.curKey, parseSpelledPitch(m.get.captures[0])), flavor: flavor, - bassPitch: bassPitch)) + bass: bass)) let METADATA_LINE_PAT = re"^([^:]+):(.*)$" let METADATA_END_PAT = re"^-+$" @@ -321,7 +201,7 @@ proc parseMetadata(ctx: var ParserContext): ChordChartMetadata = var title = "MISSING" var optProps = newStringTable() - var songKey: Option[ChordChartChord] + var songKey: Option[Key] while ctx.curLineNum < ctx.lines.len: let line = ctx.nextLine @@ -337,7 +217,12 @@ proc parseMetadata(ctx: var ParserContext): ChordChartMetadata = let value = m.get.captures[1].strip if key == "title": title = value elif key == "key": - songKey = ctx.parseChord(value) + let parts = value.split(" ", 1) + songKey = some(Key( + tonic: parseSpelledPitch(parts[0]), + mode: + if parts.len > 1: parseMode(parts[1]) + else: Ionian)) if songKey.isNone: raise ctx.makeError("unrecognized key: " & value) else: optProps[key] = value @@ -499,14 +384,9 @@ proc parseBody(ctx: var ParserContext): seq[ChordChartNode] = m = line.match(REDEFINE_KEY_PAT) if m.isSome: - let newKeyInt = ( - ord(ctx.curKeyCenter) + - parseInt(m.get.captures[0]) - ) mod 12 - addToCurSection(ChordChartNode( kind: ccnkRedefineKey, - newKey: cast[ChordChartPitch](newKeyInt))) + newKey: ctx.curKey.transpose(parseInt(m.get.captures[0])))) continue m = line.match(COL_BREAK_PAT) @@ -548,7 +428,7 @@ proc parseChordChart*(s: string): ChordChart = unparsedLineParts: @[]) let metadata = parseMetadata(parserCtx) - parserCtx.curKeyCenter = metadata.key.rootPitch + parserCtx.curKey = metadata.key result = ChordChart( rawContent: s, diff --git a/src/pco_chordspkg/cliconstants.nim b/src/pco_chordspkg/cliconstants.nim index ae2cdb4..d56d722 100644 --- a/src/pco_chordspkg/cliconstants.nim +++ b/src/pco_chordspkg/cliconstants.nim @@ -1,4 +1,4 @@ -const PCO_CHORDS_VERSION* = "0.5.6" +const PCO_CHORDS_VERSION* = "0.6.0" const USAGE* = """Usage: pco_chords [options] diff --git a/src/pco_chordspkg/html.nim b/src/pco_chordspkg/html.nim index 9a6e990..9062d84 100644 --- a/src/pco_chordspkg/html.nim +++ b/src/pco_chordspkg/html.nim @@ -1,13 +1,13 @@ import std/[logging, options, os, strutils] import zero_functional -import ./ast +import ./[ast, notation] type FormatContext = ref object chart: ChordChart - currentKey: ChordChartPitch - sourceKey: ChordChartPitch + currentKey: Key + sourceKey: Key currentSection: ChordChartNode numberChart: bool transposeSteps: int @@ -80,7 +80,7 @@ h3 .section-text { height: 1.2em; } -.chord .flavor { +.number-chart .chord .flavor { font-variant-position: super; } @@ -175,7 +175,7 @@ h3 .section-text { height: 1.2em; } -.chord .flavor { +.number-chart .chord .flavor { font-variant-position: super; } @@ -197,62 +197,23 @@ h3 .section-text { """ -func scaleDegree(root: ChordChartPitch, p: ChordChartPitch): string = - let distance = p - root - case distance: - of 0: "1" - of 1: "♭2" - of 2: "2" - of 3: "♭3" - of 4: "3" - of 5: "4" - of 6: "♭5" - of 7: "5" - of 8: "♭6" - of 9: "6" - of 10: "♭7" - of 11: "7" - 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 useNumber: - result = "" & - ctx.currentKey.scaleDegree(chord.rootPitch) & "" + result = "" + if useNumber: result &= $chord.root + else: result &= $ctx.currentKey.spellPitch(chord.root) + result &= "" - if chord.flavor.isSome: + if chord.flavor.isSome: result &= "" & chord.flavor.get & "" - if chord.bassPitch.isSome: - result &= "/" & - ctx.currentKey.scaleDegree(chord.bassPitch.get) & "" + if chord.bass.isSome: + result &= "/" + if useNumber: result &= $chord.bass.get + else: result &= $ctx.currentKey.spellPitch(chord.bass.get) + result &= "" - else: - result = "" & - renderPitchInKey(chord.rootPitch, ctx.currentKey) & "" - - if chord.flavor.isSome: - result &= "" & chord.flavor.get & "" - - if chord.bassPitch.isSome: - result &= "/" & - renderPitchInKey(chord.bassPitch.get, ctx.currentKey) & "" - -proc transpose(ctx: FormatContext, chord: ChordChartChord): ChordChartChord = - result = chord - - let distance = ctx.currentKey - ctx.sourceKey - if distance != 0: - result = ChordChartChord( - original: none[string](), - rootPitch: chord.rootPitch + distance, - flavor: - if chord.flavor.isSome: some(chord.flavor.get) - else: none[string](), - bassPitch: - if chord.bassPitch.isSome: some(chord.bassPitch.get + distance) - else: none[ChordChartPitch]()) func makeSongOrder(songOrder, indent: string): string = result = indent & "
\p" & @@ -298,7 +259,7 @@ proc toHtml(ctx: var FormatContext, node: ChordChartNode, indent: string): strin if node.chord.isSome: result &= "" & - ctx.format(ctx.transpose(node.chord.get), ctx.numberChart) & "" + ctx.format(node.chord.get, ctx.numberChart) & "" result &= "" if node.word.isSome: result &= node.word.get @@ -317,13 +278,13 @@ proc toHtml(ctx: var FormatContext, node: ChordChartNode, indent: string): strin result &= "
" of ccnkTransposeKey: - ctx.currentKey = ctx.currentKey + node.transposeSteps + ctx.currentKey = ctx.currentKey.transpose(node.transposeSteps) result &= indent & "

Key Change: " & $ctx.currentKey & "

" of ccnkRedefineKey: let oldKey = ctx.currentKey - ctx.sourceKey = node.newKey + ctx.transposeSteps - ctx.currentKey = ctx.sourceKey + ctx.sourceKey = node.newKey + ctx.currentKey = ctx.sourceKey.transpose(ctx.transposeSteps) if oldKey != ctx.currentKey: let headingVal = indent & "

Key Change: " & $ctx.currentKey & "

" @@ -342,8 +303,8 @@ proc toHtml*( var ctx = FormatContext( chart: cc, - currentKey: cc.metadata.key.rootPitch + transpose, - sourceKey: cc.metadata.key.rootPitch, + currentKey: cc.metadata.key.transpose(transpose), + sourceKey: cc.metadata.key, currentSection: EMPTY_CHORD_CHART_NODE, numberChart: numberChart, transposeSteps: transpose) @@ -368,17 +329,19 @@ proc toHtml*( result &= " \p" + var bodyClasses = newSeq[string]() + if numberChart: bodyClasses.add("number-chart") if cc.metadata.contains("columns") and cc.metadata["columns"] == "1": - result &= " " - else: - result &= " " + bodyClasses.add("one-column") + else: bodyClasses.add("two-column") + result &= " " var indent = " " # Title result &= indent & "

" & cc.metadata.title & "

\p" - var metadataPieces = @["Key: " & ctx.format(ctx.transpose(cc.metadata.key))] + var metadataPieces = @["Key: " & $ctx.currentKey] if cc.metadata.contains("time signature"): metadataPieces.add(cc.metadata["time signature"]) if cc.metadata.contains("bpm"): diff --git a/src/pco_chordspkg/notation b/src/pco_chordspkg/notation new file mode 100755 index 0000000000000000000000000000000000000000..4236165f16ad44b870c4ba16e16920f788be1514 GIT binary patch literal 156568 zcmeFa33Ob=u|7UB78V900X8^nmH`JFvxwbh6$t|av4dC~oUqu$VC=9dV1ZeT$H;;i zhJj%*h$Ugdst^cD!XgN^Wn(mgunZvqv5WlmAb@}0SJi#DnGxo__kZX7&v{DB zy|=o$y1J^my1KgWorTdUhYYExh}ge%A_qkHR5Ml~^`@ZpxFO~)5~+_&id5sjwUIHA zbpaiUfAyv`e2uN61@>BH@CatT)uudr-O}LMtL2ba)+_f{zqyv!Yotb>S+A0}wfqnM zYURJ~weTZiL?ZU;uP5y_YI!7dT|8as*sH%DWs`qF>xHjX8*06Tx!QV^GV67|uJyv# ze#3^nT6vO3{!18nhOhM~<6ph}Hxdcg>o)bm*X<0xy|NAd@ydTEcPm5TYc0z7S1bSZ zbT$mntQVGteKzQkf!*}Z@_F=o^G;^HaKG$xy{TxgwqAp&ckr#sXZX6GL9|!4!M`I> zk9t_!KVxQ*qfC3YKjCZL21?OhjXFgpbts~Z9{S^HkstC#Q=m-x3)WQ$1eThxT-C$hLFJo0uB9+`32F;i!pe9GCUoI3ZM zd8eFx%+w#Ae%6_%9COmoPd_CxW5%gxoOR}mbLO3N_PiN06txT@aAuu0W7eG0X3Rcm z?&*Ze@=gd3%^i$5n<>a%P=0#?oe%7qhX3v#IVAiut zBWIj6_sq!brZe4Apr3N~*^#qPntKlUnKl2U8MEh}dD7`~FE}M~#wlmaI-`-7b55BT zIcM&vfD5qk{K)L{&Yn9DC}aTi&)_K8i+8j1?$9ZR{osc)cH4Q^JtFqjyW8En-EHSR zA-N&=-#QpuLom*Hum7vSX!q_dY-r?T)Uf{Kx~s3paIB0B7}&_Ub4SpFt^+j?=K(2S zCo;l3H*G$5J+?Pm)#UnF>kqBcd(6CK;q7BV41Z1tUN=t(4U~ab{Y}d)ok){Dj}F0W z4SakEKGVP_h2U`mkA>h_1D_s(cN+Md5Il0O@-sgKuQBjM2wrF4ts!{5fwzU=GYz~W z1kV_FcL<&}@SYId;yWRDr@rK0f5WLaATSM@;fwzU=Sp)9~!Fvt7I|QFOU**sff@clf3Be=h zEB-(TUT@%4F9ziwH}KIRc&CAn55a3M(E5`?@OlG}h2XV|6@PjNUT5HQLhyP6pC5uZ z7gy5Y9K0gHSHSk0T9#1Kq))2h* z3Wc|Y;JpUk5rW6lir*cA*IlXbo)Em=z?~4h!N3PX@Ri!20lFm?=|o_A^6Pel>Yn>JZ|8L5InL( z@moXiS_5wj!LtV55rTIbcy|b1f4$Q03BhL?xD$f+8u&m69=Sp3RK04h8ooy220l6j zx8req2yVyGq!8TNZ7c-0b~`-;&zN@Sgy2~NpC5v^8+alF&l`Aa2;OhtZ6SC~tM-VOI;Ps~6IU%_9d-Fr^27{jn!L8qG z4Z*E{YYV|=8af?exb92j4_6^*_CaALdP& zt91WNo{DhhFWo<*FXo|@V=bxj!I=xSe>FZhY1zNgKDgvdO+l>>&Nl4dcpu!_D9h@6 za3pTrzezs$a2Fbh)cfG;``|Gj90?-#ufYemTMaCn?t@pm*pbLgAKd1kSUkrE-^hpm zf4Bc9f&Y`h|4HEgB=G-F3H-Bq>n~%?tE*zE%0Dcxh{Ret=M7ntk2SBV;!as4vS!bx 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 7: + raise newException(ValueError, "Invalid scale degree: " & $degree.number) + + result = MajorIntervals[(degree.number - 1)] + ord(degree.alteration) + + +func toPitch*(n: Note): Pitch = + cast[Pitch]((4 + MajorIntervals[(ord(n) + 5) mod 7]) mod 12) + + +func toPitch*(sp: SpelledPitch): Pitch = + cast[Pitch]((ord(sp.note.toPitch) + ord(sp.alteration) + 12) mod 12) + + +func toNote*(p: Pitch): Note = + case p + of Af, A: Note.A + of Bf, B: Note.B + of C: Note.C + of Df, D: Note.D + of Ef, E: Note.E + of F: Note.F + of Gf, G: Note.D + + +func parseMode*(str: string): Mode = + case str.toLower.strip + of "ionian", "major", "": Ionian + of "dorian": Dorian + of "phrygian": Phrygian + of "lydian": Lydian + of "mixolydian": Mixolydian + of "aeolian", "minor": Aeolian + of "locrian": Locrian + else: raise newException(ValueError, "Unrecognized mode/scale: " & str) + + +func parseSpelledPitch*(str: string): SpelledPitch = + try: result.note = parseEnum[Note]($str[0]) + except: + raise newException(ValueError, str[0] & " is not a recognized pitch name.") + + result.alteration = + case str[1..^1] + of "𝄫", "bb", "♭♭": naDoubleFlat + of "b", "♭", "f", "es": naFlat + of "", "♮": naNatural + of "s", "is", "#": naSharp + of "𝄪", "##": naDoubleSharp + else: raise newException(ValueError, + str[1..^1] & " is not a recognized accidental.") + + +func parseScaleDegree*(str: string): ScaleDegree = + try: result.number = parseInt($str[0]) + except: + raise newException(ValueError, str[0] & " is not a valid scale degree.") + + result.alteration = + case str[0..^2] + of "𝄫", "bb", "♭♭": naDoubleFlat + of "b", "♭", "f", "es": naFlat + of "", "♮": naNatural + of "s", "is", "#": naSharp + of "𝄪", "##": naDoubleSharp + else: raise newException(ValueError, + str[0..^2] & " is not a recognized accidental.") + + +func ionianPitch*(key: Key, degreeNumber: int): Pitch = + cast[Pitch]((ord(key.tonic.toPitch) + MajorIntervals[degreeNumber - 1]) mod 12) + + +#[ TODO +func spellPitch*(key: Key, p: Pitch): SpelledPitch = + +]# + +func toSpelledPitch*(p: Pitch): SpelledPitch = + SpelledPitch( + note: p.toNote, + alteration: + case p + of Af, Bf, Df, Ef, Gf: naFlat + else: naNatural) + +func spellPitch*(key: Key, sd: ScaleDegree): SpelledPitch = + result.note = key.tonic.note + (sd.number - 1) + let resultingPitch = ord(ionianPitch(key, sd.number)) + ord(sd.alteration) + result.alteration = cast[NoteAlteration]( + resultingPitch - ord(result.note.toPitch)) + + +func toScaleDegree*(key: Key, sp: SpelledPitch): ScaleDegree = + result.number = sp.note - key.tonic.note + 1 + result.alteration = cast[NoteAlteration]( + ord(sp.toPitch) - ord(ionianPitch(key, result.number))) + + +func transpose*(k: Key, steps: int): Key = + Key( + tonic: toSpelledPitch(k.tonic.toPitch + steps), + mode: k.mode) + + +when isMainModule: + assert A + 1 == Bf + assert A + 12 == A + assert A - 1 == Af + assert A + 14 == B + + assert C - A == 3 + assert A - G == 2 + assert G - A == 10 + + assert Note.D - Note.B == 2 + assert Note.A - Note.C == 5 + assert Note.C - Note.A == 2 + + assert chromaticDistanceFromTonic(ScaleDegree(number: 1, alteration: naNatural)) == 0 + assert chromaticDistanceFromTonic(ScaleDegree(number: 5, alteration: naNatural)) == 7 + assert chromaticDistanceFromTonic(ScaleDegree(number: 5, alteration: naDoubleFlat)) == 5 + assert chromaticDistanceFromTonic(ScaleDegree(number: 7, alteration: naFlat)) == 10 + assert chromaticDistanceFromTonic(ScaleDegree(number: 3, alteration: naDoubleSharp)) == 6 + + assert $SpelledPitch(note: Note.B, alteration: naFlat) == "B♭" + + assert A == parseSpelledPitch("A").toPitch + assert B == parseSpelledPitch("B").toPitch + assert C == parseSpelledPitch("C").toPitch + assert D == parseSpelledPitch("D").toPitch + assert E == parseSpelledPitch("E").toPitch + assert F == parseSpelledPitch("F").toPitch + assert G == parseSpelledPitch("G").toPitch + + assert Af == parseSpelledPitch("Ab").toPitch + assert G == parseSpelledPitch("Abb").toPitch + assert B == parseSpelledPitch("Cb").toPitch + assert Df == parseSpelledPitch("B##").toPitch + + assert "3" == $toScaleDegree( + Key(tonic: parseSpelledPitch("B"), mode: Ionian), + parseSpelledPitch("D#")) + + assert "1" == $toScaleDegree( + Key(tonic: parseSpelledPitch("Gb"), mode: Ionian), + parseSpelledPitch("Gb")) + + assert parseSpelledPitch("Cb").toPitch == parseSpelledPitch("B").toPitch + + assert "♭6" == $toScaleDegree( + Key(tonic: parseSpelledPitch("Eb"), mode: Ionian), + parseSpelledPitch("Cb")) + + assert "#5" == $toScaleDegree( + Key(tonic: parseSpelledPitch("Eb"), mode: Ionian), + parseSpelledPitch("B"))