О midi-файлах и о PHP, как инструменте повседневного программирования на живом примере

PHP*

Предисловие


Очень часто сталкиваюсь с тем, что многие не воспринимают PHP ни как иначе, чем средство для разработки сайтов, но мне хочется разубедить их этим топиком.

Сегодня мне позвонили и попросили помочь с синтезатором фирмы YAMAHA (в частности с аналогом модели 403), который имеет одну интересную особенность — он позволяет сохранять 5 сочиненных песен в своей памяти, но вот достать их на компьютер в какой либо форме, кроме как файла резервной копии нельзя, о чем говорится на всех форумах и в технической документации к этому синтезатору.


Формат .bup используется синтезатором для сохранения всех настроек и 5 записанных песен.

Все советы по записи этих мелодий сводятся к тому, что бы записать музыку с синтезатора в рельаном времени, но ведь это не выход.

Итак начнем



В моих руках оказался файл с именем 08PK61.BUP. Первый взгляд на него не вызвал никаких положительных эмоций:
06PK61 BackFile
Гласил ascii-заголовок файла. Гугл тоже не дал никаких положительных результатов.
Но тут я вспомнил, что синтезаторы используют формат midi для музыки, и почему бы разработчикам не хранить в backup-файле стандартный midi-поток?
«Но как я его найду?!» пронеслось в моей голове. А все оказалось легко.
Как выяснилось, стандартный заголовок midi-файла выглядит так:

4D 54 68 64 00 00 00 06 00 00 00 01 00 60

Для упрощенного понимания я разбил заголовок на 5 частей:
  • Mthd — заголовок, именно с него начинается midi-файл
  • 00 00 00 06 — длинна данных за этим блоком(в байтах)
  • 00 00 — тип файла(3 вида)
  • 00 01 — количество MTrk блоков
  • 00 60 — темп


Ну это я вычитал в Википедии, и тут же решил вбить MThd в поиск по этому файлу. Тут меня ждало разочарование — этой последовательности байт в файле не оказалось. У меня внось опустились руки.

От нечего делать решил дочитать статью до конца(и понял, что это надо делать всегда) и увидел там про записи MTrk(о них кратко):
Начинается с последовательности
4D 54 72 6B
Дельше идут данные и блок заканчивается последовательностью
FF 2F 00

Начинаю поиск по последовательности 4D 54 72 6B, и о чудо, я нашел целых три таких записи.

Дальше дело оставалось за малым:
Необходимо скопировать блок MTrk заканчивающийся последовательностью FF 2F 00 и дополнив вначало стандартным заголовком MThd записать все это в файл.
Сказано — сделано.
Сохранив результат в mid файл и открыв его в проигрователе я услышал записанную музыку

А теперь о программировании



Ну если нужно выдрать одну песню из такого файла — то ладно, можно было бы и закончить, но если есть необходимость обрабатывать несколько файлов подобным образом, что тогда? А вот тогда на помощь и приходит автоматизация.

Все действия, которые мы должны выполнить над испытуемым мы выяснили, теперь соберем их воедино и воплотим в жизнь, т.е. в код:

  1. Открыть файл
  2. Считать файл
  3. Перевсти полученный результат в HEX
  4. Найти совпадения с MTrk-заголовком, если таковые имеются продолжить, иначе — выход
  5. Найти совпадение с FF 2F 00
  6. Полученными индексами скопировать подстроку в переменную
  7. Создать выходной файл
  8. Записать в него заголовок MThd и переменную с блоком MTrk, обработанных функцией HEX2BIN
  9. Сохранить, закрыть файл
  10. Вернуться к п.4


