OO Design, come modellare Tonal Harmony?


12

Ho iniziato a scrivere un programma in C ++ 11 che analizzasse accordi, scale e armonia. Il problema più grande che sto riscontrando nella mia fase di progettazione è che la nota 'C' è una nota, un tipo di accordo (Cmaj, Cmin, C7, ecc.) E un tipo di chiave (la chiave di Cmajor, Cminor). Lo stesso problema si pone con intervalli (3 ° minore, 3 ° maggiore).

Sto usando una classe base, Token, che è la classe base per tutti i "simboli" nel programma. quindi ad esempio:

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;

Come puoi vedere, creare tutte le classi derivate (CMajorTriad, C, CMajorScale, CMajorKey, ecc.) Diventerebbe rapidamente ridicolmente complesso, comprese tutte le altre note, così come gli enharmonics. l'ereditarietà multipla non funzionerebbe, ovvero:

class C : public Note, Triad, Key, Scale

classe C, non possono essere tutte queste cose contemporaneamente. È contestuale, anche il polimorfismo con questo non funzionerà (come determinare quali super metodi eseguire? Chiamare tutti i costruttori di superclasse non dovrebbe accadere qui)

Ci sono idee o suggerimenti di design che le persone hanno da offrire? Non sono stato in grado di trovare nulla su Google per quanto riguarda la modellazione dell'armonia tonale da una prospettiva OO. Ci sono fin troppe relazioni tra tutti i concetti qui.


8
Perché 'C' dovrebbe essere una classe? Immagino che 'Note', 'Accordo', ecc. Siano classi, che potrebbero avere un elenco di valori in cui l'enum 'C' potrebbe svolgere un ruolo.
Rotem,

Se l'utente inserisce-> accordo CEG, dovrebbe dedurre quali sono le note per formare l'accordo appropriato. Stavo pensando di passare un vettore di <Note> come parametri al metodo execute (), che sarebbe stato gestito polimorficamente. Tuttavia, usare un enumeratore avrebbe senso, ma allora avrei bisogno di creare un'istanza di ogni oggetto con l'enum che voglio usare.
Igneous01

Sono con @Rotem su questo: a volte, devi solo preferire la composizione degli oggetti rispetto all'eredità.
Spoike,

Mi sembra che potrebbe essere utile pensare a cosa vuoi fare con queste classi note / chord / scale. Hai intenzione di produrre spartiti? File Midi? Trasformazioni su spartiti (trasposizione, raddoppio di tutte le lunghezze delle note, aggiunta di trilli a tutte le note intere sopra una certa nota, ecc.)? Una volta che hai una possibile struttura di classe, pensa a come svolgere tali compiti. Se sembra imbarazzante, forse vuoi una struttura di classe diversa.
MatrixFrog,

Risposte:


9

Penso che l'approccio migliore sia quello di riprodurre le relazioni reali tra queste entità.

Ad esempio, potresti avere:

  • un Noteoggetto, le cui proprietà sono

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

    • accidentale (naturale, piatto, affilato)

    • frequenza o un altro identificatore univoco del tono

  • un Chordoggetto, le cui proprietà sono

    • una matrice di Noteoggetti

    • nome

    • accidentale

    • qualità (maggiore, minore, diminuita, aumentata, sospesa)

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

  • un Scaleoggetto, le cui proprietà sono

    • una matrice di Noteoggetti

    • nome

    • tipo (maggiore, minore naturale, minore melodico, minore armonico)

    • modalità (ionico, dorico, frigio, lidio, mixolido, eolico, locriano)

Quindi, se il tuo input è testuale, puoi creare note con una stringa che includa il nome della nota, l'ottava accidentale e (se ne hai bisogno).

Ad esempio (pseudocodice, non conosco C ++):

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

Quindi, nella Noteclasse è possibile analizzare la stringa e impostare le proprietà.

A Chordpotrebbe essere costruito dalle sue note:

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

... o con una stringa che include nome, qualità e note aggiuntive:

chord = new Chord('Cmaj7');

Non so esattamente cosa farà la tua candidatura, quindi queste sono solo idee.

Buona fortuna con il tuo affascinante progetto!


4

Alcuni consigli generici.


Se ci sono molte incertezze nel design della classe (come nella tua situazione), consiglierei di sperimentare con diversi design di classe concorrenti.

L'uso del C ++ in questa fase potrebbe non essere produttivo come altri linguaggi. (Questo problema è evidente nei frammenti di codice che devono affrontare typedefe con i virtualdistruttori.) Anche se l'obiettivo del progetto è produrre codice C ++, potrebbe essere produttivo progettare la classe iniziale in un'altra lingua. (Ad esempio Java, anche se ci sono molte opzioni.)

Non scegliere C ++ solo per ereditarietà multipla. L'ereditarietà multipla ha i suoi usi ma non è il modo corretto di modellare questo problema (teoria della musica).


Presta particolare attenzione al chiarimento delle ambiguità. Anche se le ambiguità sono abbondanti nelle descrizioni in inglese (testuali), queste ambiguità devono essere risolte quando si progettano classi OOP.

