Blog. Just Blog

Парсинг метаданных MIDI-файлов на Ассемблере

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
Парсинг метаданных MIDI-файлов на Ассемблере
Парсинг метаданных MIDI-файлов на Ассемблере

MIDI - "цифровой интерфейс музыкальных инструментов" - стандарт цифровой звукозаписи на формат обмена данными между электронными музыкальными инструментами. В отличие от других форматов это не оцифрованный звук, а наборы команд (проигрываемые ноты, ссылки на проигрываемые инструменты, значения изменяемых параметров звука), которые могут воспроизводиться по-разному в зависимости от устройства воспроизведения. Это позволяет реализовывать устройства, производящие аранжировку по заданным аккордам, а также приложения для воспроизведения и визуализации звука.

Согласно спецификации, MIDI-файл должен начинаться с заголовка. Его можно определить по первым 4 байтам сигнатуры "MThd", за которой следует DWORD с размером данных заголовка и сами данные. Несмотря на то, что в теории заголовок предполагает динамический размер, во всех без исключения MIDI-файлах размер данных в заголовке всегда равен 6 байтам, а размер самого заголовка, соответственно, равен 14 байтам. На этом с заголовком можно закончить.

Дальше следуют один или несколько контейнеров, в которых содержатся данные об инструментах, нотах, событиях, управляющие команды, текстовая информация и т.п. Каждый контейнер начинается с сигнатуры "MTrk", после которой следует DWORD с размером данных. В контейнере содержатся блоки в формате WORD маркера, BYTE типа данных, BYTE размера данных и сами данные. К примеру, блок с произвольной текстовой информацией должен начинаться с последовательности "00 FF 01", информация об авторских правах - "00 FF 02", а название трека или инструмента - "00 FF 03". Вроде бы есть определенная структура. Но неоднозначность толкования стандарта MIDI породила полный разброд и шатание. Каждый программный продукт, создающий MIDI-файлы, записывает в них метаданные по своему усмотрению. У меня есть небольшая коллекция MIDI-файлов на пару сотен экземпляров, и на каждое мое предположение, что "вот так точно получится", обязательно находился один или несколько файлов, которые в эту теорию не вписывались. То есть метаданные запросто могли находиться не в первом контейнере, названия могли быть записаны в блоки с маркерами "00 FF 01", "00 FF 02" или "00 FF 03", причем сразу несколько и в произвольной последовательности, под теми же маркерами могли идти копирайты, названия инструментов, слова для караоке, да вообще что угодно.

Метаданные в файле
Метаданные в файле

Метаданные в файле
Метаданные в файле

Короче, мне так и не удалось выяснить алгоритм, который с помощью которого можно однозначно и гарантированно определить, где именно содержится название композиции. Если у вас есть такая информация, то большая просьба поделиться. По итогу я решил поочередно перебирать контейнеры, в каждом из них искать первый блок с любым из трех перечисленных маркеров и принимать его текстовое содержимое в качестве названия трека. Решение спорное, не дающее 100% гарантии, но другого, как я уже написал выше, у меня нет. Для большинства случаев оно дает более-менее вменяемый результат. Код парсера будет примерно следующим:
  1.         ; Прочитать начало файла
  2.         invoke  _lopen,fname,OF_READ
  3.         mov     [desc],eax
  4.         invoke  _lread,[desc],buff,8
  5.  
  6.         ; Проверка на соответствие формата
  7.         cmp     dword[buff],'MThd'
  8.         ; Неизвестный формат файла
  9.         jne     .loc_exit
  10.  
  11.         ; Перейти к первому контейнеру
  12.         mov     eax,dword[buff+4]
  13.         bswap   eax
  14.         invoke  _llseek,[desc],eax,FILE_CURRENT
  15. .loc_read:
  16.         invoke  _lread,[desc],buff,8
  17.         cmp     eax,8
  18.         jne     .loc_close
  19.         ; Это контейнер с данными?
  20.         cmp     dword[buff],'MTrk'
  21.         jne     .loc_exit
  22.  
  23.         ; Размер метаданных
  24.         mov     ebx,dword[buff+4]
  25.         bswap   ebx
  26.         mov     eax,ebx
  27.         add     eax,10
  28.  
  29.         ; Выделить память под метаданные
  30.         invoke  GlobalAlloc,GMEM_ZEROINIT,eax
  31.         mov     [hMem],eax
  32.         invoke  GlobalLock,[hMem]
  33.         mov     [pMem],eax
  34.  
  35.         ; Прочитать метаданные
  36.         invoke  _lread,[desc],[pMem],ebx
  37.         ; Указатель на начало контейнера
  38.         mov     esi,[pMem]
  39. .loc_scan:
  40.         ; Маркер блока
  41.         lodsw
  42.         cmp     ax,0FF00h
  43.         jne     .loc_next
  44.  
  45.         ; Идентификатор блока
  46.         lodsb
  47.         cmp     al,1
  48.         je      .loc_metadata
  49.         cmp     al,2
  50.         je      .loc_metadata
  51.         cmp     al,3
  52.         je      .loc_metadata
  53.  
  54.         ; Размер блока
  55.         lodsb
  56.         movzx   eax,al
  57.         add     esi,eax
  58.         jmp     .loc_scan
  59.  
  60. .loc_next:
  61.         ; Освободить память
  62.         invoke  GlobalUnlock,[hMem]
  63.  
  64.         ; Перейти к следующему контейнеру
  65.         mov     eax,dword[buff+4]
  66.         bswap   eax
  67.         invoke  _llseek,[desc],eax,FILE_CURRENT
  68.         jmp     .loc_read
  69.  
  70. .loc_metadata:
  71.         lodsb
  72.         movzx   ecx,al
  73.         mov     edi,title
  74.         rep     movsb
  75.  
  76.         ; Освободить память
  77.         invoke  GlobalUnlock,[hMem]
  78.  
  79.         ; title -> какие-то метаданные
В приложении пример программы с исходным текстом, которая извлекает метаданные из MIDI-файла и выводит их на экран.

Пример программы с исходным текстом (FASM)Пример программы с исходным текстом (FASM)

MIDI.Metadata.Demo.zip (4,485 bytes)


Поделиться ссылкой ВКонтакте
Просмотров: 318 | Комментариев: 6

Комментарии

Отзывы посетителей сайта о статье
ManHunter (07.11.2024 в 17:49):
В спецификации предельно четко:
Цитатаstruct mid_track {
unsigned int id; // identifier "MTrk"
unsigned int length; // track length, big-endian
};

Про ноль в предыдущем комментарии Лестер Глючный все написал.
voffka (07.11.2024 в 17:46):
Мне кажется у тебя сдвиг с самого начала неправильный MTrk нужно не как 4 символа рассматривать, а как 5, с завершающим нулём, тогда дальше всё как по маслу пойдёт DWORD размер MTrk, BYTE FF, BYTE тип, BYTE размер + 1 завершающий 0 нужно учесть.
FF - Означает мета-событие не отправляемое в миди порт
типы:
00 - Sequence Number
01 - Text Event
02 - Copyright Notice
03 - Track Name
04 - Instrument Name
05 - Lyrics
06 - Marker
07 - Cue Point
ManHunter (06.11.2024 в 21:22):
Цитата00 перед FF 01/02/03 — расстояние между событиями = 0 ticks, а если их размещать не в начале?

Значит это будет какая-то очередная нестандартная ситуация, которая не вписывается в схему и не будет обрабатываться. Увы.
Лестер Глючный (06.11.2024 в 21:07):
Только хотелось было написать про высчитывание длительности у того "злощастного" 93.mid (дата его изменения аж за апрель 2003 года), что "роняет" последний 32-разрядный Sekaiju, (а kuzu (разработчик) отказывается поддерживать 32-разрядную, мол для TQPN выше 960 не хватит 32 бит), а он засветился на снимке экрана :))
Во второй дорожке (MTrk) Delta-time до "End Of Track" больше 4 байт (8FFFFFFD00)= 4294966912, некоторые программы (в т.ч. mf2tXP) определяют это как -384 (отрицательная позиция)…
Ищу такой MIDI-секвенсор, где б спецификация Standard MIDI File поддерживалась полностью, видеть/редактировать SysEx`ы размером выше 4 килобайт, и особенно Sequencer Specific (а то Tyros/PSR-S* в егонное Meta-событие «Keyboard Voice» аж 400 байт вписывает)… если существует такой хотя бы под DOS, то ещё лучше… Пока что только Cubase из двух-трёх десятков DAW это умеет, и то старые версии, а то начиная с 7-й ввели уже далеко не полную на тот момент поддержку ямаховских XF-аккордов (до сих пор не знает 24 «недокументируемых» шт.)
Cakewalk/SONAR, Reaper, DP, nTrack, Mixtrack и ещё мн.др (а FL, S1, Ableton/Bitwig/Polyform и мн.др. где даже SysEx`ов нет — подавно) обходят эти XF и пр. SMF Sequencer specific Meta-события стороной при иморте таких файлов (соответственно, теряются навсегда). Кстати, XF-данные могут находиться как отдельно от .MID, так и внутри отдельным chunk`ом после всех имеющихся MTrk, хотя, вроде ничто не запрещает использовать "не-MTrk" chunk`и сразу же после MThd… Да и вообще, для меня "чистые MIDI-данные" хранятся только в MTrk, и MThd указывает лишь только "номер типа SMF", кол-во MIDI-дорожек, и разрешение "Ticks Per Quarter Note", наподобие «Sampling Rage» (опечатка понравилась), благодаря которому и выясняется точная продолжительность MIDI-файла (за счёт Delta-Time перед каждым MIDI-событием, и учитывания каждой смены темпа FF5103######).
Ах, да… 00 перед FF 01/02/03 — расстояние между событиями = 0 ticks, а если их размещать не в начале?
ManHunter (05.11.2024 в 19:11):
Зато в Win7 midi-файлы прямо в точности по стандарту, смотреть приятно.
Rustamer (05.11.2024 в 19:09):
Хотел проверить на MIDI-файлах из Win95, но вдруг увидел, что там собственный RMI-формат MIDI от MS.. Зато вспомнил мелодии, с которых начиналось мое знакомство с классической музыкой.

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2024
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.07 сек. / MySQL: 2 (0.004 сек.) / Память: 4.5 Mb
Наверх