ОО Дизайн, как моделировать Tonal Harmony?

12

Я начал писать программу на C ++ 11, которая будет анализировать аккорды, гаммы и гармонию. Самая большая проблема, с которой я сталкиваюсь на этапе разработки, заключается в том, что нота «C» - это нота, тип аккорда (Cmaj, Cmin, C7 и т. Д.) И тип ключа (ключ Cmajor, Cminor). Та же проблема возникает с интервалами (второстепенный третий, крупный третий).

Я использую базовый класс Token, который является базовым классом для всех «символов» в программе. так например:

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;

Как вы можете видеть, создание всех производных классов (CMajorTriad, C, CMajorScale, CMajorKey и т. Д.) Быстро стало бы до смешного сложным, включая все другие заметки, а также расширенную гармонику. множественное наследование не будет работать, т.е.

class C : public Note, Triad, Key, Scale

класс С, не может быть все эти вещи одновременно. Это контекстно, также полиморфирование с этим не будет работать (как определить, какие супер-методы нужно выполнить? Вызов всех конструкторов суперкласса не должен происходить здесь)

Есть ли какие-нибудь дизайнерские идеи или предложения, которые люди могут предложить? Я не смог найти что-либо на Google в отношении моделирования тональной гармонии с точки зрения ОО. Здесь слишком много связей между всеми понятиями.

Igneous01
источник
8
Почему «С» будет классом? Я бы предположил, что «Note», «Chord» и т. Д. Будут классами, которые могут иметь перечисление значений, в которых перечисление «C» может играть роль.
Rotem
Если пользователь вводит-> аккорд CEG, ему нужно будет определить, какие ноты образуют соответствующий аккорд. Я думал о передаче вектора <Notes> как параметров в метод execute (), который будет обрабатываться полиморфно. Однако использование перечислителя имело бы смысл, но тогда мне нужно было бы создать экземпляр каждого объекта с перечислением, которое я хочу использовать.
Igneous01
Я с @Rotem об этом: иногда вам просто нужно предпочесть композицию объекта наследованию.
Спойк
Мне кажется, что было бы полезно подумать о том, что вы хотите делать с этими классами note / chord / scale. Собираетесь ли вы продюсировать ноты? Миди-файлы? Производятся ли преобразования партитур (транспонирование, удвоение длины всех нот, добавление трелей ко всем нотам выше определенной ноты и т. Д.)? Как только у вас появится возможная структура классов, подумайте, как бы вы справились с этими задачами. Если это кажется неловким, может быть, вы хотите другую структуру класса.
MatrixFrog

Ответы:

9

Я думаю, что лучший подход - воспроизвести реальные отношения между этими объектами.

Например, вы могли бы иметь:

  • Noteобъект, свойство которого

    • имя (C, D, E, F, G, A, B)

    • случайный (естественный, плоский, острый)

    • частота или другой уникальный идентификатор высоты тона

  • Chordобъект, свойство которого

    • массив Noteобъектов

    • имя

    • случайный

    • качество (основное, второстепенное, пониженное, расширенное, приостановленное)

    • дополнения (7, 7+, 6, 9, 9+, 4)

  • Scaleобъект, свойство которого

    • массив Noteобъектов

    • имя

    • тип (мажор, натуральный минор, мелодический минор, гармонический минор)

    • мода (ионная, дорианская, фригийская, лидийская, миксолидианская, эоловая, локская)

Затем, если ваш ввод текста, вы можете создавать заметки со строкой, включающей название заметки, случайную и (если вам это нужно) октаву.

Например (псевдокод, я не знаю C ++):

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

Затем в Noteклассе вы можете разобрать строку и установить свойства.

А Chordможно построить по его заметкам:

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

... или строкой, включающей имя, качество и дополнительные примечания:

chord = new Chord('Cmaj7');

Я не знаю, что именно сделает ваше приложение, так что это всего лишь идеи.

Удачи в вашем увлекательном проекте!

lortabac
источник
4

Несколько общих советов.


Если в дизайне класса ожидается много неопределенности (например, в вашей ситуации), я бы порекомендовал поэкспериментировать с различными проектами конкурирующих классов.

Использование C ++ на этом этапе может быть не таким продуктивным, как в других языках. (Эта проблема проявляется в ваших фрагменты коды, чтобы иметь дело с typedefи virtualдеструкторами.) Даже если целью проекта является создание C ++ кода, он может быть продуктивным , чтобы сделать первоначальный дизайн класса на другом языке. (Например, Java, хотя есть много вариантов.)

Не выбирайте C ++ только из-за множественного наследования. Множественное наследование имеет свое применение, но это не правильный способ моделирования этой проблемы (теория музыки).


Обратите особое внимание на неоднозначность. Несмотря на то, что в англоязычных (текстовых) описаниях встречается много неясностей, эти двусмысленности необходимо устранять при разработке классов ООП.

Мы говорим о G и G резко, как примечания. Мы говорим о мажоре и минор как шкалы. Таким образом, Noteи Scaleне являются взаимозаменяемыми понятиями. Не может быть никакого объекта, который может быть одновременно экземпляром a Noteи a Scale.

Эта страница содержит несколько диаграмм, иллюстрирующих взаимосвязь: http://www.howmusicworks.org/600/ChordScale-Relations/Chord-and-Scale-Relations

Для другого примера, «Триада, начинающаяся с G по мажорной шкале C », не имеет того же значения, что и «Триада, начинающаяся с C по мажорной шкале G ».

На этой ранней стадии Tokenкласс (суперкласс всего) неоправдан, потому что он предотвращает неоднозначность. Это может быть введено позже при необходимости (поддерживается фрагментом кода, который демонстрирует, как это может быть полезно).


Для начала, начните с Noteкласса, который является центром диаграммы классов, затем постепенно добавляйте отношения (фрагменты данных, которые должны быть связаны с кортежами Notes) к диаграмме отношений классов.

C нота является экземпляром Noteкласса. С нотой будет возвращать свойства , которые имеют отношение к настоящей записке, такие , как связанные с триадами, и его относительное положение ( Interval) по отношению к , Scaleкоторый начинается с C нотой.

Отношения между экземплярами одного и того же класса (например, между заметкой C и заметкой E ) должны моделироваться как свойства, а не как наследование.

Более того, многие межклассовые отношения в ваших примерах также более подходящим образом смоделированы как свойства. Пример:

(примеры кода ожидают рассмотрения, потому что мне нужно заново изучить теорию музыки ...)

rwong
источник
Интересная мысль, но как справиться с определением аккордов в контексте гармонического анализа? Экземпляр аккордов C должен иметь качественное свойство, установленное на минор (что нормально), но как насчет доминирующих / уменьшенных / дополненных / минорных 7s, 9, 11 аккордов? Есть много аккордов, которые могут принадлежать одной ноте. Как бы я определил, какие различные типы аккордов и их соответствующие качества находятся в разделе анализа кода?
Igneous01
Я очень мало знаю теорию музыки, поэтому не могу ответить на ваш вопрос. Один из способов, который может помочь мне понять, - найти таблицу, в которой перечислены все примечания, относящиеся к этим концепциям. Запросы для аккордов могут принимать дополнительные параметры.
Rwong
2
Вот очень хороший список всех возможных аккордов: en.wikipedia.org/wiki/List_of_chords Все аккорды могут быть применены к любой ноте, что важно в моей ситуации, это то, что энгармоники правильные: т.е. Cflat major! = BMajor, они физически одинаковые аккорды на фортепиано, но их гармонические функции очень разные на бумаге. Я думаю, что перечислитель для резкого / плоского примечания будет наиболее целесообразным для экземпляра примечания. Таким образом, C.Sharpen () = C # и C.Flatten () = Cb, это может упростить мне проверку пользовательских аккордов.
Igneous01
2

В основном, музыкальные ноты - это частоты, а музыкальные интервалы - это частотные отношения.

Все остальное может быть построено на этом.

Аккорд - это список интервалов. Масштаб - фундаментальная нота и система настройки. Система настройки также представляет собой список интервалов.

Как вы их называете, просто культурный артефакт.

Статья по теории музыки в Википедии - хорошая отправная точка.

mouviciel
источник
Интересно, хотя я не уверен, что это обязательно полезно для моделирования системы с точки зрения базовой физической реальности. Помните, что модель обязана быть полезной в формулировании одного конкретного аспекта, не обязательно быть всеобъемлющей или даже точной. Хотя ваш подход будет и точным, и всеобъемлющим, он может быть слишком низким для варианта использования OP.
Конрад Рудольф
@KonradRudolph - В связи с моей крайней позицией я просто хотел указать, что не следует смешивать базовую модель со слоем представления, как при переходе на летнее время: вычисления в самой модели намного проще. Я согласен с тем, что самый полезный уровень абстракции - это не то, что я предлагаю, но я чувствую, что уровень абстракции, предложенный ФП, также не подходит.
Mouviciel
Цель этой программы - не обязательно отображать физическую реальность музыки. Но для людей, изучающих теорию (таких, как я), можно просто быстро нарисовать некоторые аккорды и заставить программу наилучшим образом интерпретировать, как эти аккорды связаны друг с другом в гармоническом смысле. Я мог бы просто проанализировать его как проверенный и верный способ чтения показателя за мерой, но это еще один инструмент, который упростит задачу и позволит сосредоточиться на более тонких деталях при анализе.
Igneous01
1

Я нахожу эту дискуссию захватывающей.

