Fork me on GitHub
#instaparse
<
2022-05-29
>
niclasnilsson12:05:49

Hi everyone. I have a grammar problem I don’t know how to get around. I’m parsing guitar chords, and there is an “ambiguity” in the grammar (at least the way I implemented it). The problem is that a chord can have a quality (major/minor for instance). If it’s minor, it’s always written out, but if it’s major it’s often omitted (default). Then after the chord quality, there are intervals. Each interval also have an optional quality and always a number. My problem is that in some cases this leads to two different possible answers. In those cases, the one with a chord quality is always the right one. I could of course always do insta/parses and analyse the results and pick the right one, but I guess/hope there is a better way to express the grammar to avoid this and just get one (correct) result? I’d like the chord-quality to always take precedence / be “greedy”. Is there a way to write the grammar to solve this? Edit: One thing to add is that if there is no chord-quality, it means major (so major is implicitly default), if that helps in any way.

(ns chord-parser
  (:require
    [instaparse.core :as insta]))

(def chord-ebnf-small
  "chord = root chord-quality interval*
   root = #'[A-G]'
   chord-quality = quality?
   interval = quality? number
   number = '7' | '9' | '11' | '13'
   quality = major | minor
   major = 'M'
   minor = 'm'")

(def chord-parser (insta/parser chord-ebnf-small))


(insta/parses chord-parser "C9")
; => ([:chord 
;      [:root "C"] 
;      [:chord-quality] 
;      [:interval [:number "9"]])
; As expected.

(insta/parses chord-parser "Cm")
; => ([:chord 
;      [:root "C"] 
;      [:chord-quality [:quality [:minor "m"]]])
; As expected.

(insta/parses chord-parser "CmM9")
; => ([:chord
;      [:root "C"]
;      [:chord-quality [:quality [:minor "m"]]]
;      [:interval [:quality [:major "M"]] [:number "9"]])
; As expected.

(insta/parses chord-parser "Cm9")
; => ([:chord 
;      [:root "C"] 
;      [:chord-quality] 
;      [:interval [:quality [:minor "m"]] [:number "9"]]
;     [:chord 
;      [:root "C"] 
;      [:chord-quality [:quality [:minor "m"]]] 
;      [:interval [:number "9"]]])
; Ambiguous. The one with an actual chord-quality is the correct one.

(insta/parses chord-parser "Cm9m11")
; => ([:chord
;      [:root "C"]
;      [:chord-quality]
;      [:interval [:quality [:minor "m"]] [:number "9"]]
;      [:interval [:quality [:minor "m"]] [:number "11"]]
;     [:chord
;      [:root "C"]
;      [:chord-quality [:quality [:minor "m"]]]
;      [:interval [:number "9"]]
;      [:interval [:quality [:minor "m"]] [:number "11"]]])
; Ambiguous. The one with an actual chord-quality is the correct one.

Linus Ericsson07:05:55

I would solve this by a separate function that processes the parsed chord entities to default major etc

niclasnilsson07:05:09

Yep, but for me that’s the step after whatever can be done in the parsing step. I changed the EBNF to the following, using the PEG extension “ordered choice” (the / ), and now insta/parse always returns the right one. I will still have to fill in major as default afterwards of course, since that information is implicit.

(def chord-ebnf-small
  "chord = root / root chord-quality / root chord-quality interval* / root interval*
   root = #'[A-G]'
   chord-quality = quality
   interval = quality? number
   number = '7' | '9' | '11' | '13'
   quality = major | minor
   major = 'M'
   minor = 'm'")

Linus Ericsson07:05:07

Yes I understood what you wanted to do. I just cannot see why you want instaparse to do it - it may be possible but it is not really a part of parsing the tabs. The implicit major chords are not really the same as explicit major chords.

niclasnilsson07:05:45

Ah, no, maybe I misunderstand you, but I didn’t want instaparse to fill in major as default. I wanted instaparse to pick the right choice and interpret Cm9 as C minor 9 and not as C (major) with a minor 9. With the PEG ordered choice I managed to get what I wanted.

Linus Ericsson07:05:02

Ah, ok! Now I see. Great that it could be solved with ordered choice.

niclasnilsson07:05:50

Yes, this was the first time I looked into those extensions, but they seem pretty useful.

Linus Ericsson07:05:05

I would extend the root to be C C# D D# E F F# G G# A A# B (and maybe all the b versions as well)

Linus Ericsson07:05:56

And all the colorings, but that might be out of the scope for the example 🙂

niclasnilsson07:05:03

Absolutely. And there are 6, sus, dim and stuff missing as well 🙂

niclasnilsson07:05:56

That’s outside of my current music theory knowledge, but that sounds interesting!

Linus Ericsson07:05:08

No, i meant chord types.

niclasnilsson07:05:39

Ah, as in playing the chord in different ways / places on the neck?

Linus Ericsson07:05:19

That would be something - idk if tabulatures has notation for that, but that was not what I meant either https://www.guitarworld.com/lessons/10-gorgeous-color-chords-can-inspire-your-playing

niclasnilsson07:05:58

Ah, got it. I don’t think I’ve seen tab notation on that, apart from “slash chords” like Am7/G to note the bass note.

niclasnilsson08:05:26

(which may or may not be part of the actual chord)

niclasnilsson08:05:07

and when it’s part of the chord, I guess it actually becomes coloring?

Linus Ericsson08:05:28

I'm in the deep end of the pond here but yes, if you was to play Cmaj7 it would be noted as that, and not C/B

Linus Ericsson08:05:38

I guess it would be a nice thing to be able to convert between (midi) note values and tabulatures, possibly with bass notes...

niclasnilsson08:05:39

Yes, I’m more thinking in the way of what’s “common” in chord progressions, like C, C/E, F

niclasnilsson08:05:13

And I suppose since E is part of C major, it’s coloring, vs if the bass note was something outside of C major, it’s probably something else?

Linus Ericsson08:05:10

Hmm, "as a" bass player i would parse C C/E F as C - E - F

niclasnilsson08:05:16

Fun stuff to think about and learn about!

niclasnilsson08:05:59

Exactly, but the guitarist can also play E in the bass of the C chord.

👍 1
Linus Ericsson08:05:02

One day I will learn musical notation by implementing it.

niclasnilsson08:05:05

Learning (non-computer) stuff through coding is my favourite way of understanding stuff, by far.