Нотный редактор Score Perfect больше не выпускается: его единственный разработчик умер в 2012 году. Перенести партитуры в другую программу можно было разве что вручную, нота за нотой. Незадолго до этого я занимался несколькими другими проектами по реверс-инжинирингу, и задача показалась мне интересной.

Подробности о формате файлов я вынес на отдельные страницы: формат SPP 1–4 и формат SPP 5–7.

Из SPP в MusicXML с помощью spp2musicxml

Из SPP в MusicXML с помощью spp2musicxml

С чего всё началось

Score Perfect Professional 7.0

Score Perfect Professional 7.0

Мой отец пользуется этой программой с первой версии: сначала на Atari, потом на Windows. Все его партитуры заперты в формате SPP. И он такой не один: на форуме MuseScore я нашёл несколько тем от людей с той же бедой.

SPP умеет экспортировать в MusicXML, но выгружает только ноты. Аккорды, текст песни и прочее теряются. Есть ещё экспорт в MIDI, но там пропадает ещё больше. Поэтому я поставил себе цель написать конвертер, который читает файлы SPP и выдаёт MusicXML. Я возился с проектом урывками последние четыре года, в сумме вышло несколько месяцев работы.

Версии SPP и их история

Score Perfect Professional 1.0

Score Perfect Professional 1.0

Score Perfect написал Клаус Кляйнбрам (Klaus Kleinbrahm), первая версия вышла для Atari в 1990 году. Программа продавалась в трёх редакциях: Education, Standard и Professional. Обычно под SPP имеют в виду Professional, и в этой статье так же. Третья версия (1995) была доступна и для Windows, возможно, через эмуляцию Atari. Пятая (2007) — переписанная с нуля программа на Visual Basic 6, только для Windows.

Score Perfect Professional Lite 5.1

Score Perfect Professional Lite 5.1

В 2001 году от SPP отпочковался PriMus. И SPP, и PriMus читают файлы, созданные до разделения. В SPP 5 (2007) появился новый формат файлов, который PriMus прочитать уже не мог. Вместе с редакциями Standard и Professional выходила и SPP 5 Lite.

Score Perfect 1.2-K

Score Perfect 1.2-K

Ещё мне попался предшественник SPP 1.0 — Score Perfect 1.2-K (1988). Он на два года старше SPP 1.0 и использует ещё один, отдельный формат файлов.

Дизассемблирование программы

Разобраться в формате можно было двумя путями: дизассемблировать сам Score Perfect и посмотреть, как он работает, или препарировать сохранённые файлы. Здесь речь о первом.

Чтобы анализировать программу или хотя бы видеть, как должна выглядеть партитура, нужна сама программа. Отец отдал мне свои зарегистрированные копии SPP 5.2, 6.0 и 7.0, а несколько демо- и Lite-версий я нашёл в сети. Все общедоступные версии перечислены в разделе загрузок к статье, вместе с тем, как их запустить и откуда они взялись.

Дальше их нужно было запустить под Wine. Версиям на Visual Basic понадобилась целая куча DLL и OCX, часть из них ещё и пришлось регистрировать.

Когда программа заработала, я начал с версии 6.0. Мне попался VBDec — декомпилятор VB6 с открытым кодом. Visual Basic умеет компилировать в P-код, байт-код своей виртуальной машины, а его разобрать сравнительно легко. Увы, SPP 6.0 оказалась скомпилирована в машинный код, так что на руках у меня были только листинги дизассемблера. То же самое со всеми версиями SPP на Visual Basic. Вдобавок бинарники как следует очищены от символов, так что видны были почти исключительно строки и вызовы методов из DLL.

Я слышал, что версии для Atari были написаны на GFA BASIC. GFA BASIC существует и для Windows, и я задумался: может, в Score Perfect сохранилось исходное ядро на GFA BASIC, а Visual Basic служит лишь тонкой обёрткой для интерфейса? Немного покопавшись, я эту гипотезу опроверг: Score Perfect 5 переписан полностью.

Опыт дизассемблирования Windows-программ в Ghidra у меня уже был — со времён реверса EVBox ChargeStation Tool, и здесь он пригодился.

Поискав в исполняемом файле названия длительностей нот из MusicXML (quarter, eighth и так далее), я нашёл функцию экспорта в MusicXML. Ещё немного поисков — и нашёлся диалог открытия файла. Где-то между этими двумя функциями должен был быть код, который работает с партитурой в памяти. После нескольких дней раскопок я отложил этот путь и переключился на формат файлов.

