Создание многоголосия при озвучивании мелодии

Дмитрий Маштаков
  В предыдущих статьях рассказывалось о программе Пианола, которая позволяет, используя обычную клавиатуру компьютера, создавать какую хотите мелодию, и не только проигрывать её, но и редактировать, хранить в виде мнемонических записей в обычных текстовых файлах, а также выводить значения частот и длительностей звучания нот в отдельный текстовый файл.
  Этот файл, или несколько таких файлов, которые мы будем называть партитурой, могут быть озвучены с использованием звуков различных инструментов. Принципы синтеза подобного звука также подробно обсуждались в предыдущих статьях.
 
  Структурная схема работы программы, озвучивающей сразу несколько файлов партитуры, показана на рисунке. Она полностью повторяет структуру взаимодействия музыкантов в ансамбле.
  Каждый музыкант - MUZ, хранит у себя дома свой набор музыкальных инструментов - FNSS. Каждый - свой. Но может случиться, что наборы у них одинаковы.
  Звучат эти инструменты разными голосами, и музыкант берёт тот, который указан ему в партитуре - R=2 рояль, R=4 альт или R=5 труба, - берёт его, и занимает своё место в оркестре.


   О ПАРТИТУРЕ

  Но, прежде чем продолжать рассказ, хочу показать Вам, как выглядит партитура коротенькой  песенки -

 1 ================= TF.TXT   VEL= 32000
 R=2
F= 493.8800048828125  T1= 7840  T2= 159
F= 554.3599853515625  T1= 7840  T2= 159
F= 659.260009765625  T1= 7840  T2= 159
F= 622.260009765625  T1= 7840  T2= 159
F= 554.3599853515625  T1= 7840  T2= 159
F= 493.8800048828125  T1= 7840  T2= 159
F= 622.260009765625  T1= 15680  T2= 319
F= 622.260009765625  T1= 15680  T2= 319
 L= 8000
F= 739.97998046875  T1= 7840  T2= 159
F= 659.260009765625  T1= 7840  T2= 159
F= 622.260009765625  T1= 7840  T2= 159
F= 739.97998046875  T1= 7840  T2= 159
F= 659.260009765625  T1= 7840  T2= 159
F= 622.260009765625  T1= 7840  T2= 159
F= 659.260009765625  T1= 15680  T2= 319
F= 659.260009765625  T1= 15680  T2= 319
 2 ============ 168000  168000  5.25
F= 659.260009765625  T1= 7840  T2= 159
F= 739.97998046875  T1= 7840  T2= 159
F= 830.5999755859375  T1= 7840  T2= 159
F= 739.97998046875  T1= 7840  T2= 159
F= 659.260009765625  T1= 7840  T2= 159
F= 830.5999755859375  T1= 7840  T2= 159
F= 622.260009765625  T1= 15680  T2= 319
F= 622.260009765625  T1= 15680  T2= 319
 L= 8000
F= 622.260009765625  T1= 7840  T2= 159
F= 554.3599853515625  T1= 7840  T2= 159
F= 493.8800048828125  T1= 7840  T2= 159
F= 554.3599853515625  T1= 7840  T2= 159
F= 493.8800048828125  T1= 7840  T2= 159
F= 466.1600036621094  T1= 7840  T2= 159
F= 415.2999877929688  T1= 15680  T2= 319
F= 415.2999877929688  T1= 15680  T2= 319
 L= 8000
 3 ============ 176000  344000  10.75