Готовый php-код:
  1. <?php
  2. function hex2bin($h) // функция, которой по неизвестной причине нет в php
  3. {
  4.     if (!is_string($h)) return null;
  5.     $r='';
  6.     for ($a=0; $a<strlen($h); $a+=2) { $r.=chr(hexdec($h{$a}.$h{($a+1)})); }
  7.     return $r;
  8. }
  9.  
  10. $lp = 0;
  11. $i = 0;
  12. $fh = fopen('test.bup', "r") or die("Can't open file!"); //открываем файл
  13. $file = fread($fh, filesize('test.bup')); //читаем
  14. $bin = bin2hex($file); //переводим в hex
  15.  
  16. while(strpos($bin,'4d54726b',$lp)!==false) //рабочий цикл
  17. {
  18.     $fp=strpos($bin,'4d54726b',$lp);
  19.     $lp=strpos($bin,'ff2f',$fp);
  20.  
  21.     $getss = substr($bin, $fp,$lp-$fp+6);
  22.  
  23.     $fm = fopen("mid$i.mid","w");
  24.     fwrite($fm, hex2bin('4d54686400000006000000010060'.$getss)); //запись в файл заголовка MThd и MTrk
  25.     fclose($fm);
  26.     $i++;
  27. }
  28. fclose($fh);
  29. ?>


Вот так вот все легко ;)

UPD. По совету неизвестного мне(кроме ника eDogs) пользователя добавлю по поводу функции конвертации в бинарный вид. Вместо нее нее можно использовать стандартную функцию PHP:
string pack ( string $format [, mixed $args [, mixed $... ]] ) )
+31
22 мая 2010, 12:04
17
Gilberg 2,0

комментарии (45)

+4
AHDPEu #
Осталось это оформить в виде консольной программы и пользоваться :)
Тоже часто на php делаю повседневные задачи. Вот всегда знал, что программирование мне пригодится.
+1
Veliant #
РНР довольно удобен в повседневных задачах, в свое время делал дизасм для x86, nes, редактор прошивок для своего телефона, 3d… если надо, потом уже релизил проги на С++, а вот для изучения каких либо файлов, для меня РНР самое то, оч удобно
+6
chemikadze #
По-моему Python для прототипирования и мелких скриптов «для себя» более удобен.
НЛО прилетело и опубликовало эту надпись здесь
+1
Arrest #
Как минимум тем, что не надо окружать код <?php ?> :)
–17
Arrest #
И, да, программу на python можно вызывать как $ ./code, а вот программу на php нужно вызывать как $ php ./code, потому что sh ищет #! в первых символах первой строки, а искать после <? он не догадывается. Маленькое различие, но глаза мозолит.
НЛО прилетело и опубликовало эту надпись здесь
+2
ukko #
не выдумывайте себе проблемы там где их нет :)

Это в файл file.php
#!/usr/bin/php
<?php
echo "Мир! Труд! Май!";


Затем делаем файл исполняемым
chmod +x file.php


И… вуаля…
./file.php
+9
sylvio #
input = bytearray(open('test.bup', 'rb').read())
start = end = fileno = 0
while True:
    start = input.find(bytearray([0x4d, 0x54, ...]), start)
    if start == -1:
        break
    end = input.find(bytearray([0xff, 0x2f]), end)
    open('mid%i.mid' % fno, 'wb').write(bytearray([0x4d, 0x54, ...]) + input[start:end])
    fno += 1
–3
alexey_uzhva #
Copy Source | Copy HTML
  1. # Выносим и именуем константы, чтобы в программе не было "магических чисел"
  2. START_MARK = "\0x4d\0x54..."
  3. END_MARK = "\0xff\0x2f...."
  4. FILE_FINALIZE = "\0x4d\0x54\0x68\0x64..."
  5.  
  6. # Читаем данные, инициализируем счетчик
  7. input = open('test.bup', 'rb').read()
  8. fileno =  0
  9.  
  10. # Пока в input есть нужный нам маркер...
  11. while START_MARK in input:
  12.     # Определяем границы песни
  13.     start = input.find(START_MARK)
  14.     end = input.find(END_MARK)
  15.  
  16.     # Записываем в файл
  17.     open('mid%i.mid' % fileno, 'wb').write(input[start:end]+FILE_FINALIZE)
  18.  
  19.     # Сдвигаем input на конец записанной песни...
  20.     input = input[end:]
  21.     fileno += 1


По сравнению с исходным PHP файлом, пример на Python:

