OO Design, comment modéliser Tonal Harmony?

12

J'ai commencé à écrire un programme en C ++ 11 qui analyserait les accords, les gammes et l'harmonie. Le plus gros problème que j'ai dans ma phase de conception, c'est que la note 'C' est une note, un type d'accord (Cmaj, Cmin, C7, etc.) et un type de clé (la clé de Cmajor, Cminor). Le même problème se pose avec les intervalles (3e mineur, 3e majeur).

J'utilise une classe de base, Token, qui est la classe de base pour tous les «symboles» du programme. donc par exemple:

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;

Comme vous pouvez le voir, créer toutes les classes dérivées (CMajorTriad, C, CMajorScale, CMajorKey, etc.) deviendrait rapidement ridiculement complexe, y compris toutes les autres notes, ainsi que les harmoniques. l'héritage multiple ne fonctionnerait pas, c'est-à-dire:

class C : public Note, Triad, Key, Scale

classe C, ne peut pas être toutes ces choses en même temps. Il est contextuel, polymorphiser avec cela ne fonctionnera pas (comment déterminer les super méthodes à exécuter? Appeler tous les constructeurs de super classes ne devrait pas se produire ici)

Y a-t-il des idées ou des suggestions de design que les gens ont à offrir? Je n'ai rien trouvé sur google en ce qui concerne la modélisation de l'harmonie tonale du point de vue OO. Il y a juste trop de relations entre tous les concepts ici.

Igneous01
la source
8
Pourquoi «C» serait-il une classe? J'imagine que «Note», «Chord», etc. seraient des classes, qui pourraient avoir une énumération de valeur dans laquelle l'énumération «C» pourrait jouer un rôle.
Rotem
Si l'utilisateur entre-> accord CEG, il devrait en déduire quelles sont les notes pour former l'accord approprié. Je pensais passer un vecteur de <Notes> comme paramètres à la méthode execute (), qui seraient tous traités de manière polymorphe. Cependant, utiliser un énumérateur aurait du sens, mais j'aurais alors besoin d'instancier chaque objet avec l'énumération que je veux utiliser.
Igneous01
Je suis avec @Rotem sur celui-ci: Parfois, il suffit de préférer la composition d'objets à l'héritage.
Spoike
Il me semble qu'il pourrait être utile de réfléchir à ce que vous voulez faire avec ces classes de notes / accords / gammes. Allez-vous produire des partitions? Fichiers midi? Faire des transformations sur les partitions (transposition, doubler toutes les longueurs de notes, ajouter des trilles à toutes les notes entières au-dessus d'une certaine note, etc.)? Une fois que vous avez une structure de classe possible, réfléchissez à la façon dont vous accomplirez ces tâches. Si cela vous semble gênant, vous souhaitez peut-être une structure de classe différente.
MatrixFrog

Réponses:

9

Je pense que la meilleure approche est de reproduire les relations réelles entre ces entités.

Par exemple, vous pourriez avoir:

  • un Noteobjet, dont les propriétés sont

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

    • accidentel (naturel, plat, tranchant)

    • fréquence ou un autre identifiant de hauteur unique

  • un Chordobjet, dont les propriétés sont

    • un tableau d' Noteobjets

    • Nom

    • accidentel

    • qualité (majeure, mineure, diminuée, augmentée, suspendue)

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

  • un Scaleobjet, dont les propriétés sont

    • un tableau d' Noteobjets

    • Nom

    • type (majeur, mineur naturel, mineur mélodique, mineur harmonique)

    • mode (ionien, dorien, phrygien, lydien, mixolidien, éolien, locrien)

Ensuite, si votre entrée est textuelle, vous pouvez créer des notes avec une chaîne comprenant le nom de la note, accidentelle et (si vous en avez besoin) l'octave.

Par exemple (pseudocode, je ne connais pas C ++):

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

Ensuite, dans la Noteclasse, vous pouvez analyser la chaîne et définir les propriétés.

