OO Design, wie modelliert man Tonal Harmony?


12

Ich habe begonnen, ein Programm in C ++ 11 zu schreiben, das Akkorde, Tonleitern und Harmonien analysiert. Das größte Problem, das ich in meiner Entwurfsphase habe, ist, dass die Note 'C' eine Note, eine Akkordart (Cmaj, Cmin, C7 usw.) und eine Tonart (die Tonart von Cmajor, Cminor) ist. Das gleiche Problem tritt mit Intervallen (Moll 3, Dur 3) auf.

Ich benutze eine Basisklasse, Token, das ist die Basisklasse für alle 'Symbole' im Programm. so zum Beispiel:

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;

Wie Sie sehen, würde das Erstellen aller abgeleiteten Klassen (CMajorTriad, C, CMajorScale, CMajorKey usw.) einschließlich aller anderen Noten und Enharmonics schnell lächerlich komplex werden. Mehrfachvererbung würde nicht funktionieren, dh:

class C : public Note, Triad, Key, Scale

Klasse C kann nicht alles gleichzeitig sein. Es ist kontextabhängig, auch Polymorphing wird damit nicht funktionieren (wie man bestimmt, welche Supermethoden auszuführen sind? Aufrufen aller Superklassenkonstruktoren sollte hier nicht passieren).

Gibt es irgendwelche Designideen oder Vorschläge, die die Leute zu bieten haben? In Bezug auf die Modellierung der tonalen Harmonie aus einer OO-Perspektive konnte ich bei Google nichts finden. Es gibt einfach viel zu viele Beziehungen zwischen allen Konzepten.


8
Warum sollte "C" eine Klasse sein? Ich würde mir vorstellen, dass 'Note', 'Chord' usw. Klassen sind, die eine Wertezählung haben könnten, in der die Aufzählung 'C' eine Rolle spielen könnte.
Rotem

Wenn der Benutzer-> Akkord-CEG eingibt, muss abgeleitet werden, welche Noten den entsprechenden Akkord bilden sollen. Ich dachte daran, einen Vektor von <Notes> als Parameter an die execute () -Methode zu übergeben, die alle polymorph behandelt würden. Die Verwendung eines Enumerators wäre jedoch sinnvoll, aber dann müsste ich jedes Objekt mit der Enumeration instanziieren, die ich verwenden möchte.
Igneous01

Ich bin mit @Rotem in dieser Sache: Manchmal muss man einfach die Objektzusammensetzung der Vererbung vorziehen.
Spoike

Es scheint mir , dass es hilfreich sein könnte , darüber nachzudenken , was Sie zu tun mit dieser Note / Akkord / Tonleiter - Klassen. Wirst du Noten produzieren? Midi-Dateien? Transformationen von Partituren (Transposition, Verdoppelung aller Notenlängen, Hinzufügen von Trills zu allen ganzen Noten über einer bestimmten Note usw.)? Wenn Sie eine mögliche Klassenstruktur haben, überlegen Sie, wie Sie diese Aufgaben erledigen würden. Wenn es Ihnen unangenehm erscheint, möchten Sie vielleicht eine andere Klassenstruktur.
MatrixFrog

Antworten:


9

Ich denke, der beste Ansatz ist es, die realen Beziehungen zwischen diesen Einheiten zu reproduzieren.

Zum Beispiel könnten Sie haben:

  • ein NoteObjekt, dessen Eigenschaften sind

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

    • zufällig (natürlich, flach, scharf)

    • Frequenz oder eine andere eindeutige Tonhöhenkennung

  • ein ChordObjekt, dessen Eigenschaften sind

    • eine Reihe von Note Objekten

    • Name

    • versehentlich

    • Qualität (Dur, Moll, vermindert, erweitert, ausgesetzt)

    • Ergänzungen (7, 7+, 6, 9, 9+, 4)

  • ein ScaleObjekt, dessen Eigenschaften sind

    • eine Reihe von Note Objekten

    • Name

    • Typ (Dur, natürliches Moll, melodisches Moll, harmonisches Moll)

    • Modus (ionisch, dorisch, phrygisch, lydisch, mixolidisch, äolisch, locrisch)

Wenn Ihre Eingabe in Textform erfolgt, können Sie Notizen mit einer Zeichenfolge erstellen, die den Notennamen, die zufällige Oktave und (falls erforderlich) die Oktave enthält.

Zum Beispiel (Pseudocode, ich kenne C ++ nicht):

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

Dann in der Note Klasse die Zeichenfolge analysieren und die Eigenschaften festlegen.