1) 11 строчек вместо 25 — почти в 2.5 раза лаконичней
2) Нет никаких «забытых разработчиками функций» — в PHP же такая ситуация очень типична и встречается постоянно, если вы отходите в сторону от прямого назначения языка — т.е. от Web-фич.
3) Визуально код чище благодаря отсутствию громадного количества служебных символов, служебной пунктуации и прочего мусора, который облегчает жизнь транслятору, но зумусоривает мозг программисту.
НЛО прилетело и опубликовало эту надпись здесь
+1
alexey_uzhva #
Я не претендую на объективность и не претендую на истинность, а высказываю лишь свое субъективное мнение, с которым вы можете быть согласны, а можете быть не согласны. Я не хочу никого переубеждать, лишь хочу обратить внимание на то, что, на мой взгляд, существуют более простые решения для подобного рода задач.

Чем вообще могут отличаться два разных языка программирования: набором предоставляемых функций, господствующей парадигмой и синтаксисом. Правильно?

Набор функций PHP и Python различен в пользу второго, но в данном примере нас это не сильно волнует, т.к. оба набора достаточны для реализации. Будем считать, что применительно к данной задаче, функционал языка эквивалентен (хотя опять же, в общем случае, это далеко не так).

Что касается синтаксиса, то по моему личному мнению синтаксис Python более чист – нет замусоривающих взгляд конструкций и излишней пунктуации. Вообще здесь, конечно, вы правы – надо рассматривать случай, когда оба куска написаны хорошо. Я буду рад, если вы представите хорошо написанный вариант указанного кода и мы сравним полученное.

Что касается семантики и господствующей парадигмы – в PHP это, в основном, структурное программирование. Да, классы там есть, а в 5.3 и старше – они сделаны на достаточно высоком уровне чтобы ими можно было комфортно и беспроблемно пользоваться. Но стандартная библиотека по-прежнему структурно-ориентирована. Массивы, строки – все это простые типы данных, а не объектов. Для манипуляций с ними нужны внешние, относительно объекта, функции, принимающие ссылку на данные в качестве своего аргумента.

Например strpos(ссылка_на_строку, ссылка_на_подстроку) vs строка.find(подстрока) – казалось бы «какая разница»? ИМХО разница есть и она значительна. Разница в том, что, например, функции по работе со строками предоставляют множество модулей – ядро, mbstring, iconv. С массивами точно так же существует 4 различных сортировки, которые реализованы независимо друг от друга и обладают своими плюсами и минусами. Каждый раз чтобы произвести какую-то операцию, нам нужно осуществить подбор нужной функции, и вспомнить ее особенности. А особенности могут быть весьма нетривиальны: в одном модуле функции принимают данные в первом параметре, в другом – в последнем. Где-то тип возвращаемых данных зависит от передаваемых в функцию флагов. Т.е. в стандартной библиотеке творится достаточно приличный разброд.

Та же функция pack – чтобы найти ее в мануале – нужно достаточно потрудиться, и, видимо, автор ее не нашел потому, что он увидел функцию hexdec и ему в голову не пришло, что аналогичная hexdec-у функция может быть реализована через pack. Да, те, кто по-опытней вспомнят аналогичную фичу C++ и найдут ее там, но вопрос в том, сколько времени они потратят на прототипирование при этом?

ИМХО в Python данная проблема решена лучше, т.к. там, в отличие от PHP, существуют очень четкие Code Conventions, которые обязаны соблюдаться всеми разработчиками. Без их соблюдения код не принимают, даже если он чист, отлажен и идеально быстр. Благодаря этому достигается полное единообразие всей стандартной библиотеки и большого количества сторонних модулей, которые придерживаются этого правила, что существенно сокращает время на обучение новым модулям и разработку.

Фактически это дает возможность программисту не думать о модулях, а думать об алгоритме. А не этого ли мы ждем от хорошего ЯП, господа? :-)
НЛО прилетело и опубликовало эту надпись здесь
0
alexey_uzhva #
> Кто о чем, а alexey_uzhva о питоне.

