Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

api: support transpose element #115

Closed
TassieBruce opened this issue Sep 11, 2020 · 9 comments · Fixed by #116
Closed

api: support transpose element #115

TassieBruce opened this issue Sep 11, 2020 · 9 comments · Fixed by #116
Labels
feature new feature request non-breaking fixes or implementation that do not require breaking changes

Comments

@TassieBruce
Copy link

TassieBruce commented Sep 11, 2020

Is there a way to specify a transposing instrument in the mx::api? Maybe its just me but I can't see how to do it.

For example, exporting a musicxml score for a B♭ Clarinet from musescore results in a element in the attributes of the first measure

<transpose>
  <diatonic>-1</diatonic>
  <chromatic>-2</chromatic>
</transpose>

How do I achieve that in mx::api?

@webern
Copy link
Owner

webern commented Sep 12, 2020

That feature has not been added to the mx::api structs. It should be easy to add it. As a workaround, it is also possible to obtain the mx::core representation and manipulate it.

mx::core::DocumentPtr getDocument( int documentId ) const;

This requires using the 'private' includes, e.g. #include "mx/core/Document.h" and dealing with the unwieldy interface to the elements themselves.

I will add this feature to mx::api as soon as possible (unfortunately that might be 2-3 weeks from now due to some circumstances). You can try adding it to mx::api before then if you wish. I should be able to respond within 24-hours to questions.

@TassieBruce
Copy link
Author

I will have a go at seeing if I can add it to mx::api but it will take me a while to get my head around the mx::core stuff. After a preliminary look, it looks like I'd have to do something similar to how divisions is handled. Will probably have real questions later.

@webern
Copy link
Owner

webern commented Sep 13, 2020

I started looking at this and wrote the following thinking that transposition would be an attribute of a Part

A new struct, e.g. TranspositionData added to this file before PartData:

An optional instance of that struct added to PartData, e.g. std::optional<TranspositionData> transposition.

There's a macro pattern to remember: https://github.com/webern/mx/blob/03a9b3254b853c04fb069cacc79281800215a0d4/Sourcecode/include/mx/api/PartData.h#L169..L210

During 'serialization', functionality has to be added to PartWriter.cpp to 'serialize' the mx::api version into mx::core version, probably in this function:

core::ScorePartPtr PartWriter::getScorePart() const

During deserialization, functionality has to be added to

api::PartData PartReader::getPartData()

However, not as simple as that

Your original statement says that transposition exists in the attributes of the first measure. Uh oh, this is more complicated. If mx::api should support the changing of transpositions in arbitrary locations throughout the music, then it adds more complexity to mx::api than I like. i.e. if mx::api supported all features of MusicXML then it would just be MusicXML and not an 'API'!

Anyway, if we stick with the idea of placing the transposition in the part, such that one can only specify the transposition once, at the beginning, then we could still create the struct and add it to PartData.h like I suggested above, but we would need to write it to the first measure of music, and detect it in the first measure of music instead of what I showed above.

There are precedents for this, anyway it will be these files:

MeasureReader::MeasureReader( const core::PartwiseMeasure& inPartwiseMeasureRef, const MeasureCursor& cursor, const MeasureCursor& previousMeasureCursor )

MeasureWriter::MeasureWriter( const api::MeasureData& inMeasureData, const MeasureCursor& inCursor, const ScoreWriter& inScoreWriter )

Unfortunately much of the code in mx::impl is really stupidly written and painful for me to look at... I mean it works, but this was one of my first projects. Anyway, I hope these links help a little.

Testing

If we go with putting this in PartData.h then probably clone a test file, e.g. https://github.com/webern/mx/blob/master/Sourcecode/private/mxtest/api/DirectionDataTest.cpp and name the copy of it PartDataTest.cpp as the starting point.

@TassieBruce
Copy link
Author

My only experience of musicxml is as something that can be read and written by musescore (i.e., I am definitely no expert on the intricacies of musicxml!). In musescore, each staff is a part in partwise musicxml and has an associated instrument, e.g., Flute, Clarinet, Trumpet, etc. Part of the details of each instrument includes its transposition. When the score is exported to musicxml, some of the instrument information ends up in the <part-list> specification (in <part-name>, <score-instrument>, <midi-instrument>, etc.) but the transposition information ends up as a <transpose> element in the <attributes> of the first <measure> of the <part>.

