OO Design, ¿cómo modelar Tonal Harmony?

12

Empecé a escribir un programa en C ++ 11 que analizaría acordes, escalas y armonía. El mayor problema que tengo en mi fase de diseño es que la nota 'C' es una nota, un tipo de acorde (Cmaj, Cmin, C7, etc.) y un tipo de clave (la clave de Cmajor, Cminor). El mismo problema surge con los intervalos (3er menor, 3er mayor).

Estoy usando una clase base, Token, que es la clase base para todos los 'símbolos' en el programa. así por ejemplo:

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 puede ver, crear todas las clases derivadas (CMajorTriad, C, CMajorScale, CMajorKey, etc.) rápidamente se volvería ridículamente complejo, incluidas todas las otras notas, así como la enarmonía. la herencia múltiple no funcionaría, es decir:

class C : public Note, Triad, Key, Scale

clase C, no pueden ser todas estas cosas al mismo tiempo. Es contextual, también polimorfizar con esto no funcionará (¿cómo determinar qué súper métodos realizar? Llamar a todos los constructores de súper clases no debería suceder aquí)

¿Hay alguna idea de diseño o sugerencia que la gente tenga para ofrecer? No he podido encontrar nada en Google con respecto al modelado de la armonía tonal desde una perspectiva OO. Hay demasiadas relaciones entre todos los conceptos aquí.

Igneous01
fuente
8
¿Por qué 'C' sería una clase? Me imagino que 'Note', 'Chord', etc. serían clases, que podrían tener una enumeración de valores en la que la enumeración 'C' podría desempeñar un papel.
Rotem
Si el usuario ingresa-> acorde CEG, necesitaría deducir cuáles son las notas para formar el acorde apropiado. Estaba pensando en pasar un vector de <Notas> como parámetros para el método execute (), que se manejaría polimórficamente. Sin embargo, usar un enumerador tendría sentido, pero luego necesitaría crear una instancia de cada objeto con la enumeración que quiero usar.
Igneous01
Estoy con @Rotem en este caso: a veces, solo tienes que preferir la composición de objetos sobre la herencia.
Spoike
Me parece que podría ser útil pensar en lo que quieres hacer con estas clases de notas / acordes / escalas. ¿Vas a producir partituras? Archivos midi? ¿Se realizan transformaciones en los puntajes (transposición, duplicar todas las duraciones de las notas, agregar trinos a todas las notas completas sobre una nota determinada, etc.)? Una vez que tenga una posible estructura de clase, piense cómo lograría esas tareas. Si parece incómodo, tal vez quieras una estructura de clase diferente.
MatrixFrog

Respuestas:

9

Creo que el mejor enfoque es reproducir las relaciones reales entre estas entidades.

Por ejemplo, podrías tener:

  • un Noteobjeto, cuyas propiedades son

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

    • accidental (natural, plano, afilado)

    • frecuencia u otro identificador de tono único

  • un Chordobjeto, cuyas propiedades son

    • una matriz de Noteobjetos

    • nombre

    • accidental

    • calidad (mayor, menor, disminuida, aumentada, suspendida)

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

  • un Scaleobjeto, cuyas propiedades son

    • una matriz de Noteobjetos

    • nombre

    • tipo (mayor, menor natural, menor melódico, menor armónico)

    • modo (jónico, dórico, frigio, lidio, mixolidiano, eólico, locriano)

Luego, si su entrada es textual, puede crear notas con una cadena que incluya el nombre de la nota, accidental y (si lo necesita) octava.

Por ejemplo (pseudocódigo, no sé C ++):

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

Luego, en la Noteclase, puede analizar la cadena y establecer las propiedades.

A Chordpodría ser construido por sus notas:

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

... o por una cadena que incluye nombre, calidad y notas adicionales:

chord = new Chord('Cmaj7');

No sé qué hará exactamente su aplicación, así que estas son solo ideas.

¡Buena suerte con tu fascinante proyecto!

lortabac
fuente
4

Un consejo genérico.


Si se espera mucha incertidumbre en el diseño de la clase (como en su situación), recomendaría experimentar con diferentes diseños de clase competitivos.

Usar C ++ en esta etapa podría no ser tan productivo como otros lenguajes. (Este problema es evidente en los fragmentos de código con los que tiene que lidiar typedefy los virtualdestructores). Incluso si el objetivo del proyecto es producir código C ++, podría ser productivo hacer el diseño inicial de la clase en otro lenguaje. (Por ejemplo, Java, aunque hay muchas opciones).

No elija C ++ solo por herencia múltiple. La herencia múltiple tiene sus usos, pero no es la forma correcta de modelar este problema (teoría de la música).


Presta especial atención a desambiguate. Aunque las ambigüedades abundan en las descripciones en inglés (textuales), estas ambigüedades deben resolverse al diseñar las clases OOP.

