Created
April 8, 2022 01:52
-
-
Save bgerm/5ba155f34aeacfe06761500e7c3cc808 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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