OO Design, come modellare Tonal Harmony?

12

Ho iniziato a scrivere un programma in C ++ 11 per analizzare accordi, scale e armonia. Il problema più grande che ho 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 presenta con intervalli (minore 3, maggiore 3).

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 includendo tutte le altre note e anche gli enharmonics. l'ereditarietà multipla non funzionerebbe, cioè:

class C : public Note, Triad, Key, Scale

la classe C, non può essere tutte queste cose allo stesso tempo. È contestuale, anche il polimorfo con questo non funzionerà (come determinare quali super metodi eseguire? Chiamare ogni costruttore di super-classe 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 troppe relazioni tra tutti i concetti qui.

    
posta Igneous01 09.12.2012 - 13:55
fonte

6 risposte

9

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

Ad esempio, potresti avere:

  • un oggetto Note , le cui proprietà sono

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

    • accidentali (naturali, piatti, nitidi)

    • frequenza o un altro identificatore di inclinazione univoco

  • un oggetto Chord , le cui proprietà sono

    • un array di Note oggetti

    • nome

    • accidentale

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

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

  • un oggetto Scale , le cui proprietà sono

    • un array di Note oggetti

    • nome

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

    • modalità (ionico, dorico, frigio, lidio, mixolidiano, eoliano, locrian)

Quindi, se il tuo input è testuale, puoi creare note con una stringa includendo il nome della nota, accidentale e (se necessario) di ottava.

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

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

Quindi, nella classe Note puoi analizzare la stringa e impostare le proprietà.

Un Chord potrebbe essere costruito con le sue note:

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

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

chord = new Chord('Cmaj7');

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

Buona fortuna con il tuo affascinante progetto!

    
risposta data 11.12.2012 - 11:59
fonte
4

Alcuni consigli generici.

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

L'uso di C ++ in questa fase potrebbe non essere produttivo quanto altri linguaggi. (Questo problema è evidente nei frammenti di codice che devono occuparsi di typedef e virtual destructors.) Anche se l'obiettivo del progetto è produrre codice C ++, potrebbe essere produttivo eseguire la progettazione di classi iniziali in un'altra lingua. (Ad esempio Java, anche se ci sono molte scelte.)

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 disambiguo. Anche se le ambiguità sono abbondanti nelle descrizioni in inglese (testuali), queste ambiguità devono essere risolte quando si progettano le classi OOP.

Parliamo di G e G sharp come note. Parliamo di Sol maggiore e Sol minore come scale. Pertanto, Note e Scale non sono concetti intercambiabili. Non è possibile che sia qualsiasi oggetto che può essere contemporaneamente un'istanza di Note e Scale .

Questa pagina contiene alcuni diagrammi che illustrano la relazione: link

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

In questa fase iniziale, la classe Token (la superclasse di tutto) è ingiustificata, perché impedisce la disambiguazione. Potrebbe essere introdotto in seguito, se necessario (supportato da un frammento di codice che dimostra come ciò potrebbe essere utile).

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

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

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

Inoltre, molte delle relazioni inter-classe nei tuoi esempi sono anche più opportunamente modellate come proprietà. Esempio:

(gli esempi di codice sono in attesa perché ho bisogno di ri-imparare la teoria musicale ...)

    
risposta data 09.12.2012 - 14:19
fonte
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 sintonizzazione. Un sistema di sintonizzazione è anche un elenco di intervalli.

Come li chiami è solo un artefatto culturale.

L'articolo Teoria della musica di Wikipedia è un buon punto di partenza.

    
risposta data 14.01.2013 - 20:24
fonte
1

Sto trovando affascinante questa dicussione.

Le note vengono inserite tramite midi (o qualche tipo di dispositivo di acquisizione toni) o vengono inserite 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 (intorno a 311 Hz se A = 440Hz), l'intervallo da C - > D-sharp è scritto un 2 ° aumentato, mentre l'intervallo da C - > E-flat è writtem come un terzo minore. Abbastanza facile se sai come è stata scritta la nota. Impossibile determinare se hai solo due toni per andare avanti.

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

Devo concordare 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, quindi indipendentemente dal valore della nota iniziale ("C", "F", "G #") si sarà in grado di dire che una sequenza di tre note che ha la radice, maggiore 3 (M3), poi minore 3 (m3) sarebbe una triade maggiore. Allo stesso modo, m3 + M3 è una triade minore, m3 + m3 diminuita, M3 + M3 aumentata. Inoltre, questo ti darebbe un modo per incapsulare trovando l'undicesimo, il 13 diminuito, ecc. Senza codificarli esplicitamente per tutte e 12 le 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 di Cmaj. Tuttavia, lo sviluppatore in me può interpretare questa aggiunta come E minor Augment 5 (Root E + m3 + a5) o Gsus4 6th no 5th (RootG + 4 + 6).

Quindi, per rispondere alla tua domanda sull'analisi, penso che il modo migliore per determinare la modalità (maggiore, minore, ecc.) sia prendere tutte le note inserite, sistemarle in valore semitonale crescente e metterle alla prova forme di accordi conosciute. Quindi, utilizzare ciascuna nota inserita come nota fondamentale ed eseguire lo stesso insieme di valutazioni.

Potresti appesantire le forme dell'accordo in modo che le più comuni (maggiori, minori) abbiano la precedenza sulle forme di accordi aumentate, sospese, elektra, ecc., ma un'analisi accurata richiederebbe di presentare tutte le forme di accordi corrispondenti come possibili soluzioni.

Anche in questo caso l'articolo di wikipedia cui si fa riferimento fa un buon lavoro di elencazione delle classi di pitch, quindi dovrebbe essere semplice (anche se noioso) codificare i modelli degli accordi, prendere le note inserite, assegnarle a classi / intervalli di pitch, e quindi confrontare le forme conosciute per le partite.

Questo è stato molto divertente. Grazie!

    
risposta data 10.12.2012 - 20:01
fonte
0

Sembra un caso per i modelli. Sembra che tu abbia un template <?> class Major : public Chord; quindi Major<C> è-a Chord , come Major<B> . Allo stesso modo, hai anche un modello Note<?> con istanze Note<C> e Note<D> .

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

    
risposta data 11.12.2012 - 13:30
fonte
0

Grazie per tutti i suggerimenti, in qualche modo sono riuscito a perdere le risposte extra. Finora le mie classi 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 permette di attraversare il buffer da qualsiasi punto, andando avanti, finché non trovo la prossima nota 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 sta procedendo solo in avanti. Ma l'offset può essere ovunque (ad esempio 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, es. se l'intervallo è 5 e la distanza è 8, allora è un 5 ° aumentato.

Fino ad ora le note e gli intervalli funzionano come previsto, ora devo solo affrontare l'identificatore di accordo.

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

    
risposta data 14.01.2013 - 19:37
fonte

Leggi altre domande sui tag