A Chordkönnte durch seine Notizen konstruiert werden:

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

... oder durch eine Zeichenfolge mit Name, Qualität und zusätzlichen Anmerkungen:

chord = new Chord('Cmaj7');

Ich weiß nicht genau, was Ihre Bewerbung bewirken wird. Das sind also nur Ideen.

Viel Glück mit Ihrem faszinierenden Projekt!


4

Einige allgemeine Ratschläge.


Wenn im Klassendesign viel Unsicherheit zu erwarten ist (wie in Ihrer Situation), würde ich empfehlen, mit verschiedenen konkurrierenden Klassendesigns zu experimentieren.

Die Verwendung von C ++ in dieser Phase ist möglicherweise nicht so produktiv wie in anderen Sprachen. (Dieses Problem tritt in Ihren Codefragmenten auf, die sich mit typedefund befassen müssenvirtual Destruktoren .) Auch wenn das Projektziel darin besteht, C ++ - Code zu erstellen, kann es produktiv sein, das Design der ersten Klasse in einer anderen Sprache durchzuführen. (Zum Beispiel Java, obwohl es viele Möglichkeiten gibt.)

Wählen Sie C ++ nicht nur wegen der Mehrfachvererbung. Mehrfachvererbung hat seine Verwendung, ist jedoch nicht der richtige Weg, um dieses Problem zu modellieren (Musiktheorie).


Achten Sie besonders auf die Disambiguierung. Obwohl in englischen (textuellen) Beschreibungen häufig Unklarheiten auftreten, müssen diese beim Entwerfen von OOP-Klassen behoben werden.

Wir sprechen von G und Gis als Noten. Wir sprechen von G-Dur und G-Moll als Tonleitern. Somit sind Noteund Scalesind keine austauschbaren Begriffe. Es kann nicht sein irgendein Objekt , das gleichzeitig eine Instanz eines sein kann , Noteund einScale .

Diese Seite enthält einige Diagramme, die die Beziehung veranschaulichen: http://www.howmusicworks.org/600/ChordScale-Relations/Chord-and-Scale-Relations

In einem anderen Beispiel hat "eine Triade, die mit G in C-Dur beginnt " nicht die gleiche Bedeutung wie "eine Triade, die mit C in G-Dur beginnt ".

Zu diesem frühen Zeitpunkt ist die TokenKlasse (die Oberklasse von allem) nicht gerechtfertigt, da sie eine Disambiguierung verhindert. Sie kann bei Bedarf später eingeführt werden (unterstützt von einem Codefragment, das zeigt, wie nützlich dies sein kann.)


Beginnen Sie zunächst mit einer NoteKlasse, die das Zentrum des Klassendiagramms darstellt, und fügen Sie dann nach und nach die Beziehungen (Datenelemente, die Tupeln von Notes zugeordnet werden müssen) zum Klassenbeziehungsdiagramm hinzu.

Eine C- Note ist eine Instanz der NoteKlasse. Eine C- Note gibt Eigenschaften zurück, die sich auf diese Note beziehen, wie z. B. verwandte Triaden und ihre relative Position ( Interval) in Bezug auf eine Note Scale, die mit einer C- Note beginnt .

Beziehungen zwischen Instanzen derselben Klasse (z. B. zwischen einer C- Note und einer E- Note) sollten als Eigenschaften und nicht als Vererbung modelliert werden.

Darüber hinaus werden viele der klassenübergreifenden Beziehungen in Ihren Beispielen auch besser als Eigenschaften modelliert. Beispiel:

(Codebeispiele stehen noch aus, da ich die Musiktheorie neu lernen muss ...)


Interessanter Gedanke, aber wie würde man mit der Bestimmung der Akkordqualitäten im Kontext der harmonischen Analyse umgehen? Eine C-Akkord-Instanz müsste eine Qualitätseigenschaft haben, die auf Moll gesetzt ist (was in Ordnung ist), aber was ist dann mit dominanten / verminderten / erweiterten / Moll-Akkorden mit 7, 9, 11? Es gibt viele Akkorde, zu denen eine einzelne Note gehören kann. Wie würde ich feststellen, welche verschiedenen Akkordtypen und welche Qualitäten sich im Analyseabschnitt des Codes befinden?
Igneous01

Ich kenne sehr wenig Musiktheorie, daher kann ich Ihre Frage nicht beantworten. Eine Möglichkeit, die mir das Verständnis erleichtern kann, besteht darin, eine Tabelle zu finden, in der alle an diesen Konzepten beteiligten Notizen aufgeführt sind. Abfragen nach Akkorden können zusätzliche Parameter enthalten.
Rwong

