OO Design, como modelar Tonal Harmony?


12

Comecei a escrever um programa em C ++ 11 que analisaria acordes, escalas e harmonia. O maior problema que estou tendo na minha fase de design é que a nota 'C' é uma nota, um tipo de acorde (Cmaj, Cmin, C7, etc) e um tipo de chave (a chave de Cmajor, Cminor). O mesmo problema surge com intervalos (terceiro menor, terceiro maior).

Estou usando uma classe base, Token, que é a classe base para todos os 'símbolos' no programa. por exemplo:

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;

Como você pode ver, criar todas as classes derivadas (CMajorTriad, C, CMajorScale, CMajorKey etc.) rapidamente se tornaria ridiculamente complexa, incluindo todas as outras notas, além de enarmônicos. herança múltipla não funcionaria, ou seja:

class C : public Note, Triad, Key, Scale

classe C, não pode ser todas essas coisas ao mesmo tempo. É contextual, também polimorfismo com isso não funcionará (como determinar quais super métodos a serem executados? Chamar todos os construtores de superclasses não deve acontecer aqui)

Existem idéias ou sugestões de design que as pessoas têm a oferecer? Não consegui encontrar nada no google com relação à modelagem da harmonia tonal de uma perspectiva OO. Existem relações demais entre todos os conceitos aqui.


8
Por que 'C' seria uma classe? Eu imaginaria 'Note', 'Chord', etc. seriam classes, que poderiam ter uma enumeração de valor na qual a enumeração 'C' poderia desempenhar um papel.
Rotem

Se o usuário digitar-> acorde CEG, seria necessário deduzir quais são as notas para formar o acorde apropriado. Eu estava pensando em passar um vetor de <Notes> como parâmetros para o método execute (), que seria tratado polimorficamente. No entanto, o uso de um enumerador faria sentido, mas seria necessário instanciar cada objeto com a enumeração que desejo usar.
precisa saber é o seguinte

Estou com o @Rotem nesta questão: às vezes, você só precisa preferir a composição do objeto do que a herança.
Spoike 9/12/12

Parece-me que pode ser útil pensar no que você quer fazer com essas classes de notas / acordes / escalas. Você vai produzir partituras? Arquivos Midi? Faça transformações nas partituras (transposição, duplicação de todos os comprimentos das notas, adição de notas em todas as notas inteiras acima de uma determinada nota etc.)? Depois de ter uma estrutura de classe possível, pense em como você executaria essas tarefas. Se parecer estranho, talvez você queira uma estrutura de classe diferente.
MatrixFrog #

Respostas:


9

Eu acho que a melhor abordagem é reproduzir os relacionamentos reais entre essas entidades.

Por exemplo, você poderia ter:

  • um Noteobjeto cujas propriedades são

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

    • acidental (natural, plano, afiado)

    • frequência ou outro identificador exclusivo de afinação

  • um Chordobjeto cujas propriedades são

    • uma matriz de Noteobjetos

    • nome

    • acidental

    • qualidade (maior, menor, diminuída, aumentada, suspensa)

    • adições (7, 7+, 6, 9, 9+, 4)

  • um Scaleobjeto cujas propriedades são

    • uma matriz de Noteobjetos

    • nome

    • tipo (maior, menor natural, menor melódico, menor harmônico)

    • modo (ioniano, dórico, frígio, lídio, mixolidiano, eólico, locriano)

Então, se sua entrada for textual, você poderá criar notas com uma string, incluindo o nome da nota, acidental e (se necessário) oitava.

Por exemplo (pseudocódigo, não sei C ++):

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

Em seguida, na Noteclasse você pode analisar a sequência e definir as propriedades.

A Chordpoderia ser construído por suas notas:

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

... ou por uma string, incluindo nome, qualidade e notas adicionais:

chord = new Chord('Cmaj7');

Não sei exatamente o que seu aplicativo fará, então são apenas ideias.

Boa sorte com seu projeto fascinante!


4

Alguns conselhos genéricos.


Se houver muita incerteza esperada no design da classe (como na sua situação), eu recomendaria experimentar diferentes designs de classe concorrentes.

O uso do C ++ nesse estágio pode não ser tão produtivo quanto outras linguagens. (Esse problema é aparente nos fragmentos de código que precisam ser tratados typedefe virtualdestruidores.) Mesmo que o objetivo do projeto seja produzir código C ++, pode ser produtivo criar o design de classe inicial em outro idioma. (Por exemplo, Java, embora haja muitas opções.)

Não escolha C ++ apenas por causa da herança múltipla. A herança múltipla tem seus usos, mas não é a maneira correta de modelar esse problema (teoria musical).


Tome especial atenção para desambiguar. Embora as ambigüidades sejam abundantes nas descrições em inglês (textuais), essas ambigüidades devem ser resolvidas ao projetar classes OOP.

