Hi all,
I apologise for a complete and utter lack of movement here - 2022 has been the wrong kind of exciting for me and I've simply had to let stuff slide, this included. @mrreason 's done some work in this area recently, so I figure an update is warranted.
I spent a bit of time discussing and working out how to recursively parse the chord modifiers, I came up with this code:
this is behaving as I would expect, you can pass it any combination of any number of chord modifiers, it will crash if you pass it an unknown option :
Just a quick my-two-bits drive-by: Parsec / AttoParsec / MegaParsec is almost surely the way to go.
TBH I wouldnāt be too worried about the data structure, as the grammar itself is rather more important in terms of design (though in this case largely already settled, unless thereās some need/want of a change in grammar.) The code around the resulting data structure can easily-enough lean hard into the type system for confident maintenance and expansion.
ok, I spent a bit of time to implement the following:
I first tried to do it via a data structure Chord but that wasn't really leading me anywhere, so I added a node to TPat, specifically for chords, made a simple sum type for Modifiers. Then i implemented parsers for the modifiers and lastly a function
chord :: (Num a, Enum a) => Pattern a -> Pattern String -> [Pattern [Modifier]] -> Pattern a
that can turn given patterns of notes/doubles, a pattern of chord names and a list of patterns of modifiers into a single combined pattern that represents the specified chord. An example:
chord "c" "<maj min>" ["i","10"] :: Pattern Note is equivalent to "c'<maj min>'i'10" :: Pattern Note.
what this means in summary is that all of the old features should still behave the same, but now you can also do most of the things that were collected in this thread (patterning everything, stacking modifiers, dropN notation, new inversion syntax).
my changes also seperated the syntax and semantics more clearly, at the expense of a more tricky TPat type, depended on some language extensions.
It would be great if some people could look at the code and let me know what they think.
Also some people testing the code would be perfect (clone the repo and cabal repl, then import Sound.Tidal.Context and :set -XOverloadedStrings when in the repl, then you can type expressions like note "c'maj'i" and try to generate some unexpected result)
I apologise for not replying back here (or doing adequate testing), this year blew up on me a bit.
I do just want to say thankyou though, this was a big feature I was interested in and I'm finally getting some time to do some experimentation and play, and they have opened a world of possibilities
The only quirk I came across is to do with the list ordering used in conjunction with arpeggiating functions -
For example:
c'min9 == [0,3,7,10,14]
Grow it to 8 notes:
c'min9'8 == [0,3,7,10,14,12,15,19]
you can see that the numbers are no longer ordered small to large - this doesn't matter for chord usage, but it does create an effect when arpeggiating the chord (no longer running low to high)
This is not specifically a bug, but it was a bit unexpected when I encountered it.
Should it be in numerical order? What do other people think?
I think there is no disadvantage in always ordering the chords since we're dealing with note values and not pitch-classes could you please open an issue about it so we don't forget?
I'm not sure about this. The ordering [0,3,7,10,14,12,15,19] is quite logical to me:
It first starts with the chord notes c'min9 = c,ef,g,bf,d on one octave, then it starts with the same chord on the next octave, c, ef, g. If I was arpeggiating this chord on the piano, I'd probably do it this way.
After reordering, the d is like the second note on the scale (like a sus2) rather than the ninth.