Ну мы же про него начали:) Не я начал, я лишь поддерживаю разговор;)

Функции типа [:], in, List Comprehensions — это из разряда того, что единожды учится при изучении нового языка. Такие фичи есть в любом языке — что те же доллары перед переменной, что === и тому подобные конструкции.

Конечно, идеально чтобы их было бы по-меньше, но одно дело изучить какой-то небольшой объем вначале и дальше работать свободно, другое дело — когда процесс обучения нужен постоянно. Из ряда языков, на которых мне приходится работать — C#, Java, Python, PHP, C++ (Qt) — в PHP эта проблема выражена ярче всего (в C тоже много такого, но там Qt спасает). Т.е. в процессе работы на PHP надо постоянно следить за порядком параметров, искать функции с далеко не очевидными названиями и следить даже за их написанием, т.к. часть из них написана без "_" в имени, а часть с ним.

> Я замечу, ни в pyqt4 ни в opencv эта проблема не решена и решается она как б-г на душу положит.

Да, тут вы правы. Но тут скорей проблема в том, что и Qt и Python написаны по четким Code Conventions и написаны хорошо и качественно. Но эти конвенции, увы, не сходятся. И потому местами получается ядреный микс, от которого может потянуть на тошноту. Для этого приходится перетягивать GUI в отдельный модуль или пакет.

С другой стороны неизвестно что было бы лучше — оставить чистый код, но заставить людей изучать Qt дважды (с оригинальным написание и без), либо попустится чистотой. Для такой большой библиотеки, возможно, их решение оптимально т.к. позволяет использовать те тысячи готовых в сети примеров на C++ практически без портирования. А вот более мелкие библиотеки надо было бы писать в «питонячьем» синтаксисе.
НЛО прилетело и опубликовало эту надпись здесь
+4
chemikadze #
Ничего личного, чисто субъективное ощущение =) Синтаксис более компактный, куча фишек вроде генераторов и проч., биндинги к Qt и gtk (это к прототипированию) и наконец на нем просто приятно писать =)
+2
VolCh #
Удобен, как правило, тот язык, который лучше знаешь, даже если понимаешь, что тот, которого не знаешь или знаешь плохо для этой задачи объективно лучше :)

Изучаю питон сейчас «для себя», но когда встаёт реальная задача, например, перекодировать пару тысяч файлов «вчера», то мне в разы, а может и на порядки, быстрее её решить на php, поскольку после составления алгоритма останется только «тупое кодирования» с редким подглядыванием функций в мануале (помню что есть такая, но не помню как называется), а на питоне буду лезть в доки после каждого слова (не настолько, конечно, но всё же), а потом пару часов отлавливать какой-нить баг, скажем, из-за того, что в срез [0:5] не входит 6-й символ.