К дизассемблированию я вернулся, когда занимался форматом SPP 1–4.

Демоверсия SPP 4.0 запустилась под Wine без единой проблемы. Это оказалось 16-битное Windows-приложение. Выглядело оно очень похоже на родные программы для Atari, и это укрепило мою догадку, что на самом деле это программа для Atari, работающая в эмуляторе. В дизассемблированном коде нашлись имена процедур в стиле GFA (_P_demo_nicht), и это подтвердило, что программа написана на GFA BASIC.

Ещё один способ изучить формат SPP 1–4 — конвертер son2emil из PriMus. Разбирая его в Ghidra, я подключил Claude Code, чтобы понять, как интерпретируются некоторые из оставшихся тегов.

Реверс сохранённых файлов

В итоге я сосредоточился в основном на сохранённых файлах: в них мне было проще разобраться, чем в дизассемблированном коде на Visual Basic.

В комплекте с Score Perfect шло множество демонстрационных песен, созданных в разных версиях SPP. Вместе с несколькими песнями отца они составили корпус для анализа.

Версии формата

Формат *.SON существует в двух вариантах, если не считать предшественника: SPP 1–4 и SPP 5–7. Граница между форматами 1–4 и 5–7 совпадает с переписыванием SPP на Visual Basic.

Первые шаги с форматом SPP 5–7

Поиск различий между двумя сохранёнными файлами

Поиск различий между двумя сохранёнными файлами

До этого я провёл бессчётные часы, сравнивая дампы последовательного порта, когда превращал управляемую зарядку для электромобиля в автономную. Теперь тот же приём пригодился для формата файлов.

Начал я с SPP 6: создал пустую партитуру и сохранил её. Потом добавил одну ноту, снова сохранил и сравнил различия. Повторяя это раз за разом, постепенно составляешь список известных байтов. А когда знаешь, что искать, можно искать эти байты по всему корпусу. Через несколько дней разглядывания шестнадцатеричных дампов я начал видеть общую структуру формата. Тогда я взялся за скрипт на Python, который его разбирает.

Общая структура

Файл начинается с магического числа, за которым идут блоки: настройки, описания нотных станов, такты, ноты, текстовые объекты, партии и, наконец, настройки MIDI-микшера.


SON file
├── Magic
├── Header
├── Settings
├── Staff definitions
├── Measures
│   ├── Measure 1
│   ├── Measure 2
│   └── ...
├── Notes
│   ├── Measure 1
│   │   ├── Note 1
│   │   ├── Note 2
│   │   └── ...
│   ├── Measure 2
│   │   ├── Note 1
│   │   ├── Note 2
│   │   └── ...
│   └── ...
├── Text objects
├── Parts
└── MIDI settings

У одних блоков фиксированная длина или длина указана в начале. Другие узнаются по своему магическому числу. У остальных нет ни того, ни другого, и чтобы понять, где такой блок кончается, приходится разбирать его содержимое. Например, длина блока тактов записана количеством тактов, и каждый такт при этом переменной длины.

Большинство блоков было уже в SPP 5.0, но некоторые добавились в более поздних версиях.

Теги

Любопытная особенность блоков тактов и нот: для каждого такта (номер такта, число нот и ширина на экране) или ноты (номер ноты, высота, длительность и положение по горизонтали) в них лежит фиксированный набор данных, а за ним ноль или больше необязательных тегов. У каждого тега одна функция и предсказуемая длина. Я составил список встреченных тегов: смена ключа, смена тональности, смена размера, вольты, тактовые черты и несколько тегов, назначение которых до сих пор неизвестно.

Одни теги могут стоять и у нот, и у тактов, другие относятся только к одному типу.

Большинство тегов было уже в SPP 5.0, но в поздних версиях появились дополнительные.

Нот бывает два основных типа. Короткий вмещает только одну четвертную, восьмую или шестнадцатую ноту. Длинный вмещает любое количество нот любой длительности.

И это лишь верхушка айсберга.

Эту структуру я размечал вручную, составляя список блоков и возможных тегов. Каждый раз, когда мне казалось, что я нашёл новый тег, я прогонял весь корпус и проверял, держится ли закономерность.

Дальнейший анализ

Я снова взялся за проект после того, как написал новую прошивку для дешёвой релейной платы, — на ней я впервые попробовал Claude Code. В этом проекте я тоже его использовал, на этот раз для анализа своей документации и корпуса файлов. Анализ помог найти и разметить недостающие теги, поля и блоки — работу, которую раньше я делал руками. Ещё я поручал Claude Code статистику: какие теги встречаются и при каких условиях.