Falamos de G e G afiados como notas. Falamos de Sol maior e Sol menor como escalas. Assim, Notee Scalenão são conceitos intercambiáveis. Não pode haver nenhum objeto que possa ser simultaneamente uma instância de a Notee a Scale.

Esta página contém alguns diagramas que ilustram o relacionamento: http://www.howmusicworks.org/600/ChordScale-Relations/Chord-and-Scale-Relations

Para outro exemplo, "uma tríade que começa com G em uma escala maior C " não tem o mesmo significado que "uma tríade que começa com C em uma escala maior G ".

Nesta fase inicial, a Tokenclasse (a superclasse de tudo) é injustificada, porque impede a desambiguação. Pode ser introduzido posteriormente, se necessário (suportado por um fragmento de código que demonstra como isso pode ser útil).


Para começar, comece com uma Noteclasse que é o centro do diagrama de classes e adicione gradualmente os relacionamentos (dados que precisam ser associados às tuplas de Notes) ao diagrama de relacionamentos de classe.

Uma nota C é uma instância da Noteclasse. Uma nota C retornará propriedades relacionadas a esta nota, como tríades relacionadas, e sua posição relativa ( Interval) em relação a uma Scaleque comece com uma nota C.

Os relacionamentos entre instâncias da mesma classe (por exemplo, entre uma nota C e uma nota E ) devem ser modelados como propriedades, não como herança.

Além disso, muitos dos relacionamentos entre classes nos seus exemplos também são modelados de maneira mais apropriada como propriedades. Exemplo:

(exemplos de código estão pendentes porque preciso reaprender teoria musical ...)


Pensamento interessante, mas como lidar com a determinação das qualidades de acordes no contexto da análise harmônica? C A instância de acorde precisaria ter uma propriedade de qualidade, definida como menor (o que é razoável), mas e quanto a acordes dominantes / diminuídos / aumentados / menores 7s, 9, 11? Existem muitos acordes aos quais uma única nota pode pertencer. Como determinaria quais são os vários tipos de acordes e suas respectivas qualidades na seção de análise do código?
perfil completo de Igneous

Conheço muito pouca teoria musical, por isso não sou capaz de responder à sua pergunta. Uma maneira de me ajudar a entender seria encontrar uma tabela que lista todas as notas envolvidas nesses conceitos. As consultas para acordes podem ter parâmetros adicionais.
rwong

2
Aqui está uma lista muito agradável de todos os acordes possíveis: en.wikipedia.org/wiki/List_of_chords Todos os acordes podem ser aplicados a qualquer nota, o que é importante na minha situação é que as enarmônicas estão corretas: ie. Cflat major! = BMajor, Eles são fisicamente o mesmo acorde no piano, mas suas funções harmônicas são muito diferentes no papel. Eu estou pensando que um enumerador para afiar / achatar uma nota faria mais sentido para uma instância de nota. Dessa forma, C.Sharpen () = C # e C.Flatten () = Cb, isso pode facilitar a validação de acordes do usuário.
precisa saber é o seguinte

2

Basicamente, as notas musicais são frequências e intervalos musicais são proporções de frequência.

Tudo o resto pode ser construído sobre isso.

Um acorde é uma lista de intervalos. Uma escala é uma nota fundamental e um sistema de afinação. Um sistema de ajuste também é uma lista de intervalos.

Como você os nomeia é apenas um artefato cultural.

O artigo de teoria musical da Wikipedia é um bom ponto de partida.


Interessante, embora não tenha certeza de que seja necessariamente útil modelar o sistema em termos da realidade física subjacente. Lembre-se de que o modelo deve ser útil na articulação de um aspecto específico, não necessariamente para ser abrangente ou mesmo preciso. Embora sua abordagem seja precisa e abrangente, pode ser um nível muito baixo para o caso de uso do OP.
precisa saber é o seguinte

@KonradRudolph - Com minha posição extrema, eu só queria ressaltar que não se deve misturar o modelo subjacente com a camada de apresentação, de maneira semelhante aos horários de verão: as computações são muito mais fáceis no próprio modelo. Concordo que o nível de abstração mais útil não é o que sugiro, mas acho que o nível de abstração sugerido pelo OP também não é adequado.
Mouviciel 15/01

O objetivo deste programa não é necessariamente exibir a realidade física da música. Porém, para as pessoas que estudam teoria (como eu), são capazes de plotar rapidamente alguns acordes e fazer com que o programa interprete com a melhor capacidade possível como esses acordes estão relacionados entre si em um sentido harmônico. Eu poderia apenas analisá-lo da maneira experimentada e verdadeira de ler a pontuação medida por medida, mas essa é outra ferramenta para facilitar as coisas e ser capaz de focar nos detalhes mais refinados ao analisar.
precisa saber é o seguinte

1

Estou achando essa discussão fascinante.