,,,

  Именно в таком виде, в форме файла TF.TXT партитура была получена от программы Пианола. В ней указаны частоты F, длительности звучания нот в семплах T1 и длительности коротких пауз, следующих после окончания звучания нот T2.
  Длительности больших пауз указаны отдельно - L=...
  Партитура разделена на страницы длинными чёрточками. В конце чёрточек указано суммарное время звучания в семплах и секундах. Страниц в партитуре две.
  Я добавил в эту партитуру вторую строку - R=2. Она говорит музыканту, что при озвучивании нужно использовать звуки фортепьяно.
  Я предназначил эту партитуру первому музыканту и переименовал файл в TF1.TXT

  Затем я сделал две копии файла - TF2.TXT и TF3.TXT, и слегка эти копии изменил. Добавил пустую первую страницу во второй файл, проведя ещё одну длинную чёрточку в начале, а за ней (и это будет уже вторая страница) указал R=4, а в начале третьей страницы указал R=3 (нет, это не чистый звук фортепьяно, это звук "дзинь").
  Второй музыкант, заглянув в файл TF2.TXT и увидев в начале его две чёрточки, подождёт, пока первый музыкант не закончит играть свою первую страницу, и только после этого, с новой страницы, начнёт дружно играть вместе с ним.

  А третий музыкант всё ещё молчит - ведь я ему, проведя лишние две чёрточки в начале, добавил две пустых страницы. И начнёт играть третий музыкант только со своей третьей страницы, когда первый всю свою игру на двух страницах уже закончит. И будет играть третий музыкант на трубе. Потому что я указал ему R=5.

  Хотите услышать, что получилось? - https://yadi.sk/d/Jljx_snNqLNDy


  О РАБОТЕ ДИРИЖЁРА

  Если любопытство знать, как всё устроено, ещё не оставило Вас, то продолжим рассказ. Вот та часть программы, которую можно назвать дирижёром (смотрите также и на рисунок)-

    CASE "z"
SEEK #1,44 : VEL=32000 : COF=1
  CALL MUZ1(-1) : CALL MUZ2(-1) : CALL MUZ3(-1)
 OPEN "TF1.TXT" FOR INPUT AS #11 : CALL MUZ1(1)
 OPEN "TF2.TXT" FOR INPUT AS #12 : CALL MUZ2(1)
 OPEN "TF3.TXT" FOR INPUT AS #13 : CALL MUZ3(1)

12 S$=INKEY$ : IF S$=" " THEN GOTO 10
  FEF=0 : REF=0 : PRECOF=0
  CALL MUZ1(0) : CALL MUZ2(0) : CALL MUZ3(0) 'go on please
  COF=PRECOF : IF COF=1 THEN M%=INT(FEF*1000) : PUT$ #1,MKI$(M%)
  IF REF=1 THEN 12
  GOSUB 1000 : CLOSE #1 : PRINT "Ok" : STOP

  Когда мы запускаем программу, то она печатает знак ? В ответ мы вводим "or", что означает -  открыть файл "r.wav". Этот файл с внутренним номером #1 дирижёр будет заполнять семплами звука.

  Дирижёр приходит в случае CASE "z" - тогда, когда мы введём "z".
  Он устанавливает поинтер на начало записи - SEEK #1,44
  указывает скорость раздачи VEL=32000 семплов в сек
  устанавливает COF=1, означающее разрешение записи, а также подающее ободряющий знак музыкантам - если у вас страница закончилась, то не беспокойтесь, другие всё ещё играют, подождите их.
  Затем дирижёр собирает музыкантов, посылая каждому условное сообщение с отрицательным знаком -
  CALL MUZ1(-1) : CALL MUZ2(-1) : CALL MUZ3(-1)
означающее, приходите, но пока ничего не делайте.
И они замечают себе в своих внутренних переменных, что не следует играть сразу, а надо подождать других Co=0, и даже партитуру можно пока не раскрывать, может быть и играть даже не придётся Re=0. Но можно перекусить, и каждый занимается своим бутербродом, принесённым из дома.
 
  Тут дирижёр открывает файлы партитуры и раздаёт их тем музыкантам, которые будут играть, первому например -
 OPEN "TF1.TXT" FOR INPUT AS #11 : CALL MUZ1(1)
посылая ему положительный знак и приглашая подготовиться к игре.

Первый музыкант делает это - Re=1 : Co=1, и проводит ещё некоторые установки по умолчанию -
      H=1 : T1=0 : T2=0
      TB=1 : TE=20 : R=KK : 
