Струн рекурсивный перебор…
АрхивТрудно найти того, кто не пел хоть раз в жизни под гитару. Что и говорить, у этого инструмента есть способность к объединению самых разных по духу и убеждениям людей, которая, пожалуй, не снилась и Интернету. Нередко у одного и того же костра можно встретить компьютерщиков всех мастей - от сисадмина до неискушенного пользователя. И при этом из голов как-то улетучиваются мысли о "глюках", "багах", "мастдаях" и прочей нечисти, и как-то сразу охватывает желание жить и любить всех подряд.
В музыкальном отношении жанр авторской песни достаточно демократичен: требования к вокальным данным предъявляются минимальные, а своеобразным "паролем", дающим право пополнить ряды исполнителей, является овладение небогатым арсеналом аккордов (владея уже их хрестоматийной "троицей", среди определенного круга слушателей можно прослыть настоящим экспертом). Но рано или поздно настоящему исполнителю (а уж сочинителю - тем более) становится тесно в рамках, и пальцы волей-неволей начинают бродить по грифу в поисках новых, более интересных гармонических ходов. Поиск по наитию здесь неэффективен - подобрать нужный аккорд вовсе не так уж просто, ведь один и тот же звук на гитаре можно извлечь, прижимая разные струны, следовательно, любой аккорд можно взять самыми различными способами. С другой стороны, на одной и той же струне могут быть извлечены разные звуки, входящие в заданный аккорд. А ведь еще приходится учитывать особенности конкретного строя…
Вписывается ли в гармонический ход это созвучие, не устают ли от него пальцы, так ли оно звучит, как хотелось бы (а ведь комбинации звуков, образующих один и тот же аккорд, но взятых на разной высоте, звучат неодинаково!) - все это потом. Сначала нужно просто найти несколько вариантов, а это не так просто. Даже подбор обычных трезвучий и септаккордов вызывает трудности у новичков. Что уж говорить о случаях, когда неведомо даже название аккорда, знаешь только, какие звуки присутствуют в нем. По стандартным аккордам хотя бы есть шпаргалки, а тут… "Метод тыка" плох еще и тем, что дает случайный, выборочный результат. А как быть, если хочется оценить все возможные аккорды и выбрать самый подходящий, - неужели для этого придется обреченно перебирать все возможные сочетания звуков? Стоп! Мы подошли к классической переборной задаче (не удивлюсь, если эта идея не нова, а представляет собой один из "бродячих сюжетов" программирования - поделюсь лишь своим подходом).
В октаве 12 полутонов, расположенных между 12 звуками - 7 "белыми" и 5 "черными" (стиль мышления явно выдает во мне "клавишника"). Таким образом, любой звук можно представить числом, лежащим в диапазоне от 0 до 11 (от "до" до "си" соответственно). Одним набором таких чисел-звуков можно закодировать аккорд, а другим - строй гитары (высоту звучания каждой струны, например, знаменитое "ми-си-соль-ре-ля-ми" шестиструнки). Зажимая пальцем лад с некоторым номером, мы повышаем высоту звучания струны на соответствующее количество полутонов. Вслед за "си" одной октавы сразу идет "до" следующей, так что арифметика тут проста: номер издаваемого звука будет остатком от деления суммы на 12. Используется и другая техника (так называемое барэ): на каком-то ладу зажимаются и звучат выше все струны (или несколько верхних, но этот случай в принципе сводится к предыдущему), в то время как к некоторым опять-таки применяется "индивидуальный подход".
Каждый звук, полученный таким образом на какой-либо из струн, нужно проверить на соответствие требуемому аккорду и запустить перебор для следующей струны. Проще всего такую проверку осуществить, применив "тайное оружие" языка Паскаль - множества. Если данная струна последняя, то напечатать результат и продолжить поиск. Для реализации этой идеи годится простой рекурсивный алгоритм с возвратом.
Заметим, что возможности исполнителя (а ведь полученные аккорды нужно еще сыграть!) довольно ограниченны. В мире найдется не так много людей, чья растяжка пальцев достаточна, чтобы охватить 5 и более ладов. С другой стороны, использовать в аккорде на той же "шестиструнке" все пять пальцев, как правило, нецелесообразно, поскольку есть барэ (отсюда возникают отсечения, заметно сокращающие перебор). Нужно будет также произвести проверку, все ли звуки из заказанного аккорда "прозвучат" в найденном компьютером варианте. Как эффективнее это сделать? Ну конечно, заведя еще одно множество для "пока неиспользованных" нот! Все эти идеи и воплотились в нижеприведенном листинге.
{Синтезатор гитарных аккордов. Turbo Pascal 7.0 Автор: Д.Коновальчик, 1998}
Type TChord = Set of Byte;
TNote = String[2]; {Запись ноты лат. буквами, C=до, D=ре и т. д.,
#-диез, b-бемоль}
{"Настраиваемая" часть программы - подставьте Ваши значения в константы}
Const NGUITARSTRINGS=6;{Сколько струн у вашей гитары? Ниже - ее настройка}
STRINGS: array[1..NGUITARSTRINGS] of TNote=('E','H','G','D','A','E');
NSTRINGS = 5; {Сколько верхних струн звучит в аккорде?}
MAXLEFT = 10; {Максимальный номер зажимаемого лада}
MAXTOUCHED = 4; {Максимальное число пальцев, занятых в аккорде}
MAXDELTA = 2; {Максимальный "разброс" пальцев в ладах}
Var Chord: TChord; NNotes, i, Bare: Byte;
StrPitch, Touch: array[1..NGUITARSTRINGS] of Byte;
Vars, p: Integer; Note: TNote;
Function Pitch(Note:TNote):Integer; {Считаем высоту ноты от "до" в 1/2 тона}
Const DELTA: array[1..8] of Byte=(9, 10, 0, 2, 4, 5, 7, 11);
Var p: Integer; {Высоты для A, B (си бемоль!), C, D, E, F, G, H (си)}
Begin
p:=Ord(Note[1])-Ord('D')+4; If not (p in [1..8]) Then p:=-1 Else Begin
p := DELTA[p]; If Length(Note)>1 Then Case Note[2] of '#': Inc(p);
'b': Dec(p); Else p:=-1 End End; Pitch:=p
End;
Procedure Print; {Печать найденных аккордов в следующем виде:}
Var i: Byte; (* [Bare:<лад>] {<номер струны>:<лад>} *)
Begin
Inc(Vars); If Bare>0 Then Write ('Барэ:', Bare);
For i:=1 to NSTRINGS do If Touch[i]>0 Then Write(' ',i,':',Touch[i]);
Writeln
End;
Function Min(a,b:Integer):Integer; Begin Min:=a; If b<a Then Min:=b End;
Function Max(a,b:Integer):Integer; Begin Max:=a; If b>a Then Max:=b End;
Procedure Try (n, t, Left, Right: Byte; Unused: TChord);{Берем n-ю струну}
Var i, NotePitch, t1, Right1, Left1: Byte; {при t уже прижатых, границах}
Begin {перебора Left и Right и наборе "неиспользованных" нот Unused}
For i:=0 to Right do Begin NotePitch:=(StrPitch[n]+i) mod 12;
If (i in [Bare,Left..Right]) and (NotePitch in Chord) Then Begin
t1:=t; If i>Bare Then Inc(t1);
Touch[n]:=0; Left1:=Left; Right1:=Right;
If i>Bare Then Begin {струна прижата - фиксируем на ней руку}
Touch[n]:=i; Left1:=Max(Left,i-MAXDELTA);
Right1:=Min(Right,i+MAXDELTA)
End;
If (n<NSTRINGS) Then Try(n+1,t1,Left1,Right1,Unused-[NotePitch])
Else If (t1<=MAXTOUCHED) and (Unused=[]) Then Print
End
End
End;
Begin
Write ('Сколько нот в аккорде? '); Readln (NNotes); Chord := [];
Writeln ('Введите ноты аккорда (C#, Db и т.д.)');
For i:=1 to NNotes do {Ввод названий нот}
Repeat Write(i, '-я нота (C#, Db и т.д.)? '); Readln(Note);
p:=Pitch(Note); If p<0 Then Writeln('Ошибка!')
Else Include(Chord,p); {Запоминаем высоту очередной ноты аккорда}
Until p>=0; For i:=1 to NSTRINGS do StrPitch[i] := Pitch(STRINGS[i]);
Bare:=0; Vars:=0; Try(1,0,1,MAXLEFT,Chord); {Ищем варианты без барэ...}
For Bare:=1 to MAXLEFT-1 do Try(1,1,Bare+1,Bare+MAXDELTA,Chord);{и с ним}
Writeln ('Найдено вариантов: ', Vars)
End.
Интерфейс у программы получился чересчур строгий, в DOS-духе (впрочем, украсить ее "нарядом" от Delphi - дело минутное). От пользователя требуется задать латинские имена нот, входящих в аккорд (напомню: отсчет идет от "A" - "ля", при этом "B" обозначает не "си", а "си-бемоль", "C" - "до", и так далее до "H"). Рядом с нотой можно указать знак - "диез" (#) либо "бемоль" (вместо него используется латинское b - что поделаешь, нет адекватного символа в кодовой таблице). Результаты выдаются на экран в виде пар <струна>:<лад>, в соответствии с которыми (и со своими возможностями) испытатель будет извлекать из чрева гитары самые неожиданные варианты аккордов. Не забыты и "пользовательские настройки" - меняя значения констант, можно изменить строй гитары (к примеру, исполнить романс на русской семиструнной), превратить гитару в этакое "банджо" или "балалайку" (отключив соответственно пару либо тройку верхних струн). Отмечу, что при первом же удачном запуске с собственными "настройками" взору моему представились 12 различных "ля-миноров" (ингредиенты "блюда": A, C, E), из которых по сей день мною активно использовался лишь один вариант, именуемый в дворовом фольклоре "малой звездочкой".
Для всех желающих модернизировать сие произведение могу предложить ряд прогрессивных идей. Например, "ознакомить" программу с рядом популярных аккордов, дабы не вводить их "позвучно" с нуля; создать режим поиска наиболее эффективных гармонических ходов (скажем, проведя минимизацию квадрата расстояния хода каждого пальца при смене аккорда). В конце концов, написать на ее основе апплет, доступный для всех страждущих…
А выучив новые аккорды, выключите свои любимые компьютеры, махните за город и расчехлите гитары.