This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-05-29
Channels
- # announcements (6)
- # babashka-sci-dev (15)
- # beginners (46)
- # calva (1)
- # clj-kondo (1)
- # clojure-australia (2)
- # clojure-europe (10)
- # clojure-uk (4)
- # clojured (3)
- # clojurescript (16)
- # fulcro (6)
- # helix (1)
- # hyperfiddle (8)
- # instaparse (28)
- # joyride (33)
- # malli (17)
- # off-topic (13)
- # pedestal (3)
- # portal (5)
- # react (1)
- # sci (1)
- # sql (6)
- # vim (1)
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.
I would solve this by a separate function that processes the parsed chord entities to default major etc
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'")
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.
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.
Ah, ok! Now I see. Great that it could be solved with ordered choice.
Yes, this was the first time I looked into those extensions, but they seem pretty useful.
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)
And all the colorings, but that might be out of the scope for the example 🙂
Absolutely. And there are 6, sus, dim and stuff missing as well 🙂
colorings?
That’s outside of my current music theory knowledge, but that sounds interesting!
No, i meant chord types.
Ah, as in playing the chord in different ways / places on the neck?
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
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.
(which may or may not be part of the actual chord)
and when it’s part of the chord, I guess it actually becomes coloring?
interesting
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
I guess it would be a nice thing to be able to convert between (midi) note values and tabulatures, possibly with bass notes...
Yes, I’m more thinking in the way of what’s “common” in chord progressions, like C, C/E, F
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?
Hmm, "as a" bass player i would parse C C/E F as C - E - F
Fun stuff to think about and learn about!
One day I will learn musical notation by implementing it.
Learning (non-computer) stuff through coding is my favourite way of understanding stuff, by far.