Уровень громкости ставит в единицу H=1, на затухание звука в конце ноты даёт 20 периодов, по умолчанию выбирает тот инструмент, на который ему указали при обращении, в данном случае  номер 1. А это простая синусоида. Но на синусоиде он играть конечно не будет, ведь мы указали ему R=2 и он усядется за рояль.
  Сделав установки, первый музыкант перестаёт суетиться и внимает дирижёру - EXIT SUB.

  Если не пригласить таким же образом других музыкантов (поставить апострофы в начале следующих двух строк программы), то первый музыкант будет играть свою партию в одиночестве. Но наш дирижёр пригласил и других -
 OPEN "TF2.TXT" FOR INPUT AS #12 : CALL MUZ2(1)
 OPEN "TF3.TXT" FOR INPUT AS #13 : CALL MUZ3(1)

  В дальнейшем дирижёр будет непрерывно зацикливаться на метке 12, поглядывая в этот момент на дверь - не стучится ли кто, а вдруг пожар! Или слушатели утомились ждать окончания процесса озвучивания? Если мы нажмём пробел, то дирижёр прервёт свою работу и пошлёт нас на метку 10, в начало программы, где мы можем дать какие-нибудь новые указания, например, ввести "h" - определить длину файла и записать его шапку (с 0 по 43-й байт), затем ввести  "c" - закрыть файл записи, и ввести "e" - выйти из программы в среду Бейсик.
 
       12 S$=INKEY$ : IF S$=" " THEN GOTO 10

  Но если пробел мы не нажимаем, то дирижёр работает.
  FEF=0 : REF=0 : PRECOF=0
он осуществляет контакт с музыкантами, поглядывая на них через эти переменные, обнуляет их,
обращается к музыкантам -
  CALL MUZ1(0) : CALL MUZ2(0) : CALL MUZ3(0) 'go on please
Пожалуйста, продолжайте! А музыканты эти переменные изменяют.
  В FEF накапливается амплитуда звука, полученная от инструмента и добавляемая каждым музыкантом в общее звучание.
  REF каждый музыкант устанавливает в единицу, в том случае, если ему осталось что играть, если он ещё не дошёл в своей партитуре до строки ",,," означающий финиш.
  Дойдя до этой строки он перестаёт устанавливать в 1 переменную REF, закрывает свой файл партитуры, отключает сам себя от работы Re=0, и сидит, отвернувшись от дирижёра, и ровно ничего не делает.
  Ну, а дирижёр сам себе на уме - он возвращается к метке 12 только пока REF=1, то есть пока остаётся хоть один играющий музыкант. А если все закончили, то пишем программе шапку, закрываем звуковой файл #1, и заканчиваем работу, выходим в среду Бейсик.
  Музыканты изменяют не только переменную REF. Они изменяют также переменную PRECOF (предварительное значение COF), устанавливая её в 1 в том случае, если они ещё не закончили играть текущую страницу партитуры.

  Осталось сказать об этой строке -
  COF=PRECOF : IF COF=1 THEN M%=INT(FEF*1000) : PUT$ #1,MKI$(M%)
Вы всё видите сами - звук записывается только если не все музыканты дошли каждый до конца своей текущей страницы. Но если закончились страницы у всех, то и FEF оказывается равным нулю, но этот ноль является лишним, и его не следует записывать в звуковой файл.
В следующем цикле каждый музыкант поглядит на переменную COF=0, и подумает - все остановились, и я не играю - Co=0, а давайте новую страницу дружно вместе и со свежими силами. И, установив Co=1, музыкант включается в работу.


  ТЕКСТ ПРОГРАММЫ ИЗОБРАЖАЮЩЕЙ МУЗЫКАНТА

  Приведу этот текст без комментариев, точно такие же тексты с заменой названия, метки перехода и обращений к звуковым функциям представляют собой MUZ2 и MUZ3
  Целиком вся программа озвучивания TRIO.bas находится тут, в общем наборе программ по созданию звука - https://yadi.sk/d/Ca2WYIkeq9w48 
  Там же находятся файлы партитуры TF1.TXT, TF2.TXT и TF3.TXT.
  Можете пробовать.

 SUB MUZ1(K)