As notas estão sendo inseridas via midi (ou algum tipo de dispositivo de captura de tom) ou estão sendo digitadas digitando as letras e os símbolos?

No caso do intervalo de C a D-sharp / E-flat:

Embora D-sharp e E-flat sejam o mesmo tom (em torno de 311Hz se A = 440Hz), o intervalo de C -> D-sharp é gravado em um segundo aumentado, enquanto o intervalo de C -> E-flat é gravado como um menor 3º. Fácil, se você souber como a nota foi escrita. Impossível determinar se você tem apenas os dois tons para continuar.

Nesse caso, acredito que você também precisará de uma maneira de aumentar / diminuir o tom, juntamente com os métodos .Sharpen () e .Flatten () mencionados, como .SemiToneUp (), .FullToneDown () etc.) que você pode encontrar notas subesquentes em uma escala sem "colori-las" como objetos cortantes / planos.

Eu tenho que concordar com o @Rotem que "C" não é uma classe em si, mas sim uma instanciação da classe Note.

Se você definir as propriedades de uma nota, incluindo todos os intervalos como semitons, independentemente do valor inicial da nota ("C", "F", "G #"), poderá dizer que uma sequência de três notas possui o raiz, terceiro maior (M3) e terceiro menor (m3) seria uma tríade maior. Da mesma forma, m3 + M3 é uma tríade menor, m3 + m3 diminuído, M3 + M3 aumentado. Além disso, isso permitiria encapsular a localização da 11ª, da 13ª diminuída, etc., sem codificá-las explicitamente para todas as 12 notas de base e suas oitavas para cima e para baixo.

Feito isso, você ainda terá alguns problemas a resolver.

Tome a tríade C, E, G. Como músico, vejo isso claramente como um acorde de Cmaj. No entanto, o desenvolvedor em mim pode interpretar isso adicionalmente como E menor Augment 5 (Root E + m3 + a5) ou Gsus4 6th no 5th (RootG + 4 + 6).

Portanto, para responder à sua pergunta sobre a análise, acho que a melhor maneira de determinar a modalidade (maior, menor, etc.) seria pegar todas as notas inseridas, organizá-las em um valor de semitom crescente e testá-las nas formas de acordes conhecidas . Em seguida, use cada nota inserida como nota raiz e execute o mesmo conjunto de avaliações.

Você pode ponderar as formas de acordes para que mais comuns (maior, menor) tenham precedência sobre as formas de acordes aumentadas, suspensas, elektra etc., mas uma análise precisa exigiria apresentar todas as formas de acordes correspondentes como possíveis soluções.

Novamente, o artigo da wikipedia mencionado faz um bom trabalho ao listar as classes de afinação, por isso deve ser simples (embora tedioso) codificar os modelos dos acordes, fazer as anotações inseridas, atribuí-las às classes / intervalos de afinação e comparar contra as formas conhecidas de correspondências.

Isso tem sido muito divertido. Obrigado!


Eles estão sendo inseridos, via texto, por enquanto. No entanto, mais tarde, poderei usar o midi se o programa estiver encapsulado corretamente. Os passos do bebê agora: D
Igneous01

0

Parece um caso para modelos. Você parece ter um template <?> class Major : public Chord;modo Major<C>é-um Chord, como é Major<B>. Da mesma forma, você também tem um Note<?>modelo com instâncias Note<C>e Note<D>.

A única coisa que deixei de fora é a ?parte. Parece que você tem um, enum {A,B,C,D,E,F,G}mas eu não sei como você nomearia esse enum.


0

Obrigado por todas as sugestões, de alguma forma eu consegui perder as respostas extras. Até agora, minhas aulas foram projetadas da seguinte forma:

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

Para resolver meus problemas de cálculo de intervalo e acordes, decidi usar o buffer circular, o que me permite atravessá-lo a partir de qualquer ponto, seguindo em frente até encontrar a próxima nota que corresponda.

Para encontrar o intervalo interpretado - atravesse o buffer de notas reais, pare quando as letras corresponderem (apenas a letra, não a nota ou a posição real), então c - g # = 5

Para encontrar a distância real - atravessar outro buffer de 12 números inteiros, pare quando a posição da nota superior for igual ao valor do buffer no índice, novamente isso está avançando apenas. Mas o deslocamento pode estar em qualquer lugar (ou seja, buffer.at (-10))

agora conheço o intervalo interpretado e a distância física entre os dois. então o nome do intervalo já está meio completo.

agora eu sou capaz de interpretar o intervalo, ou seja. se o intervalo for 5 e a distância for 8, será um quinto aumentado.

Até agora, a nota e o intervalo estão funcionando conforme o esperado, agora só tenho que resolver o identificador de acordes.

Mais uma vez obrigado, releremos algumas dessas respostas e incorporarei algumas idéias aqui.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.