Собственно трудность изучения новых языков «за свой счёт» (а не работодателя :) ) заключается в том, что пока язык не знаешь (прежде всего библиотеки и типовые приёмы; синтаксис в императивных языках, как правило, изучить трудности не представляет), реальные задачи будешь решать очень долго — то будешь тратить время на поиски несуществующей функции/метода, то изобретатать велосипед, реализованный в стандартной библиотеке (о сторонних библиотеках/фреймворках и оптимизации кода вообще не заикаюсь даже). Чтобы решать реальные задачи (с реальными дедлайнами) быстро, нужен опыт, которого на учебных задачах не получишь — замкнутый круг :( Думаю лучший способ — в свободное время переписывать на «новый» язык, проекты уже реализованные на «старом», но, блин, до чего это скучно — чаще всего переписываешь код чуть ли не строка в строку, макрос, кажется, лучше бы справился.

P.S. Упс, что-то пива перепил, многабукв вышло
0
chemikadze #
Но разве появившаяся возможнасть написать «боевой» скриптец не отличный способ выучить язык? ;)
0
VolCh #
Грубо говоря, задание даётся на определённое количества часов/дней, рассчитанного на средний, как минимум, уровень владения, языком (каким, работодателю часто не важно, если он не «спонсирует» изучение нового языка) и просто не успеешь сделать к дедлайну (со всеми вытекающими), если язык только начал изучать и практики нет. Так что остаётся в свободное время переписывать уже готовое на новый язык чисто для получения практики кодирования на нём. Как вариант — сделать с нуля свой проект, типа стартапа (если есть идеи), но скорее всего этот проект уйдёт в «корзину», максимум для портфолио оставить можно будет (главное чтоб не стыдно было код показывать :) ), т. к. с плохой идей всё равно уйдёт в треш, хоть на каком языке пиши, а с хорошей пока напишешь на малознакомом языке, то нишу уже, скорее всего, займут.
+1
chemikadze #
Понятно, что делать заказ на незнакомом языке никто в здравом уме не станет. Я имел в виду все те же самые пресловутые «скрипт для себя»: с одной стороны это уже лучше, чем отвлеченные от реальности задачки, с другой — на горизонте не маячит дедлайн.
0
Veliant #
Как тут сказали, что лучше знаешь на том и удобней, питон я только недавно начал изучать, и соглашусь довольно гибок, даже при том же чтении из файла.
0
NightWriter #
Перенесите в PHP, кармы хватает
0
Gilberg #
Сделано
+1
mephisto #
А не долго преобразовывать весь файл в хекс а потом уже искать?
Ну хотя миди файлы маленькие конечно…
0
Stalker_RED #
а кто мешает искать не в хексе, а в бинарнике?

Note: This function is binary-safe.
© http://php.net/manual/en/function.strpos.php

как-то так
$header = base_convert(4d54726b, 16, 2);
strpos($file_content, $header);
+1
mephisto #
Да вот я собственно об этом и говорил, зачем искать хексом, когда можно бином?
0
seriyPS #
Опять же можно в потоке искать а не грузить весь файл в память. Но midi маленькие, да, так что ниче страшного
–18
GHS #
Кто-то еще кодит на пхп? Казалось, что все давно перешли на лисп
+3
Levsha100 #
Извращенство… Снимаю шляпу =)
+9
Gilberg #
Извращенство — это когда на официальном сайте предлагают записать через аудио-карту, а потом уже на компьютере перегнать в миди
+2
Stalker_RED #
зото из этого можно сразу сделать онлайн-сервис
+1
ilgub #
Мне кажется, что вообще привязывать полноценные языки к какой-то конкретной области неправильно. Я тоже использую perl как для веба, так и для повседневных задач: от обработки данных до сканирования сети на предмет SNMP-агентов, например. Всё ведь зависит от привычек и предпочтений.
+2
zxmd #
вот из-за таких строк как 2-8 я и стал зоофилом — питонопоклонником :)
+5
GHS #
А всегда знал, что питонисты — извращенцы
+1
shuvalov #
Зачем вы в hex и обратно гоняете?? Я бы еще мог понять, если бы Вы хранили как байты и искали вхождеия в массив…
Да, а почему file_get_contents не используем, что бы кода побольше написать?
0
Gilberg #
Прочитав комментарии — я понял свой косяк. В ближайшее время код доработаю, и он действительно станет очень маленьким
+1
docomo #
Сделайте сайт как сервис, позволяющий на входной бэкап-файл получить на выходе midi-файлы. Думаю, ямаховцы будут вам благодарны :)
0
Gilberg #
Идея хорошая, но вопрос — на сколько востребованная?
НЛО прилетело и опубликовало эту надпись здесь
–3
highw #
Изврат:(
+1
siasia #
hex2bin — PHP-программисты такие велосипедисты. А функция unpack не?
0
siasia #
Упс, не заметил UPD. И да. Всё-таки это pack.
НЛО прилетело и опубликовало эту надпись здесь
0
xaxaTyH #
А вот вопрос в тему: есть некоторые ФТП сервера.
Каким образом я могу прочитать с помощью PHP заголовки этих файлов (необходимо для поиска подобных).
0
smileonl #
PHP 5.4.0 alpha — 1 Added hex2bin() function.

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