STATIC L$,Re,Co,F,R,T,T1,T2,TB,TE,H,KK
SHARED FEF,REF,COF,PRECOF '====
   KK=K
  IF KK=-1 THEN Re=0 : Co=0 : EXIT SUB
  IF KK>0 THEN
     Re=1 : Co=1 : H=1 : T1=0 : T2=0
     TB=1 : TE=20 : R=KK : EXIT SUB
  END IF
  IF Re=0 THEN EXIT SUB
  IF Co=0 AND COF=1 THEN REF=1 : EXIT SUB ELSE Co=1
MU1:
  IF T1=0 AND T2=0 THEN
   LINE INPUT #11,L$
   IF MID$(L$,6,3)="===" THEN PRINT "1 "+L$ : Co=0 : REF=1 : EXIT SUB
   IF LEFT$(L$,3)=",,," THEN CLOSE #11 : Re=0 : EXIT SUB
   I=INSTR(L$,"H") : IF I>0 THEN H=VAL(MID$(L$,I+2))
   I=INSTR(L$,"R=") : IF I>0 THEN R=VAL(MID$(L$,I+2))
   I=INSTR(L$,"TB") : IF I>0 THEN TB=VAL(MID$(L$,I+3)) : IF TB<1 THEN TB=1
   I=INSTR(L$,"TE") : IF I>0 THEN TE=VAL(MID$(L$,I+3)) : IF TE<1 THEN TE=1
   I=INSTR(L$,"L") : IF I>0 THEN T2=VAL(MID$(L$,I+2)) : F=0 : T1=0
   I=INSTR(L$,"F") : IF I>0 THEN
     F=VAL(MID$(L$,I+3)) : T=TB : AO=FNSS1(0,0) : print F
     I=INSTR(L$,"T1") : IF I>0 THEN T1=VAL(MID$(L$,I+3))
     I=INSTR(L$,"T2") : IF I>0 THEN T2=VAL(MID$(L$,I+3))
        END IF : GOTO MU1
  END IF
    PRECOF=1 : REF=1
    IF F=0 THEN T2=T2-1 : EXIT SUB 'pause
    AO=FNSS1(F,R)*H : T1=T1-1: IF T1<TE THEN AO=AO*T1/TE
    IF T>1 THEN T=T-1 : AO=AO*(1-T/TB)
    IF T1<=0 THEN F=0
    FEF=FEF+AO
 END SUB