I don't know if there is any other valid use case where there could be a <transpose> later in a <part> (oops! seems like there is - see below). The description of <transpose> at https://usermanuals.musicxml.com/MusicXML/Content/EL-MusicXML-transpose.htm says

If the part is being encoded for a transposing instrument in written vs. concert pitch, the transposition must be encoded in the transpose element using the transpose type.

which makes it sound like it is envisaged to be used as part of the description of the instrument that should be the same for the entire part. In a saner world, maybe <transpose> should have been part of the instrument description in <part-list>.

But, to counter that view, the description of the <semitones> element of <transpose> at https://usermanuals.musicxml.com/MusicXML/Content/EL-MusicXML-chromatic.htm has an example where transposition changes mid-part.

The question is, do you want mx::api to accommodate such uses?

I would argue not to. As it is, mx::api::PartData holds an InstrumentData attribute. To me, this implies that the entire part represents a particular instrument (as does the presence of <score-instrument> etc., in <part-list>). Part of the description of that instrument should surely be its transposition.

Rather than create a new struct TranspositionData, why not just add an int chromaticTranspose attribute to the InstrumentData struct? The <chromatic> element is then chromaticTranspose % 12 while <octave-change> is chromaticTranspose / 12. I am not a musician so don't really understand the significance of the <diatonic> element. Can it be computed from <chromatic>?

The situation seems similar to <divisions>. Does mx::api allow changing <divisions> in the middle of a score or is it always set in the first measure of each part from the ScoreData.ticksPerQuarter?

@webern
Copy link
Owner

webern commented Sep 13, 2020

I would argue not to. As it is, mx::api::PartData holds an InstrumentData attribute. To me, this implies that the entire part represents a particular instrument (as does the presence of etc., in ). Part of the description of that instrument should surely be its transposition.

I agree, if someone needs to change transposition later in a piece, then a different feature could be added for that more advanced use-case.

The example you provided

<transpose>
  <diatonic>-1</diatonic>
  <chromatic>-2</chromatic>
</transpose>

Does seem non-ambiguous, but I'm thinking there are probably some ambiguous cases where the ability to set both diatonic and chromatic might be necessary. I asked a question about it just now: w3c/musicxml#333

@TassieBruce
Copy link
Author

My problem is I don't know much musical theory and assume all one needs to know about music is that frequency is proportional to 2**(n/12)!

The purpose of <transpose> is to define the interval from written pitch to sounding pitch and is usually expressed as the note that sounds when a C4 is written. Hence a Bb clarinet, etc.

I gather the <diatonic> term part of <transpose> is the change in the base note name ignoring sharps or flats. So C to F# has diatonic=3 while C to Gb has diatonic=4 but both have chromatic=6. But if I look at Wikipedia's list of transposing instruments https://en.wikipedia.org/wiki/List_of_transposing_instruments I notice that, where necessary, the sound of written C4 is always expressed as a flat, never a sharp. I assume this is some sort of convention.

So, if we associate int chromaticTranspose with an instrument, then on reading we set chromaticTranspose from the <chromatic> element (and <octave-change> if present) and ignore <diatonic> while on writing we lookup <diatonic> from chromaticTranspose using a table like

Note C Db D Eb E F Gb G Ab A Bb B
Chromatic 0 1 2 3 4 5 6 7 8 9 10 11
Diatonic 0 1 1 2 2 3 4 4 5 5 6 6

@TassieBruce
Copy link
Author

I think I've changed my mind. Mostly because of this quote from wikipedia

The transposition is not a property of the instrument, but rather a convention of musical notation. Instruments whose music is typically notated in this way are called transposing instruments.

This makes sense with the way musicxml places <transpose> in the <attributes> of a <measure>. So I'm creating a new type TransposeData used in a similar way as the keys attribute of MeasureData that should handle a <transpose> wherever it occurs.

@webern
Copy link
Owner

webern commented Sep 17, 2020

Yeah that's fine. I think it's disappointing for the user to have to interact with transposition in that way (when the most common use-case is to set it once at the beginning of the piece when setting the instrument), but your approach will work and will support additional use cases.

@webern webern added feature new feature request non-breaking fixes or implementation that do not require breaking changes labels Sep 19, 2020
@webern webern changed the title Transposing Instruments? api: support transpose element Sep 19, 2020
@webern
Copy link
Owner

webern commented Sep 30, 2020

I've started working on this. Let me know if you want me to get it done or if you want to collaborate in some way...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature new feature request non-breaking fixes or implementation that do not require breaking changes
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants