Pull to refresh

Забытая музыка или немного о Nokia 3310, PC-Seaker’е и генерации MIDI файлов (ч1)

Reading time7 min
Views12K
Здесь мы немного поговорим о формате мелодий (RTTL) в старых моделях телефонов, о воспроизведении таких мелодий с помощью обычного PC-Speaker’а компьютера, а так же о создании (генерации) MIDI файлов. Все мои мысли я буду подкреплять кодом на языке Pascal.


Сразу оговорюсь, что эта заметка скорее всего не представляет для большинства уже никакого практического интереса и создана автором скорее для себя, на память так сказать… Но хватит лирики, начнем.

Теория.



Я думаю, что многие еще помнят, как сидя на галерке в школе или институте перепечатывали из телефона товарищей мелодии вида “8e,16d,16b4,16a4,16b4,8a4,16a4,16a#4”. Этот так называемый RTTL формат записи мелодий. Подробно описывать его здесь смысла нет, поскольку описание полностью доступно в интернете, но для дальнейшего понимания простейший пример мы рассмотрим. Итак, возьмем такую RTTL мелодию:

Simpsons:d=4,o=5,b=160:32p,c.6,e6,f#6,8a6,g.6,e6,c6,8a,8f#,8f#,8f#,2g

Как можно заметить, формат имеет вид

[название:длительность,октава,скорость в минуту(BPM):сама мелодия].

Нам понадобятся следующие параметры:

“d=4” Длительность ноты по умолчанию. Это значит, что в записи самой мелодии, когда мы захотим проиграть ноту a “ля” длительностью 4, нам не обязательно будет записывать ее как “4a”. Достаточно будет записать ее просто как “a”. Здесь же отмечу, что если сравнивать длительность RTTL и длительность ноты в музыкальном понимании, то идет простое соответствие – 1 / длительность RTTL. Таким образом, “d=4” в записи RTTL означает, что мы играем ноты по умолчанию, с длительностью “одна четвертая”. Если d=6, то “одна шестая” и так далее.



“o=5” Октава по умолчанию. Из теории музыки вспомним, что октава – это удвоенная частота между одинаковыми нотами. Так, если нота “ля” четвертой октавы имеет частоту 440 Гц, то та же нота “ля” пятой октавы будет иметь частоту 880 Гц. Ровно так же, как и с длительностью, в дальнейшей записи нам нет необходимости записывать ноту a “ля” пятой октавы, как “a5”. Достаточно просто записать ее как “a” и мы сыграем ноту “ля” пятой октавы с длительностью одна четвертая. Таким образом получается, что в нашем случае запись “a” будет равнозначна записи “4a5”.



“b=160”. Темп, или скорость в минуту (BPM). Чтобы вычислить миллисекунды, которые должна звучать нота, я воспользовался такой формулой: ((60000 / b) / d) * 8, где b – наш темп, а d – длительность ноты. Почему так, я уже признаться и сам забыл. Но это работает :)

Дальше начинается сама мелодия. Из описания следует, что формат записи нот в общем случае такой:

[длительность нота октава доп. знак].

В качестве разделителя между нотами выступают запятые. Используются ноты в английской(?) системе записи 'c', 'c#' ,'d' ,'d#' ,'e' ,'f' ,'f#' ,'g' ,'g#' ,'a', 'a#', 'b', что соответствует итальянским ‘до’, ‘до диез’, ‘ре’, ‘ре диез’, ‘ми’, ‘фа’, ‘фа диез’, ‘соль’, ‘соль диез’, ‘ля’, ‘ля диез’, ‘си’. А также пауза – ‘p’. Дополнительные знаки могут быть:
"." (точка) — увеличение длительности ноты в полтора раза
";" (точка с запятой) — в два раза
"&" (амперсанд) — в 2.5 раза
С этим не должно возникнуть проблем — когда мы находим один из таких знаков в конце ноты, мы будем просто посчитанные миллисекунды умножать на ½, два и два с половиной раза.

С теорией на этом закончим, если остались вопросы, еще раз порекомендую почитать описание RTTL формата. Переходим к практике.

PC-Speaker



Для начала, будем выводить нашу мелодию для простоты на обычный PC-Speaker. В большинстве языков программирования для вывода звука на спикер нам нужно знать всего два параметра – частота и задержка в миллисекундах. Так, в Virtual Pascal существует процедура
PlaySound(Freq, Duration: Longint);

которую я и буду использовать.
Теперь давайте подумаем и начнем с простого. Допустим, что наша мелодия состоит всего из одной ноты и выглядит так: “test:d=4,o=5,b=125:a”. Таким образом, нам нужно проиграть ноту “ля” пятой октавы длительностью одна четвертая с темпом 125. Первое что приходит на ум – мы должны знать все частоты нот для всех октав, для этого естественно строим таблицу частот:
Var
      Frequency : Array [1..8*12] of word;  {таблица частот}
 
{ …… some code ……}
 
Procedure InitFreqTable; {Процедура инициализации таблицы частот}
Const
  HerzOfFirst = 32.703195258;       {нота C первой октавы}
 
Var
      tmpReal1, tmpReal2 : real;
      i : word;
 
Begin
  tmpReal1 := HerzOfFirst;  {начнем с первой ноты C первой октавы}
  tmpReal2 := exp(ln(2.0)/12.0);  {постоянная, с которой изменяется частота от ноты к ноте}
  for i := 1 to 8*12 do {12 нот на октаву, 8 октав}
  begin
    Frequency[i] := round(tmpReal1);
    tmpReal1 := tmpReal1 * tmpReal2;
  end;
End
 

Отлично. Далее, все дело упирается в разбор записи нот на длительность, октаву, и собственно саму ноту. Приводить здесь сам парсинг считаю неуместно, если кому-то интересно в конце статьи будет полный исходный текст программы. Давайте разберем лишь вычисление в таблице самой ноты. Я реализовал это так:
{предварительно мы распарсили длительность и октаву}
      // parse note
{i – будет обозначать смещение в таблице частот}
      i := (octave-1)*12{сначала переместимся на нужную октаву}
      Case S[1] of    {В s[1] находится символ ноты}
            'c' : inc(i, 1){и прибавляем к смещению ноту}
            'd' : inc(i, 3){здесь не учитываем диезы, я их обрабатываю после}
            'e' : inc(i, 5);
            'f' : inc(i, 6);
            'g' : inc(i, 8);
            'a' : inc(i, 10);
            'b','h' : inc(i, 12);
            'p' : i := 0{а это  - пауза}
         else
            begin {если встретилась другая буква, выдаем ошибку}
                  PlayRTTTLNote := 1;
                  WriteLn('DEBUG: Error[1]: Wrong note - "',S[1],'"');
                  exit;
            end;
 

Итак, теперь в переменной “i” находится смещение в массиве частот нужной нам ноты. Теперь можно обработать в конце знак “#” (диез), и если он присутствует в записи ноты, просто увеличить значение “i” на единицу. Теперь мы можем проиграть ноту, пока с длительностью в 1000 мс простой командой:
PlaySound(Frequency[i], 1000);

С нотой и частотой разобрались. Остается разобраться лишь с длительностью звучания ноты. Выше я уже давал формулу, по которой рассчитывал длительность звучания в миллисекундах в зависимости от темпа и длительности самой ноты. Ниже просто приведу код:
       //calculate microseconds
      PrecalcMS := (60000 / BPMspeed);  {BMPspeed – это темп, тоесть параметр “b=”}
      PrecalcMS := (PrecalcMS / Duration)*8{duration – отпарсенная частота ноты}
      MicroSecs := round(PrecalcMS){округляем до целочисленного}
 

Здесь главное не забыть про дополнительные значения “точка”,“точка с запятой” или “амперсанд”, которые могут присутствовать в нотной записи, и в зависимости от их наличия домножать значение PrecalcMS на ½, 2 или 2.5. В основной программе это есть, здесь не привожу. Все, теперь можем проиграть ноту с определенной выше частотой и нужной нам длительностью:
Procedure PlayNote(Note : byte; MicroSecs : Word);
Begin
If Note = 0 then    {Помните, если мы встретили “p”, то установили I = 0}
          Delay(MicroSecs)  {значит это пауза}
Else
  playsound(Frequency[Note],microsecs);
End;
 

А в основной программе играем ноту:
PlayNote(I, MicroSecs);

Таким нехитрым способом мы реализовали проигрыватель RTTL мелодий на PC-Speaker. Полный исходный текст программы, а так же скомпилированная версия, доступны здесь: rghost.ru/2230394 Я использовал для компиляции Virtual Pascal, но я думаю что без проблем должен собраться и в Free Pascal.

В следующей статье поговорим о создании конвертера RTTL 2 MIDI в целом и создании и работе с MIDI файлами в частности. Удачного дня!

При написании статьи использовались материалы:
personal.telefonica.terra.es/web/japus/rtplayer.html
www.music4sale.ru/articles/knowledge_base/586
members.home.nl/bas.de.reuver/files/convert_rtttl.html
Tags:
Hubs:
+3
Comments8

Articles