Un Chordpourrait être construit par ses notes:

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

... ou par une chaîne comprenant le nom, la qualité et des notes supplémentaires:

chord = new Chord('Cmaj7');

Je ne sais pas exactement ce que fera votre application, ce ne sont donc que des idées.

Bonne chance avec votre projet fascinant!

lortabac
la source
4

Quelques conseils génériques.


S'il y a beaucoup d'incertitude dans la conception de la classe (comme dans votre situation), je recommanderais d'expérimenter avec différentes conceptions de classes concurrentes.

L'utilisation de C ++ à ce stade peut ne pas être aussi productive que d'autres langages. (Ce problème est apparent dans vos fragments de code devant être traités typedefet virtualdestructeurs.) Même si l'objectif du projet est de produire du code C ++, il peut être productif de faire la conception de classe initiale dans un autre langage. (Par exemple, Java, bien qu'il existe de nombreux choix.)

Ne choisissez pas C ++ simplement en raison de l'héritage multiple. L'héritage multiple a ses utilités mais ce n'est pas la bonne façon de modéliser ce problème (théorie de la musique).


Faites particulièrement attention à lever l'ambiguïté. Même si les ambiguïtés sont abondantes dans les descriptions anglaises (textuelles), ces ambiguïtés doivent être résolues lors de la conception des classes OOP.

On parle de G et de G sharp comme notes. Nous parlons de sol majeur et de sol mineur comme des gammes. Ainsi, Noteet Scalene sont pas des concepts interchangeables. Il ne pouvait pas être tout objet qui peut être simultanément une instance d'un Noteet un Scale.

Cette page contient quelques diagrammes qui illustrent la relation: http://www.howmusicworks.org/600/ChordScale-Relations/Chord-and-Scale-Relations

Pour un autre exemple, "une Triade qui commence par G sur une échelle majeure de C " n'a pas la même signification que "une Triade qui commence par C sur une échelle majeure de G ".

À ce stade précoce, la Tokenclasse (la superclasse de tout) est injustifiée, car elle empêche toute ambiguïté. Il pourrait être introduit plus tard si nécessaire (soutenu par un fragment de code qui montre comment cela pourrait être utile.)


Pour commencer, commencez par une Noteclasse qui est le centre du diagramme de classe, puis ajoutez progressivement les relations (éléments de données qui doivent être associés à des tuples de Notes) au diagramme de relations de classe.

Une note C est une instance de la Noteclasse. Une note C renverra des propriétés liées à cette note, telles que des triades liées, et sa position relative ( Interval) par rapport à une Scalequi commence par une note C.

Les relations entre les instances de la même classe (par exemple, entre une note C et une note E ) doivent être modélisées comme des propriétés et non comme un héritage.

De plus, la plupart des relations inter-classes dans vos exemples sont également mieux modélisées en tant que propriétés. Exemple:

