Skip to content

Commit

Permalink
api: support non-traditional key signatures (#81)
Browse files Browse the repository at this point in the history
* support non-traditional keys

* strangely failing test

* key data tests

* support non-traditional keys
  • Loading branch information
webern committed Apr 4, 2020
1 parent d64afd1 commit 7f4be10
Show file tree
Hide file tree
Showing 16 changed files with 623 additions and 79 deletions.
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 )
{
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

0 comments on commit 7f4be10

Please sign in to comment.