2
Hier ist eine sehr schöne Liste aller möglichen Akkorde: en.wikipedia.org/wiki/List_of_chords Alle Akkorde können auf jede Note angewendet werden. In meiner Situation ist wichtig, dass die Enharmonik korrekt ist: dh. Cflat major! = BMajor, Sie sind physikalisch der gleiche Akkord auf dem Klavier, aber ihre harmonischen Funktionen sind auf dem Papier sehr unterschiedlich. Ich denke, dass ein Enumerator zum Schärfen / Abflachen einer Note für eine Noteninstanz am sinnvollsten wäre. Auf diese Weise erleichtern mir C.Sharpen () = C # und C.Flatten () = Cb das Überprüfen von Benutzerakkorden.
Igneous01

2

Grundsätzlich sind Noten Frequenzen und musikalische Intervalle Frequenzverhältnisse.

Alles andere kann darauf aufgebaut werden.

Ein Akkord ist eine Liste von Intervallen. Eine Skala ist eine Grundnote und ein Stimmsystem. Ein Stimmsystem ist auch eine Liste von Intervallen.

Wie Sie sie nennen, ist nur ein kulturelles Artefakt.

Der musiktheoretische Artikel von Wikipedia ist ein guter Ausgangspunkt.


Interessant, obwohl ich nicht sicher bin, ob es unbedingt hilfreich ist, das System in Bezug auf die zugrunde liegende physikalische Realität zu modellieren. Denken Sie daran, dass das Modell bei der Formulierung eines bestimmten Aspekts hilfreich sein muss , nicht unbedingt umfassend oder sogar genau. Ihr Ansatz wäre zwar genau und umfassend, aber für den Anwendungsfall von OP möglicherweise zu niedrig.
Konrad Rudolph

@KonradRudolph - Mit meiner extremen Position wollte ich nur darauf hinweisen, dass man das zugrunde liegende Modell nicht mit der Präsentationsebene mischen sollte, ähnlich wie bei der Sommerzeit: Berechnungen sind für das Modell selbst viel einfacher. Ich stimme zu, dass das hilfreichste Abstraktionsniveau nicht das ist, was ich vorschlage, aber ich denke, dass das vom OP vorgeschlagene Abstraktionsniveau auch nicht angemessen ist.
Mouviciel

Der Zweck dieses Programms besteht nicht notwendigerweise darin, die physische Realität der Musik darzustellen. Aber für Leute, die Theorie studieren (wie ich), ist es nur möglich, einige Akkorde schnell einzuzeichnen und das Programm so gut wie möglich interpretieren zu lassen, wie diese Akkorde im harmonischen Sinne zueinander in Beziehung stehen. Ich könnte es einfach auf die bewährte Art und Weise analysieren, die Punktzahl nach Maß abzulesen, aber dies ist ein weiteres Werkzeug, um die Dinge einfacher zu machen und sich bei der Analyse auf die feineren Details konzentrieren zu können.
Igneous01

1

Ich finde diese Diskussion faszinierend.

Werden die Noten über Midi (oder eine Art Tonaufnahmegerät) eingegeben oder werden sie durch Eingabe der Buchstaben und Symbole eingegeben?

Im Fall des Intervalls von C bis Dis / Es:

Obwohl Dis und Es der gleiche Ton sind (um 311 Hz, wenn A = 440 Hz), wird das Intervall von C -> Dis um eine erhöhte Sekunde erhöht, während das Intervall von C -> Es wie a geschrieben wird Moll 3.. Einfach genug, wenn Sie wissen, wie die Notiz geschrieben wurde. Es ist unmöglich zu bestimmen, ob Sie nur die zwei Töne haben, die weitergehen sollen.

In diesem Fall werden Sie meines Erachtens auch eine Möglichkeit benötigen, den Ton zusammen mit den genannten Methoden .Sharpen () und .Flatten () zu erhöhen / zu verringern, z. B. .SemiToneUp (), .FullToneDown () usw. dass Sie nachfolgende Noten in einer Skala finden können, ohne sie als Sharps / Flats "einzufärben".

Ich muss @Rotem zustimmen, dass "C" keine Klasse für sich ist, sondern eine Instanziierung der Note-Klasse.

