12

I have started to write a program in C++ 11 that would analyse chords, scales, and harmony. The biggest problem I am having in my design phase, is that the note 'C' is a note, a type of chord (Cmaj, Cmin, C7, etc), and a type of key (the key of Cmajor, Cminor). The same issue arises with intervals (minor 3rd, major 3rd).

I am using a base class, Token, that is the base class for all 'symbols' in the program. so for example:

class Token {
public:
    typedef shared_ptr<Token> pointer_type;
    Token() {}
    virtual ~Token() {}
};

class Command : public Token {
public:
    Command() {}
    pointer_type execute();
}

class Note : public Token;

class Triad : public Token; class MajorTriad : public Triad; // CMajorTriad, etc

class Key : public Token; class MinorKey : public Key; // Natural Minor, Harmonic minor,etc

class Scale : public Token;

As you can see, to create all the derived classes (CMajorTriad, C, CMajorScale, CMajorKey, etc) would quickly become ridiculously complex including all the other notes, as well as enharmonics. multiple inheritance would not work, ie:

class C : public Note, Triad, Key, Scale

class C, cannot be all of these things at the same time. It is contextual, also polymorphing with this will not work (how to determine which super methods to perform? calling every super class constructors should not happen here)

Are there any design ideas or suggestions that people have to offer? I have not been able to find anything on google in regards to modelling tonal harmony from an OO perspective. There are just far too many relationships between all the concepts here.

4
  • 8
    Why would 'C' be a class? I would imagine 'Note', 'Chord', etc. would be classes, which could have a value enumeration in which the enum 'C' might play a part.
    – Rotem
    Commented Dec 9, 2012 at 13:06
  • If the user inputs->chord C E G, it would need to deduce what the notes are to form the appropriate chord. I was thinking of passing in a vector of <Notes> as params to the execute() method, which would all be handled polymorphically. However using an enumerator would make sense, but then I would need to instantiate every object with the enum that I want to use.
    – Igneous01
    Commented Dec 9, 2012 at 13:14
  • I'm with @Rotem on this one: Sometimes, you just have to prefer object composition over inheritance.
    – Spoike
    Commented Dec 9, 2012 at 15:02
  • It seems to me that it might be helpful to think about what you want to do with these note/chord/scale classes. Are you going to produce sheet music? Midi files? Do transformations on scores (transposition, doubling all the note lengths, adding trills to all whole notes above a certain note, etc.)? Once you have a possible class structure, think about how you would accomplish those tasks. If it seems awkward, maybe you want a different class structure.
    – Tyler
    Commented Dec 11, 2012 at 6:42

6 Answers 6

9

I think the best approach is to reproduce the real relationships between these entities.

For example, you could have:

  • a Note object, whose properties are

    • name (C, D, E, F, G, A, B)

    • accidental (natural, flat, sharp)

    • frequency or another unique pitch identifier

  • a Chord object, whose properties are

    • an array of Note objects

    • name

    • accidental

    • quality (major, minor, diminished, augmented, suspended)

    • additions (7, 7+, 6, 9, 9+, 4)

  • a Scale object, whose properties are

    • an array of Note objects

    • name

    • type (major, natural minor, melodic minor, harmonic minor)

    • mode (ionian, dorian, phrygian, lydian, mixolidian, aeolian, locrian)

Then, if your input is textual, you can create notes with a string including the note name, accidental and (if you need it) octave.