Parliamo di G e G taglienti come note. Parliamo di sol maggiore e sol minore come scale. Pertanto, Notee Scalenon sono concetti intercambiabili. Non potrebbe esserci alcun oggetto che possa essere contemporaneamente un'istanza di a Notee a Scale.

Questa pagina contiene alcuni diagrammi che illustrano la relazione: http://www.howmusicworks.org/600/ChordScale-Relations/Chord-and-Scale-Relations

Per un altro esempio, "una triade che inizia con G su una scala maggiore di C " non ha lo stesso significato di "una triade che inizia con C su una scala di maggiore G ".

In questa fase iniziale, la Tokenclasse (la superclasse di tutto) è ingiustificata, perché impedisce la chiarimento delle ambiguità. Potrebbe essere introdotto in seguito, se necessario (supportato da un frammento di codice che dimostra come ciò possa essere utile.)


Per cominciare, inizia con una Noteclasse che è il centro del diagramma di classe, quindi aggiungi gradualmente le relazioni (parti di dati che devono essere associate alle tuple di Notes) al diagramma delle relazioni di classe.

Una nota C è un'istanza della Noteclasse. Una nota C restituirà proprietà correlate a questa nota, come le triadi correlate, e la sua posizione relativa ( Interval) rispetto a una Scaleche inizia con una nota C.

Le relazioni tra istanze della stessa classe (ad esempio, tra una nota C e una nota E ) devono essere modellate come proprietà, non ereditarietà.

Inoltre, molte delle relazioni tra le classi nei tuoi esempi sono anche modellate in modo più appropriato come proprietà. Esempio:

(esempi di codice sono in sospeso perché ho bisogno di riapprendere la teoria musicale ...)


Pensiero interessante, ma come si potrebbe gestire la determinazione delle qualità degli accordi nel contesto dell'analisi armonica? C L'istanza di Chord dovrebbe avere proprietà di qualità, impostata su minore (il che è ok) ma che dire di un accordo dominante / diminuito / aumentato / minore di 7s, 9, 11? Ci sono molti accordi a cui può appartenere una singola nota. Come determinerei quali sono i vari tipi di accordi e le loro rispettive qualità nella sezione di analisi del codice?
Igneous01

Conosco pochissima teoria musicale, quindi non sono in grado di rispondere alla tua domanda. Un modo che può aiutarmi a capire sarebbe quello di trovare una tabella che elenca tutte le note coinvolte in quei concetti. Le query per gli accordi possono richiedere parametri aggiuntivi.
rwong

2
Ecco un bellissimo elenco di tutti gli accordi possibili: en.wikipedia.org/wiki/List_of_chords Tutti gli accordi possono essere applicati a qualsiasi nota, ciò che è importante nella mia situazione è che gli enarmonici siano corretti: ad es. Cflat major! = BMajor, Fisicamente sono lo stesso accordo sul piano, ma le loro funzioni armoniche sono molto diverse sulla carta. Sto pensando che un enumeratore per lo sharp / flatting di una nota avrebbe più senso per un'istanza di nota. In entrambi i casi, C.Sharpen () = C # e C.Flatten () = Cb, questo potrebbe semplificarmi la convalida degli accordi dell'utente.
Igneous01

2

Fondamentalmente, le note musicali sono frequenze e gli intervalli musicali sono rapporti di frequenza.

Tutto il resto può essere costruito su questo.

Un accordo è un elenco di intervalli. Una scala è una nota fondamentale e un sistema di accordatura. Un sistema di ottimizzazione è anche un elenco di intervalli.

Il modo in cui li chiami è solo un manufatto culturale.

L' articolo della teoria musicale di Wikipedia è un buon punto di partenza.


Interessante, anche se non sono sicuro che sia necessariamente utile modellare il sistema in termini di realtà fisica sottostante. Ricorda, il modello deve essere utile per articolare un aspetto particolare, non necessariamente per essere completo o addirittura accurato. Sebbene il tuo approccio sia accurato e completo, potrebbe essere di livello troppo basso per il caso d'uso di OP.
Konrad Rudolph,

@KonradRudolph - Con la mia posizione estrema, volevo solo sottolineare che non si dovrebbe mescolare il modello sottostante con il livello di presentazione, in modo simile ai tempi di ora legale: i calcoli sono molto più facili sul modello stesso. Concordo sul fatto che il livello di astrazione più utile non è quello che suggerisco, ma ritengo che anche il livello di astrazione suggerito dal PO non sia adeguato.
mouviciel,

Lo scopo di questo programma non è necessariamente quello di mostrare la realtà fisica della musica. Ma per le persone che studiano la teoria (come me) essere in grado di tracciare rapidamente alcuni accordi e far sì che il programma interpreti al meglio le loro capacità in che modo questi accordi sono collegati tra loro in senso armonico. Potrei semplicemente analizzarlo nel modo provato e vero di leggere la misura del punteggio per misura, ma questo è un altro strumento per semplificare le cose e per essere in grado di concentrarsi sui dettagli più fini durante l'analisi.
Igneous01

1

Trovo questa dicussione affascinante.

