RFC: working on making chord naming/chordList more consistent

I would like to second Raph's suggestion about including some new syntactic sugar for defining our own pitch collections on the fly. I think the strength of Tidal is in how it does things differently and is fun and responsive sandbox environment. It would be great to be able to quickly define these pitch collections and then be able to transform them with inversions and drop or open voicings as well. Currently the discussion on harmony has been tertiary focused but implementing something like what Raph describes would make working in quartal, quintal, and secundal harmonies far easier and more flexible.

Edit: Also on a related note has anybody had experience with Euterpea and/or reading the Haskell School of Music? http://euterpea.com/haskell-school-of-music/. I have been slowing going through this book and it is actually quite awesome how it takes you step by step through the process of creating Euterpea and is a decent (if fast paced) intro to Haskell programming. Anyway I'm reading it currently while trying to learn Haskell with "Learn You a Haskell", so I'll report back about anything interesting I find that might give us some insight into how to proceed.

2 Likes

Some great stuff in here! I've skimmed quickly, and plan to revisit in detail but I'll just call out a couple of highlights:

This is cool! I think this could be a good next step on my learning journey

The concept of arbitrary pitch collections is something that is very present in my head when I'm thinking about this stuff - a set of common use chord shortcuts, and then the ability to quickly define and modify arbitrary note collections as though they were a standard chord, is an endpoint I'm hoping to get to

This looks from the outside like something I will need to work through too - thanks a lot!
as a side note, I have a selfish interest in using this project to advance my haskell skills in order to contribute more, but I am interested in seeing what others are coming up with too and my time is extremely limited, so please feel free to work on and share stuff that will move us along :slight_smile:

2 Likes

a couple more updates, I went back to the drawing board to redo my workflow so that I could separate breaking changes, dependent changes, etc so all the github links above don't really apply anymore (they still exist, but link to unattached forks).

I've made three PRs now,

  1. Adding domN and addN chords - merged
  2. Sorting and consistent naming changes - pending merged
  3. Add open voicing for chords 'o option - pending merged

None of these include any breaking changes, so I'm hoping they'll be acceptable to add to the next minor release (1.7.2).

...and with that, I think I've met the initial improvement targets. I've got a good idea of where I'll be heading from here, based on the comments, I think everyone is in agreement with that direction, namely:

  1. allow stacking of chord modifiers (ie 'maj7'ii'o'10 should work as expected) Issue #777
  2. add dropN notation (ie 'maj7'd2) @Raph
  3. update inversion notation in line with drop notation (ie 'maj7'i2 for second inversion) @RTylerMclaughlin
  4. allow patterning of the chord modifiers (eg 'maj7'i<2 3 4>) @kit-christopher Issue #620
  5. allow "exploding" chords ie spreading them over N octaves @Raph
  6. consider adding complementary functions for each of these modifiers (ie invert 2 "0'maj7" as a complement to "0'maj7'i2") @ghales
  7. define our own pitch collections on the fly, so that they can be manipulated/patterned in the same way as our predefined chords @Raph @wshbmusic

I would call these the intermediary steps, before we head into @jwaldmann's semantics territory -

If I get through this batch on my own, I will be in far better shape skill-wise to consider attacking the semantics application ... wish me luck! :slight_smile:

11 Likes

huge leap forward! so excited to see these changes come to fruition!

1 Like

eek I forgot one!

  1. added :slight_smile:

Nice one @cleary! I've merged the PRs. What could be really nice is having a list of expected mini-notation chords together with expected outputs. Then we can add that to the automated tests to catch regressions.

Thankyou!

Can do, I'll look at this next :+1:

1 Like

Hi,

hope this isn't is (1) a dump question: Is there a place where I can learn about the new chord naming conventions or is this knowledge right now only available within this thread?

(1) EDIT: oops... :wink: I really don't mean to ask dump question on purpose.

1 Like

Hi, this will be a place when it happens, I'll update the tidal cycles wiki as well

At this point there are no major changes, I've just made things more consistent (eg ref the m9 but no min9 mentioned above

Next change will be a test suite, then I'll probably start on the complementary functions option (6), which I think might be the next easiest task/best path to learn from.

None of these will introduce any changes to the current notation though

2 Likes

A quick update, I've procrastinated long enough and am looking at this again, beginning with the unit tests. I have a steep curve ahead, and it's slow going but if you're interested there's a branch here where I'm working:

3 Likes

chord test PR is in

Next on the list is to make 'o not crash when chord length is < 3 and add a test case :wink:

Then I may try and look at #777 - going to need some more steep learning I think

2 Likes

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 :

modsFunc :: [Float] -> [String] -> [Float]
modsFunc xs [] = xs
modsFunc xs ["o"] = if (length xs > 2)
                      then [ (xs !! 0 - 12), (xs !! 2 - 12), (xs !! 1) ] ++ reverse (take (length xs - 3) (reverse xs))
                    else xs
modsFunc xs ['i' : is] =  take (length xs) $ drop ((length is) + 1) $ concatMap (\x -> map (+ x) xs) [0,12..]
modsFunc xs [m] = if (length xs > (read m :: Int)
                    then take (read m :: Int) xs
                  else xs
modsFunc xs ms = modsFunc (modsFunc xs (init ms)) [(last ms)

you can call it as per: modsFunc [0,2,4,6] ["ii", "o", "12"] where the chord is the first list, and the modifiers sit in the second list, in any order

If you're interested in checking out the discussion that got to this point, please see:

@yaxu had some recommendations for parsing differently which I was planning to explore but have been unable to do so after more than a year :frowning:

If anyone would like to pick this up, be my guest!

2 Likes

yes i also think this would be a good way to do it.

i think we would have to follow these steps:

  • First introduce a good data structure representing chords data Chord = ..
  • write a parser for it (for a given string input like "c'maj7'ii" yields a Chord
  • implement the semantics (a function Chord -> [Double])
  • integrate the parser with the mini parser
  • integrate the semantics (TPat Chord -> Pattern Double)

imo the hardest part is to define a good date structure that is flexible enough to handle new modifiers in the future

1 Like

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.

1 Like

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)

3 Likes

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 :pray:

I've spent some time documenting the changes too, which should end up on this page shortly:
https://tidalcycles.org/docs/reference/harmony_melody

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?

3 Likes

I think there is no disadvantage in always ordering the chords since we're dealing with note values and not pitch-classes :slight_smile: could you please open an issue about it so we don't forget?

2 Likes

Done! Thanks again :slight_smile:

1 Like

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.

Wishing you all the best, @cleary!! Thanks a lot for even starting the conversation :slight_smile:
Makes me excited about the future of tidal

ps. +1 for ordering chords - it makes things like arp easier to reason about

1 Like