Wenn Sie die Eigenschaften für eine Note definieren, einschließlich aller Intervalle als Halbtöne, können Sie unabhängig vom anfänglichen Notenwert ("C", "F", "G #") feststellen, dass eine Sequenz mit drei Noten die folgende hat root, major 3rd (M3), dann minor 3rd (m3) wäre eine Dur-Triade. In ähnlicher Weise ist m3 + M3 eine kleine Triade, m3 + m3 verringert, M3 + M3 erhöht. Außerdem können Sie auf diese Weise das Ermitteln der 11., der verminderten 13. usw. kapseln, ohne diese explizit für alle 12 Basisnoten und ihre Oktaven nach oben und unten zu codieren.

Sobald dies erledigt ist, haben Sie noch einige Probleme zu lösen.

Nehmen Sie die Triade C, E, G. Als Musiker sehe ich das deutlich als Cmaj-Akkord. Der Entwickler in mir kann dies jedoch zusätzlich als e-Moll-Augment 5 (Wurzel E + m3 + a5) oder als Gsus4 6.-5. (Wurzel G + 4 + 6) interpretieren.

Um Ihre Frage zur Durchführung der Analyse zu beantworten, ist es meiner Meinung nach die beste Methode, die Modalität (Maj, Moll usw.) zu bestimmen, alle eingegebenen Noten zu nehmen, in aufsteigenden Halbtonwerten anzuordnen und sie mit den bekannten Akkordformen zu testen . Verwenden Sie dann jede als Grundton eingegebene Note und führen Sie die gleichen Auswertungen durch.

Sie können die Akkordformen so gewichten, dass häufiger (Dur, Moll) Vorrang vor den Akkordformen Augmented, Suspended, Elektra usw. hat. Für eine genaue Analyse müssen jedoch alle übereinstimmenden Akkordformen als mögliche Lösungen angegeben werden.

Auch in dem Wikipedia-Artikel, auf den verwiesen wird, werden die Tonhöhenklassen gut aufgelistet. Daher sollte es einfach (wenn auch mühsam) sein, die Modelle der Akkorde zu codieren, die eingegebenen Noten zu nehmen, sie Tonhöhenklassen / Intervallen zuzuordnen und dann zu vergleichen gegen die bekannten Formen für Streichhölzer.

Das hat sehr viel Spaß gemacht. Vielen Dank!


Sie werden vorerst per Text eingegeben. Allerdings kann ich später möglicherweise Midi verwenden, wenn das Programm richtig gekapselt ist.
Derzeitige Babyschritte

0

Klingt wie ein Fall für Vorlagen. Sie scheinen ein zu haben , template <?> class Major : public Chord;so Major<C>ist-ein Chord, wie Major<B>. Ebenso haben Sie auch eine Note<?>Vorlage mit Instanzen Note<C>und Note<D>.

Das einzige, was ich ausgelassen habe, ist der ?Teil. Es scheint, Sie haben eine, enum {A,B,C,D,E,F,G}aber ich weiß nicht, wie Sie diese Aufzählung nennen würden.


0

Vielen Dank für alle Vorschläge, irgendwie habe ich es geschafft, die zusätzlichen Antworten zu verpassen. Bisher sind meine Klassen so angelegt:

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

Um meine Probleme mit der Intervall- und Akkordberechnung zu lösen, habe ich mich für den Ringspeicher entschieden, mit dem ich den Puffer von einem beliebigen Punkt aus durchlaufen kann, bis ich die nächste passende Note finde.

Stoppen Sie, wenn die Buchstaben übereinstimmen (nur der Buchstabe, nicht die tatsächliche Note oder Position), um das interpretierte Intervall zu finden, das den Puffer für echte Noten durchläuft. C - g # = 5

Um die reale Distanz zu finden, die einen anderen Puffer mit 12 ganzen Zahlen durchläuft, stoppen Sie, wenn die obere Notenposition mit dem Wert des Puffers am Index übereinstimmt. Auch dies bewegt sich nur vorwärts. Der Offset kann aber überall sein (zB buffer.at (-10))

Jetzt kenne ich sowohl das interpretierte Intervall als auch die physische Entfernung zwischen den beiden. Der Intervallname ist also bereits zur Hälfte vollständig.

jetzt kann ich das Intervall interpretieren, dh. Wenn das Intervall 5 und der Abstand 8 ist, ist es ein erhöhter fünfter.

Bisher funktionieren Note und Intervall wie erwartet, jetzt muss ich nur noch die Akkordkennung in Angriff nehmen.

Nochmals vielen Dank, ich werde einige dieser Antworten noch einmal lesen und hier einige Ideen einfließen lassen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.