Le note vengono immesse tramite MIDI (o un qualche tipo di dispositivo di acquisizione del tono) o vengono immesse digitando lettere e simboli?

Nel caso dell'intervallo da C a D-sharp / E-flat:

Sebbene D-sharp ed E-flat abbiano lo stesso tono (circa 311Hz se A = 440Hz), l'intervallo da C -> D-sharp è scritto un 2 ° aumentato, mentre l'intervallo da C -> E-flat è scritto come un terzo minore. Abbastanza facile se sai come è stata scritta la nota. Impossibile determinare se hai solo i due toni per continuare.

In questo caso, credo che avrai anche bisogno di un modo per aumentare / diminuire il tono insieme ai metodi .Sharpen () e .Flatten () menzionati, come .SemiToneUp (), .FullToneDown (), ecc. che puoi trovare le note successive in una scala senza "colorarle" come oggetti taglienti / piatti.

Sono d'accordo con @Rotem che "C" non è una classe in sé e per sé, ma piuttosto un'istanza della classe Note.

Se si definiscono le proprietà di una nota, inclusi tutti gli intervalli come semitoni, indipendentemente dal valore della nota iniziale ("C", "F", "G #") si sarebbe in grado di dire che una sequenza di tre note che ha il radice, terza maggiore (M3), quindi terza minore (m3) sarebbe una triade maggiore. Allo stesso modo, m3 + M3 è una triade minore, m3 + m3 diminuito, M3 + M3 aumentato. Inoltre, questo ti darebbe un modo per incapsulare la ricerca dell'11 °, 13 ° diminuito, ecc. Senza codificarli esplicitamente per tutte le 12 note di base e le loro ottave su e giù.

Una volta fatto, ti rimangono ancora alcuni problemi da risolvere.

Prendi la triade C, E, G. Come musicista, lo vedo chiaramente come un accordo Cmaj. Tuttavia, lo sviluppatore in me può interpretare questo addizionale come E minore Augment 5 (Root E + m3 + a5) o Gsus4 6th no 5th (RootG + 4 + 6).

Quindi, per rispondere alla tua domanda sull'esecuzione dell'analisi, penso che il modo migliore per determinare la modalità (maggiore, minore, ecc.) Sarebbe quello di prendere tutte le note inserite, disporle in valore crescente di semitono e metterle alla prova con le forme note dell'accordo . Quindi, utilizzare ciascuna nota immessa come nota radice ed eseguire lo stesso set di valutazioni.

È possibile ponderare le forme di accordi in modo tale che più comuni (maggiore, minore) abbiano la precedenza sulle forme di accordo aumentate, sospese, elektra, ecc., Ma un'analisi accurata richiederebbe di presentare tutte le forme di accordo corrispondenti come possibili soluzioni.

Ancora una volta l'articolo di Wikipedia a cui fa riferimento fa un buon lavoro nell'elencare le classi di intonazione, quindi dovrebbe essere semplice (anche se noioso) codificare i modelli degli accordi, prendere le note inserite, assegnarle a classi / intervalli di intonazione e quindi confrontare contro le forme conosciute per le partite.

È stato molto divertente. Grazie!


Sono stati immessi, tramite testo per ora. Tuttavia in seguito potrei essere in grado di utilizzare Midi se il programma è incapsulato correttamente. I
piccoli

0

Sembra un caso per i modelli. Ti sembra di avere un template <?> class Major : public Chord;così Major<C>è-un Chord, come è Major<B>. Allo stesso modo, hai anche un Note<?>modello con istanze Note<C>e Note<D>.

L'unica cosa che ho lasciato fuori è la ?parte. Sembra che tu abbia un enum {A,B,C,D,E,F,G}ma non so come chiameresti quell'enum.


0

Grazie per tutti i suggerimenti, in qualche modo sono riuscito a perdere le risposte extra. Finora le mie lezioni sono state progettate in questo modo:

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

Per risolvere i miei problemi di calcolo degli intervalli e degli accordi, ho deciso di utilizzare il buffer circolare, che mi consente di attraversare il buffer da qualsiasi punto, andando avanti, fino a quando non trovo la nota successiva che corrisponde.

Per trovare l'intervallo interpretato, attraversa il buffer delle note reali, fermati quando le lettere corrispondono (solo la lettera, non la nota o la posizione effettiva), quindi c - g # = 5

Per trovare la distanza reale, attraversa un altro buffer di 12 numeri interi, fermati quando la posizione della nota superiore è uguale al valore del buffer nell'indice, anche in questo caso si sposta solo in avanti. Ma l'offset può essere ovunque (es. Buffer.at (-10))

ora conosco sia l'intervallo interpretato, sia la distanza fisica tra i due. quindi il nome dell'intervallo è già mezzo completo.

ora sono in grado di interpretare l'intervallo, cioè. se l'intervallo è 5 e la distanza è 8, allora è un 5 ° aumentato.

Finora note e intervalli funzionano come previsto, ora devo solo affrontare l'identificatore degli accordi.

Grazie ancora, rileggerò alcune di queste risposte e includerò alcune idee qui.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.