Skip to content

Instantly share code, notes, and snippets.

@bgerm
Created April 8, 2022 01:52
Show Gist options
  • Save bgerm/5ba155f34aeacfe06761500e7c3cc808 to your computer and use it in GitHub Desktop.
Save bgerm/5ba155f34aeacfe06761500e7c3cc808 to your computer and use it in GitHub Desktop.
// Parses:
//
// C7, C Major, Cm (as a Chord)
// 1 b3 5 (as Degrees)
// C E F (as Notes)
//
//
// Notes:
//
// `-` denotes only a minor chord.
// It could otherwise mean a flat or lowered interval.
//
// `+` denotes only an augmented chord.
// It could otherwise mean sharp or raised interval.
//
//
// Disclaimer:
//
// I have no music background, so I started this to better
// understand music notation. It could be wrong.
{
function loc() {
if (options.withLocation) {
return range();
}
return undefined;
}
}
Start
= Notes /
Inversion /
Polychord /
Slashchord /
Chord /
Polydegrees /
Slashdegrees /
Degrees
// ---- Notes ----
Notes =
value:NoteList {
return { type: 'notes', value: value };
}
NoteList =
head:Note tail:(" "+ @Note)+ {
return [head, ...tail];
}
// Ugly hack to prevent add, aug, dom, dim from matching as a chord
Note =
note:$(NoteLetter Accent?) octave:(IntervalNums)? !("dd"i/[h-z]i) {
return { type: 'note', note: note, octave: octave, location: loc() }
}
// ---- Degrees ----
Degrees =
value:DegreeList {
return { type: 'degrees', value: value };
}
DegreeList =
head:Degree tail:(" "+ @Degree)* {
return [head, ...tail];
}
Degree =
value:Interval {
return { type: 'step', value: value, location: loc() }
}
// ---- Chords ----
Chord
= root:Root _
first:(
MajorOrMinor
/ MajorDefaults
/ Diminished
/ HalfDiminished
/ Dom
/ Aug
/ Sus
/ Group
)?
_
second:(
@(
Add
/ MajorOrMinor
/ Alt
/ Sub
/ Omit
/ AltImplied
/ Group
) _
)* {
return {
type: "chord",
value: {
root: root.value,
attributes: first === null
? [{ type: 'major', value: 'triad' }, ...second]
: [first, ...second],
},
};
}
Slashchord
= left:Chord _ "/" _ right:Chord
{
return { type: 'slashchord', left: left, right: right, location: loc() }
}
Slashdegrees
= left:Degrees _ "/" _ right:Degrees
{
return { type: 'slashdegrees', left: left, right: right, location: loc() }
}
Polychord
= left:Chord _ "|" _ right:Chord
{
return { type: 'polychord', left: left, right: right, location: loc() }
}
Polydegrees
= left:Degrees _ "|" _ right:Degrees
{
return { type: 'polydegrees', left: left, right: right, location: loc() }
}
Inversion
= left:Chord _ "/" _ right:Chord _ ("pedal" / "ped"i "."? / "bass")
{
return { type: 'inversion', left: left, right: right, location: loc() }
}
Root
= value:($(NoteLetter(Accent)?)) {
return { type: 'root', value: value, location: loc() }
}
Major
= ("major"i / "maj"i[.]? / "ma"i!("d"i) / "M"!("I")) { return 'major' }
Minor
= ("minor"i / "min"i[.]? / "mi"i / "m" / "-") { return 'minor' }
MajorOrMinor
= quality:(Major / Minor) _ value:(ExtendedNums / SixesAndFives)?
{ return { type: quality, value: value || 'triad', location: loc() } }
/ quality:("Δ"{ return 'major' }) value:(ExtendedNums)
{ return { type: quality, value: value, location: loc() } }
MajorDefaults
= "Δ"
{ return { type: 'major', value: "7", location: loc() } }
/ value:SixesAndFives
{ return { type: 'major', value: value, location: loc() } }
Dom
= ("dominant"i / "dom"i / "") _ value:ExtendedNums {
return { type: 'dominant', value: value, location: loc() }
}
Aug
= ("augmented"i / "aug"i / "+") _ value:ExtendedNums? {
return { type: 'augmented', value: value === null ? 'triad' : value, location: loc() }
}
Diminished
= ("diminished"i / "dim"i / "°" / "o") _ value:ExtendedNums? {
return { type: 'diminished', value: value === null ? 'triad' : value, location: loc() };
}
HalfDiminished
= ("ø" / "1/2dim" / "½dim" / "/o") value:ExtendedNums? {
return { type: 'half_diminished', value: value === null ? '7' : value, location: loc() };
}
Sus
= "sus"i value:[247] { // sus7 really isn't a thing, but it is on the internet
return { type: 'suspended', value: value, location: loc() }
}
Add
= "add"i _ value:Interval
{ return { type: 'add', value: { type: 'interval', value: value, location: loc() } } }
/ "add"i value:(Sus / MajorOrMinor)
{ return { type: 'add', value: value, location: loc() } }
Sub
= "sub"i _ value:Interval {
return { type: 'sub', value: value, location: loc() }
}
Omit
= ("no"i / "omit"i / "drop"i) _ value:Interval {
return { type: 'omit', value: value, location: loc() };
}
Alt = ("alt"i "."?) _ value:Alterations? {
return { type: 'alt', value: value === null ? 'all' : value, location: loc() };
}
AltImplied = value:Alterations {
return { type: 'alt', value: value, location: loc() };
}
Group
= "(" value:(IntervalList / (MajorOrMinor / Omit / Add / Sub / Alt)+) ")" {
return { type: 'group', value: value, loc: loc() };
}
// Comma-delimited list of intervals
IntervalList =
head:IntervalWithLocation tail:(_ "," _ @IntervalWithLocation)* {
return [head, ...tail];
}
IntervalWithLocation =
value:Interval {
return { type: 'add', value: { type: 'interval', value: value }, location: loc() }
}
// ----- Music Helpers -----
// Allowed interval numbers
IntervalNums = "2" / "3" / "4" / "5" / "6" / "7" / "8" / "9" / "10" / "11" / "12" / "13" / "1"
ExtendedNums = @("7" / "9" / "11" / "13")("th")?
Alterations = $(Accent ("3" / "5" / "9" / "11" / "13" / "1"))
SixesAndFives = "6/9" / @("6" / "5")"th"?
Interval = $(Accent?IntervalNums)
NoteLetter = "A" / "B" / "C" / "D" / "E" / "F" / "G"
/* can you have a natural or double sharp in the interval? */
Accent = [b♭#♯♮][b♭#♯♮]?
// ----- General Helpers -----
// Space
_ = " "*
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment