no-chris / chord-symbol

The definitive chord symbol parser and renderer for Javascript/NodeJS.
https://chord-symbol.netlify.app
MIT License
151 stars 8 forks source link

iReal Pro parsing and MusicXML representation #321

Closed infojunkie closed 3 years ago

infojunkie commented 3 years ago

Hello!

Your module looks fantastic, and it's great that you are basing your work on solid literature. I am working on a leadsheet converter from iReal Pro to MusicXML, and I'm currently hand-parsing the iReal Pro chords, and hand-generating the MusicXML chords.

Since your module seems so, um, modular, it looks like a good candidate to replace both these functions in a coherent whole. I'm thinking specifically of the following:

Thanks!

infojunkie commented 3 years ago

Output MusicXML harmony elements - noting that their specification is pretty close to your approach

To be clear, I am not requesting to output a MusicXML text string: a JSON structure that contains the equivalent info is much more useful.

In the coming days, I will be attempting to replace my handmade chord handling with your module and will provide more specific feedback here.

no-chris commented 3 years ago

Hello!

Looks like the perfect match indeed 👍 Thanks for the kind words. Your project is a cool one and I'll be glad to help ;)

The parsing mostly works out of the box. I've added all iReal Pro symbols to the unit test suite and most were already there under a different variation: https://github.com/no-chris/chord-symbol/pull/322/files

I'm just unsure about 2 of them:

Now for the output imho it should belong to its own repo as a custom filter for chord-symbol renderer. That way it would be a generic feature available for other to use. Something like:

import { chordPaserFactory, chordRendererFactory } from 'chord-symbol';
import { musicXmlRenderer } from 'chord-symbol-music-xml';

const parseChord = chordParserFactory();
const renderChord = chordRendererFactory({ 
    customFilters: [musicXmlRenderer], 
    printer: 'raw' 
});

const chord = parseChord('C^7'); // some iReal Pro chord symbol

console.log(renderChord(chord)); // <- the Music XML Json object

What musicXmlRenderer would do is build the Music XML Json representation from the internal chord-symbol one. It could completely replace it or just add a new property (chord.musicXml for ex).

I haven't checked all details of the harmony object but I see multiple cases:

What do you think of this approach?

infojunkie commented 3 years ago

Thanks for your helpful reply!

do you understand C^ as identical to C^7? If so I need to fix the ^ modifier

I just tested on iReal Pro, and yes they play the same.

C-b6 I'm really not sure about that one...

It exists among the available iReal Pro chord qualities, and it seems to be a known chord - so it sounds legit to me. It's just a C- with an added b6 if I understand correctly.

Regarding MusicXML:

Now for the output imho it should belong to its own repo as a custom filter for chord-symbol renderer.

I would argue that since MusicXML is a W3C standard, it would make sense to include this filter in the main repo.

Otherwise, yes, the API makes sense to me - adding a chord.musicXml attribute sounds great.

I will have more to say about the specifics of the proposed MusicXML filter when I work on the integration.

Thanks again!

ecstrema commented 3 years ago

I've indeed seen C-b6 in the real book. Even though it seems more logical to use AbMaj7/C, the reason for this flat sixth was the chord progression:

| C-  C-^7 | C-7  C-6 | C-b6 A-7(b5)| ...
                         Î                

@infojunkie Good idea! Do you use Musescore or something after the conversion to play your musicxml files or are you crafting an accompaniment generator?

infojunkie commented 3 years ago

Do you use Musescore or something after the conversion to play your musicxml files or are you crafting an accompaniment generator?

@Marr11317 both, actually: I use MuseScore to validate and edit the generated MusicXML (e.g. to add the melodic head), then I plan to use the finalized MusicXML with a combination of OSMD, MMA, and a Web MIDI player to reach a playable version including accompaniment. That's the medium-term plan at least :sweat_smile:

no-chris commented 3 years ago

do you understand C^ as identical to C^7? If so I need to fix the ^ modifier

I just tested on iReal Pro, and yes they play the same.

Right. So I'll fix it and I guess Δ too.

the reason for this flat sixth was the chord progression

Got it. So we need support for it as well. But then I assume the 5 would be left out of the chord correct?

I will have more to say about the specifics of the proposed MusicXML filter when I work on the integration.

Great! Let us know about the progress. At some point could you post a sample of the JSON harmony object to get an idea of the missing information?

ecstrema commented 3 years ago

But then I assume the 5 would be left out of the chord correct?

Not sure about that. Let me ask someone more competent, but I think technically the 5th should be there even if the pianist may decide to omit it (like he does for other fifths). I would simply render C-b6 exactly like `AbMaj7/C', so including the G.

ecstrema commented 3 years ago

(In my book, that's the difference between a b6 and a #5: the b6 should contain the 5th.)

infojunkie commented 3 years ago

I just pushed a branch where I use chord-symbol instead of my handmade chord parsing. Here's the relevant function. Some comments based on this experience - please forgive my ignorance / misunderstandings:

I hope this is constructive enough to continue this conversation :pray: Thanks!

ecstrema commented 3 years ago
* How come "Ab(add b9,#11)" shows #11 in `alterations`, not in `adds` ? The original "Ab" has no 11 extension. I would have expected `alterations` to only affect degrees that exist in the chord's quality + extensions

If I recall correctly, that is standard jazz notation: #11 is always an extension. Do you mean that because it's "Ab(..." and not "Ab7(..." it should be considered an addition?

infojunkie commented 3 years ago

Do you mean that because it's "Ab(..." and not "Ab7(..." it should be considered an addition?

Right: Ab has only a major triad. Even Ab7 is triad + dominant 7, which still does not contain the 11 extension. Of course, I'm approaching this not from a musician's convention, but from a consistency point of view. So the "notation" part is less relevant than the "structural" part.

ecstrema commented 3 years ago

what is an alteration for you if they exist only to replace additions?

Does that mean that in G11(#11) the 11 would be adding two extension: 9 and 11, and then the #11 would alter the existing 11th?

infojunkie commented 3 years ago

Does that mean that in G11(#11) the 11 would be adding two extension: 9 and 11, and then the #11 would alter the existing 11th?

Your specific example does not work at https://chord-symbol.netlify.app/, but G13(#11) works, and it does not show the 11 in the extensions. I would have expected it to show there, as per my argument that

I would have expected alterations to only affect degrees that exist in the chord's quality + extensions

To be precise, I don't expect alterations to show for additions, but for extensions and for the degrees in the core quality. It's not a big deal, btw, since practically my code needs to loop on all those arrays :-)

no-chris commented 3 years ago

Many thanks for the investigation and the great feedback! 🙏

Not sure how to handle the qualities conversion. My first intuition would be to build something like: https://github.com/no-chris/chord-symbol/blob/802cbb326ee29a902b466d58fb19c6b0765694ef/src/parser/filters/normalizeDescriptor.js#L131

In order to do that you need a "straight", or "unaltered", version of the chord, which could totally be part of the chord-symbol object since I'm already building that information in the referenced filter above. Then the degrees would be built by looping over the alterations, as you are already doing.

I have the feeling this would be a more robust approach than deriving MusicXML chord qualities from a mix of chord-symbol qualities and extensions/alterations.

I can quickly add this information to an experimental branch, if that makes sense?

How come "Ab(add b9,#11)" shows #11 in alterations, not in adds ? The original "Ab" has no 11 extension. I would have expected alterations to only affect degrees that exist in the chord's quality + extensions

I think I see your point. I need to dig a bit in my supporting literature to find out if this has some kind of background or if it is just an inconsistency with Cb13 and Cb9

Altered chords show as alt in alterations. Would be great to spell out the actual degrees there.

Alt chords are a special case, since the alterations themselves are configurable. You can easily reconstruct that information based on the parser configuration object: https://github.com/no-chris/chord-symbol/blob/master/API.md#AltIntervals

Would be great to return an explanation when a chord fails to parse

👍

I suggest adding a parameter to isSuspended to distinguish between sus4 and sus2

I knew this topic would come up at some point but Mark Harrison makes it very clear that there is no such thing as a suspended2 chord. Since the usage is so widespread (it even made it to MusicXML 😄), maybe we could register a sus2 intent to allow building the MusicXml quality, for example.

no-chris commented 3 years ago

what is an alteration for you if they exist only to replace additions?

Does that mean that in G11(#11) the 11 would be adding two extension: 9 and 11, and then the #11 would alter the existing 11th?

In my understanding that would have been the case, and prior to adding the interval consistency filter this is probably what chord-symbol would have yield.

no-chris commented 3 years ago

What's interesting is that #11 comes out as an add if the chord is minor Cm(#11) so there is definitely some logic behind this, that I don't remember. Same thing with Cb13 vs Cm(b13) https://github.com/no-chris/chord-symbol/blob/802cbb326ee29a902b466d58fb19c6b0765694ef/src/parser/filters/normalizeDescriptor.js#L271

In that case the alterations relates to the scale and not the chord extensions.

infojunkie commented 3 years ago

In order to do that you need a "straight", or "unaltered", version of the chord, which could totally be part of the chord-symbol object since I'm already building that information in the referenced filter above. Then the degrees would be built by looping over the alterations, as you are already doing. I have the feeling this would be a more robust approach than deriving MusicXML chord qualities from a mix of chord-symbol qualities and extensions/alterations.

My code which is referenced above does not attempt to recreate the "missing" chord qualities from further examination of the alterations: I only map what chord-symbol returns to the MusicXML equivalent. I was just highlighting the differences for you to decide whether you want to enlarge the set of qualities (e.g. to include half-diminished, dominant9, etc.) or if you think that your set is "more correct" than MusicXML's. I have no strong opinion either way, but it seems that MusicXML defines "quality" differently than you do.

Regardless, though, I think it's important to keep extensions, alterations, adds and omits consistent so that any downstream application such as mine can rely on them for further processing. Specifically, to clearly define the logic behind each array, to resolve the apparent inconsistencies discussed above.

maybe we could register a sus2 intent to allow building the MusicXml quality, for example.

More info about the sus2 "controversy" on Stack Exchange. Yes, an intent would be fine. Would you consider a half-diminished intent too? Assuming it's not a legit quality.

Alt chords are a special case, since the alterations themselves are configurable. You can easily reconstruct that information based on the parser configuration object

Yes, that's what my code does - I was commenting from the pov of a user of your API, how this is an additional complexity to keep in mind and that will probably make its way into every client code.

Most immediately, recognizing "^" and "-b6" chords will allow me finalize the chord-symbol integration. Next step may be to move my chord-symbol conversion function to a filter in your repo. Or we can keep the status quo and go about the rest of our lives :joy:

Thanks!

no-chris commented 3 years ago

My code which is referenced above does not attempt to recreate the "missing" chord qualities from further examination of the alterations: I only map what chord-symbol returns to the MusicXML equivalent.

Is that enough for your use case?

I was just highlighting the differences for you to decide whether you want to enlarge the set of qualities (e.g. to include half-diminished, dominant9, etc.) or if you think that your set is "more correct" than MusicXML's. I have no strong opinion either way, but it seems that MusicXML defines "quality" differently than you do.

Just to be clear I do not define anything here 🙌 As stated in the readme qualities definitions are based on Mark Harrison's body of work (https://www.harrisonmusic.com). MusicXml use a different approach, which might be as valid a his, but switching the approach would represent a significative amount of work that I'm not sure would make sense 😛

Regardless, though, I think it's important to keep extensions, alterations, adds and omits consistent so that any downstream application such as mine can rely on them for further processing. Specifically, to clearly define the logic behind each array, to resolve the apparent inconsistencies discussed above.

I definitely agree there.

Would you consider a half-diminished intent too? Assuming it's not a legit quality.

Half-diminished is really mi7(b5). An intent would allow to render Ch as CØ (for ex) and Cmi7(b5) as Cmi7(b5) but that defeats a bit the purpose of normalisation. I would consider this "user preferences" to be handled by custom filters, which are basically made for this kind of use cases. Here you could test the intervals array against 1, b3, b5, b7 and set the quality as you prefer.

Most immediately, recognizing "^" and "-b6" chords will allow me finalize the chord-symbol integration.

That has made it to the todo list for v1.2.0 😄 https://github.com/no-chris/chord-symbol/projects/4 No ETA, though, but feel free to contribute if you feel like it 😉

Next step may be to move my chord-symbol conversion function to a filter in your repo.

I would love that! ❤️

infojunkie commented 3 years ago

Is that enough for your use case?

I think so - for my current purposes, I care about rendering the right chord, even if its metadata are a bit off.

[..] but switching the approach would represent a significative amount of work that I'm not sure would make sense

Understood. Since you seem interested in the ontological aspects of music theory, I encourage you to lurk or participate in the W3C music notation community - that's where MusicXML and its successor / competitor MNX are being designed and discussed.

[..] I would consider this "user preferences" to be handled by custom filters

Makes sense. In that case, though, it would be good to also clearly define what the intents array means and is expected to hold.

That has made it to the todo list for v1.2.0

:tada: I will first reach an alpha version of ireal-musicxml before I work on merging chord-symbol. Hope I can contribute!

Next step may be to move my chord-symbol conversion function to a filter in your repo.

I would love that!

Sounds great. Thanks for your cooperation!

no-chris commented 3 years ago

from the API documentation: [the intent object] keeps track of intents that are part of the symbol but cannot be conveyed by the interval list only

definitely could use some refinements

no-chris commented 3 years ago

👋 The v1.2.0 branch has b6 parsing as well as correct parsing for ^/Δ modifiers. If you could test it locally before release that would be amazing 🙌 Thanks! 😁

infojunkie commented 3 years ago

Just ran my branch https://github.com/infojunkie/ireal-musicxml/tree/chord-symbol against your v1.2.0 branch and it passes :raised_hands: - it is currently failing on Travis against your latest official release. Thanks!!

no-chris commented 3 years ago

v1.2.0 is released 🎉

infojunkie commented 3 years ago

Merged my working chord-symbol branch into main. Thanks so much for your cooperation :pray:

infojunkie commented 3 years ago

Fyi, to convert the chord-symbol output to something that MusicXML can deal with, I've had to write some non-trivial code. If you are still interested, we can cooperate on turning it into a filter.

no-chris commented 3 years ago

Yes that is interesting. I'll look into it once I'm done with the error handling.