Особенно полезной оказалась демонстрационная песня Objektarten («типы объектов»). В ней показан каждый вид объектов, которые поддерживает SPP, и рядом подписано его название. Она стала моим эталоном для тактовых черт, динамики, штрихов, арпеджио, глиссандо, октавных знаков и репетиционных обозначений.

Полные версии SPP не принимали (действующие) лицензионные ключи. Пользоваться ими всё же было можно, если игнорировать всплывающее окно. Мышь была заблокирована, но клавиатурные команды работали.

Раньше я вручную создавал множество тестовых файлов, каждый с небольшими отличиями, чтобы найти, где в формате хранятся конкретные данные. На этот раз я использовал сгенерированные ИИ скрипты автоматизации для SPP под Wine. Пришлось поэкспериментировать, чтобы всё заработало, но потом я мог скриптом нажимать на экранную нотную клавиатуру и все кнопки и генерировать намного больше тестовых файлов. Их я тоже сравнивал, и дело пошло быстрее.

Был и ещё один способ узнать, как хранятся данные: создать файлы MusicXML с нужными мне элементами, импортировать их в SPP, сохранить в формате SPP и разобрать результат. К сожалению, в SPP 5 импорт не работал вообще. SPP 6 и 7 импортировали только ноты.

В какой-то момент я знал большинство тегов, но не всегда понимал, что каждый из них делает. Чтобы выяснить, я делал снимок экрана с исходной партитурой в SPP, добавлял или удалял один тег, делал ещё один снимок и сравнивал их. Можно было также менять параметры тегов и смотреть, к чему приводят конкретные значения. Например, я сделал шесть вариантов одной и той же песни с разными ключами, чтобы понять, какое значение соответствует какому ключу.

Формат SPP 1–4

Чтобы изучить формат SPP 1–4, я установил несколько демоверсий SPP 1–4, в которых было намного больше песен. Все новые песни я пересохранил в SPP 5.1 с помощью той же автоматизации. Корпус заметно вырос, и это позволило разобрать ещё часть формата 5–7.

Те же файлы я экспортировал и через конвертер son2emil из PriMus. Сравнение трёх версий одного и того же файла помогло разобраться в старом формате.

Формат 1–4 очень похож на 5–7. Поскольку он родом с Atari, значения в нём записаны в порядке big-endian, а строки в основном в кодировке Atari. Но устроены структуры более-менее так же, как в формате 5–7. Высота звука нумеруется одинаково, поэтому эту часть файлов было легко найти.

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

Конвертер spp2musicxml

Конвертер spp2musicxml

Конвертер spp2musicxml

Итог всей этой работы, не считая документации, — конвертер spp2musicxml. Это полностью переписанная версия кода, который я писал в процессе реверса. У конвертера открытый код, так что любой может посмотреть, как он работает, и помочь его улучшить.

Требования скромные: нужен достаточно свежий Python (3.11 или новее). Приложение упаковано в PYZ-файл, его можно просто запустить. Оно проверено на Windows и Linux и должно работать на macOS. Есть режимы командной строки и графического интерфейса. Интерфейс сделан на tkinter, который обычно идёт в комплекте с Python.

Конвертер поддерживает файлы из SPP 1–7. Старый формат Score Perfect 1.2-K не поддерживается.

Перенести удаётся не всю информацию о вёрстке, потому что MusicXML поддерживает не всё. Неизвестные значения игнорируются и не попадают в результат. Среди них, например, некоторые варианты вилок крещендо и диминуэндо.

Результат конвертера проверяется по схеме MusicXML 4.0, и все песни из корпуса открываются в MuseScore 4.

Открытый код

Всё в этом проекте по возможности делалось на свободном ПО с открытым кодом.

Поэтому программа, статья и документация, созданные для этого проекта, переданы в общественное достояние (CC0). Соответствующие файлы лежат в разделе загрузок к статье.

Это не касается того, что сделал не я: программ Score Perfect и PriMus и их демонстрационных песен. Их раздавать не мне.

Напоследок

Не все части форматов поняты до конца, но большинство оставшихся неизвестных касается вёрстки, которую MusicXML всё равно не умеет представить.

Формат Score Perfect 1.2-K только кратко описан на странице формата, конвертер его не поддерживает. Реального сценария, ради которого стоило бы копать его глубже, я не вижу.

Поддержку формата SPP можно было бы добавить и в импорт MuseScore.

Удачи с переносом всех ваших файлов Score Perfect в MusicXML!