For example (pseudocode, I don't know C++):

note = new Note('F#2');

Then, in the Note class you can parse the string and set the properties.

A Chord could be constructed by its notes:

chord = new Chord(['C2', 'E2', 'G2']);

...or by a string including name, quality and additional notes:

chord = new Chord('Cmaj7');

I don't know what your application will do exactly, so these are just ideas.

Good luck with your fascinating project!

4

Some generic advice.


If there is a lot of uncertainty expected in the class design (such as in your situation), I would recommend experimenting with different competing class designs.

Using C++ at this stage might be not as productive as other languages. (This issue is apparent in your code fragments having to deal with typedef and virtual destructors.) Even if the project goal is to produce C++ code, it might be productive to do initial class design in another language. (For example Java, though there are many choices.)

Do not choose C++ just because of multiple inheritance. Multiple inheritance has its uses but it is not the correct way to model this problem (music theory).


Take special attention to disambiguate. Even though ambiguities are abundant in English (textual) descriptions, these ambiguities must be resolved when designing OOP classes.

We speak of G and G sharp as notes. We speak of G major and G minor as scales. Thus, Note and Scale are not interchangeable concepts. There could not be any object that can be simultaneously an instance of a Note and a Scale.

This page contains a few diagrams that illustrate the relationship: http://www.howmusicworks.org/600/ChordScale-Relations/Chord-and-Scale-Relations

For another example, "a Triad that starts with G on a C major scale" does not have the same meaning as "a Triad that starts with C on a G major scale".

At this early stage, the Token class (the superclass of everything) is unwarranted, because it prevents disambiguation. It could be introduced later if needed (supported by a code fragment that demonstrates how this could be useful.)


To begin with, start with a Note class which is the center of the class diagram, then gradually add the relationships (pieces of data that needs to be associated with tuples of Notes) to the class relationship diagram.

A C note is an instance of the Note class. A C note will return properties that are related to this note, such as related triads, and its relative position (Interval) with respect to a Scale that starts with a C note.

Relationships between instances of the same class (for example, between a C note and a E note) should be modeled as properties, not inheritance.

Moreover, many of the inter-class relationships in your examples are also more appropriately modeled as properties. Example:

(code examples are pending because I need to re-learn music theory...)

3
  • Interesting thought, but how would one handle determining chord qualities in the context of harmonic analysis? C Chord instance would need to have quality property, set to minor (which is ok) but then what about a dominant/diminished/augmented/minor 7s, 9, 11 chords? There are a lot of chords that a single note can belong to. How would I determine what the various types of chords and their respective qualities are in the analysis section of the code?
    – Igneous01
    Commented Dec 9, 2012 at 14:02
  • I know very little music theory, so I'm not able to answer your question. One way that can help me understand would be to find a table that lists all of the notes involved in those concepts. Queries for chords can take additional parameters.
    – rwong
    Commented Dec 9, 2012 at 14:22
  • 2
    Here is a very nice list of all possible chords: en.wikipedia.org/wiki/List_of_chords All the chords can be applied to any note, what is important with my situation is that the enharmonics are correct: ie. Cflat major != BMajor, They are physically the same chord on piano, but their harmonic functions are very different on paper. I am thinking that an enumerator for sharping/flatting a note would make most sense for an instance of note. THat way C.Sharpen() = C#, and C.Flatten() = Cb, this may make it easier for me to validate user chords.
    – Igneous01
    Commented Dec 9, 2012 at 15:23
2

Basically, musical notes are frequencies and musical intervals are frequency ratios.

Everything else can be built upon that.

A chord is a list of intervals. A scale is a fundamental note and a tuning system. A tuning system is also a list of intervals.

How you name them is just a cultural artefact.

Wikipedia's Music theory article is a nice starting point.

3
  • Interesting, although I’m not sure that it’s necessarily helpful to model the system in terms of the underlying physical reality. Remember, the model is required to be helpful in articulating one particular aspect, not necessarily to be comprehensive or even accurate. While your approach would be both accurate and comprehensive, it might be too low level for OP’s use case. Commented Jan 15, 2013 at 8:58
  • @KonradRudolph - With my extreme position I just wanted to point out that one should not mix the underlying model with the presentation layer, in a way similar to daylight saving times: Computations are far easier on the model itself. I agree that the most helpful abstraction level is not what I suggest, but I feel that the abstraction level suggested by the OP is not adequate either.
    – mouviciel
    Commented Jan 15, 2013 at 9:06
  • The purpose of this program is not to necessarily display the physical reality of the music. But for people studying theory (like myself) to be able to just quickly plot in some chords and have the program interpret to its best ability how these chords are related to each other in a harmonic sense. I could just analyze it the tried and true way of reading the score measure by measure, but this is another tool to make things easier, and to be able to focus on the more finer details when analyzing.
    – Igneous01
    Commented Jan 16, 2013 at 23:14
1

I'm finding this dicussion fascinating.

Are the notes being input via midi (or some type of tone capture device) or are they being entered by typing the letters and symbols?

In the case of the interval from C to D-sharp/E-flat:

Although D-sharp and E-flat are the same tone (around 311Hz if A=440Hz), the interval from C -> D-sharp is written an augmented 2nd, while the interval from C -> E-flat is writtem as a minor 3rd. Easy enough if you know how the note was written. Impossible to determine if you only have the two tones to go on.

In this case, I believe you are also going to need a way to increment/decrement the tone along with the .Sharpen() and .Flatten() methods mentioned, such as .SemiToneUp() , .FullToneDown(), etc. so that you can find subesquent notes in a scale without "coloring" them as sharps/flats.

I have to agree with @Rotem that "C" is not a class in and of itself, but rather an instantiation of the Note class.

If you define the properties for a note, including all the intervals as semitones, then regardless of the initial note value ("C", "F", "G#") you would be able to tell that a three note sequence that has the root, major 3rd (M3), then minor 3rd(m3) would be a major triad. Similarly, m3+M3 is a minor triad, m3+m3 diminished, M3+M3 augmented. Additionally, this would give you a way to encapsulate finding the 11th, diminished 13th, etc. without explicitly coding them for all 12 base notes, and their octaves up and down.

Once that's done, you're still left with some problems to solve.

Take the triad C,E,G. As a musician, I see this clearly as a Cmaj chord. However, the developer in me can interpret this additionaly as E minor Augment 5 (Root E+m3+a5) or Gsus4 6th no 5th (RootG + 4 + 6).

So, to answer your question about doing the analysis, I think the best way to determine modality (maj, minor, etc) would be to take all the notes entered, arrange them in ascending semitone value, and test them against the known chord forms. Then, use each note entered as the root note, and perform the same set of evaluations.

You could weight the chord forms so that more common (major, minor) have precedence over the augmented, suspended, elektra, etc. chord forms, but an accurate analysis would require presenting all matching chord forms as possible solutions.

Again the wikipedia article referenced does a good job of listing the pitch classes, so it should be simple (albeit tedious) to code up the models of the chords, the take the entered notes, assigne them to pitch classes/intervals, and then compare against the known forms for matches.

This has been a lot of fun. Thanks!

1
  • They are being inputted, via text for now. However later on I may be able to use midi if the program is encapsulated properly. Baby steps right now :D
    – Igneous01
    Commented Jan 14, 2013 at 18:38
0

Sounds like a case for templates. You seem to have a template <?> class Major : public Chord; so Major<C> is-a Chord, as is Major<B>. Similarly, you also have a Note<?> template with instances Note<C> and Note<D>.

The only thing I've left out is the ? part. It seems you have an enum {A,B,C,D,E,F,G} but I don't know how you'd name that enum.

0

Thank you for all the suggestions, somehow I managed to miss the extra responses. So far my classes have been designed like so:

Note
enum Qualities - { DFLAT = -2, FLAT, NATURAL, SHARP, DSHARP }
char letter[1] // 1 char letter
string name // real name of note
int value // absolute value, the position on the keyboard for a real note (ie. c is always 0)
int position // relative position on keyboard, when adding sharp/flat, position is modified
Qualities quality // the quality of the note ie sharp flat

To solve my interval and chord calculation problems, I decided to use the circular buffer, which allows me to traverse the buffer from any point, going forward, until i find the next note that matches.

To find the interpreted interval- traverse the real notes buffer, stop when the letters match (just the letter, not actual note or position) so c - g# = 5

To find the real distance- traverse another buffer of 12 integers, stop when the top note position is the same as the buffer's value at the index, again this is moving forward only. But the offset can be anywhere (ie. buffer.at(-10))

now I know both the interpreted interval, and the physical distance between the two. so interval name is already half complete.

now I am able to interpret the interval, ie. if the interval is 5, and distance is 8, then it is an augmented 5th.

So far note and interval are working as expected, now I only have to tackle the chord identifier.

Thanks again, I shall reread some of these responses and incorporate some ideas here.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.