Как устроена программа Пианола

Дмитрий Маштаков
   Программу «Пианола» я только что усовершенствовал, и при этом выяснилось, что я немножко забыл, как она устроена и как она работает. Потому и пишу эту статью. Она не только полезна на будущее, но и рассказывает о том, какие новые возможности в программе появились, и о том, как эти возможности реализованы. Интересна также и общая концепция создания музыкального файла с мелодией, сопровождаемой подголоском или аккордами.

   Главная задача «Пианолы» состоит в создании файлов партитуры. В них записываются частоты нот и длительности их звучания. Затем, по партитурам, с помощью другой программы – «Трио», делаются звуковые музыкальные записи высокого качества.
   Между тем и сама программа «Пианола»  непосредственно создаёт звуки фортепьяно, которыми она проигрывает одноголосые мелодии. Поэтому её можно использовать автономно, для создания музыкальных записей, как некую музыкальную шкатулку. Именно в этом аспекте и было сделано последнее усовершенствование программы.

     МНЕМОНИКА НОТНОЙ СТРОКИ

   Основой программы является блок, который называется «Интерпретатор нотной строки». Нотная строка, это та строка, которую видит перед собой пользователь. Нажимая на клавиши ноутбука и кликая по кнопкам, нарисованным в окне программы, он заполняет строку мнемонической записью.
   В записи значками CDEFGABcdefgab представлены ноты двух октав, значки после них # и – обозначают диезы и бемоли, цифры и значки ^~ после них означают удлинения, а ‘` - укорочения длительности звучания ноты. Эти же значки, поставленные в отрыве от нот, обозначают паузы.
   Значки > и < означают увеличение и замедление темпа вдвое, а те же значки с цифрами после них увеличивают или замедляют темп немного, в соответствии с той цифрой, которая там стоит.
   Знаки / и \ поднимают и опускают звук на октаву, а те же знаки с цифрами после них – на указанное число полутонов. Есть ещё указания на трели – t и на мелизмы - m, а также скобки и ещё несколько знаков, позволяющие записывать в виде одноголосия любую последовательность нот.

   Обратите внимание на это – «Пианола» в принципе своём одноголосая программа. Нотную запись, которая имеется в её 50-ти строках, она использует для создания файла партитуры или просто проигрывает эти строки последовательно, интерпретируя их поочерёдно – одну за другой.
   Таким образом, например, создаётся одноголосая партитура для исполнения на фортепьяно правой рукой, потом - аналогичная партия левой руки, а потом и партия альта, если такой инструмент предусмотрен.
   По трём полученным партитурам, со сделанными в них затем пометками, работает программа «Трио», она и завершает весь процесс – создаёт полноценную и качественную звуковую запись музыкального произведения.
   То звучание проигрываемых строк, которое мы получаем от программы «Пианола», это побочный, упрощённый и одноголосый результат первой части работы. Хотя и он может иметь своё собственное автономное значение - звук «музыкальной шкатулки» тоже бывает интересен и приятен на слух.

     ПРИНЦИП РАБОТЫ ПРОГРАММЫ

   Схема работы программы «Пианола» показана на иллюстрации.
   Как уже говорилось, центральной частью программы является Интерпретатор строки. Если мы хотим проверить звучание того, что мы записали в строки, то мы кликаем кнопку «play» и подпрограмма Play, связанная с этой кнопкой, организуем нашу работу.
   Она посылает -1 в блок fnNote и тогда он открывает файл P_M.wav для записи в него семплов звука. Затем Play, поочерёдно выставляя напоказ имеющиеся строки со сделанными в них нотными записями, обращается к Интерпретатору строки.
   То же самое делает и программа «make», только файл она открывает другой – TFP, TFL или TFA –текстовый файл партитуры правой руки, левой руки или альта. А затем она точно так же обращается к Интерпретатору строки.

   По какому поводу сделано обращение, Интерпретатору знать не надо, его дело простое – он идёт по строке от её начала к её концу, рассматривает в строке каждый символ, иногда заглядывая на один символ вперёд, и, интерпретируя написанное в строке, изредка выдаёт в блок «FT распорядитель» три числа – F –частоту, T1 –длительность ноты, T2 –длительность посленотной паузы.
   Если же Интерпретатор обнаруживает в строке на простую ноту, а указание на трель или мелизм, то номер трели или мелизма и их длительность он передаёт блокам «трель» или «мелизм» и уже они направляют числа F,T1,T2 в блок Распорядитель.

   Что делает блок «Распорядитель»?
   Функция его тоже проста – распорядиться той информацией, которую он получил. Как распорядиться, он узнаёт об этом заранее – блок «Play» или блок «make» перед началом своих действий подают ему об этом сигнал (F=-7 или F=-1). Как видите, информация о частоте может использоваться как способ управления. Ясно, что частота на может быть отрицательной или нулевой, получая отрицательное число, Распорядитель понимает его как команду, в данном случае, как команду приготовиться к работе по созданию звука, или по созданию файла партитуры.
   Если он получит положительное значение F, он поймёт, что пришла нота, а если F=0, то это будет означать, что пришла пауза, и тогда он обработает пришедшую информацию по-другому.

   Предположим, что Интерпретатор прочитал ноту Ля, частота которой составляет 440Гц, а длительность равна 1/4. Тогда Распорядитель получает числа F=440 T1=0.49 T2=0.01 для исполнения легато, или F=440 T1=0.15 T2=0.35 для стаккато.
   Если перед этим было указание F=-1, Распорядитель запишет в файл партитуры следующую строку:
 F= 440     T1= 15680  T2= 320   -поскольку скорость раздачи семплов в программе «Трио» равна 32000 семпл/сек, то и получаются такие числа. Длительность в одну сотую секунды превратилась в 320 семплов. С длительностью T1 произошло то же самое.
   Если же Интерпретатор встретит в строке не ноту, а знак паузы, при счёте на четверти обозначаемой так ^ , то тогда он пошлёт Распорядителю числа F=0 T1=0 T2=0.5, по нулю в первом числе Распорядитель поймёт, что это пауза и з запишет в партитуру строку
 L= 16000

   Итак, с созданием партитуры мы разобрались.
   Работа Распорядителя в режиме создания звука тоже очень проста – получив числа F=440 T1=0.49 T2=0.01, он сначала посылает в блок fnNote первые два, то есть F=440 T=0.49 а потом числа F=0 T=0.01.
   По первым числам делается звук, а по вторым – пауза.

     КАК ДЕЛАЕТСЯ ЗВУК И ПАУЗА   

   Перенесём наше внимание на блок fnNote и функцию «ф-но», связанную с ним. Блок начинается со строки
Public Sub FNNote(F As Single, T As Single)

   В обычном режиме всего эти два параметра F и T управляют всей работой блока. Когда, в самом начале, приходит значение F=-1, то блок стартует – открывает файл P_M.wav для записи музыки и устанавливает в ноль индекс массива ArM() (см.иллюстрацию). В дальнейшем именно с этим массивом блок будет иметь дело.
   
   Массив накопления семплов ArM() содержит целые числа, это амплитуды звука записываемые парами – первое целое число предназначено для левого динамика, а вторая амплитуда слышится в правом. Массив имеет размер 96000 и вмешает ровно три ноты четвертной длительности при скорости раздачи семплов VEL=32000 семпл/сек.
   
   Когда в блок приходят значения F=440 и T=0.49 он умножает второе число на скорость раздачи и получает число N=15680 – число семплов в звуке ноты. Затем, с параметром 440 блок обращается к функции «ф-но».
   Эта функция делает амплитуды звука. Она запоминает частоту F=440, устанавливает в соответствии с этой частотой свои внутренние параметры и возвращает значение 0. Это и будет первая амплитуда семпла – звук начинается с нуля.
   Первую амплитуду блок пропускает и организует цикл. В этом цикле он N раз обращается к функции «ф-но» с параметром 0, и получает от неё амплитуды второго, третьего и т.д. семплов, эти амплитуды он записывает в чётную и нечётную ячейки массива, после чего увеличивает индекс на два.  Сказанное выражается в следующем фрагменте:

If F > 0 Then
  N = T * VEL: A = FNFno(F)
  Do 
    If I < 96000 Then
      ArM(I) = A: ArM(I +1) = A
      I = I + 2: N = N – 1 ‘здесь I это индекс массива
    Else
      I = 0: Put #1, , ArM()
    End If
  Loop While N > 0
 Exit Sub: End If

   Вы можете видеть, что как только индекс массива I превысит число 96000, а это значит, что буфер амплитуд заполнен полностью, то происходит сброс содержимого буфера в открытый звуковой файл, а его индекс опять устанавливается на 0, то есть, на его начало.
   Посленотная пауза с входными параметрами F=0 T=0.01 обрабатывается аналогично, с той разницей, что вычислять амплитуду звука A не требуется, она равна нулю. Точно так же обрабатывается и обычная пауза.

   Всё это прекрасно работает, и нам нужно только дождаться окончания процесса, то есть, того момента, когда в наш блок придёт сигнал -2. Чаще всего бывает, что в этот момент буфер заполнен частично, и тогда верхняя часть буфера обнуляется, и только после этого его содержимое сбрасывается в звуковой файл. Затем в начало звукового файла записывается шапка, файл закрывается и вызывается программа для его прослушивания.
   
   Так всё происходит в обычном режиме работы программы.
   Как уже было отмечено выше, программа «Пианола» в сущности своей одноголосная программа, и при создании партитур ей незачем организовывать их предварительное прослушивание иным звуком – что мы слышим в левом динамике, то же самое слышим и в правом. Одни и те же амплитуды в массив ArM() записываются дважды.
   А ведь можно было бы записать в нечётные ячейки массива одни звуки, а в чётные – другие, и тогда бы мы услышали двухголосие. Как вам такая идея?
   Звук фортепьяно программа «Пианола» создаёт хороший, а при двух отдельных партиях для левой и правой руки было бы совсем хорошо?

     ДВХГОЛОСИЕ В ПРОГРАММЕ «ПИАНОЛА»

   О нововведениях в программу, позволяющих ей работать в двухголосом режиме, рассказывается тут http://proza.ru/2024/03/29/1539
   Здесь мы обсудим технические аспекты сделанных изменений. Двухголосие вводится в нотную строку особыми скобками W…w между буквами которых размещаются ноты и паузы, имеющие общую длительность длительность в 1.5 сек. Эти звуки записываются в буфер, полностью заполняя его, однако буфер пока не сбрасывается в звуковой файл. Специально установленный флаг в начале этой записи устанавливает I=0 поэтому буфер заполняется с самого его начала и до самого его конца.

   По окончании этой записи этот флаг переводится в другое положение, которое не позволяет последующим нотам и паузам записываться в нечётные ячейки буфера, тогда как в чётные ячейки они продолжают записываться так, как и при обычном режиме. В результате, когда новыми звуками буфер наполняться и сбрасываться, мы услышим двухголосие - в правом наушнике будет с периодичностью 1.5 сек звучать находившиеся в скобках ноты, а в левом – вновь поступающая мелодия. Тем самым для мелодии будет создан аккомпанемент. Новая скобка W…w оборвёт этот процесс и начнёт его с начала. Быстрый вальс на три счёта за полторы секунды получается таким способом без особых усилий.

  Меняя темп раздачи, можно менять и темп музыки, а то, что при этом будет меняться слышимая ухом частота, легко компенсируется – в арсенале «Пианолы» есть символы \ / меняющие частоту в два раза, то есть, на 12 ступеней звукоряда, и те же символы с последующими за ними цифрами, такие сочетания изменяют частоту на указанное цифрой число ступеней.
   Чтобы эти преобразования делались автоматически, в начале проигрываемой строки нужно указать на это так - S/ или так S/6 или так S//3. По окончании создания файла для проигрывания программа, так же автоматически, возвращается в свой обычный режим.

   Есть и другая возможность изменить темп двухголосия.
Буквенные скобки с другой начальной буквой - V2…w или V4…w удлиняют и укорачивают длительности заключённых в них нот и пауз так, чтобы в буфер помещалось не три, а ровно две или четыре четверти, а V3…w и V…w устанавливают обычное число четвертей в буфере, то есть три.

V4CDEFw<Acdf >WFEDCw<Acdf

   В этом примере буфер разделён на 4 части. Следующая за скобками нота Ля сопровождается нотами CD, звучащими ниже, затем нота До сопровождается нотами EF, затем сопровождение повторяется – d_CD  f_EF. Следующий фрагмент играется так A_FE c_DC d_FE f_DC, то есть, сопровождение в нём идёт в обратном порядке.
   Как создаётся двухголосие нам стало ясно, теперь разберёмся с аккордами.

     АККОРДЫ В ПРАВОЙ РУКЕ

   Двухголосие, описанное выше, основано на том, что семплы последовательности нот не сбрасываются в звуковой файл в той последовательности, в которой они возникают, а предварительно накапливаются в буфере, где они накладываются друг на друга.
   Следуя таким путём, в буфере вполне можно накапливать аккорды. На первый случай, представим себе, как можно в нём накопить аккорды из двух нот.
   Сопровождающие двухголосие семплы звука находятся в правых (нечётных) ячейках буфера. Туда же можно добавлять и семплы вторых нот, составляющих аккорд, складывая их с первыми. То же самое можно сделать и с семплами третьих нот аккорда. Нужно только подумать, как именно эти семплы складывать.
   В программе «Пианола» семплы складываются так A=A1/2+A2/2 для аккорда, состоящего из двух нот, и A=A+A3/2 при добавлении третьей ноты. В последнем случае звук получается в полтора раза более громким.
   Практически аккорд делается так – когда приходит его первая нота, то индекс I по которому она начинает записываться, мы запоминаем, а по приходе второй и третьей ноты восстанавливаем индекс. Таким способом а аккорд можно записать и четыре, и пять нот, но пока ограничимся тремя.

   Формально аккорды из двух нот вводятся буквенной скобкой v…w аналогичной по своим свойствам со скобкой V…w с той разницей, что ноты в аккордной скобке записываются парами. Например, вот так –

   v4CGDAEBFcw>Bcdefgab 

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

   На второй осциллограмме показан звук, порождённой буквенной скобкой с аккордами, отдельные ноты которых заменены паузами –

   v4C ^^AEBFcw~~

   Волнистые значки в конце это паузы, соединяясь с которыми звучат ноты, записанные в нечётные (правые) ячейки буфера.
   Первые два звука получаются одиночными, а вторые представляют собой аккорды из двух нот.  Заметьте, что пауза на месте второй ноты к первому аккорду не ослабляет звука первой ноты, тогда как во втором аккорде пауза предшествует звуку, и в соответствии с приведённой выше формулой A=0/2+A2/2 ослабляет звук второй ноты вдвое. Таким образом, с помощью расстановки пауз, можно управлять громкостью одиночных звуков в аккордах.

   Аккорды из трёх нот вводятся буквенной скобкой v9…w при этом деление буфера на части остаётся прежним, то есть тем, каким оно было введено раньше. По умолчанию, то есть в самом начале проигрывания нотных строк, когда мы кликаем по кнопке «play», буфер разделён четвертными нотами на три части.

   Итак, мы видим, что типично одноголосая программа «Пианола» путём манипуляций с буфером, накапливающем семплы, может быть превращена в интересную музыкальную игрушку, играющую на фортепьяно в две руки и даже с аккордами звучащими в правом динамике.
   Если вам не нравится слушать аккорды справа, то вы, кликнув по картинке, изображающей рояль, обнаружите на месте картинки кнопку, с помощью которой вы можете поменять звук в динамиках местами, или смешать звук, сделав его справа и слева одинаковым.
   Ещё большие подробности, связанные с использованием буфера, обсуждаются тут - http://proza.ru/2024/04/11/3   

   Пока я писал эту статью, я нашёл ошибки и сделал массу полезных улучшений в своей программе. Чувствуете, насколько продуктивным бывает написание подробных статей на любую тему? Я всегда так делаю.
   Спасибо за внимание.

_________
7.04.2024      


ПРИМЕР нотной записи с двухголосием и аккордами.
прослушать можно тут - https://disk.yandex.ru/d/_EUKOYBLmsIiiw

{Шопен вальс}
 1  {*3420|Z4-Y1^ed0v\FF/AdAdw>(-3)ded()cdfe <v\E0E0GdGc/w<d>c v\EEG-cG-c/w^>A0B(-3)ced()}
 2  {*3420|v\DDFBFB/w<c>B v\D0D0FBFB0/wB7>A(-3)AGF() <v\EEAcAc/w<F>E                }
 3  {*3420|v9\\EEE/EBdEGdw/>^`'>>E<<gfed <v9\\AAA/EAcEAc/w<B0>c v\FFAdAd/w>^ebafd        }
 4  {*3420|v\E0E0GdGc/w<d>c v\EEG-cG-c/w>>(-10)A0BA0G#A0(-3)<cef()g-7>A0                }
 5  {*3420|v\``E4E4FcFB/w>>A0<<<c4>B  v\D0D0FBFB0/wB7>A(-3)AGF()                }
 6  {*3420|v9\\EEE/EAcEAc/w<F>E v9\\EEE/EGd-EAf/w3>(-3)EFE()D0Ed-c <v\\AA/<Ec/w9<A7H6H0Y1}
 7  {*3420|^ed0Y2v\FFAdAd/w>(-3)d-ed-()cd-fe< v\E0E0AdAc/w<d>c v\EEG-cG-c/w^>A0Bc>>e-<<d3}
 8  {*3420|v\DDFcFA/w<d>A v\D0D0FAFA0wb7>a(-3)agf() <vEEAcAcw<f>e                }
 9  {*3420|v\EEAcAc/w>^'`>>E<<gfed <v9\\AAA/EAcEAc/w<B0>c v\FFAdAd/w>^dbafd<             }
 10  {*3420|v\E0E0GdGc/w<d>c }
 11  {*3420|H0v\`D4D4FcFB/w>>>A0<<<<c4>B v\D0D0FBFB0/wB7>A(-3)AGF()< v9\\EEE/EAcEAcw<f>e  }
 12  {*3420|v9\\EEE/EGdEBf/w>(-3)EFE()D0E<d W6w6<v9E7B7f7wc7 W5w5v\\A7A7/E7c7 /wA7        }
,,,

   В этой записи, в её начале Z4- это указание на 4 бемоля в ключе, а между символами (-3) и () заключены триоли.