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 non-traditional key signatures #81

Merged
merged 4 commits into from
Apr 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 14 additions & 11 deletions Sourcecode/include/mx/api/ApiEquality.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,12 @@
#include <map>
#include <iostream>

// #define MX_DEBUG // shows traces to std::cout

namespace mx
{
namespace api
{

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// All of the uglines below this point is mainly to be used in test code. It is not expected that clients will have need of the //
// functions defined here. If you do wish to use the equality operator, you may want to silence the cout's that occur when two //
// items are found to be not-equal. This cout stream was necessary for testing during development. //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

template <typename T>
inline bool areVectorsEqual( const std::vector<T>& lhs, const std::vector<T>& rhs )
{
Expand Down Expand Up @@ -72,6 +67,7 @@ namespace mx
return areSame( lhs, rhs );
}

#ifdef MX_DEBUG
inline void streamComparisonUnequalMessage( const char* const inClassName, const char* const inMemberName )
{
std::cout << inClassName;
Expand All @@ -80,23 +76,30 @@ namespace mx
std::cout << " members are not equal ";
std::cout << std::endl;
}

#endif

#ifdef MX_DEBUG
#define MX_SHOW_UNEQUAL( XtheCurrentClassName, XmxapiMemberName ) streamComparisonUnequalMessage( XtheCurrentClassName, XmxapiMemberName );
#else
#define MX_SHOW_UNEQUAL( XtheCurrentClassName, XmxapiMemberName )
#endif

#define MXAPI_EQUALS_BEGIN( mxapiClassName ) \
inline bool operator==( const mxapiClassName& lhs, const mxapiClassName& rhs ) \
{ \
const char* const theCurrentClassName = #mxapiClassName;

#define MXAPI_EQUALS_MEMBER( mxapiMemberName ) \
if( ! ( lhs.mxapiMemberName == rhs.mxapiMemberName ) ) \
{ \
streamComparisonUnequalMessage( theCurrentClassName, #mxapiMemberName ); \
MX_SHOW_UNEQUAL( theCurrentClassName, #mxapiMemberName ); \
return false; \
}

#define MXAPI_DOUBLES_EQUALS_MEMBER( mxapiMemberName ) \
if( std::abs( lhs.mxapiMemberName - rhs.mxapiMemberName ) > MX_API_EQUALITY_EPSILON ) \
{ \
streamComparisonUnequalMessage( theCurrentClassName, #mxapiMemberName ); \
MX_SHOW_UNEQUAL( theCurrentClassName, #mxapiMemberName ); \
return false; \
}

Expand Down
2 changes: 1 addition & 1 deletion Sourcecode/include/mx/api/FontData.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ namespace mx
MXAPI_EQUALS_MEMBER( weight )
if( ! areVectorsEqual( lhs.fontFamily, rhs.fontFamily ) )
{
streamComparisonUnequalMessage( "FontData", "fontFamily" );
MX_SHOW_UNEQUAL( "FontData", "fontFamily" );
return false;
}
MXAPI_EQUALS_MEMBER( underline )
Expand Down
42 changes: 42 additions & 0 deletions Sourcecode/include/mx/api/KeyComponent.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// MusicXML Class Library
// Copyright (c) by Matthew James Briggs
// Distributed under the MIT License

#pragma once
#include "mx/api/ApiCommon.h"
#include "mx/api/PitchData.h"

namespace mx
{
namespace api
{

// KeyComponent is only used for non-traditional key signatures. It facilitates specifying the
// exact accidentals that are found in the key signature.
struct KeyComponent
{
// The note name that is to be altered by the key signature.
Step step;

// The amount that notes of this note name should be altered, in semitones.
int alter;

// Additional amount that notes of this note name should be altered, in cents. If alter = 1 and cents = 25.1
// then the note is altered by 1.251 semitones in total.
double cents;

// The accidental to display for this note name.
Accidental accidental;
};


MXAPI_EQUALS_BEGIN( KeyComponent )
MXAPI_EQUALS_MEMBER( step )
MXAPI_EQUALS_MEMBER( alter )
MXAPI_EQUALS_MEMBER( cents )
MXAPI_EQUALS_MEMBER( accidental )
MXAPI_EQUALS_END;

MXAPI_NOT_EQUALS_AND_VECTORS( KeyComponent );
}
}
101 changes: 65 additions & 36 deletions Sourcecode/include/mx/api/KeyData.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,72 +3,101 @@
// Distributed under the MIT License

#pragma once
#include "mx/api/KeyComponent.h"

namespace mx
{
namespace api
{
namespace api
{
enum class KeyMode
{
// a mode value was not provided
unspecified,

// a mode value was provided but
// is not supported
unsupported,

unspecified, // a mode value was not provided
unsupported, // a mode value was provided but is not supported
major,
minor

};
struct KeyData
{
// From MusicXML Specification:
// The fifths type represents the number of flats or sharps in a
// traditional key signature. Negative numbers are used for flats
// and positive numbers for sharps, reflecting the key's
// placement within the circle of fifths (hence the type name).
int fifths;

// From MusicXML Specification:
// A cancel element indicates that the old key signature should be
// cancelled before the new one appears. This will always happen
// when changing to C major or A minor and need not be specified
// then. The cancel value matches the fifths value of the cancelled
// key signature (e.g., a cancel of -2 will provide an explicit
// cancellation for changing from B flat major to F major). The
// optional location attribute indicates whether the cancellation
// appears relative to the new key signature.
int cancel;
// KeyData represents a key signature. It can be in one of two configurations. Either you specify
// 'fifths' and 'mode', or you can create a custom key signature by adding items to the customKey
// vector. If anything is found in the customKey vector, then fifths and mode will be ignored.
//
// Example, a traditional key signature (D Major):
// KeyData key;
// key.fifths = 2; // (i.e. 2 sharps)
// key.mode = KeyMode::major; // (optional)
//
// Example, a traditional key signature (G Minor):
// KeyData key;
// key.fifths = -2; // (i.e. 2 flats)
// key.mode = KeyMode::minor; // (optional)
//
// If you want to create a custom time signature, you can do so like this. Here we are creating a
// key where C's are sharp and D's are one-quarter-tone sharp. See KeyComponent for details.
//
// KeyComponent cSharp{ Step::c, 1, 0.0, Accidental::sharp };
// KeyComponent dQuarterTone{ Step::d, 0, 50.0, Accidental:quarterSharp };
// KeyData key;
// key.nonTraditional.push_back( cSharp );
// key.nonTraditional.push_back( dQuarterTone );
//
struct KeyData
{
// From MusicXML Specification:
// The fifths type represents the number of flats or sharps in a
// traditional key signature. Negative numbers are used for flats
// and positive numbers for sharps, reflecting the key's
// placement within the circle of fifths (hence the type name).
int fifths;

// From MusicXML Specification:
// A cancel element indicates that the old key signature should be
// cancelled before the new one appears. This will always happen
// when changing to C major or A minor and need not be specified
// then. The cancel value matches the fifths value of the cancelled
// key signature (e.g., a cancel of -2 will provide an explicit
// cancellation for changing from B flat major to F major). The
// optional location attribute indicates whether the cancellation
// appears relative to the new key signature.
int cancel;

// Mode specifies whether the key is major or minor. It is optional.
KeyMode mode;

// Supports changing the key somewhere other than at the start of a measure.
int tickTimePosition;

// this value is optional. -1 means unspecified. when value is
// unspecified it means that the key signature applies to all staves
// within the part
int staffIndex;

// TODO support position data and/or other attribtues


// TODO support position data and/or other attributes

// Supports the creation of customized, non-traditional key signatures by specifying the exact note
// alterations. When custom is non-empty, then fifths and mode are ignored.
std::vector<KeyComponent> nonTraditional;

KeyData()
: fifths{ 0 }
, cancel{ 0 }
, mode{ KeyMode::unspecified }
, tickTimePosition{ 0 }
, staffIndex{ -1 }
, nonTraditional{}
{

}
};
};

MXAPI_EQUALS_BEGIN( KeyData )
MXAPI_EQUALS_MEMBER( fifths )
MXAPI_EQUALS_MEMBER( cancel )
MXAPI_EQUALS_MEMBER( mode )
MXAPI_EQUALS_MEMBER( tickTimePosition )
MXAPI_EQUALS_MEMBER( staffIndex )
MXAPI_EQUALS_MEMBER( nonTraditional )
MXAPI_EQUALS_END;

MXAPI_NOT_EQUALS_AND_VECTORS( KeyData );
}
}
}
2 changes: 1 addition & 1 deletion Sourcecode/include/mx/api/StaffData.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ namespace mx
MXAPI_EQUALS_MEMBER( directions )
if( !( voicesAreEqual( lhs.voices, rhs.voices ) ) )
{
streamComparisonUnequalMessage( "StaffData", "voices" );
MX_SHOW_UNEQUAL( "StaffData", "voices" );
return false;
}
MXAPI_EQUALS_END;
Expand Down
4 changes: 3 additions & 1 deletion Sourcecode/private/mx/core/elements/KeyAttributes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ namespace mx
,hasFontWeight( false )
,hasColor( false )
,hasPrintObject( false )
{}
{

}


bool KeyAttributes::hasValues() const
Expand Down
30 changes: 29 additions & 1 deletion Sourcecode/private/mx/impl/Converter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1804,6 +1804,34 @@ namespace mx
{
return findApiItem( kindMap, api::ChordKind::unspecified, value );
}


mx::core::DecimalType Converter::convertToAlter( int semitones, double cents )
{
double alter = 0.0;
if( semitones != 0 || cents != 0.0 )
{
double microtones = 0.0;
if( cents != 0.0 ) {
microtones = cents / 100.0;
}
alter = semitones + microtones;
}
return alter;
}

std::pair<int, double> Converter::convertToSemitonesAndCents( mx::core::DecimalType xmlAlter )
{
double myCents = 0.0;
const auto intAlter = static_cast<int>( xmlAlter );
const auto micro = xmlAlter - static_cast<mx::core::DecimalType> ( intAlter );
const auto microDistance = std::abs( micro );
if( microDistance >= 0.000000000001 )
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want, you can use std::numeric_limits<double>::epsilon() which gives the smallest increment of the numeric type T you provide it with.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll play around with that.

{
const auto theCents = micro * 100.0;
const auto theNarrowCents = static_cast<decltype(mx::api::PitchData::cents)>( theCents );
myCents = theNarrowCents;
}
return std::make_pair( intAlter, myCents );
}
}
}
8 changes: 7 additions & 1 deletion Sourcecode/private/mx/impl/Converter.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "mx/core/elements/OrnamentsChoice.h"
#include "mx/core/elements/TechnicalChoice.h"
#include "mx/core/Enums.h"
#include "mx/core/Decimals.h"

#include <map>

Expand All @@ -25,6 +26,8 @@ namespace mx
class Converter
{
public:
// TODO - all of these functions should be static

core::StepEnum convert( api::Step value ) const;
api::Step convert( core::StepEnum value ) const;

Expand Down Expand Up @@ -114,7 +117,10 @@ namespace mx

core::KindValue convert( api::ChordKind value ) const;
api::ChordKind convert( core::KindValue value ) const;


static mx::core::DecimalType convertToAlter( int semitones, double cents );
static std::pair<int, double> convertToSemitonesAndCents( mx::core::DecimalType alter );

const static std::map<core::StepEnum, api::Step> stepMap;
const static std::map<core::NoteTypeValue, api::DurationName> durationMap;
const static std::map<core::NoteheadValue, api::Notehead> noteheadMap;
Expand Down
25 changes: 24 additions & 1 deletion Sourcecode/private/mx/impl/MeasureReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,30 @@ namespace mx

if( keyType == core::KeyChoice::Choice::nonTraditionalKey )
{
// TODO - support non-traditional keys
api::KeyData keyData;
const auto& nonTraditionalKeyParts = key.getKeyChoice()->getNonTraditionalKeySet();
for( const auto& nonTraditionalKeyPart : nonTraditionalKeyParts )
{
api::KeyComponent keyComponent{};

if( nonTraditionalKeyPart->getHasKeyAccidental() )
{
keyComponent.accidental = myConverter.convert( nonTraditionalKeyPart->getKeyAccidental()->getValue() );
}

const auto alter = nonTraditionalKeyPart->getKeyAlter()->getValue().getValue();
if( alter != 0.0 )
{
const auto semitoneAndCents = Converter::convertToSemitonesAndCents( alter );
keyComponent.alter = semitoneAndCents.first;
keyComponent.cents = semitoneAndCents.second;
}

keyComponent.step = myConverter.convert( nonTraditionalKeyPart->getKeyStep()->getValue() );
keyData.nonTraditional.emplace_back( keyComponent );
}

myOutMeasureData.keys.emplace_back( std::move( keyData ) );
continue;
}

Expand Down
Loading