Вводятся ли ноты через миди (или какое-либо устройство захвата тона) или вводятся путем ввода букв и символов?

В случае интервала от C до D-Sharp / E-flat:

Хотя D-Sharp и E-Flat являются одним и тем же тоном (около 311 Гц, если A = 440 Гц), интервал от C -> D-Sharp записывается как расширенный 2-й, а интервал от C -> E-Flat записывается как несовершеннолетний третий. Достаточно просто, если вы знаете, как записка была написана. Невозможно определить, есть ли у вас только два тона.

В этом случае, я полагаю, вам также понадобится способ увеличения / уменьшения тона вместе с упомянутыми методами .Sharpen () и .Flatten (), такими как .SemiToneUp (), .FullToneDown () и т. Д., Поэтому что вы можете найти последующие заметки в шкале, не «окрашивая» их как острые предметы / квартиры.

Я должен согласиться с @Rotem, что «C» не является классом сам по себе, а скорее является экземпляром класса Note.

Если вы определите свойства для примечания, включая все интервалы в виде полутонов, то независимо от начального значения примечания ("C", "F", "G #") вы сможете сказать, что последовательность из трех примечаний, которая имеет корень, главный 3-й (М3), затем младший 3-й (м3) будет основной триадой. Точно так же m3 + M3 - это второстепенная триада, уменьшенная m3 + m3, увеличенная M3 + M3. Кроме того, это даст вам возможность инкапсулировать нахождение 11-го, уменьшенного 13-го и т. Д. Без явного кодирования их для всех 12 базовых нот и их октав вверх и вниз.

Как только это будет сделано, у вас все еще есть проблемы, которые нужно решить.

Возьмите триаду C, E, G. Как музыкант, я ясно вижу это как аккорд Cmaj. Тем не менее, разработчик во мне может интерпретировать это дополнительно как ми минор Augment 5 (Root E + m3 + a5) или Gsus4 6th no 5th (RootG + 4 + 6).

Итак, чтобы ответить на ваш вопрос о проведении анализа, я думаю, что лучший способ определить модальность (майор, минор и т. Д.) - это взять все введенные ноты, расположить их в возрастающем полутоновом значении и проверить их по известным формам аккордов. , Затем используйте каждую введенную заметку в качестве корневой заметки и выполните тот же набор оценок.

Вы можете взвесить формы аккордов так, чтобы более распространенные (мажорные, минорные) имели приоритет над расширенными, приостановленными, электровыми и т. Д. Аккордами, но точный анализ потребовал бы представления всех соответствующих форм аккордов в качестве возможных решений.

Опять же, статья в Википедии, на которую ссылаются, хорошо перечисляет классы основного тона, поэтому должно быть просто (хотя и утомительно) кодировать модели аккордов, брать введенные ноты, назначать их классам / интервалам основного тона, а затем сравнивать против известных форм для матчей.

Это было очень весело. Благодарность!

Джон
источник
Они вводятся через текст на данный момент. Однако позже я смогу использовать midi, если программа инкапсулирована правильно. Детские шаги прямо сейчас: D
Igneous01
0

Походит на случай для шаблонов. Вы , кажется , есть template <?> class Major : public Chord;так Major<C>есть- Chord, как это Major<B>. Точно так же у вас есть Note<?>шаблон с экземплярами Note<C>и Note<D>.

Единственное, что я оставил, это ?часть. Кажется, у вас есть, enum {A,B,C,D,E,F,G}но я не знаю, как бы вы назвали это перечисление.

MSalters
источник
0

Спасибо за все предложения, как-то мне удалось пропустить лишние ответы. До сих пор мои занятия были разработаны так:

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

Чтобы решить мои проблемы с вычислением интервалов и аккордов, я решил использовать кольцевой буфер, который позволяет мне проходить через буфер из любой точки, двигаясь вперед, пока не найду следующую ноту, которая соответствует.

Чтобы найти интерпретированный интервал, проходящий через буфер реальных нот, остановитесь, когда буквы совпадают (только буква, а не реальная нота или позиция), поэтому c - g # = 5

Чтобы найти реальное расстояние - пройдите еще один буфер из 12 целых чисел, остановитесь, когда верхняя позиция ноты совпадает со значением буфера в индексе, опять же, это только движение вперед. Но смещение может быть где угодно (т. Е. Buffer.at (-10))

теперь я знаю как интерпретируемый интервал, так и физическое расстояние между ними. поэтому имя интервала уже наполовину завершено.

Теперь я могу интерпретировать интервал, т.е. если интервал равен 5, а расстояние равно 8, то это увеличенный 5-й.

Пока нота и интервал работают, как и ожидалось, теперь мне нужно только определить идентификатор аккорда.

Еще раз спасибо, я перечитал некоторые из этих ответов и включил некоторые идеи здесь.

Igneous01
источник