'===========


   ТЕКСТЫ СЦЕНАРИЕВ ДЛЯ ИНСТРУМЕНТОВ

  Все три функции создания звука FNSS1, FNSS2 и FNSS3 используемые в TRIO.bas работают с одинаковыми наборами сценариев. Эти функции несколько усечены - в них упрощена группа Виолы, отсутствует обертонная группа, отсутствует ревербератор.
  Упрощение сделано для того, чтобы, во-первых, было легче отлаживать программу, и во-вторых, сам процесс озвучивания без обертонов и эха идёт быстрее.
  Кроме того есть надежда на фоне более бедных возможностей ещё как-то попытаться улучшить основной звук, не прибегая к обертонам и к эху.
   Итак, сценарии -

  CASE 1 '~sin
    MU1=32.2 : AU1=2 : DU1=INT(FS/40)+.995
    MU2=24.3 : AU2=.7 : DU2=100.999 : mod2=3

  CASE 2 'f-no
    MU1=64.1 : AU1=2 : DU1=3.995 ': EA=.5 : E0=1.25 : E1=1.3 : E2=1.3
     MU3=0 : AU3=1.5 : DU3=2.99
    '>>>>="_1_2_3_4_5_6_7_8_9_0_1_2"
     Arr$="905030307050505070303050"
  Apm$="505050707050505560657035504744433857" : Dpm=.98 ': DK1=.92
   CASE -2 : E0=1.25*(1+.2*FNTIM(4,T)) : EA=.3*(1+.2*FNTIM(8,T))
   E1=1.3*(1+.2*FNTIM(10,T)) : E2=1.3*(1+.2*FNTIM(7,T))

  CASE 3 'dzin
    MU3=64.1 : AU3=2 : DU3=2.995
        Apm$="505050707050505560657035504744433857" : Dpm=.95

  CASE 4 'viola
    MU2=20.2 : AU2=4 : DU2=150.99 : mod2=3 : DK1=.92
  CASE -4 : Vi=.35+.15*FNTIM(4/(1+.6*T),T) : IF NT=100 THEN MU2=40.05

  CASE 5 'du du
    MU1=50.06 : AU1=2 : DU1=100.995 : EA=-2
    Apm$="505050707050505560657035504744433857" : Dpm=.99
  CASE -5 : E0=1.25*(1+.2*FNTIM(4,T)) ': EA=.3*(1+.2*FNTIM(8,T))
   E1=1.3*(1+.2*FNTIM(10,T)) : E2=1.3*(1+.2*FNTIM(7,T))

  CASE 6 'bumz
    MU1=55.06 : AU1=2 : DU1=20.99
    MU3=0 : AU3=3 : DU3=3.95
    Arr$="603060506030605060306050"
    'эта переменная программой не используется - такая строка создаёт более высокие обертоны с писклявым звуком
    Apm$="505050707050505560657035504744433857" : Dpm=.95
  CASE -6 : SWAP Arr$,A3r1$ : E0=1.25*(1+.2*FNTIM(4,T))

  Звучание всех сценариев от 1-го до 6-го можно прослушать здесь, на квинтовых ходах - https://yadi.sk/d/Unh8DQgAqLNth

  Об особенностях работы сценариев см. статью "О математическом моделировании звука"


  О ДАЛЬНЕЙШЕМ РАЗВИТИИ ПРОЕКТА

  Вы обратили внимание, - музыканты у нас получились универсальными и унифицированными. Они пользуются одинаковым набором инструментов с одинаковым тембром, но обладают бедными возможностями чтения партитуры, и бедными средствами корректировки и изменения звука. Только его громкость и выбор инструмента, скорость атаки и спада доступны в качестве указаний в партитуре.
  Между тем как в игре, например, на скрипке, используется масса так называемых штрихов - указаний на способ извлечения звука, влияющий на его тембр и характер развития. Для такой массы нюансов игры, и указаний на эти нюансы, универсализм является помехой, поскольку он неизбежно будет способствовать разрастанию объёма текста программы. Примерно втрое, если для озвучивания используется ансамбль трио.
  Поэтому цель дальнейшего развития проекта, это специализация музыкантов одновременно с расширением возможностей каждого внутри своей специальности. Как и у обычных оркестрантов - у альтистов свои штрихи в тексте партитуры, у барабанщика - свои. Один не обязан понимать другого. И у каждого есть "заточенный под него" музыкальный инструмент. Своя звуковая функция со своим специфическим набором сценариев.
  Представляется перспективным и использование внешних параметров - глобальных переменных, влияющих на развитие звука по локальным, внутренним сценариям звуковой функции. Эти параметры могут читаться прямо из партитуры, где они могут быть записаны в удобной для понимания мнемонической форме. Такой взаимный перехлёст внутренних и внешних параметров позволит создать гибкую и эффективную систему управления музыкальным звуком в ходе его синтеза.
  Такие вот планы на будущее.
  Опробование программы "Трио" показало перспективность будущего развития такой концепции.

Спасибо за внимание.
==========
P/S Проект продвинулся вперёд. Результат озвучивания произведений классической музыки Вы можете прослушать здесь - http://yadi.sk/d/poMxlrBuxendv