(des exemples de code sont en attente car j'ai besoin de réapprendre la théorie musicale ...)

rwong
la source
Pensée intéressante, mais comment gérer la détermination des qualités des accords dans le contexte de l'analyse harmonique? C Une instance d'accord devrait avoir une propriété de qualité, définie sur mineur (ce qui est correct) mais qu'en est-il des accords dominants / diminués / augmentés / mineurs 7s, 9, 11 accords? Il existe de nombreux accords auxquels une seule note peut appartenir. Comment déterminer quels sont les différents types d'accords et leurs qualités respectives dans la section d'analyse du code?
Igneous01
Je connais très peu de théorie musicale, donc je ne suis pas en mesure de répondre à votre question. Une façon qui pourrait m'aider à comprendre serait de trouver un tableau qui répertorie toutes les notes impliquées dans ces concepts. Les requêtes d'accords peuvent prendre des paramètres supplémentaires.
rwong
2
Voici une très belle liste de tous les accords possibles: en.wikipedia.org/wiki/List_of_chords Tous les accords peuvent être appliqués à n'importe quelle note, ce qui est important dans ma situation, c'est que les enharmonics sont corrects: ie. Cflat major! = BMajor, Ils sont physiquement le même accord au piano, mais leurs fonctions harmoniques sont très différentes sur le papier. Je pense qu'un énumérateur pour aiguiser / aplatir une note aurait le plus de sens pour une instance de note. De cette façon, C.Sharpen () = C # et C.Flatten () = Cb, cela peut me faciliter la validation des accords utilisateur.
Igneous01
2

Fondamentalement, les notes de musique sont des fréquences et les intervalles musicaux sont des rapports de fréquence.

Tout le reste peut être construit sur cela.

Un accord est une liste d'intervalles. Une gamme est une note fondamentale et un système d'accord. Un système de réglage est également une liste d'intervalles.

Comment vous les nommez n'est qu'un artefact culturel.

L' article sur la théorie musicale de Wikipédia est un bon point de départ.

mouviciel
la source
Intéressant, même si je ne suis pas sûr qu'il soit nécessairement utile de modéliser le système en termes de réalité physique sous-jacente. N'oubliez pas que le modèle doit être utile pour articuler un aspect particulier, pas nécessairement pour être complet ou même précis. Bien que votre approche soit à la fois précise et complète, elle pourrait être de niveau trop bas pour le cas d'utilisation d'OP.
Konrad Rudolph
@KonradRudolph - Avec ma position extrême, je voulais juste souligner qu'il ne faut pas mélanger le modèle sous-jacent avec la couche de présentation, d'une manière similaire à l'heure d'été: les calculs sont beaucoup plus faciles sur le modèle lui-même. Je suis d'accord que le niveau d'abstraction le plus utile n'est pas ce que je suggère, mais je pense que le niveau d'abstraction suggéré par le PO n'est pas non plus adéquat.
mouviciel
Le but de ce programme n'est pas nécessairement d'afficher la réalité physique de la musique. Mais pour les personnes qui étudient la théorie (comme moi) pour pouvoir tracer rapidement quelques accords et faire interpréter au mieux le programme comment ces accords sont liés les uns aux autres dans un sens harmonique. Je pourrais simplement l'analyser de la manière éprouvée de lire la partition mesure par mesure, mais c'est un autre outil pour faciliter les choses et pour pouvoir se concentrer sur les détails les plus fins lors de l'analyse.
Igneous01
1

Je trouve cette discussion fascinante.

Les notes sont-elles entrées via midi (ou un type d'appareil de capture de tonalité) ou sont-elles entrées en tapant les lettres et les symboles?

Dans le cas de l'intervalle de C à D-sharp / E-flat:

Bien que D-sharp et E-flat aient le même ton (environ 311Hz si A = 440Hz), l'intervalle de C -> D-sharp est écrit un 2ème augmenté, tandis que l'intervalle de C -> E-flat est écrit comme un mineur 3e. Assez facile si vous savez comment la note a été écrite. Impossible de déterminer si vous n'avez que les deux tonalités pour continuer.

Dans ce cas, je pense que vous aurez également besoin d'un moyen d'incrémenter / décrémenter le ton avec les méthodes .Sharpen () et .Flatten () mentionnées, telles que .SemiToneUp (), .FullToneDown (), etc. que vous pouvez trouver des notes moins fréquentes dans une échelle sans les "colorer" comme des objets tranchants / plats.

Je suis d'accord avec @Rotem que "C" n'est pas une classe en soi, mais plutôt une instanciation de la classe Note.

Si vous définissez les propriétés d'une note, y compris tous les intervalles sous forme de demi-tons, alors quelle que soit la valeur de note initiale ("C", "F", "G #"), vous pourrez dire qu'une séquence de trois notes qui a le racine, 3e majeur (M3), puis 3e mineur (m3) serait une triade majeure. De même, m3 + M3 est une triade mineure, m3 + m3 diminué, M3 + M3 augmenté. De plus, cela vous donnerait un moyen d'encapsuler la recherche de la 11e, de la 13e diminuée, etc. sans les coder explicitement pour les 12 notes de base et leurs octaves de haut en bas.

Une fois cela fait, il vous reste encore des problèmes à résoudre.

Prenez la triade C, E, G. En tant que musicien, je vois cela clairement comme un accord Cmaj. Cependant, le développeur en moi peut interpréter cela en plus comme E mineur Augment 5 (Root E + m3 + a5) ou Gsus4 6th no 5th (RootG + 4 + 6).

Donc, pour répondre à votre question sur l'analyse, je pense que la meilleure façon de déterminer la modalité (maj, mineur, etc.) serait de prendre toutes les notes entrées, de les disposer en demi-tons ascendants et de les tester par rapport aux formes d'accord connues. . Ensuite, utilisez chaque note entrée comme note fondamentale et effectuez le même ensemble d'évaluations.

Vous pouvez pondérer les formes d'accords afin que les formes d'accords plus courantes (majeures, mineures) aient priorité sur les formes d'accords augmentées, suspendues, elektra, etc., mais une analyse précise nécessiterait de présenter toutes les formes d'accords correspondantes comme solutions possibles.

Encore une fois, l'article de Wikipédia référencé fait un bon travail de listage des classes de hauteur, donc il devrait être simple (quoique fastidieux) de coder les modèles des accords, de prendre les notes entrées, de les affecter à des classes / intervalles de hauteur, puis de comparer contre les formes connues pour les matchs.

Cela a été très amusant. Merci!

John
la source
Ils sont saisis, via le texte pour l'instant. Cependant, plus tard, je pourrai peut-être utiliser midi si le programme est correctement encapsulé. Pas de bébé en ce moment: D
Igneous01
0

Cela ressemble à un étui pour les modèles. Vous semblez avoir un template <?> class Major : public Chord;tel qui Major<C>est Chord, tel quel Major<B>. De même, vous avez également un Note<?>modèle avec des instances Note<C>et Note<D>.

La seule chose que j'ai laissée de côté, c'est la ?partie. Il semble que vous en ayez un, enum {A,B,C,D,E,F,G}mais je ne sais pas comment vous nommeriez cette énumération.

MSalters
la source
0

Merci pour toutes les suggestions, j'ai réussi à manquer les réponses supplémentaires. Jusqu'à présent, mes cours ont été conçus comme suit:

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

Pour résoudre mes problèmes de calcul d'intervalle et d'accord, j'ai décidé d'utiliser le tampon circulaire, ce qui me permet de parcourir le tampon à partir de n'importe quel point, à l'avenir, jusqu'à ce que je trouve la prochaine note qui correspond.

Pour trouver l'intervalle interprété - parcourez le tampon de notes réelles, arrêtez-vous lorsque les lettres correspondent (juste la lettre, pas la note ou la position réelle) donc c - g # = 5

Pour trouver la distance réelle, parcourez un autre tampon de 12 entiers, arrêtez-vous lorsque la position de la note de tête est la même que la valeur du tampon à l'index, là encore, cela ne fait que progresser. Mais le décalage peut être n'importe où (c.-à-d. Buffer.at (-10))

maintenant je connais à la fois l'intervalle interprété et la distance physique entre les deux. le nom de l'intervalle est donc à moitié complet.

maintenant je suis capable d'interpréter l'intervalle, c'est à dire. si l'intervalle est de 5 et que la distance est de 8, alors c'est un 5e augmenté.

Jusqu'à présent, la note et l'intervalle fonctionnent comme prévu, maintenant je n'ai plus qu'à aborder l'identifiant de l'accord.

Merci encore, je vais relire certaines de ces réponses et y incorporer quelques idées.

Igneous01
la source