Пишем программное обеспечение для генерации данных музыкальной открытки. Часть первая: разбираем MIDI файл
Введение
В своих статьях о переходе на российский микроконтроллер К1986ВЕ92QI я ни раз рассказывал о генерации звука средствами микроконтроллера. Тогда передо мной стояла задача лишь воспроизвести данные. Для создания этих самих данных, получаемых из MIDI файлов, использовались весьма экзотические методы, например, как в этой статье. Да, подобные методы имеют право на жизнь, если требуется получить данные для воспроизведения пару раз в жизни. Но так как я достаточно часто сталкиваюсь с задачами, когда на контроллере нужно получить достаточно сложный звук, или же звук — лишь дополнительная опция, то задача преобразовывать MIDI файлы такими экзотическими способами, становится весьма нетривиальной. В этой небольшой серии статей я поставил для себя задачу создать (а за одно и подробно рассказать о процессе создания) универсальную программу для преобразования MIDI файлов в приемлемый для микроконтроллера формат, а так же генерирующую все необходимые для микроконтроллера данные инициализации.
Итогом данной статьи станет реализация основного функционала программы: создание массивов нота-длительность, созданного из MIDI файла. Кто заинтересовался — прошу под кат.
Структура статьи
- Выработка требований к программе.
- Определение способа реализации.
- Общие сведения о MIDI.
- Заголовок.
- Блок MIDI файла.
- События.
- Разбор полученных данных.
- Заключение.
Выработка требований к программе
Как уже говорилось выше, основной задачей нашей программы будет преобразование данных из MIDI формата в наш собственный. Иначе говоря, перед нами не стоит задачи учитывать силу нажатия клавиш, использование редких инструментов или же использования эффектов, предусмотренных стандартом MIDI. Всю эту и подобную ей ненужную информацию мы должны игнорировать. По окончании работы программы, мы должны получить N-е число массивов, в каждом из которых в текущий момент времени будет играть лишь одна клавиша (это нужно для упрощения программы в микроконтроллере). Иначе говоря, мы должны получить список массивов с полифонией в одну ноту.
Определение способа реализации
В одной из предыдущих статей мы уже писали программу, которая реализовала подобный функционал на основе уже переработанных другой программой данных в специфичном виде. Программа была написана на Pascal ABC, потому что на тот момент задача сводилась к обработке строк txt файла, не более. Сейчас же мы пишем программу с нуля, предполагая работу с чистыми данными MIDI. Так же, в будущем планируем расширить ее до полноценного генератора данных инициализации. Так что в этот раз программа будет написана в графической среде Visual Studio на языке C#. Много ресурсов компьютера нам не требуется, а красивый синтаксис и возможность легкого чтения программы, способствующее легкой поддержки — не помешают.
Общие сведения о MIDI
Многие знакомы с MIDI форматом, или, по крайне мере, о нем наслышаны. В этом формате удобно хранить, например, ноты музыкальных произведений, с возможностью прослушать их. Именно для этой цели, чаще всего, MIDI и используется в современном мире. Но когда-то в него пытались запихать достаточно много всяких дополнительных функций. Так из чего состоит MIDI файл?
Как видно из рисунка, MIDI файл состоит из:
- Заголовка файла (он начинается с четырех символов, составляющих слово MThd).
- Блоков файла (начинающихся с символов MTrk).
Для начала давайте рассмотрим заголовок MIDI файла (MThd).
Заголовок
Разберем, из чего состоит заголовок MIDI файла.
- Стандартные значения. В заголовке присутствуют ячейки, значения которых одинаковы для всех MIDI файлов.
- Надпись заголовка «MThd». Данный параметр позволяет однозначно сказать, что перед нами блок заголовка.
- Размер индивидуальных параметров файла в блоке заголовка. Так как в заголовке всегда присутствуют 3 индивидуальных параметра, каждый из которых занимает по 2 байта — то общая длинна блока заголовка (без учета надписи «MThd» и четырех байт самого размера) составляет 6 байт.
- Индивидуальные параметры.
- Формат MIDI файла. По сути говоря, форматов MIDI файла всего 2: 0 и 1. Имеется еще формат 2, но за всю свою девятилетнюю работу со звуком, в реальной жизни мне так и не довелось столкнуться с MIDI файлом в этом формате. Данный параметр показывает, как упакованы события (в нашем случае, нажатия/отпускание клавиш). Если перед нами формат 0, то мы знаем наверняка, что вся полезная информация обо всех каналах (коих может быть до 16) расположена в одном единственном блоке MTrk. Если же перед нами формат 1, то каждый канал имеет свой собственный блок MTrk. Наша программа будет иметь возможность работать с обоими форматами.
- Число блоков MIDI файла (MTrk). Тут мы можем посмотреть, сколько блоков содержится в нашем MIDI файле. Данный параметр актуален лишь для формата 1. Ибо в формате 0 блок всегда 1.
- Формат времени MIDI файла. А вот тут дела обстоят весьма интересно. Дело в том, что в MIDI файле счет идет не секундами, а «тиками». Причем существует музыкальный способ, когда значение нашего параметра показывает, сколько «тиков» приходится на музыкальную четверть и абсолютный, показывающий количество «тиков» в SMPTE блоке. Опять же. Чаще всего встречается первый способ. Второй, все таки, экзотика. Поэтому мы не будем учитывать существования абсолютного способа отсчета времени и будем оперировать только музыкальным.
Теперь, зная структуру заголовка MIDI файла, мы можем его считать. Но прежде нужно уяснить один момент. Данные в MIDI файле (длина которых более одного байта), представлены в формате big-endian. Это значит, что если перед нами ячейка, состоящая из двух байт, то первым байтом идет старший байт, а вторым — младший. Непривычно, но формат не молодой, и можно ему это простить.
- Для работы нам нужно создать Windows Forms приложение (WPF тут без надобности, но если хотите, то никто не запрещает).
- Внутри формы создадим button и richTextBox (у меня они имеют имена button1 и richTextBox1 соответственно), а так же окно для открытия файла openFileDialog (у меня, опять же, имеет имя openFileDialogMIDI).
- Создадим событие, привязанное к нажатию на кнопку, в котором очистим richTextBox от старых данных. Так же получим путь к MIDI файлу и передадим его функции, которая откроет его. (openMIDIFile)
Прошу обратить внимание на строку создания массив структур MTrkStruct. Как говорилось выше, в заголовке файла есть ячейка, указывающая, сколько еще блоков, помимо блока заголовка, содержится в MIDI файле. Сразу же после считывания заголовка мы можем создать массив структур информационных блоков MIDI файла. Данная структура будет рассмотрена далее. После выбора MIDI файла, мы увидим следующее.
Блок MIDI файла
Рассмотрев заголовок файла, мы можем приступить к рассмотрению структуры блока.
Блок состоит из:
- Четырех символов, составляющих слово MTrk. Это указатель того, что перед нами MIDI блок.
- Длинны блока, записанной с помощью четырех байт. В длину блока не входят первые 8 байт (MTrk + 4 байта длины).
События.
Вот мы и подошли к самому интересному. Именно в событиях содержится вся нужная нам информация. MIDI события бывают четырех типов.
- События первого уровня.
В MIDI файлах принято считать, что существуют 16 каналов. Место, где находится номер канала помечено как nnnn. 0 = первому каналу, 1 = второму и так далее. Таким образом, под номер канала выделены младшие 4 бита. На каждом канале может быть нажато N-е число нот. В зависимости от того, сколько позволяет воспроизвести устройство, читающее MIDI файл. Номер канала для нас не имеет никакой роли, потому что у нас в тех. задании ясно сказано, что на каждом канале в текущий момент времени должно быть включено не более одной клавиши. Иначе говоря, разбитие по каналам мы будем осуществлять сами. Из представленных команд первого уровня мы будем использовать 0x8n (отпустить ноту), 0x9n (взять ноту), 0xBn (для обращения к сообщения второго уровня, о чем будет далее) и 0xA (сменить силу нажатия клавиши).
События второго уровня. Данные события представляют из себя событие первого уровня 0xBn + номер события (коих порядка сотни) + параметр данного события (если параметра нет, то передается 0).
Команды второго уровня мы использовать не будем. Но мы теперь знаем, как их игнорировать.
События третьего уровня. События третьего уровня представляют из себя 3 события второго. Первыми двумя событиями мы указываем номер нужной команды, а в третей — ее параметр.
Команды третьего уровня мы так же не используем. А метод их игнорирования совпадает с методом игнорирования команд второго уровня (по сути ведь мы игнорируем 3 команды второго).
SysEx-события. Это эксклюзивные сообщения. В MIDI файлах партитур фортепианных произведений (или других классических инструментов) не встречается. При написании программы мы будем считать, что таковых сообщений не существует. Структура сообщения выглядит так.
Теперь, зная о том, какие события существуют мы бы могли приступить к их считыванию, но… В какой момент времени они появляются? А вот тут все обстоит следующим образом. Перед каждым событием первого/второго уровня (третий не рассматриваем, так как мне за все время тестирования нотных произведений еще ни разу не попалось такого MIDI файла) стоит n-е количество байт, описывающих прошедшее с последнего MIDI события время. Если байт данных о времени последний, то его старший байт установлен в 0. Если нет, то 1. Рассмотрим пример.
Флаг установлен в 0 (7-й бит = 0). Следовательно этот байт последний и единственный. Далее, не обращая внимания на старший разряд, смотрим на оставшееся число. Оно равно 0 => событие 0 произошло в нулевую секунду. Теперь рассмотрим событие 1. Тут уже старший байт установлен в 1 => байт не последний. Сохраняем значение оставшееся, если вычеркнуть старший разряд. Получаем 1. Смотрим следующий байт. Там флаг = 0 и оставшаяся часть = 0. Теперь считаем, сколько на самом деле прошло времени. Так как каждый байт может переносить всего лишь по 7 байт информации, то у нас прошло (1 Структура блока
Источник статьи: http://habr.com/ru/post/271693/
Написание MIDI-файла для Synthesia: зачем усложнять, если всё можно сделать в одной программе?
Давно хотел попробовать Синтезию, ибо она своим видом напоминает ритм-игры. Только вместо тапанья по экрану или абстрактных кнопок на геймпаде у меня тут реальная фортепианная клавиатура. На две или четыре октавы, так что есть выбор! Просто после покупки четырёхоктавки ещё не продал свой Корг Наноки 2. XD
Играть по памяти под метроном в ФЛ Студио не особо получалось, а вот опыт ритм-игр показал, что я вполне справляюсь на лёгком уровне сложности. Ещё в ФЛ-ке есть приличная задержка реакции, из-за чего каждый раз сначала приходится тратить немного времени, чтобы к ней привыкнуть. Задержка эта связана больше с железом, но вдруг в Синтезии её не будет.
И вот, пару дней назад вновь вспомнил о ней, скачал и начал разбираться, что это за шайтан-шарманка.
Играть мелодии из стандартного банка не хотелось. Как говорил Копатыч, моё сердце бьётся только в ритме диско. Хотя, в моём случае это больше синт-поп и американ-поп, представленный в плей-листе одной певицей. XD Ну, и всякие рандомные песни.
Решил поискать готовые мид-файлы, чтобы рубануть что-нибудь из электроники 70-х — 80-х. Попробовал скачать что-то, но когда запустил в Cинтезии, то понял, что скачал цельные каверы со множеством инструментов вплоть до ударных. Можно ли играть отдельные партии и отдельные куски рифов, я так и не понял. Так что оказалось проще написать свой МИДИ.
Сначала стал искать в Синтезии редактор. Естественно, не нашёл. Вышел в интернет посмотреть, как это делается, и знатно подохренел: музыканты, помимо Синтезии, используют аж три программы для создания .mid-файла: одну для того, чтобы написать/прочитать партитуру, вторую, чтобы нарисовать по этим нотам табы и экспортировать проект в МИДИ, и третью, чтобы разделить получившееся на дорожки.
Смотрю на эти пляски с бубном, и думаю: йо@#$%^ сарай. окей, эти закорючки я всё равно не различаю ни по форме ни по пространственному положению. Этап написания нот пропускаем. Накатать на слух табулатуру в ФЛ-ке — не проблема. Но вот искать по торрентам ещё какую-то фигню для раскраивания табов на дорожки было влом.
Ладно, надо попробовать сделать хотя бы рабочий МИДИ-файл. Отправился рисовать в ФЛ Студио. Запустил первый попавшийся синтезатор, которым по иронии судьбы оказался Юпитер 8, и, потыкавшись в клавиши, нащупал вступление Journey — Separate Ways. Нарисовал (правда, сильно упростив басовую часть для ускорения процесса), применил МИДИ макрос, экспортировал в МИДИ. Работает! Но всё это записано как для одной руки. А я хотел бы поупражнять каждую руку отдельно, нужно делить на дорожки.
Скачивать дополнительное ПО ради такой фигни всё ещё было лень. Поэтому ещё раз внимательно глянул на структуру треков в Синтезии. Каждая дорожка — отдельный инструмент. И тут возникла гениальная идея: а что, если я клонирую Юпитер 8 и запишу на одном кусок рифов для правой руки, а на другом — бас для левой? Разные ж инструменты типа! Сказано — сделано!
Берегите уши! Бандикам не реагирует на громкость и настройки синтезатора, так что тут отвратительно свистящий голый звук Юпитера 8 и фактически максимальная мощность инпута.
Источник статьи: http://pikabu.ru/story/napisanie_midifayla_dlya_synthesia_zachem_uslozhnyat_esli_vsyo_mozhno_sdelat_v_odnoy_programme_6723120