Hablamos de G y G afilados como notas. Hablamos de sol mayor y sol menor como escalas. Por lo tanto, Notey Scaleno son conceptos intercambiables. No podría haber ningún objeto que pueda ser simultáneamente una instancia de a Notey a Scale.

Esta página contiene algunos diagramas que ilustran la relación: http://www.howmusicworks.org/600/ChordScale-Relations/Chord-and-Scale-Relations

Para otro ejemplo, "una Tríada que comienza con G en una escala mayor de C " no tiene el mismo significado que "una Tríada que comienza con C en una escala mayor de G ".

En esta etapa temprana, la Tokenclase (la superclase de todo) no tiene justificación, porque evita la desambiguación. Podría introducirse más tarde si es necesario (respaldado por un fragmento de código que demuestra cómo esto podría ser útil).


Para comenzar, comience con una Noteclase que sea el centro del diagrama de clase, luego agregue gradualmente las relaciones (piezas de datos que deben asociarse con tuplas de Notes) al diagrama de relación de clase.

Una nota C es una instancia de la Noteclase. Una nota C devolverá propiedades relacionadas con esta nota, como tríadas relacionadas, y su posición relativa ( Interval) con respecto a una Scaleque comience con una nota C.

Las relaciones entre instancias de la misma clase (por ejemplo, entre una nota C y una nota E ) deben modelarse como propiedades, no como herencia.

Además, muchas de las relaciones entre clases en sus ejemplos también se modelan más apropiadamente como propiedades. Ejemplo:

(los ejemplos de código están pendientes porque necesito volver a aprender teoría musical ...)

rwong
fuente
Pensamiento interesante, pero ¿cómo manejaría la determinación de las cualidades de los acordes en el contexto del análisis armónico? La instancia de C Chord necesitaría tener una propiedad de calidad, establecida en menor (lo cual está bien), pero ¿qué pasa con un acorde dominante / disminuido / aumentado / menor 7s, 9, 11? Hay muchos acordes a los que puede pertenecer una sola nota. ¿Cómo determinaría cuáles son los distintos tipos de acordes y sus respectivas cualidades en la sección de análisis del código?
Igneous01
Sé muy poca teoría musical, así que no puedo responder a tu pregunta. Una forma de ayudarme a comprender sería encontrar una tabla que enumere todas las notas involucradas en esos conceptos. Las consultas para acordes pueden tomar parámetros adicionales.
rwong
2
Aquí hay una lista muy buena de todos los acordes posibles: en.wikipedia.org/wiki/List_of_chords Todos los acordes se pueden aplicar a cualquier nota, lo importante con mi situación es que las enarmónicas son correctas: es decir. Cflat major! = BMajor, son físicamente el mismo acorde en el piano, pero sus funciones armónicas son muy diferentes en el papel. Estoy pensando que un enumerador para afilar / aplanar una nota tendría más sentido para una instancia de nota. De esta manera, C.Sharpen () = C # y C.Flatten () = Cb, esto puede facilitarme la validación de los acordes de usuario.
Igneous01
2

Básicamente, las notas musicales son frecuencias y los intervalos musicales son relaciones de frecuencia.

Todo lo demás se puede construir sobre eso.

Un acorde es una lista de intervalos. Una escala es una nota fundamental y un sistema de ajuste. Un sistema de ajuste también es una lista de intervalos.

Cómo los nombras es solo un artefacto cultural.

El artículo de la teoría de la música de Wikipedia es un buen punto de partida.

Mouviciel
fuente
Interesante, aunque no estoy seguro de que sea necesariamente útil modelar el sistema en términos de la realidad física subyacente. Recuerde, se requiere que el modelo sea útil para articular un aspecto en particular, no necesariamente para ser exhaustivo o incluso preciso. Si bien su enfoque sería preciso y completo, podría ser un nivel demasiado bajo para el caso de uso de OP.
Konrad Rudolph
@KonradRudolph: con mi posición extrema, solo quería señalar que no se debe mezclar el modelo subyacente con la capa de presentación, de una manera similar a los horarios de verano: los cálculos son mucho más fáciles en el modelo en sí. Estoy de acuerdo en que el nivel de abstracción más útil no es el que sugiero, pero creo que el nivel de abstracción sugerido por el OP tampoco es adecuado.
Mouviciel
El propósito de este programa no es necesariamente mostrar la realidad física de la música. Pero para las personas que estudian teoría (como yo) poder trazar rápidamente algunos acordes y hacer que el programa interprete de la mejor manera cómo estos acordes se relacionan entre sí en un sentido armónico. Podría analizarlo de la manera probada y verdadera de leer el puntaje medida por medida, pero esta es otra herramienta para facilitar las cosas y poder enfocarnos en los detalles más finos al analizar.
Igneous01
1

Me parece fascinante esta discusión.

¿Las notas se ingresan a través de midi (o algún tipo de dispositivo de captura de tonos) o se ingresan escribiendo las letras y los símbolos?

En el caso del intervalo de C a D-sharp / E-flat:

Aunque D-sharp y E-flat son el mismo tono (alrededor de 311Hz si A = 440Hz), el intervalo de C -> D-sharp se escribe un segundo aumentado, mientras que el intervalo de C -> E-flat se escribe como menor 3er. Es bastante fácil si sabes cómo se escribió la nota. Imposible determinar si solo tiene los dos tonos para continuar.

En este caso, creo que también necesitará una forma de aumentar / disminuir el tono junto con los métodos .Sharpen () y .Flatten () mencionados, como .SemiToneUp (), .FullToneDown (), etc. que puede encontrar notas posteriores en una escala sin "colorearlas" como objetos punzantes / planos.

Tengo que estar de acuerdo con @Rotem en que "C" no es una clase en sí misma, sino una instanciación de la clase Note.

Si define las propiedades de una nota, incluidos todos los intervalos como semitonos, independientemente del valor de la nota inicial ("C", "F", "G #") podrá determinar que una secuencia de tres notas tiene el raíz, 3ra mayor (M3), luego 3ra menor (m3) sería una tríada mayor. Del mismo modo, m3 + M3 es una tríada menor, m3 + m3 disminuido, M3 + M3 aumentado. Además, esto le daría una forma de encapsular la búsqueda del 11º, 13º disminuido, etc. sin codificarlos explícitamente para las 12 notas base, y sus octavas hacia arriba y hacia abajo.

Una vez hecho esto, todavía te quedan algunos problemas por resolver.

Tome la tríada C, E, G. Como músico, veo esto claramente como un acorde de Cmaj. Sin embargo, el desarrollador en mí puede interpretar esto adicionalmente como E menor Augment 5 (Root E + m3 + a5) o Gsus4 6th no 5th (RootG + 4 + 6).

Entonces, para responder a su pregunta sobre el análisis, creo que la mejor manera de determinar la modalidad (mayor, menor, etc.) sería tomar todas las notas ingresadas, organizarlas en un valor de semitono ascendente y probarlas con las formas de acordes conocidas . Luego, use cada nota ingresada como nota raíz y realice el mismo conjunto de evaluaciones.

Puede ponderar las formas de acordes para que los más comunes (mayor, menor) tengan prioridad sobre las formas de acordes aumentadas, suspendidas, elektra, etc., pero un análisis preciso requeriría presentar todas las formas de acordes coincidentes como posibles soluciones.

Nuevamente, el artículo de wikipedia al que se hace referencia hace un buen trabajo al enumerar las clases de tono, por lo que debería ser simple (aunque tedioso) codificar los modelos de los acordes, tomar las notas ingresadas, asignarlas a clases / intervalos de tono y luego comparar contra las formas conocidas para los partidos.

Esto ha sido muy divertido. ¡Gracias!

John
fuente
Están siendo ingresados, a través de texto por ahora. Sin embargo, más adelante podría utilizar midi si el programa se encapsula correctamente.
Pequeños
0

Suena como un caso para las plantillas. Parece que tienes un template <?> class Major : public Chord;so Major<C>-a Chord, como es Major<B>. Del mismo modo, también tiene una Note<?>plantilla con instancias Note<C>y Note<D>.

Lo único que he dejado fuera es la ?parte. Parece que tienes un enum {A,B,C,D,E,F,G}pero no sé cómo nombrarías esa enumeración.

MSalters
fuente
0

Gracias por todas las sugerencias, de alguna manera me las arreglé para perder las respuestas adicionales. Hasta ahora mis clases se han diseñado así:

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 mis problemas de cálculo de intervalos y acordes, decidí usar el búfer circular, que me permite recorrer el búfer desde cualquier punto, hacia adelante, hasta que encuentre la siguiente nota que coincida.

Para encontrar el intervalo interpretado: atraviese el búfer de notas reales, deténgase cuando las letras coincidan (solo la letra, no la nota real o la posición) para que c - g # = 5

Para encontrar la distancia real, atraviese otro búfer de 12 enteros, deténgase cuando la posición de la nota más alta sea la misma que el valor del búfer en el índice, nuevamente esto solo se mueve hacia adelante. Pero el desplazamiento puede estar en cualquier lugar (es decir, buffer.at (-10))

ahora sé tanto el intervalo interpretado como la distancia física entre los dos. entonces el nombre del intervalo ya está medio completo.

ahora puedo interpretar el intervalo, es decir. si el intervalo es 5 y la distancia es 8, entonces es un quinto aumentado.

Hasta ahora, la nota y el intervalo funcionan como se esperaba, ahora solo tengo que abordar el identificador de acorde.

Gracias de nuevo, volveré a leer algunas de estas respuestas e incorporaré algunas ideas aquí.

Igneous01
fuente